• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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