blob: fc4e78956614f0dca4a3923fa605446ae20d2ae5 [file] [log] [blame]
Adam Snaider1c095c92023-07-08 02:09:58 -04001use std::collections::{BTreeMap, BTreeSet};
Brian Silvermancc09f182022-03-09 15:40:20 -08002
3use anyhow::{anyhow, Context, Result};
4use cfg_expr::targets::{get_builtin_target_by_triple, TargetInfo};
5use cfg_expr::{Expression, Predicate};
6
7use crate::context::CrateContext;
8use crate::utils::starlark::Select;
9
10/// Walk through all dependencies in a [CrateContext] list for all configuration specific
11/// dependencies to produce a mapping of configuration to compatible platform triples.
12pub fn resolve_cfg_platforms(
13 crates: Vec<&CrateContext>,
14 supported_platform_triples: &BTreeSet<String>,
15) -> Result<BTreeMap<String, BTreeSet<String>>> {
16 // Collect all unique configurations from all dependencies into a single set
17 let configurations: BTreeSet<String> = crates
18 .iter()
19 .flat_map(|ctx| {
20 let attr = &ctx.common_attrs;
21 attr.deps
22 .configurations()
23 .into_iter()
24 .chain(attr.deps_dev.configurations().into_iter())
25 .chain(attr.proc_macro_deps.configurations().into_iter())
26 .chain(attr.proc_macro_deps_dev.configurations().into_iter())
27 // Chain the build dependencies if some are defined
28 .chain(if let Some(attr) = &ctx.build_script_attrs {
29 attr.deps
30 .configurations()
31 .into_iter()
32 .chain(attr.proc_macro_deps.configurations().into_iter())
33 .collect::<BTreeSet<Option<&String>>>()
34 .into_iter()
35 } else {
36 BTreeSet::new().into_iter()
37 })
38 .flatten()
39 })
40 .cloned()
41 .collect();
42
43 // Generate target information for each triple string
44 let target_infos = supported_platform_triples
45 .iter()
46 .map(|t| match get_builtin_target_by_triple(t) {
47 Some(info) => Ok(info),
48 None => Err(anyhow!(
49 "Invalid platform triple in supported platforms: {}",
50 t
51 )),
52 })
53 .collect::<Result<Vec<&'static TargetInfo>>>()?;
54
55 // `cfg-expr` does not understand configurations that are simply platform triples
56 // (`x86_64-unknown-linux-gun` vs `cfg(target = "x86_64-unkonwn-linux-gnu")`). So
57 // in order to parse configurations, the text is renamed for the check but the
58 // original is retained for comaptibility with the manifest.
Adam Snaider1c095c92023-07-08 02:09:58 -040059 let rename = |cfg: &str| -> String { format!("cfg(target = \"{cfg}\")") };
60 let original_cfgs: BTreeMap<String, String> = configurations
Brian Silvermancc09f182022-03-09 15:40:20 -080061 .iter()
62 .filter(|cfg| !cfg.starts_with("cfg("))
63 .map(|cfg| (rename(cfg), cfg.clone()))
64 .collect();
65
66 configurations
67 .into_iter()
68 // `cfg-expr` requires that the expressions be actual `cfg` expressions. Any time
69 // there's a target triple (which is a valid constraint), convert it to a cfg expression.
70 .map(|cfg| match cfg.starts_with("cfg(") {
71 true => cfg.to_string(),
72 false => rename(&cfg),
73 })
74 // Check the current configuration with against each supported triple
75 .map(|cfg| {
Adam Snaider1c095c92023-07-08 02:09:58 -040076 let expression =
77 Expression::parse(&cfg).context(format!("Failed to parse expression: '{cfg}'"))?;
Brian Silvermancc09f182022-03-09 15:40:20 -080078
79 let triples = target_infos
80 .iter()
81 .filter(|info| {
82 expression.eval(|p| match p {
83 Predicate::Target(tp) => tp.matches(**info),
84 Predicate::KeyValue { key, val } => {
85 *key == "target" && val == &info.triple.as_str()
86 }
87 // For now there is no other kind of matching
88 _ => false,
89 })
90 })
91 .map(|info| info.triple.to_string())
92 .collect();
93
94 // Map any renamed configurations back to their original IDs
95 let cfg = match original_cfgs.get(&cfg) {
96 Some(orig) => orig.clone(),
97 None => cfg,
98 };
99
100 Ok((cfg, triples))
101 })
102 .collect()
103}
104
105#[cfg(test)]
106mod test {
107 use crate::config::CrateId;
108 use crate::context::crate_context::CrateDependency;
109 use crate::context::CommonAttributes;
110 use crate::utils::starlark::SelectList;
111
112 use super::*;
113
114 fn supported_platform_triples() -> BTreeSet<String> {
115 BTreeSet::from([
116 "aarch64-apple-darwin".to_owned(),
117 "aarch64-apple-ios".to_owned(),
118 "aarch64-linux-android".to_owned(),
Adam Snaider1c095c92023-07-08 02:09:58 -0400119 "aarch64-pc-windows-msvc".to_owned(),
Brian Silvermancc09f182022-03-09 15:40:20 -0800120 "aarch64-unknown-linux-gnu".to_owned(),
121 "arm-unknown-linux-gnueabi".to_owned(),
122 "armv7-unknown-linux-gnueabi".to_owned(),
123 "i686-apple-darwin".to_owned(),
124 "i686-linux-android".to_owned(),
125 "i686-pc-windows-msvc".to_owned(),
126 "i686-unknown-freebsd".to_owned(),
127 "i686-unknown-linux-gnu".to_owned(),
128 "powerpc-unknown-linux-gnu".to_owned(),
129 "s390x-unknown-linux-gnu".to_owned(),
130 "wasm32-unknown-unknown".to_owned(),
131 "wasm32-wasi".to_owned(),
132 "x86_64-apple-darwin".to_owned(),
133 "x86_64-apple-ios".to_owned(),
134 "x86_64-linux-android".to_owned(),
135 "x86_64-pc-windows-msvc".to_owned(),
136 "x86_64-unknown-freebsd".to_owned(),
137 "x86_64-unknown-linux-gnu".to_owned(),
138 ])
139 }
140
141 #[test]
142 fn resolve_no_targeted() {
143 let mut deps = SelectList::default();
144 deps.insert(
145 CrateDependency {
146 id: CrateId::new("mock_crate_b".to_owned(), "0.1.0".to_owned()),
147 target: "mock_crate_b".to_owned(),
148 alias: None,
149 },
150 None,
151 );
152
153 let context = CrateContext {
154 name: "mock_crate_a".to_owned(),
155 version: "0.1.0".to_owned(),
156 common_attrs: CommonAttributes {
157 deps,
158 ..CommonAttributes::default()
159 },
160 ..CrateContext::default()
161 };
162
163 let configurations =
164 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
165
166 assert_eq!(configurations, BTreeMap::new(),)
167 }
168
Adam Snaider1c095c92023-07-08 02:09:58 -0400169 fn mock_resolve_context(configuration: String) -> CrateContext {
Brian Silvermancc09f182022-03-09 15:40:20 -0800170 let mut deps = SelectList::default();
171 deps.insert(
172 CrateDependency {
173 id: CrateId::new("mock_crate_b".to_owned(), "0.1.0".to_owned()),
174 target: "mock_crate_b".to_owned(),
175 alias: None,
176 },
Adam Snaider1c095c92023-07-08 02:09:58 -0400177 Some(configuration),
Brian Silvermancc09f182022-03-09 15:40:20 -0800178 );
179
Adam Snaider1c095c92023-07-08 02:09:58 -0400180 CrateContext {
Brian Silvermancc09f182022-03-09 15:40:20 -0800181 name: "mock_crate_a".to_owned(),
182 version: "0.1.0".to_owned(),
183 common_attrs: CommonAttributes {
184 deps,
185 ..CommonAttributes::default()
186 },
187 ..CrateContext::default()
Adam Snaider1c095c92023-07-08 02:09:58 -0400188 }
189 }
Brian Silvermancc09f182022-03-09 15:40:20 -0800190
Adam Snaider1c095c92023-07-08 02:09:58 -0400191 #[test]
192 fn resolve_targeted() {
193 let data = BTreeMap::from([
194 (
195 r#"cfg(target = "x86_64-unknown-linux-gnu")"#.to_owned(),
196 BTreeSet::from(["x86_64-unknown-linux-gnu".to_owned()]),
197 ),
198 (
199 r#"cfg(any(target_os = "macos", target_os = "ios"))"#.to_owned(),
200 BTreeSet::from([
201 "aarch64-apple-darwin".to_owned(),
202 "aarch64-apple-ios".to_owned(),
203 "i686-apple-darwin".to_owned(),
204 "x86_64-apple-darwin".to_owned(),
205 "x86_64-apple-ios".to_owned(),
206 ]),
207 ),
208 ]);
Brian Silvermancc09f182022-03-09 15:40:20 -0800209
Adam Snaider1c095c92023-07-08 02:09:58 -0400210 data.into_iter().for_each(|(configuration, expectation)| {
211 let context = mock_resolve_context(configuration.clone());
212
213 let configurations =
214 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
215
216 assert_eq!(
217 configurations,
218 BTreeMap::from([(configuration, expectation,)])
219 );
220 })
Brian Silvermancc09f182022-03-09 15:40:20 -0800221 }
222
223 #[test]
224 fn resolve_platforms() {
225 let configuration = r#"x86_64-unknown-linux-gnu"#.to_owned();
226 let mut deps = SelectList::default();
227 deps.insert(
228 CrateDependency {
229 id: CrateId::new("mock_crate_b".to_owned(), "0.1.0".to_owned()),
230 target: "mock_crate_b".to_owned(),
231 alias: None,
232 },
233 Some(configuration.clone()),
234 );
235
236 let context = CrateContext {
237 name: "mock_crate_a".to_owned(),
238 version: "0.1.0".to_owned(),
239 common_attrs: CommonAttributes {
240 deps,
241 ..CommonAttributes::default()
242 },
243 ..CrateContext::default()
244 };
245
246 let configurations =
247 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
248
249 assert_eq!(
250 configurations,
251 BTreeMap::from([(
252 configuration,
253 BTreeSet::from(["x86_64-unknown-linux-gnu".to_owned()])
254 )])
255 );
256 }
257
258 #[test]
259 fn resolve_unsupported_targeted() {
260 let configuration = r#"cfg(target = "x86_64-unknown-unknown")"#.to_owned();
261 let mut deps = SelectList::default();
262 deps.insert(
263 CrateDependency {
264 id: CrateId::new("mock_crate_b".to_owned(), "0.1.0".to_owned()),
265 target: "mock_crate_b".to_owned(),
266 alias: None,
267 },
268 Some(configuration.clone()),
269 );
270
271 let context = CrateContext {
272 name: "mock_crate_a".to_owned(),
273 version: "0.1.0".to_owned(),
274 common_attrs: CommonAttributes {
275 deps,
276 ..CommonAttributes::default()
277 },
278 ..CrateContext::default()
279 };
280
281 let configurations =
282 resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
283
284 assert_eq!(
285 configurations,
286 BTreeMap::from([(configuration, BTreeSet::new())])
287 );
288 }
289}