• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, BTreeSet};
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 = BTreeSet::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.into_iter().collect()
356 }
357 
358 /// Adds the given feature and all features it depends on to the given list of features.
359 ///
360 /// Ignores features of other packages, and features which don't exist.
add_feature_and_dependencies( features: &mut BTreeSet<String>, feature: &str, package_features: &BTreeMap<String, Vec<String>>, )361 fn add_feature_and_dependencies(
362     features: &mut BTreeSet<String>,
363     feature: &str,
364     package_features: &BTreeMap<String, Vec<String>>,
365 ) {
366     if features.contains(&feature.to_string()) {
367         return;
368     }
369     if package_features.contains_key(feature) || feature.starts_with("dep:") {
370         features.insert(feature.to_owned());
371     }
372 
373     if let Some(dependencies) = package_features.get(feature) {
374         for dependency in dependencies {
375             if let Some((dependency_package, _)) = dependency.split_once('/') {
376                 add_feature_and_dependencies(features, dependency_package, package_features);
377             } else {
378                 add_feature_and_dependencies(features, dependency, package_features);
379             }
380         }
381     }
382 }
383 
384 #[cfg(test)]
385 mod tests {
386     use super::*;
387     use crate::config::Config;
388     use crate::tests::testdata_directories;
389     use googletest::matchers::eq;
390     use googletest::prelude::assert_that;
391     use std::fs::{read_to_string, File};
392 
393     #[test]
extract_package_dir_from_id() -> Result<()>394     fn extract_package_dir_from_id() -> Result<()> {
395         assert_eq!(
396             package_dir_from_id("path+file:///path/to/crate#1.2.3")?,
397             PathBuf::from("/path/to/crate")
398         );
399         Ok(())
400     }
401 
402     #[test]
resolve_multi_level_feature_dependencies()403     fn resolve_multi_level_feature_dependencies() {
404         let chosen = vec!["default".to_string(), "extra".to_string(), "on_by_default".to_string()];
405         let package_features = [
406             (
407                 "default".to_string(),
408                 vec!["std".to_string(), "other".to_string(), "on_by_default".to_string()],
409             ),
410             ("std".to_string(), vec!["alloc".to_string()]),
411             ("not_enabled".to_string(), vec![]),
412             ("on_by_default".to_string(), vec![]),
413             ("other".to_string(), vec![]),
414             ("extra".to_string(), vec![]),
415             ("alloc".to_string(), vec![]),
416         ]
417         .into_iter()
418         .collect();
419         assert_eq!(
420             resolve_features(&Some(chosen), &package_features, &[]),
421             vec![
422                 "alloc".to_string(),
423                 "default".to_string(),
424                 "extra".to_string(),
425                 "on_by_default".to_string(),
426                 "other".to_string(),
427                 "std".to_string(),
428             ]
429         );
430     }
431 
432     #[test]
resolve_dep_features()433     fn resolve_dep_features() {
434         let package_features = [(
435             "default".to_string(),
436             vec![
437                 "optionaldep/feature".to_string(),
438                 "requireddep/feature".to_string(),
439                 "optionaldep2?/feature".to_string(),
440             ],
441         )]
442         .into_iter()
443         .collect();
444         let dependencies = vec![
445             DependencyMetadata {
446                 name: "optionaldep".to_string(),
447                 kind: None,
448                 optional: true,
449                 target: None,
450                 rename: None,
451             },
452             DependencyMetadata {
453                 name: "optionaldep2".to_string(),
454                 kind: None,
455                 optional: true,
456                 target: None,
457                 rename: None,
458             },
459             DependencyMetadata {
460                 name: "requireddep".to_string(),
461                 kind: None,
462                 optional: false,
463                 target: None,
464                 rename: None,
465             },
466         ];
467         assert_eq!(
468             resolve_features(&None, &package_features, &dependencies),
469             vec!["default".to_string(), "dep:optionaldep".to_string(), "optionaldep".to_string()]
470         );
471     }
472 
473     #[test]
resolve_dep_features_recursion()474     fn resolve_dep_features_recursion() {
475         let chosen = vec!["tokio".to_string()];
476         let package_features = [
477             ("default".to_string(), vec![]),
478             ("tokio".to_string(), vec!["dep:tokio".to_string(), "tokio/net".to_string()]),
479         ]
480         .into_iter()
481         .collect();
482         assert_eq!(
483             resolve_features(&Some(chosen), &package_features, &[]),
484             vec!["dep:tokio".to_string(), "tokio".to_string(),]
485         );
486     }
487 
488     #[test]
get_externs_cfg()489     fn get_externs_cfg() {
490         let package = PackageMetadata {
491             name: "test_package".to_string(),
492             dependencies: vec![
493                 DependencyMetadata {
494                     name: "alwayslib".to_string(),
495                     kind: None,
496                     optional: false,
497                     target: None,
498                     rename: None,
499                 },
500                 DependencyMetadata {
501                     name: "unixlib".to_string(),
502                     kind: None,
503                     optional: false,
504                     target: Some("cfg(unix)".to_string()),
505                     rename: None,
506                 },
507                 DependencyMetadata {
508                     name: "windowslib".to_string(),
509                     kind: None,
510                     optional: false,
511                     target: Some("cfg(windows)".to_string()),
512                     rename: None,
513                 },
514             ],
515             features: [].into_iter().collect(),
516             targets: vec![],
517             ..Default::default()
518         };
519         let packages = vec![
520             package.clone(),
521             PackageMetadata {
522                 name: "alwayslib".to_string(),
523                 targets: vec![TargetMetadata {
524                     name: "alwayslib".to_string(),
525                     kind: vec![TargetKind::Lib],
526                     ..Default::default()
527                 }],
528                 ..Default::default()
529             },
530             PackageMetadata {
531                 name: "unixlib".to_string(),
532                 targets: vec![TargetMetadata {
533                     name: "unixlib".to_string(),
534                     kind: vec![TargetKind::Lib],
535                     ..Default::default()
536                 }],
537                 ..Default::default()
538             },
539             PackageMetadata {
540                 name: "windowslib".to_string(),
541                 targets: vec![TargetMetadata {
542                     name: "windowslib".to_string(),
543                     kind: vec![TargetKind::Lib],
544                     ..Default::default()
545                 }],
546                 ..Default::default()
547             },
548         ];
549         assert_eq!(
550             get_externs(&package, &packages, &[], &[], &[], false).unwrap(),
551             vec![
552                 Extern {
553                     name: "alwayslib".to_string(),
554                     lib_name: "alwayslib".to_string(),
555                     raw_name: "alwayslib".to_string(),
556                     extern_type: ExternType::Rust
557                 },
558                 Extern {
559                     name: "unixlib".to_string(),
560                     lib_name: "unixlib".to_string(),
561                     raw_name: "unixlib".to_string(),
562                     extern_type: ExternType::Rust
563                 },
564             ]
565         );
566     }
567 
568     #[test]
get_externs_extra_cfg()569     fn get_externs_extra_cfg() {
570         let package = PackageMetadata {
571             name: "test_package".to_string(),
572             dependencies: vec![
573                 DependencyMetadata {
574                     name: "foolib".to_string(),
575                     kind: None,
576                     optional: false,
577                     target: Some("cfg(foo)".to_string()),
578                     rename: None,
579                 },
580                 DependencyMetadata {
581                     name: "barlib".to_string(),
582                     kind: None,
583                     optional: false,
584                     target: Some("cfg(bar)".to_string()),
585                     rename: None,
586                 },
587             ],
588             features: [].into_iter().collect(),
589             targets: vec![],
590             ..Default::default()
591         };
592         let packages = vec![
593             package.clone(),
594             PackageMetadata {
595                 name: "foolib".to_string(),
596                 targets: vec![TargetMetadata {
597                     name: "foolib".to_string(),
598                     kind: vec![TargetKind::Lib],
599                     ..Default::default()
600                 }],
601                 ..Default::default()
602             },
603             PackageMetadata {
604                 name: "barlib".to_string(),
605                 targets: vec![TargetMetadata {
606                     name: "barlib".to_string(),
607                     kind: vec![TargetKind::Lib],
608                     ..Default::default()
609                 }],
610                 ..Default::default()
611             },
612         ];
613         assert_eq!(
614             get_externs(&package, &packages, &[], &["foo".to_string()], &[], false).unwrap(),
615             vec![Extern {
616                 name: "foolib".to_string(),
617                 lib_name: "foolib".to_string(),
618                 raw_name: "foolib".to_string(),
619                 extern_type: ExternType::Rust
620             },]
621         );
622     }
623 
624     #[test]
get_externs_rename()625     fn get_externs_rename() {
626         let package = PackageMetadata {
627             name: "test_package".to_string(),
628             dependencies: vec![
629                 DependencyMetadata {
630                     name: "foo".to_string(),
631                     kind: None,
632                     optional: false,
633                     target: None,
634                     rename: Some("foo2".to_string()),
635                 },
636                 DependencyMetadata {
637                     name: "bar".to_string(),
638                     kind: None,
639                     optional: true,
640                     target: None,
641                     rename: None,
642                 },
643                 DependencyMetadata {
644                     name: "bar".to_string(),
645                     kind: None,
646                     optional: true,
647                     target: None,
648                     rename: Some("baz".to_string()),
649                 },
650             ],
651             ..Default::default()
652         };
653         let packages = vec![
654             package.clone(),
655             PackageMetadata {
656                 name: "foo".to_string(),
657                 targets: vec![TargetMetadata {
658                     name: "foo".to_string(),
659                     kind: vec![TargetKind::Lib],
660                     ..Default::default()
661                 }],
662                 ..Default::default()
663             },
664             PackageMetadata {
665                 name: "bar".to_string(),
666                 targets: vec![TargetMetadata {
667                     name: "bar".to_string(),
668                     kind: vec![TargetKind::Lib],
669                     ..Default::default()
670                 }],
671                 ..Default::default()
672             },
673         ];
674         assert_eq!(
675             get_externs(&package, &packages, &["dep:bar".to_string()], &[], &[], false).unwrap(),
676             vec![
677                 Extern {
678                     name: "bar".to_string(),
679                     lib_name: "bar".to_string(),
680                     raw_name: "bar".to_string(),
681                     extern_type: ExternType::Rust
682                 },
683                 Extern {
684                     name: "foo2".to_string(),
685                     lib_name: "foo".to_string(),
686                     raw_name: "foo".to_string(),
687                     extern_type: ExternType::Rust
688                 },
689             ]
690         );
691         assert_eq!(
692             get_externs(&package, &packages, &["dep:baz".to_string()], &[], &[], false).unwrap(),
693             vec![
694                 Extern {
695                     name: "baz".to_string(),
696                     lib_name: "bar".to_string(),
697                     raw_name: "bar".to_string(),
698                     extern_type: ExternType::Rust
699                 },
700                 Extern {
701                     name: "foo2".to_string(),
702                     lib_name: "foo".to_string(),
703                     raw_name: "foo".to_string(),
704                     extern_type: ExternType::Rust
705                 },
706             ]
707         );
708     }
709 
710     #[test]
parse_metadata()711     fn parse_metadata() {
712         /// Remove anything before "external/rust/crates/" from the
713         /// `package_dir` field. This makes the test robust since you
714         /// can use `cargo metadata` to regenerate the test files and
715         /// you don't have to care about where your AOSP checkout
716         /// lives.
717         fn normalize_package_dir(mut c: Crate) -> Crate {
718             const EXTERNAL_RUST_CRATES: &str = "external/rust/crates/";
719             let package_dir = c.package_dir.to_str().unwrap();
720             if let Some(idx) = package_dir.find(EXTERNAL_RUST_CRATES) {
721                 c.package_dir = PathBuf::from(format!(".../{}", &package_dir[idx..]));
722             }
723             c
724         }
725 
726         for testdata_directory_path in testdata_directories() {
727             let cfg = Config::from_json_str(
728                 &read_to_string(testdata_directory_path.join("cargo_embargo.json"))
729                     .with_context(|| {
730                         format!(
731                             "Failed to open {:?}",
732                             testdata_directory_path.join("cargo_embargo.json")
733                         )
734                     })
735                     .unwrap(),
736             )
737             .unwrap();
738             let cargo_metadata_path = testdata_directory_path.join("cargo.metadata");
739             let expected_crates: Vec<Vec<Crate>> = serde_json::from_reader::<_, Vec<Vec<Crate>>>(
740                 File::open(testdata_directory_path.join("crates.json")).unwrap(),
741             )
742             .unwrap()
743             .into_iter()
744             .map(|crates: Vec<Crate>| crates.into_iter().map(normalize_package_dir).collect())
745             .collect();
746 
747             let crates = cfg
748                 .variants
749                 .iter()
750                 .map(|variant_cfg| {
751                     parse_cargo_metadata_str(
752                         &read_to_string(&cargo_metadata_path)
753                             .with_context(|| format!("Failed to open {:?}", cargo_metadata_path))
754                             .unwrap(),
755                         variant_cfg,
756                     )
757                     .unwrap()
758                     .into_iter()
759                     .map(normalize_package_dir)
760                     .collect::<Vec<Crate>>()
761                 })
762                 .collect::<Vec<Vec<Crate>>>();
763             assert_that!(format!("{crates:#?}"), eq(&format!("{expected_crates:#?}")));
764         }
765     }
766 }
767