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