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