blob: 4518f9fe7cf05d47b354a21ea8e8c3b5b00cfd2f [file] [log] [blame]
//! Resolves conditional compilation for [`features` section] in the manifest.
//!
//! This is a [new feature resolver] that runs independently of the main
//! dependency resolver. It has several options which can enable new feature
//! resolution behavior.
//!
//! One of its key characteristics is that it can avoid unifying features for
//! shared dependencies in some situations. See [`FeatureOpts`] for the
//! different behaviors that can be enabled. If no extra options are enabled,
//! then it should behave exactly the same as the dependency resolver's
//! feature resolution.
//!
//! The preferred way to engage this new resolver is via [`resolve_ws_with_opts`].
//!
//! This does not *replace* feature resolution in the dependency resolver, but
//! instead acts as a second pass which can *narrow* the features selected in
//! the dependency resolver. The dependency resolver still needs to do its own
//! feature resolution in order to avoid selecting optional dependencies that
//! are never enabled. The dependency resolver could, in theory, just assume
//! all optional dependencies on all packages are enabled (and remove all
//! knowledge of features), but that could introduce new requirements that
//! might change old behavior or cause conflicts. Maybe some day in the future
//! we could experiment with that, but it seems unlikely to work or be all
//! that helpful.
//!
//! ## Assumptions
//!
//! There are many assumptions made about the dependency resolver:
//!
//! * Assumes feature validation has already been done during the construction
//! of feature maps, so the feature resolver doesn't do that validation at all.
//! * Assumes `dev-dependencies` within a dependency have been removed
//! in the given [`Resolve`].
//!
//! There are probably other assumptions that I am forgetting.
//!
//! [`features` section]: https://doc.rust-lang.org/nightly/cargo/reference/features.html
//! [new feature resolver]: https://doc.rust-lang.org/nightly/cargo/reference/resolver.html#feature-resolver-version-2
//! [`resolve_ws_with_opts`]: crate::ops::resolve_ws_with_opts
use crate::core::compiler::{CompileKind, CompileTarget, RustcTargetData};
use crate::core::dependency::{ArtifactTarget, DepKind, Dependency};
use crate::core::resolver::types::FeaturesSet;
use crate::core::resolver::{Resolve, ResolveBehavior};
use crate::core::{FeatureValue, PackageId, PackageIdSpec, PackageSet, Workspace};
use crate::util::interning::InternedString;
use crate::util::CargoResult;
use anyhow::bail;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::rc::Rc;
/// The key used in various places to store features for a particular dependency.
/// The actual discrimination happens with the [`FeaturesFor`] type.
type PackageFeaturesKey = (PackageId, FeaturesFor);
/// Map of activated features.
type ActivateMap = HashMap<PackageFeaturesKey, BTreeSet<InternedString>>;
/// Set of all activated features for all packages in the resolve graph.
pub struct ResolvedFeatures {
activated_features: ActivateMap,
/// Optional dependencies that should be built.
///
/// The value is the `name_in_toml` of the dependencies.
activated_dependencies: ActivateMap,
opts: FeatureOpts,
}
/// Options for how the feature resolver works.
#[derive(Default)]
pub struct FeatureOpts {
/// Build deps and proc-macros will not share features with other dep kinds,
/// and so won't artifact targets.
/// In other terms, if true, features associated with certain kinds of dependencies
/// will only be unified together.
/// If false, there is only one namespace for features, unifying all features across
/// all dependencies, no matter what kind.
decouple_host_deps: bool,
/// Dev dep features will not be activated unless needed.
decouple_dev_deps: bool,
/// Targets that are not in use will not activate features.
ignore_inactive_targets: bool,
/// If enabled, compare against old resolver (for testing).
compare: bool,
}
/// Flag to indicate if Cargo is building *any* dev units (tests, examples, etc.).
///
/// This disables decoupling of dev dependencies. It may be possible to relax
/// this in the future, but it will require significant changes to how unit
/// dependencies are computed, and can result in longer build times with
/// `cargo test` because the lib may need to be built 3 times instead of
/// twice.
#[derive(Copy, Clone, PartialEq)]
pub enum HasDevUnits {
Yes,
No,
}
/// Flag to indicate that target-specific filtering should be disabled.
#[derive(Copy, Clone, PartialEq)]
pub enum ForceAllTargets {
Yes,
No,
}
/// Flag to indicate if features are requested for a certain type of dependency.
///
/// This is primarily used for constructing a [`PackageFeaturesKey`] to decouple
/// activated features of the same package with different types of dependency.
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum FeaturesFor {
/// Normal or dev dependency.
#[default]
NormalOrDev,
/// Build dependency or proc-macro.
HostDep,
/// Any dependency with both artifact and target specified.
///
/// That is, `dep = { …, artifact = <crate-type>, target = <triple> }`
ArtifactDep(CompileTarget),
}
impl std::fmt::Display for FeaturesFor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FeaturesFor::HostDep => f.write_str("host"),
FeaturesFor::ArtifactDep(target) => f.write_str(&target.rustc_target()),
FeaturesFor::NormalOrDev => Ok(()),
}
}
}
impl FeaturesFor {
pub fn from_for_host(for_host: bool) -> FeaturesFor {
if for_host {
FeaturesFor::HostDep
} else {
FeaturesFor::NormalOrDev
}
}
pub fn from_for_host_or_artifact_target(
for_host: bool,
artifact_target: Option<CompileTarget>,
) -> FeaturesFor {
match artifact_target {
Some(target) => FeaturesFor::ArtifactDep(target),
None => {
if for_host {
FeaturesFor::HostDep
} else {
FeaturesFor::NormalOrDev
}
}
}
}
fn apply_opts(self, opts: &FeatureOpts) -> Self {
if opts.decouple_host_deps {
self
} else {
FeaturesFor::default()
}
}
}
impl FeatureOpts {
pub fn new(
ws: &Workspace<'_>,
has_dev_units: HasDevUnits,
force_all_targets: ForceAllTargets,
) -> CargoResult<FeatureOpts> {
let mut opts = FeatureOpts::default();
let unstable_flags = ws.config().cli_unstable();
let mut enable = |feat_opts: &Vec<String>| {
for opt in feat_opts {
match opt.as_ref() {
"build_dep" | "host_dep" => opts.decouple_host_deps = true,
"dev_dep" => opts.decouple_dev_deps = true,
"itarget" => opts.ignore_inactive_targets = true,
"all" => {
opts.decouple_host_deps = true;
opts.decouple_dev_deps = true;
opts.ignore_inactive_targets = true;
}
"compare" => opts.compare = true,
"ws" => unimplemented!(),
s => bail!("-Zfeatures flag `{}` is not supported", s),
}
}
Ok(())
};
if let Some(feat_opts) = unstable_flags.features.as_ref() {
enable(feat_opts)?;
}
match ws.resolve_behavior() {
ResolveBehavior::V1 => {}
ResolveBehavior::V2 => {
enable(&vec!["all".to_string()]).unwrap();
}
}
if let HasDevUnits::Yes = has_dev_units {
// Dev deps cannot be decoupled when they are in use.
opts.decouple_dev_deps = false;
}
if let ForceAllTargets::Yes = force_all_targets {
opts.ignore_inactive_targets = false;
}
Ok(opts)
}
/// Creates a new FeatureOpts for the given behavior.
pub fn new_behavior(behavior: ResolveBehavior, has_dev_units: HasDevUnits) -> FeatureOpts {
match behavior {
ResolveBehavior::V1 => FeatureOpts::default(),
ResolveBehavior::V2 => FeatureOpts {
decouple_host_deps: true,
decouple_dev_deps: has_dev_units == HasDevUnits::No,
ignore_inactive_targets: true,
compare: false,
},
}
}
}
/// Features flags requested for a package.
///
/// This should be cheap and fast to clone, it is used in the resolver for
/// various caches.
///
/// This is split into enum variants because the resolver needs to handle
/// features coming from different places (command-line and dependency
/// declarations), but those different places have different constraints on
/// which syntax is allowed. This helps ensure that every place dealing with
/// features is properly handling those syntax restrictions.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum RequestedFeatures {
/// Features requested on the command-line with flags.
CliFeatures(CliFeatures),
/// Features specified in a dependency declaration.
DepFeatures {
/// The `features` dependency field.
features: FeaturesSet,
/// The `default-features` dependency field.
uses_default_features: bool,
},
}
/// Features specified on the command-line.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct CliFeatures {
/// Features from the `--features` flag.
pub features: Rc<BTreeSet<FeatureValue>>,
/// The `--all-features` flag.
pub all_features: bool,
/// Inverse of `--no-default-features` flag.
pub uses_default_features: bool,
}
impl CliFeatures {
/// Creates a new CliFeatures from the given command-line flags.
pub fn from_command_line(
features: &[String],
all_features: bool,
uses_default_features: bool,
) -> CargoResult<CliFeatures> {
let features = Rc::new(CliFeatures::split_features(features));
// Some early validation to ensure correct syntax.
for feature in features.iter() {
match feature {
// Maybe call validate_feature_name here once it is an error?
FeatureValue::Feature(_) => {}
FeatureValue::Dep { .. } => {
bail!(
"feature `{}` is not allowed to use explicit `dep:` syntax",
feature
);
}
FeatureValue::DepFeature { dep_feature, .. } => {
if dep_feature.contains('/') {
bail!("multiple slashes in feature `{}` is not allowed", feature);
}
}
}
}
Ok(CliFeatures {
features,
all_features,
uses_default_features,
})
}
/// Creates a new CliFeatures with the given `all_features` setting.
pub fn new_all(all_features: bool) -> CliFeatures {
CliFeatures {
features: Rc::new(BTreeSet::new()),
all_features,
uses_default_features: true,
}
}
fn split_features(features: &[String]) -> BTreeSet<FeatureValue> {
features
.iter()
.flat_map(|s| s.split_whitespace())
.flat_map(|s| s.split(','))
.filter(|s| !s.is_empty())
.map(InternedString::new)
.map(FeatureValue::new)
.collect()
}
}
impl ResolvedFeatures {
/// Returns the list of features that are enabled for the given package.
pub fn activated_features(
&self,
pkg_id: PackageId,
features_for: FeaturesFor,
) -> Vec<InternedString> {
self.activated_features_int(pkg_id, features_for)
.expect("activated_features for invalid package")
}
/// Returns if the given dependency should be included.
///
/// This handles dependencies disabled via `cfg` expressions and optional
/// dependencies which are not enabled.
pub fn is_dep_activated(
&self,
pkg_id: PackageId,
features_for: FeaturesFor,
dep_name: InternedString,
) -> bool {
let key = features_for.apply_opts(&self.opts);
self.activated_dependencies
.get(&(pkg_id, key))
.map(|deps| deps.contains(&dep_name))
.unwrap_or(false)
}
/// Variant of `activated_features` that returns `None` if this is
/// not a valid pkg_id/is_build combination. Used in places which do
/// not know which packages are activated (like `cargo clean`).
pub fn activated_features_unverified(
&self,
pkg_id: PackageId,
features_for: FeaturesFor,
) -> Option<Vec<InternedString>> {
self.activated_features_int(pkg_id, features_for).ok()
}
fn activated_features_int(
&self,
pkg_id: PackageId,
features_for: FeaturesFor,
) -> CargoResult<Vec<InternedString>> {
let fk = features_for.apply_opts(&self.opts);
if let Some(fs) = self.activated_features.get(&(pkg_id, fk)) {
Ok(fs.iter().cloned().collect())
} else {
bail!("features did not find {:?} {:?}", pkg_id, fk)
}
}
/// Compares the result against the original resolver behavior.
///
/// Used by `cargo fix --edition` to display any differences.
pub fn compare_legacy(&self, legacy: &ResolvedFeatures) -> DiffMap {
self.activated_features
.iter()
.filter_map(|((pkg_id, for_host), new_features)| {
let old_features = legacy
.activated_features
.get(&(*pkg_id, *for_host))
// The new features may have for_host entries where the old one does not.
.or_else(|| {
legacy
.activated_features
.get(&(*pkg_id, FeaturesFor::default()))
})
.map(|feats| feats.iter().cloned().collect())
.unwrap_or_else(|| BTreeSet::new());
// The new resolver should never add features.
assert_eq!(new_features.difference(&old_features).next(), None);
let removed_features: BTreeSet<_> =
old_features.difference(new_features).cloned().collect();
if removed_features.is_empty() {
None
} else {
Some(((*pkg_id, *for_host), removed_features))
}
})
.collect()
}
}
/// Map of differences.
///
/// Key is `(pkg_id, for_host)`. Value is a set of features or dependencies removed.
pub type DiffMap = BTreeMap<PackageFeaturesKey, BTreeSet<InternedString>>;
/// The new feature resolver that [`resolve`]s your project.
///
/// For more information, please see the [module-level documentation].
///
/// [`resolve`]: Self::resolve
/// [module-level documentation]: crate::core::resolver::features
pub struct FeatureResolver<'a, 'cfg> {
ws: &'a Workspace<'cfg>,
target_data: &'a RustcTargetData<'cfg>,
/// The platforms to build for, requested by the user.
requested_targets: &'a [CompileKind],
resolve: &'a Resolve,
package_set: &'a PackageSet<'cfg>,
/// Options that change how the feature resolver operates.
opts: FeatureOpts,
/// Map of features activated for each package.
activated_features: ActivateMap,
/// Map of optional dependencies activated for each package.
activated_dependencies: ActivateMap,
/// Keeps track of which packages have had its dependencies processed.
/// Used to avoid cycles, and to speed up processing.
processed_deps: HashSet<PackageFeaturesKey>,
/// If this is `true`, then a non-default `feature_key` needs to be tracked while
/// traversing the graph.
///
/// This is only here to avoid calling `is_proc_macro` when all feature
/// options are disabled (because `is_proc_macro` can trigger downloads).
/// This has to be separate from `FeatureOpts.decouple_host_deps` because
/// `for_host` tracking is also needed for `itarget` to work properly.
track_for_host: bool,
/// `dep_name?/feat_name` features that will be activated if `dep_name` is
/// ever activated.
///
/// The key is the `(package, for_host, dep_name)` of the package whose
/// dependency will trigger the addition of new features. The value is the
/// set of features to activate.
deferred_weak_dependencies:
HashMap<(PackageId, FeaturesFor, InternedString), HashSet<InternedString>>,
}
impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
/// Runs the resolution algorithm and returns a new [`ResolvedFeatures`]
/// with the result.
pub fn resolve(
ws: &Workspace<'cfg>,
target_data: &RustcTargetData<'cfg>,
resolve: &Resolve,
package_set: &'a PackageSet<'cfg>,
cli_features: &CliFeatures,
specs: &[PackageIdSpec],
requested_targets: &[CompileKind],
opts: FeatureOpts,
) -> CargoResult<ResolvedFeatures> {
use crate::util::profile;
let _p = profile::start("resolve features");
let track_for_host = opts.decouple_host_deps || opts.ignore_inactive_targets;
let mut r = FeatureResolver {
ws,
target_data,
requested_targets,
resolve,
package_set,
opts,
activated_features: HashMap::new(),
activated_dependencies: HashMap::new(),
processed_deps: HashSet::new(),
track_for_host,
deferred_weak_dependencies: HashMap::new(),
};
r.do_resolve(specs, cli_features)?;
tracing::debug!("features={:#?}", r.activated_features);
if r.opts.compare {
r.compare();
}
Ok(ResolvedFeatures {
activated_features: r.activated_features,
activated_dependencies: r.activated_dependencies,
opts: r.opts,
})
}
/// Performs the process of resolving all features for the resolve graph.
fn do_resolve(
&mut self,
specs: &[PackageIdSpec],
cli_features: &CliFeatures,
) -> CargoResult<()> {
let member_features = self.ws.members_with_features(specs, cli_features)?;
for (member, cli_features) in &member_features {
let fvs = self.fvs_from_requested(member.package_id(), cli_features);
let fk = if self.track_for_host && self.is_proc_macro(member.package_id()) {
// Also activate for normal dependencies. This is needed if the
// proc-macro includes other targets (like binaries or tests),
// or running in `cargo test`. Note that in a workspace, if
// the proc-macro is selected on the command like (like with
// `--workspace`), this forces feature unification with normal
// dependencies. This is part of the bigger problem where
// features depend on which packages are built.
self.activate_pkg(member.package_id(), FeaturesFor::default(), &fvs)?;
FeaturesFor::HostDep
} else {
FeaturesFor::default()
};
self.activate_pkg(member.package_id(), fk, &fvs)?;
}
Ok(())
}
/// Activates [`FeatureValue`]s on the given package.
///
/// This is the main entrance into the recursion of feature activation
/// for a package.
fn activate_pkg(
&mut self,
pkg_id: PackageId,
fk: FeaturesFor,
fvs: &[FeatureValue],
) -> CargoResult<()> {
tracing::trace!("activate_pkg {} {}", pkg_id.name(), fk);
// Add an empty entry to ensure everything is covered. This is intended for
// finding bugs where the resolver missed something it should have visited.
// Remove this in the future if `activated_features` uses an empty default.
self.activated_features
.entry((pkg_id, fk.apply_opts(&self.opts)))
.or_insert_with(BTreeSet::new);
for fv in fvs {
self.activate_fv(pkg_id, fk, fv)?;
}
if !self.processed_deps.insert((pkg_id, fk)) {
// Already processed dependencies. There's no need to process them
// again. This is primarily to avoid cycles, but also helps speed
// things up.
//
// This is safe because if another package comes along and adds a
// feature on this package, it will immediately add it (in
// `activate_fv`), and recurse as necessary right then and there.
// For example, consider we've already processed our dependencies,
// and another package comes along and enables one of our optional
// dependencies, it will do so immediately in the
// `FeatureValue::DepFeature` branch, and then immediately
// recurse into that optional dependency. This also holds true for
// features that enable other features.
return Ok(());
}
for (dep_pkg_id, deps) in self.deps(pkg_id, fk) {
for (dep, dep_fk) in deps {
if dep.is_optional() {
// Optional dependencies are enabled in `activate_fv` when
// a feature enables it.
continue;
}
// Recurse into the dependency.
let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
self.activate_pkg(dep_pkg_id, dep_fk, &fvs)?;
}
}
Ok(())
}
/// Activate a single FeatureValue for a package.
fn activate_fv(
&mut self,
pkg_id: PackageId,
fk: FeaturesFor,
fv: &FeatureValue,
) -> CargoResult<()> {
tracing::trace!("activate_fv {} {} {}", pkg_id.name(), fk, fv);
match fv {
FeatureValue::Feature(f) => {
self.activate_rec(pkg_id, fk, *f)?;
}
FeatureValue::Dep { dep_name } => {
self.activate_dependency(pkg_id, fk, *dep_name)?;
}
FeatureValue::DepFeature {
dep_name,
dep_feature,
weak,
} => {
self.activate_dep_feature(pkg_id, fk, *dep_name, *dep_feature, *weak)?;
}
}
Ok(())
}
/// Activate the given feature for the given package, and then recursively
/// activate any other features that feature enables.
fn activate_rec(
&mut self,
pkg_id: PackageId,
fk: FeaturesFor,
feature_to_enable: InternedString,
) -> CargoResult<()> {
tracing::trace!(
"activate_rec {} {} feat={}",
pkg_id.name(),
fk,
feature_to_enable
);
let enabled = self
.activated_features
.entry((pkg_id, fk.apply_opts(&self.opts)))
.or_insert_with(BTreeSet::new);
if !enabled.insert(feature_to_enable) {
// Already enabled.
return Ok(());
}
let summary = self.resolve.summary(pkg_id);
let feature_map = summary.features();
let fvs = match feature_map.get(&feature_to_enable) {
Some(fvs) => fvs,
None => {
// TODO: this should only happen for optional dependencies.
// Other cases should be validated by Summary's `build_feature_map`.
// Figure out some way to validate this assumption.
tracing::debug!(
"pkg {:?} does not define feature {}",
pkg_id,
feature_to_enable
);
return Ok(());
}
};
for fv in fvs {
self.activate_fv(pkg_id, fk, fv)?;
}
Ok(())
}
/// Activate a dependency (`dep:dep_name` syntax).
fn activate_dependency(
&mut self,
pkg_id: PackageId,
fk: FeaturesFor,
dep_name: InternedString,
) -> CargoResult<()> {
// Mark this dependency as activated.
let save_decoupled = fk.apply_opts(&self.opts);
self.activated_dependencies
.entry((pkg_id, save_decoupled))
.or_default()
.insert(dep_name);
// Check for any deferred features.
let to_enable = self
.deferred_weak_dependencies
.remove(&(pkg_id, fk, dep_name));
// Activate the optional dep.
for (dep_pkg_id, deps) in self.deps(pkg_id, fk) {
for (dep, dep_fk) in deps {
if dep.name_in_toml() != dep_name {
continue;
}
if let Some(to_enable) = &to_enable {
for dep_feature in to_enable {
tracing::trace!(
"activate deferred {} {} -> {}/{}",
pkg_id.name(),
fk,
dep_name,
dep_feature
);
let fv = FeatureValue::new(*dep_feature);
self.activate_fv(dep_pkg_id, dep_fk, &fv)?;
}
}
let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
self.activate_pkg(dep_pkg_id, dep_fk, &fvs)?;
}
}
Ok(())
}
/// Activate a feature within a dependency (`dep_name/feat_name` syntax).
fn activate_dep_feature(
&mut self,
pkg_id: PackageId,
fk: FeaturesFor,
dep_name: InternedString,
dep_feature: InternedString,
weak: bool,
) -> CargoResult<()> {
for (dep_pkg_id, deps) in self.deps(pkg_id, fk) {
for (dep, dep_fk) in deps {
if dep.name_in_toml() != dep_name {
continue;
}
if dep.is_optional() {
let save_for_host = fk.apply_opts(&self.opts);
if weak
&& !self
.activated_dependencies
.get(&(pkg_id, save_for_host))
.map(|deps| deps.contains(&dep_name))
.unwrap_or(false)
{
// This is weak, but not yet activated. Defer in case
// something comes along later and enables it.
tracing::trace!(
"deferring feature {} {} -> {}/{}",
pkg_id.name(),
fk,
dep_name,
dep_feature
);
self.deferred_weak_dependencies
.entry((pkg_id, fk, dep_name))
.or_default()
.insert(dep_feature);
continue;
}
// Activate the dependency on self.
let fv = FeatureValue::Dep { dep_name };
self.activate_fv(pkg_id, fk, &fv)?;
if !weak {
// The old behavior before weak dependencies were
// added is to also enables a feature of the same
// name.
//
// Don't enable if the implicit optional dependency
// feature wasn't created due to `dep:` hiding.
// See rust-lang/cargo#10788 and rust-lang/cargo#12130
let summary = self.resolve.summary(pkg_id);
let feature_map = summary.features();
if feature_map.contains_key(&dep_name) {
self.activate_rec(pkg_id, fk, dep_name)?;
}
}
}
// Activate the feature on the dependency.
let fv = FeatureValue::new(dep_feature);
self.activate_fv(dep_pkg_id, dep_fk, &fv)?;
}
}
Ok(())
}
/// Returns Vec of FeatureValues from a Dependency definition.
fn fvs_from_dependency(&self, dep_id: PackageId, dep: &Dependency) -> Vec<FeatureValue> {
let summary = self.resolve.summary(dep_id);
let feature_map = summary.features();
let mut result: Vec<FeatureValue> = dep
.features()
.iter()
.map(|f| FeatureValue::new(*f))
.collect();
let default = InternedString::new("default");
if dep.uses_default_features() && feature_map.contains_key(&default) {
result.push(FeatureValue::Feature(default));
}
result
}
/// Returns Vec of FeatureValues from a set of command-line features.
fn fvs_from_requested(
&self,
pkg_id: PackageId,
cli_features: &CliFeatures,
) -> Vec<FeatureValue> {
let summary = self.resolve.summary(pkg_id);
let feature_map = summary.features();
let mut result: Vec<FeatureValue> = cli_features.features.iter().cloned().collect();
let default = InternedString::new("default");
if cli_features.uses_default_features && feature_map.contains_key(&default) {
result.push(FeatureValue::Feature(default));
}
if cli_features.all_features {
result.extend(feature_map.keys().map(|k| FeatureValue::Feature(*k)))
}
result
}
/// Returns the dependencies for a package, filtering out inactive targets.
fn deps(
&self,
pkg_id: PackageId,
fk: FeaturesFor,
) -> Vec<(PackageId, Vec<(&'a Dependency, FeaturesFor)>)> {
// Helper for determining if a platform is activated.
let platform_activated = |dep: &Dependency| -> bool {
// We always count platforms as activated if the target stems from an artifact
// dependency's target specification. This triggers in conjunction with
// `[target.'cfg(…)'.dependencies]` manifest sections.
match (dep.is_build(), fk) {
(true, _) | (_, FeaturesFor::HostDep) => {
// We always care about build-dependencies, and they are always
// Host. If we are computing dependencies "for a build script",
// even normal dependencies are host-only.
self.target_data
.dep_platform_activated(dep, CompileKind::Host)
}
(_, FeaturesFor::NormalOrDev) => self
.requested_targets
.iter()
.any(|kind| self.target_data.dep_platform_activated(dep, *kind)),
(_, FeaturesFor::ArtifactDep(target)) => self
.target_data
.dep_platform_activated(dep, CompileKind::Target(target)),
}
};
self.resolve
.deps(pkg_id)
.map(|(dep_id, deps)| {
let deps = deps
.iter()
.filter(|dep| {
if dep.platform().is_some()
&& self.opts.ignore_inactive_targets
&& !platform_activated(dep)
{
return false;
}
if self.opts.decouple_dev_deps && dep.kind() == DepKind::Development {
return false;
}
true
})
.flat_map(|dep| {
// Each `dep`endency can be built for multiple targets. For one, it
// may be a library target which is built as initially configured
// by `fk`. If it appears as build dependency, it must be built
// for the host.
//
// It may also be an artifact dependency,
// which could be built either
//
// - for a specified (aka 'forced') target, specified by
// `dep = { …, target = <triple>` }`
// - as an artifact for use in build dependencies that should
// build for whichever `--target`s are specified
// - like a library would be built
//
// Generally, the logic for choosing a target for dependencies is
// unaltered and used to determine how to build non-artifacts,
// artifacts without target specification and no library,
// or an artifacts library.
//
// All this may result in a dependency being built multiple times
// for various targets which are either specified in the manifest
// or on the cargo command-line.
let lib_fk = if fk == FeaturesFor::default() {
(self.track_for_host && (dep.is_build() || self.is_proc_macro(dep_id)))
.then(|| FeaturesFor::HostDep)
.unwrap_or_default()
} else {
fk
};
// `artifact_target_keys` are produced to fulfil the needs of artifacts that have a target specification.
let artifact_target_keys = dep.artifact().map(|artifact| {
(
artifact.is_lib(),
artifact.target().map(|target| match target {
ArtifactTarget::Force(target) => {
vec![FeaturesFor::ArtifactDep(target)]
}
ArtifactTarget::BuildDependencyAssumeTarget => self
.requested_targets
.iter()
.map(|kind| match kind {
CompileKind::Host => {
let host_triple = self.target_data.rustc.host;
CompileTarget::new(&host_triple).unwrap()
}
CompileKind::Target(target) => *target,
})
.map(FeaturesFor::ArtifactDep)
.collect(),
}),
)
});
let dep_fks = match artifact_target_keys {
// The artifact is also a library and does specify custom
// targets.
// The library's feature key needs to be used alongside
// the keys artifact targets.
Some((is_lib, Some(mut dep_fks))) if is_lib => {
dep_fks.push(lib_fk);
dep_fks
}
// The artifact is not a library, but does specify
// custom targets.
// Use only these targets feature keys.
Some((_, Some(dep_fks))) => dep_fks,
// There is no artifact in the current dependency
// or there is no target specified on the artifact.
// Use the standard feature key without any alteration.
Some((_, None)) | None => vec![lib_fk],
};
dep_fks.into_iter().map(move |dep_fk| (dep, dep_fk))
})
.collect::<Vec<_>>();
(dep_id, deps)
})
.filter(|(_id, deps)| !deps.is_empty())
.collect()
}
/// Compare the activated features to the resolver. Used for testing.
fn compare(&self) {
let mut found = false;
for ((pkg_id, dep_kind), features) in &self.activated_features {
let r_features = self.resolve.features(*pkg_id);
if !r_features.iter().eq(features.iter()) {
crate::drop_eprintln!(
self.ws.config(),
"{}/{:?} features mismatch\nresolve: {:?}\nnew: {:?}\n",
pkg_id,
dep_kind,
r_features,
features
);
found = true;
}
}
if found {
panic!("feature mismatch");
}
}
fn is_proc_macro(&self, package_id: PackageId) -> bool {
self.package_set
.get_one(package_id)
.expect("packages downloaded")
.proc_macro()
}
}