xref: /aosp_15_r20/development/tools/cargo_embargo/src/cargo/metadata.rs (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1 // Copyright (C) 2023 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Types for parsing cargo.metadata JSON files.
16 
17 use super::{Crate, CrateType, Extern, ExternType};
18 use crate::config::VariantConfig;
19 use anyhow::{bail, Context, Result};
20 use serde::Deserialize;
21 use std::collections::BTreeMap;
22 use std::path::{Path, PathBuf};
23 
24 /// `cfg` strings for dependencies which should be considered enabled. It would be better to parse
25 /// them properly, but this is good enough in practice so far.
26 const ENABLED_CFGS: [&str; 6] = [
27     r#"unix"#,
28     r#"not(windows)"#,
29     r#"any(unix, target_os = "wasi")"#,
30     r#"not(all(target_family = "wasm", target_os = "unknown"))"#,
31     r#"not(target_family = "wasm")"#,
32     r#"any(target_os = "linux", target_os = "android")"#,
33 ];
34 
35 /// `cargo metadata` output.
36 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
37 pub struct WorkspaceMetadata {
38     pub packages: Vec<PackageMetadata>,
39     pub workspace_members: Vec<String>,
40 }
41 
42 #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
43 pub struct PackageMetadata {
44     pub name: String,
45     pub version: String,
46     pub edition: String,
47     pub manifest_path: String,
48     pub dependencies: Vec<DependencyMetadata>,
49     pub features: BTreeMap<String, Vec<String>>,
50     pub id: String,
51     pub targets: Vec<TargetMetadata>,
52     pub license: Option<String>,
53     pub license_file: Option<String>,
54 }
55 
56 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
57 pub struct DependencyMetadata {
58     pub name: String,
59     pub kind: Option<String>,
60     pub optional: bool,
61     pub target: Option<String>,
62     pub rename: Option<String>,
63 }
64 
65 impl DependencyMetadata {
66     /// Returns whether the dependency should be included when the given features are enabled.
enabled(&self, features: &[String], cfgs: &[String]) -> bool67     fn enabled(&self, features: &[String], cfgs: &[String]) -> bool {
68         if let Some(target) = &self.target {
69             if target.starts_with("cfg(") && target.ends_with(')') {
70                 let target_cfg = &target[4..target.len() - 1];
71                 if !ENABLED_CFGS.contains(&target_cfg) && !cfgs.contains(&target_cfg.to_string()) {
72                     return false;
73                 }
74             }
75         }
76         let name = self.rename.as_ref().unwrap_or(&self.name);
77         !self.optional || features.contains(&format!("dep:{}", name))
78     }
79 }
80 
81 #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
82 #[allow(dead_code)]
83 pub struct TargetMetadata {
84     pub crate_types: Vec<CrateType>,
85     pub doc: bool,
86     pub doctest: bool,
87     pub edition: String,
88     pub kind: Vec<TargetKind>,
89     pub name: String,
90     pub src_path: PathBuf,
91     pub test: bool,
92 }
93 
94 #[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq)]
95 #[serde[rename_all = "kebab-case"]]
96 pub enum TargetKind {
97     Bin,
98     CustomBuild,
99     Bench,
100     Example,
101     Lib,
102     Rlib,
103     Staticlib,
104     Cdylib,
105     ProcMacro,
106     Test,
107 }
108 
parse_cargo_metadata_str(cargo_metadata: &str, cfg: &VariantConfig) -> Result<Vec<Crate>>109 pub fn parse_cargo_metadata_str(cargo_metadata: &str, cfg: &VariantConfig) -> Result<Vec<Crate>> {
110     let metadata =
111         serde_json::from_str(cargo_metadata).context("failed to parse cargo metadata")?;
112     parse_cargo_metadata(
113         &metadata,
114         &cfg.features,
115         &cfg.extra_cfg,
116         cfg.tests,
117         &cfg.workspace_excludes,
118     )
119 }
120 
parse_cargo_metadata( metadata: &WorkspaceMetadata, features: &Option<Vec<String>>, cfgs: &[String], include_tests: bool, workspace_excludes: &[String], ) -> Result<Vec<Crate>>121 fn parse_cargo_metadata(
122     metadata: &WorkspaceMetadata,
123     features: &Option<Vec<String>>,
124     cfgs: &[String],
125     include_tests: bool,
126     workspace_excludes: &[String],
127 ) -> Result<Vec<Crate>> {
128     let mut crates = Vec::new();
129     for package in &metadata.packages {
130         if !metadata.workspace_members.contains(&package.id)
131             || workspace_excludes.contains(&package.name)
132         {
133             continue;
134         }
135 
136         let features = resolve_features(features, &package.features, &package.dependencies);
137         let features_without_deps: Vec<String> =
138             features.clone().into_iter().filter(|feature| !feature.starts_with("dep:")).collect();
139         let package_dir = package_dir_from_id(&package.id)?;
140 
141         for target in &package.targets {
142             let target_kinds = target
143                 .kind
144                 .clone()
145                 .into_iter()
146                 .filter(|kind| {
147                     [
148                         TargetKind::Bin,
149                         TargetKind::Cdylib,
150                         TargetKind::Lib,
151                         TargetKind::ProcMacro,
152                         TargetKind::Rlib,
153                         TargetKind::Staticlib,
154                         TargetKind::Test,
155                     ]
156                     .contains(kind)
157                 })
158                 .collect::<Vec<_>>();
159             if target_kinds.is_empty() {
160                 // Only binaries, libraries and integration tests are supported.
161                 continue;
162             }
163             let main_src = split_src_path(&target.src_path, &package_dir);
164             // Hypens are not allowed in crate names. See
165             // https://github.com/rust-lang/rfcs/blob/master/text/0940-hyphens-considered-harmful.md
166             // for background.
167             let target_name = target.name.replace('-', "_");
168             let target_triple = if target_kinds == [TargetKind::ProcMacro] {
169                 None
170             } else {
171                 Some("x86_64-unknown-linux-gnu".to_string())
172             };
173             // Don't generate an entry for integration tests, they will be covered by the test case
174             // below.
175             if target_kinds != [TargetKind::Test] {
176                 crates.push(Crate {
177                     name: target_name.clone(),
178                     package_name: package.name.to_owned(),
179                     version: Some(package.version.to_owned()),
180                     types: target.crate_types.clone(),
181                     features: features_without_deps.clone(),
182                     edition: package.edition.to_owned(),
183                     license: package.license.clone(),
184                     license_file: package.license_file.clone(),
185                     package_dir: package_dir.clone(),
186                     main_src: main_src.to_owned(),
187                     target: target_triple.clone(),
188                     externs: get_externs(
189                         package,
190                         &metadata.packages,
191                         &features,
192                         cfgs,
193                         &target_kinds,
194                         false,
195                     )?,
196                     cfgs: cfgs.to_owned(),
197                     ..Default::default()
198                 });
199             }
200             // This includes both unit tests and integration tests.
201             if target.test && include_tests {
202                 crates.push(Crate {
203                     name: target_name,
204                     package_name: package.name.to_owned(),
205                     version: Some(package.version.to_owned()),
206                     types: vec![CrateType::Test],
207                     features: features_without_deps.clone(),
208                     edition: package.edition.to_owned(),
209                     license: package.license.clone(),
210                     license_file: package.license_file.clone(),
211                     package_dir: package_dir.clone(),
212                     main_src: main_src.to_owned(),
213                     target: target_triple.clone(),
214                     externs: get_externs(
215                         package,
216                         &metadata.packages,
217                         &features,
218                         cfgs,
219                         &target_kinds,
220                         true,
221                     )?,
222                     cfgs: cfgs.to_owned(),
223                     ..Default::default()
224                 });
225             }
226         }
227     }
228     Ok(crates)
229 }
230 
get_externs( package: &PackageMetadata, packages: &[PackageMetadata], features: &[String], cfgs: &[String], target_kinds: &[TargetKind], test: bool, ) -> Result<Vec<Extern>>231 fn get_externs(
232     package: &PackageMetadata,
233     packages: &[PackageMetadata],
234     features: &[String],
235     cfgs: &[String],
236     target_kinds: &[TargetKind],
237     test: bool,
238 ) -> Result<Vec<Extern>> {
239     let mut externs = package
240         .dependencies
241         .iter()
242         .filter_map(|dependency| {
243             // Kind is None for normal dependencies, as opposed to dev dependencies.
244             if dependency.enabled(features, cfgs)
245                 && dependency.kind.as_deref() != Some("build")
246                 && (dependency.kind.is_none() || test)
247             {
248                 Some(make_extern(packages, dependency))
249             } else {
250                 None
251             }
252         })
253         .collect::<Result<Vec<Extern>>>()?;
254 
255     // If there is a library target and this is a binary or integration test, add the library as an
256     // extern.
257     if matches!(target_kinds, [TargetKind::Bin] | [TargetKind::Test]) {
258         for target in &package.targets {
259             if target.kind.contains(&TargetKind::Lib) {
260                 let lib_name = target.name.replace('-', "_");
261                 externs.push(Extern {
262                     name: lib_name.clone(),
263                     lib_name,
264                     raw_name: target.name.clone(),
265                     extern_type: ExternType::Rust,
266                 });
267             }
268         }
269     }
270 
271     externs.sort();
272     externs.dedup();
273     Ok(externs)
274 }
275 
make_extern(packages: &[PackageMetadata], dependency: &DependencyMetadata) -> Result<Extern>276 fn make_extern(packages: &[PackageMetadata], dependency: &DependencyMetadata) -> Result<Extern> {
277     let Some(package) = packages.iter().find(|package| package.name == dependency.name) else {
278         bail!("package {} not found in metadata", dependency.name);
279     };
280     let Some(target) = package.targets.iter().find(|target| {
281         target.kind.contains(&TargetKind::Lib) || target.kind.contains(&TargetKind::ProcMacro)
282     }) else {
283         bail!("Package {} didn't have any library or proc-macro targets", dependency.name);
284     };
285     let lib_name = target.name.replace('-', "_");
286     // This is ugly but looking at the source path is the easiest way to tell if the raw
287     // crate name uses a hyphen instead of an underscore. It won't work if it uses both.
288     let raw_name = target.name.replace('_', "-");
289     let src_path = target.src_path.to_str().expect("failed to convert src_path to string");
290     let raw_name = if src_path.contains(&raw_name) { raw_name } else { lib_name.clone() };
291     let name =
292         if let Some(rename) = &dependency.rename { rename.clone() } else { lib_name.clone() };
293 
294     // Check whether the package is a proc macro.
295     let extern_type =
296         if package.targets.iter().any(|target| target.kind.contains(&TargetKind::ProcMacro)) {
297             ExternType::ProcMacro
298         } else {
299             ExternType::Rust
300         };
301     Ok(Extern { name, lib_name, raw_name, extern_type })
302 }
303 
304 /// Given a Cargo package ID, returns the path.
305 ///
306 /// Extracts `"/path/to/crate"` from
307 /// `"path+file:///path/to/crate#1.2.3"`. See
308 /// https://doc.rust-lang.org/cargo/reference/pkgid-spec.html for
309 /// information on Cargo package ID specifications.
package_dir_from_id(id: &str) -> Result<PathBuf>310 fn package_dir_from_id(id: &str) -> Result<PathBuf> {
311     const PREFIX: &str = "path+file://";
312     const SEPARATOR: char = '#';
313     let Some(stripped) = id.strip_prefix(PREFIX) else {
314         bail!("Invalid package ID {id:?}, expected it to start with {PREFIX:?}");
315     };
316     let Some(idx) = stripped.rfind(SEPARATOR) else {
317         bail!("Invalid package ID {id:?}, expected it to contain {SEPARATOR:?}");
318     };
319     Ok(PathBuf::from(stripped[..idx].to_string()))
320 }
321 
split_src_path<'a>(src_path: &'a Path, package_dir: &Path) -> &'a Path322 fn split_src_path<'a>(src_path: &'a Path, package_dir: &Path) -> &'a Path {
323     if let Ok(main_src) = src_path.strip_prefix(package_dir) {
324         main_src
325     } else {
326         src_path
327     }
328 }
329 
330 /// Given a set of chosen features, and the feature dependencies from a package's metadata, returns
331 /// the full set of features which should be enabled.
resolve_features( chosen_features: &Option<Vec<String>>, package_features: &BTreeMap<String, Vec<String>>, dependencies: &[DependencyMetadata], ) -> Vec<String>332 fn resolve_features(
333     chosen_features: &Option<Vec<String>>,
334     package_features: &BTreeMap<String, Vec<String>>,
335     dependencies: &[DependencyMetadata],
336 ) -> Vec<String> {
337     let mut package_features = package_features.to_owned();
338     // Add implicit features for optional dependencies.
339     for dependency in dependencies {
340         if dependency.optional && !package_features.contains_key(&dependency.name) {
341             package_features
342                 .insert(dependency.name.to_owned(), vec![format!("dep:{}", dependency.name)]);
343         }
344     }
345 
346     let mut features = Vec::new();
347     if let Some(chosen_features) = chosen_features {
348         for feature in chosen_features {
349             add_feature_and_dependencies(&mut features, feature, &package_features);
350         }
351     } else {
352         // If there are no chosen features, then enable the default feature.
353         add_feature_and_dependencies(&mut features, "default", &package_features);
354     }
355     features.sort();
356     features.dedup();
357     features
358 }
359 
360 /// Adds the given feature and all features it depends on to the given list of features.
361 ///
362 /// Ignores features of other packages, and features which don't exist.
add_feature_and_dependencies( features: &mut Vec<String>, feature: &str, package_features: &BTreeMap<String, Vec<String>>, )363 fn add_feature_and_dependencies(
364     features: &mut Vec<String>,
365     feature: &str,
366     package_features: &BTreeMap<String, Vec<String>>,
367 ) {
368     if package_features.contains_key(feature) || feature.starts_with("dep:") {
369         features.push(feature.to_owned());
370     }
371 
372     if let Some(dependencies) = package_features.get(feature) {
373         for dependency in dependencies {
374             if let Some((dependency_package, _)) = dependency.split_once('/') {
375                 add_feature_and_dependencies(features, dependency_package, package_features);
376             } else {
377                 add_feature_and_dependencies(features, dependency, package_features);
378             }
379         }
380     }
381 }
382 
383 #[cfg(test)]
384 mod tests {
385     use super::*;
386     use crate::config::Config;
387     use crate::tests::testdata_directories;
388     use googletest::matchers::eq;
389     use googletest::prelude::assert_that;
390     use std::fs::{read_to_string, File};
391 
392     #[test]
extract_package_dir_from_id() -> Result<()>393     fn extract_package_dir_from_id() -> Result<()> {
394         assert_eq!(
395             package_dir_from_id("path+file:///path/to/crate#1.2.3")?,
396             PathBuf::from("/path/to/crate")
397         );
398         Ok(())
399     }
400 
401     #[test]
resolve_multi_level_feature_dependencies()402     fn resolve_multi_level_feature_dependencies() {
403         let chosen = vec!["default".to_string(), "extra".to_string(), "on_by_default".to_string()];
404         let package_features = [
405             (
406                 "default".to_string(),
407                 vec!["std".to_string(), "other".to_string(), "on_by_default".to_string()],
408             ),
409             ("std".to_string(), vec!["alloc".to_string()]),
410             ("not_enabled".to_string(), vec![]),
411             ("on_by_default".to_string(), vec![]),
412             ("other".to_string(), vec![]),
413             ("extra".to_string(), vec![]),
414             ("alloc".to_string(), vec![]),
415         ]
416         .into_iter()
417         .collect();
418         assert_eq!(
419             resolve_features(&Some(chosen), &package_features, &[]),
420             vec![
421                 "alloc".to_string(),
422                 "default".to_string(),
423                 "extra".to_string(),
424                 "on_by_default".to_string(),
425                 "other".to_string(),
426                 "std".to_string(),
427             ]
428         );
429     }
430 
431     #[test]
resolve_dep_features()432     fn resolve_dep_features() {
433         let package_features = [(
434             "default".to_string(),
435             vec![
436                 "optionaldep/feature".to_string(),
437                 "requireddep/feature".to_string(),
438                 "optionaldep2?/feature".to_string(),
439             ],
440         )]
441         .into_iter()
442         .collect();
443         let dependencies = vec![
444             DependencyMetadata {
445                 name: "optionaldep".to_string(),
446                 kind: None,
447                 optional: true,
448                 target: None,
449                 rename: None,
450             },
451             DependencyMetadata {
452                 name: "optionaldep2".to_string(),
453                 kind: None,
454                 optional: true,
455                 target: None,
456                 rename: None,
457             },
458             DependencyMetadata {
459                 name: "requireddep".to_string(),
460                 kind: None,
461                 optional: false,
462                 target: None,
463                 rename: None,
464             },
465         ];
466         assert_eq!(
467             resolve_features(&None, &package_features, &dependencies),
468             vec!["default".to_string(), "dep:optionaldep".to_string(), "optionaldep".to_string()]
469         );
470     }
471 
472     #[test]
get_externs_cfg()473     fn get_externs_cfg() {
474         let package = PackageMetadata {
475             name: "test_package".to_string(),
476             dependencies: vec![
477                 DependencyMetadata {
478                     name: "alwayslib".to_string(),
479                     kind: None,
480                     optional: false,
481                     target: None,
482                     rename: None,
483                 },
484                 DependencyMetadata {
485                     name: "unixlib".to_string(),
486                     kind: None,
487                     optional: false,
488                     target: Some("cfg(unix)".to_string()),
489                     rename: None,
490                 },
491                 DependencyMetadata {
492                     name: "windowslib".to_string(),
493                     kind: None,
494                     optional: false,
495                     target: Some("cfg(windows)".to_string()),
496                     rename: None,
497                 },
498             ],
499             features: [].into_iter().collect(),
500             targets: vec![],
501             ..Default::default()
502         };
503         let packages = vec![
504             package.clone(),
505             PackageMetadata {
506                 name: "alwayslib".to_string(),
507                 targets: vec![TargetMetadata {
508                     name: "alwayslib".to_string(),
509                     kind: vec![TargetKind::Lib],
510                     ..Default::default()
511                 }],
512                 ..Default::default()
513             },
514             PackageMetadata {
515                 name: "unixlib".to_string(),
516                 targets: vec![TargetMetadata {
517                     name: "unixlib".to_string(),
518                     kind: vec![TargetKind::Lib],
519                     ..Default::default()
520                 }],
521                 ..Default::default()
522             },
523             PackageMetadata {
524                 name: "windowslib".to_string(),
525                 targets: vec![TargetMetadata {
526                     name: "windowslib".to_string(),
527                     kind: vec![TargetKind::Lib],
528                     ..Default::default()
529                 }],
530                 ..Default::default()
531             },
532         ];
533         assert_eq!(
534             get_externs(&package, &packages, &[], &[], &[], false).unwrap(),
535             vec![
536                 Extern {
537                     name: "alwayslib".to_string(),
538                     lib_name: "alwayslib".to_string(),
539                     raw_name: "alwayslib".to_string(),
540                     extern_type: ExternType::Rust
541                 },
542                 Extern {
543                     name: "unixlib".to_string(),
544                     lib_name: "unixlib".to_string(),
545                     raw_name: "unixlib".to_string(),
546                     extern_type: ExternType::Rust
547                 },
548             ]
549         );
550     }
551 
552     #[test]
get_externs_extra_cfg()553     fn get_externs_extra_cfg() {
554         let package = PackageMetadata {
555             name: "test_package".to_string(),
556             dependencies: vec![
557                 DependencyMetadata {
558                     name: "foolib".to_string(),
559                     kind: None,
560                     optional: false,
561                     target: Some("cfg(foo)".to_string()),
562                     rename: None,
563                 },
564                 DependencyMetadata {
565                     name: "barlib".to_string(),
566                     kind: None,
567                     optional: false,
568                     target: Some("cfg(bar)".to_string()),
569                     rename: None,
570                 },
571             ],
572             features: [].into_iter().collect(),
573             targets: vec![],
574             ..Default::default()
575         };
576         let packages = vec![
577             package.clone(),
578             PackageMetadata {
579                 name: "foolib".to_string(),
580                 targets: vec![TargetMetadata {
581                     name: "foolib".to_string(),
582                     kind: vec![TargetKind::Lib],
583                     ..Default::default()
584                 }],
585                 ..Default::default()
586             },
587             PackageMetadata {
588                 name: "barlib".to_string(),
589                 targets: vec![TargetMetadata {
590                     name: "barlib".to_string(),
591                     kind: vec![TargetKind::Lib],
592                     ..Default::default()
593                 }],
594                 ..Default::default()
595             },
596         ];
597         assert_eq!(
598             get_externs(&package, &packages, &[], &["foo".to_string()], &[], false).unwrap(),
599             vec![Extern {
600                 name: "foolib".to_string(),
601                 lib_name: "foolib".to_string(),
602                 raw_name: "foolib".to_string(),
603                 extern_type: ExternType::Rust
604             },]
605         );
606     }
607 
608     #[test]
get_externs_rename()609     fn get_externs_rename() {
610         let package = PackageMetadata {
611             name: "test_package".to_string(),
612             dependencies: vec![
613                 DependencyMetadata {
614                     name: "foo".to_string(),
615                     kind: None,
616                     optional: false,
617                     target: None,
618                     rename: Some("foo2".to_string()),
619                 },
620                 DependencyMetadata {
621                     name: "bar".to_string(),
622                     kind: None,
623                     optional: true,
624                     target: None,
625                     rename: None,
626                 },
627                 DependencyMetadata {
628                     name: "bar".to_string(),
629                     kind: None,
630                     optional: true,
631                     target: None,
632                     rename: Some("baz".to_string()),
633                 },
634             ],
635             ..Default::default()
636         };
637         let packages = vec![
638             package.clone(),
639             PackageMetadata {
640                 name: "foo".to_string(),
641                 targets: vec![TargetMetadata {
642                     name: "foo".to_string(),
643                     kind: vec![TargetKind::Lib],
644                     ..Default::default()
645                 }],
646                 ..Default::default()
647             },
648             PackageMetadata {
649                 name: "bar".to_string(),
650                 targets: vec![TargetMetadata {
651                     name: "bar".to_string(),
652                     kind: vec![TargetKind::Lib],
653                     ..Default::default()
654                 }],
655                 ..Default::default()
656             },
657         ];
658         assert_eq!(
659             get_externs(&package, &packages, &["dep:bar".to_string()], &[], &[], false).unwrap(),
660             vec![
661                 Extern {
662                     name: "bar".to_string(),
663                     lib_name: "bar".to_string(),
664                     raw_name: "bar".to_string(),
665                     extern_type: ExternType::Rust
666                 },
667                 Extern {
668                     name: "foo2".to_string(),
669                     lib_name: "foo".to_string(),
670                     raw_name: "foo".to_string(),
671                     extern_type: ExternType::Rust
672                 },
673             ]
674         );
675         assert_eq!(
676             get_externs(&package, &packages, &["dep:baz".to_string()], &[], &[], false).unwrap(),
677             vec![
678                 Extern {
679                     name: "baz".to_string(),
680                     lib_name: "bar".to_string(),
681                     raw_name: "bar".to_string(),
682                     extern_type: ExternType::Rust
683                 },
684                 Extern {
685                     name: "foo2".to_string(),
686                     lib_name: "foo".to_string(),
687                     raw_name: "foo".to_string(),
688                     extern_type: ExternType::Rust
689                 },
690             ]
691         );
692     }
693 
694     #[test]
parse_metadata()695     fn parse_metadata() {
696         /// Remove anything before "external/rust/crates/" from the
697         /// `package_dir` field. This makes the test robust since you
698         /// can use `cargo metadata` to regenerate the test files and
699         /// you don't have to care about where your AOSP checkout
700         /// lives.
701         fn normalize_package_dir(mut c: Crate) -> Crate {
702             const EXTERNAL_RUST_CRATES: &str = "external/rust/crates/";
703             let package_dir = c.package_dir.to_str().unwrap();
704             if let Some(idx) = package_dir.find(EXTERNAL_RUST_CRATES) {
705                 c.package_dir = PathBuf::from(format!(".../{}", &package_dir[idx..]));
706             }
707             c
708         }
709 
710         for testdata_directory_path in testdata_directories() {
711             let cfg = Config::from_json_str(
712                 &read_to_string(testdata_directory_path.join("cargo_embargo.json"))
713                     .with_context(|| {
714                         format!(
715                             "Failed to open {:?}",
716                             testdata_directory_path.join("cargo_embargo.json")
717                         )
718                     })
719                     .unwrap(),
720             )
721             .unwrap();
722             let cargo_metadata_path = testdata_directory_path.join("cargo.metadata");
723             let expected_crates: Vec<Vec<Crate>> = serde_json::from_reader::<_, Vec<Vec<Crate>>>(
724                 File::open(testdata_directory_path.join("crates.json")).unwrap(),
725             )
726             .unwrap()
727             .into_iter()
728             .map(|crates: Vec<Crate>| crates.into_iter().map(normalize_package_dir).collect())
729             .collect();
730 
731             let crates = cfg
732                 .variants
733                 .iter()
734                 .map(|variant_cfg| {
735                     parse_cargo_metadata_str(
736                         &read_to_string(&cargo_metadata_path)
737                             .with_context(|| format!("Failed to open {:?}", cargo_metadata_path))
738                             .unwrap(),
739                         variant_cfg,
740                     )
741                     .unwrap()
742                     .into_iter()
743                     .map(normalize_package_dir)
744                     .collect::<Vec<Crate>>()
745                 })
746                 .collect::<Vec<Vec<Crate>>>();
747             assert_that!(format!("{crates:#?}"), eq(format!("{expected_crates:#?}")));
748         }
749     }
750 }
751