1 use cfg_expr::{
2 expr::Predicate,
3 targets::{get_builtin_target_by_triple, ALL_BUILTINS as all},
4 Expression,
5 };
6
7 struct Target {
8 builtin: &'static cfg_expr::targets::TargetInfo<'static>,
9 #[cfg(feature = "targets")]
10 lexicon: target_lexicon::Triple,
11 }
12
13 impl Target {
make(s: &str) -> Self14 fn make(s: &str) -> Self {
15 Self {
16 builtin: get_builtin_target_by_triple(s).unwrap(),
17 #[cfg(feature = "targets")]
18 lexicon: {
19 // Hack to workaround the addition in 1.48.0 of this weird, non-conformant
20 // target triple, until https://github.com/bytecodealliance/target-lexicon/issues/63 is
21 // resolved in a satisfactory manner, not really concerned about
22 // the presence of this triple in most normal cases
23 use target_lexicon as tl;
24 match s {
25 "avr-unknown-gnu-atmega328" => tl::Triple {
26 architecture: tl::Architecture::Avr,
27 vendor: tl::Vendor::Unknown,
28 operating_system: tl::OperatingSystem::Unknown,
29 environment: tl::Environment::Unknown,
30 binary_format: tl::BinaryFormat::Unknown,
31 },
32 triple => match triple.parse() {
33 Ok(l) => l,
34 Err(e) => panic!("failed to parse '{}': {:?}", triple, e),
35 },
36 }
37 },
38 }
39 }
40 }
41
42 macro_rules! tg_match {
43 ($pred:expr, $target:expr) => {
44 match $pred {
45 Predicate::Target(tg) => {
46 let tinfo = tg.matches($target.builtin);
47
48 #[cfg(feature = "targets")]
49 {
50 let linfo = tg.matches(&$target.lexicon);
51 assert_eq!(
52 tinfo, linfo,
53 "{:#?} builtin didn't match lexicon {:#?} for predicate {:#?}",
54 $target.builtin, $target.lexicon, tg,
55 );
56
57 return linfo;
58 }
59
60 #[cfg(not(feature = "targets"))]
61 tinfo
62 }
63 _ => panic!("not a target predicate"),
64 }
65 };
66
67 ($pred:expr, $target:expr, $feats:expr) => {
68 match $pred {
69 Predicate::Target(tg) => {
70 let tinfo = tg.matches($target.builtin);
71
72 #[cfg(feature = "targets")]
73 {
74 let linfo = tg.matches(&$target.lexicon);
75 assert_eq!(
76 tinfo, linfo,
77 "{:#?} builtin didn't match lexicon {:#?} for predicate {:#?}",
78 $target.builtin, $target.lexicon, tg,
79 );
80
81 return linfo;
82 }
83
84 #[cfg(not(feature = "targets"))]
85 tinfo
86 }
87 Predicate::TargetFeature(feat) => $feats.iter().find(|f| *f == feat).is_some(),
88 _ => panic!("not a target predicate"),
89 }
90 };
91 }
92
93 #[test]
target_family()94 fn target_family() {
95 let matches_any_family = Expression::parse("any(unix, target_family = \"windows\")").unwrap();
96 let impossible = Expression::parse("all(windows, target_family = \"unix\")").unwrap();
97
98 for target in all {
99 let target = Target::make(target.triple);
100 match target.builtin.family {
101 Some(_) => {
102 assert!(matches_any_family.eval(|pred| { tg_match!(pred, target) }));
103 assert!(!impossible.eval(|pred| { tg_match!(pred, target) }));
104 }
105 None => {
106 assert!(!matches_any_family.eval(|pred| { tg_match!(pred, target) }));
107 assert!(!impossible.eval(|pred| { tg_match!(pred, target) }));
108 }
109 }
110 }
111 }
112
113 #[test]
tiny()114 fn tiny() {
115 assert!(Expression::parse("all()").unwrap().eval(|_| false));
116 assert!(!Expression::parse("any()").unwrap().eval(|_| true));
117 assert!(!Expression::parse("not(all())").unwrap().eval(|_| false));
118 assert!(Expression::parse("not(any())").unwrap().eval(|_| true));
119 assert!(Expression::parse("all(not(blah))").unwrap().eval(|_| false));
120 assert!(!Expression::parse("any(not(blah))").unwrap().eval(|_| true));
121 }
122
123 #[test]
very_specific()124 fn very_specific() {
125 let specific = Expression::parse(
126 r#"all(
127 target_os = "windows",
128 target_arch = "x86",
129 windows,
130 target_env = "msvc",
131 target_feature = "fxsr",
132 target_feature = "sse",
133 target_feature = "sse2",
134 target_pointer_width = "32",
135 target_endian = "little",
136 not(target_vendor = "uwp"),
137 )"#,
138 )
139 .unwrap();
140
141 for target in all {
142 let t = Target::make(target.triple);
143 assert_eq!(
144 target.triple == "i686-pc-windows-msvc" || target.triple == "i586-pc-windows-msvc",
145 specific.eval(|pred| { tg_match!(pred, t, &["fxsr", "sse", "sse2"]) }),
146 "expected true for i686-pc-windows-msvc, but got true for {}",
147 target.triple,
148 );
149 }
150
151 for target in all {
152 let expr = format!(
153 r#"cfg(
154 all(
155 target_arch = "{}",
156 {}
157 {}
158 target_env = "{}"
159 )
160 )"#,
161 target.arch.0,
162 if let Some(v) = target.vendor {
163 format!(r#"target_vendor = "{}","#, v.0)
164 } else {
165 "".to_owned()
166 },
167 if let Some(v) = target.os {
168 format!(r#"target_os = "{}","#, v.0)
169 } else {
170 "".to_owned()
171 },
172 target.env.map(|e| e.0).unwrap_or_else(|| ""),
173 );
174
175 let specific = Expression::parse(&expr).unwrap();
176
177 let t = Target::make(target.triple);
178 assert!(
179 specific.eval(|pred| { tg_match!(pred, t) }),
180 "failed expression '{}' for {:#?}",
181 expr,
182 t.builtin,
183 );
184 }
185 }
186
187 #[test]
complex()188 fn complex() {
189 let complex = Expression::parse(r#"cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))"#).unwrap();
190
191 // Should match linuxes
192 let linux_gnu = Target::make("x86_64-unknown-linux-gnu");
193 let linux_musl = Target::make("x86_64-unknown-linux-musl");
194
195 assert!(complex.eval(|pred| tg_match!(pred, linux_gnu)));
196 assert!(complex.eval(|pred| tg_match!(pred, linux_musl)));
197
198 // Should *not* match windows or mac or android
199 let windows_msvc = Target::make("x86_64-pc-windows-msvc");
200 let mac = Target::make("x86_64-apple-darwin");
201 let android = Target::make("aarch64-linux-android");
202
203 assert!(!complex.eval(|pred| tg_match!(pred, windows_msvc)));
204 assert!(!complex.eval(|pred| tg_match!(pred, mac)));
205 assert!(!complex.eval(|pred| tg_match!(pred, android)));
206
207 let complex =
208 Expression::parse(r#"all(not(target_os = "ios"), not(target_os = "android"))"#).unwrap();
209
210 assert!(complex.eval(|pred| tg_match!(pred, linux_gnu)));
211 assert!(complex.eval(|pred| tg_match!(pred, linux_musl)));
212 assert!(complex.eval(|pred| tg_match!(pred, windows_msvc)));
213 assert!(complex.eval(|pred| tg_match!(pred, mac)));
214 assert!(!complex.eval(|pred| tg_match!(pred, android)));
215
216 let complex = Expression::parse(r#"all(any(unix, target_arch="x86"), not(any(target_os="android", target_os="emscripten")))"#).unwrap();
217
218 // Should match linuxes and mac
219 assert!(complex.eval(|pred| tg_match!(pred, linux_gnu)));
220 assert!(complex.eval(|pred| tg_match!(pred, linux_musl)));
221 assert!(complex.eval(|pred| tg_match!(pred, mac)));
222
223 // Should *not* match x86_64 windows or android
224 assert!(!complex.eval(|pred| tg_match!(pred, windows_msvc)));
225 assert!(!complex.eval(|pred| tg_match!(pred, android)));
226 }
227
228 #[test]
features()229 fn features() {
230 let enabled = ["good", "bad", "ugly"];
231
232 let many_features = Expression::parse(
233 r#"all(feature = "good", feature = "bad", feature = "ugly", not(feature = "nope"))"#,
234 )
235 .unwrap();
236
237 assert!(many_features.eval(|pred| {
238 match pred {
239 Predicate::Feature(name) => enabled.contains(name),
240 _ => false,
241 }
242 }));
243
244 let feature_and_target_feature =
245 Expression::parse(r#"all(feature = "make_fast", target_feature = "sse4.2")"#).unwrap();
246
247 assert!(feature_and_target_feature.eval(|pred| {
248 match pred {
249 Predicate::Feature(name) => *name == "make_fast",
250 Predicate::TargetFeature(feat) => *feat == "sse4.2",
251 _ => false,
252 }
253 }));
254
255 assert_eq!(
256 feature_and_target_feature.eval(|pred| {
257 match pred {
258 Predicate::Feature(_) => Some(false),
259 Predicate::TargetFeature(_) => None,
260 _ => panic!("unexpected predicate"),
261 }
262 }),
263 Some(false),
264 "all() with Some(false) and None evaluates to Some(false)"
265 );
266
267 assert_eq!(
268 feature_and_target_feature.eval(|pred| {
269 match pred {
270 Predicate::Feature(_) => Some(true),
271 Predicate::TargetFeature(_) => None,
272 _ => panic!("unexpected predicate"),
273 }
274 }),
275 None,
276 "all() with Some(true) and None evaluates to None"
277 );
278 }
279