• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Gathering dependencies is the largest part of annotating.
2 use std::collections::BTreeSet;
3 
4 use anyhow::{bail, Result};
5 use cargo_metadata::{
6     DependencyKind, Metadata as CargoMetadata, Node, NodeDep, Package, PackageId,
7 };
8 use cargo_platform::Platform;
9 use serde::{Deserialize, Serialize};
10 
11 use crate::select::Select;
12 use crate::utils::sanitize_module_name;
13 
14 /// A representation of a crate dependency
15 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
16 pub(crate) struct Dependency {
17     /// The PackageId of the target
18     pub(crate) package_id: PackageId,
19 
20     /// The library target name of the dependency.
21     pub(crate) target_name: String,
22 
23     /// The alias for the dependency from the perspective of the current package
24     pub(crate) alias: Option<String>,
25 }
26 
27 /// A collection of [Dependency]s sorted by dependency kind.
28 #[derive(Debug, Default, Serialize, Deserialize)]
29 pub(crate) struct DependencySet {
30     pub(crate) normal_deps: Select<BTreeSet<Dependency>>,
31     pub(crate) normal_dev_deps: Select<BTreeSet<Dependency>>,
32     pub(crate) proc_macro_deps: Select<BTreeSet<Dependency>>,
33     pub(crate) proc_macro_dev_deps: Select<BTreeSet<Dependency>>,
34     pub(crate) build_deps: Select<BTreeSet<Dependency>>,
35     pub(crate) build_link_deps: Select<BTreeSet<Dependency>>,
36     pub(crate) build_proc_macro_deps: Select<BTreeSet<Dependency>>,
37 }
38 
39 impl DependencySet {
40     /// Collect all dependencies for a given node in the resolve graph.
new_for_node(node: &Node, metadata: &CargoMetadata) -> Self41     pub(crate) fn new_for_node(node: &Node, metadata: &CargoMetadata) -> Self {
42         let (normal_dev_deps, normal_deps) = {
43             let (dev, normal) = node
44                 .deps
45                 .iter()
46                 // Do not track workspace members as dependencies. Users are expected to maintain those connections
47                 .filter(|dep| !is_workspace_member(dep, metadata))
48                 .filter(|dep| is_lib_package(&metadata[&dep.pkg]))
49                 .filter(|dep| is_normal_dependency(dep) || is_dev_dependency(dep))
50                 .partition(|dep| is_dev_dependency(dep));
51 
52             (
53                 collect_deps_selectable(node, dev, metadata, DependencyKind::Development),
54                 collect_deps_selectable(node, normal, metadata, DependencyKind::Normal),
55             )
56         };
57 
58         let (proc_macro_dev_deps, proc_macro_deps) = {
59             let (dev, normal) = node
60                 .deps
61                 .iter()
62                 // Do not track workspace members as dependencies. Users are expected to maintain those connections
63                 .filter(|dep| !is_workspace_member(dep, metadata))
64                 .filter(|dep| is_proc_macro_package(&metadata[&dep.pkg]))
65                 .filter(|dep| is_normal_dependency(dep) || is_dev_dependency(dep))
66                 .partition(|dep| is_dev_dependency(dep));
67 
68             (
69                 collect_deps_selectable(node, dev, metadata, DependencyKind::Development),
70                 collect_deps_selectable(node, normal, metadata, DependencyKind::Normal),
71             )
72         };
73 
74         // For rules on build script dependencies see:
75         //  https://doc.rust-lang.org/cargo/reference/build-scripts.html#build-dependencies
76         let (build_proc_macro_deps, build_deps) = {
77             let (proc_macro, normal) = node
78                 .deps
79                 .iter()
80                 // Do not track workspace members as dependencies. Users are expected to maintain those connections
81                 .filter(|dep| !is_workspace_member(dep, metadata))
82                 .filter(|dep| is_build_dependency(dep))
83                 .filter(|dep| !is_dev_dependency(dep))
84                 .partition(|dep| is_proc_macro_package(&metadata[&dep.pkg]));
85 
86             (
87                 collect_deps_selectable(node, proc_macro, metadata, DependencyKind::Build),
88                 collect_deps_selectable(node, normal, metadata, DependencyKind::Build),
89             )
90         };
91 
92         // packages with the `links` property follow slightly different rules than other
93         // dependencies. These packages provide zero or more environment variables to the build
94         // script's of packages that directly (non-transitively) depend on these packages. Note that
95         // dependency specifically means of the package (`dependencies`), and not of the build
96         // script (`build-dependencies`).
97         // https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
98         // https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages
99         // https://doc.rust-lang.org/cargo/reference/build-script-examples.html#using-another-sys-crate
100         let mut build_link_deps: Select<BTreeSet<Dependency>> = Select::default();
101         for (configuration, dependency) in normal_deps
102             .items()
103             .into_iter()
104             .filter(|(_, dependency)| metadata[&dependency.package_id].links.is_some())
105         {
106             // Add any normal dependency to build dependencies that are associated `*-sys` crates
107             build_link_deps.insert(dependency.clone(), configuration.clone());
108         }
109 
110         Self {
111             normal_deps,
112             normal_dev_deps,
113             proc_macro_deps,
114             proc_macro_dev_deps,
115             build_deps,
116             build_link_deps,
117             build_proc_macro_deps,
118         }
119     }
120 }
121 
122 /// For details on optional dependencies see [the Rust docs](https://doc.rust-lang.org/cargo/reference/features.html#optional-dependencies).
is_optional_crate_enabled( parent: &Node, dep: &NodeDep, target: Option<&Platform>, metadata: &CargoMetadata, kind: DependencyKind, ) -> bool123 fn is_optional_crate_enabled(
124     parent: &Node,
125     dep: &NodeDep,
126     target: Option<&Platform>,
127     metadata: &CargoMetadata,
128     kind: DependencyKind,
129 ) -> bool {
130     let pkg = &metadata[&parent.id];
131 
132     let mut enabled_deps = pkg
133         .features
134         .iter()
135         .filter(|(pkg_feature, _)| parent.features.contains(pkg_feature))
136         .flat_map(|(_, features)| features)
137         .filter_map(|f| f.strip_prefix("dep:"));
138 
139     // if the crate is marked as optional dependency, we check whether
140     // a feature prefixed with dep: is enabled
141     if let Some(toml_dep) = pkg
142         .dependencies
143         .iter()
144         .filter(|&d| d.kind == kind)
145         .filter(|&d| d.target.as_ref() == target)
146         .filter(|&d| d.optional)
147         .find(|&d| sanitize_module_name(d.rename.as_ref().unwrap_or(&d.name)) == dep.name)
148     {
149         enabled_deps.any(|d| d == toml_dep.rename.as_ref().unwrap_or(&toml_dep.name))
150     } else {
151         true
152     }
153 }
154 
collect_deps_selectable( node: &Node, deps: Vec<&NodeDep>, metadata: &cargo_metadata::Metadata, kind: DependencyKind, ) -> Select<BTreeSet<Dependency>>155 fn collect_deps_selectable(
156     node: &Node,
157     deps: Vec<&NodeDep>,
158     metadata: &cargo_metadata::Metadata,
159     kind: DependencyKind,
160 ) -> Select<BTreeSet<Dependency>> {
161     let mut select: Select<BTreeSet<Dependency>> = Select::default();
162 
163     for dep in deps.into_iter() {
164         let dep_pkg = &metadata[&dep.pkg];
165         let target_name = get_library_target_name(dep_pkg, &dep.name)
166             .expect("Nodes Dependencies are expected to exclusively be library-like targets");
167         let alias = get_target_alias(&dep.name, dep_pkg);
168 
169         for kind_info in &dep.dep_kinds {
170             if is_optional_crate_enabled(node, dep, kind_info.target.as_ref(), metadata, kind) {
171                 let dependency = Dependency {
172                     package_id: dep.pkg.clone(),
173                     target_name: target_name.clone(),
174                     alias: alias.clone(),
175                 };
176                 select.insert(
177                     dependency,
178                     kind_info
179                         .target
180                         .as_ref()
181                         .map(|platform| platform.to_string()),
182                 );
183             }
184         }
185     }
186 
187     select
188 }
189 
is_lib_package(package: &Package) -> bool190 fn is_lib_package(package: &Package) -> bool {
191     package.targets.iter().any(|target| {
192         target
193             .crate_types
194             .iter()
195             .any(|t| ["lib", "rlib"].contains(&t.as_str()))
196     })
197 }
198 
is_proc_macro_package(package: &Package) -> bool199 fn is_proc_macro_package(package: &Package) -> bool {
200     package
201         .targets
202         .iter()
203         .any(|target| target.crate_types.iter().any(|t| t == "proc-macro"))
204 }
205 
is_dev_dependency(node_dep: &NodeDep) -> bool206 fn is_dev_dependency(node_dep: &NodeDep) -> bool {
207     let is_normal_dep = is_normal_dependency(node_dep);
208     let is_dev_dep = node_dep
209         .dep_kinds
210         .iter()
211         .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Development));
212 
213     // In the event that a dependency is listed as both a dev and normal dependency,
214     // it's only considered a dev dependency if it's __not__ a normal dependency.
215     !is_normal_dep && is_dev_dep
216 }
217 
is_build_dependency(node_dep: &NodeDep) -> bool218 fn is_build_dependency(node_dep: &NodeDep) -> bool {
219     node_dep
220         .dep_kinds
221         .iter()
222         .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Build))
223 }
224 
is_normal_dependency(node_dep: &NodeDep) -> bool225 fn is_normal_dependency(node_dep: &NodeDep) -> bool {
226     node_dep
227         .dep_kinds
228         .iter()
229         .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Normal))
230 }
231 
is_workspace_member(node_dep: &NodeDep, metadata: &CargoMetadata) -> bool232 fn is_workspace_member(node_dep: &NodeDep, metadata: &CargoMetadata) -> bool {
233     metadata
234         .workspace_members
235         .iter()
236         .any(|id| id == &node_dep.pkg)
237 }
238 
get_library_target_name(package: &Package, potential_name: &str) -> Result<String>239 fn get_library_target_name(package: &Package, potential_name: &str) -> Result<String> {
240     // If the potential name is not an alias in a dependent's package, a target's name
241     // should match which means we already know what the target library name is.
242     if package.targets.iter().any(|t| t.name == potential_name) {
243         return Ok(potential_name.to_string());
244     }
245 
246     // Locate any library type targets
247     let lib_targets: Vec<&cargo_metadata::Target> = package
248         .targets
249         .iter()
250         .filter(|t| {
251             t.kind
252                 .iter()
253                 .any(|k| k == "lib" || k == "rlib" || k == "proc-macro")
254         })
255         .collect();
256 
257     // Only one target should be found
258     if lib_targets.len() != 1 {
259         bail!(
260             "Unexpected number of 'library-like' targets found for {}: {:?}",
261             package.name,
262             package.targets
263         )
264     }
265 
266     let target = lib_targets.into_iter().last().unwrap();
267     Ok(target.name.clone())
268 }
269 
270 /// The resolve graph (resolve.nodes[#].deps[#].name) of Cargo metadata uses module names
271 /// for targets where packages (packages[#].targets[#].name) uses crate names. In order to
272 /// determine whether or not a dependency is aliased, we compare it with all available targets
273 /// on it's package. Note that target names are not guaranteed to be module names where Node
274 /// dependencies are, so we need to do a conversion to check for this
get_target_alias(target_name: &str, package: &Package) -> Option<String>275 fn get_target_alias(target_name: &str, package: &Package) -> Option<String> {
276     match package
277         .targets
278         .iter()
279         .all(|t| sanitize_module_name(&t.name) != target_name)
280     {
281         true => Some(target_name.to_string()),
282         false => None,
283     }
284 }
285 
286 #[cfg(test)]
287 mod test {
288     use std::collections::BTreeSet;
289 
290     use super::*;
291 
292     use crate::test::*;
293 
294     #[test]
get_expected_lib_target_name()295     fn get_expected_lib_target_name() {
296         let mut package = mock_cargo_metadata_package();
297         package
298             .targets
299             .extend(vec![serde_json::from_value(serde_json::json!({
300                 "name": "potential",
301                 "kind": ["lib"],
302                 "crate_types": [],
303                 "required_features": [],
304                 "src_path": "/tmp/mock.rs",
305                 "edition": "2021",
306                 "doctest": false,
307                 "test": false,
308                 "doc": false,
309             }))
310             .unwrap()]);
311 
312         assert_eq!(
313             get_library_target_name(&package, "potential").unwrap(),
314             "potential"
315         );
316     }
317 
318     #[test]
get_lib_target_name()319     fn get_lib_target_name() {
320         let mut package = mock_cargo_metadata_package();
321         package
322             .targets
323             .extend(vec![serde_json::from_value(serde_json::json!({
324                 "name": "lib_target",
325                 "kind": ["lib"],
326                 "crate_types": [],
327                 "required_features": [],
328                 "src_path": "/tmp/mock.rs",
329                 "edition": "2021",
330                 "doctest": false,
331                 "test": false,
332                 "doc": false,
333             }))
334             .unwrap()]);
335 
336         assert_eq!(
337             get_library_target_name(&package, "mock-pkg").unwrap(),
338             "lib_target"
339         );
340     }
341 
342     #[test]
get_rlib_target_name()343     fn get_rlib_target_name() {
344         let mut package = mock_cargo_metadata_package();
345         package
346             .targets
347             .extend(vec![serde_json::from_value(serde_json::json!({
348                 "name": "rlib_target",
349                 "kind": ["rlib"],
350                 "crate_types": [],
351                 "required_features": [],
352                 "src_path": "/tmp/mock.rs",
353                 "edition": "2021",
354                 "doctest": false,
355                 "test": false,
356                 "doc": false,
357             }))
358             .unwrap()]);
359 
360         assert_eq!(
361             get_library_target_name(&package, "mock-pkg").unwrap(),
362             "rlib_target"
363         );
364     }
365 
366     #[test]
get_proc_macro_target_name()367     fn get_proc_macro_target_name() {
368         let mut package = mock_cargo_metadata_package();
369         package
370             .targets
371             .extend(vec![serde_json::from_value(serde_json::json!({
372                 "name": "proc_macro_target",
373                 "kind": ["proc-macro"],
374                 "crate_types": [],
375                 "required_features": [],
376                 "src_path": "/tmp/mock.rs",
377                 "edition": "2021",
378                 "doctest": false,
379                 "test": false,
380                 "doc": false,
381             }))
382             .unwrap()]);
383 
384         assert_eq!(
385             get_library_target_name(&package, "mock-pkg").unwrap(),
386             "proc_macro_target"
387         );
388     }
389 
390     #[test]
get_bin_target_name()391     fn get_bin_target_name() {
392         let mut package = mock_cargo_metadata_package();
393         package
394             .targets
395             .extend(vec![serde_json::from_value(serde_json::json!({
396                 "name": "bin_target",
397                 "kind": ["bin"],
398                 "crate_types": [],
399                 "required_features": [],
400                 "src_path": "/tmp/mock.rs",
401                 "edition": "2021",
402                 "doctest": false,
403                 "test": false,
404                 "doc": false,
405             }))
406             .unwrap()]);
407 
408         // It's an error for no library target to be found.
409         assert!(get_library_target_name(&package, "mock-pkg").is_err());
410     }
411 
412     /// 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::Node413     fn find_metadata_node<'a>(
414         name: &str,
415         metadata: &'a cargo_metadata::Metadata,
416     ) -> &'a cargo_metadata::Node {
417         metadata
418             .resolve
419             .as_ref()
420             .unwrap()
421             .nodes
422             .iter()
423             .find(|node| {
424                 let pkg = &metadata[&node.id];
425                 pkg.name == name
426             })
427             .unwrap()
428     }
429 
430     #[test]
sys_dependencies()431     fn sys_dependencies() {
432         let metadata = metadata::build_scripts();
433 
434         let openssl_node = find_metadata_node("openssl", &metadata);
435 
436         let dependencies = DependencySet::new_for_node(openssl_node, &metadata);
437 
438         let normal_sys_crate =
439             dependencies
440                 .normal_deps
441                 .items()
442                 .into_iter()
443                 .find(|(configuration, dep)| {
444                     let pkg = &metadata[&dep.package_id];
445                     configuration.is_none() && pkg.name == "openssl-sys"
446                 });
447 
448         let link_dep_sys_crate =
449             dependencies
450                 .build_link_deps
451                 .items()
452                 .into_iter()
453                 .find(|(configuration, dep)| {
454                     let pkg = &metadata[&dep.package_id];
455                     configuration.is_none() && pkg.name == "openssl-sys"
456                 });
457 
458         // sys crates like `openssl-sys` should always be dependencies of any
459         // crate which matches it's name minus the `-sys` suffix
460         assert!(normal_sys_crate.is_some());
461         assert!(link_dep_sys_crate.is_some());
462     }
463 
464     #[test]
sys_crate_with_build_script()465     fn sys_crate_with_build_script() {
466         let metadata = metadata::build_scripts();
467 
468         let libssh2 = find_metadata_node("libssh2-sys", &metadata);
469         let libssh2_depset = DependencySet::new_for_node(libssh2, &metadata);
470 
471         // Collect build dependencies into a set
472         let build_deps: BTreeSet<String> = libssh2_depset
473             .build_deps
474             .values()
475             .into_iter()
476             .map(|dep| dep.package_id.repr)
477             .collect();
478 
479         assert_eq!(
480             BTreeSet::from([
481                 "cc 1.0.72 (registry+https://github.com/rust-lang/crates.io-index)".to_owned(),
482                 "pkg-config 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)"
483                     .to_owned(),
484                 "vcpkg 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)".to_owned()
485             ]),
486             build_deps,
487         );
488 
489         // Collect normal dependencies into a set
490         let normal_deps: BTreeSet<String> = libssh2_depset
491             .normal_deps
492             .values()
493             .into_iter()
494             .map(|dep| dep.package_id.to_string())
495             .collect();
496 
497         assert_eq!(
498             BTreeSet::from([
499                 "libc 0.2.112 (registry+https://github.com/rust-lang/crates.io-index)".to_owned(),
500                 "libz-sys 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)".to_owned(),
501                 "openssl-sys 0.9.87 (registry+https://github.com/rust-lang/crates.io-index)"
502                     .to_owned(),
503             ]),
504             normal_deps,
505         );
506 
507         assert!(libssh2_depset.proc_macro_deps.is_empty());
508         assert!(libssh2_depset.normal_dev_deps.is_empty());
509         assert!(libssh2_depset.proc_macro_dev_deps.is_empty());
510         assert!(libssh2_depset.build_proc_macro_deps.is_empty());
511     }
512 
513     #[test]
tracked_aliases()514     fn tracked_aliases() {
515         let metadata = metadata::alias();
516 
517         let aliases_node = find_metadata_node("aliases", &metadata);
518         let dependencies = DependencySet::new_for_node(aliases_node, &metadata);
519 
520         let aliases: Vec<Dependency> = dependencies
521             .normal_deps
522             .items()
523             .into_iter()
524             .filter(|(configuration, dep)| configuration.is_none() && dep.alias.is_some())
525             .map(|(_, dep)| dep)
526             .collect();
527 
528         assert_eq!(aliases.len(), 2);
529 
530         let expected: BTreeSet<String> =
531             aliases.into_iter().map(|dep| dep.alias.unwrap()).collect();
532 
533         assert_eq!(
534             expected,
535             BTreeSet::from(["pinned_log".to_owned(), "pinned_names".to_owned()])
536         );
537     }
538 
539     #[test]
matched_rlib()540     fn matched_rlib() {
541         let metadata = metadata::crate_types();
542 
543         let node = find_metadata_node("crate-types", &metadata);
544         let dependencies = DependencySet::new_for_node(node, &metadata);
545 
546         let rlib_deps: Vec<Dependency> = dependencies
547             .normal_deps
548             .items()
549             .into_iter()
550             .filter(|(configuration, dep)| {
551                 let pkg = &metadata[&dep.package_id];
552                 configuration.is_none()
553                     && pkg
554                         .targets
555                         .iter()
556                         .any(|t| t.crate_types.contains(&"rlib".to_owned()))
557             })
558             .map(|(_, dep)| dep)
559             .collect();
560 
561         // Currently the only expected __explicitly__ "rlib" target in this metadata is `sysinfo`.
562         assert_eq!(rlib_deps.len(), 1);
563 
564         let sysinfo_dep = rlib_deps.iter().last().unwrap();
565         assert_eq!(sysinfo_dep.target_name, "sysinfo");
566     }
567 
568     #[test]
multiple_dep_kinds()569     fn multiple_dep_kinds() {
570         let metadata = metadata::multi_cfg_dep();
571 
572         let node = find_metadata_node("cpufeatures", &metadata);
573         let dependencies = DependencySet::new_for_node(node, &metadata);
574 
575         let libc_cfgs: BTreeSet<Option<String>> = dependencies
576             .normal_deps
577             .items()
578             .into_iter()
579             .filter(|(_, dep)| dep.target_name == "libc")
580             .map(|(configuration, _)| configuration)
581             .collect();
582 
583         assert_eq!(
584             BTreeSet::from([
585                 Some("aarch64-linux-android".to_owned()),
586                 Some("cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))".to_owned()),
587                 Some("cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))".to_owned()),
588             ]),
589             libc_cfgs,
590         );
591     }
592 
593     #[test]
multi_kind_proc_macro_dep()594     fn multi_kind_proc_macro_dep() {
595         let metadata = metadata::multi_kind_proc_macro_dep();
596 
597         let node = find_metadata_node("multi-kind-proc-macro-dep", &metadata);
598         let dependencies = DependencySet::new_for_node(node, &metadata);
599 
600         let lib_deps: Vec<_> = dependencies
601             .proc_macro_deps
602             .items()
603             .into_iter()
604             .map(|(_, dep)| dep.target_name)
605             .collect();
606         assert_eq!(lib_deps, vec!["paste"]);
607 
608         let build_deps: Vec<_> = dependencies
609             .build_proc_macro_deps
610             .items()
611             .into_iter()
612             .map(|(_, dep)| dep.target_name)
613             .collect();
614         assert_eq!(build_deps, vec!["paste"]);
615     }
616 
617     #[test]
optional_deps_disabled()618     fn optional_deps_disabled() {
619         let metadata = metadata::optional_deps_disabled();
620 
621         let node = find_metadata_node("clap", &metadata);
622         let dependencies = DependencySet::new_for_node(node, &metadata);
623 
624         assert!(!dependencies
625             .normal_deps
626             .items()
627             .iter()
628             .any(|(configuration, dep)| configuration.is_none()
629                 && (dep.target_name == "is-terminal" || dep.target_name == "termcolor")));
630     }
631 
632     #[test]
renamed_optional_deps_disabled()633     fn renamed_optional_deps_disabled() {
634         let metadata = metadata::renamed_optional_deps_disabled();
635 
636         let serde_with = find_metadata_node("serde_with", &metadata);
637         let serde_with_depset = DependencySet::new_for_node(serde_with, &metadata);
638         assert!(!serde_with_depset
639             .normal_deps
640             .items()
641             .iter()
642             .any(|(configuration, dep)| configuration.is_none() && dep.target_name == "indexmap"));
643     }
644 
645     #[test]
optional_deps_enabled()646     fn optional_deps_enabled() {
647         let metadata = metadata::optional_deps_enabled();
648 
649         let clap = find_metadata_node("clap", &metadata);
650         let clap_depset = DependencySet::new_for_node(clap, &metadata);
651         assert_eq!(
652             clap_depset
653                 .normal_deps
654                 .items()
655                 .iter()
656                 .filter(|(configuration, dep)| configuration.is_none()
657                     && (dep.target_name == "is-terminal" || dep.target_name == "termcolor"))
658                 .count(),
659             2
660         );
661 
662         let notify = find_metadata_node("notify", &metadata);
663         let notify_depset = DependencySet::new_for_node(notify, &metadata);
664 
665         // mio is not present in the common list of dependencies
666         assert!(!notify_depset
667             .normal_deps
668             .items()
669             .iter()
670             .any(|(configuration, dep)| configuration.is_none() && dep.target_name == "mio"));
671 
672         // mio is a dependency on linux
673         assert!(notify_depset
674             .normal_deps
675             .items()
676             .iter()
677             .any(|(configuration, dep)| configuration.as_deref()
678                 == Some("cfg(target_os = \"linux\")")
679                 && dep.target_name == "mio"));
680 
681         // mio is marked optional=true on macos
682         assert!(!notify_depset
683             .normal_deps
684             .items()
685             .iter()
686             .any(|(configuration, dep)| configuration.as_deref()
687                 == Some("cfg(target_os = \"macos\")")
688                 && dep.target_name == "mio"));
689     }
690 
691     #[test]
optional_deps_disabled_build_dep_enabled()692     fn optional_deps_disabled_build_dep_enabled() {
693         let metadata = metadata::optional_deps_disabled_build_dep_enabled();
694 
695         let node = find_metadata_node("gherkin", &metadata);
696         let dependencies = DependencySet::new_for_node(node, &metadata);
697 
698         assert!(!dependencies
699             .normal_deps
700             .items()
701             .iter()
702             .any(|(configuration, dep)| configuration.is_none() && dep.target_name == "serde"));
703 
704         assert!(dependencies
705             .build_deps
706             .items()
707             .iter()
708             .any(|(configuration, dep)| configuration.is_none() && dep.target_name == "serde"));
709     }
710 
711     #[test]
renamed_optional_deps_enabled()712     fn renamed_optional_deps_enabled() {
713         let metadata = metadata::renamed_optional_deps_enabled();
714 
715         let p256 = find_metadata_node("p256", &metadata);
716         let p256_depset = DependencySet::new_for_node(p256, &metadata);
717         assert_eq!(
718             p256_depset
719                 .normal_deps
720                 .items()
721                 .iter()
722                 .filter(|(configuration, dep)| configuration.is_none() && dep.target_name == "ecdsa")
723                 .count(),
724             1
725         );
726     }
727 }
728