use core::mem;
use crate::{
build_env::EnvSnapshot,
target::TargetInfo,
utilities::{lookup, OnceLock},
Error, ErrorKind,
};
#[derive(Debug)]
struct TargetInfoParserInner {
full_arch: Box<str>,
arch: Box<str>,
vendor: Box<str>,
os: Box<str>,
env: Box<str>,
abi: Box<str>,
}
impl TargetInfoParserInner {
fn from_cargo_environment_variables(vars: &EnvSnapshot) -> Result<Self, Error> {
let target_name = vars.get_cargo_var("TARGET")?;
let (full_arch, _rest) = target_name.split_once('-').ok_or(Error::new(
ErrorKind::InvalidTarget,
format!("target `{target_name}` only had a single component (at least two required)"),
))?;
let cargo_env = |key, fallback: Option<&str>| match vars.get_cargo_var(key) {
Ok(var) => Ok(var.into()),
Err(err) => match fallback {
Some(fallback) => Ok(fallback.into()),
None => Err(Error::new(
ErrorKind::EnvVarNotFound,
format!(
"did not find fallback information for target `{target_name}`: {}",
err.message
),
)),
},
};
let fallback_target = TargetInfo::from_rustc_target(target_name).ok();
let ft = fallback_target.as_ref();
let arch = cargo_env("CARGO_CFG_TARGET_ARCH", ft.map(|t| t.arch))?;
let vendor = cargo_env("CARGO_CFG_TARGET_VENDOR", ft.map(|t| t.vendor))?;
let os = cargo_env("CARGO_CFG_TARGET_OS", ft.map(|t| t.os))?;
let mut env = cargo_env("CARGO_CFG_TARGET_ENV", ft.map(|t| t.env))?;
let mut abi = cargo_env("CARGO_CFG_TARGET_ABI", ft.map(|t| t.abi))
.unwrap_or_else(|_| String::default().into_boxed_str());
if matches!(&*abi, "macabi" | "sim") {
debug_assert!(
matches!(&*env, "" | "macabi" | "sim"),
"env/abi mismatch: {:?}, {:?}",
env,
abi,
);
env = mem::replace(&mut abi, String::default().into_boxed_str());
}
Ok(Self {
full_arch: full_arch.to_string().into_boxed_str(),
arch,
vendor,
os,
env,
abi,
})
}
}
#[derive(Default, Debug)]
pub(crate) struct TargetInfoParser(OnceLock<Result<TargetInfoParserInner, Error>>);
impl TargetInfoParser {
pub fn parse_from_cargo_environment_variables(
&self,
vars: &EnvSnapshot,
) -> Result<TargetInfo<'_>, Error> {
match self
.0
.get_or_init(|| TargetInfoParserInner::from_cargo_environment_variables(vars))
{
Ok(TargetInfoParserInner {
full_arch,
arch,
vendor,
os,
env,
abi,
}) => Ok(TargetInfo {
full_arch,
arch,
vendor,
os,
env,
abi,
}),
Err(e) => Err(e.clone()),
}
}
}
const ARCHES: &[(&str, &str)] = &[
("arm64ec", "arm64ec"), ("asmjs", "wasm32"),
("z80", "z80"),
("sm83", "sm83"),
("sparc", "sparc"),
("sparcv7", "sparc"),
("sparcv8", "sparc"),
("sparc64", "sparc64"),
("sparcv9", "sparc64"),
("amdgcn", "amdgpu"),
("avr", "avr"),
("csky", "csky"),
("hexagon", "hexagon"),
("m68k", "m68k"),
("msp430", "msp430"),
("r600", "r600"),
("s390x", "s390x"),
("xtensa", "xtensa"),
("hppa", "hppa"),
];
const ARCH_PREFIXES: &[(&str, &str)] = &[
("mipsisa32r6", "mips32r6"), ("mipsisa64r6", "mips64r6"), ("mips64", "mips64"), ("mips", "mips"), ("loongarch64", "loongarch64"),
("loongarch32", "loongarch32"),
("powerpc64", "powerpc64"), ("powerpc", "powerpc"),
("ppc64", "powerpc64"),
("ppc", "powerpc"),
("x86_64", "x86_64"), ("aarch64", "aarch64"), ("arm64", "aarch64"), ("arm", "arm"), ("thumb", "arm"), ("riscv64", "riscv64"),
("riscv32", "riscv32"),
("wasm64", "wasm64"),
("wasm32", "wasm32"), ("nvptx64", "nvptx64"),
("nvptx", "nvptx"),
("bpf", "bpf"), ("sh4", "sh4"), ("pulley64", "pulley64"),
("pulley32", "pulley32"),
("clever", "clever"),
("alpha", "alpha"), ("sh", "sh"), ];
fn parse_arch(full_arch: &str) -> Option<&'static str> {
if full_arch.starts_with('i') && full_arch.ends_with("86") {
return Some("x86");
}
if let Some(arch) = lookup(ARCHES, full_arch) {
return Some(arch);
}
for (prefix, arch) in ARCH_PREFIXES {
if full_arch.starts_with(prefix) {
return Some(arch);
}
}
None
}
const ENVABIS: &[(&str, (&str, &str))] = &[
("pauthtest", ("musl", "pauthtest")),
("msvc", ("msvc", "")),
("ohos", ("ohos", "")),
("qnx700", ("nto70", "")),
("qnx710_iosock", ("nto71_iosock", "")),
("qnx710", ("nto71", "")),
("sgx", ("sgx", "")),
("threads", ("threads", "")),
("mlibc", ("mlibc", "")),
("relibc", ("relibc", "")),
("abi64", ("", "abi64")),
("abiv2", ("", "spe")),
("eabi", ("", "eabi")),
("eabihf", ("", "eabihf")),
("macabi", ("macabi", "")),
("sim", ("sim", "")),
("softfloat", ("", "softfloat")),
("spe", ("", "spe")),
("x32", ("", "x32")),
("elf", ("", "")),
("freestanding", ("", "")),
];
fn parse_envabi(last_component: &str) -> Option<(&str, &str)> {
for env in ["gnu", "musl", "uclibc", "newlib"] {
if let Some(abi) = last_component.strip_prefix(env) {
let abi = if env == "gnu" {
abi.strip_prefix('_').unwrap_or(abi)
} else {
abi
};
return Some((env, abi));
}
}
for (component, envabi) in ENVABIS {
if *component == last_component {
return Some(*envabi);
}
}
None
}
impl<'a> TargetInfo<'a> {
pub(crate) fn from_rustc_target(target: &'a str) -> Result<Self, Error> {
if target == "x86_64-unknown-linux-none" {
return Ok(Self {
full_arch: "x86_64",
arch: "x86_64",
vendor: "unknown",
os: "linux",
env: "",
abi: "",
});
}
let mut components = target.split('-');
let full_arch = components.next().ok_or(Error::new(
ErrorKind::InvalidTarget,
"target was empty".to_string(),
))?;
if target == "armv7a-vex-v5" || target == "thumbv7a-vex-v5" {
return Ok(Self {
full_arch,
arch: "arm",
vendor: "vex",
os: "vexos",
env: "v5",
abi: "eabihf",
});
}
let arch = parse_arch(full_arch).ok_or_else(|| {
Error::new(
ErrorKind::UnknownTarget,
format!("target `{target}` had an unknown architecture"),
)
})?;
let components: Vec<_> = components.collect();
let (vendor, os, mut env, mut abi) = match &*components {
[] => {
return Err(Error::new(
ErrorKind::InvalidTarget,
format!("target `{target}` must have at least two components"),
))
}
[os] => ("unknown", *os, "", ""),
[vendor_or_os, os_or_envabi] => {
if let Some((env, abi)) = parse_envabi(os_or_envabi) {
("unknown", *vendor_or_os, env, abi)
} else {
(*vendor_or_os, *os_or_envabi, "", "")
}
}
[vendor, os, envabi] => {
let (env, abi) = parse_envabi(envabi).ok_or_else(|| {
Error::new(
ErrorKind::UnknownTarget,
format!("unknown environment/ABI `{envabi}` in target `{target}`"),
)
})?;
(*vendor, *os, env, abi)
}
_ => {
return Err(Error::new(
ErrorKind::InvalidTarget,
format!("too many components in target `{target}`"),
))
}
};
match full_arch {
arch if arch.starts_with("riscv32e") => {
abi = "ilp32e";
}
_ => {}
}
match os {
"3ds" | "rtems" | "espidf" => env = "newlib",
"vxworks" => env = "gnu",
"redox" => env = "relibc",
"aix" => abi = "vec-default",
_ => {}
}
match target {
"i386-apple-ios" | "x86_64-apple-ios" | "x86_64-apple-tvos" => {
env = "sim";
}
"mips64-openwrt-linux-musl" => {
abi = "abi64";
}
"armv6-unknown-freebsd" | "armv6k-nintendo-3ds" | "armv7-unknown-freebsd" => {
abi = "eabihf";
}
"armv7-unknown-linux-ohos" | "armv7-unknown-trusty" => {
abi = "eabi";
}
"sparc-unknown-linux-gnu" => {
abi = "v8plus";
}
_ => {}
}
let os = match os {
"3ds" | "switch" => "horizon",
"darwin" => "macos",
os if os.starts_with("wasi") => {
env = os.strip_prefix("wasi").unwrap();
"wasi"
}
"androideabi" => {
abi = "eabi";
"android"
}
os => os,
};
let vendor = match vendor {
vendor if vendor.starts_with("esp") => "espressif",
"linux" if os == "android" || os == "androideabi" => "unknown",
"wali" => "unknown",
"lynx" => "unknown",
"oe" => "unknown",
vendor => vendor,
};
if vendor == "fortanix" {
abi = "fortanix";
}
if vendor == "uwp" {
abi = "uwp";
}
if [
"powerpc64-unknown-linux-gnu",
"powerpc64-wrs-vxworks",
"powerpc64-sony-ps3",
]
.contains(&target)
{
abi = "elfv1";
}
if [
"powerpc64-unknown-freebsd",
"powerpc64-unknown-linux-musl",
"powerpc64-unknown-openbsd",
"powerpc64le-unknown-freebsd",
"powerpc64le-unknown-linux-gnu",
"powerpc64le-unknown-linux-musl",
]
.contains(&target)
{
abi = "elfv2";
}
if ["asan", "msan", "tsan"].contains(&abi) {
abi = "";
}
Ok(Self {
full_arch,
arch,
vendor,
os,
env,
abi,
})
}
}
#[cfg(test)]
#[allow(unexpected_cfgs)]
#[allow(clippy::disallowed_methods)]
mod tests {
use std::process::Command;
use super::TargetInfo;
use crate::ErrorKind;
#[test]
fn tier1() {
let targets = [
"aarch64-unknown-linux-gnu",
"aarch64-apple-darwin",
"i686-pc-windows-gnu",
"i686-pc-windows-msvc",
"i686-unknown-linux-gnu",
"x86_64-apple-darwin",
"x86_64-pc-windows-gnu",
"x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
];
for target in targets {
let _ = TargetInfo::from_rustc_target(target).unwrap();
}
}
#[test]
fn parse_extra() {
let targets = [
"aarch64-unknown-none-gnu",
"aarch64-uwp-windows-gnu",
"arm-frc-linux-gnueabi",
"arm-unknown-netbsd-eabi",
"armv7neon-unknown-linux-gnueabihf",
"armv7neon-unknown-linux-musleabihf",
"thumbv7-unknown-linux-gnueabihf",
"thumbv7-unknown-linux-musleabihf",
"armv7-apple-ios",
"wasm32-wasi",
"x86_64-rumprun-netbsd",
"x86_64-unknown-linux",
"x86_64-alpine-linux-musl",
"x86_64-chimera-linux-musl",
"x86_64-foxkit-linux-musl",
"arm-poky-linux-gnueabi",
"x86_64-unknown-moturus",
"x86_64-unknown-managarm-mlibc",
];
for target in targets {
let _ = TargetInfo::from_rustc_target(target).unwrap();
}
}
fn target_from_rustc_cfgs<'a>(target: &'a str, cfgs: &'a str) -> TargetInfo<'a> {
let (full_arch, _rest) = target.split_once('-').expect("target to have arch");
let mut target = TargetInfo {
full_arch,
arch: "invalid-none-set",
vendor: "invalid-none-set",
os: "invalid-none-set",
env: "invalid-none-set",
abi: "",
};
for cfg in cfgs.lines() {
if let Some((name, value)) = cfg.split_once('=') {
let name = name.trim();
let value = value.trim();
let value = value.strip_prefix('"').unwrap_or(value);
let value = value.strip_suffix('"').unwrap_or(value);
match name {
"target_arch" => target.arch = value,
"target_vendor" => target.vendor = value,
"target_os" => target.os = value,
"target_env" => target.env = value,
"target_abi" => target.abi = value,
_ => {}
}
} else {
}
}
if matches!(target.abi, "macabi" | "sim") {
assert_eq!(target.env, target.abi);
target.abi = "";
}
target
}
#[test]
fn unknown_env_determined_as_unknown() {
let err = TargetInfo::from_rustc_target("aarch64-unknown-linux-bogus").unwrap_err();
assert!(matches!(err.kind, ErrorKind::UnknownTarget));
}
#[test]
#[cfg_attr(
not(rustc_target_test),
ignore = "must enable explicitly with --cfg=rustc_target_test"
)]
fn parse_rustc_targets() {
#[allow(clippy::disallowed_methods)]
let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
let target_list = Command::new(&rustc)
.arg("--print=target-list")
.output()
.unwrap()
.stdout;
let target_list = String::from_utf8(target_list).unwrap();
let mut has_failure = false;
for target in target_list.lines() {
let cfgs = Command::new(&rustc)
.arg("--target")
.arg(target)
.arg("--print=cfg")
.output()
.unwrap()
.stdout;
let cfgs = String::from_utf8(cfgs).unwrap();
let expected = target_from_rustc_cfgs(target, &cfgs);
let actual = TargetInfo::from_rustc_target(target);
if Some(&expected) != actual.as_ref().ok() {
eprintln!("failed comparing {target}:");
eprintln!(" expected: Ok({expected:?})");
eprintln!(" actual: {actual:?}");
eprintln!();
has_failure = true;
}
}
if has_failure {
panic!("failed comparing targets");
}
}
#[test]
fn parses_apple_envs_correctly() {
assert_eq!(
TargetInfo::from_rustc_target("aarch64-apple-ios-macabi").unwrap(),
TargetInfo {
full_arch: "aarch64",
arch: "aarch64",
vendor: "apple",
os: "ios",
env: "macabi",
abi: "",
}
);
assert_eq!(
TargetInfo::from_rustc_target("aarch64-apple-ios-sim").unwrap(),
TargetInfo {
full_arch: "aarch64",
arch: "aarch64",
vendor: "apple",
os: "ios",
env: "sim",
abi: "",
}
);
}
}