| use cfg_expr::{ |
| expr::{Predicate, TargetMatcher}, |
| targets::{get_builtin_target_by_triple, ALL_BUILTINS as all}, |
| Expression, TargetPredicate, |
| }; |
| |
| struct Target { |
| builtin: &'static cfg_expr::targets::TargetInfo, |
| #[cfg(feature = "targets")] |
| lexicon: Option<target_lexicon::Triple>, |
| } |
| |
| impl Target { |
| fn make(s: &str) -> Self { |
| Self { |
| builtin: get_builtin_target_by_triple(s).unwrap(), |
| #[cfg(feature = "targets")] |
| lexicon: { |
| // Hack to workaround the addition in 1.48.0 of this weird, non-conformant |
| // target triple, until https://github.com/bytecodealliance/target-lexicon/issues/63 is |
| // resolved in a satisfactory manner, not really concerned about |
| // the presence of this triple in most normal cases |
| use target_lexicon as tl; |
| match s { |
| "avr-unknown-gnu-atmega328" => Some(tl::Triple { |
| architecture: tl::Architecture::Avr, |
| vendor: tl::Vendor::Unknown, |
| operating_system: tl::OperatingSystem::Unknown, |
| environment: tl::Environment::Unknown, |
| binary_format: tl::BinaryFormat::Unknown, |
| }), |
| triple => match triple.parse::<tl::Triple>() { |
| Ok(l) => Some(l), |
| Err(e) => { |
| // There are enough new weird architectures added in each version of |
| // Rust that it is difficult to keep target-lexicon aware of all of |
| // them. So try parsing this triple, but don't fail if it doesn't work. |
| eprintln!("failed to parse '{triple}': {e:?}"); |
| None |
| } |
| }, |
| } |
| }, |
| } |
| } |
| } |
| |
| macro_rules! tg_match { |
| ($pred:expr, $target:expr) => { |
| match $pred { |
| Predicate::Target(tg) => { |
| let tinfo = tg.matches($target.builtin); |
| |
| #[cfg(feature = "targets")] |
| if !matches!(tg, TargetPredicate::HasAtomic(_)) |
| && !matches!(tg, TargetPredicate::Panic(_)) |
| { |
| if let Some(l) = &$target.lexicon { |
| let linfo = tg.matches(l); |
| assert_eq!( |
| tinfo, linfo, |
| "{:#?} builtin didn't match lexicon {:#?} for predicate {tg:#?}", |
| $target.builtin, $target.lexicon, |
| ); |
| |
| return linfo; |
| } |
| } |
| |
| tinfo |
| } |
| _ => panic!("not a target predicate"), |
| } |
| }; |
| |
| ($pred:expr, $target:expr, $feats:expr) => { |
| match $pred { |
| Predicate::Target(tg) => { |
| let tinfo = tg.matches($target.builtin); |
| |
| #[cfg(feature = "targets")] |
| if !matches!(tg, TargetPredicate::HasAtomic(_)) |
| && !matches!(tg, TargetPredicate::Panic(_)) |
| { |
| if let Some(l) = &$target.lexicon { |
| let linfo = tg.matches(l); |
| assert_eq!( |
| tinfo, linfo, |
| "{:#?} builtin didn't match lexicon {:#?} for predicate {tg:#?}", |
| $target.builtin, $target.lexicon, |
| ); |
| |
| return linfo; |
| } |
| } |
| |
| tinfo |
| } |
| Predicate::TargetFeature(feat) => $feats.iter().find(|f| *f == feat).is_some(), |
| _ => panic!("not a target predicate"), |
| } |
| }; |
| } |
| |
| #[test] |
| fn target_family() { |
| let matches_any_family = |
| Expression::parse("any(unix, target_family = \"windows\", target_family = \"wasm\")") |
| .unwrap(); |
| let impossible = Expression::parse("all(windows, target_family = \"unix\")").unwrap(); |
| |
| for target in all { |
| let target = Target::make(target.triple.as_str()); |
| if target.builtin.families.is_empty() { |
| assert!(!matches_any_family.eval(|pred| { tg_match!(pred, target) })); |
| } else { |
| assert!(matches_any_family.eval(|pred| { tg_match!(pred, target) })); |
| } |
| assert!(!impossible.eval(|pred| { tg_match!(pred, target) })); |
| } |
| } |
| |
| #[test] |
| fn tiny() { |
| assert!(Expression::parse("all()").unwrap().eval(|_| false)); |
| assert!(!Expression::parse("any()").unwrap().eval(|_| true)); |
| assert!(!Expression::parse("not(all())").unwrap().eval(|_| false)); |
| assert!(Expression::parse("not(any())").unwrap().eval(|_| true)); |
| assert!(Expression::parse("all(not(blah))").unwrap().eval(|_| false)); |
| assert!(!Expression::parse("any(not(blah))").unwrap().eval(|_| true)); |
| } |
| |
| #[test] |
| fn very_specific() { |
| let specific = Expression::parse( |
| r#"all( |
| target_os = "windows", |
| target_arch = "x86", |
| windows, |
| target_env = "msvc", |
| target_feature = "fxsr", |
| target_feature = "sse", |
| target_feature = "sse2", |
| target_pointer_width = "32", |
| target_endian = "little", |
| not(target_vendor = "uwp"), |
| target_has_atomic = "8", |
| target_has_atomic = "16", |
| target_has_atomic = "32", |
| target_has_atomic = "64", |
| not(target_has_atomic = "128"), |
| target_has_atomic = "ptr", |
| panic = "unwind", |
| not(panic = "abort"), |
| )"#, |
| ) |
| .unwrap(); |
| |
| for target in all { |
| let t = Target::make(target.triple.as_str()); |
| assert_eq!( |
| matches!( |
| target.triple.as_str(), |
| "i686-pc-windows-msvc" | "i586-pc-windows-msvc" | "i686-win7-windows-msvc" |
| ), |
| specific.eval(|pred| { tg_match!(pred, t, &["fxsr", "sse", "sse2"]) }), |
| "expected true for i686-pc-windows-msvc, but got true for {}", |
| target.triple, |
| ); |
| } |
| |
| for target in all { |
| let expr = format!( |
| r#"cfg( |
| all( |
| target_arch = "{}", |
| {} |
| {} |
| target_env = "{}" |
| ) |
| )"#, |
| target.arch.0, |
| if let Some(v) = &target.vendor { |
| format!(r#"target_vendor = "{}","#, v.0) |
| } else { |
| "".to_owned() |
| }, |
| if let Some(v) = &target.os { |
| format!(r#"target_os = "{}","#, v.0) |
| } else { |
| "".to_owned() |
| }, |
| target.env.as_ref().map_or("", |e| e.as_str()), |
| ); |
| |
| let specific = Expression::parse(&expr).unwrap(); |
| |
| let t = Target::make(target.triple.as_str()); |
| assert!( |
| specific.eval(|pred| { |
| if target.triple.as_str() == "mips64-openwrt-linux-musl" { |
| if let Predicate::Target(TargetPredicate::Vendor(vendor)) = pred { |
| // This is a special predicate that doesn't follow the usual rules for |
| // target-lexicon. |
| return t.builtin.matches(&TargetPredicate::Vendor(vendor.clone())); |
| } |
| } |
| tg_match!(pred, t) |
| }), |
| "failed expression '{}' for {:#?}", |
| expr, |
| t.builtin, |
| ); |
| } |
| } |
| |
| #[test] |
| fn complex() { |
| let complex = Expression::parse(r#"cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))"#).unwrap(); |
| |
| // Should match linuxes |
| let linux_gnu = Target::make("x86_64-unknown-linux-gnu"); |
| let linux_musl = Target::make("x86_64-unknown-linux-musl"); |
| |
| assert!(complex.eval(|pred| tg_match!(pred, linux_gnu))); |
| assert!(complex.eval(|pred| tg_match!(pred, linux_musl))); |
| |
| // Should *not* match windows or mac or android |
| let windows_msvc = Target::make("x86_64-pc-windows-msvc"); |
| let mac = Target::make("x86_64-apple-darwin"); |
| let android = Target::make("aarch64-linux-android"); |
| |
| assert!(!complex.eval(|pred| tg_match!(pred, windows_msvc))); |
| assert!(!complex.eval(|pred| tg_match!(pred, mac))); |
| assert!(!complex.eval(|pred| tg_match!(pred, android))); |
| |
| let complex = |
| Expression::parse(r#"all(not(target_os = "ios"), not(target_os = "android"))"#).unwrap(); |
| |
| assert!(complex.eval(|pred| tg_match!(pred, linux_gnu))); |
| assert!(complex.eval(|pred| tg_match!(pred, linux_musl))); |
| assert!(complex.eval(|pred| tg_match!(pred, windows_msvc))); |
| assert!(complex.eval(|pred| tg_match!(pred, mac))); |
| assert!(!complex.eval(|pred| tg_match!(pred, android))); |
| |
| let complex = Expression::parse(r#"all(any(unix, target_arch="x86"), not(any(target_os="android", target_os="emscripten")))"#).unwrap(); |
| |
| // Should match linuxes and mac |
| assert!(complex.eval(|pred| tg_match!(pred, linux_gnu))); |
| assert!(complex.eval(|pred| tg_match!(pred, linux_musl))); |
| assert!(complex.eval(|pred| tg_match!(pred, mac))); |
| |
| // Should *not* match x86_64 windows or android |
| assert!(!complex.eval(|pred| tg_match!(pred, windows_msvc))); |
| assert!(!complex.eval(|pred| tg_match!(pred, android))); |
| |
| // Ensure that target_os = "none" matches against Os == None. |
| let complex = Expression::parse(r#"all(target_os="none")"#).unwrap(); |
| let armebv7r_none_eabi = Target::make("armebv7r-none-eabi"); |
| assert!(!complex.eval(|pred| tg_match!(pred, linux_gnu))); |
| assert!(complex.eval(|pred| tg_match!(pred, armebv7r_none_eabi))); |
| } |
| |
| #[test] |
| fn unstable_target_abi() { |
| let linux_gnu = Target::make("x86_64-unknown-linux-gnu"); |
| let linux_musl = Target::make("x86_64-unknown-linux-musl"); |
| let windows_msvc = Target::make("x86_64-pc-windows-msvc"); |
| let mac = Target::make("x86_64-apple-darwin"); |
| let android = Target::make("aarch64-linux-android"); |
| |
| let target_with_abi_that_matches = cfg_expr::targets::TargetInfo { |
| triple: cfg_expr::targets::Triple::new_const("aarch64-apple-darwin"), |
| os: None, |
| abi: Some(cfg_expr::targets::Abi::new_const("eabihf")), |
| arch: cfg_expr::targets::Arch::aarch64, |
| env: None, |
| vendor: None, |
| families: cfg_expr::targets::Families::unix, |
| pointer_width: 64, |
| endian: cfg_expr::targets::Endian::little, |
| has_atomics: cfg_expr::targets::HasAtomics::atomic_8_16_32_64_128_ptr, |
| panic: cfg_expr::targets::Panic::unwind, |
| }; |
| |
| let target_with_abi_that_doesnt_match = cfg_expr::targets::TargetInfo { |
| abi: Some(cfg_expr::targets::Abi::new_const("ilp32")), |
| ..target_with_abi_that_matches.clone() |
| }; |
| |
| let abi_pred = |
| Expression::parse(r#"cfg(any(target_arch = "wasm32", target_abi = "eabihf"))"#).unwrap(); |
| |
| // Should match a specified target_abi that's the same |
| assert!(abi_pred.eval(|pred| { |
| match pred { |
| Predicate::Target(tp) => tp.matches(&target_with_abi_that_matches), |
| _ => false, |
| } |
| })); |
| |
| // Should *not* match a specified target_abi that isn't the same |
| assert!(!abi_pred.eval(|pred| { |
| match pred { |
| Predicate::Target(tp) => tp.matches(&target_with_abi_that_doesnt_match), |
| _ => false, |
| } |
| })); |
| |
| // Should *not* match any builtins at this point because target_abi isn't stable |
| assert!(!abi_pred.eval(|pred| tg_match!(pred, linux_gnu))); |
| assert!(!abi_pred.eval(|pred| tg_match!(pred, linux_musl))); |
| assert!(!abi_pred.eval(|pred| tg_match!(pred, mac))); |
| assert!(!abi_pred.eval(|pred| tg_match!(pred, windows_msvc))); |
| assert!(!abi_pred.eval(|pred| tg_match!(pred, android))); |
| } |
| |
| #[test] |
| fn wasm_family() { |
| let wasm = Expression::parse(r#"cfg(target_family = "wasm")"#).unwrap(); |
| |
| let wasm32_unknown = Target::make("wasm32-unknown-unknown"); |
| let wasm32_emscripten = Target::make("wasm32-unknown-emscripten"); |
| let wasm32_wasi = Target::make("wasm32-wasi"); |
| let wasm64_unknown = Target::make("wasm64-unknown-unknown"); |
| |
| // All of the above targets match. |
| assert!(wasm.eval(|pred| tg_match!(pred, wasm32_unknown))); |
| assert!(wasm.eval(|pred| tg_match!(pred, wasm32_emscripten))); |
| assert!(wasm.eval(|pred| tg_match!(pred, wasm32_wasi))); |
| assert!(wasm.eval(|pred| tg_match!(pred, wasm64_unknown))); |
| } |
| |
| #[test] |
| fn features() { |
| let enabled = ["good", "bad", "ugly"]; |
| |
| let many_features = Expression::parse( |
| r#"all(feature = "good", feature = "bad", feature = "ugly", not(feature = "nope"))"#, |
| ) |
| .unwrap(); |
| |
| assert!(many_features.eval(|pred| { |
| match pred { |
| Predicate::Feature(name) => enabled.contains(name), |
| _ => false, |
| } |
| })); |
| |
| let feature_and_target_feature = |
| Expression::parse(r#"all(feature = "make_fast", target_feature = "sse4.2")"#).unwrap(); |
| |
| assert!(feature_and_target_feature.eval(|pred| { |
| match pred { |
| Predicate::Feature(name) => *name == "make_fast", |
| Predicate::TargetFeature(feat) => *feat == "sse4.2", |
| _ => false, |
| } |
| })); |
| |
| assert_eq!( |
| feature_and_target_feature.eval(|pred| { |
| match pred { |
| Predicate::Feature(_) => Some(false), |
| Predicate::TargetFeature(_) => None, |
| _ => panic!("unexpected predicate"), |
| } |
| }), |
| Some(false), |
| "all() with Some(false) and None evaluates to Some(false)" |
| ); |
| |
| assert_eq!( |
| feature_and_target_feature.eval(|pred| { |
| match pred { |
| Predicate::Feature(_) => Some(true), |
| Predicate::TargetFeature(_) => None, |
| _ => panic!("unexpected predicate"), |
| } |
| }), |
| None, |
| "all() with Some(true) and None evaluates to None" |
| ); |
| } |