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