Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 1 | use std::collections::{BTreeMap, BTreeSet}; |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 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::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. |
| 12 | pub 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 Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 59 | let rename = |cfg: &str| -> String { format!("cfg(target = \"{cfg}\")") }; |
| 60 | let original_cfgs: BTreeMap<String, String> = configurations |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 61 | .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 Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 76 | let expression = |
| 77 | Expression::parse(&cfg).context(format!("Failed to parse expression: '{cfg}'"))?; |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 78 | |
| 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)] |
| 106 | mod 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 Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 119 | "aarch64-pc-windows-msvc".to_owned(), |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 120 | "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 Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 169 | fn mock_resolve_context(configuration: String) -> CrateContext { |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 170 | 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 Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 177 | Some(configuration), |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 178 | ); |
| 179 | |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 180 | CrateContext { |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 181 | 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 Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 188 | } |
| 189 | } |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 190 | |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 191 | #[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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 209 | |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 210 | 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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 221 | } |
| 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 | } |