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 };
162
163 let configurations =
164 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
165
166 assert_eq!(
167 configurations,
168 BTreeMap::from([
169 // All known triples.
170 (
171 "aarch64-apple-darwin".to_owned(),
172 BTreeSet::from([TargetTriple::from_bazel("aarch64-apple-darwin".to_owned())]),
173 ),
174 (
175 "i686-apple-darwin".to_owned(),
176 BTreeSet::from([TargetTriple::from_bazel("i686-apple-darwin".to_owned())]),
177 ),
178 (
179 "x86_64-unknown-linux-gnu".to_owned(),
180 BTreeSet::from([TargetTriple::from_bazel(
181 "x86_64-unknown-linux-gnu".to_owned()
182 )]),
183 ),
184 ])
185 )
186 }
187
mock_resolve_context(configuration: String) -> CrateContext188 fn mock_resolve_context(configuration: String) -> CrateContext {
189 let mut deps: Select<BTreeSet<CrateDependency>> = Select::default();
190 deps.insert(
191 CrateDependency {
192 id: CrateId::new("mock_crate_b".to_owned(), VERSION_ZERO_ONE_ZERO),
193 target: "mock_crate_b".to_owned(),
194 alias: None,
195 },
196 Some(configuration),
197 );
198
199 CrateContext {
200 name: "mock_crate_a".to_owned(),
201 version: VERSION_ZERO_ONE_ZERO,
202 package_url: None,
203 repository: None,
204 targets: BTreeSet::default(),
205 library_target_name: None,
206 common_attrs: CommonAttributes {
207 deps,
208 ..CommonAttributes::default()
209 },
210 build_script_attrs: None,
211 license: None,
212 license_ids: BTreeSet::default(),
213 license_file: None,
214 additive_build_file_content: None,
215 disable_pipelining: false,
216 extra_aliased_targets: BTreeMap::default(),
217 alias_rule: None,
218 }
219 }
220
221 #[test]
resolve_targeted()222 fn resolve_targeted() {
223 let data = BTreeMap::from([
224 (
225 r#"cfg(target = "x86_64-unknown-linux-gnu")"#.to_owned(),
226 BTreeSet::from([TargetTriple::from_bazel(
227 "x86_64-unknown-linux-gnu".to_owned(),
228 )]),
229 ),
230 (
231 r#"cfg(any(target_os = "macos", target_os = "ios"))"#.to_owned(),
232 BTreeSet::from([
233 TargetTriple::from_bazel("aarch64-apple-darwin".to_owned()),
234 TargetTriple::from_bazel("i686-apple-darwin".to_owned()),
235 ]),
236 ),
237 ]);
238
239 data.into_iter().for_each(|(configuration, expectation)| {
240 let context = mock_resolve_context(configuration.clone());
241
242 let configurations =
243 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
244
245 assert_eq!(
246 configurations,
247 BTreeMap::from([
248 (configuration, expectation,),
249 // All known triples.
250 (
251 "aarch64-apple-darwin".to_owned(),
252 BTreeSet::from([TargetTriple::from_bazel(
253 "aarch64-apple-darwin".to_owned()
254 )]),
255 ),
256 (
257 "i686-apple-darwin".to_owned(),
258 BTreeSet::from([TargetTriple::from_bazel("i686-apple-darwin".to_owned())]),
259 ),
260 (
261 "x86_64-unknown-linux-gnu".to_owned(),
262 BTreeSet::from([TargetTriple::from_bazel(
263 "x86_64-unknown-linux-gnu".to_owned()
264 )]),
265 ),
266 ])
267 );
268 })
269 }
270
271 #[test]
resolve_platforms()272 fn resolve_platforms() {
273 let configuration = r#"x86_64-unknown-linux-gnu"#.to_owned();
274 let mut deps: Select<BTreeSet<CrateDependency>> = Select::default();
275 deps.insert(
276 CrateDependency {
277 id: CrateId::new("mock_crate_b".to_owned(), VERSION_ZERO_ONE_ZERO),
278 target: "mock_crate_b".to_owned(),
279 alias: None,
280 },
281 Some(configuration.clone()),
282 );
283
284 let context = CrateContext {
285 name: "mock_crate_a".to_owned(),
286 version: VERSION_ZERO_ONE_ZERO,
287 package_url: None,
288 repository: None,
289 targets: BTreeSet::default(),
290 library_target_name: None,
291 common_attrs: CommonAttributes {
292 deps,
293 ..CommonAttributes::default()
294 },
295 build_script_attrs: None,
296 license: None,
297 license_ids: BTreeSet::default(),
298 license_file: None,
299 additive_build_file_content: None,
300 disable_pipelining: false,
301 extra_aliased_targets: BTreeMap::default(),
302 alias_rule: None,
303 };
304
305 let configurations =
306 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
307
308 assert_eq!(
309 configurations,
310 BTreeMap::from([
311 (
312 configuration,
313 BTreeSet::from([TargetTriple::from_bazel(
314 "x86_64-unknown-linux-gnu".to_owned()
315 )])
316 ),
317 // All known triples.
318 (
319 "aarch64-apple-darwin".to_owned(),
320 BTreeSet::from([TargetTriple::from_bazel("aarch64-apple-darwin".to_owned())]),
321 ),
322 (
323 "i686-apple-darwin".to_owned(),
324 BTreeSet::from([TargetTriple::from_bazel("i686-apple-darwin".to_owned())]),
325 ),
326 (
327 "x86_64-unknown-linux-gnu".to_owned(),
328 BTreeSet::from([TargetTriple::from_bazel(
329 "x86_64-unknown-linux-gnu".to_owned()
330 )]),
331 ),
332 ])
333 );
334 }
335
336 #[test]
resolve_unsupported_targeted()337 fn resolve_unsupported_targeted() {
338 let configuration = r#"cfg(target = "x86_64-unknown-unknown")"#.to_owned();
339 let mut deps: Select<BTreeSet<CrateDependency>> = Select::default();
340 deps.insert(
341 CrateDependency {
342 id: CrateId::new("mock_crate_b".to_owned(), VERSION_ZERO_ONE_ZERO),
343 target: "mock_crate_b".to_owned(),
344 alias: None,
345 },
346 Some(configuration.clone()),
347 );
348
349 let context = CrateContext {
350 name: "mock_crate_a".to_owned(),
351 version: VERSION_ZERO_ONE_ZERO,
352 package_url: None,
353 repository: None,
354 targets: BTreeSet::default(),
355 library_target_name: None,
356 common_attrs: CommonAttributes {
357 deps,
358 ..CommonAttributes::default()
359 },
360 build_script_attrs: None,
361 license: None,
362 license_ids: BTreeSet::default(),
363 license_file: None,
364 additive_build_file_content: None,
365 disable_pipelining: false,
366 extra_aliased_targets: BTreeMap::default(),
367 alias_rule: None,
368 };
369
370 let configurations =
371 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
372
373 assert_eq!(
374 configurations,
375 BTreeMap::from([
376 (configuration, BTreeSet::new()),
377 // All known triples.
378 (
379 "aarch64-apple-darwin".to_owned(),
380 BTreeSet::from([TargetTriple::from_bazel("aarch64-apple-darwin".to_owned())]),
381 ),
382 (
383 "i686-apple-darwin".to_owned(),
384 BTreeSet::from([TargetTriple::from_bazel("i686-apple-darwin".to_owned())]),
385 ),
386 (
387 "x86_64-unknown-linux-gnu".to_owned(),
388 BTreeSet::from([TargetTriple::from_bazel(
389 "x86_64-unknown-linux-gnu".to_owned()
390 )]),
391 ),
392 ])
393 );
394 }
395 }
396