1 use std::collections::{BTreeMap, BTreeSet};
2
3 use anyhow::{anyhow, Context, Result};
4 use cfg_expr::targets::{get_builtin_target_by_triple, TargetInfo};
5 use cfg_expr::{Expression, Predicate};
6
7 use crate::context::CrateContext;
8 use crate::utils::target_triple::TargetTriple;
9
10 /// Walk through all dependencies in a [CrateContext] list for all configuration specific
11 /// dependencies to produce a mapping of configurations/Cargo target_triples to compatible
12 /// Bazel target_triples. Also adds mappings for all known target_triples.
resolve_cfg_platforms( crates: Vec<&CrateContext>, supported_platform_triples: &BTreeSet<TargetTriple>, ) -> Result<BTreeMap<String, BTreeSet<TargetTriple>>>13 pub(crate) fn resolve_cfg_platforms(
14 crates: Vec<&CrateContext>,
15 supported_platform_triples: &BTreeSet<TargetTriple>,
16 ) -> Result<BTreeMap<String, BTreeSet<TargetTriple>>> {
17 // Collect all unique configurations from all dependencies into a single set
18 let configurations: BTreeSet<String> = crates
19 .iter()
20 .flat_map(|ctx| {
21 let attr = &ctx.common_attrs;
22 let mut configurations = BTreeSet::new();
23
24 configurations.extend(attr.deps.configurations());
25 configurations.extend(attr.deps_dev.configurations());
26 configurations.extend(attr.proc_macro_deps.configurations());
27 configurations.extend(attr.proc_macro_deps_dev.configurations());
28
29 // Chain the build dependencies if some are defined
30 if let Some(attr) = &ctx.build_script_attrs {
31 configurations.extend(attr.deps.configurations());
32 configurations.extend(attr.proc_macro_deps.configurations());
33 }
34
35 configurations
36 })
37 .collect();
38
39 // Generate target information for each triple string
40 let target_infos = supported_platform_triples
41 .iter()
42 .map(
43 |target_triple| match get_builtin_target_by_triple(&target_triple.to_cargo()) {
44 Some(info) => Ok((target_triple, info)),
45 None => Err(anyhow!(
46 "Invalid platform triple in supported platforms: {}",
47 target_triple
48 )),
49 },
50 )
51 .collect::<Result<BTreeMap<&TargetTriple, &'static TargetInfo>>>()?;
52
53 // `cfg-expr` does not understand configurations that are simply platform triples
54 // (`x86_64-unknown-linux-gnu` vs `cfg(target = "x86_64-unkonwn-linux-gnu")`). So
55 // in order to parse configurations, the text is renamed for the check but the
56 // original is retained for comaptibility with the manifest.
57 let rename = |cfg: &str| -> String { format!("cfg(target = \"{cfg}\")") };
58 let original_cfgs: BTreeMap<String, String> = configurations
59 .iter()
60 .filter(|cfg| !cfg.starts_with("cfg("))
61 .map(|cfg| (rename(cfg), cfg.clone()))
62 .collect();
63
64 let mut conditions = configurations
65 .into_iter()
66 // `cfg-expr` requires that the expressions be actual `cfg` expressions. Any time
67 // there's a target triple (which is a valid constraint), convert it to a cfg expression.
68 .map(|cfg| match cfg.starts_with("cfg(") {
69 true => cfg,
70 false => rename(&cfg),
71 })
72 // Check the current configuration with against each supported triple
73 .map(|cfg| {
74 let expression =
75 Expression::parse(&cfg).context(format!("Failed to parse expression: '{cfg}'"))?;
76
77 let triples = target_infos
78 .iter()
79 .filter(|(_, target_info)| {
80 expression.eval(|p| match p {
81 Predicate::Target(tp) => tp.matches(**target_info),
82 Predicate::KeyValue { key, val } => {
83 *key == "target" && val == &target_info.triple.as_str()
84 }
85 // For now there is no other kind of matching
86 _ => false,
87 })
88 })
89 .map(|(triple, _)| (*triple).clone())
90 .collect();
91
92 // Map any renamed configurations back to their original IDs
93 let cfg = match original_cfgs.get(&cfg) {
94 Some(orig) => orig.clone(),
95 None => cfg,
96 };
97
98 Ok((cfg, triples))
99 })
100 .collect::<Result<BTreeMap<String, BTreeSet<TargetTriple>>>>()?;
101 // Insert identity relationships.
102 for target_triple in supported_platform_triples.iter() {
103 conditions
104 .entry(target_triple.to_bazel())
105 .or_default()
106 .insert(target_triple.clone());
107 }
108 Ok(conditions)
109 }
110
111 #[cfg(test)]
112 mod test {
113 use crate::config::CrateId;
114 use crate::context::crate_context::CrateDependency;
115 use crate::context::CommonAttributes;
116 use crate::select::Select;
117
118 use super::*;
119
120 const VERSION_ZERO_ONE_ZERO: semver::Version = semver::Version::new(0, 1, 0);
121
supported_platform_triples() -> BTreeSet<TargetTriple>122 fn supported_platform_triples() -> BTreeSet<TargetTriple> {
123 BTreeSet::from([
124 TargetTriple::from_bazel("aarch64-apple-darwin".to_owned()),
125 TargetTriple::from_bazel("i686-apple-darwin".to_owned()),
126 TargetTriple::from_bazel("x86_64-unknown-linux-gnu".to_owned()),
127 ])
128 }
129
130 #[test]
resolve_no_targeted()131 fn resolve_no_targeted() {
132 let mut deps: Select<BTreeSet<CrateDependency>> = Select::default();
133 deps.insert(
134 CrateDependency {
135 id: CrateId::new("mock_crate_b".to_owned(), VERSION_ZERO_ONE_ZERO),
136 target: "mock_crate_b".to_owned(),
137 alias: None,
138 },
139 None,
140 );
141
142 let context = CrateContext {
143 name: "mock_crate_a".to_owned(),
144 version: VERSION_ZERO_ONE_ZERO,
145 package_url: None,
146 repository: None,
147 targets: BTreeSet::default(),
148 library_target_name: None,
149 common_attrs: CommonAttributes {
150 deps,
151 ..CommonAttributes::default()
152 },
153 build_script_attrs: None,
154 license: None,
155 license_ids: BTreeSet::default(),
156 license_file: None,
157 additive_build_file_content: None,
158 disable_pipelining: false,
159 extra_aliased_targets: BTreeMap::default(),
160 alias_rule: None,
161 override_targets: BTreeMap::default(),
162 };
163
164 let configurations =
165 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
166
167 assert_eq!(
168 configurations,
169 BTreeMap::from([
170 // All known triples.
171 (
172 "aarch64-apple-darwin".to_owned(),
173 BTreeSet::from([TargetTriple::from_bazel("aarch64-apple-darwin".to_owned())]),
174 ),
175 (
176 "i686-apple-darwin".to_owned(),
177 BTreeSet::from([TargetTriple::from_bazel("i686-apple-darwin".to_owned())]),
178 ),
179 (
180 "x86_64-unknown-linux-gnu".to_owned(),
181 BTreeSet::from([TargetTriple::from_bazel(
182 "x86_64-unknown-linux-gnu".to_owned()
183 )]),
184 ),
185 ])
186 )
187 }
188
mock_resolve_context(configuration: String) -> CrateContext189 fn mock_resolve_context(configuration: String) -> CrateContext {
190 let mut deps: Select<BTreeSet<CrateDependency>> = Select::default();
191 deps.insert(
192 CrateDependency {
193 id: CrateId::new("mock_crate_b".to_owned(), VERSION_ZERO_ONE_ZERO),
194 target: "mock_crate_b".to_owned(),
195 alias: None,
196 },
197 Some(configuration),
198 );
199
200 CrateContext {
201 name: "mock_crate_a".to_owned(),
202 version: VERSION_ZERO_ONE_ZERO,
203 package_url: None,
204 repository: None,
205 targets: BTreeSet::default(),
206 library_target_name: None,
207 common_attrs: CommonAttributes {
208 deps,
209 ..CommonAttributes::default()
210 },
211 build_script_attrs: None,
212 license: None,
213 license_ids: BTreeSet::default(),
214 license_file: None,
215 additive_build_file_content: None,
216 disable_pipelining: false,
217 extra_aliased_targets: BTreeMap::default(),
218 alias_rule: None,
219 override_targets: BTreeMap::default(),
220 }
221 }
222
223 #[test]
resolve_targeted()224 fn resolve_targeted() {
225 let data = BTreeMap::from([
226 (
227 r#"cfg(target = "x86_64-unknown-linux-gnu")"#.to_owned(),
228 BTreeSet::from([TargetTriple::from_bazel(
229 "x86_64-unknown-linux-gnu".to_owned(),
230 )]),
231 ),
232 (
233 r#"cfg(any(target_os = "macos", target_os = "ios"))"#.to_owned(),
234 BTreeSet::from([
235 TargetTriple::from_bazel("aarch64-apple-darwin".to_owned()),
236 TargetTriple::from_bazel("i686-apple-darwin".to_owned()),
237 ]),
238 ),
239 ]);
240
241 data.into_iter().for_each(|(configuration, expectation)| {
242 let context = mock_resolve_context(configuration.clone());
243
244 let configurations =
245 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
246
247 assert_eq!(
248 configurations,
249 BTreeMap::from([
250 (configuration, expectation),
251 // All known triples.
252 (
253 "aarch64-apple-darwin".to_owned(),
254 BTreeSet::from([TargetTriple::from_bazel(
255 "aarch64-apple-darwin".to_owned()
256 )]),
257 ),
258 (
259 "i686-apple-darwin".to_owned(),
260 BTreeSet::from([TargetTriple::from_bazel("i686-apple-darwin".to_owned())]),
261 ),
262 (
263 "x86_64-unknown-linux-gnu".to_owned(),
264 BTreeSet::from([TargetTriple::from_bazel(
265 "x86_64-unknown-linux-gnu".to_owned()
266 )]),
267 ),
268 ])
269 );
270 })
271 }
272
273 #[test]
resolve_platforms()274 fn resolve_platforms() {
275 let configuration = r#"x86_64-unknown-linux-gnu"#.to_owned();
276 let mut deps: Select<BTreeSet<CrateDependency>> = Select::default();
277 deps.insert(
278 CrateDependency {
279 id: CrateId::new("mock_crate_b".to_owned(), VERSION_ZERO_ONE_ZERO),
280 target: "mock_crate_b".to_owned(),
281 alias: None,
282 },
283 Some(configuration.clone()),
284 );
285
286 let context = CrateContext {
287 name: "mock_crate_a".to_owned(),
288 version: VERSION_ZERO_ONE_ZERO,
289 package_url: None,
290 repository: None,
291 targets: BTreeSet::default(),
292 library_target_name: None,
293 common_attrs: CommonAttributes {
294 deps,
295 ..CommonAttributes::default()
296 },
297 build_script_attrs: None,
298 license: None,
299 license_ids: BTreeSet::default(),
300 license_file: None,
301 additive_build_file_content: None,
302 disable_pipelining: false,
303 extra_aliased_targets: BTreeMap::default(),
304 alias_rule: None,
305 override_targets: BTreeMap::default(),
306 };
307
308 let configurations =
309 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
310
311 assert_eq!(
312 configurations,
313 BTreeMap::from([
314 (
315 configuration,
316 BTreeSet::from([TargetTriple::from_bazel(
317 "x86_64-unknown-linux-gnu".to_owned()
318 )])
319 ),
320 // All known triples.
321 (
322 "aarch64-apple-darwin".to_owned(),
323 BTreeSet::from([TargetTriple::from_bazel("aarch64-apple-darwin".to_owned())]),
324 ),
325 (
326 "i686-apple-darwin".to_owned(),
327 BTreeSet::from([TargetTriple::from_bazel("i686-apple-darwin".to_owned())]),
328 ),
329 (
330 "x86_64-unknown-linux-gnu".to_owned(),
331 BTreeSet::from([TargetTriple::from_bazel(
332 "x86_64-unknown-linux-gnu".to_owned()
333 )]),
334 ),
335 ])
336 );
337 }
338
339 #[test]
resolve_unsupported_targeted()340 fn resolve_unsupported_targeted() {
341 let configuration = r#"cfg(target = "x86_64-unknown-unknown")"#.to_owned();
342 let mut deps: Select<BTreeSet<CrateDependency>> = Select::default();
343 deps.insert(
344 CrateDependency {
345 id: CrateId::new("mock_crate_b".to_owned(), VERSION_ZERO_ONE_ZERO),
346 target: "mock_crate_b".to_owned(),
347 alias: None,
348 },
349 Some(configuration.clone()),
350 );
351
352 let context = CrateContext {
353 name: "mock_crate_a".to_owned(),
354 version: VERSION_ZERO_ONE_ZERO,
355 package_url: None,
356 repository: None,
357 targets: BTreeSet::default(),
358 library_target_name: None,
359 common_attrs: CommonAttributes {
360 deps,
361 ..CommonAttributes::default()
362 },
363 build_script_attrs: None,
364 license: None,
365 license_ids: BTreeSet::default(),
366 license_file: None,
367 additive_build_file_content: None,
368 disable_pipelining: false,
369 extra_aliased_targets: BTreeMap::default(),
370 alias_rule: None,
371 override_targets: BTreeMap::default(),
372 };
373
374 let configurations =
375 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
376
377 assert_eq!(
378 configurations,
379 BTreeMap::from([
380 (configuration, BTreeSet::new()),
381 // All known triples.
382 (
383 "aarch64-apple-darwin".to_owned(),
384 BTreeSet::from([TargetTriple::from_bazel("aarch64-apple-darwin".to_owned())]),
385 ),
386 (
387 "i686-apple-darwin".to_owned(),
388 BTreeSet::from([TargetTriple::from_bazel("i686-apple-darwin".to_owned())]),
389 ),
390 (
391 "x86_64-unknown-linux-gnu".to_owned(),
392 BTreeSet::from([TargetTriple::from_bazel(
393 "x86_64-unknown-linux-gnu".to_owned()
394 )]),
395 ),
396 ])
397 );
398 }
399 }
400