Auto merge of #8799 - ehuss:new-namespaced, r=alexcrichton

New namespaced features implementation.

This is a new implementation for namespaced features (#5565). See the `unstable.md` docs for a description of the new behavior. This is intended to fix several issues with the existing design, and to make it backwards compatible so that it does not require an opt-in flag.

This also includes tangentially-related changes to the new feature resolver. The changes are:

* `crate_name/feat_name` syntax will now always enable the feature `crate_name`, even if it is an inactive optional dependency (such as a dependency for another platform). The intent here is to have a certain amount of consistency whereby "features" are always activated, but individual crates will still not be activated.
* `--all-features` will now enable features for inactive optional dependencies. This is more consistent with `--features foo` enabling the `foo` feature, even when the `foo` dep is not activated.

I'm still very conflicted on how that should work, but I think it is better from a simplicity/consistency perspective. I still think it may be confusing if someone has a `cfg(some_dep)` in their code, and `some_dep` isn't built, it will error. The intent is that `cfg(accessible(some_dep))` will be the preferred method in the future, or using platform `cfg` expression like `cfg(windows)` to match whatever is in Cargo.toml.

Closes #8044
Closes #8046
Closes #8047
Closes #8316

## Questions
- For various reasons, I changed the way dependency conflict errors are collected. One slightly negative consequence is that it will raise an error for the first problem it detects (like a "missing feature"). Previously it would collect a list of all missing features and display all of them in the error message. Let me know if I should retain the old behavior. I think it will make the code more complicated and brittle, but it shouldn't be too hard (essentially `Requirements` will need to collect a list of errors, and then `resolve_features` would need to check if the list is non-empty, and then aggregate the errors).

- Should `cargo metadata` show the implicit features in the "features" table? Currently it does not, and I think that is probably best (it mirrors what is in `Cargo.toml`), but I could potentially see an argument to show how cargo sees the implicit features.
This commit is contained in:
bors 2020-10-27 19:58:34 +00:00
commit 963bfe4dd9
34 changed files with 1904 additions and 970 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 `dep:` prefix
Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
);

View File

@ -15,6 +15,6 @@ Deprecated, use `cargo metadata --no-deps` instead.\
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
let ws = args.workspace(config)?;
config.shell().print_json(&ws.current()?);
config.shell().print_json(&ws.current()?.serialized(config));
Ok(())
}

View File

