New namespaced features implementation.

This commit is contained in:
Eric Huss 2020-10-20 11:15:48 -07:00
parent dca0b11566
commit bcfdf9fbad
24 changed files with 1505 additions and 591 deletions

View file

@ -1218,7 +1218,7 @@ enum MatchKind {
/// Compares a line with an expected pattern.
/// - Use `[..]` as a wildcard to match 0 or more characters on the same line
/// (similar to `.*` in a regex).
/// (similar to `.*` in a regex). It is non-greedy.
/// - Use `[EXE]` to optionally add `.exe` on Windows (empty string on other
/// platforms).
/// - There is a wide range of macros (such as `[COMPILING]` or `[WARNING]`)

View file

@ -170,14 +170,7 @@ pub fn resolve_with_config_raw(
list: registry,
used: HashSet::new(),
};
let summary = Summary::new(
pkg_id("root"),
deps,
&BTreeMap::<String, Vec<String>>::new(),
None::<&String>,
false,
)
.unwrap();
let summary = Summary::new(pkg_id("root"), deps, &BTreeMap::new(), None::<&String>).unwrap();
let opts = ResolveOpts::everything();
let start = Instant::now();
let resolve = resolver::resolve(
@ -571,14 +564,7 @@ pub fn pkg_dep<T: ToPkgId>(name: T, dep: Vec<Dependency>) -> Summary {
} else {
None
};
Summary::new(
name.to_pkgid(),
dep,
&BTreeMap::<String, Vec<String>>::new(),
link,
false,
)
.unwrap()
Summary::new(name.to_pkgid(), dep, &BTreeMap::new(), link).unwrap()
}
pub fn pkg_id(name: &str) -> PackageId {
@ -599,14 +585,7 @@ pub fn pkg_loc(name: &str, loc: &str) -> Summary {
} else {
None
};
Summary::new(
pkg_id_loc(name, loc),
Vec::new(),
&BTreeMap::<String, Vec<String>>::new(),
link,
false,
)
.unwrap()
Summary::new(pkg_id_loc(name, loc), Vec::new(), &BTreeMap::new(), link).unwrap()
}
pub fn remove_dep(sum: &Summary, ind: usize) -> Summary {
@ -616,9 +595,8 @@ pub fn remove_dep(sum: &Summary, ind: usize) -> Summary {
Summary::new(
sum.package_id(),
deps,
&BTreeMap::<String, Vec<String>>::new(),
&BTreeMap::new(),
sum.links().map(|a| a.as_str()),
sum.namespaced_features(),
)
.unwrap()
}

View file

@ -228,6 +228,7 @@ proptest! {
}
#[test]
#[should_panic(expected = "pub dep")] // The error handling is not yet implemented.
fn pub_fail() {
let input = vec![
pkg!(("a", "0.0.4")),

View file

@ -35,13 +35,14 @@ pub fn main(config: &mut Config) -> CliResult {
"
Available unstable (nightly-only) flags:
-Z avoid-dev-deps -- Avoid installing dev-dependencies if possible
-Z minimal-versions -- Install minimal dependency versions instead of maximum
-Z no-index-update -- Do not update the registry, avoids a network request for benchmarking
-Z unstable-options -- Allow the usage of unstable options
-Z timings -- Display concurrency information
-Z doctest-xcompile -- Compile and run doctests for non-host target using runner config
-Z terminal-width -- Provide a terminal width to rustc for error truncation
-Z avoid-dev-deps -- Avoid installing dev-dependencies if possible
-Z minimal-versions -- Install minimal dependency versions instead of maximum
-Z no-index-update -- Do not update the registry, avoids a network request for benchmarking
-Z unstable-options -- Allow the usage of unstable options
-Z timings -- Display concurrency information
-Z doctest-xcompile -- Compile and run doctests for non-host target using runner config
-Z terminal-width -- Provide a terminal width to rustc for error truncation
-Z namespaced-features -- Allow features with `crate:` prefix
Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
);

View file

@ -713,6 +713,16 @@ impl<'a, 'cfg> State<'a, 'cfg> {
features.activated_features(pkg_id, features_for)
}
fn is_dep_activated(
&self,
pkg_id: PackageId,
features_for: FeaturesFor,
dep_name: InternedString,
) -> bool {
self.features()
.is_dep_activated(pkg_id, features_for, dep_name)
}
fn get(&self, id: PackageId) -> &'a Package {
self.package_set
.get_one(id)
@ -738,9 +748,7 @@ impl<'a, 'cfg> State<'a, 'cfg> {
// did not enable it, don't include it.
if dep.is_optional() {
let features_for = unit_for.map_to_features_for();
let feats = self.activated_features(pkg_id, features_for);
if !feats.contains(&dep.name_in_toml()) {
if !self.is_dep_activated(pkg_id, features_for, dep.name_in_toml()) {
return false;
}
}

View file

@ -197,9 +197,6 @@ features! {
// Overriding profiles for dependencies.
[stable] profile_overrides: bool,
// Separating the namespaces for features and dependencies
[unstable] namespaced_features: bool,
// "default-run" manifest option,
[stable] default_run: bool,
@ -360,6 +357,7 @@ pub struct CliUnstable {
pub multitarget: bool,
pub rustdoc_map: bool,
pub terminal_width: Option<Option<usize>>,
pub namespaced_features: bool,
}
fn deserialize_build_std<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
@ -465,6 +463,7 @@ impl CliUnstable {
"multitarget" => self.multitarget = parse_empty(k, v)?,
"rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
"terminal-width" => self.terminal_width = Some(parse_usize_opt(v)?),
"namespaced-features" => self.namespaced_features = parse_empty(k, v)?,
_ => bail!("unknown `-Z` flag specified: {}", k),
}

View file

@ -1,6 +1,6 @@
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::cmp::Ordering;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt;
use std::hash;
use std::mem;
@ -23,7 +23,7 @@ use crate::core::dependency::DepKind;
use crate::core::resolver::{HasDevUnits, Resolve};
use crate::core::source::MaybePackage;
use crate::core::{Dependency, Manifest, PackageId, SourceId, Target};
use crate::core::{FeatureMap, SourceMap, Summary, Workspace};
use crate::core::{SourceMap, Summary, Workspace};
use crate::ops;
use crate::util::config::PackageCacheLock;
use crate::util::errors::{CargoResult, CargoResultExt, HttpNot200};
@ -87,7 +87,7 @@ struct SerializedPackage<'a> {
source: SourceId,
dependencies: &'a [Dependency],
targets: Vec<&'a Target>,
features: &'a FeatureMap,
features: &'a BTreeMap<InternedString, Vec<InternedString>>,
manifest_path: &'a Path,
metadata: Option<&'a toml::Value>,
publish: Option<&'a Vec<String>>,
@ -131,6 +131,12 @@ impl ser::Serialize for Package {
.iter()
.filter(|t| t.src_path().is_path())
.collect();
let empty_feats = BTreeMap::new();
let features = self
.manifest()
.original()
.features()
.unwrap_or(&empty_feats);
SerializedPackage {
name: &*package_id.name(),
@ -142,7 +148,7 @@ impl ser::Serialize for Package {
source: summary.source_id(),
dependencies: summary.dependencies(),
targets,
features: summary.features(),
features,
manifest_path: self.manifest_path(),
metadata: self.manifest().custom_metadata(),
authors,

View file

@ -12,7 +12,7 @@
use crate::core::resolver::context::Context;
use crate::core::resolver::errors::describe_path;
use crate::core::resolver::types::{ConflictReason, DepInfo, FeaturesSet};
use crate::core::resolver::{ActivateResult, ResolveOpts};
use crate::core::resolver::{ActivateError, ActivateResult, ResolveOpts};
use crate::core::{Dependency, FeatureValue, PackageId, PackageIdSpec, Registry, Summary};
use crate::core::{GitReference, SourceId};
use crate::util::errors::{CargoResult, CargoResultExt};
@ -307,10 +307,10 @@ pub fn resolve_features<'b>(
let deps = s.dependencies();
let deps = deps.iter().filter(|d| d.is_transitive() || opts.dev_deps);
let reqs = build_requirements(s, opts)?;
let reqs = build_requirements(parent, s, opts)?;
let mut ret = Vec::new();
let mut used_features = HashSet::new();
let default_dep = (false, BTreeSet::new());
let default_dep = BTreeSet::new();
let mut valid_dep_names = HashSet::new();
// Next, collect all actually enabled dependencies and their features.
for dep in deps {
@ -319,33 +319,15 @@ pub fn resolve_features<'b>(
if dep.is_optional() && !reqs.deps.contains_key(&dep.name_in_toml()) {
continue;
}
valid_dep_names.insert(dep.name_in_toml());
// So we want this dependency. Move the features we want from
// `feature_deps` to `ret` and register ourselves as using this
// name.
let base = reqs.deps.get(&dep.name_in_toml()).unwrap_or(&default_dep);
used_features.insert(dep.name_in_toml());
let always_required = !dep.is_optional()
&& !s
.dependencies()
.iter()
.any(|d| d.is_optional() && d.name_in_toml() == dep.name_in_toml());
if always_required && base.0 {
return Err(match parent {
None => anyhow::format_err!(
"Package `{}` does not have feature `{}`. It has a required dependency \
with that name, but only optional dependencies can be used as features.",
s.package_id(),
dep.name_in_toml()
)
.into(),
Some(p) => (
p,
ConflictReason::RequiredDependencyAsFeatures(dep.name_in_toml()),
)
.into(),
});
}
let mut base = base.1.clone();
let mut base = reqs
.deps
.get(&dep.name_in_toml())
.unwrap_or(&default_dep)
.clone();
base.extend(dep.features().iter());
for feature in base.iter() {
if feature.contains('/') {
@ -359,74 +341,88 @@ pub fn resolve_features<'b>(
ret.push((dep.clone(), Rc::new(base)));
}
// Any entries in `reqs.dep` which weren't used are bugs in that the
// package does not actually have those dependencies. We classified
// them as dependencies in the first place because there is no such
// feature, either.
let remaining = reqs
.deps
.keys()
.cloned()
.filter(|s| !used_features.contains(s))
.collect::<Vec<_>>();
if !remaining.is_empty() {
let features = remaining.join(", ");
return Err(match parent {
None => anyhow::format_err!(
"Package `{}` does not have these features: `{}`",
s.package_id(),
features
)
.into(),
Some(p) => (p, ConflictReason::MissingFeatures(features)).into(),
});
// This is a special case for command-line `--features
// crate_name/feat_name` where `crate_name` does not exist. All other
// validation is done either in `build_requirements` or
// `build_feature_map`.
for dep_name in reqs.deps.keys() {
if !valid_dep_names.contains(dep_name) {
let e = RequirementError::MissingDependency(*dep_name);
return Err(e.into_activate_error(parent, s));
}
}
Ok((reqs.into_used(), ret))
Ok((reqs.into_features(), ret))
}
/// Takes requested features for a single package from the input `ResolveOpts` and
/// recurses to find all requested features, dependencies and requested
/// dependency features in a `Requirements` object, returning it to the resolver.
fn build_requirements<'a, 'b: 'a>(
parent: Option<PackageId>,
s: &'a Summary,
opts: &'b ResolveOpts,
) -> CargoResult<Requirements<'a>> {
) -> ActivateResult<Requirements<'a>> {
let mut reqs = Requirements::new(s);
if opts.features.all_features {
for key in s.features().keys() {
reqs.require_feature(*key)?;
}
for dep in s.dependencies().iter().filter(|d| d.is_optional()) {
reqs.require_dependency(dep.name_in_toml());
if let Err(e) = reqs.require_feature(*key) {
return Err(e.into_activate_error(parent, s));
}
}
} else {
for &f in opts.features.features.iter() {
reqs.require_value(&FeatureValue::new(f, s))?;
let fv = FeatureValue::new(f);
if fv.is_explicit_crate() {
return Err(ActivateError::Fatal(anyhow::format_err!(
"feature value `{}` is not allowed to use explicit `crate:` syntax",
fv
)));
}
if let Err(e) = reqs.require_value(&fv) {
return Err(e.into_activate_error(parent, s));
}
}
}
if opts.features.uses_default_features && s.features().contains_key("default") {
reqs.require_feature(InternedString::new("default"))?;
if let Err(e) = reqs.require_feature(InternedString::new("default")) {
return Err(e.into_activate_error(parent, s));
}
}
Ok(reqs)
}
/// Set of feature and dependency requirements for a package.
#[derive(Debug)]
struct Requirements<'a> {
summary: &'a Summary,
// The deps map is a mapping of package name to list of features enabled.
// Each package should be enabled, and each package should have the
// specified set of features enabled. The boolean indicates whether this
// package was specifically requested (rather than just requesting features
// *within* this package).
deps: HashMap<InternedString, (bool, BTreeSet<InternedString>)>,
// The used features set is the set of features which this local package had
// enabled, which is later used when compiling to instruct the code what
// features were enabled.
used: HashSet<InternedString>,
visited: HashSet<InternedString>,
/// The deps map is a mapping of dependency name to list of features enabled.
///
/// The resolver will activate all of these dependencies, with the given
/// features enabled.
deps: HashMap<InternedString, BTreeSet<InternedString>>,
/// The set of features enabled on this package which is later used when
/// compiling to instruct the code what features were enabled.
features: HashSet<InternedString>,
}
/// An error for a requirement.
///
/// This will later be converted to an `ActivateError` depending on whether or
/// not this is a dependency or a root package.
enum RequirementError {
/// The package does not have the requested feature.
MissingFeature(InternedString),
/// The package does not have the requested dependency.
MissingDependency(InternedString),
/// A feature has a direct cycle to itself.
///
/// Note that cycles through multiple features are allowed (but perhaps
/// they shouldn't be?).
Cycle(InternedString),
}
impl Requirements<'_> {
@ -434,80 +430,146 @@ impl Requirements<'_> {
Requirements {
summary,
deps: HashMap::new(),
used: HashSet::new(),
visited: HashSet::new(),
features: HashSet::new(),
}
}
fn into_used(self) -> HashSet<InternedString> {
self.used
fn into_features(self) -> HashSet<InternedString> {
self.features
}
fn require_crate_feature(&mut self, package: InternedString, feat: InternedString) {
fn require_crate_feature(
&mut self,
package: InternedString,
feat: InternedString,
explicit: bool,
) -> Result<(), RequirementError> {
// If `package` is indeed an optional dependency then we activate the
// feature named `package`, but otherwise if `package` is a required
// dependency then there's no feature associated with it.
if self
.summary
.dependencies()
.iter()
.any(|dep| dep.name_in_toml() == package && dep.is_optional())
if !explicit
&& self
.summary
.dependencies()
.iter()
.any(|dep| dep.name_in_toml() == package && dep.is_optional())
{
self.used.insert(package);
}
self.deps
.entry(package)
.or_insert((false, BTreeSet::new()))
.1
.insert(feat);
}
fn seen(&mut self, feat: InternedString) -> bool {
if self.visited.insert(feat) {
self.used.insert(feat);
false
} else {
true
self.require_feature(package)?;
}
self.deps.entry(package).or_default().insert(feat);
Ok(())
}
fn require_dependency(&mut self, pkg: InternedString) {
if self.seen(pkg) {
return;
}
self.deps.entry(pkg).or_insert((false, BTreeSet::new())).0 = true;
self.deps.entry(pkg).or_default();
}
fn require_feature(&mut self, feat: InternedString) -> CargoResult<()> {
if feat.is_empty() || self.seen(feat) {
fn require_feature(&mut self, feat: InternedString) -> Result<(), RequirementError> {
if !self.features.insert(feat) {
// Already seen this feature.
return Ok(());
}
for fv in self
.summary
.features()
.get(&feat)
.expect("must be a valid feature")
{
match *fv {
FeatureValue::Feature(ref dep_feat) if **dep_feat == *feat => anyhow::bail!(
"cyclic feature dependency: feature `{}` depends on itself",
feat
),
_ => {}
let fvs = match self.summary.features().get(&feat) {
Some(fvs) => fvs,
None => return Err(RequirementError::MissingFeature(feat)),
};
for fv in fvs {
if let FeatureValue::Feature(dep_feat) = fv {
if *dep_feat == feat {
return Err(RequirementError::Cycle(feat));
}
}
self.require_value(fv)?;
}
Ok(())
}
fn require_value(&mut self, fv: &FeatureValue) -> CargoResult<()> {
fn require_value(&mut self, fv: &FeatureValue) -> Result<(), RequirementError> {
match fv {
FeatureValue::Feature(feat) => self.require_feature(*feat)?,
FeatureValue::Crate(dep) => self.require_dependency(*dep),
FeatureValue::CrateFeature(dep, dep_feat) => {
self.require_crate_feature(*dep, *dep_feat)
}
FeatureValue::Crate { dep_name } => self.require_dependency(*dep_name),
FeatureValue::CrateFeature {
dep_name,
dep_feature,
explicit,
} => self.require_crate_feature(*dep_name, *dep_feature, *explicit)?,
};
Ok(())
}
}
impl RequirementError {
fn into_activate_error(self, parent: Option<PackageId>, summary: &Summary) -> ActivateError {
match self {
RequirementError::MissingFeature(feat) => {
let deps: Vec<_> = summary
.dependencies()
.iter()
.filter(|dep| dep.name_in_toml() == feat)
.collect();
if deps.is_empty() {
return match parent {
None => ActivateError::Fatal(anyhow::format_err!(
"Package `{}` does not have the feature `{}`",
summary.package_id(),
feat
)),
Some(p) => ActivateError::Conflict(
p,
ConflictReason::MissingFeatures(feat.to_string()),
),
};
}
if deps.iter().any(|dep| dep.is_optional()) {
match parent {
None => ActivateError::Fatal(anyhow::format_err!(
"Package `{}` does not have feature `{}`. It has an optional dependency \
with that name, but that dependency uses the \"crate:\" \
syntax in the features table, so it does not have an implicit feature with that name.",
summary.package_id(),
feat
)),
Some(p) => ActivateError::Conflict(
p,
ConflictReason::NonImplicitDependencyAsFeature(feat),
),
}
} else {
match parent {
None => ActivateError::Fatal(anyhow::format_err!(
"Package `{}` does not have feature `{}`. It has a required dependency \
with that name, but only optional dependencies can be used as features.",
summary.package_id(),
feat
)),
Some(p) => ActivateError::Conflict(
p,
ConflictReason::RequiredDependencyAsFeature(feat),
),
}
}
}
RequirementError::MissingDependency(dep_name) => {
match parent {
None => ActivateError::Fatal(anyhow::format_err!(
"package `{}` does not have a dependency named `{}`",
summary.package_id(),
dep_name
)),
// This code path currently isn't used, since `foo/bar`
// and `crate:` syntax is not allowed in a dependency.
Some(p) => ActivateError::Conflict(
p,
ConflictReason::MissingFeatures(dep_name.to_string()),
),
}
}
RequirementError::Cycle(feat) => ActivateError::Fatal(anyhow::format_err!(
"cyclic feature dependency: feature `{}` depends on itself",
feat
)),
}
}
}

View file

@ -142,7 +142,7 @@ pub(super) fn activation_error(
msg.push_str("` does not have these features.\n");
// p == parent so the full path is redundant.
}
ConflictReason::RequiredDependencyAsFeatures(features) => {
ConflictReason::RequiredDependencyAsFeature(features) => {
msg.push_str("\n\nthe package `");
msg.push_str(&*p.name());
msg.push_str("` depends on `");
@ -158,6 +158,24 @@ pub(super) fn activation_error(
);
// p == parent so the full path is redundant.
}
ConflictReason::NonImplicitDependencyAsFeature(features) => {
msg.push_str("\n\nthe package `");
msg.push_str(&*p.name());
msg.push_str("` depends on `");
msg.push_str(&*dep.package_name());
msg.push_str("`, with features: `");
msg.push_str(features);
msg.push_str("` but `");
msg.push_str(&*dep.package_name());
msg.push_str("` does not have these features.\n");
msg.push_str(
" It has an optional dependency with that name, \
but but that dependency uses the \"crate:\" \
syntax in the features table, so it does not have an \
implicit feature with that name.\n",
);
// p == parent so the full path is redundant.
}
ConflictReason::PublicDependency(pkg_id) => {
// TODO: This needs to be implemented.
unimplemented!("pub dep {:?}", pkg_id);

View file

@ -57,8 +57,18 @@ type ActivateMap = HashMap<(PackageId, bool), 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,
/// This is only here for legacy support when `-Zfeatures` is not enabled.
legacy: Option<HashMap<PackageId, Vec<InternedString>>>,
///
/// This is the set of features enabled for each package.
legacy_features: Option<HashMap<PackageId, Vec<InternedString>>>,
/// This is only here for legacy support when `-Zfeatures` is not enabled.
///
/// This is the set of optional dependencies enabled for each package.
legacy_dependencies: Option<HashMap<PackageId, HashSet<InternedString>>>,
opts: FeatureOpts,
}
@ -227,6 +237,30 @@ impl ResolvedFeatures {
.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 {
if let Some(legacy) = &self.legacy_dependencies {
legacy
.get(&pkg_id)
.map(|deps| deps.contains(&dep_name))
.unwrap_or(false)
} else {
let is_build = self.opts.decouple_host_deps && features_for == FeaturesFor::HostDep;
self.activated_dependencies
.get(&(pkg_id, is_build))
.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`).
@ -243,7 +277,7 @@ impl ResolvedFeatures {
pkg_id: PackageId,
features_for: FeaturesFor,
) -> CargoResult<Vec<InternedString>> {
if let Some(legacy) = &self.legacy {
if let Some(legacy) = &self.legacy_features {
Ok(legacy.get(&pkg_id).map_or_else(Vec::new, |v| v.clone()))
} else {
let is_build = self.opts.decouple_host_deps && features_for == FeaturesFor::HostDep;
@ -267,6 +301,8 @@ pub struct FeatureResolver<'a, 'cfg> {
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<(PackageId, bool)>,
@ -294,7 +330,9 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
// Legacy mode.
return Ok(ResolvedFeatures {
activated_features: HashMap::new(),
legacy: Some(resolve.features_clone()),
activated_dependencies: HashMap::new(),
legacy_features: Some(resolve.features_clone()),
legacy_dependencies: Some(compute_legacy_deps(resolve)),
opts,
});
}
@ -306,6 +344,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
package_set,
opts,
activated_features: HashMap::new(),
activated_dependencies: HashMap::new(),
processed_deps: HashSet::new(),
};
r.do_resolve(specs, requested_features)?;
@ -315,7 +354,9 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
}
Ok(ResolvedFeatures {
activated_features: r.activated_features,
legacy: None,
activated_dependencies: r.activated_dependencies,
legacy_features: None,
legacy_dependencies: None,
opts: r.opts,
})
}
@ -402,9 +443,12 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
FeatureValue::Feature(f) => {
self.activate_rec(pkg_id, *f, for_host)?;
}
FeatureValue::Crate(dep_name) => {
// Activate the feature name on self.
self.activate_rec(pkg_id, *dep_name, for_host)?;
FeatureValue::Crate { dep_name } => {
// Mark this dependency as activated.
self.activated_dependencies
.entry((pkg_id, self.opts.decouple_host_deps && for_host))
.or_default()
.insert(*dep_name);
// Activate the optional dep.
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
for (dep, dep_for_host) in deps {
@ -416,7 +460,11 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
}
}
}
FeatureValue::CrateFeature(dep_name, dep_feature) => {
FeatureValue::CrateFeature {
dep_name,
dep_feature,
explicit,
} => {
// Activate a feature within a dependency.
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
for (dep, dep_for_host) in deps {
@ -425,12 +473,20 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
}
if dep.is_optional() {
// Activate the crate on self.
let fv = FeatureValue::Crate(*dep_name);
let fv = FeatureValue::Crate {
dep_name: *dep_name,
// explicit: *explicit,
};
self.activate_fv(pkg_id, &fv, for_host)?;
if !explicit {
// To retain compatibility with old behavior,
// this also enables a feature of the same
// name.
self.activate_rec(pkg_id, *dep_name, for_host)?;
}
}
// Activate the feature on the dependency.
let summary = self.resolve.summary(dep_pkg_id);
let fv = FeatureValue::new(*dep_feature, summary);
let fv = FeatureValue::new(*dep_feature);
self.activate_fv(dep_pkg_id, &fv, dep_for_host)?;
}
}
@ -484,7 +540,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
let mut result: Vec<FeatureValue> = dep
.features()
.iter()
.map(|f| FeatureValue::new(*f, summary))
.map(|f| FeatureValue::new(*f))
.collect();
let default = InternedString::new("default");
if dep.uses_default_features() && feature_map.contains_key(&default) {
@ -502,28 +558,16 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
let summary = self.resolve.summary(pkg_id);
let feature_map = summary.features();
if requested_features.all_features {
let mut fvs: Vec<FeatureValue> = feature_map
feature_map
.keys()
.map(|k| FeatureValue::Feature(*k))
.collect();
// Add optional deps.
// Top-level requested features can never apply to
// build-dependencies, so for_host is `false` here.
for (_dep_pkg_id, deps) in self.deps(pkg_id, false) {
for (dep, _dep_for_host) in deps {
if dep.is_optional() {
// This may result in duplicates, but that should be ok.
fvs.push(FeatureValue::Crate(dep.name_in_toml()));
}
}
}
fvs
.collect()
} else {
let mut result: Vec<FeatureValue> = requested_features
.features
.as_ref()
.iter()
.map(|f| FeatureValue::new(*f, summary))
.map(|f| FeatureValue::new(*f))
.collect();
let default = InternedString::new("default");
if requested_features.uses_default_features && feature_map.contains_key(&default) {
@ -612,3 +656,19 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
.proc_macro()
}
}
/// Computes a map of PackageId to the set of optional dependencies that are
/// enabled for that dep (when the new resolver is not enabled).
fn compute_legacy_deps(resolve: &Resolve) -> HashMap<PackageId, HashSet<InternedString>> {
let mut result: HashMap<PackageId, HashSet<InternedString>> = HashMap::new();
for pkg_id in resolve.iter() {
for (_dep_id, deps) in resolve.deps(pkg_id) {
for dep in deps {
if dep.is_optional() {
result.entry(pkg_id).or_default().insert(dep.name_in_toml());
}
}
}
}
result
}

View file

@ -290,11 +290,15 @@ pub enum ConflictReason {
/// candidate we're activating didn't actually have the feature `foo`.
MissingFeatures(String),
/// A dependency listed features that ended up being a required dependency.
/// A dependency listed a feature that ended up being a required dependency.
/// For example we tried to activate feature `foo` but the
/// candidate we're activating didn't actually have the feature `foo`
/// it had a dependency `foo` instead.
RequiredDependencyAsFeatures(InternedString),
RequiredDependencyAsFeature(InternedString),
/// A dependency listed a feature for an optional dependency, but that
/// optional dependency is "hidden" using namespaced `crate:` syntax.
NonImplicitDependencyAsFeature(InternedString),
// TODO: needs more info for `activation_error`
// TODO: needs more info for `find_candidate`
@ -319,7 +323,7 @@ impl ConflictReason {
}
pub fn is_required_dependency_as_features(&self) -> bool {
if let ConflictReason::RequiredDependencyAsFeatures(_) = *self {
if let ConflictReason::RequiredDependencyAsFeature(_) = *self {
return true;
}
false

View file

@ -1,18 +1,14 @@
use std::borrow::Borrow;
use std::collections::{BTreeMap, HashMap};
use std::fmt::Display;
use crate::core::{Dependency, PackageId, SourceId};
use crate::util::interning::InternedString;
use crate::util::CargoResult;
use anyhow::bail;
use semver::Version;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::mem;
use std::rc::Rc;
use serde::{Serialize, Serializer};
use crate::core::{Dependency, PackageId, SourceId};
use crate::util::interning::InternedString;
use semver::Version;
use crate::util::CargoResult;
/// Subset of a `Manifest`. Contains only the most important information about
/// a package.
///
@ -27,39 +23,33 @@ struct Inner {
package_id: PackageId,
dependencies: Vec<Dependency>,
features: Rc<FeatureMap>,
has_namespaced_features: bool,
has_overlapping_features: Option<InternedString>,
checksum: Option<String>,
links: Option<InternedString>,
namespaced_features: bool,
}
impl Summary {
pub fn new<K>(
pub fn new(
pkg_id: PackageId,
dependencies: Vec<Dependency>,
features: &BTreeMap<K, Vec<impl AsRef<str>>>,
features: &BTreeMap<InternedString, Vec<InternedString>>,
links: Option<impl Into<InternedString>>,
namespaced_features: bool,
) -> CargoResult<Summary>
where
K: Borrow<str> + Ord + Display,
{
) -> CargoResult<Summary> {
let mut has_overlapping_features = None;
for dep in dependencies.iter() {
let feature = dep.name_in_toml();
if !namespaced_features && features.get(&*feature).is_some() {
anyhow::bail!(
"Features and dependencies cannot have the \
same name: `{}`",
feature
)
let dep_name = dep.name_in_toml();
if features.contains_key(&dep_name) {
has_overlapping_features = Some(dep_name);
}
if dep.is_optional() && !dep.is_transitive() {
anyhow::bail!(
"Dev-dependencies are not allowed to be optional: `{}`",
feature
bail!(
"dev-dependencies are not allowed to be optional: `{}`",
dep_name
)
}
}
let feature_map = build_feature_map(features, &dependencies, namespaced_features)?;
let (feature_map, has_namespaced_features) = build_feature_map(features, &dependencies)?;
Ok(Summary {
inner: Rc::new(Inner {
package_id: pkg_id,
@ -67,7 +57,8 @@ impl Summary {
features: Rc::new(feature_map),
checksum: None,
links: links.map(|l| l.into()),
namespaced_features,
has_namespaced_features,
has_overlapping_features,
}),
})
}
@ -90,15 +81,33 @@ impl Summary {
pub fn features(&self) -> &FeatureMap {
&self.inner.features
}
/// Returns an error if this Summary is using an unstable feature that is
/// not enabled.
pub fn unstable_gate(&self, namespaced_features: bool) -> CargoResult<()> {
if !namespaced_features {
if self.inner.has_namespaced_features {
bail!(
"namespaced features with the `crate:` prefix are only allowed on \
the nightly channel and requires the `-Z namespaced-features` flag on the command-line"
);
}
if let Some(dep_name) = self.inner.has_overlapping_features {
bail!(
"features and dependencies cannot have the same name: `{}`",
dep_name
)
}
}
Ok(())
}
pub fn checksum(&self) -> Option<&str> {
self.inner.checksum.as_deref()
}
pub fn links(&self) -> Option<InternedString> {
self.inner.links
}
pub fn namespaced_features(&self) -> bool {
self.inner.namespaced_features
}
pub fn override_id(mut self, id: PackageId) -> Summary {
Rc::make_mut(&mut self.inner).package_id = id;
@ -145,16 +154,16 @@ impl Hash for Summary {
}
}
// Checks features for errors, bailing out a CargoResult:Err if invalid,
// and creates FeatureValues for each feature.
fn build_feature_map<K>(
features: &BTreeMap<K, Vec<impl AsRef<str>>>,
/// Checks features for errors, bailing out a CargoResult:Err if invalid,
/// and creates FeatureValues for each feature.
///
/// The returned `bool` indicates whether or not the `[features]` table
/// included a `crate:` prefixed namespaced feature (used for gating on
/// nightly).
fn build_feature_map(
features: &BTreeMap<InternedString, Vec<InternedString>>,
dependencies: &[Dependency],
namespaced: bool,
) -> CargoResult<FeatureMap>
where
K: Borrow<str> + Ord + Display,
{
) -> CargoResult<(FeatureMap, bool)> {
use self::FeatureValue::*;
let mut dep_map = HashMap::new();
for dep in dependencies.iter() {
@ -164,54 +173,62 @@ where
.push(dep);
}
let mut map = BTreeMap::new();
for (feature, list) in features.iter() {
// If namespaced features is active and the key is the same as that of an
// optional dependency, that dependency must be included in the values.
// Thus, if a `feature` is found that has the same name as a dependency, we
// (a) bail out if the dependency is non-optional, and (b) we track if the
// feature requirements include the dependency `crate:feature` in the list.
// This is done with the `dependency_found` variable, which can only be
// false if features are namespaced and the current feature key is the same
// as the name of an optional dependency. If so, it gets set to true during
// iteration over the list if the dependency is found in the list.
let mut dependency_found = if namespaced {
match dep_map.get(feature.borrow()) {
Some(dep_data) => {
if !dep_data.iter().any(|d| d.is_optional()) {
anyhow::bail!(
"Feature `{}` includes the dependency of the same name, but this is \
left implicit in the features included by this feature.\n\
Additionally, the dependency must be marked as optional to be \
included in the feature definition.\n\
Consider adding `crate:{}` to this feature's requirements \
and marking the dependency as `optional = true`",
feature,
feature
)
} else {
false
}
}
None => true,
}
} else {
true
let mut map: FeatureMap = features
.iter()
.map(|(feature, list)| {
let fvs: Vec<_> = list
.iter()
.map(|feat_value| FeatureValue::new(*feat_value))
.collect();
(*feature, fvs)
})
.collect();
let has_namespaced_features = map.values().flatten().any(|fv| fv.is_explicit_crate());
// Add implicit features for optional dependencies if they weren't
// explicitly listed anywhere.
let explicitly_listed: HashSet<_> = map
.values()
.flatten()
.filter_map(|fv| match fv {
Crate { dep_name }
| CrateFeature {
dep_name,
explicit: true,
..
} => Some(*dep_name),
_ => None,
})
.collect();
for dep in dependencies {
if !dep.is_optional() {
continue;
}
let dep_name_in_toml = dep.name_in_toml();
if features.contains_key(&dep_name_in_toml) || explicitly_listed.contains(&dep_name_in_toml)
{
continue;
}
let fv = Crate {
dep_name: dep_name_in_toml,
};
map.insert(dep_name_in_toml, vec![fv]);
}
let mut values = vec![];
for dep in list {
let val = FeatureValue::build(
InternedString::new(dep.as_ref()),
|fs| features.contains_key(fs.as_str()),
namespaced,
// Validate features are listed properly.
for (feature, fvs) in &map {
if feature.starts_with("crate:") {
bail!(
"feature named `{}` is not allowed to start with `crate:`",
feature
);
}
for fv in fvs {
// Find data for the referenced dependency...
let dep_data = {
match val {
Feature(ref dep_name) | Crate(ref dep_name) | CrateFeature(ref dep_name, _) => {
dep_map.get(dep_name.as_str())
match fv {
Feature(dep_name) | Crate { dep_name, .. } | CrateFeature { dep_name, .. } => {
dep_map.get(dep_name)
}
}
};
@ -219,198 +236,160 @@ where
.iter()
.flat_map(|d| d.iter())
.any(|d| d.is_optional());
if let FeatureValue::Crate(ref dep_name) = val {
// If we have a dependency value, check if this is the dependency named
// the same as the feature that we were looking for.
if !dependency_found && feature.borrow() == dep_name.as_str() {
dependency_found = true;
}
}
match (&val, dep_data.is_some(), is_optional_dep) {
// The value is a feature. If features are namespaced, this just means
// it's not prefixed with `crate:`, so we have to check whether the
// feature actually exist. If the feature is not defined *and* an optional
// dependency of the same name exists, the feature is defined implicitly
// here by adding it to the feature map, pointing to the dependency.
// If features are not namespaced, it's been validated as a feature already
// while instantiating the `FeatureValue` in `FeatureValue::build()`, so
// we don't have to do so here.
(&Feature(feat), _, true) => {
if namespaced && !features.contains_key(&*feat) {
map.insert(feat, vec![FeatureValue::Crate(feat)]);
}
}
// If features are namespaced and the value is not defined as a feature
// and there is no optional dependency of the same name, error out.
// If features are not namespaced, there must be an existing feature
// here (checked by `FeatureValue::build()`), so it will always be defined.
(&Feature(feat), dep_exists, false) => {
if namespaced && !features.contains_key(&*feat) {
if dep_exists {
anyhow::bail!(
"Feature `{}` includes `{}` which is not defined as a feature.\n\
A non-optional dependency of the same name is defined; consider \
adding `optional = true` to its definition",
let is_any_dep = dep_data.is_some();
match fv {
Feature(f) => {
if !features.contains_key(f) {
if !is_any_dep {
bail!(
"feature `{}` includes `{}` which is neither a dependency \
nor another feature",
feature,
feat
)
fv
);
}
if is_optional_dep {
if !map.contains_key(f) {
bail!(
"feature `{}` includes `{}`, but `{}` is an \
optional dependency without an implicit feature\n\
Use `crate:{}` to enable the dependency.",
feature,
fv,
f,
f
);
}
} else {
anyhow::bail!(
"Feature `{}` includes `{}` which is not defined as a feature",
feature,
feat
)
bail!("feature `{}` includes `{}`, but `{}` is not an optional dependency\n\
A non-optional dependency of the same name is defined; \
consider adding `optional = true` to its definition.",
feature, fv, f);
}
}
}
// The value is a dependency. If features are namespaced, it is explicitly
// tagged as such (`crate:value`). If features are not namespaced, any value
// not recognized as a feature is pegged as a `Crate`. Here we handle the case
// where the dependency exists but is non-optional. It branches on namespaced
// just to provide the correct string for the crate dependency in the error.
(&Crate(ref dep), true, false) => {
if namespaced {
anyhow::bail!(
"Feature `{}` includes `crate:{}` which is not an \
optional dependency.\nConsider adding \
`optional = true` to the dependency",
Crate { dep_name } => {
if !is_any_dep {
bail!(
"feature `{}` includes `{}`, but `{}` is not listed as a dependency",
feature,
dep
)
} else {
anyhow::bail!(
"Feature `{}` depends on `{}` which is not an \
optional dependency.\nConsider adding \
`optional = true` to the dependency",
fv,
dep_name
);
}
if !is_optional_dep {
bail!(
"feature `{}` includes `{}`, but `{}` is not an optional dependency\n\
A non-optional dependency of the same name is defined; \
consider adding `optional = true` to its definition.",
feature,
dep
)
fv,
dep_name
);
}
}
// If namespaced, the value was tagged as a dependency; if not namespaced,
// this could be anything not defined as a feature. This handles the case
// where no such dependency is actually defined; again, the branch on
// namespaced here is just to provide the correct string in the error.
(&Crate(ref dep), false, _) => {
if namespaced {
anyhow::bail!(
"Feature `{}` includes `crate:{}` which is not a known \
dependency",
CrateFeature { dep_name, .. } => {
// Validation of the feature name will be performed in the resolver.
if !is_any_dep {
bail!(
"feature `{}` includes `{}`, but `{}` is not a dependency",
feature,
dep
)
} else {
anyhow::bail!(
"Feature `{}` includes `{}` which is neither a dependency nor \
another feature",
feature,
dep
)
fv,
dep_name
);
}
}
(&Crate(_), true, true) => {}
// If the value is a feature for one of the dependencies, bail out if no such
// dependency is actually defined in the manifest.
(&CrateFeature(ref dep, _), false, _) => anyhow::bail!(
"Feature `{}` requires a feature of `{}` which is not a \
dependency",
feature,
dep
),
(&CrateFeature(_, _), true, _) => {}
}
values.push(val);
}
if !dependency_found {
// If we have not found the dependency of the same-named feature, we should
// bail here.
anyhow::bail!(
"Feature `{}` includes the optional dependency of the \
same name, but this is left implicit in the features \
included by this feature.\nConsider adding \
`crate:{}` to this feature's requirements.",
feature,
feature
)
}
map.insert(InternedString::new(feature.borrow()), values);
}
Ok(map)
// Make sure every optional dep is mentioned at least once.
let used: HashSet<_> = map
.values()
.flatten()
.filter_map(|fv| match fv {
Crate { dep_name } | CrateFeature { dep_name, .. } => Some(dep_name),
_ => None,
})
.collect();
if let Some(dep) = dependencies
.iter()
.find(|dep| dep.is_optional() && !used.contains(&dep.name_in_toml()))
{
bail!(
"optional dependency `{}` is not included in any feature\n\
Make sure that `crate:{}` is included in one of features in the [features] table.",
dep.name_in_toml(),
dep.name_in_toml(),
);
}
Ok((map, has_namespaced_features))
}
/// FeatureValue represents the types of dependencies a feature can have:
///
/// * Another feature
/// * An optional dependency
/// * A feature in a dependency
///
/// The selection between these 3 things happens as part of the construction of the FeatureValue.
/// FeatureValue represents the types of dependencies a feature can have.
#[derive(Clone, Debug)]
pub enum FeatureValue {
/// A feature enabling another feature.
Feature(InternedString),
Crate(InternedString),
CrateFeature(InternedString, InternedString),
/// A feature enabling a dependency with `crate:dep_name` syntax.
Crate { dep_name: InternedString },
/// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
CrateFeature {
dep_name: InternedString,
dep_feature: InternedString,
/// If this is true, then the feature used the `crate:` prefix, which
/// prevents enabling the feature named `dep_name`.
explicit: bool,
},
}
impl FeatureValue {
fn build<T>(feature: InternedString, is_feature: T, namespaced: bool) -> FeatureValue
where
T: Fn(InternedString) -> bool,
{
match (feature.find('/'), namespaced) {
(Some(pos), _) => {
pub fn new(feature: InternedString) -> FeatureValue {
match feature.find('/') {
Some(pos) => {
let (dep, dep_feat) = feature.split_at(pos);
let dep_feat = &dep_feat[1..];
FeatureValue::CrateFeature(InternedString::new(dep), InternedString::new(dep_feat))
}
(None, true) if feature.starts_with("crate:") => {
FeatureValue::Crate(InternedString::new(&feature[6..]))
}
(None, true) => FeatureValue::Feature(feature),
(None, false) if is_feature(feature) => FeatureValue::Feature(feature),
(None, false) => FeatureValue::Crate(feature),
}
}
pub fn new(feature: InternedString, s: &Summary) -> FeatureValue {
Self::build(
feature,
|fs| s.features().contains_key(&fs),
s.namespaced_features(),
)
}
pub fn to_string(&self, s: &Summary) -> String {
use self::FeatureValue::*;
match *self {
Feature(ref f) => f.to_string(),
Crate(ref c) => {
if s.namespaced_features() {
format!("crate:{}", &c)
let (dep, explicit) = if dep.starts_with("crate:") {
(&dep[6..], true)
} else {
c.to_string()
(dep, false)
};
FeatureValue::CrateFeature {
dep_name: InternedString::new(dep),
dep_feature: InternedString::new(dep_feat),
explicit,
}
}
CrateFeature(ref c, ref f) => [c.as_ref(), f.as_ref()].join("/"),
None if feature.starts_with("crate:") => FeatureValue::Crate {
dep_name: InternedString::new(&feature[6..]),
},
None => FeatureValue::Feature(feature),
}
}
/// Returns `true` if this feature explicitly used `crate:` syntax.
pub fn is_explicit_crate(&self) -> bool {
matches!(self, FeatureValue::Crate{..} | FeatureValue::CrateFeature{explicit:true, ..})
}
}
impl Serialize for FeatureValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
impl fmt::Display for FeatureValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::FeatureValue::*;
match *self {
Feature(ref f) => serializer.serialize_str(f),
Crate(ref c) => serializer.serialize_str(c),
CrateFeature(ref c, ref f) => {
serializer.serialize_str(&[c.as_ref(), f.as_ref()].join("/"))
}
match self {
Feature(feat) => write!(f, "{}", feat),
Crate { dep_name } => write!(f, "crate:{}", dep_name),
CrateFeature {
dep_name,
dep_feature,
explicit: true,
} => write!(f, "crate:{}/{}", dep_name, dep_feature),
CrateFeature {
dep_name,
dep_feature,
explicit: false,
} => write!(f, "{}/{}", dep_name, dep_feature),
}
}
}

View file

@ -1006,8 +1006,9 @@ fn generate_targets(
{
let unavailable_features = match target.required_features() {
Some(rf) => {
warn_on_missing_features(
validate_required_features(
workspace_resolve,
target.name(),
rf,
pkg.summary(),
&mut config.shell(),
@ -1042,8 +1043,13 @@ fn generate_targets(
Ok(units.into_iter().collect())
}
fn warn_on_missing_features(
/// Warns if a target's required-features references a feature that doesn't exist.
///
/// This is a warning because historically this was not validated, and it
/// would cause too much breakage to make it an error.
fn validate_required_features(
resolve: &Option<Resolve>,
target_name: &str,
required_features: &[String],
summary: &Summary,
shell: &mut Shell,
@ -1054,47 +1060,55 @@ fn warn_on_missing_features(
};
for feature in required_features {
match FeatureValue::new(feature.into(), summary) {
// No need to do anything here, since the feature must exist to be parsed as such
FeatureValue::Feature(_) => {}
// Possibly mislabeled feature that was not found
FeatureValue::Crate(krate) => {
if !summary
.dependencies()
.iter()
.any(|dep| dep.name_in_toml() == krate && dep.is_optional())
{
let fv = FeatureValue::new(feature.into());
match &fv {
FeatureValue::Feature(f) => {
if !summary.features().contains_key(f) {
shell.warn(format!(
"feature `{}` is not present in [features] section.",
krate
"invalid feature `{}` in required-features of target `{}`: \
`{}` is not present in [features] section",
fv, target_name, fv
))?;
}
}
FeatureValue::Crate { .. } | FeatureValue::CrateFeature { explicit: true, .. } => {
anyhow::bail!(
"invalid feature `{}` in required-features of target `{}`: \
explicit `crate:` feature values are not allowed in required-features",
fv,
target_name
);
}
// Handling of dependent_crate/dependent_crate_feature syntax
FeatureValue::CrateFeature(krate, feature) => {
FeatureValue::CrateFeature {
dep_name,
dep_feature,
explicit: false,
} => {
match resolve
.deps(summary.package_id())
.find(|(_dep_id, deps)| deps.iter().any(|dep| dep.name_in_toml() == krate))
.find(|(_dep_id, deps)| deps.iter().any(|dep| dep.name_in_toml() == *dep_name))
{
Some((dep_id, _deps)) => {
let dep_summary = resolve.summary(dep_id);
if !dep_summary.features().contains_key(&feature)
if !dep_summary.features().contains_key(dep_feature)
&& !dep_summary
.dependencies()
.iter()
.any(|dep| dep.name_in_toml() == feature && dep.is_optional())
.any(|dep| dep.name_in_toml() == *dep_feature && dep.is_optional())
{
shell.warn(format!(
"feature `{}` does not exist in package `{}`.",
feature, dep_id
"invalid feature `{}` in required-features of target `{}`: \
feature `{}` does not exist in package `{}`",
fv, target_name, dep_feature, dep_id
))?;
}
}
None => {
shell.warn(format!(
"dependency `{}` specified in required-features as `{}/{}` \
does not exist.",
krate, krate, feature
"invalid feature `{}` in required-features of target `{}`: \
dependency `{}` does not exist",
fv, target_name, dep_name
))?;
}
}

View file

@ -274,7 +274,7 @@ fn transmit(
.map(|(feat, values)| {
(
feat.to_string(),
values.iter().map(|fv| fv.to_string(summary)).collect(),
values.iter().map(|fv| fv.to_string()).collect(),
)
})
.collect::<BTreeMap<String, Vec<String>>>();

View file

@ -340,7 +340,11 @@ fn add_pkg(
if dep.is_optional() {
// If the new feature resolver does not enable this
// optional dep, then don't use it.
if !node_features.contains(&dep.name_in_toml()) {
if !resolved_features.is_dep_activated(
package_id,
features_for,
dep.name_in_toml(),
) {
return false;
}
}
@ -542,10 +546,10 @@ fn add_feature_rec(
};
for fv in fvs {
match fv {
FeatureValue::Feature(fv_name) | FeatureValue::Crate(fv_name) => {
FeatureValue::Feature(dep_name) => {
let feat_index = add_feature(
graph,
*fv_name,
*dep_name,
Some(from),
package_index,
EdgeKind::Feature,
@ -553,13 +557,18 @@ fn add_feature_rec(
add_feature_rec(
graph,
resolve,
*fv_name,
*dep_name,
package_id,
feat_index,
package_index,
);
}
FeatureValue::CrateFeature(dep_name, fv_name) => {
FeatureValue::Crate { .. } => {}
FeatureValue::CrateFeature {
dep_name,
dep_feature,
explicit,
} => {
let dep_indexes = match graph.dep_name_map[&package_index].get(dep_name) {
Some(indexes) => indexes.clone(),
None => {
@ -569,14 +578,14 @@ fn add_feature_rec(
feature_name,
package_id,
dep_name,
fv_name
dep_feature
);
continue;
}
};
for (dep_index, is_optional) in dep_indexes {
let dep_pkg_id = graph.package_id_for_index(dep_index);
if is_optional {
if is_optional && !explicit {
// Activate the optional dep on self.
add_feature(
graph,
@ -586,9 +595,21 @@ fn add_feature_rec(
EdgeKind::Feature,
);
}
let feat_index =
add_feature(graph, *fv_name, Some(from), dep_index, EdgeKind::Feature);
add_feature_rec(graph, resolve, *fv_name, dep_pkg_id, feat_index, dep_index);
let feat_index = add_feature(
graph,
*dep_feature,
Some(from),
dep_index,
EdgeKind::Feature,
);
add_feature_rec(
graph,
resolve,
*dep_feature,
dep_pkg_id,
feat_index,
dep_index,
);
}
}
}

View file

@ -270,6 +270,7 @@ impl<'cfg> RegistryIndex<'cfg> {
'a: 'b,
{
let source_id = self.source_id;
let namespaced_features = self.config.cli_unstable().namespaced_features;
// First up actually parse what summaries we have available. If Cargo
// has run previously this will parse a Cargo-specific cache file rather
@ -294,7 +295,8 @@ impl<'cfg> RegistryIndex<'cfg> {
info!("failed to parse `{}` registry package: {}", name, e);
None
}
}))
})
.filter(move |is| is.summary.unstable_gate(namespaced_features).is_ok()))
}
fn load_summaries(
@ -723,8 +725,7 @@ impl IndexSummary {
.into_iter()
.map(|dep| dep.into_dep(source_id))
.collect::<CargoResult<Vec<_>>>()?;
let namespaced_features = false;
let mut summary = Summary::new(pkgid, deps, &features, links, namespaced_features)?;
let mut summary = Summary::new(pkgid, deps, &features, links)?;
summary.set_checksum(cksum);
Ok(IndexSummary {
summary,

View file

@ -262,7 +262,7 @@ pub struct TomlManifest {
build_dependencies: Option<BTreeMap<String, TomlDependency>>,
#[serde(rename = "build_dependencies")]
build_dependencies2: Option<BTreeMap<String, TomlDependency>>,
features: Option<BTreeMap<String, Vec<String>>>,
features: Option<BTreeMap<InternedString, Vec<InternedString>>>,
target: Option<BTreeMap<String, TomlPlatform>>,
replace: Option<BTreeMap<String, TomlDependency>>,
patch: Option<BTreeMap<String, BTreeMap<String, TomlDependency>>>,
@ -800,8 +800,6 @@ pub struct TomlProject {
autoexamples: Option<bool>,
autotests: Option<bool>,
autobenches: Option<bool>,
#[serde(rename = "namespaced-features")]
namespaced_features: Option<bool>,
#[serde(rename = "default-run")]
default_run: Option<String>,
@ -1190,26 +1188,15 @@ impl TomlManifest {
let exclude = project.exclude.clone().unwrap_or_default();
let include = project.include.clone().unwrap_or_default();
if project.namespaced_features.is_some() {
features.require(Feature::namespaced_features())?;
}
let empty_features = BTreeMap::new();
let summary_features = me
.features
.as_ref()
.map(|x| {
x.iter()
.map(|(k, v)| (k.as_str(), v.iter().collect()))
.collect()
})
.unwrap_or_else(BTreeMap::new);
let summary = Summary::new(
pkgid,
deps,
&summary_features,
me.features.as_ref().unwrap_or(&empty_features),
project.links.as_deref(),
project.namespaced_features.unwrap_or(false),
)?;
summary.unstable_gate(config.cli_unstable().namespaced_features)?;
let metadata = ManifestMetadata {
description: project.description.clone(),
@ -1526,6 +1513,10 @@ impl TomlManifest {
pub fn has_profiles(&self) -> bool {
self.profile.is_some()
}
pub fn features(&self) -> Option<&BTreeMap<InternedString, Vec<InternedString>>> {
self.features.as_ref()
}
}
/// Returns the name of the README file for a `TomlProject`.

View file

@ -191,29 +191,62 @@ lto = true
* Original issue: [#1286](https://github.com/rust-lang/cargo/issues/1286)
* Tracking Issue: [#5565](https://github.com/rust-lang/cargo/issues/5565)
Currently, it is not possible to have a feature and a dependency with the same
name in the manifest. If you set `namespaced-features` to `true`, the namespaces
for features and dependencies are separated. The effect of this is that, in the
feature requirements, dependencies have to be prefixed with `crate:`. Like this:
The `namespaced-features` option makes two changes to how features can be
specified:
* Features may now be defined with the same name as a dependency.
* Optional dependencies can be explicitly enabled in the `[features]` table
with the `crate:` prefix, which enables the dependency without enabling a
feature of the same name.
By default, an optional dependency `foo` will define a feature `foo =
["crate:foo"]` *unless* `crate:foo` is mentioned in any other feature, or the
`foo` feature is already defined. This helps prevent unnecessary boilerplate
of listing every optional dependency, but still allows you to override the
implicit feature.
This allows two use cases that were previously not possible:
* You can "hide" an optional dependency, so that external users cannot
explicitly enable that optional dependency.
* There is no longer a need to create "funky" feature names to work around the
restriction that features cannot shadow dependency names.
To enable namespaced-features, use the `-Z namespaced-features` command-line
flag.
An example of hiding an optional dependency:
```toml
[package]
namespaced-features = true
[dependencies]
regex = { version = "1.4.1", optional = true }
lazy_static = { version = "1.4.0", optional = true }
[features]
bar = ["crate:baz", "foo"]
foo = []
[dependencies]
baz = { version = "0.1", optional = true }
regex = ["crate:regex", "crate:lazy_static"]
```
To prevent unnecessary boilerplate from having to explicitly declare features
for each optional dependency, implicit features get created for any optional
dependencies where a feature of the same name is not defined. However, if
a feature of the same name as a dependency is defined, that feature must
include the dependency as a requirement, as `foo = ["crate:foo"]`.
In this example, the "regex" feature enables both `regex` and `lazy_static`.
The `lazy_static` feature does not exist, and a user cannot explicitly enable
it. This helps hide internal details of how your package is implemented.
An example of avoiding "funky" names:
```toml
[dependencies]
bigdecimal = "0.1"
chrono = "0.4"
num-bigint = "0.2"
serde = {version = "1.0", optional = true }
[features]
serde = ["crate:serde", "bigdecimal/serde", "chrono/serde", "num-bigint/serde"]
```
In this case, `serde` is a natural name to use for a feature, because it is
relevant to your exported API. However, previously you would need to use a
name like `serde1` to work around the naming limitation if you wanted to also
enable other features.
### Build-plan
* Tracking Issue: [#5579](https://github.com/rust-lang/cargo/issues/5579)

View file

@ -29,7 +29,7 @@ fn invalid1() {
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `bar` includes `baz` which is neither a dependency nor another feature
feature `bar` includes `baz` which is neither a dependency nor another feature
",
)
.run();
@ -48,12 +48,15 @@ fn invalid2() {
[features]
bar = ["baz"]
baz = []
[dependencies.bar]
path = "foo"
path = "bar"
"#,
)
.file("src/main.rs", "")
.file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0"))
.file("bar/src/lib.rs", "")
.build();
p.cargo("build")
@ -63,7 +66,7 @@ fn invalid2() {
[ERROR] failed to parse manifest at `[..]`
Caused by:
Features and dependencies cannot have the same name: `bar`
features and dependencies cannot have the same name: `bar`
",
)
.run();
@ -97,8 +100,8 @@ fn invalid3() {
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `bar` depends on `baz` which is not an optional dependency.
Consider adding `optional = true` to the dependency
feature `bar` includes `baz`, but `baz` is not an optional dependency
A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition.
",
)
.run();
@ -144,7 +147,7 @@ failed to select a version for `bar` which could resolve this conflict",
p.cargo("build --features test")
.with_status(101)
.with_stderr("error: Package `foo v0.0.1 ([..])` does not have these features: `test`")
.with_stderr("error: Package `foo v0.0.1 ([..])` does not have the feature `test`")
.run();
}
@ -174,7 +177,7 @@ fn invalid5() {
[ERROR] failed to parse manifest at `[..]`
Caused by:
Dev-dependencies are not allowed to be optional: `bar`
dev-dependencies are not allowed to be optional: `bar`
",
)
.run();
@ -205,7 +208,7 @@ fn invalid6() {
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `foo` requires a feature of `bar` which is not a dependency
feature `foo` includes `bar/baz`, but `bar` is not a dependency
",
)
.run();
@ -237,7 +240,7 @@ fn invalid7() {
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `foo` requires a feature of `bar` which is not a dependency
feature `foo` includes `bar/baz`, but `bar` is not a dependency
",
)
.run();
@ -1183,7 +1186,7 @@ fn dep_feature_in_cmd_line() {
// Trying to enable features of transitive dependencies is an error
p.cargo("build --features bar/some-feat")
.with_status(101)
.with_stderr("error: Package `foo v0.0.1 ([..])` does not have these features: `bar`")
.with_stderr("error: package `foo v0.0.1 ([..])` does not have a dependency named `bar`")
.run();
// Hierarchical feature specification should still be disallowed
@ -1959,9 +1962,14 @@ fn nonexistent_required_features() {
p.cargo("build --examples")
.with_stderr_contains(
"[WARNING] feature `not_present` is not present in [features] section.
[WARNING] feature `not_existing` does not exist in package `required_dependency v0.1.0`.
[WARNING] dependency `not_specified_dependency` specified in required-features as `not_specified_dependency/some_feature` does not exist.
"\
[WARNING] invalid feature `not_present` in required-features of target `ololo`: \
`not_present` is not present in [features] section
[WARNING] invalid feature `required_dependency/not_existing` in required-features \
of target `ololo`: feature `not_existing` does not exist in package \
`required_dependency v0.1.0`
[WARNING] invalid feature `not_specified_dependency/some_feature` in required-features \
of target `ololo`: dependency `not_specified_dependency` does not exist
",
)
.run();

View file

@ -96,6 +96,7 @@ fn inactive_target_optional() {
[features]
foo1 = ["dep1/f2"]
foo2 = ["dep2"]
"#,
)
.file(
@ -103,6 +104,7 @@ fn inactive_target_optional() {
r#"
fn main() {
if cfg!(feature="foo1") { println!("foo1"); }
if cfg!(feature="foo2") { println!("foo2"); }
if cfg!(feature="dep1") { println!("dep1"); }
if cfg!(feature="dep2") { println!("dep2"); }
if cfg!(feature="common") { println!("common"); }
@ -149,7 +151,7 @@ fn inactive_target_optional() {
.build();
p.cargo("run --all-features")
.with_stdout("foo1\ndep1\ndep2\ncommon\nf1\nf2\nf3\nf4\n")
.with_stdout("foo1\nfoo2\ndep1\ndep2\ncommon\nf1\nf2\nf3\nf4\n")
.run();
p.cargo("run --features dep1")
.with_stdout("dep1\nf1\n")
@ -166,7 +168,7 @@ fn inactive_target_optional() {
p.cargo("run -Zfeatures=itarget --all-features")
.masquerade_as_nightly_cargo()
.with_stdout("foo1\n")
.with_stdout("foo1\nfoo2\ndep1\ndep2\ncommon")
.run();
p.cargo("run -Zfeatures=itarget --features dep1")
.masquerade_as_nightly_cargo()

File diff suppressed because it is too large Load diff

View file

@ -48,7 +48,7 @@ fn z_flags_help() {
// Test that the output of `cargo -Z help` shows a different help screen with
// all the `-Z` flags.
cargo_process("-Z help")
.with_stdout_contains(" -Z unstable-options -- Allow the usage of unstable options")
.with_stdout_contains(" -Z unstable-options -- Allow the usage of unstable options")
.run();
}

View file

@ -290,7 +290,7 @@ fn other_member_from_current() {
p.cargo("run -p bar --features f1,f2")
.with_status(101)
.with_stderr("[ERROR] Package `foo[..]` does not have these features: `f2`")
.with_stderr("[ERROR] Package `foo[..]` does not have the feature `f2`")
.run();
p.cargo("run -p bar --features f1,f2 -Zpackage-features")

View file

@ -361,7 +361,7 @@ fn features_not_working() {
error: failed to parse manifest at `[..]`
Caused by:
Feature `default` includes `p1` which is neither a dependency nor another feature
feature `default` includes `p1` which is neither a dependency nor another feature
",
)
.run();