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