Skip to main content

cargo/core/resolver/
features.rs

1//! Feature resolver.
2//!
3//! This is a new feature resolver that runs independently of the main
4//! dependency resolver. It is intended to make it easier to experiment with
5//! new behaviors. When `-Zfeatures` is not used, it will fall back to using
6//! the original `Resolve` feature computation. With `-Zfeatures` enabled,
7//! this will walk the dependency graph and compute the features using a
8//! different algorithm.
9//!
10//! One of its key characteristics is that it can avoid unifying features for
11//! shared dependencies in some situations. See `FeatureOpts` for the
12//! different behaviors that can be enabled. If no extra options are enabled,
13//! then it should behave exactly the same as the dependency resolver's
14//! feature resolution. This can be verified by setting the
15//! `__CARGO_FORCE_NEW_FEATURES=compare` environment variable and running
16//! Cargo's test suite (or building other projects), and checking if it
17//! panics. Note: the `features2` tests will fail because they intentionally
18//! compare the old vs new behavior, so forcing the old behavior will
19//! naturally fail the tests.
20//!
21//! The preferred way to engage this new resolver is via
22//! `resolve_ws_with_opts`.
23//!
24//! This does not *replace* feature resolution in the dependency resolver, but
25//! instead acts as a second pass which can *narrow* the features selected in
26//! the dependency resolver. The dependency resolver still needs to do its own
27//! feature resolution in order to avoid selecting optional dependencies that
28//! are never enabled. The dependency resolver could, in theory, just assume
29//! all optional dependencies on all packages are enabled (and remove all
30//! knowledge of features), but that could introduce new requirements that
31//! might change old behavior or cause conflicts. Maybe some day in the future
32//! we could experiment with that, but it seems unlikely to work or be all
33//! that helpful.
34//!
35//! There are many assumptions made about the dependency resolver. This
36//! feature resolver assumes validation has already been done on the feature
37//! maps, and doesn't do any validation itself. It assumes dev-dependencies
38//! within a dependency have been removed. There are probably other
39//! assumptions that I am forgetting.
40
41use crate::core::compiler::{CompileKind, RustcTargetData};
42use crate::core::dependency::{DepKind, Dependency};
43use crate::core::resolver::types::FeaturesSet;
44use crate::core::resolver::Resolve;
45use crate::core::{FeatureValue, InternedString, PackageId, PackageIdSpec, PackageSet, Workspace};
46use crate::util::{CargoResult, Config};
47use std::collections::{BTreeSet, HashMap, HashSet};
48use std::rc::Rc;
49
50/// Map of activated features.
51///
52/// The key is `(PackageId, bool)` where the bool is `true` if these
53/// are features for a build dependency or proc-macro.
54type ActivateMap = HashMap<(PackageId, bool), BTreeSet<InternedString>>;
55
56/// Set of all activated features for all packages in the resolve graph.
57pub struct ResolvedFeatures {
58    activated_features: ActivateMap,
59    /// This is only here for legacy support when `-Zfeatures` is not enabled.
60    legacy: Option<HashMap<PackageId, Vec<InternedString>>>,
61    opts: FeatureOpts,
62}
63
64/// Options for how the feature resolver works.
65#[derive(Default)]
66struct FeatureOpts {
67    /// -Zpackage-features, changes behavior of feature flags in a workspace.
68    package_features: bool,
69    /// -Zfeatures is enabled, use new resolver.
70    new_resolver: bool,
71    /// Build deps and proc-macros will not share share features with other dep kinds.
72    decouple_host_deps: bool,
73    /// Dev dep features will not be activated unless needed.
74    decouple_dev_deps: bool,
75    /// Targets that are not in use will not activate features.
76    ignore_inactive_targets: bool,
77    /// If enabled, compare against old resolver (for testing).
78    compare: bool,
79}
80
81/// Flag to indicate if Cargo is building *any* dev units (tests, examples, etc.).
82///
83/// This disables decoupling of dev dependencies. It may be possible to relax
84/// this in the future, but it will require significant changes to how unit
85/// dependencies are computed, and can result in longer build times with
86/// `cargo test` because the lib may need to be built 3 times instead of
87/// twice.
88#[derive(Copy, Clone, PartialEq)]
89pub enum HasDevUnits {
90    Yes,
91    No,
92}
93
94/// Flag to indicate if features are requested for a build dependency or not.
95#[derive(Debug, PartialEq)]
96pub enum FeaturesFor {
97    NormalOrDev,
98    /// Build dependency or proc-macro.
99    HostDep,
100}
101
102impl FeatureOpts {
103    fn new(config: &Config, has_dev_units: HasDevUnits) -> CargoResult<FeatureOpts> {
104        let mut opts = FeatureOpts::default();
105        let unstable_flags = config.cli_unstable();
106        opts.package_features = unstable_flags.package_features;
107        let mut enable = |feat_opts: &Vec<String>| {
108            opts.new_resolver = true;
109            for opt in feat_opts {
110                match opt.as_ref() {
111                    "build_dep" | "host_dep" => opts.decouple_host_deps = true,
112                    "dev_dep" => opts.decouple_dev_deps = true,
113                    "itarget" => opts.ignore_inactive_targets = true,
114                    "all" => {
115                        opts.decouple_host_deps = true;
116                        opts.decouple_dev_deps = true;
117                        opts.ignore_inactive_targets = true;
118                    }
119                    "compare" => opts.compare = true,
120                    "ws" => unimplemented!(),
121                    s => anyhow::bail!("-Zfeatures flag `{}` is not supported", s),
122                }
123            }
124            Ok(())
125        };
126        if let Some(feat_opts) = unstable_flags.features.as_ref() {
127            enable(feat_opts)?;
128        }
129        // This env var is intended for testing only.
130        if let Ok(env_opts) = std::env::var("__CARGO_FORCE_NEW_FEATURES") {
131            if env_opts == "1" {
132                opts.new_resolver = true;
133            } else {
134                let env_opts = env_opts.split(',').map(|s| s.to_string()).collect();
135                enable(&env_opts)?;
136            }
137        }
138        if let HasDevUnits::Yes = has_dev_units {
139            opts.decouple_dev_deps = false;
140        }
141        Ok(opts)
142    }
143}
144
145/// Features flags requested for a package.
146#[derive(Debug, Clone, Eq, PartialEq, Hash)]
147pub struct RequestedFeatures {
148    pub features: FeaturesSet,
149    pub all_features: bool,
150    pub uses_default_features: bool,
151}
152
153impl RequestedFeatures {
154    /// Creates a new RequestedFeatures from the given command-line flags.
155    pub fn from_command_line(
156        features: &[String],
157        all_features: bool,
158        uses_default_features: bool,
159    ) -> RequestedFeatures {
160        RequestedFeatures {
161            features: Rc::new(RequestedFeatures::split_features(features)),
162            all_features,
163            uses_default_features,
164        }
165    }
166
167    /// Creates a new RequestedFeatures with the given `all_features` setting.
168    pub fn new_all(all_features: bool) -> RequestedFeatures {
169        RequestedFeatures {
170            features: Rc::new(BTreeSet::new()),
171            all_features,
172            uses_default_features: true,
173        }
174    }
175
176    fn split_features(features: &[String]) -> BTreeSet<InternedString> {
177        features
178            .iter()
179            .flat_map(|s| s.split_whitespace())
180            .flat_map(|s| s.split(','))
181            .filter(|s| !s.is_empty())
182            .map(InternedString::new)
183            .collect::<BTreeSet<InternedString>>()
184    }
185}
186
187impl ResolvedFeatures {
188    /// Returns the list of features that are enabled for the given package.
189    pub fn activated_features(
190        &self,
191        pkg_id: PackageId,
192        features_for: FeaturesFor,
193    ) -> Vec<InternedString> {
194        self.activated_features_int(pkg_id, features_for, true)
195    }
196
197    /// Variant of `activated_features` that returns an empty Vec if this is
198    /// not a valid pkg_id/is_build combination. Used in places which do
199    /// not know which packages are activated (like `cargo clean`).
200    pub fn activated_features_unverified(
201        &self,
202        pkg_id: PackageId,
203        features_for: FeaturesFor,
204    ) -> Vec<InternedString> {
205        self.activated_features_int(pkg_id, features_for, false)
206    }
207
208    fn activated_features_int(
209        &self,
210        pkg_id: PackageId,
211        features_for: FeaturesFor,
212        verify: bool,
213    ) -> Vec<InternedString> {
214        if let Some(legacy) = &self.legacy {
215            legacy.get(&pkg_id).map_or_else(Vec::new, |v| v.clone())
216        } else {
217            let is_build = self.opts.decouple_host_deps && features_for == FeaturesFor::HostDep;
218            if let Some(fs) = self.activated_features.get(&(pkg_id, is_build)) {
219                fs.iter().cloned().collect()
220            } else if verify {
221                panic!("features did not find {:?} {:?}", pkg_id, is_build)
222            } else {
223                Vec::new()
224            }
225        }
226    }
227}
228
229pub struct FeatureResolver<'a, 'cfg> {
230    ws: &'a Workspace<'cfg>,
231    target_data: &'a RustcTargetData,
232    /// The platform to build for, requested by the user.
233    requested_target: CompileKind,
234    resolve: &'a Resolve,
235    package_set: &'a PackageSet<'cfg>,
236    /// Options that change how the feature resolver operates.
237    opts: FeatureOpts,
238    /// Map of features activated for each package.
239    activated_features: ActivateMap,
240    /// Keeps track of which packages have had its dependencies processed.
241    /// Used to avoid cycles, and to speed up processing.
242    processed_deps: HashSet<(PackageId, bool)>,
243}
244
245impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
246    /// Runs the resolution algorithm and returns a new `ResolvedFeatures`
247    /// with the result.
248    pub fn resolve(
249        ws: &Workspace<'cfg>,
250        target_data: &RustcTargetData,
251        resolve: &Resolve,
252        package_set: &'a PackageSet<'cfg>,
253        requested_features: &RequestedFeatures,
254        specs: &[PackageIdSpec],
255        requested_target: CompileKind,
256        has_dev_units: HasDevUnits,
257    ) -> CargoResult<ResolvedFeatures> {
258        use crate::util::profile;
259        let _p = profile::start("resolve features");
260
261        let opts = FeatureOpts::new(ws.config(), has_dev_units)?;
262        if !opts.new_resolver {
263            // Legacy mode.
264            return Ok(ResolvedFeatures {
265                activated_features: HashMap::new(),
266                legacy: Some(resolve.features_clone()),
267                opts,
268            });
269        }
270        let mut r = FeatureResolver {
271            ws,
272            target_data,
273            requested_target,
274            resolve,
275            package_set,
276            opts,
277            activated_features: HashMap::new(),
278            processed_deps: HashSet::new(),
279        };
280        r.do_resolve(specs, requested_features)?;
281        log::debug!("features={:#?}", r.activated_features);
282        if r.opts.compare {
283            r.compare();
284        }
285        Ok(ResolvedFeatures {
286            activated_features: r.activated_features,
287            legacy: None,
288            opts: r.opts,
289        })
290    }
291
292    /// Performs the process of resolving all features for the resolve graph.
293    fn do_resolve(
294        &mut self,
295        specs: &[PackageIdSpec],
296        requested_features: &RequestedFeatures,
297    ) -> CargoResult<()> {
298        let member_features = self.ws.members_with_features(specs, requested_features)?;
299        for (member, requested_features) in &member_features {
300            let fvs = self.fvs_from_requested(member.package_id(), requested_features);
301            let for_host = self.opts.decouple_host_deps && self.is_proc_macro(member.package_id());
302            self.activate_pkg(member.package_id(), &fvs, for_host)?;
303            if for_host {
304                // Also activate without for_host. This is needed if the
305                // proc-macro includes other targets (like binaries or tests),
306                // or running in `cargo test`. Note that in a workspace, if
307                // the proc-macro is selected on the command like (like with
308                // `--workspace`), this forces feature unification with normal
309                // dependencies. This is part of the bigger problem where
310                // features depend on which packages are built.
311                self.activate_pkg(member.package_id(), &fvs, false)?;
312            }
313        }
314        Ok(())
315    }
316
317    fn activate_pkg(
318        &mut self,
319        pkg_id: PackageId,
320        fvs: &[FeatureValue],
321        for_host: bool,
322    ) -> CargoResult<()> {
323        // Add an empty entry to ensure everything is covered. This is intended for
324        // finding bugs where the resolver missed something it should have visited.
325        // Remove this in the future if `activated_features` uses an empty default.
326        self.activated_features
327            .entry((pkg_id, for_host))
328            .or_insert_with(BTreeSet::new);
329        for fv in fvs {
330            self.activate_fv(pkg_id, fv, for_host)?;
331        }
332        if !self.processed_deps.insert((pkg_id, for_host)) {
333            // Already processed dependencies. There's no need to process them
334            // again. This is primarily to avoid cycles, but also helps speed
335            // things up.
336            //
337            // This is safe because if another package comes along and adds a
338            // feature on this package, it will immediately add it (in
339            // `activate_fv`), and recurse as necessary right then and there.
340            // For example, consider we've already processed our dependencies,
341            // and another package comes along and enables one of our optional
342            // dependencies, it will do so immediately in the
343            // `FeatureValue::CrateFeature` branch, and then immediately
344            // recurse into that optional dependency. This also holds true for
345            // features that enable other features.
346            return Ok(());
347        }
348        for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
349            for (dep, dep_for_host) in deps {
350                if dep.is_optional() {
351                    // Optional dependencies are enabled in `activate_fv` when
352                    // a feature enables it.
353                    continue;
354                }
355                // Recurse into the dependency.
356                let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
357                self.activate_pkg(dep_pkg_id, &fvs, dep_for_host)?;
358            }
359        }
360        Ok(())
361    }
362
363    /// Activate a single FeatureValue for a package.
364    fn activate_fv(
365        &mut self,
366        pkg_id: PackageId,
367        fv: &FeatureValue,
368        for_host: bool,
369    ) -> CargoResult<()> {
370        match fv {
371            FeatureValue::Feature(f) => {
372                self.activate_rec(pkg_id, *f, for_host)?;
373            }
374            FeatureValue::Crate(dep_name) => {
375                // Activate the feature name on self.
376                self.activate_rec(pkg_id, *dep_name, for_host)?;
377                // Activate the optional dep.
378                for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
379                    for (dep, dep_for_host) in deps {
380                        if dep.name_in_toml() != *dep_name {
381                            continue;
382                        }
383                        let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
384                        self.activate_pkg(dep_pkg_id, &fvs, dep_for_host)?;
385                    }
386                }
387            }
388            FeatureValue::CrateFeature(dep_name, dep_feature) => {
389                // Activate a feature within a dependency.
390                for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
391                    for (dep, dep_for_host) in deps {
392                        if dep.name_in_toml() != *dep_name {
393                            continue;
394                        }
395                        if dep.is_optional() {
396                            // Activate the crate on self.
397                            let fv = FeatureValue::Crate(*dep_name);
398                            self.activate_fv(pkg_id, &fv, for_host)?;
399                        }
400                        // Activate the feature on the dependency.
401                        let summary = self.resolve.summary(dep_pkg_id);
402                        let fv = FeatureValue::new(*dep_feature, summary);
403                        self.activate_fv(dep_pkg_id, &fv, dep_for_host)?;
404                    }
405                }
406            }
407        }
408        Ok(())
409    }
410
411    /// Activate the given feature for the given package, and then recursively
412    /// activate any other features that feature enables.
413    fn activate_rec(
414        &mut self,
415        pkg_id: PackageId,
416        feature_to_enable: InternedString,
417        for_host: bool,
418    ) -> CargoResult<()> {
419        let enabled = self
420            .activated_features
421            .entry((pkg_id, for_host))
422            .or_insert_with(BTreeSet::new);
423        if !enabled.insert(feature_to_enable) {
424            // Already enabled.
425            return Ok(());
426        }
427        let summary = self.resolve.summary(pkg_id);
428        let feature_map = summary.features();
429        let fvs = match feature_map.get(&feature_to_enable) {
430            Some(fvs) => fvs,
431            None => {
432                // TODO: this should only happen for optional dependencies.
433                // Other cases should be validated by Summary's `build_feature_map`.
434                // Figure out some way to validate this assumption.
435                log::debug!(
436                    "pkg {:?} does not define feature {}",
437                    pkg_id,
438                    feature_to_enable
439                );
440                return Ok(());
441            }
442        };
443        for fv in fvs {
444            self.activate_fv(pkg_id, fv, for_host)?;
445        }
446        Ok(())
447    }
448
449    /// Returns Vec of FeatureValues from a Dependency definition.
450    fn fvs_from_dependency(&self, dep_id: PackageId, dep: &Dependency) -> Vec<FeatureValue> {
451        let summary = self.resolve.summary(dep_id);
452        let feature_map = summary.features();
453        let mut result: Vec<FeatureValue> = dep
454            .features()
455            .iter()
456            .map(|f| FeatureValue::new(*f, summary))
457            .collect();
458        let default = InternedString::new("default");
459        if dep.uses_default_features() && feature_map.contains_key(&default) {
460            result.push(FeatureValue::Feature(default));
461        }
462        result
463    }
464
465    /// Returns Vec of FeatureValues from a set of command-line features.
466    fn fvs_from_requested(
467        &self,
468        pkg_id: PackageId,
469        requested_features: &RequestedFeatures,
470    ) -> Vec<FeatureValue> {
471        let summary = self.resolve.summary(pkg_id);
472        let feature_map = summary.features();
473        if requested_features.all_features {
474            let mut fvs: Vec<FeatureValue> = feature_map
475                .keys()
476                .map(|k| FeatureValue::Feature(*k))
477                .collect();
478            // Add optional deps.
479            // Top-level requested features can never apply to
480            // build-dependencies, so for_host is `false` here.
481            for (_dep_pkg_id, deps) in self.deps(pkg_id, false) {
482                for (dep, _dep_for_host) in deps {
483                    if dep.is_optional() {
484                        // This may result in duplicates, but that should be ok.
485                        fvs.push(FeatureValue::Crate(dep.name_in_toml()));
486                    }
487                }
488            }
489            fvs
490        } else {
491            let mut result: Vec<FeatureValue> = requested_features
492                .features
493                .as_ref()
494                .iter()
495                .map(|f| FeatureValue::new(*f, summary))
496                .collect();
497            let default = InternedString::new("default");
498            if requested_features.uses_default_features && feature_map.contains_key(&default) {
499                result.push(FeatureValue::Feature(default));
500            }
501            result
502        }
503    }
504
505    /// Returns the dependencies for a package, filtering out inactive targets.
506    fn deps(
507        &self,
508        pkg_id: PackageId,
509        for_host: bool,
510    ) -> Vec<(PackageId, Vec<(&'a Dependency, bool)>)> {
511        // Helper for determining if a platform is activated.
512        let platform_activated = |dep: &Dependency| -> bool {
513            // We always care about build-dependencies, and they are always
514            // Host. If we are computing dependencies "for a build script",
515            // even normal dependencies are host-only.
516            if for_host || dep.is_build() {
517                return self
518                    .target_data
519                    .dep_platform_activated(dep, CompileKind::Host);
520            }
521            // Not a build dependency, and not for a build script, so must be Target.
522            self.target_data
523                .dep_platform_activated(dep, self.requested_target)
524        };
525        self.resolve
526            .deps(pkg_id)
527            .map(|(dep_id, deps)| {
528                let is_proc_macro = self.is_proc_macro(dep_id);
529                let deps = deps
530                    .iter()
531                    .filter(|dep| {
532                        if dep.platform().is_some()
533                            && self.opts.ignore_inactive_targets
534                            && !platform_activated(dep)
535                        {
536                            return false;
537                        }
538                        if self.opts.decouple_dev_deps && dep.kind() == DepKind::Development {
539                            return false;
540                        }
541                        true
542                    })
543                    .map(|dep| {
544                        let dep_for_host = for_host
545                            || (self.opts.decouple_host_deps && (dep.is_build() || is_proc_macro));
546                        (dep, dep_for_host)
547                    })
548                    .collect::<Vec<_>>();
549                (dep_id, deps)
550            })
551            .filter(|(_id, deps)| !deps.is_empty())
552            .collect()
553    }
554
555    /// Compare the activated features to the resolver. Used for testing.
556    fn compare(&self) {
557        let mut found = false;
558        for ((pkg_id, dep_kind), features) in &self.activated_features {
559            let r_features = self.resolve.features(*pkg_id);
560            if !r_features.iter().eq(features.iter()) {
561                eprintln!(
562                    "{}/{:?} features mismatch\nresolve: {:?}\nnew: {:?}\n",
563                    pkg_id, dep_kind, r_features, features
564                );
565                found = true;
566            }
567        }
568        if found {
569            panic!("feature mismatch");
570        }
571    }
572
573    fn is_proc_macro(&self, package_id: PackageId) -> bool {
574        self.package_set
575            .get_one(package_id)
576            .expect("packages downloaded")
577            .proc_macro()
578    }
579}