use std::env::consts;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use crate::build::CApiConfig;
use anyhow::*;
use cargo::core::compiler::CompileTarget;
use cargo_platform::Cfg;
#[derive(Clone, Debug)]
pub struct Target {
pub is_target_overridden: bool,
pub arch: String,
pub os: String,
pub env: String,
pub target: Option<CompileTarget>,
pub cfg: Vec<Cfg>,
}
impl Target {
pub fn new<T: AsRef<std::ffi::OsStr> + AsRef<str>>(
target: Option<T>,
is_target_overridden: bool,
) -> Result<Self> {
let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".into());
let mut cmd = std::process::Command::new(rustc);
let target = target.as_ref();
cmd.arg("--print").arg("cfg");
if let Some(target) = target {
cmd.arg("--target").arg(target);
}
let out = cmd.output()?;
if out.status.success() {
fn match_re(re: regex::Regex, s: &str) -> String {
re.captures(s)
.map_or("", |cap| cap.get(1).unwrap().as_str())
.to_owned()
}
let arch_re = regex::Regex::new(r#"target_arch="(.+)""#).unwrap();
let os_re = regex::Regex::new(r#"target_os="(.+)""#).unwrap();
let env_re = regex::Regex::new(r#"target_env="(.+)""#).unwrap();
let s = std::str::from_utf8(&out.stdout).unwrap();
let lines = s.lines();
let cfg = lines
.map(|line| Ok(Cfg::from_str(line)?))
.collect::<Result<Vec<_>>>()
.with_context(|| {
format!(
"failed to parse the cfg from `rustc --print=cfg`, got:\n{}",
s
)
})?;
Ok(Target {
arch: match_re(arch_re, s),
os: match_re(os_re, s),
env: match_re(env_re, s),
is_target_overridden,
target: target
.map(|t| CompileTarget::new(t.as_ref(), false))
.transpose()?,
cfg,
})
} else {
Err(anyhow!("Cannot run {:?}", cmd))
}
}
pub fn name(&self) -> Option<&str> {
self.target.as_ref().map(|t| t.short_name())
}
pub fn shared_object_link_args(
&self,
capi_config: &CApiConfig,
libdir: &Path,
target_dir: &Path,
) -> Vec<String> {
let mut lines = Vec::new();
let lib_name = &capi_config.library.name;
let version = &capi_config.library.version;
let major = version.major;
let minor = version.minor;
let patch = version.patch;
let os = &self.os;
let env = &self.env;
let sover = capi_config.library.sover();
if os == "android" {
lines.push(format!("-Wl,-soname,lib{lib_name}.so"));
} else if os == "linux"
|| os == "freebsd"
|| os == "dragonfly"
|| os == "netbsd"
|| os == "haiku"
|| os == "illumos"
|| os == "openbsd"
|| os == "hurd"
{
lines.push(if capi_config.library.versioning {
format!("-Wl,-soname,lib{lib_name}.so.{sover}")
} else {
format!("-Wl,-soname,lib{lib_name}.so")
});
} else if os == "macos" || os == "ios" || os == "tvos" || os == "visionos" {
let line = if capi_config.library.versioning {
format!("-Wl,-install_name,{1}/lib{0}.{5}.dylib,-current_version,{2}.{3}.{4},-compatibility_version,{5}",
lib_name, libdir.display(), major, minor, patch, sover)
} else {
format!(
"-Wl,-install_name,{1}/lib{0}.dylib",
lib_name,
libdir.display()
)
};
lines.push(line);
lines.push("-Wl,-headerpad_max_install_names".to_string());
} else if (os == "windows" && env == "gnu") || (os == "cygwin") {
lines.push(format!(
"-Wl,--output-def,{}",
target_dir.join(format!("{lib_name}.def")).display()
));
}
lines
}
fn is_freebsd(&self) -> bool {
self.os.eq_ignore_ascii_case("freebsd")
}
fn is_haiku(&self) -> bool {
self.os.eq_ignore_ascii_case("haiku")
}
fn is_windows(&self) -> bool {
self.os.eq_ignore_ascii_case("windows")
}
pub fn default_libdir(&self) -> PathBuf {
if self.is_freebsd() {
return "lib".into();
}
if PathBuf::from("/etc/debian_version").exists() {
if self.is_target_overridden {
let debian_target = std::env::var("DEB_HOST_RUST_TYPE").ok();
if debian_target.as_deref() != self.name() {
return "lib".into();
}
}
let pc = std::process::Command::new("dpkg-architecture")
.arg("-qDEB_HOST_MULTIARCH")
.output();
if let std::result::Result::Ok(v) = pc {
if v.status.success() {
let archpath = String::from_utf8_lossy(&v.stdout);
return format!("lib/{}", archpath.trim()).into();
}
}
}
if self.is_target_overridden {
return "lib".into();
}
if consts::ARCH.eq_ignore_ascii_case(&self.arch)
&& consts::OS.eq_ignore_ascii_case(&self.os)
{
let usr_lib64 = PathBuf::from("/usr/lib64");
if usr_lib64.exists() && !usr_lib64.is_symlink() {
return "lib64".into();
}
}
"lib".into()
}
pub fn default_prefix(&self) -> PathBuf {
if self.is_windows() {
"c:/".into()
} else if self.is_haiku() {
"/boot/system/non-packaged".into()
} else {
"/usr/local".into()
}
}
pub fn default_datadir(&self) -> PathBuf {
if self.is_haiku() {
return "data".into();
}
"share".into()
}
pub fn default_includedir(&self) -> PathBuf {
if self.is_haiku() {
return "develop/headers".into();
}
"include".into()
}
}