@ -1034,7 +1034,7 @@ pub fn extern_args(
if unit
.pkg
.manifest()
.features()
.unstable_features()
.require(Feature::public_dependency())
.is_ok()
&& !dep.public

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

@ -25,7 +25,7 @@
//! use core::{Feature, Features};
//!
//! let feature = Feature::launch_into_space();
//! package.manifest().features().require(feature).chain_err(|| {
//! package.manifest().unstable_features().require(feature).chain_err(|| {
//! "launching Cargo into space right now is unstable and may result in \
//! unintended damage to your codebase, use with caution"
//! })?;
@ -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

@ -42,7 +42,7 @@ pub struct Manifest {
patch: HashMap<Url, Vec<Dependency>>,
workspace: WorkspaceConfig,
original: Rc<TomlManifest>,
features: Features,
unstable_features: Features,
edition: Edition,
im_a_teapot: Option<bool>,
default_run: Option<String>,
@ -371,7 +371,7 @@ impl Manifest {
replace: Vec<(PackageIdSpec, Dependency)>,
patch: HashMap<Url, Vec<Dependency>>,
workspace: WorkspaceConfig,
features: Features,
unstable_features: Features,
edition: Edition,
im_a_teapot: Option<bool>,
default_run: Option<String>,
@ -393,7 +393,7 @@ impl Manifest {
replace,
patch,
workspace,
features,
unstable_features,
edition,
original,
im_a_teapot,
@ -467,8 +467,9 @@ impl Manifest {
&self.workspace
}
pub fn features(&self) -> &Features {
&self.features
/// Unstable, nightly features that are enabled in this manifest.
pub fn unstable_features(&self) -> &Features {
&self.unstable_features
}
/// The style of resolver behavior to use, declared with the `resolver` field.
@ -487,7 +488,7 @@ impl Manifest {
pub fn feature_gate(&self) -> CargoResult<()> {
if self.im_a_teapot.is_some() {
self.features
self.unstable_features
.require(Feature::test_dummy_unstable())
.chain_err(|| {
anyhow::format_err!(
@ -578,7 +579,7 @@ impl VirtualManifest {
&self.warnings
}
pub fn features(&self) -> &Features {
pub fn unstable_features(&self) -> &Features {
&self.features
}

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;
@ -15,7 +15,6 @@ use curl::multi::{EasyHandle, Multi};
use lazycell::LazyCell;
use log::{debug, warn};
use semver::Version;
use serde::ser;
use serde::Serialize;
use crate::core::compiler::{CompileKind, RustcTargetData};
@ -23,7 +22,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};
@ -77,88 +76,31 @@ impl PartialOrd for Package {
/// A Package in a form where `Serialize` can be derived.
#[derive(Serialize)]
struct SerializedPackage<'a> {
name: &'a str,
version: &'a Version,
pub struct SerializedPackage {
name: InternedString,
version: Version,
id: PackageId,
license: Option<&'a str>,
license_file: Option<&'a str>,
description: Option<&'a str>,
license: Option<String>,
license_file: Option<String>,
description: Option<String>,
source: SourceId,
dependencies: &'a [Dependency],
targets: Vec<&'a Target>,
features: &'a FeatureMap,
manifest_path: &'a Path,
metadata: Option<&'a toml::Value>,
publish: Option<&'a Vec<String>>,
authors: &'a [String],
categories: &'a [String],
keywords: &'a [String],
readme: Option<&'a str>,
repository: Option<&'a str>,
homepage: Option<&'a str>,
documentation: Option<&'a str>,
edition: &'a str,
links: Option<&'a str>,
dependencies: Vec<Dependency>,
targets: Vec<Target>,
features: BTreeMap<InternedString, Vec<InternedString>>,
manifest_path: PathBuf,
metadata: Option<toml::Value>,
publish: Option<Vec<String>>,
authors: Vec<String>,
categories: Vec<String>,
keywords: Vec<String>,
readme: Option<String>,
repository: Option<String>,
homepage: Option<String>,
documentation: Option<String>,
edition: String,
links: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
metabuild: Option<&'a Vec<String>>,
}
impl ser::Serialize for Package {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let summary = self.manifest().summary();
let package_id = summary.package_id();
let manmeta = self.manifest().metadata();
let license = manmeta.license.as_deref();
let license_file = manmeta.license_file.as_deref();
let description = manmeta.description.as_deref();
let authors = manmeta.authors.as_ref();
let categories = manmeta.categories.as_ref();
let keywords = manmeta.keywords.as_ref();
let readme = manmeta.readme.as_deref();
let repository = manmeta.repository.as_deref();
let homepage = manmeta.homepage.as_ref().map(String::as_ref);
let documentation = manmeta.documentation.as_ref().map(String::as_ref);
// Filter out metabuild targets. They are an internal implementation
// detail that is probably not relevant externally. There's also not a
// real path to show in `src_path`, and this avoids changing the format.
let targets: Vec<&Target> = self
.manifest()
.targets()
.iter()
.filter(|t| t.src_path().is_path())
.collect();
SerializedPackage {
name: &*package_id.name(),
version: package_id.version(),
id: package_id,
license,
license_file,
description,
source: summary.source_id(),
dependencies: summary.dependencies(),
targets,
features: summary.features(),
manifest_path: self.manifest_path(),
metadata: self.manifest().custom_metadata(),
authors,
categories,
keywords,
readme,
repository,
homepage,
documentation,
edition: &self.manifest().edition().to_string(),
links: self.manifest().links(),
metabuild: self.manifest().metabuild(),
publish: self.publish().as_ref(),
}
.serialize(s)
}
metabuild: Option<Vec<String>>,
}
impl Package {
@ -255,6 +197,69 @@ impl Package {
pub fn include_lockfile(&self) -> bool {
self.targets().iter().any(|t| t.is_example() || t.is_bin())
}
pub fn serialized(&self, config: &Config) -> SerializedPackage {
let summary = self.manifest().summary();
let package_id = summary.package_id();
let manmeta = self.manifest().metadata();
// Filter out metabuild targets. They are an internal implementation
// detail that is probably not relevant externally. There's also not a
// real path to show in `src_path`, and this avoids changing the format.
let targets: Vec<Target> = self
.manifest()
.targets()
.iter()
.filter(|t| t.src_path().is_path())
.cloned()
.collect();
let features = if config.cli_unstable().namespaced_features {
// Convert Vec<FeatureValue> to Vec<InternedString>
summary
.features()
.iter()
.map(|(k, v)| {
(
*k,
v.iter()
.map(|fv| InternedString::new(&fv.to_string()))
.collect(),
)
})
.collect()
} else {
self.manifest()
.original()
.features()
.cloned()
.unwrap_or_default()
};
SerializedPackage {
name: package_id.name(),
version: package_id.version().clone(),
id: package_id,
license: manmeta.license.clone(),
license_file: manmeta.license_file.clone(),
description: manmeta.description.clone(),
source: summary.source_id(),
dependencies: summary.dependencies().to_vec(),
targets,
features,
manifest_path: self.manifest_path().to_path_buf(),
metadata: self.manifest().custom_metadata().cloned(),
authors: manmeta.authors.clone(),
categories: manmeta.categories.clone(),
keywords: manmeta.keywords.clone(),
readme: manmeta.readme.clone(),
repository: manmeta.repository.clone(),
homepage: manmeta.homepage.clone(),
documentation: manmeta.documentation.clone(),
edition: self.manifest().edition().to_string(),
links: self.manifest().links().map(|s| s.to_owned()),
metabuild: self.manifest().metabuild().cloned(),
publish: self.publish().as_ref().cloned(),
}
}
}
impl fmt::Display for Package {

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
// dep_name/feat_name` where `dep_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.has_dep_prefix() {
return Err(ActivateError::Fatal(anyhow::format_err!(
"feature value `{}` is not allowed to use explicit `dep:` 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_dep_feature(
&mut self,
package: InternedString,
feat: InternedString,
dep_prefix: 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 !dep_prefix
&& 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::Dep { dep_name } => self.require_dependency(*dep_name),
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_prefix,
} => self.require_dep_feature(*dep_name, *dep_feature, *dep_prefix)?,
};
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 \"dep:\" \
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 `dep:` 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

@ -108,76 +108,94 @@ pub(super) fn activation_error(
let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect();
conflicting_activations.sort_unstable();
let (links_errors, mut other_errors): (Vec<_>, Vec<_>) = conflicting_activations
.drain(..)
.rev()
.partition(|&(_, r)| r.is_links());
// This is reversed to show the newest versions first. I don't know if there is
// a strong reason to do this, but that is how the code previously worked
// (see https://github.com/rust-lang/cargo/pull/5037) and I don't feel like changing it.
conflicting_activations.reverse();
// Flag used for grouping all semver errors together.
let mut has_semver = false;
for &(p, r) in links_errors.iter() {
if let ConflictReason::Links(ref link) = *r {
msg.push_str("\n\nthe package `");
msg.push_str(&*dep.package_name());
msg.push_str("` links to the native library `");
msg.push_str(link);
msg.push_str("`, but it conflicts with a previous package which links to `");
msg.push_str(link);
msg.push_str("` as well:\n");
for (p, r) in &conflicting_activations {
match r {
ConflictReason::Semver => {
has_semver = true;
}
ConflictReason::Links(link) => {
msg.push_str("\n\nthe package `");
msg.push_str(&*dep.package_name());
msg.push_str("` links to the native library `");
msg.push_str(link);
msg.push_str("`, but it conflicts with a previous package which links to `");
msg.push_str(link);
msg.push_str("` as well:\n");
msg.push_str(&describe_path(&cx.parents.path_to_bottom(p)));
}
ConflictReason::MissingFeatures(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");
// p == parent so the full path is redundant.
}
ConflictReason::RequiredDependencyAsFeature(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 a required dependency with that name, \
but only optional dependencies can be used as features.\n",
);
// 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 \"dep:\" \
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);
}
ConflictReason::PubliclyExports(pkg_id) => {
// TODO: This needs to be implemented.
unimplemented!("pub exp {:?}", pkg_id);
}
}
msg.push_str(&describe_path(&cx.parents.path_to_bottom(p)));
}
let (features_errors, mut other_errors): (Vec<_>, Vec<_>) = other_errors
.drain(..)
.partition(|&(_, r)| r.is_missing_features());
for &(p, r) in features_errors.iter() {
if let ConflictReason::MissingFeatures(ref features) = *r {
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");
if has_semver {
// Group these errors together.
msg.push_str("\n\nall possible versions conflict with previously selected packages.");
for (p, r) in &conflicting_activations {
if let ConflictReason::Semver = r {
msg.push_str("\n\n previously selected ");
msg.push_str(&describe_path(&cx.parents.path_to_bottom(p)));
}
}
// p == parent so the full path is redundant.
}
let (required_dependency_as_features_errors, other_errors): (Vec<_>, Vec<_>) = other_errors
.drain(..)
.partition(|&(_, r)| r.is_required_dependency_as_features());
for &(p, r) in required_dependency_as_features_errors.iter() {
if let ConflictReason::RequiredDependencyAsFeatures(ref features) = *r {
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 a required dependency with that name, \
but only optional dependencies can be used as features.\n",
);
}
// p == parent so the full path is redundant.
}
if !other_errors.is_empty() {
msg.push_str(
"\n\nall possible versions conflict with \
previously selected packages.",
);
}
for &(p, _) in other_errors.iter() {
msg.push_str("\n\n previously selected ");
msg.push_str(&describe_path(&cx.parents.path_to_bottom(p)));
}
msg.push_str("\n\nfailed to select a version for `");

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,
}
@ -224,6 +234,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`).
@ -240,7 +274,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;
@ -264,6 +298,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)>,
@ -291,7 +327,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,
});
}
@ -303,6 +341,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)?;
@ -312,7 +351,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,
})
}
@ -368,7 +409,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
// 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::CrateFeature` branch, and then immediately
// `FeatureValue::DepFeature` branch, and then immediately
// recurse into that optional dependency. This also holds true for
// features that enable other features.
return Ok(());
@ -399,9 +440,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::Dep { 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 {
@ -413,7 +457,11 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
}
}
}
FeatureValue::CrateFeature(dep_name, dep_feature) => {
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_prefix,
} => {
// 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 {
@ -421,13 +469,20 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
continue;
}
if dep.is_optional() {
// Activate the crate on self.
let fv = FeatureValue::Crate(*dep_name);
// Activate the dependency on self.
let fv = FeatureValue::Dep {
dep_name: *dep_name,
};
self.activate_fv(pkg_id, &fv, for_host)?;
if !dep_prefix {
// 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)?;
}
}
@ -481,7 +536,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) {
@ -499,28 +554,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) {
@ -609,3 +652,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 `dep:` 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 `dep:` 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 `dep:` 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.has_dep_prefix());
// 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 {
Dep { dep_name }
| DepFeature {
dep_name,
dep_prefix: 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 = Dep {
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("dep:") {
bail!(
"feature named `{}` is not allowed to start with `dep:`",
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) | Dep { dep_name, .. } | DepFeature { dep_name, .. } => {
dep_map.get(dep_name)
}
}
};
@ -219,198 +236,165 @@ 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 `dep:{}` 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",
Dep { 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",
DepFeature { 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 {
Dep { dep_name } | DepFeature { 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 `dep:{}` 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 `dep:dep_name` syntax.
Dep { dep_name: InternedString },
/// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
DepFeature {
dep_name: InternedString,
dep_feature: InternedString,
/// If this is true, then the feature used the `dep:` prefix, which
/// prevents enabling the feature named `dep_name`.
dep_prefix: 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, dep_prefix) = if let Some(dep) = dep.strip_prefix("dep:") {
(dep, true)
} else {
c.to_string()
(dep, false)
};
FeatureValue::DepFeature {
dep_name: InternedString::new(dep),
dep_feature: InternedString::new(dep_feat),
dep_prefix,
}
}
None => {
if let Some(dep_name) = feature.strip_prefix("dep:") {
FeatureValue::Dep {
dep_name: InternedString::new(dep_name),
}
} else {
FeatureValue::Feature(feature)
}
}
CrateFeature(ref c, ref f) => [c.as_ref(), f.as_ref()].join("/"),
}
}
/// Returns `true` if this feature explicitly used `dep:` syntax.
pub fn has_dep_prefix(&self) -> bool {
matches!(self, FeatureValue::Dep{..} | FeatureValue::DepFeature{dep_prefix: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),
Dep { dep_name } => write!(f, "dep:{}", dep_name),
DepFeature {
dep_name,
dep_feature,
dep_prefix: true,
} => write!(f, "dep:{}/{}", dep_name, dep_feature),
DepFeature {
dep_name,
dep_feature,
dep_prefix: false,
} => write!(f, "{}/{}", dep_name, dep_feature),
}
}
}

View File

@ -625,10 +625,11 @@ impl<'cfg> Workspace<'cfg> {
Ok(())
}
pub fn features(&self) -> &Features {
/// Returns the unstable nightly-only features enabled via `cargo-features` in the manifest.
pub fn unstable_features(&self) -> &Features {
match self.root_maybe() {
MaybePackage::Package(p) => p.manifest().features(),
MaybePackage::Virtual(vm) => vm.features(),
MaybePackage::Package(p) => p.manifest().unstable_features(),
MaybePackage::Virtual(vm) => vm.unstable_features(),
}
}

View File

@ -34,7 +34,12 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
return rm_rf(&target_dir.into_path_unlocked(), config);
}
let profiles = Profiles::new(ws.profiles(), config, opts.requested_profile, ws.features())?;
let profiles = Profiles::new(
ws.profiles(),
config,
opts.requested_profile,
ws.unstable_features(),
)?;
if opts.profile_specified {
// After parsing profiles we know the dir-name of the profile, if a profile

View File

@ -424,7 +424,7 @@ pub fn create_bcx<'a, 'cfg>(
ws.profiles(),
config,
build_config.requested_profile,
ws.features(),
ws.unstable_features(),
)?;
profiles.validate_packages(
ws.profiles(),
@ -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,58 @@ 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::Dep { .. }
| FeatureValue::DepFeature {
dep_prefix: true, ..
} => {
anyhow::bail!(
"invalid feature `{}` in required-features of target `{}`: \
`dep:` prefixed feature values are not allowed in required-features",
fv,
target_name
);
}
// Handling of dependent_crate/dependent_crate_feature syntax
FeatureValue::CrateFeature(krate, feature) => {
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_prefix: 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

@ -1,5 +1,6 @@
use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::dependency::DepKind;
use crate::core::package::SerializedPackage;
use crate::core::resolver::{HasDevUnits, Resolve, ResolveOpts};
use crate::core::{Dependency, Package, PackageId, Workspace};
use crate::ops::{self, Packages};
@ -32,8 +33,9 @@ pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> Cargo
VERSION
);
}
let config = ws.config();
let (packages, resolve) = if opt.no_deps {
let packages = ws.members().cloned().collect();
let packages = ws.members().map(|pkg| pkg.serialized(config)).collect();
(packages, None)
} else {
let (packages, resolve) = build_resolve_graph(ws, opt)?;
@ -56,7 +58,7 @@ pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> Cargo
/// See cargo-metadata.adoc for detailed documentation of the format.
#[derive(Serialize)]
pub struct ExportInfo {
packages: Vec<Package>,
packages: Vec<SerializedPackage>,
workspace_members: Vec<PackageId>,
resolve: Option<MetadataResolve>,
target_directory: PathBuf,
@ -105,7 +107,7 @@ impl From<&Dependency> for DepKindInfo {
fn build_resolve_graph(
ws: &Workspace<'_>,
metadata_opts: &OutputMetadataOptions,
) -> CargoResult<(Vec<Package>, MetadataResolve)> {
) -> CargoResult<(Vec<SerializedPackage>, MetadataResolve)> {
// TODO: Without --filter-platform, features are being resolved for `host` only.
// How should this work?
let requested_kinds =
@ -153,9 +155,11 @@ fn build_resolve_graph(
);
}
// Get a Vec of Packages.
let config = ws.config();
let actual_packages = package_map
.into_iter()
.filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
.map(|pkg| pkg.serialized(config))
.collect();
let mr = MetadataResolve {

View File

@ -681,7 +681,7 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
let rustc_args = if pkg
.manifest()
.features()
.unstable_features()
.require(Feature::public_dependency())
.is_ok()
{

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

@ -352,7 +352,9 @@ pub fn resolve_with_previous<'cfg>(
registry,
&try_to_use,
Some(ws.config()),
ws.features().require(Feature::public_dependency()).is_ok(),
ws.unstable_features()
.require(Feature::public_dependency())
.is_ok(),
)?;
resolved.register_used_patches(&registry.patches());
if register_patches {

View File

@ -305,7 +305,7 @@ fn add_pkg(
};
let node = Node::Package {
package_id,
features: node_features.clone(),
features: node_features,
kind: node_kind,
};
if let Some(idx) = graph.index.get(&node) {
@ -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::Dep { .. } => {}
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_prefix,
} => {
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 && !dep_prefix {
// 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

@ -20,7 +20,7 @@ use std::mem;
use std::path::Path;
use std::str;
fn make_crate_prefix(name: &str) -> String {
fn make_dep_prefix(name: &str) -> String {
match name.len() {
1 => String::from("1"),
2 => String::from("2"),
@ -274,7 +274,7 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
{
write!(url, "/{}/{}/download", CRATE_TEMPLATE, VERSION_TEMPLATE).unwrap();
}
let prefix = make_crate_prefix(&*pkg.name());
let prefix = make_dep_prefix(&*pkg.name());
let url = url
.replace(CRATE_TEMPLATE, &*pkg.name())
.replace(VERSION_TEMPLATE, &pkg.version().to_string())
@ -341,15 +341,15 @@ impl<'cfg> Drop for RemoteRegistry<'cfg> {
#[cfg(test)]
mod tests {
use super::make_crate_prefix;
use super::make_dep_prefix;
#[test]
fn crate_prefix() {
assert_eq!(make_crate_prefix("a"), "1");
assert_eq!(make_crate_prefix("ab"), "2");
assert_eq!(make_crate_prefix("abc"), "3/a");
assert_eq!(make_crate_prefix("Abc"), "3/A");
assert_eq!(make_crate_prefix("AbCd"), "Ab/Cd");
assert_eq!(make_crate_prefix("aBcDe"), "aB/cD");
fn dep_prefix() {
assert_eq!(make_dep_prefix("a"), "1");
assert_eq!(make_dep_prefix("ab"), "2");
assert_eq!(make_dep_prefix("abc"), "3/a");
assert_eq!(make_dep_prefix("Abc"), "3/A");
assert_eq!(make_dep_prefix("AbCd"), "Ab/Cd");
assert_eq!(make_dep_prefix("aBcDe"), "aB/cD");
}
}

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 `dep:` prefix, which enables the dependency without enabling a
feature of the same name.
By default, an optional dependency `foo` will define a feature `foo =
["dep:foo"]` *unless* `dep: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 = ["dep:regex", "dep: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 = ["dep: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
@ -1353,277 +1356,6 @@ fn many_cli_features_comma_and_space_delimited() {
.run();
}
#[cargo_test]
fn namespaced_invalid_feature() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["namespaced-features"]
[project]
name = "foo"
version = "0.0.1"
authors = []
namespaced-features = true
[features]
bar = ["baz"]
"#,
)
.file("src/main.rs", "")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `bar` includes `baz` which is not defined as a feature
",
)
.run();
}
#[cargo_test]
fn namespaced_invalid_dependency() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["namespaced-features"]
[project]
name = "foo"
version = "0.0.1"
authors = []
namespaced-features = true
[features]
bar = ["crate:baz"]
"#,
)
.file("src/main.rs", "")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `bar` includes `crate:baz` which is not a known dependency
",
)
.run();
}
#[cargo_test]
fn namespaced_non_optional_dependency() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["namespaced-features"]
[project]
name = "foo"
version = "0.0.1"
authors = []
namespaced-features = true
[features]
bar = ["crate:baz"]
[dependencies]
baz = "0.1"
"#,
)
.file("src/main.rs", "")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `bar` includes `crate:baz` which is not an optional dependency.
Consider adding `optional = true` to the dependency
",
)
.run();
}
#[cargo_test]
fn namespaced_implicit_feature() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["namespaced-features"]
[project]
name = "foo"
version = "0.0.1"
authors = []
namespaced-features = true
[features]
bar = ["baz"]
[dependencies]
baz = { version = "0.1", optional = true }
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build").masquerade_as_nightly_cargo().run();
}
#[cargo_test]
fn namespaced_shadowed_dep() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["namespaced-features"]
[project]
name = "foo"
version = "0.0.1"
authors = []
namespaced-features = true
[features]
baz = []
[dependencies]
baz = { version = "0.1", optional = true }
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build").masquerade_as_nightly_cargo().with_status(101).with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `baz` includes the optional dependency of the same name, but this is left implicit in the features included by this feature.
Consider adding `crate:baz` to this feature's requirements.
",
)
.run();
}
#[cargo_test]
fn namespaced_shadowed_non_optional() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["namespaced-features"]
[project]
name = "foo"
version = "0.0.1"
authors = []
namespaced-features = true
[features]
baz = []
[dependencies]
baz = "0.1"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build").masquerade_as_nightly_cargo().with_status(101).with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `baz` includes the dependency of the same name, but this is left implicit in the features included by this feature.
Additionally, the dependency must be marked as optional to be included in the feature definition.
Consider adding `crate:baz` to this feature's requirements and marking the dependency as `optional = true`
",
)
.run();
}
#[cargo_test]
fn namespaced_implicit_non_optional() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["namespaced-features"]
[project]
name = "foo"
version = "0.0.1"
authors = []
namespaced-features = true
[features]
bar = ["baz"]
[dependencies]
baz = "0.1"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build").masquerade_as_nightly_cargo().with_status(101).with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
Feature `bar` includes `baz` which is not defined as a feature.
A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition
",
).run();
}
#[cargo_test]
fn namespaced_same_name() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["namespaced-features"]
[project]
name = "foo"
version = "0.0.1"
authors = []
namespaced-features = true
[features]
baz = ["crate:baz"]
[dependencies]
baz = { version = "0.1", optional = true }
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build").masquerade_as_nightly_cargo().run();
}
#[cargo_test]
fn only_dep_is_optional() {
Package::new("bar", "0.1.0").publish();
@ -2230,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()

View File

@ -0,0 +1,999 @@
//! Tests for namespaced features.
use cargo_test_support::project;
use cargo_test_support::registry::{Dependency, Package};
#[cargo_test]
fn gated() {
// Need namespaced-features to use `dep:` syntax.
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", optional = true }
[features]
foo = ["dep:bar"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
Caused by:
namespaced features with the `dep:` prefix are only allowed on the nightly channel \
and requires the `-Z namespaced-features` flag on the command-line
",
)
.run();
}
#[cargo_test]
fn dependency_gate_ignored() {
// Dependencies with `dep:` features are ignored in the registry if not on nightly.
Package::new("baz", "1.0.0").publish();
Package::new("bar", "1.0.0")
.add_dep(Dependency::new("baz", "1.0").optional(true))
.feature("feat", &["dep:baz"])
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = "1.0"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[UPDATING] [..]
[ERROR] no matching package named `bar` found
location searched: registry `https://github.com/rust-lang/crates.io-index`
required by package `foo v0.1.0 ([..]/foo)`
",
)
.run();
// Publish a version without namespaced features, it should ignore 1.0.0
// an use this instead.
Package::new("bar", "1.0.1")
.add_dep(Dependency::new("baz", "1.0").optional(true))
.feature("feat", &["baz"])
.publish();
p.cargo("check")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar [..]
[CHECKING] bar v1.0.1
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn dependency_with_crate_syntax() {
// Registry dependency uses dep: syntax.
Package::new("baz", "1.0.0").publish();
Package::new("bar", "1.0.0")
.add_dep(Dependency::new("baz", "1.0").optional(true))
.feature("feat", &["dep:baz"])
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = {version="1.0", features=["feat"]}
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] [..]
[DOWNLOADED] [..]
[CHECKING] baz v1.0.0
[CHECKING] bar v1.0.0
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn namespaced_invalid_feature() {
// Specifies a feature that doesn't exist.
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[features]
bar = ["baz"]
"#,
)
.file("src/main.rs", "")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
feature `bar` includes `baz` which is neither a dependency nor another feature
",
)
.run();
}
#[cargo_test]
fn namespaced_invalid_dependency() {
// Specifies a dep:name that doesn't exist.
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
[features]
bar = ["dep:baz"]
"#,
)
.file("src/main.rs", "")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
feature `bar` includes `dep:baz`, but `baz` is not listed as a dependency
",
)
.run();
}
#[cargo_test]
fn namespaced_non_optional_dependency() {
// Specifies a dep:name for a dependency that is not optional.
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
[features]
bar = ["dep:baz"]
[dependencies]
baz = "0.1"
"#,
)
.file("src/main.rs", "")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
feature `bar` includes `dep: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();
}
#[cargo_test]
fn namespaced_implicit_feature() {
// Backwards-compatible with old syntax.
Package::new("baz", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
[features]
bar = ["baz"]
[dependencies]
baz = { version = "0.1", optional = true }
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[CHECKING] foo v0.0.1 [..]
[FINISHED] [..]
",
)
.run();
p.cargo("check -Z namespaced-features --features baz")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.1.0 [..]
[CHECKING] baz v0.1.0
[CHECKING] foo v0.0.1 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn namespaced_shadowed_dep() {
// An optional dependency is not listed in the features table, and its
// implicit feature is overridden.
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
[features]
baz = []
[dependencies]
baz = { version = "0.1", optional = true }
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
optional dependency `baz` is not included in any feature
Make sure that `dep:baz` is included in one of features in the [features] table.
",
)
.run();
}
#[cargo_test]
fn namespaced_shadowed_non_optional() {
// Able to specify a feature with the same name as a required dependency.
Package::new("baz", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
[features]
baz = []
[dependencies]
baz = "0.1"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
.run();
}
#[cargo_test]
fn namespaced_implicit_non_optional() {
// Includes a non-optional dependency in [features] table.
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
[features]
bar = ["baz"]
[dependencies]
baz = "0.1"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build -Z namespaced-features").masquerade_as_nightly_cargo().with_status(101).with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
Caused by:
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();
}
#[cargo_test]
fn namespaced_same_name() {
// Explicitly listing an optional dependency in the [features] table.
Package::new("baz", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
[features]
baz = ["dep:baz"]
[dependencies]
baz = { version = "0.1", optional = true }
"#,
)
.file(
"src/main.rs",
r#"
fn main() {
if cfg!(feature="baz") { println!("baz"); }
}
"#,
)
.build();
p.cargo("run -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[COMPILING] foo v0.0.1 [..]
[FINISHED] [..]
[RUNNING] [..]
",
)
.with_stdout("")
.run();
p.cargo("run -Z namespaced-features --features baz")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.1.0 [..]
[COMPILING] baz v0.1.0
[COMPILING] foo v0.0.1 [..]
[FINISHED] [..]
[RUNNING] [..]
",
)
.with_stdout("baz")
.run();
}
#[cargo_test]
fn no_implicit_feature() {
// Using `dep:` will not create an implicit feature.
Package::new("regex", "1.0.0").publish();
Package::new("lazy_static", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
regex = { version = "1.0", optional = true }
lazy_static = { version = "1.0", optional = true }
[features]
regex = ["dep:regex", "dep:lazy_static"]
"#,
)
.file(
"src/main.rs",
r#"
fn main() {
if cfg!(feature = "regex") { println!("regex"); }
if cfg!(feature = "lazy_static") { println!("lazy_static"); }
}
"#,
)
.build();
p.cargo("run -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[COMPILING] foo v0.1.0 [..]
[FINISHED] [..]
[RUNNING] `target/debug/foo[EXE]`
",
)
.with_stdout("")
.run();
p.cargo("run -Z namespaced-features --features regex")
.masquerade_as_nightly_cargo()
.with_stderr_unordered(
"\
[DOWNLOADING] crates ...
[DOWNLOADED] regex v1.0.0 [..]
[DOWNLOADED] lazy_static v1.0.0 [..]
[COMPILING] regex v1.0.0
[COMPILING] lazy_static v1.0.0
[COMPILING] foo v0.1.0 [..]
[FINISHED] [..]
[RUNNING] `target/debug/foo[EXE]`
",
)
.with_stdout("regex")
.run();
p.cargo("run -Z namespaced-features --features lazy_static")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[ERROR] Package `foo v0.1.0 [..]` does not have feature `lazy_static`. \
It has an optional dependency with that name, but that dependency uses the \"dep:\" \
syntax in the features table, so it does not have an implicit feature with that name.
",
)
.with_status(101)
.run();
}
#[cargo_test]
fn crate_feature_explicit() {
// dep:name/feature syntax shouldn't set implicit feature.
Package::new("bar", "1.0.0")
.file(
"src/lib.rs",
r#"
#[cfg(not(feature="feat"))]
compile_error!{"feat missing"}
"#,
)
.feature("feat", &[])
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = {version = "1.0", optional=true}
[features]
f1 = ["dep:bar/feat"]
"#,
)
.file(
"src/lib.rs",
r#"
#[cfg(not(feature="f1"))]
compile_error!{"f1 missing"}
#[cfg(feature="bar")]
compile_error!{"bar should not be set"}
"#,
)
.build();
p.cargo("check -Z namespaced-features --features f1")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 [..]
[CHECKING] bar v1.0.0
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn crate_syntax_bad_name() {
// "dep:bar" = []
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version="1.0", optional=true }
[features]
"dep:bar" = []
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features --features dep:bar")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at [..]/foo/Cargo.toml`
Caused by:
feature named `dep:bar` is not allowed to start with `dep:`
",
)
.run();
}
#[cargo_test]
fn crate_syntax_in_dep() {
// features = ["dep:baz"]
Package::new("baz", "1.0.0").publish();
Package::new("bar", "1.0.0")
.add_dep(Dependency::new("baz", "1.0").optional(true))
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", features = ["dep:baz"] }
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[UPDATING] [..]
[ERROR] feature value `dep:baz` is not allowed to use explicit `dep:` syntax
",
)
.run();
}
#[cargo_test]
fn crate_syntax_cli() {
// --features dep:bar
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", optional=true }
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features --features dep:bar")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[UPDATING] [..]
[ERROR] feature value `dep:bar` is not allowed to use explicit `dep:` syntax
",
)
.run();
}
#[cargo_test]
fn crate_required_features() {
// required-features = ["dep:bar"]
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", optional=true }
[[bin]]
name = "foo"
required-features = ["dep:bar"]
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[UPDATING] [..]
[ERROR] invalid feature `dep:bar` in required-features of target `foo`: \
`dep:` prefixed feature values are not allowed in required-features
",
)
.run();
}
#[cargo_test]
fn json_exposed() {
// Checks that the implicit dep: values are exposed in JSON.
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", optional=true }
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("metadata -Z namespaced-features --no-deps")
.masquerade_as_nightly_cargo()
.with_json(
r#"
{
"packages": [
{
"name": "foo",
"version": "0.1.0",
"id": "foo 0.1.0 [..]",
"license": null,
"license_file": null,
"description": null,
"homepage": null,
"documentation": null,
"source": null,
"dependencies": "{...}",
"targets": "{...}",
"features": {
"bar": ["dep:bar"]
},
"manifest_path": "[..]foo/Cargo.toml",
"metadata": null,
"publish": null,
"authors": [],
"categories": [],
"keywords": [],
"readme": null,
"repository": null,
"edition": "2015",
"links": null
}
],
"workspace_members": "{...}",
"resolve": null,
"target_directory": "[..]foo/target",
"version": 1,
"workspace_root": "[..]foo",
"metadata": null
}
"#,
)
.run();
}
#[cargo_test]
fn crate_feature_with_explicit() {
// crate_name/feat_name syntax where crate_name already has a feature defined.
// NOTE: I don't know if this is actually ideal behavior.
Package::new("bar", "1.0.0")
.feature("bar_feat", &[])
.file(
"src/lib.rs",
r#"
#[cfg(not(feature="bar_feat"))]
compile_error!("bar_feat is not enabled");
"#,
)
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version="1.0", optional = true }
[features]
f1 = ["bar/bar_feat"]
bar = ["dep:bar", "f2"]
f2 = []
"#,
)
.file(
"src/lib.rs",
r#"
#[cfg(not(feature="bar"))]
compile_error!("bar should be enabled");
#[cfg(not(feature="f2"))]
compile_error!("f2 should be enabled");
"#,
)
.build();
p.cargo("check -Z namespaced-features --features f1")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 [..]
[CHECKING] bar v1.0.0
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn optional_explicit_without_crate() {
// "feat" syntax when there is no implicit "feat" feature because it is
// explicitly listed elsewhere.
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", optional = true }
[features]
feat1 = ["dep:bar"]
feat2 = ["bar"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at [..]
Caused by:
feature `feat2` includes `bar`, but `bar` is an optional dependency without an implicit feature
Use `dep:bar` to enable the dependency.
",
)
.run();
}
#[cargo_test]
fn tree() {
Package::new("baz", "1.0.0").publish();
Package::new("bar", "1.0.0")
.add_dep(Dependency::new("baz", "1.0").optional(true))
.feature("feat1", &["dep:baz"])
.feature("feat2", &[])
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", features = ["feat1"], optional=true }
[features]
a = ["bar/feat2"]
b = ["dep:bar/feat2"]
bar = ["dep:bar"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("tree -e features -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout("foo v0.1.0 ([ROOT]/foo)")
.run();
p.cargo("tree -e features --features a -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
bar feature \"default\"
bar v1.0.0
baz feature \"default\"
baz v1.0.0
bar feature \"feat1\"
bar v1.0.0 (*)
",
)
.run();
p.cargo("tree -e features --features a -i bar -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
bar v1.0.0
bar feature \"default\"
foo v0.1.0 ([ROOT]/foo)
foo feature \"a\" (command-line)
foo feature \"bar\"
foo feature \"a\" (command-line)
foo feature \"default\" (command-line)
bar feature \"feat1\"
foo v0.1.0 ([ROOT]/foo) (*)
bar feature \"feat2\"
foo feature \"a\" (command-line)
",
)
.run();
p.cargo("tree -e features --features b -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
bar feature \"default\"
bar v1.0.0
baz feature \"default\"
baz v1.0.0
bar feature \"feat1\"
bar v1.0.0 (*)
",
)
.run();
p.cargo("tree -e features --features b -i bar -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
bar v1.0.0
bar feature \"default\"
foo v0.1.0 ([ROOT]/foo)
foo feature \"b\" (command-line)
foo feature \"default\" (command-line)
bar feature \"feat1\"
foo v0.1.0 ([ROOT]/foo) (*)
bar feature \"feat2\"
foo feature \"b\" (command-line)
",
)
.run();
p.cargo("tree -e features --features bar -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
bar feature \"default\"
bar v1.0.0
baz feature \"default\"
baz v1.0.0
bar feature \"feat1\"
bar v1.0.0 (*)
",
)
.run();
p.cargo("tree -e features --features bar -i bar -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
bar v1.0.0
bar feature \"default\"
foo v0.1.0 ([ROOT]/foo)
foo feature \"bar\" (command-line)
foo feature \"default\" (command-line)
bar feature \"feat1\"
foo v0.1.0 ([ROOT]/foo) (*)
",
)
.run();
}

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

@ -45,6 +45,7 @@ mod edition;
mod error;
mod features;
mod features2;
mod features_namespaced;
mod fetch;
mod fix;
mod freshness;

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();