xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/metadata/dependency.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 //! Gathering dependencies is the largest part of annotating.
2 
3 use std::collections::{BTreeMap, BTreeSet};
4 
5 use anyhow::{bail, Result};
6 use cargo_metadata::{
7     DependencyKind, Metadata as CargoMetadata, Node, NodeDep, Package, PackageId, Target,
8 };
9 use cargo_platform::Platform;
10 use serde::{Deserialize, Serialize};
11 
12 use crate::metadata::{CrateId, TreeResolverMetadata};
13 use crate::select::Select;
14 use crate::utils::sanitize_module_name;
15 
16 /// A representation of a crate dependency
17 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
18 pub(crate) struct Dependency {
19     /// The PackageId of the target
20     pub(crate) package_id: PackageId,
21 
22     /// The library target name of the dependency.
23     pub(crate) target_name: String,
24 
25     /// The alias for the dependency from the perspective of the current package
26     pub(crate) alias: Option<String>,
27 }
28 
29 /// A collection of [Dependency]s sorted by dependency kind.
30 #[derive(Debug, Default, Serialize, Deserialize)]
31 pub(crate) struct DependencySet {
32     pub(crate) normal_deps: Select<BTreeSet<Dependency>>,
33     pub(crate) normal_dev_deps: Select<BTreeSet<Dependency>>,
34     pub(crate) proc_macro_deps: Select<BTreeSet<Dependency>>,
35     pub(crate) proc_macro_dev_deps: Select<BTreeSet<Dependency>>,
36     pub(crate) build_deps: Select<BTreeSet<Dependency>>,
37     pub(crate) build_link_deps: Select<BTreeSet<Dependency>>,
38     pub(crate) build_proc_macro_deps: Select<BTreeSet<Dependency>>,
39 }
40 
41 impl DependencySet {
42     /// Collect all dependencies for a given node in the resolve graph.
new_for_node( node: &Node, metadata: &CargoMetadata, resolver_data: &TreeResolverMetadata, ) -> Self43     pub(crate) fn new_for_node(
44         node: &Node,
45         metadata: &CargoMetadata,
46         resolver_data: &TreeResolverMetadata,
47     ) -> Self {
48         // Build a dep tree mapping that's easily indexable via `cargo_metadata::PackageId`
49         let dep_tree: BTreeMap<CrateId, Select<BTreeSet<CrateId>>> = resolver_data
50             .iter()
51             .map(|(id, tree_data)| {
52                 let mut select = Select::new();
53                 for (config, data) in tree_data.items() {
54                     for dep in data.deps {
55                         select.insert(dep, config.clone());
56                     }
57                 }
58                 (id.clone(), select)
59             })
60             .collect();
61 
62         let crate_id = {
63             let package = &metadata[&node.id];
64             CrateId::from(package)
65         };
66 
67         let (normal_dev_deps, normal_deps) = {
68             let (dev, normal) = node
69                 .deps
70                 .iter()
71                 // Do not track workspace members as dependencies. Users are expected to maintain those connections
72                 .filter(|dep| !is_workspace_member(dep, metadata))
73                 .filter(|dep| is_lib_package(&metadata[&dep.pkg]))
74                 .filter(|dep| is_normal_dependency(dep) || is_dev_dependency(dep))
75                 .partition(|dep| is_dev_dependency(dep));
76 
77             (
78                 collect_deps_selectable(
79                     node,
80                     dev,
81                     metadata,
82                     DependencyKind::Development,
83                     dep_tree.get(&crate_id),
84                 ),
85                 collect_deps_selectable(
86                     node,
87                     normal,
88                     metadata,
89                     DependencyKind::Normal,
90                     dep_tree.get(&crate_id),
91                 ),
92             )
93         };
94 
95         let (proc_macro_dev_deps, proc_macro_deps) = {
96             let (dev, normal) = node
97                 .deps
98                 .iter()
99                 // Do not track workspace members as dependencies. Users are expected to maintain those connections
100                 .filter(|dep| !is_workspace_member(dep, metadata))
101                 .filter(|dep| is_proc_macro_package(&metadata[&dep.pkg]))
102                 .filter(|dep| is_normal_dependency(dep) || is_dev_dependency(dep))
103                 .partition(|dep| is_dev_dependency(dep));
104 
105             (
106                 collect_deps_selectable(
107                     node,
108                     dev,
109                     metadata,
110                     DependencyKind::Development,
111                     dep_tree.get(&crate_id),
112                 ),
113                 collect_deps_selectable(
114                     node,
115                     normal,
116                     metadata,
117                     DependencyKind::Normal,
118                     dep_tree.get(&crate_id),
119                 ),
120             )
121         };
122 
123         // For rules on build script dependencies see:
124         //  https://doc.rust-lang.org/cargo/reference/build-scripts.html#build-dependencies
125         let (build_proc_macro_deps, build_deps) = {
126             let (proc_macro, normal) = node
127                 .deps
128                 .iter()
129                 // Do not track workspace members as dependencies. Users are expected to maintain those connections
130                 .filter(|dep| !is_workspace_member(dep, metadata))
131                 .filter(|dep| is_build_dependency(dep))
132                 .filter(|dep| !is_dev_dependency(dep))
133                 .partition(|dep| is_proc_macro_package(&metadata[&dep.pkg]));
134 
135             (
136                 collect_deps_selectable(
137                     node,
138                     proc_macro,
139                     metadata,
140                     DependencyKind::Build,
141                     dep_tree.get(&crate_id),
142                 ),
143                 collect_deps_selectable(
144                     node,
145                     normal,
146                     metadata,
147                     DependencyKind::Build,
148                     dep_tree.get(&crate_id),
149                 ),
150             )
151         };
152 
153         // packages with the `links` property follow slightly different rules than other
154         // dependencies. These packages provide zero or more environment variables to the build
155         // script's of packages that directly (non-transitively) depend on these packages. Note that
156         // dependency specifically means of the package (`dependencies`), and not of the build
157         // script (`build-dependencies`).
158         // https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
159         // https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages
160         // https://doc.rust-lang.org/cargo/reference/build-script-examples.html#using-another-sys-crate
161         let mut build_link_deps: Select<BTreeSet<Dependency>> = Select::default();
162         for (configuration, dependency) in normal_deps
163             .items()
164             .into_iter()
165             .filter(|(_, dependency)| metadata[&dependency.package_id].links.is_some())
166         {
167             // Add any normal dependency to build dependencies that are associated `*-sys` crates
168             build_link_deps.insert(dependency.clone(), configuration.clone());
169         }
170 
171         Self {
172             normal_deps,
173             normal_dev_deps,
174             proc_macro_deps,
175             proc_macro_dev_deps,
176             build_deps,
177             build_link_deps,
178             build_proc_macro_deps,
179         }
180     }
181 }
182 
183 /// For details on optional dependencies see [the Rust docs](https://doc.rust-lang.org/cargo/reference/features.html#optional-dependencies).
is_optional_dependency( parent: &Node, dep: &NodeDep, target: Option<&Platform>, metadata: &CargoMetadata, kind: DependencyKind, ) -> bool184 fn is_optional_dependency(
185     parent: &Node,
186     dep: &NodeDep,
187     target: Option<&Platform>,
188     metadata: &CargoMetadata,
189     kind: DependencyKind,
190 ) -> bool {
191     let pkg = &metadata[&parent.id];
192 
193     pkg.dependencies
194         .iter()
195         .filter(|&d| d.kind == kind)
196         .filter(|&d| d.target.as_ref() == target)
197         .filter(|&d| d.optional)
198         .any(|d| sanitize_module_name(d.rename.as_ref().unwrap_or(&d.name)) == dep.name)
199 }
200 
collect_deps_selectable( node: &Node, deps: Vec<&NodeDep>, metadata: &cargo_metadata::Metadata, kind: DependencyKind, tree_data: Option<&Select<BTreeSet<CrateId>>>, ) -> Select<BTreeSet<Dependency>>201 fn collect_deps_selectable(
202     node: &Node,
203     deps: Vec<&NodeDep>,
204     metadata: &cargo_metadata::Metadata,
205     kind: DependencyKind,
206     tree_data: Option<&Select<BTreeSet<CrateId>>>,
207 ) -> Select<BTreeSet<Dependency>> {
208     let mut select: Select<BTreeSet<Dependency>> = Select::default();
209 
210     // Unfortunately, Cargo metadata is not as accurate as it could be due
211     // to the lack of `resolver = 2` support in the `cargo metadata` subcommand.
212     // To ensure accurate dependencies are determined, metadata is only used to
213     // determine the general list of dependencies but any one of them can be demoted
214     // to a platform specific dep depending on the `cargo tree` data provided.
215     //
216     // For more details see: https://github.com/rust-lang/cargo/issues/9863
217     for dep in deps.into_iter() {
218         let dep_pkg = &metadata[&dep.pkg];
219         let target_name = get_library_target_name(dep_pkg, &dep.name)
220             .expect("Nodes Dependencies are expected to exclusively be library-like targets");
221         let alias = get_target_alias(&dep.name, dep_pkg);
222         let crate_id = CrateId::from(dep_pkg);
223 
224         for kind_info in &dep.dep_kinds {
225             if kind_info.kind != kind {
226                 continue;
227             }
228 
229             // For optional dependencies, use the Feature tree resolver data to determine
230             // if or how the dependency should be added.
231             if is_optional_dependency(node, dep, kind_info.target.as_ref(), metadata, kind) {
232                 // Collect the dependency from any configuration.
233                 if let Some(data) = tree_data {
234                     for (config, tree_dep) in data.items() {
235                         if crate_id == tree_dep {
236                             let dependency = Dependency {
237                                 package_id: dep.pkg.clone(),
238                                 target_name: target_name.clone(),
239                                 alias: alias.clone(),
240                             };
241                             select.insert(dependency, config);
242                         }
243                     }
244                 }
245             } else {
246                 let dependency = Dependency {
247                     package_id: dep.pkg.clone(),
248                     target_name: target_name.clone(),
249                     alias: alias.clone(),
250                 };
251                 select.insert(
252                     dependency,
253                     kind_info
254                         .target
255                         .as_ref()
256                         .map(|platform| platform.to_string()),
257                 );
258             }
259         }
260     }
261 
262     select
263 }
264 
265 /// Packages may have targets that match aliases of dependents. This function
266 /// checks a target to see if it's an unexpected type for a dependency.
is_ignored_package_target(target: &Target) -> bool267 fn is_ignored_package_target(target: &Target) -> bool {
268     target
269         .kind
270         .iter()
271         .any(|t| ["example", "bench", "test"].contains(&t.as_str()))
272 }
273 
is_lib_package(package: &Package) -> bool274 fn is_lib_package(package: &Package) -> bool {
275     package.targets.iter().any(|target| {
276         target
277             .crate_types
278             .iter()
279             .any(|t| ["lib", "rlib"].contains(&t.as_str()))
280             && !is_ignored_package_target(target)
281     })
282 }
283 
is_proc_macro_package(package: &Package) -> bool284 fn is_proc_macro_package(package: &Package) -> bool {
285     package.targets.iter().any(|target| {
286         target.crate_types.iter().any(|t| t == "proc-macro") && !is_ignored_package_target(target)
287     })
288 }
289 
is_dev_dependency(node_dep: &NodeDep) -> bool290 fn is_dev_dependency(node_dep: &NodeDep) -> bool {
291     let is_normal_dep = is_normal_dependency(node_dep);
292     let is_dev_dep = node_dep
293         .dep_kinds
294         .iter()
295         .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Development));
296 
297     // In the event that a dependency is listed as both a dev and normal dependency,
298     // it's only considered a dev dependency if it's __not__ a normal dependency.
299     !is_normal_dep && is_dev_dep
300 }
301 
is_build_dependency(node_dep: &NodeDep) -> bool302 fn is_build_dependency(node_dep: &NodeDep) -> bool {
303     node_dep
304         .dep_kinds
305         .iter()
306         .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Build))
307 }
308 
is_normal_dependency(node_dep: &NodeDep) -> bool309 fn is_normal_dependency(node_dep: &NodeDep) -> bool {
310     node_dep
311         .dep_kinds
312         .iter()
313         .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Normal))
314 }
315 
is_workspace_member(node_dep: &NodeDep, metadata: &CargoMetadata) -> bool316 fn is_workspace_member(node_dep: &NodeDep, metadata: &CargoMetadata) -> bool {
317     metadata
318         .workspace_members
319         .iter()
320         .any(|id| id == &node_dep.pkg)
321 }
322 
get_library_target_name(package: &Package, potential_name: &str) -> Result<String>323 fn get_library_target_name(package: &Package, potential_name: &str) -> Result<String> {
324     // If the potential name is not an alias in a dependent's package, a target's name
325     // should match which means we already know what the target library name is. The
326     // only exception is for targets that are otherwise ignored (like benchmarks or examples).
327     if package
328         .targets
329         .iter()
330         .any(|t| t.name == potential_name && !is_ignored_package_target(t))
331     {
332         return Ok(potential_name.to_string());
333     }
334 
335     // Locate any library type targets
336     let lib_targets: Vec<&cargo_metadata::Target> = package
337         .targets
338         .iter()
339         .filter(|t| {
340             t.kind
341                 .iter()
342                 .any(|k| k == "lib" || k == "rlib" || k == "proc-macro")
343         })
344         .collect();
345 
346     // Only one target should be found
347     if lib_targets.len() != 1 {
348         bail!(
349             "Unexpected number of 'library-like' targets found for {}: {:?}",
350             package.name,
351             package.targets
352         )
353     }
354 
355     let target = lib_targets.into_iter().last().unwrap();
356     Ok(target.name.clone())
357 }
358 
359 /// The resolve graph (resolve.nodes[#].deps[#].name) of Cargo metadata uses module names
360 /// for targets where packages (packages[#].targets[#].name) uses crate names. In order to
361 /// determine whether or not a dependency is aliased, we compare it with all available targets
362 /// on it's package. Note that target names are not guaranteed to be module names where Node
363 /// dependencies are, so we need to do a conversion to check for this. This function will
364 /// return the name of a target's alias in the content of the current dependent if it is aliased.
get_target_alias(target_name: &str, package: &Package) -> Option<String>365 fn get_target_alias(target_name: &str, package: &Package) -> Option<String> {
366     match package
367         .targets
368         .iter()
369         .filter(|t| !is_ignored_package_target(t))
370         .all(|t| sanitize_module_name(&t.name) != target_name)
371     {
372         true => Some(target_name.to_string()),
373         false => None,
374     }
375 }
376 
377 #[cfg(test)]
378 mod test {
379     use super::*;
380 
381     use semver::Version;
382 
383     use crate::metadata::CargoTreeEntry;
384     use crate::test::*;
385 
386     #[test]
get_expected_lib_target_name()387     fn get_expected_lib_target_name() {
388         let mut package = mock_cargo_metadata_package();
389         package
390             .targets
391             .extend(vec![serde_json::from_value(serde_json::json!({
392                 "name": "potential",
393                 "kind": ["lib"],
394                 "crate_types": [],
395                 "required_features": [],
396                 "src_path": "/tmp/mock.rs",
397                 "edition": "2021",
398                 "doctest": false,
399                 "test": false,
400                 "doc": false,
401             }))
402             .unwrap()]);
403 
404         assert_eq!(
405             get_library_target_name(&package, "potential").unwrap(),
406             "potential"
407         );
408     }
409 
410     #[test]
get_lib_target_name()411     fn get_lib_target_name() {
412         let mut package = mock_cargo_metadata_package();
413         package
414             .targets
415             .extend(vec![serde_json::from_value(serde_json::json!({
416                 "name": "lib_target",
417                 "kind": ["lib"],
418                 "crate_types": [],
419                 "required_features": [],
420                 "src_path": "/tmp/mock.rs",
421                 "edition": "2021",
422                 "doctest": false,
423                 "test": false,
424                 "doc": false,
425             }))
426             .unwrap()]);
427 
428         assert_eq!(
429             get_library_target_name(&package, "mock-pkg").unwrap(),
430             "lib_target"
431         );
432     }
433 
434     #[test]
get_rlib_target_name()435     fn get_rlib_target_name() {
436         let mut package = mock_cargo_metadata_package();
437         package
438             .targets
439             .extend(vec![serde_json::from_value(serde_json::json!({
440                 "name": "rlib_target",
441                 "kind": ["rlib"],
442                 "crate_types": [],
443                 "required_features": [],
444                 "src_path": "/tmp/mock.rs",
445                 "edition": "2021",
446                 "doctest": false,
447                 "test": false,
448                 "doc": false,
449             }))
450             .unwrap()]);
451 
452         assert_eq!(
453             get_library_target_name(&package, "mock-pkg").unwrap(),
454             "rlib_target"
455         );
456     }
457 
458     #[test]
get_proc_macro_target_name()459     fn get_proc_macro_target_name() {
460         let mut package = mock_cargo_metadata_package();
461         package
462             .targets
463             .extend(vec![serde_json::from_value(serde_json::json!({
464                 "name": "proc_macro_target",
465                 "kind": ["proc-macro"],
466                 "crate_types": [],
467                 "required_features": [],
468                 "src_path": "/tmp/mock.rs",
469                 "edition": "2021",
470                 "doctest": false,
471                 "test": false,
472                 "doc": false,
473             }))
474             .unwrap()]);
475 
476         assert_eq!(
477             get_library_target_name(&package, "mock-pkg").unwrap(),
478             "proc_macro_target"
479         );
480     }
481 
482     #[test]
get_bin_target_name()483     fn get_bin_target_name() {
484         let mut package = mock_cargo_metadata_package();
485         package
486             .targets
487             .extend(vec![serde_json::from_value(serde_json::json!({
488                 "name": "bin_target",
489                 "kind": ["bin"],
490                 "crate_types": [],
491                 "required_features": [],
492                 "src_path": "/tmp/mock.rs",
493                 "edition": "2021",
494                 "doctest": false,
495                 "test": false,
496                 "doc": false,
497             }))
498             .unwrap()]);
499 
500         // It's an error for no library target to be found.
501         assert!(get_library_target_name(&package, "mock-pkg").is_err());
502     }
503 
504     /// Locate the [cargo_metadata::Node] for the crate matching the given name
find_metadata_node<'a>( name: &str, metadata: &'a cargo_metadata::Metadata, ) -> &'a cargo_metadata::Node505     fn find_metadata_node<'a>(
506         name: &str,
507         metadata: &'a cargo_metadata::Metadata,
508     ) -> &'a cargo_metadata::Node {
509         metadata
510             .resolve
511             .as_ref()
512             .unwrap()
513             .nodes
514             .iter()
515             .find(|node| {
516                 let pkg = &metadata[&node.id];
517                 pkg.name == name
518             })
519             .unwrap()
520     }
521 
522     #[test]
example_proc_macro_dep()523     fn example_proc_macro_dep() {
524         let metadata = metadata::example_proc_macro_dep();
525 
526         let node = find_metadata_node("example-proc-macro-dep", &metadata);
527         let dependencies =
528             DependencySet::new_for_node(node, &metadata, &TreeResolverMetadata::default());
529 
530         let normal_deps: Vec<_> = dependencies
531             .normal_deps
532             .items()
533             .into_iter()
534             .map(|(_, dep)| dep.target_name)
535             .collect();
536         assert_eq!(normal_deps, vec!["proc_macro_rules"]);
537 
538         let proc_macro_deps: Vec<_> = dependencies
539             .proc_macro_deps
540             .items()
541             .into_iter()
542             .map(|(_, dep)| dep.target_name)
543             .collect();
544         assert_eq!(proc_macro_deps, Vec::<&str>::new());
545     }
546 
547     #[test]
bench_name_alias_dep()548     fn bench_name_alias_dep() {
549         let metadata = metadata::alias();
550 
551         let node = find_metadata_node("surrealdb-core", &metadata);
552         let dependencies =
553             DependencySet::new_for_node(node, &metadata, &TreeResolverMetadata::default());
554 
555         let bindings = dependencies.normal_deps.items();
556 
557         // It's critical that the dep be found with the correct name and not the
558         // alias that the `aliases` package is using that coincidentally matches the
559         // `bench` target `executor` in the `async-executor` package.
560         let async_executor = bindings
561             .iter()
562             .find(|(_, dep)| dep.target_name == "async_executor")
563             .map(|(_, dep)| dep)
564             .unwrap();
565 
566         // Ensure alias data is still tracked.
567         assert_eq!(async_executor.alias, Some("executor".to_owned()));
568     }
569 
570     #[test]
sys_dependencies()571     fn sys_dependencies() {
572         let metadata = metadata::build_scripts();
573 
574         let openssl_node = find_metadata_node("openssl", &metadata);
575 
576         let dependencies =
577             DependencySet::new_for_node(openssl_node, &metadata, &TreeResolverMetadata::default());
578 
579         let normal_sys_crate =
580             dependencies
581                 .normal_deps
582                 .items()
583                 .into_iter()
584                 .find(|(configuration, dep)| {
585                     let pkg = &metadata[&dep.package_id];
586                     configuration.is_none() && pkg.name == "openssl-sys"
587                 });
588 
589         let link_dep_sys_crate =
590             dependencies
591                 .build_link_deps
592                 .items()
593                 .into_iter()
594                 .find(|(configuration, dep)| {
595                     let pkg = &metadata[&dep.package_id];
596                     configuration.is_none() && pkg.name == "openssl-sys"
597                 });
598 
599         // sys crates like `openssl-sys` should always be dependencies of any
600         // crate which matches it's name minus the `-sys` suffix
601         assert!(normal_sys_crate.is_some());
602         assert!(link_dep_sys_crate.is_some());
603     }
604 
605     #[test]
sys_crate_with_build_script()606     fn sys_crate_with_build_script() {
607         let metadata = metadata::build_scripts();
608 
609         let libssh2 = find_metadata_node("libssh2-sys", &metadata);
610         let libssh2_depset =
611             DependencySet::new_for_node(libssh2, &metadata, &TreeResolverMetadata::default());
612 
613         // Collect build dependencies into a set
614         let build_deps: BTreeSet<String> = libssh2_depset
615             .build_deps
616             .values()
617             .into_iter()
618             .map(|dep| dep.package_id.repr)
619             .collect();
620 
621         assert_eq!(
622             BTreeSet::from([
623                 "registry+https://github.com/rust-lang/crates.io-index#[email protected]".to_owned(),
624                 "registry+https://github.com/rust-lang/crates.io-index#[email protected]"
625                     .to_owned(),
626                 "registry+https://github.com/rust-lang/crates.io-index#[email protected]".to_owned()
627             ]),
628             build_deps,
629         );
630 
631         // Collect normal dependencies into a set
632         let normal_deps: BTreeSet<String> = libssh2_depset
633             .normal_deps
634             .values()
635             .into_iter()
636             .map(|dep| dep.package_id.to_string())
637             .collect();
638 
639         assert_eq!(
640             BTreeSet::from([
641                 "registry+https://github.com/rust-lang/crates.io-index#[email protected]".to_owned(),
642                 "registry+https://github.com/rust-lang/crates.io-index#[email protected]".to_owned(),
643                 "registry+https://github.com/rust-lang/crates.io-index#[email protected]"
644                     .to_owned(),
645             ]),
646             normal_deps,
647         );
648 
649         assert!(libssh2_depset.proc_macro_deps.is_empty());
650         assert!(libssh2_depset.normal_dev_deps.is_empty());
651         assert!(libssh2_depset.proc_macro_dev_deps.is_empty());
652         assert!(libssh2_depset.build_proc_macro_deps.is_empty());
653     }
654 
655     #[test]
tracked_aliases()656     fn tracked_aliases() {
657         let metadata = metadata::alias();
658 
659         let aliases_node = find_metadata_node("aliases", &metadata);
660         let dependencies =
661             DependencySet::new_for_node(aliases_node, &metadata, &TreeResolverMetadata::default());
662 
663         let aliases: Vec<Dependency> = dependencies
664             .normal_deps
665             .items()
666             .into_iter()
667             .filter(|(configuration, dep)| configuration.is_none() && dep.alias.is_some())
668             .map(|(_, dep)| dep)
669             .collect();
670 
671         assert_eq!(aliases.len(), 2);
672 
673         let expected: BTreeSet<String> =
674             aliases.into_iter().map(|dep| dep.alias.unwrap()).collect();
675 
676         assert_eq!(
677             expected,
678             BTreeSet::from(["pinned_log".to_owned(), "pinned_names".to_owned()])
679         );
680     }
681 
682     #[test]
matched_rlib()683     fn matched_rlib() {
684         let metadata = metadata::crate_types();
685 
686         let node = find_metadata_node("crate-types", &metadata);
687         let dependencies =
688             DependencySet::new_for_node(node, &metadata, &TreeResolverMetadata::default());
689 
690         let rlib_deps: Vec<Dependency> = dependencies
691             .normal_deps
692             .items()
693             .into_iter()
694             .filter(|(configuration, dep)| {
695                 let pkg = &metadata[&dep.package_id];
696                 configuration.is_none()
697                     && pkg
698                         .targets
699                         .iter()
700                         .any(|t| t.crate_types.contains(&"rlib".to_owned()))
701             })
702             .map(|(_, dep)| dep)
703             .collect();
704 
705         // Currently the only expected __explicitly__ "rlib" target in this metadata is `sysinfo`.
706         assert_eq!(rlib_deps.len(), 1);
707 
708         let sysinfo_dep = rlib_deps.iter().last().unwrap();
709         assert_eq!(sysinfo_dep.target_name, "sysinfo");
710     }
711 
712     #[test]
multiple_dep_kinds()713     fn multiple_dep_kinds() {
714         let metadata = metadata::multi_cfg_dep();
715 
716         let node = find_metadata_node("cpufeatures", &metadata);
717         let dependencies =
718             DependencySet::new_for_node(node, &metadata, &TreeResolverMetadata::default());
719 
720         let libc_cfgs: BTreeSet<Option<String>> = dependencies
721             .normal_deps
722             .items()
723             .into_iter()
724             .filter(|(_, dep)| dep.target_name == "libc")
725             .map(|(configuration, _)| configuration)
726             .collect();
727 
728         assert_eq!(
729             BTreeSet::from([
730                 Some("aarch64-linux-android".to_owned()),
731                 Some("cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))".to_owned()),
732                 Some("cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))".to_owned()),
733             ]),
734             libc_cfgs,
735         );
736     }
737 
738     #[test]
multi_kind_proc_macro_dep()739     fn multi_kind_proc_macro_dep() {
740         let metadata = metadata::multi_kind_proc_macro_dep();
741 
742         let node = find_metadata_node("multi-kind-proc-macro-dep", &metadata);
743         let dependencies =
744             DependencySet::new_for_node(node, &metadata, &TreeResolverMetadata::default());
745 
746         let lib_deps: Vec<_> = dependencies
747             .proc_macro_deps
748             .items()
749             .into_iter()
750             .map(|(_, dep)| dep.target_name)
751             .collect();
752         assert_eq!(lib_deps, vec!["paste"]);
753 
754         let build_deps: Vec<_> = dependencies
755             .build_proc_macro_deps
756             .items()
757             .into_iter()
758             .map(|(_, dep)| dep.target_name)
759             .collect();
760         assert_eq!(build_deps, vec!["paste"]);
761     }
762 
763     #[test]
optional_deps_disabled()764     fn optional_deps_disabled() {
765         let metadata = metadata::optional_deps_disabled();
766 
767         let node = find_metadata_node("clap", &metadata);
768         let dependencies =
769             DependencySet::new_for_node(node, &metadata, &TreeResolverMetadata::default());
770 
771         assert!(!dependencies
772             .normal_deps
773             .items()
774             .iter()
775             .any(|(configuration, dep)| configuration.is_none()
776                 && (dep.target_name == "is-terminal" || dep.target_name == "termcolor")));
777     }
778 
779     #[test]
renamed_optional_deps_disabled()780     fn renamed_optional_deps_disabled() {
781         let metadata = metadata::renamed_optional_deps_disabled();
782 
783         let serde_with = find_metadata_node("serde_with", &metadata);
784         let serde_with_depset =
785             DependencySet::new_for_node(serde_with, &metadata, &TreeResolverMetadata::new());
786         assert!(!serde_with_depset
787             .normal_deps
788             .items()
789             .iter()
790             .any(|(configuration, dep)| configuration.is_none() && dep.target_name == "indexmap"));
791     }
792 
793     #[test]
optional_deps_enabled()794     fn optional_deps_enabled() {
795         let metadata = metadata::optional_deps_enabled();
796         let mut select = Select::new();
797         select.insert(
798             CargoTreeEntry {
799                 features: BTreeSet::new(),
800                 deps: BTreeSet::from([
801                     CrateId::new("is-terminal".to_owned(), Version::new(0, 4, 5)),
802                     CrateId::new("termcolor".to_owned(), Version::new(1, 2, 0)),
803                 ]),
804             },
805             None,
806         );
807         let resolver_data = TreeResolverMetadata::from([(
808             CrateId::new("clap".to_owned(), Version::new(4, 1, 1)),
809             select,
810         )]);
811 
812         let clap = find_metadata_node("clap", &metadata);
813         let clap_depset = DependencySet::new_for_node(clap, &metadata, &resolver_data);
814         assert_eq!(
815             clap_depset
816                 .normal_deps
817                 .items()
818                 .iter()
819                 .filter(|(configuration, dep)| configuration.is_none()
820                     && (dep.target_name == "is_terminal" || dep.target_name == "termcolor"))
821                 .count(),
822             2
823         );
824 
825         let notify = find_metadata_node("notify", &metadata);
826         let notify_depset =
827             DependencySet::new_for_node(notify, &metadata, &TreeResolverMetadata::default());
828 
829         // mio is not present in the common list of dependencies
830         assert!(!notify_depset
831             .normal_deps
832             .items()
833             .iter()
834             .any(|(configuration, dep)| configuration.is_none() && dep.target_name == "mio"));
835 
836         // mio is a dependency on linux
837         assert!(notify_depset
838             .normal_deps
839             .items()
840             .iter()
841             .any(|(configuration, dep)| configuration.as_deref()
842                 == Some("cfg(target_os = \"linux\")")
843                 && dep.target_name == "mio"));
844 
845         // mio is marked optional=true on macos
846         assert!(!notify_depset
847             .normal_deps
848             .items()
849             .iter()
850             .any(|(configuration, dep)| configuration.as_deref()
851                 == Some("cfg(target_os = \"macos\")")
852                 && dep.target_name == "mio"));
853     }
854 
855     #[test]
optional_deps_disabled_build_dep_enabled()856     fn optional_deps_disabled_build_dep_enabled() {
857         let metadata = metadata::optional_deps_disabled_build_dep_enabled();
858 
859         let node = find_metadata_node("gherkin", &metadata);
860         let dependencies =
861             DependencySet::new_for_node(node, &metadata, &TreeResolverMetadata::default());
862 
863         assert!(!dependencies
864             .normal_deps
865             .items()
866             .iter()
867             .any(|(configuration, dep)| configuration.is_none() && dep.target_name == "serde"));
868 
869         assert!(dependencies
870             .build_deps
871             .items()
872             .iter()
873             .any(|(configuration, dep)| configuration.is_none() && dep.target_name == "serde"));
874     }
875 
876     #[test]
renamed_optional_deps_enabled()877     fn renamed_optional_deps_enabled() {
878         let metadata = metadata::renamed_optional_deps_enabled();
879 
880         let mut select = Select::new();
881         select.insert(
882             CargoTreeEntry {
883                 features: BTreeSet::new(),
884                 deps: BTreeSet::from([CrateId::new("ecdsa".to_owned(), Version::new(0, 16, 8))]),
885             },
886             None,
887         );
888         let resolver_data = TreeResolverMetadata::from([(
889             CrateId::new("p256".to_owned(), Version::new(0, 13, 2)),
890             select,
891         )]);
892 
893         let p256 = find_metadata_node("p256", &metadata);
894         let p256_depset = DependencySet::new_for_node(p256, &metadata, &resolver_data);
895         assert_eq!(
896             p256_depset
897                 .normal_deps
898                 .items()
899                 .iter()
900                 .filter(|(configuration, dep)| configuration.is_none() && dep.target_name == "ecdsa")
901                 .count(),
902             1
903         );
904     }
905 
906     #[test]
tree_resolver_deps()907     fn tree_resolver_deps() {
908         let metadata = metadata::resolver_2_deps_metadata();
909 
910         let mut select = Select::new();
911         select.insert(
912             CargoTreeEntry {
913                 deps: BTreeSet::from([
914                     CrateId::new("libc".to_owned(), Version::new(0, 2, 153)),
915                     CrateId::new("mio".to_owned(), Version::new(0, 8, 11)),
916                     CrateId::new("socket2".to_owned(), Version::new(0, 5, 6)),
917                 ]),
918                 features: BTreeSet::from([
919                     "io-std".to_owned(),
920                     "libc".to_owned(),
921                     "mio".to_owned(),
922                     "net".to_owned(),
923                     "rt".to_owned(),
924                     "socket2".to_owned(),
925                     "sync".to_owned(),
926                     "time".to_owned(),
927                 ]),
928             },
929             Some("x86_64-unknown-linux-gnu".to_owned()),
930         );
931         select.insert(
932             CargoTreeEntry {
933                 deps: BTreeSet::from([
934                     CrateId::new("bytes".to_owned(), Version::new(1, 6, 0)),
935                     CrateId::new("pin-project-lite".to_owned(), Version::new(0, 2, 14)),
936                 ]),
937                 features: BTreeSet::from([
938                     "bytes".to_owned(),
939                     "default".to_owned(),
940                     "io-util".to_owned(),
941                 ]),
942             },
943             None,
944         );
945 
946         let tree_metadata = TreeResolverMetadata::from([(
947             CrateId::new("tokio".to_owned(), Version::new(1, 37, 0)),
948             select,
949         )]);
950 
951         let tokio_node = find_metadata_node("tokio", &metadata);
952         let tokio_depset = DependencySet::new_for_node(tokio_node, &metadata, &tree_metadata);
953         assert_eq!(
954             tokio_depset
955                 .normal_deps
956                 .items()
957                 .iter()
958                 .filter(|(configuration, dep)| {
959                     let is_common = configuration.is_none();
960                     let is_mio =
961                         dep.target_name == "mio" || dep.package_id.to_string().contains("mio");
962 
963                     is_common && is_mio
964                 })
965                 .count(),
966             0,
967             "`mio` is a platform specific dependency and therefore should not be identified under the common configuration."
968         );
969     }
970 }
971