cargo/core/resolver/features.rs
1//! Resolves conditional compilation for [`features` section] in the manifest.
2//!
3//! This is a [new feature resolver] that runs independently of the main
4//! dependency resolver. It has several options which can enable new feature
5//! resolution behavior.
6//!
7//! One of its key characteristics is that it can avoid unifying features for
8//! shared dependencies in some situations. See [`FeatureOpts`] for the
9//! different behaviors that can be enabled. If no extra options are enabled,
10//! then it should behave exactly the same as the dependency resolver's
11//! feature resolution.
12//!
13//! The preferred way to engage this new resolver is via [`resolve_ws_with_opts`].
14//!
15//! This does not *replace* feature resolution in the dependency resolver, but
16//! instead acts as a second pass which can *narrow* the features selected in
17//! the dependency resolver. The dependency resolver still needs to do its own
18//! feature resolution in order to avoid selecting optional dependencies that
19//! are never enabled. The dependency resolver could, in theory, just assume
20//! all optional dependencies on all packages are enabled (and remove all
21//! knowledge of features), but that could introduce new requirements that
22//! might change old behavior or cause conflicts. Maybe some day in the future
23//! we could experiment with that, but it seems unlikely to work or be all
24//! that helpful.
25//!
26//! ## Assumptions
27//!
28//! There are many assumptions made about the dependency resolver:
29//!
30//! * Assumes feature validation has already been done during the construction
31//! of feature maps, so the feature resolver doesn't do that validation at all.
32//! * Assumes `dev-dependencies` within a dependency have been removed
33//! in the given [`Resolve`].
34//!
35//! There are probably other assumptions that I am forgetting.
36//!
37//! [`features` section]: https://doc.rust-lang.org/nightly/cargo/reference/features.html
38//! [new feature resolver]: https://doc.rust-lang.org/nightly/cargo/reference/resolver.html#feature-resolver-version-2
39//! [`resolve_ws_with_opts`]: crate::ops::resolve_ws_with_opts
40
41use crate::core::compiler::{CompileKind, CompileTarget, RustcTargetData};
42use crate::core::dependency::{ArtifactTarget, DepKind, Dependency};
43use crate::core::resolver::types::FeaturesSet;
44use crate::core::resolver::{Resolve, ResolveBehavior};
45use crate::core::{FeatureValue, PackageId, PackageIdSpec, PackageSet, Workspace};
46use crate::util::interning::{InternedString, INTERNED_DEFAULT};
47use crate::util::CargoResult;
48use anyhow::{bail, Context};
49use itertools::Itertools;
50use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
51use std::rc::Rc;
52
53/// The key used in various places to store features for a particular dependency.
54/// The actual discrimination happens with the [`FeaturesFor`] type.
55type PackageFeaturesKey = (PackageId, FeaturesFor);
56/// Map of activated features.
57type ActivateMap = HashMap<PackageFeaturesKey, BTreeSet<InternedString>>;
58
59/// Set of all activated features for all packages in the resolve graph.
60pub struct ResolvedFeatures {
61 activated_features: ActivateMap,
62 /// Optional dependencies that should be built.
63 ///
64 /// The value is the `name_in_toml` of the dependencies.
65 activated_dependencies: ActivateMap,
66 opts: FeatureOpts,
67}
68
69/// Options for how the feature resolver works.
70#[derive(Default)]
71pub struct FeatureOpts {
72 /// Build deps and proc-macros will not share features with other dep kinds,
73 /// and so won't artifact targets.
74 /// In other terms, if true, features associated with certain kinds of dependencies
75 /// will only be unified together.
76 /// If false, there is only one namespace for features, unifying all features across
77 /// all dependencies, no matter what kind.
78 decouple_host_deps: bool,
79 /// Dev dep features will not be activated unless needed.
80 decouple_dev_deps: bool,
81 /// Targets that are not in use will not activate features.
82 ignore_inactive_targets: bool,
83 /// If enabled, compare against old resolver (for testing).
84 compare: bool,
85}
86
87/// Flag to indicate if Cargo is building *any* dev units (tests, examples, etc.).
88///
89/// This disables decoupling of dev dependencies. It may be possible to relax
90/// this in the future, but it will require significant changes to how unit
91/// dependencies are computed, and can result in longer build times with
92/// `cargo test` because the lib may need to be built 3 times instead of
93/// twice.
94#[derive(Copy, Clone, PartialEq)]
95pub enum HasDevUnits {
96 Yes,
97 No,
98}
99
100/// Flag to indicate that target-specific filtering should be disabled.
101#[derive(Copy, Clone, PartialEq)]
102pub enum ForceAllTargets {
103 Yes,
104 No,
105}
106
107/// Flag to indicate if features are requested for a certain type of dependency.
108///
109/// This is primarily used for constructing a [`PackageFeaturesKey`] to decouple
110/// activated features of the same package with different types of dependency.
111#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
112pub enum FeaturesFor {
113 /// Normal or dev dependency.
114 #[default]
115 NormalOrDev,
116 /// Build dependency or proc-macro.
117 HostDep,
118 /// Any dependency with both artifact and target specified.
119 ///
120 /// That is, `dep = { …, artifact = <crate-type>, target = <triple> }`
121 ArtifactDep(CompileTarget),
122}
123
124impl std::fmt::Display for FeaturesFor {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 match self {
127 FeaturesFor::HostDep => f.write_str("host"),
128 FeaturesFor::ArtifactDep(target) => f.write_str(&target.rustc_target()),
129 FeaturesFor::NormalOrDev => Ok(()),
130 }
131 }
132}
133
134impl FeaturesFor {
135 pub fn from_for_host(for_host: bool) -> FeaturesFor {
136 if for_host {
137 FeaturesFor::HostDep
138 } else {
139 FeaturesFor::NormalOrDev
140 }
141 }
142
143 pub fn from_for_host_or_artifact_target(
144 for_host: bool,
145 artifact_target: Option<CompileTarget>,
146 ) -> FeaturesFor {
147 match artifact_target {
148 Some(target) => FeaturesFor::ArtifactDep(target),
149 None => {
150 if for_host {
151 FeaturesFor::HostDep
152 } else {
153 FeaturesFor::NormalOrDev
154 }
155 }
156 }
157 }
158
159 fn apply_opts(self, opts: &FeatureOpts) -> Self {
160 if opts.decouple_host_deps {
161 self
162 } else {
163 FeaturesFor::default()
164 }
165 }
166}
167
168impl FeatureOpts {
169 pub fn new(
170 ws: &Workspace<'_>,
171 has_dev_units: HasDevUnits,
172 force_all_targets: ForceAllTargets,
173 ) -> CargoResult<FeatureOpts> {
174 let mut opts = FeatureOpts::default();
175 let unstable_flags = ws.gctx().cli_unstable();
176 let mut enable = |feat_opts: &Vec<String>| {
177 for opt in feat_opts {
178 match opt.as_ref() {
179 "build_dep" | "host_dep" => opts.decouple_host_deps = true,
180 "dev_dep" => opts.decouple_dev_deps = true,
181 "itarget" => opts.ignore_inactive_targets = true,
182 "all" => {
183 opts.decouple_host_deps = true;
184 opts.decouple_dev_deps = true;
185 opts.ignore_inactive_targets = true;
186 }
187 "compare" => opts.compare = true,
188 "ws" => unimplemented!(),
189 s => bail!("-Zfeatures flag `{}` is not supported", s),
190 }
191 }
192 Ok(())
193 };
194 if let Some(feat_opts) = unstable_flags.features.as_ref() {
195 enable(feat_opts)?;
196 }
197 match ws.resolve_behavior() {
198 ResolveBehavior::V1 => {}
199 ResolveBehavior::V2 | ResolveBehavior::V3 => {
200 enable(&vec!["all".to_string()]).unwrap();
201 }
202 }
203 if let HasDevUnits::Yes = has_dev_units {
204 // Dev deps cannot be decoupled when they are in use.
205 opts.decouple_dev_deps = false;
206 }
207 if let ForceAllTargets::Yes = force_all_targets {
208 opts.ignore_inactive_targets = false;
209 }
210 Ok(opts)
211 }
212
213 /// Creates a new `FeatureOpts` for the given behavior.
214 pub fn new_behavior(behavior: ResolveBehavior, has_dev_units: HasDevUnits) -> FeatureOpts {
215 match behavior {
216 ResolveBehavior::V1 => FeatureOpts::default(),
217 ResolveBehavior::V2 | ResolveBehavior::V3 => FeatureOpts {
218 decouple_host_deps: true,
219 decouple_dev_deps: has_dev_units == HasDevUnits::No,
220 ignore_inactive_targets: true,
221 compare: false,
222 },
223 }
224 }
225}
226
227/// Features flags requested for a package.
228///
229/// This should be cheap and fast to clone, it is used in the resolver for
230/// various caches.
231///
232/// This is split into enum variants because the resolver needs to handle
233/// features coming from different places (command-line and dependency
234/// declarations), but those different places have different constraints on
235/// which syntax is allowed. This helps ensure that every place dealing with
236/// features is properly handling those syntax restrictions.
237#[derive(Debug, Clone, Eq, PartialEq, Hash)]
238pub enum RequestedFeatures {
239 /// Features requested on the command-line with flags.
240 CliFeatures(CliFeatures),
241 /// Features specified in a dependency declaration.
242 DepFeatures {
243 /// The `features` dependency field.
244 features: FeaturesSet,
245 /// The `default-features` dependency field.
246 uses_default_features: bool,
247 },
248}
249
250/// Features specified on the command-line.
251#[derive(Debug, Clone, Eq, PartialEq, Hash)]
252pub struct CliFeatures {
253 /// Features from the `--features` flag.
254 pub features: Rc<BTreeSet<FeatureValue>>,
255 /// The `--all-features` flag.
256 pub all_features: bool,
257 /// Inverse of `--no-default-features` flag.
258 pub uses_default_features: bool,
259}
260
261impl CliFeatures {
262 /// Creates a new `CliFeatures` from the given command-line flags.
263 pub fn from_command_line(
264 features: &[String],
265 all_features: bool,
266 uses_default_features: bool,
267 ) -> CargoResult<CliFeatures> {
268 let features = Rc::new(CliFeatures::split_features(features));
269 // Some early validation to ensure correct syntax.
270 for feature in features.iter() {
271 match feature {
272 // Maybe call validate_feature_name here once it is an error?
273 FeatureValue::Feature(_) => {}
274 FeatureValue::Dep { .. } => {
275 bail!(
276 "feature `{}` is not allowed to use explicit `dep:` syntax",
277 feature
278 );
279 }
280 FeatureValue::DepFeature { dep_feature, .. } => {
281 if dep_feature.contains('/') {
282 bail!("multiple slashes in feature `{}` is not allowed", feature);
283 }
284 }
285 }
286 }
287 Ok(CliFeatures {
288 features,
289 all_features,
290 uses_default_features,
291 })
292 }
293
294 /// Creates a new `CliFeatures` with the given `all_features` setting.
295 pub fn new_all(all_features: bool) -> CliFeatures {
296 CliFeatures {
297 features: Rc::new(BTreeSet::new()),
298 all_features,
299 uses_default_features: true,
300 }
301 }
302
303 fn split_features(features: &[String]) -> BTreeSet<FeatureValue> {
304 features
305 .iter()
306 .flat_map(|s| s.split_whitespace())
307 .flat_map(|s| s.split(','))
308 .filter(|s| !s.is_empty())
309 .map(InternedString::new)
310 .map(FeatureValue::new)
311 .collect()
312 }
313}
314
315impl ResolvedFeatures {
316 /// Returns the list of features that are enabled for the given package.
317 pub fn activated_features(
318 &self,
319 pkg_id: PackageId,
320 features_for: FeaturesFor,
321 ) -> Vec<InternedString> {
322 if let Some(res) = self.activated_features_unverified(pkg_id, features_for) {
323 res
324 } else {
325 panic!(
326 "did not find features for ({pkg_id:?}, {features_for:?}) within activated_features:\n{:#?}",
327 self.activated_features.keys()
328 )
329 }
330 }
331
332 /// Variant of `activated_features` that returns `None` if this is
333 /// not a valid `pkg_id/is_build` combination. Used in places which do
334 /// not know which packages are activated (like `cargo clean`).
335 pub fn activated_features_unverified(
336 &self,
337 pkg_id: PackageId,
338 features_for: FeaturesFor,
339 ) -> Option<Vec<InternedString>> {
340 let fk = features_for.apply_opts(&self.opts);
341 if let Some(fs) = self.activated_features.get(&(pkg_id, fk)) {
342 Some(fs.iter().cloned().collect())
343 } else {
344 None
345 }
346 }
347
348 /// Returns if the given dependency should be included.
349 ///
350 /// This handles dependencies disabled via `cfg` expressions and optional
351 /// dependencies which are not enabled.
352 pub fn is_dep_activated(
353 &self,
354 pkg_id: PackageId,
355 features_for: FeaturesFor,
356 dep_name: InternedString,
357 ) -> bool {
358 let key = features_for.apply_opts(&self.opts);
359 self.activated_dependencies
360 .get(&(pkg_id, key))
361 .map(|deps| deps.contains(&dep_name))
362 .unwrap_or(false)
363 }
364
365 /// Compares the result against the original resolver behavior.
366 ///
367 /// Used by `cargo fix --edition` to display any differences.
368 pub fn compare_legacy(&self, legacy: &ResolvedFeatures) -> DiffMap {
369 self.activated_features
370 .iter()
371 .filter_map(|((pkg_id, for_host), new_features)| {
372 let old_features = legacy
373 .activated_features
374 .get(&(*pkg_id, *for_host))
375 // The new features may have for_host entries where the old one does not.
376 .or_else(|| {
377 legacy
378 .activated_features
379 .get(&(*pkg_id, FeaturesFor::default()))
380 })
381 .map(|feats| feats.iter().cloned().collect())
382 .unwrap_or_else(|| BTreeSet::new());
383 // The new resolver should never add features.
384 assert_eq!(new_features.difference(&old_features).next(), None);
385 let removed_features: BTreeSet<_> =
386 old_features.difference(new_features).cloned().collect();
387 if removed_features.is_empty() {
388 None
389 } else {
390 Some(((*pkg_id, *for_host), removed_features))
391 }
392 })
393 .collect()
394 }
395}
396
397/// Map of differences.
398///
399/// Key is `(pkg_id, for_host)`. Value is a set of features or dependencies removed.
400pub type DiffMap = BTreeMap<PackageFeaturesKey, BTreeSet<InternedString>>;
401
402/// The new feature resolver that [`resolve`]s your project.
403///
404/// For more information, please see the [module-level documentation].
405///
406/// [`resolve`]: Self::resolve
407/// [module-level documentation]: crate::core::resolver::features
408pub struct FeatureResolver<'a, 'gctx> {
409 ws: &'a Workspace<'gctx>,
410 target_data: &'a mut RustcTargetData<'gctx>,
411 /// The platforms to build for, requested by the user.
412 requested_targets: &'a [CompileKind],
413 resolve: &'a Resolve,
414 package_set: &'a PackageSet<'gctx>,
415 /// Options that change how the feature resolver operates.
416 opts: FeatureOpts,
417 /// Map of features activated for each package.
418 activated_features: ActivateMap,
419 /// Map of optional dependencies activated for each package.
420 activated_dependencies: ActivateMap,
421 /// Keeps track of which packages have had its dependencies processed.
422 /// Used to avoid cycles, and to speed up processing.
423 processed_deps: HashSet<PackageFeaturesKey>,
424 /// If this is `true`, then a non-default `feature_key` needs to be tracked while
425 /// traversing the graph.
426 ///
427 /// This is only here to avoid calling [`has_any_proc_macro`] when all feature
428 /// options are disabled (because [`has_any_proc_macro`] can trigger downloads).
429 /// This has to be separate from [`FeatureOpts::decouple_host_deps`] because
430 /// `for_host` tracking is also needed for `itarget` to work properly.
431 ///
432 /// [`has_any_proc_macro`]: FeatureResolver::has_any_proc_macro
433 track_for_host: bool,
434 /// `dep_name?/feat_name` features that will be activated if `dep_name` is
435 /// ever activated.
436 ///
437 /// The key is the `(package, for_host, dep_name)` of the package whose
438 /// dependency will trigger the addition of new features. The value is the
439 /// set of features to activate.
440 deferred_weak_dependencies:
441 HashMap<(PackageId, FeaturesFor, InternedString), HashSet<InternedString>>,
442}
443
444impl<'a, 'gctx> FeatureResolver<'a, 'gctx> {
445 /// Runs the resolution algorithm and returns a new [`ResolvedFeatures`]
446 /// with the result.
447 #[tracing::instrument(skip_all)]
448 pub fn resolve(
449 ws: &Workspace<'gctx>,
450 target_data: &'a mut RustcTargetData<'gctx>,
451 resolve: &Resolve,
452 package_set: &'a PackageSet<'gctx>,
453 cli_features: &CliFeatures,
454 specs: &[PackageIdSpec],
455 requested_targets: &[CompileKind],
456 opts: FeatureOpts,
457 ) -> CargoResult<ResolvedFeatures> {
458 let track_for_host = opts.decouple_host_deps || opts.ignore_inactive_targets;
459 let mut r = FeatureResolver {
460 ws,
461 target_data,
462 requested_targets,
463 resolve,
464 package_set,
465 opts,
466 activated_features: HashMap::new(),
467 activated_dependencies: HashMap::new(),
468 processed_deps: HashSet::new(),
469 track_for_host,
470 deferred_weak_dependencies: HashMap::new(),
471 };
472 r.do_resolve(specs, cli_features)?;
473 tracing::debug!("features={:#?}", r.activated_features);
474 if r.opts.compare {
475 r.compare();
476 }
477 Ok(ResolvedFeatures {
478 activated_features: r.activated_features,
479 activated_dependencies: r.activated_dependencies,
480 opts: r.opts,
481 })
482 }
483
484 /// Performs the process of resolving all features for the resolve graph.
485 fn do_resolve(
486 &mut self,
487 specs: &[PackageIdSpec],
488 cli_features: &CliFeatures,
489 ) -> CargoResult<()> {
490 let member_features = self.ws.members_with_features(specs, cli_features)?;
491 for (member, cli_features) in &member_features {
492 let fvs = self.fvs_from_requested(member.package_id(), cli_features);
493 let fk = if self.track_for_host && self.has_any_proc_macro(member.package_id()) {
494 // Also activate for normal dependencies. This is needed if the
495 // proc-macro includes other targets (like binaries or tests),
496 // or running in `cargo test`. Note that in a workspace, if
497 // the proc-macro is selected on the command like (like with
498 // `--workspace`), this forces feature unification with normal
499 // dependencies. This is part of the bigger problem where
500 // features depend on which packages are built.
501 self.activate_pkg(member.package_id(), FeaturesFor::default(), &fvs)?;
502 FeaturesFor::HostDep
503 } else {
504 FeaturesFor::default()
505 };
506 self.activate_pkg(member.package_id(), fk, &fvs)?;
507 }
508 Ok(())
509 }
510
511 /// Activates [`FeatureValue`]s on the given package.
512 ///
513 /// This is the main entrance into the recursion of feature activation
514 /// for a package.
515 fn activate_pkg(
516 &mut self,
517 pkg_id: PackageId,
518 fk: FeaturesFor,
519 fvs: &[FeatureValue],
520 ) -> CargoResult<()> {
521 tracing::trace!("activate_pkg {} {}", pkg_id.name(), fk);
522 // Add an empty entry to ensure everything is covered. This is intended for
523 // finding bugs where the resolver missed something it should have visited.
524 // Remove this in the future if `activated_features` uses an empty default.
525 self.activated_features
526 .entry((pkg_id, fk.apply_opts(&self.opts)))
527 .or_insert_with(BTreeSet::new);
528 for fv in fvs {
529 self.activate_fv(pkg_id, fk, fv)?;
530 }
531 if !self.processed_deps.insert((pkg_id, fk)) {
532 // Already processed dependencies. There's no need to process them
533 // again. This is primarily to avoid cycles, but also helps speed
534 // things up.
535 //
536 // This is safe because if another package comes along and adds a
537 // feature on this package, it will immediately add it (in
538 // `activate_fv`), and recurse as necessary right then and there.
539 // For example, consider we've already processed our dependencies,
540 // and another package comes along and enables one of our optional
541 // dependencies, it will do so immediately in the
542 // `FeatureValue::DepFeature` branch, and then immediately
543 // recurse into that optional dependency. This also holds true for
544 // features that enable other features.
545 return Ok(());
546 }
547 for (dep_pkg_id, deps) in self.deps(pkg_id, fk)? {
548 for (dep, dep_fk) in deps {
549 if dep.is_optional() {
550 // Optional dependencies are enabled in `activate_fv` when
551 // a feature enables it.
552 continue;
553 }
554 // Recurse into the dependency.
555 let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
556 self.activate_pkg(dep_pkg_id, dep_fk, &fvs)?;
557 }
558 }
559 Ok(())
560 }
561
562 /// Activate a single `FeatureValue` for a package.
563 fn activate_fv(
564 &mut self,
565 pkg_id: PackageId,
566 fk: FeaturesFor,
567 fv: &FeatureValue,
568 ) -> CargoResult<()> {
569 tracing::trace!("activate_fv {} {} {}", pkg_id.name(), fk, fv);
570 match fv {
571 FeatureValue::Feature(f) => {
572 self.activate_rec(pkg_id, fk, *f)?;
573 }
574 FeatureValue::Dep { dep_name } => {
575 self.activate_dependency(pkg_id, fk, *dep_name)?;
576 }
577 FeatureValue::DepFeature {
578 dep_name,
579 dep_feature,
580 weak,
581 } => {
582 self.activate_dep_feature(pkg_id, fk, *dep_name, *dep_feature, *weak)?;
583 }
584 }
585 Ok(())
586 }
587
588 /// Activate the given feature for the given package, and then recursively
589 /// activate any other features that feature enables.
590 fn activate_rec(
591 &mut self,
592 pkg_id: PackageId,
593 fk: FeaturesFor,
594 feature_to_enable: InternedString,
595 ) -> CargoResult<()> {
596 tracing::trace!(
597 "activate_rec {} {} feat={}",
598 pkg_id.name(),
599 fk,
600 feature_to_enable
601 );
602 let enabled = self
603 .activated_features
604 .entry((pkg_id, fk.apply_opts(&self.opts)))
605 .or_insert_with(BTreeSet::new);
606 if !enabled.insert(feature_to_enable) {
607 // Already enabled.
608 return Ok(());
609 }
610 let summary = self.resolve.summary(pkg_id);
611 let feature_map = summary.features();
612 let Some(fvs) = feature_map.get(&feature_to_enable) else {
613 // TODO: this should only happen for optional dependencies.
614 // Other cases should be validated by Summary's `build_feature_map`.
615 // Figure out some way to validate this assumption.
616 tracing::debug!(
617 "pkg {:?} does not define feature {}",
618 pkg_id,
619 feature_to_enable
620 );
621 return Ok(());
622 };
623 for fv in fvs {
624 self.activate_fv(pkg_id, fk, fv)?;
625 }
626 Ok(())
627 }
628
629 /// Activate a dependency (`dep:dep_name` syntax).
630 fn activate_dependency(
631 &mut self,
632 pkg_id: PackageId,
633 fk: FeaturesFor,
634 dep_name: InternedString,
635 ) -> CargoResult<()> {
636 // Mark this dependency as activated.
637 let save_decoupled = fk.apply_opts(&self.opts);
638 self.activated_dependencies
639 .entry((pkg_id, save_decoupled))
640 .or_default()
641 .insert(dep_name);
642 // Check for any deferred features.
643 let to_enable = self
644 .deferred_weak_dependencies
645 .remove(&(pkg_id, fk, dep_name));
646 // Activate the optional dep.
647 for (dep_pkg_id, deps) in self.deps(pkg_id, fk)? {
648 for (dep, dep_fk) in deps {
649 if dep.name_in_toml() != dep_name {
650 continue;
651 }
652 if let Some(to_enable) = &to_enable {
653 for dep_feature in to_enable {
654 tracing::trace!(
655 "activate deferred {} {} -> {}/{}",
656 pkg_id.name(),
657 fk,
658 dep_name,
659 dep_feature
660 );
661 let fv = FeatureValue::new(*dep_feature);
662 self.activate_fv(dep_pkg_id, dep_fk, &fv)?;
663 }
664 }
665 let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
666 self.activate_pkg(dep_pkg_id, dep_fk, &fvs)?;
667 }
668 }
669 Ok(())
670 }
671
672 /// Activate a feature within a dependency (`dep_name/feat_name` syntax).
673 fn activate_dep_feature(
674 &mut self,
675 pkg_id: PackageId,
676 fk: FeaturesFor,
677 dep_name: InternedString,
678 dep_feature: InternedString,
679 weak: bool,
680 ) -> CargoResult<()> {
681 for (dep_pkg_id, deps) in self.deps(pkg_id, fk)? {
682 for (dep, dep_fk) in deps {
683 if dep.name_in_toml() != dep_name {
684 continue;
685 }
686 if dep.is_optional() {
687 let save_for_host = fk.apply_opts(&self.opts);
688 if weak
689 && !self
690 .activated_dependencies
691 .get(&(pkg_id, save_for_host))
692 .map(|deps| deps.contains(&dep_name))
693 .unwrap_or(false)
694 {
695 // This is weak, but not yet activated. Defer in case
696 // something comes along later and enables it.
697 tracing::trace!(
698 "deferring feature {} {} -> {}/{}",
699 pkg_id.name(),
700 fk,
701 dep_name,
702 dep_feature
703 );
704 self.deferred_weak_dependencies
705 .entry((pkg_id, fk, dep_name))
706 .or_default()
707 .insert(dep_feature);
708 continue;
709 }
710
711 // Activate the dependency on self.
712 let fv = FeatureValue::Dep { dep_name };
713 self.activate_fv(pkg_id, fk, &fv)?;
714 if !weak {
715 // The old behavior before weak dependencies were
716 // added is to also enables a feature of the same
717 // name.
718 //
719 // Don't enable if the implicit optional dependency
720 // feature wasn't created due to `dep:` hiding.
721 // See rust-lang/cargo#10788 and rust-lang/cargo#12130
722 let summary = self.resolve.summary(pkg_id);
723 let feature_map = summary.features();
724 if feature_map.contains_key(&dep_name) {
725 self.activate_rec(pkg_id, fk, dep_name)?;
726 }
727 }
728 }
729 // Activate the feature on the dependency.
730 let fv = FeatureValue::new(dep_feature);
731 self.activate_fv(dep_pkg_id, dep_fk, &fv)?;
732 }
733 }
734 Ok(())
735 }
736
737 /// Returns Vec of `FeatureValues` from a Dependency definition.
738 fn fvs_from_dependency(&self, dep_id: PackageId, dep: &Dependency) -> Vec<FeatureValue> {
739 let summary = self.resolve.summary(dep_id);
740 let feature_map = summary.features();
741 let mut result: Vec<FeatureValue> = dep
742 .features()
743 .iter()
744 .map(|f| FeatureValue::new(*f))
745 .collect();
746 if dep.uses_default_features() && feature_map.contains_key(&INTERNED_DEFAULT) {
747 result.push(FeatureValue::Feature(INTERNED_DEFAULT));
748 }
749 result
750 }
751
752 /// Returns Vec of `FeatureValues` from a set of command-line features.
753 fn fvs_from_requested(
754 &self,
755 pkg_id: PackageId,
756 cli_features: &CliFeatures,
757 ) -> Vec<FeatureValue> {
758 let summary = self.resolve.summary(pkg_id);
759 let feature_map = summary.features();
760
761 let mut result: Vec<FeatureValue> = cli_features.features.iter().cloned().collect();
762 if cli_features.uses_default_features && feature_map.contains_key(&INTERNED_DEFAULT) {
763 result.push(FeatureValue::Feature(INTERNED_DEFAULT));
764 }
765
766 if cli_features.all_features {
767 result.extend(feature_map.keys().map(|k| FeatureValue::Feature(*k)))
768 }
769
770 result
771 }
772
773 /// Returns the dependencies for a package, filtering out inactive targets.
774 fn deps(
775 &mut self,
776 pkg_id: PackageId,
777 fk: FeaturesFor,
778 ) -> CargoResult<Vec<(PackageId, Vec<(&'a Dependency, FeaturesFor)>)>> {
779 // Helper for determining if a platform is activated.
780 fn platform_activated(
781 dep: &Dependency,
782 fk: FeaturesFor,
783 target_data: &RustcTargetData<'_>,
784 requested_targets: &[CompileKind],
785 ) -> bool {
786 // We always count platforms as activated if the target stems from an artifact
787 // dependency's target specification. This triggers in conjunction with
788 // `[target.'cfg(…)'.dependencies]` manifest sections.
789 match (dep.is_build(), fk) {
790 (true, _) | (_, FeaturesFor::HostDep) => {
791 // We always care about build-dependencies, and they are always
792 // Host. If we are computing dependencies "for a build script",
793 // even normal dependencies are host-only.
794 target_data.dep_platform_activated(dep, CompileKind::Host)
795 }
796 (_, FeaturesFor::NormalOrDev) => requested_targets
797 .iter()
798 .any(|kind| target_data.dep_platform_activated(dep, *kind)),
799 (_, FeaturesFor::ArtifactDep(target)) => {
800 target_data.dep_platform_activated(dep, CompileKind::Target(target))
801 }
802 }
803 }
804
805 self.resolve
806 .deps(pkg_id)
807 .map(|(dep_id, deps)| {
808 let deps = deps
809 .iter()
810 .filter(|dep| {
811 if dep.platform().is_some()
812 && self.opts.ignore_inactive_targets
813 && !platform_activated(
814 dep,
815 fk,
816 self.target_data,
817 self.requested_targets,
818 )
819 {
820 return false;
821 }
822 if self.opts.decouple_dev_deps && dep.kind() == DepKind::Development {
823 return false;
824 }
825 true
826 })
827 .collect_vec() // collect because the next closure mutably borrows `self.target_data`
828 .into_iter()
829 .map(|dep| {
830 // Each `dep`endency can be built for multiple targets. For one, it
831 // may be a library target which is built as initially configured
832 // by `fk`. If it appears as build dependency, it must be built
833 // for the host.
834 //
835 // It may also be an artifact dependency,
836 // which could be built either
837 //
838 // - for a specified (aka 'forced') target, specified by
839 // `dep = { …, target = <triple>` }`
840 // - as an artifact for use in build dependencies that should
841 // build for whichever `--target`s are specified
842 // - like a library would be built
843 //
844 // Generally, the logic for choosing a target for dependencies is
845 // unaltered and used to determine how to build non-artifacts,
846 // artifacts without target specification and no library,
847 // or an artifacts library.
848 //
849 // All this may result in a dependency being built multiple times
850 // for various targets which are either specified in the manifest
851 // or on the cargo command-line.
852 let lib_fk = if fk == FeaturesFor::default() {
853 (self.track_for_host && (dep.is_build() || self.has_proc_macro_lib(dep_id)))
854 .then(|| FeaturesFor::HostDep)
855 .unwrap_or_default()
856 } else {
857 fk
858 };
859
860 // `artifact_target_keys` are produced to fulfil the needs of artifacts that have a target specification.
861 let artifact_target_keys = dep
862 .artifact()
863 .map(|artifact| {
864 let host_triple = self.target_data.rustc.host;
865 // not all targets may be queried before resolution since artifact dependencies
866 // and per-pkg-targets are not immediately known.
867 let mut activate_target = |target| {
868 let name = dep.name_in_toml();
869 self.target_data
870 .merge_compile_kind(CompileKind::Target(target))
871 .with_context(|| format!("failed to determine target information for target `{target}`.\n \
872 Artifact dependency `{name}` in package `{pkg_id}` requires building for `{target}`", target = target.rustc_target()))
873 };
874 CargoResult::Ok((
875 artifact.is_lib(),
876 artifact
877 .target()
878 .map(|target| {
879 CargoResult::Ok(match target {
880 ArtifactTarget::Force(target) => {
881 activate_target(target)?;
882 vec![FeaturesFor::ArtifactDep(target)]
883 }
884 // FIXME: this needs to interact with the `default-target` and `forced-target` values
885 // of the dependency
886 ArtifactTarget::BuildDependencyAssumeTarget => self
887 .requested_targets
888 .iter()
889 .map(|kind| match kind {
890 CompileKind::Host => {
891 CompileTarget::new(&host_triple)
892 .unwrap()
893 }
894 CompileKind::Target(target) => *target,
895 })
896 .map(|target| {
897 activate_target(target)?;
898 Ok(FeaturesFor::ArtifactDep(target))
899 })
900 .collect::<CargoResult<_>>()?,
901 })
902 })
903 .transpose()?,
904 ))
905 })
906 .transpose()?;
907
908 let dep_fks = match artifact_target_keys {
909 // The artifact is also a library and does specify custom
910 // targets.
911 // The library's feature key needs to be used alongside
912 // the keys artifact targets.
913 Some((is_lib, Some(mut dep_fks))) if is_lib => {
914 dep_fks.push(lib_fk);
915 dep_fks
916 }
917 // The artifact is not a library, but does specify
918 // custom targets.
919 // Use only these targets feature keys.
920 Some((_, Some(dep_fks))) => dep_fks,
921 // There is no artifact in the current dependency
922 // or there is no target specified on the artifact.
923 // Use the standard feature key without any alteration.
924 Some((_, None)) | None => vec![lib_fk],
925 };
926 Ok(dep_fks.into_iter().map(move |dep_fk| (dep, dep_fk)))
927 })
928 .flatten_ok()
929 .collect::<CargoResult<Vec<_>>>()?;
930 Ok((dep_id, deps))
931 })
932 .filter(|res| res.as_ref().map_or(true, |(_id, deps)| !deps.is_empty()))
933 .collect()
934 }
935
936 /// Compare the activated features to the resolver. Used for testing.
937 fn compare(&self) {
938 let mut found = false;
939 for ((pkg_id, dep_kind), features) in &self.activated_features {
940 let r_features = self.resolve.features(*pkg_id);
941 if !r_features.iter().eq(features.iter()) {
942 crate::drop_eprintln!(
943 self.ws.gctx(),
944 "{}/{:?} features mismatch\nresolve: {:?}\nnew: {:?}\n",
945 pkg_id,
946 dep_kind,
947 r_features,
948 features
949 );
950 found = true;
951 }
952 }
953 if found {
954 panic!("feature mismatch");
955 }
956 }
957
958 /// Whether the given package has any proc macro target, including proc-macro examples.
959 fn has_any_proc_macro(&self, package_id: PackageId) -> bool {
960 self.package_set
961 .get_one(package_id)
962 .expect("packages downloaded")
963 .proc_macro()
964 }
965
966 /// Whether the given package is a proc macro lib target.
967 ///
968 /// This is useful for checking if a dependency is a proc macro,
969 /// as it is not possible to depend on a non-lib target as a proc-macro.
970 fn has_proc_macro_lib(&self, package_id: PackageId) -> bool {
971 self.package_set
972 .get_one(package_id)
973 .expect("packages downloaded")
974 .library()
975 .map(|lib| lib.proc_macro())
976 .unwrap_or_default()
977 }
978}