mirror of
https://github.com/rust-lang/cargo
synced 2024-10-02 22:13:47 +00:00
New namespaced features implementation.
This commit is contained in:
parent
dca0b11566
commit
bcfdf9fbad
|
@ -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]`)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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")),
|
||||
|
|
|
@ -35,13 +35,14 @@ pub fn main(config: &mut Config) -> CliResult {
|
|||
"
|
||||
Available unstable (nightly-only) flags:
|
||||
|
||||
-Z avoid-dev-deps -- Avoid installing dev-dependencies if possible
|
||||
-Z minimal-versions -- Install minimal dependency versions instead of maximum
|
||||
-Z no-index-update -- Do not update the registry, avoids a network request for benchmarking
|
||||
-Z unstable-options -- Allow the usage of unstable options
|
||||
-Z timings -- Display concurrency information
|
||||
-Z doctest-xcompile -- Compile and run doctests for non-host target using runner config
|
||||
-Z terminal-width -- Provide a terminal width to rustc for error truncation
|
||||
-Z avoid-dev-deps -- Avoid installing dev-dependencies if possible
|
||||
-Z minimal-versions -- Install minimal dependency versions instead of maximum
|
||||
-Z no-index-update -- Do not update the registry, avoids a network request for benchmarking
|
||||
-Z unstable-options -- Allow the usage of unstable options
|
||||
-Z timings -- Display concurrency information
|
||||
-Z doctest-xcompile -- Compile and run doctests for non-host target using runner config
|
||||
-Z terminal-width -- Provide a terminal width to rustc for error truncation
|
||||
-Z namespaced-features -- Allow features with `crate:` prefix
|
||||
|
||||
Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::hash;
|
||||
use std::mem;
|
||||
|
@ -23,7 +23,7 @@ use crate::core::dependency::DepKind;
|
|||
use crate::core::resolver::{HasDevUnits, Resolve};
|
||||
use crate::core::source::MaybePackage;
|
||||
use crate::core::{Dependency, Manifest, PackageId, SourceId, Target};
|
||||
use crate::core::{FeatureMap, SourceMap, Summary, Workspace};
|
||||
use crate::core::{SourceMap, Summary, Workspace};
|
||||
use crate::ops;
|
||||
use crate::util::config::PackageCacheLock;
|
||||
use crate::util::errors::{CargoResult, CargoResultExt, HttpNot200};
|
||||
|
@ -87,7 +87,7 @@ struct SerializedPackage<'a> {
|
|||
source: SourceId,
|
||||
dependencies: &'a [Dependency],
|
||||
targets: Vec<&'a Target>,
|
||||
features: &'a FeatureMap,
|
||||
features: &'a BTreeMap<InternedString, Vec<InternedString>>,
|
||||
manifest_path: &'a Path,
|
||||
metadata: Option<&'a toml::Value>,
|
||||
publish: Option<&'a Vec<String>>,
|
||||
|
@ -131,6 +131,12 @@ impl ser::Serialize for Package {
|
|||
.iter()
|
||||
.filter(|t| t.src_path().is_path())
|
||||
.collect();
|
||||
let empty_feats = BTreeMap::new();
|
||||
let features = self
|
||||
.manifest()
|
||||
.original()
|
||||
.features()
|
||||
.unwrap_or(&empty_feats);
|
||||
|
||||
SerializedPackage {
|
||||
name: &*package_id.name(),
|
||||
|
@ -142,7 +148,7 @@ impl ser::Serialize for Package {
|
|||
source: summary.source_id(),
|
||||
dependencies: summary.dependencies(),
|
||||
targets,
|
||||
features: summary.features(),
|
||||
features,
|
||||
manifest_path: self.manifest_path(),
|
||||
metadata: self.manifest().custom_metadata(),
|
||||
authors,
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
use crate::core::resolver::context::Context;
|
||||
use crate::core::resolver::errors::describe_path;
|
||||
use crate::core::resolver::types::{ConflictReason, DepInfo, FeaturesSet};
|
||||
use crate::core::resolver::{ActivateResult, ResolveOpts};
|
||||
use crate::core::resolver::{ActivateError, ActivateResult, ResolveOpts};
|
||||
use crate::core::{Dependency, FeatureValue, PackageId, PackageIdSpec, Registry, Summary};
|
||||
use crate::core::{GitReference, SourceId};
|
||||
use crate::util::errors::{CargoResult, CargoResultExt};
|
||||
|
@ -307,10 +307,10 @@ pub fn resolve_features<'b>(
|
|||
let deps = s.dependencies();
|
||||
let deps = deps.iter().filter(|d| d.is_transitive() || opts.dev_deps);
|
||||
|
||||
let reqs = build_requirements(s, opts)?;
|
||||
let reqs = build_requirements(parent, s, opts)?;
|
||||
let mut ret = Vec::new();
|
||||
let mut used_features = HashSet::new();
|
||||
let default_dep = (false, BTreeSet::new());
|
||||
let default_dep = BTreeSet::new();
|
||||
let mut valid_dep_names = HashSet::new();
|
||||
|
||||
// Next, collect all actually enabled dependencies and their features.
|
||||
for dep in deps {
|
||||
|
@ -319,33 +319,15 @@ pub fn resolve_features<'b>(
|
|||
if dep.is_optional() && !reqs.deps.contains_key(&dep.name_in_toml()) {
|
||||
continue;
|
||||
}
|
||||
valid_dep_names.insert(dep.name_in_toml());
|
||||
// So we want this dependency. Move the features we want from
|
||||
// `feature_deps` to `ret` and register ourselves as using this
|
||||
// name.
|
||||
let base = reqs.deps.get(&dep.name_in_toml()).unwrap_or(&default_dep);
|
||||
used_features.insert(dep.name_in_toml());
|
||||
let always_required = !dep.is_optional()
|
||||
&& !s
|
||||
.dependencies()
|
||||
.iter()
|
||||
.any(|d| d.is_optional() && d.name_in_toml() == dep.name_in_toml());
|
||||
if always_required && base.0 {
|
||||
return Err(match parent {
|
||||
None => anyhow::format_err!(
|
||||
"Package `{}` does not have feature `{}`. It has a required dependency \
|
||||
with that name, but only optional dependencies can be used as features.",
|
||||
s.package_id(),
|
||||
dep.name_in_toml()
|
||||
)
|
||||
.into(),
|
||||
Some(p) => (
|
||||
p,
|
||||
ConflictReason::RequiredDependencyAsFeatures(dep.name_in_toml()),
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
let mut base = base.1.clone();
|
||||
let mut base = reqs
|
||||
.deps
|
||||
.get(&dep.name_in_toml())
|
||||
.unwrap_or(&default_dep)
|
||||
.clone();
|
||||
base.extend(dep.features().iter());
|
||||
for feature in base.iter() {
|
||||
if feature.contains('/') {
|
||||
|
@ -359,74 +341,88 @@ pub fn resolve_features<'b>(
|
|||
ret.push((dep.clone(), Rc::new(base)));
|
||||
}
|
||||
|
||||
// Any entries in `reqs.dep` which weren't used are bugs in that the
|
||||
// package does not actually have those dependencies. We classified
|
||||
// them as dependencies in the first place because there is no such
|
||||
// feature, either.
|
||||
let remaining = reqs
|
||||
.deps
|
||||
.keys()
|
||||
.cloned()
|
||||
.filter(|s| !used_features.contains(s))
|
||||
.collect::<Vec<_>>();
|
||||
if !remaining.is_empty() {
|
||||
let features = remaining.join(", ");
|
||||
return Err(match parent {
|
||||
None => anyhow::format_err!(
|
||||
"Package `{}` does not have these features: `{}`",
|
||||
s.package_id(),
|
||||
features
|
||||
)
|
||||
.into(),
|
||||
Some(p) => (p, ConflictReason::MissingFeatures(features)).into(),
|
||||
});
|
||||
// This is a special case for command-line `--features
|
||||
// crate_name/feat_name` where `crate_name` does not exist. All other
|
||||
// validation is done either in `build_requirements` or
|
||||
// `build_feature_map`.
|
||||
for dep_name in reqs.deps.keys() {
|
||||
if !valid_dep_names.contains(dep_name) {
|
||||
let e = RequirementError::MissingDependency(*dep_name);
|
||||
return Err(e.into_activate_error(parent, s));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((reqs.into_used(), ret))
|
||||
Ok((reqs.into_features(), ret))
|
||||
}
|
||||
|
||||
/// Takes requested features for a single package from the input `ResolveOpts` and
|
||||
/// recurses to find all requested features, dependencies and requested
|
||||
/// dependency features in a `Requirements` object, returning it to the resolver.
|
||||
fn build_requirements<'a, 'b: 'a>(
|
||||
parent: Option<PackageId>,
|
||||
s: &'a Summary,
|
||||
opts: &'b ResolveOpts,
|
||||
) -> CargoResult<Requirements<'a>> {
|
||||
) -> ActivateResult<Requirements<'a>> {
|
||||
let mut reqs = Requirements::new(s);
|
||||
|
||||
if opts.features.all_features {
|
||||
for key in s.features().keys() {
|
||||
reqs.require_feature(*key)?;
|
||||
}
|
||||
for dep in s.dependencies().iter().filter(|d| d.is_optional()) {
|
||||
reqs.require_dependency(dep.name_in_toml());
|
||||
if let Err(e) = reqs.require_feature(*key) {
|
||||
return Err(e.into_activate_error(parent, s));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &f in opts.features.features.iter() {
|
||||
reqs.require_value(&FeatureValue::new(f, s))?;
|
||||
let fv = FeatureValue::new(f);
|
||||
if fv.is_explicit_crate() {
|
||||
return Err(ActivateError::Fatal(anyhow::format_err!(
|
||||
"feature value `{}` is not allowed to use explicit `crate:` syntax",
|
||||
fv
|
||||
)));
|
||||
}
|
||||
if let Err(e) = reqs.require_value(&fv) {
|
||||
return Err(e.into_activate_error(parent, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.features.uses_default_features && s.features().contains_key("default") {
|
||||
reqs.require_feature(InternedString::new("default"))?;
|
||||
if let Err(e) = reqs.require_feature(InternedString::new("default")) {
|
||||
return Err(e.into_activate_error(parent, s));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(reqs)
|
||||
}
|
||||
|
||||
/// Set of feature and dependency requirements for a package.
|
||||
#[derive(Debug)]
|
||||
struct Requirements<'a> {
|
||||
summary: &'a Summary,
|
||||
// The deps map is a mapping of package name to list of features enabled.
|
||||
// Each package should be enabled, and each package should have the
|
||||
// specified set of features enabled. The boolean indicates whether this
|
||||
// package was specifically requested (rather than just requesting features
|
||||
// *within* this package).
|
||||
deps: HashMap<InternedString, (bool, BTreeSet<InternedString>)>,
|
||||
// The used features set is the set of features which this local package had
|
||||
// enabled, which is later used when compiling to instruct the code what
|
||||
// features were enabled.
|
||||
used: HashSet<InternedString>,
|
||||
visited: HashSet<InternedString>,
|
||||
/// The deps map is a mapping of dependency name to list of features enabled.
|
||||
///
|
||||
/// The resolver will activate all of these dependencies, with the given
|
||||
/// features enabled.
|
||||
deps: HashMap<InternedString, BTreeSet<InternedString>>,
|
||||
/// The set of features enabled on this package which is later used when
|
||||
/// compiling to instruct the code what features were enabled.
|
||||
features: HashSet<InternedString>,
|
||||
}
|
||||
|
||||
/// An error for a requirement.
|
||||
///
|
||||
/// This will later be converted to an `ActivateError` depending on whether or
|
||||
/// not this is a dependency or a root package.
|
||||
enum RequirementError {
|
||||
/// The package does not have the requested feature.
|
||||
MissingFeature(InternedString),
|
||||
/// The package does not have the requested dependency.
|
||||
MissingDependency(InternedString),
|
||||
/// A feature has a direct cycle to itself.
|
||||
///
|
||||
/// Note that cycles through multiple features are allowed (but perhaps
|
||||
/// they shouldn't be?).
|
||||
Cycle(InternedString),
|
||||
}
|
||||
|
||||
impl Requirements<'_> {
|
||||
|
@ -434,80 +430,146 @@ impl Requirements<'_> {
|
|||
Requirements {
|
||||
summary,
|
||||
deps: HashMap::new(),
|
||||
used: HashSet::new(),
|
||||
visited: HashSet::new(),
|
||||
features: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_used(self) -> HashSet<InternedString> {
|
||||
self.used
|
||||
fn into_features(self) -> HashSet<InternedString> {
|
||||
self.features
|
||||
}
|
||||
|
||||
fn require_crate_feature(&mut self, package: InternedString, feat: InternedString) {
|
||||
fn require_crate_feature(
|
||||
&mut self,
|
||||
package: InternedString,
|
||||
feat: InternedString,
|
||||
explicit: bool,
|
||||
) -> Result<(), RequirementError> {
|
||||
// If `package` is indeed an optional dependency then we activate the
|
||||
// feature named `package`, but otherwise if `package` is a required
|
||||
// dependency then there's no feature associated with it.
|
||||
if self
|
||||
.summary
|
||||
.dependencies()
|
||||
.iter()
|
||||
.any(|dep| dep.name_in_toml() == package && dep.is_optional())
|
||||
if !explicit
|
||||
&& self
|
||||
.summary
|
||||
.dependencies()
|
||||
.iter()
|
||||
.any(|dep| dep.name_in_toml() == package && dep.is_optional())
|
||||
{
|
||||
self.used.insert(package);
|
||||
}
|
||||
self.deps
|
||||
.entry(package)
|
||||
.or_insert((false, BTreeSet::new()))
|
||||
.1
|
||||
.insert(feat);
|
||||
}
|
||||
|
||||
fn seen(&mut self, feat: InternedString) -> bool {
|
||||
if self.visited.insert(feat) {
|
||||
self.used.insert(feat);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
self.require_feature(package)?;
|
||||
}
|
||||
self.deps.entry(package).or_default().insert(feat);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require_dependency(&mut self, pkg: InternedString) {
|
||||
if self.seen(pkg) {
|
||||
return;
|
||||
}
|
||||
self.deps.entry(pkg).or_insert((false, BTreeSet::new())).0 = true;
|
||||
self.deps.entry(pkg).or_default();
|
||||
}
|
||||
|
||||
fn require_feature(&mut self, feat: InternedString) -> CargoResult<()> {
|
||||
if feat.is_empty() || self.seen(feat) {
|
||||
fn require_feature(&mut self, feat: InternedString) -> Result<(), RequirementError> {
|
||||
if !self.features.insert(feat) {
|
||||
// Already seen this feature.
|
||||
return Ok(());
|
||||
}
|
||||
for fv in self
|
||||
.summary
|
||||
.features()
|
||||
.get(&feat)
|
||||
.expect("must be a valid feature")
|
||||
{
|
||||
match *fv {
|
||||
FeatureValue::Feature(ref dep_feat) if **dep_feat == *feat => anyhow::bail!(
|
||||
"cyclic feature dependency: feature `{}` depends on itself",
|
||||
feat
|
||||
),
|
||||
_ => {}
|
||||
|
||||
let fvs = match self.summary.features().get(&feat) {
|
||||
Some(fvs) => fvs,
|
||||
None => return Err(RequirementError::MissingFeature(feat)),
|
||||
};
|
||||
|
||||
for fv in fvs {
|
||||
if let FeatureValue::Feature(dep_feat) = fv {
|
||||
if *dep_feat == feat {
|
||||
return Err(RequirementError::Cycle(feat));
|
||||
}
|
||||
}
|
||||
self.require_value(fv)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require_value(&mut self, fv: &FeatureValue) -> CargoResult<()> {
|
||||
fn require_value(&mut self, fv: &FeatureValue) -> Result<(), RequirementError> {
|
||||
match fv {
|
||||
FeatureValue::Feature(feat) => self.require_feature(*feat)?,
|
||||
FeatureValue::Crate(dep) => self.require_dependency(*dep),
|
||||
FeatureValue::CrateFeature(dep, dep_feat) => {
|
||||
self.require_crate_feature(*dep, *dep_feat)
|
||||
}
|
||||
FeatureValue::Crate { dep_name } => self.require_dependency(*dep_name),
|
||||
FeatureValue::CrateFeature {
|
||||
dep_name,
|
||||
dep_feature,
|
||||
explicit,
|
||||
} => self.require_crate_feature(*dep_name, *dep_feature, *explicit)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RequirementError {
|
||||
fn into_activate_error(self, parent: Option<PackageId>, summary: &Summary) -> ActivateError {
|
||||
match self {
|
||||
RequirementError::MissingFeature(feat) => {
|
||||
let deps: Vec<_> = summary
|
||||
.dependencies()
|
||||
.iter()
|
||||
.filter(|dep| dep.name_in_toml() == feat)
|
||||
.collect();
|
||||
if deps.is_empty() {
|
||||
return match parent {
|
||||
None => ActivateError::Fatal(anyhow::format_err!(
|
||||
"Package `{}` does not have the feature `{}`",
|
||||
summary.package_id(),
|
||||
feat
|
||||
)),
|
||||
Some(p) => ActivateError::Conflict(
|
||||
p,
|
||||
ConflictReason::MissingFeatures(feat.to_string()),
|
||||
),
|
||||
};
|
||||
}
|
||||
if deps.iter().any(|dep| dep.is_optional()) {
|
||||
match parent {
|
||||
None => ActivateError::Fatal(anyhow::format_err!(
|
||||
"Package `{}` does not have feature `{}`. It has an optional dependency \
|
||||
with that name, but that dependency uses the \"crate:\" \
|
||||
syntax in the features table, so it does not have an implicit feature with that name.",
|
||||
summary.package_id(),
|
||||
feat
|
||||
)),
|
||||
Some(p) => ActivateError::Conflict(
|
||||
p,
|
||||
ConflictReason::NonImplicitDependencyAsFeature(feat),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
match parent {
|
||||
None => ActivateError::Fatal(anyhow::format_err!(
|
||||
"Package `{}` does not have feature `{}`. It has a required dependency \
|
||||
with that name, but only optional dependencies can be used as features.",
|
||||
summary.package_id(),
|
||||
feat
|
||||
)),
|
||||
Some(p) => ActivateError::Conflict(
|
||||
p,
|
||||
ConflictReason::RequiredDependencyAsFeature(feat),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
RequirementError::MissingDependency(dep_name) => {
|
||||
match parent {
|
||||
None => ActivateError::Fatal(anyhow::format_err!(
|
||||
"package `{}` does not have a dependency named `{}`",
|
||||
summary.package_id(),
|
||||
dep_name
|
||||
)),
|
||||
// This code path currently isn't used, since `foo/bar`
|
||||
// and `crate:` syntax is not allowed in a dependency.
|
||||
Some(p) => ActivateError::Conflict(
|
||||
p,
|
||||
ConflictReason::MissingFeatures(dep_name.to_string()),
|
||||
),
|
||||
}
|
||||
}
|
||||
RequirementError::Cycle(feat) => ActivateError::Fatal(anyhow::format_err!(
|
||||
"cyclic feature dependency: feature `{}` depends on itself",
|
||||
feat
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ pub(super) fn activation_error(
|
|||
msg.push_str("` does not have these features.\n");
|
||||
// p == parent so the full path is redundant.
|
||||
}
|
||||
ConflictReason::RequiredDependencyAsFeatures(features) => {
|
||||
ConflictReason::RequiredDependencyAsFeature(features) => {
|
||||
msg.push_str("\n\nthe package `");
|
||||
msg.push_str(&*p.name());
|
||||
msg.push_str("` depends on `");
|
||||
|
@ -158,6 +158,24 @@ pub(super) fn activation_error(
|
|||
);
|
||||
// p == parent so the full path is redundant.
|
||||
}
|
||||
ConflictReason::NonImplicitDependencyAsFeature(features) => {
|
||||
msg.push_str("\n\nthe package `");
|
||||
msg.push_str(&*p.name());
|
||||
msg.push_str("` depends on `");
|
||||
msg.push_str(&*dep.package_name());
|
||||
msg.push_str("`, with features: `");
|
||||
msg.push_str(features);
|
||||
msg.push_str("` but `");
|
||||
msg.push_str(&*dep.package_name());
|
||||
msg.push_str("` does not have these features.\n");
|
||||
msg.push_str(
|
||||
" It has an optional dependency with that name, \
|
||||
but but that dependency uses the \"crate:\" \
|
||||
syntax in the features table, so it does not have an \
|
||||
implicit feature with that name.\n",
|
||||
);
|
||||
// p == parent so the full path is redundant.
|
||||
}
|
||||
ConflictReason::PublicDependency(pkg_id) => {
|
||||
// TODO: This needs to be implemented.
|
||||
unimplemented!("pub dep {:?}", pkg_id);
|
||||
|
|
|
@ -57,8 +57,18 @@ type ActivateMap = HashMap<(PackageId, bool), BTreeSet<InternedString>>;
|
|||
/// Set of all activated features for all packages in the resolve graph.
|
||||
pub struct ResolvedFeatures {
|
||||
activated_features: ActivateMap,
|
||||
/// Optional dependencies that should be built.
|
||||
///
|
||||
/// The value is the `name_in_toml` of the dependencies.
|
||||
activated_dependencies: ActivateMap,
|
||||
/// This is only here for legacy support when `-Zfeatures` is not enabled.
|
||||
legacy: Option<HashMap<PackageId, Vec<InternedString>>>,
|
||||
///
|
||||
/// This is the set of features enabled for each package.
|
||||
legacy_features: Option<HashMap<PackageId, Vec<InternedString>>>,
|
||||
/// This is only here for legacy support when `-Zfeatures` is not enabled.
|
||||
///
|
||||
/// This is the set of optional dependencies enabled for each package.
|
||||
legacy_dependencies: Option<HashMap<PackageId, HashSet<InternedString>>>,
|
||||
opts: FeatureOpts,
|
||||
}
|
||||
|
||||
|
@ -227,6 +237,30 @@ impl ResolvedFeatures {
|
|||
.expect("activated_features for invalid package")
|
||||
}
|
||||
|
||||
/// Returns if the given dependency should be included.
|
||||
///
|
||||
/// This handles dependencies disabled via `cfg` expressions and optional
|
||||
/// dependencies which are not enabled.
|
||||
pub fn is_dep_activated(
|
||||
&self,
|
||||
pkg_id: PackageId,
|
||||
features_for: FeaturesFor,
|
||||
dep_name: InternedString,
|
||||
) -> bool {
|
||||
if let Some(legacy) = &self.legacy_dependencies {
|
||||
legacy
|
||||
.get(&pkg_id)
|
||||
.map(|deps| deps.contains(&dep_name))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
let is_build = self.opts.decouple_host_deps && features_for == FeaturesFor::HostDep;
|
||||
self.activated_dependencies
|
||||
.get(&(pkg_id, is_build))
|
||||
.map(|deps| deps.contains(&dep_name))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Variant of `activated_features` that returns `None` if this is
|
||||
/// not a valid pkg_id/is_build combination. Used in places which do
|
||||
/// not know which packages are activated (like `cargo clean`).
|
||||
|
@ -243,7 +277,7 @@ impl ResolvedFeatures {
|
|||
pkg_id: PackageId,
|
||||
features_for: FeaturesFor,
|
||||
) -> CargoResult<Vec<InternedString>> {
|
||||
if let Some(legacy) = &self.legacy {
|
||||
if let Some(legacy) = &self.legacy_features {
|
||||
Ok(legacy.get(&pkg_id).map_or_else(Vec::new, |v| v.clone()))
|
||||
} else {
|
||||
let is_build = self.opts.decouple_host_deps && features_for == FeaturesFor::HostDep;
|
||||
|
@ -267,6 +301,8 @@ pub struct FeatureResolver<'a, 'cfg> {
|
|||
opts: FeatureOpts,
|
||||
/// Map of features activated for each package.
|
||||
activated_features: ActivateMap,
|
||||
/// Map of optional dependencies activated for each package.
|
||||
activated_dependencies: ActivateMap,
|
||||
/// Keeps track of which packages have had its dependencies processed.
|
||||
/// Used to avoid cycles, and to speed up processing.
|
||||
processed_deps: HashSet<(PackageId, bool)>,
|
||||
|
@ -294,7 +330,9 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
|
|||
// Legacy mode.
|
||||
return Ok(ResolvedFeatures {
|
||||
activated_features: HashMap::new(),
|
||||
legacy: Some(resolve.features_clone()),
|
||||
activated_dependencies: HashMap::new(),
|
||||
legacy_features: Some(resolve.features_clone()),
|
||||
legacy_dependencies: Some(compute_legacy_deps(resolve)),
|
||||
opts,
|
||||
});
|
||||
}
|
||||
|
@ -306,6 +344,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
|
|||
package_set,
|
||||
opts,
|
||||
activated_features: HashMap::new(),
|
||||
activated_dependencies: HashMap::new(),
|
||||
processed_deps: HashSet::new(),
|
||||
};
|
||||
r.do_resolve(specs, requested_features)?;
|
||||
|
@ -315,7 +354,9 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
|
|||
}
|
||||
Ok(ResolvedFeatures {
|
||||
activated_features: r.activated_features,
|
||||
legacy: None,
|
||||
activated_dependencies: r.activated_dependencies,
|
||||
legacy_features: None,
|
||||
legacy_dependencies: None,
|
||||
opts: r.opts,
|
||||
})
|
||||
}
|
||||
|
@ -402,9 +443,12 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
|
|||
FeatureValue::Feature(f) => {
|
||||
self.activate_rec(pkg_id, *f, for_host)?;
|
||||
}
|
||||
FeatureValue::Crate(dep_name) => {
|
||||
// Activate the feature name on self.
|
||||
self.activate_rec(pkg_id, *dep_name, for_host)?;
|
||||
FeatureValue::Crate { dep_name } => {
|
||||
// Mark this dependency as activated.
|
||||
self.activated_dependencies
|
||||
.entry((pkg_id, self.opts.decouple_host_deps && for_host))
|
||||
.or_default()
|
||||
.insert(*dep_name);
|
||||
// Activate the optional dep.
|
||||
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
|
||||
for (dep, dep_for_host) in deps {
|
||||
|
@ -416,7 +460,11 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
|
|||
}
|
||||
}
|
||||
}
|
||||
FeatureValue::CrateFeature(dep_name, dep_feature) => {
|
||||
FeatureValue::CrateFeature {
|
||||
dep_name,
|
||||
dep_feature,
|
||||
explicit,
|
||||
} => {
|
||||
// Activate a feature within a dependency.
|
||||
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
|
||||
for (dep, dep_for_host) in deps {
|
||||
|
@ -425,12 +473,20 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
|
|||
}
|
||||
if dep.is_optional() {
|
||||
// Activate the crate on self.
|
||||
let fv = FeatureValue::Crate(*dep_name);
|
||||
let fv = FeatureValue::Crate {
|
||||
dep_name: *dep_name,
|
||||
// explicit: *explicit,
|
||||
};
|
||||
self.activate_fv(pkg_id, &fv, for_host)?;
|
||||
if !explicit {
|
||||
// To retain compatibility with old behavior,
|
||||
// this also enables a feature of the same
|
||||
// name.
|
||||
self.activate_rec(pkg_id, *dep_name, for_host)?;
|
||||
}
|
||||
}
|
||||
// Activate the feature on the dependency.
|
||||
let summary = self.resolve.summary(dep_pkg_id);
|
||||
let fv = FeatureValue::new(*dep_feature, summary);
|
||||
let fv = FeatureValue::new(*dep_feature);
|
||||
self.activate_fv(dep_pkg_id, &fv, dep_for_host)?;
|
||||
}
|
||||
}
|
||||
|
@ -484,7 +540,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
|
|||
let mut result: Vec<FeatureValue> = dep
|
||||
.features()
|
||||
.iter()
|
||||
.map(|f| FeatureValue::new(*f, summary))
|
||||
.map(|f| FeatureValue::new(*f))
|
||||
.collect();
|
||||
let default = InternedString::new("default");
|
||||
if dep.uses_default_features() && feature_map.contains_key(&default) {
|
||||
|
@ -502,28 +558,16 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
|
|||
let summary = self.resolve.summary(pkg_id);
|
||||
let feature_map = summary.features();
|
||||
if requested_features.all_features {
|
||||
let mut fvs: Vec<FeatureValue> = feature_map
|
||||
feature_map
|
||||
.keys()
|
||||
.map(|k| FeatureValue::Feature(*k))
|
||||
.collect();
|
||||
// Add optional deps.
|
||||
// Top-level requested features can never apply to
|
||||
// build-dependencies, so for_host is `false` here.
|
||||
for (_dep_pkg_id, deps) in self.deps(pkg_id, false) {
|
||||
for (dep, _dep_for_host) in deps {
|
||||
if dep.is_optional() {
|
||||
// This may result in duplicates, but that should be ok.
|
||||
fvs.push(FeatureValue::Crate(dep.name_in_toml()));
|
||||
}
|
||||
}
|
||||
}
|
||||
fvs
|
||||
.collect()
|
||||
} else {
|
||||
let mut result: Vec<FeatureValue> = requested_features
|
||||
.features
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|f| FeatureValue::new(*f, summary))
|
||||
.map(|f| FeatureValue::new(*f))
|
||||
.collect();
|
||||
let default = InternedString::new("default");
|
||||
if requested_features.uses_default_features && feature_map.contains_key(&default) {
|
||||
|
@ -612,3 +656,19 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
|
|||
.proc_macro()
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes a map of PackageId to the set of optional dependencies that are
|
||||
/// enabled for that dep (when the new resolver is not enabled).
|
||||
fn compute_legacy_deps(resolve: &Resolve) -> HashMap<PackageId, HashSet<InternedString>> {
|
||||
let mut result: HashMap<PackageId, HashSet<InternedString>> = HashMap::new();
|
||||
for pkg_id in resolve.iter() {
|
||||
for (_dep_id, deps) in resolve.deps(pkg_id) {
|
||||
for dep in deps {
|
||||
if dep.is_optional() {
|
||||
result.entry(pkg_id).or_default().insert(dep.name_in_toml());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
|
|
@ -290,11 +290,15 @@ pub enum ConflictReason {
|
|||
/// candidate we're activating didn't actually have the feature `foo`.
|
||||
MissingFeatures(String),
|
||||
|
||||
/// A dependency listed features that ended up being a required dependency.
|
||||
/// A dependency listed a feature that ended up being a required dependency.
|
||||
/// For example we tried to activate feature `foo` but the
|
||||
/// candidate we're activating didn't actually have the feature `foo`
|
||||
/// it had a dependency `foo` instead.
|
||||
RequiredDependencyAsFeatures(InternedString),
|
||||
RequiredDependencyAsFeature(InternedString),
|
||||
|
||||
/// A dependency listed a feature for an optional dependency, but that
|
||||
/// optional dependency is "hidden" using namespaced `crate:` syntax.
|
||||
NonImplicitDependencyAsFeature(InternedString),
|
||||
|
||||
// TODO: needs more info for `activation_error`
|
||||
// TODO: needs more info for `find_candidate`
|
||||
|
@ -319,7 +323,7 @@ impl ConflictReason {
|
|||
}
|
||||
|
||||
pub fn is_required_dependency_as_features(&self) -> bool {
|
||||
if let ConflictReason::RequiredDependencyAsFeatures(_) = *self {
|
||||
if let ConflictReason::RequiredDependencyAsFeature(_) = *self {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
use std::borrow::Borrow;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt::Display;
|
||||
use crate::core::{Dependency, PackageId, SourceId};
|
||||
use crate::util::interning::InternedString;
|
||||
use crate::util::CargoResult;
|
||||
use anyhow::bail;
|
||||
use semver::Version;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::core::{Dependency, PackageId, SourceId};
|
||||
use crate::util::interning::InternedString;
|
||||
use semver::Version;
|
||||
|
||||
use crate::util::CargoResult;
|
||||
|
||||
/// Subset of a `Manifest`. Contains only the most important information about
|
||||
/// a package.
|
||||
///
|
||||
|
@ -27,39 +23,33 @@ struct Inner {
|
|||
package_id: PackageId,
|
||||
dependencies: Vec<Dependency>,
|
||||
features: Rc<FeatureMap>,
|
||||
has_namespaced_features: bool,
|
||||
has_overlapping_features: Option<InternedString>,
|
||||
checksum: Option<String>,
|
||||
links: Option<InternedString>,
|
||||
namespaced_features: bool,
|
||||
}
|
||||
|
||||
impl Summary {
|
||||
pub fn new<K>(
|
||||
pub fn new(
|
||||
pkg_id: PackageId,
|
||||
dependencies: Vec<Dependency>,
|
||||
features: &BTreeMap<K, Vec<impl AsRef<str>>>,
|
||||
features: &BTreeMap<InternedString, Vec<InternedString>>,
|
||||
links: Option<impl Into<InternedString>>,
|
||||
namespaced_features: bool,
|
||||
) -> CargoResult<Summary>
|
||||
where
|
||||
K: Borrow<str> + Ord + Display,
|
||||
{
|
||||
) -> CargoResult<Summary> {
|
||||
let mut has_overlapping_features = None;
|
||||
for dep in dependencies.iter() {
|
||||
let feature = dep.name_in_toml();
|
||||
if !namespaced_features && features.get(&*feature).is_some() {
|
||||
anyhow::bail!(
|
||||
"Features and dependencies cannot have the \
|
||||
same name: `{}`",
|
||||
feature
|
||||
)
|
||||
let dep_name = dep.name_in_toml();
|
||||
if features.contains_key(&dep_name) {
|
||||
has_overlapping_features = Some(dep_name);
|
||||
}
|
||||
if dep.is_optional() && !dep.is_transitive() {
|
||||
anyhow::bail!(
|
||||
"Dev-dependencies are not allowed to be optional: `{}`",
|
||||
feature
|
||||
bail!(
|
||||
"dev-dependencies are not allowed to be optional: `{}`",
|
||||
dep_name
|
||||
)
|
||||
}
|
||||
}
|
||||
let feature_map = build_feature_map(features, &dependencies, namespaced_features)?;
|
||||
let (feature_map, has_namespaced_features) = build_feature_map(features, &dependencies)?;
|
||||
Ok(Summary {
|
||||
inner: Rc::new(Inner {
|
||||
package_id: pkg_id,
|
||||
|
@ -67,7 +57,8 @@ impl Summary {
|
|||
features: Rc::new(feature_map),
|
||||
checksum: None,
|
||||
links: links.map(|l| l.into()),
|
||||
namespaced_features,
|
||||
has_namespaced_features,
|
||||
has_overlapping_features,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
@ -90,15 +81,33 @@ impl Summary {
|
|||
pub fn features(&self) -> &FeatureMap {
|
||||
&self.inner.features
|
||||
}
|
||||
|
||||
/// Returns an error if this Summary is using an unstable feature that is
|
||||
/// not enabled.
|
||||
pub fn unstable_gate(&self, namespaced_features: bool) -> CargoResult<()> {
|
||||
if !namespaced_features {
|
||||
if self.inner.has_namespaced_features {
|
||||
bail!(
|
||||
"namespaced features with the `crate:` prefix are only allowed on \
|
||||
the nightly channel and requires the `-Z namespaced-features` flag on the command-line"
|
||||
);
|
||||
}
|
||||
if let Some(dep_name) = self.inner.has_overlapping_features {
|
||||
bail!(
|
||||
"features and dependencies cannot have the same name: `{}`",
|
||||
dep_name
|
||||
)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn checksum(&self) -> Option<&str> {
|
||||
self.inner.checksum.as_deref()
|
||||
}
|
||||
pub fn links(&self) -> Option<InternedString> {
|
||||
self.inner.links
|
||||
}
|
||||
pub fn namespaced_features(&self) -> bool {
|
||||
self.inner.namespaced_features
|
||||
}
|
||||
|
||||
pub fn override_id(mut self, id: PackageId) -> Summary {
|
||||
Rc::make_mut(&mut self.inner).package_id = id;
|
||||
|
@ -145,16 +154,16 @@ impl Hash for Summary {
|
|||
}
|
||||
}
|
||||
|
||||
// Checks features for errors, bailing out a CargoResult:Err if invalid,
|
||||
// and creates FeatureValues for each feature.
|
||||
fn build_feature_map<K>(
|
||||
features: &BTreeMap<K, Vec<impl AsRef<str>>>,
|
||||
/// Checks features for errors, bailing out a CargoResult:Err if invalid,
|
||||
/// and creates FeatureValues for each feature.
|
||||
///
|
||||
/// The returned `bool` indicates whether or not the `[features]` table
|
||||
/// included a `crate:` prefixed namespaced feature (used for gating on
|
||||
/// nightly).
|
||||
fn build_feature_map(
|
||||
features: &BTreeMap<InternedString, Vec<InternedString>>,
|
||||
dependencies: &[Dependency],
|
||||
namespaced: bool,
|
||||
) -> CargoResult<FeatureMap>
|
||||
where
|
||||
K: Borrow<str> + Ord + Display,
|
||||
{
|
||||
) -> CargoResult<(FeatureMap, bool)> {
|
||||
use self::FeatureValue::*;
|
||||
let mut dep_map = HashMap::new();
|
||||
for dep in dependencies.iter() {
|
||||
|
@ -164,54 +173,62 @@ where
|
|||
.push(dep);
|
||||
}
|
||||
|
||||
let mut map = BTreeMap::new();
|
||||
for (feature, list) in features.iter() {
|
||||
// If namespaced features is active and the key is the same as that of an
|
||||
// optional dependency, that dependency must be included in the values.
|
||||
// Thus, if a `feature` is found that has the same name as a dependency, we
|
||||
// (a) bail out if the dependency is non-optional, and (b) we track if the
|
||||
// feature requirements include the dependency `crate:feature` in the list.
|
||||
// This is done with the `dependency_found` variable, which can only be
|
||||
// false if features are namespaced and the current feature key is the same
|
||||
// as the name of an optional dependency. If so, it gets set to true during
|
||||
// iteration over the list if the dependency is found in the list.
|
||||
let mut dependency_found = if namespaced {
|
||||
match dep_map.get(feature.borrow()) {
|
||||
Some(dep_data) => {
|
||||
if !dep_data.iter().any(|d| d.is_optional()) {
|
||||
anyhow::bail!(
|
||||
"Feature `{}` includes the dependency of the same name, but this is \
|
||||
left implicit in the features included by this feature.\n\
|
||||
Additionally, the dependency must be marked as optional to be \
|
||||
included in the feature definition.\n\
|
||||
Consider adding `crate:{}` to this feature's requirements \
|
||||
and marking the dependency as `optional = true`",
|
||||
feature,
|
||||
feature
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
let mut map: FeatureMap = features
|
||||
.iter()
|
||||
.map(|(feature, list)| {
|
||||
let fvs: Vec<_> = list
|
||||
.iter()
|
||||
.map(|feat_value| FeatureValue::new(*feat_value))
|
||||
.collect();
|
||||
(*feature, fvs)
|
||||
})
|
||||
.collect();
|
||||
let has_namespaced_features = map.values().flatten().any(|fv| fv.is_explicit_crate());
|
||||
|
||||
// Add implicit features for optional dependencies if they weren't
|
||||
// explicitly listed anywhere.
|
||||
let explicitly_listed: HashSet<_> = map
|
||||
.values()
|
||||
.flatten()
|
||||
.filter_map(|fv| match fv {
|
||||
Crate { dep_name }
|
||||
| CrateFeature {
|
||||
dep_name,
|
||||
explicit: true,
|
||||
..
|
||||
} => Some(*dep_name),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
for dep in dependencies {
|
||||
if !dep.is_optional() {
|
||||
continue;
|
||||
}
|
||||
let dep_name_in_toml = dep.name_in_toml();
|
||||
if features.contains_key(&dep_name_in_toml) || explicitly_listed.contains(&dep_name_in_toml)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let fv = Crate {
|
||||
dep_name: dep_name_in_toml,
|
||||
};
|
||||
map.insert(dep_name_in_toml, vec![fv]);
|
||||
}
|
||||
|
||||
let mut values = vec![];
|
||||
for dep in list {
|
||||
let val = FeatureValue::build(
|
||||
InternedString::new(dep.as_ref()),
|
||||
|fs| features.contains_key(fs.as_str()),
|
||||
namespaced,
|
||||
// Validate features are listed properly.
|
||||
for (feature, fvs) in &map {
|
||||
if feature.starts_with("crate:") {
|
||||
bail!(
|
||||
"feature named `{}` is not allowed to start with `crate:`",
|
||||
feature
|
||||
);
|
||||
|
||||
}
|
||||
for fv in fvs {
|
||||
// Find data for the referenced dependency...
|
||||
let dep_data = {
|
||||
match val {
|
||||
Feature(ref dep_name) | Crate(ref dep_name) | CrateFeature(ref dep_name, _) => {
|
||||
dep_map.get(dep_name.as_str())
|
||||
match fv {
|
||||
Feature(dep_name) | Crate { dep_name, .. } | CrateFeature { dep_name, .. } => {
|
||||
dep_map.get(dep_name)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -219,198 +236,160 @@ where
|
|||
.iter()
|
||||
.flat_map(|d| d.iter())
|
||||
.any(|d| d.is_optional());
|
||||
if let FeatureValue::Crate(ref dep_name) = val {
|
||||
// If we have a dependency value, check if this is the dependency named
|
||||
// the same as the feature that we were looking for.
|
||||
if !dependency_found && feature.borrow() == dep_name.as_str() {
|
||||
dependency_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
match (&val, dep_data.is_some(), is_optional_dep) {
|
||||
// The value is a feature. If features are namespaced, this just means
|
||||
// it's not prefixed with `crate:`, so we have to check whether the
|
||||
// feature actually exist. If the feature is not defined *and* an optional
|
||||
// dependency of the same name exists, the feature is defined implicitly
|
||||
// here by adding it to the feature map, pointing to the dependency.
|
||||
// If features are not namespaced, it's been validated as a feature already
|
||||
// while instantiating the `FeatureValue` in `FeatureValue::build()`, so
|
||||
// we don't have to do so here.
|
||||
(&Feature(feat), _, true) => {
|
||||
if namespaced && !features.contains_key(&*feat) {
|
||||
map.insert(feat, vec![FeatureValue::Crate(feat)]);
|
||||
}
|
||||
}
|
||||
// If features are namespaced and the value is not defined as a feature
|
||||
// and there is no optional dependency of the same name, error out.
|
||||
// If features are not namespaced, there must be an existing feature
|
||||
// here (checked by `FeatureValue::build()`), so it will always be defined.
|
||||
(&Feature(feat), dep_exists, false) => {
|
||||
if namespaced && !features.contains_key(&*feat) {
|
||||
if dep_exists {
|
||||
anyhow::bail!(
|
||||
"Feature `{}` includes `{}` which is not defined as a feature.\n\
|
||||
A non-optional dependency of the same name is defined; consider \
|
||||
adding `optional = true` to its definition",
|
||||
let is_any_dep = dep_data.is_some();
|
||||
match fv {
|
||||
Feature(f) => {
|
||||
if !features.contains_key(f) {
|
||||
if !is_any_dep {
|
||||
bail!(
|
||||
"feature `{}` includes `{}` which is neither a dependency \
|
||||
nor another feature",
|
||||
feature,
|
||||
feat
|
||||
)
|
||||
fv
|
||||
);
|
||||
}
|
||||
if is_optional_dep {
|
||||
if !map.contains_key(f) {
|
||||
bail!(
|
||||
"feature `{}` includes `{}`, but `{}` is an \
|
||||
optional dependency without an implicit feature\n\
|
||||
Use `crate:{}` to enable the dependency.",
|
||||
feature,
|
||||
fv,
|
||||
f,
|
||||
f
|
||||
);
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Feature `{}` includes `{}` which is not defined as a feature",
|
||||
feature,
|
||||
feat
|
||||
)
|
||||
bail!("feature `{}` includes `{}`, but `{}` is not an optional dependency\n\
|
||||
A non-optional dependency of the same name is defined; \
|
||||
consider adding `optional = true` to its definition.",
|
||||
feature, fv, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The value is a dependency. If features are namespaced, it is explicitly
|
||||
// tagged as such (`crate:value`). If features are not namespaced, any value
|
||||
// not recognized as a feature is pegged as a `Crate`. Here we handle the case
|
||||
// where the dependency exists but is non-optional. It branches on namespaced
|
||||
// just to provide the correct string for the crate dependency in the error.
|
||||
(&Crate(ref dep), true, false) => {
|
||||
if namespaced {
|
||||
anyhow::bail!(
|
||||
"Feature `{}` includes `crate:{}` which is not an \
|
||||
optional dependency.\nConsider adding \
|
||||
`optional = true` to the dependency",
|
||||
Crate { dep_name } => {
|
||||
if !is_any_dep {
|
||||
bail!(
|
||||
"feature `{}` includes `{}`, but `{}` is not listed as a dependency",
|
||||
feature,
|
||||
dep
|
||||
)
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Feature `{}` depends on `{}` which is not an \
|
||||
optional dependency.\nConsider adding \
|
||||
`optional = true` to the dependency",
|
||||
fv,
|
||||
dep_name
|
||||
);
|
||||
}
|
||||
if !is_optional_dep {
|
||||
bail!(
|
||||
"feature `{}` includes `{}`, but `{}` is not an optional dependency\n\
|
||||
A non-optional dependency of the same name is defined; \
|
||||
consider adding `optional = true` to its definition.",
|
||||
feature,
|
||||
dep
|
||||
)
|
||||
fv,
|
||||
dep_name
|
||||
);
|
||||
}
|
||||
}
|
||||
// If namespaced, the value was tagged as a dependency; if not namespaced,
|
||||
// this could be anything not defined as a feature. This handles the case
|
||||
// where no such dependency is actually defined; again, the branch on
|
||||
// namespaced here is just to provide the correct string in the error.
|
||||
(&Crate(ref dep), false, _) => {
|
||||
if namespaced {
|
||||
anyhow::bail!(
|
||||
"Feature `{}` includes `crate:{}` which is not a known \
|
||||
dependency",
|
||||
CrateFeature { dep_name, .. } => {
|
||||
// Validation of the feature name will be performed in the resolver.
|
||||
if !is_any_dep {
|
||||
bail!(
|
||||
"feature `{}` includes `{}`, but `{}` is not a dependency",
|
||||
feature,
|
||||
dep
|
||||
)
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Feature `{}` includes `{}` which is neither a dependency nor \
|
||||
another feature",
|
||||
feature,
|
||||
dep
|
||||
)
|
||||
fv,
|
||||
dep_name
|
||||
);
|
||||
}
|
||||
}
|
||||
(&Crate(_), true, true) => {}
|
||||
// If the value is a feature for one of the dependencies, bail out if no such
|
||||
// dependency is actually defined in the manifest.
|
||||
(&CrateFeature(ref dep, _), false, _) => anyhow::bail!(
|
||||
"Feature `{}` requires a feature of `{}` which is not a \
|
||||
dependency",
|
||||
feature,
|
||||
dep
|
||||
),
|
||||
(&CrateFeature(_, _), true, _) => {}
|
||||
}
|
||||
values.push(val);
|
||||
}
|
||||
|
||||
if !dependency_found {
|
||||
// If we have not found the dependency of the same-named feature, we should
|
||||
// bail here.
|
||||
anyhow::bail!(
|
||||
"Feature `{}` includes the optional dependency of the \
|
||||
same name, but this is left implicit in the features \
|
||||
included by this feature.\nConsider adding \
|
||||
`crate:{}` to this feature's requirements.",
|
||||
feature,
|
||||
feature
|
||||
)
|
||||
}
|
||||
|
||||
map.insert(InternedString::new(feature.borrow()), values);
|
||||
}
|
||||
Ok(map)
|
||||
|
||||
// Make sure every optional dep is mentioned at least once.
|
||||
let used: HashSet<_> = map
|
||||
.values()
|
||||
.flatten()
|
||||
.filter_map(|fv| match fv {
|
||||
Crate { dep_name } | CrateFeature { dep_name, .. } => Some(dep_name),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
if let Some(dep) = dependencies
|
||||
.iter()
|
||||
.find(|dep| dep.is_optional() && !used.contains(&dep.name_in_toml()))
|
||||
{
|
||||
bail!(
|
||||
"optional dependency `{}` is not included in any feature\n\
|
||||
Make sure that `crate:{}` is included in one of features in the [features] table.",
|
||||
dep.name_in_toml(),
|
||||
dep.name_in_toml(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok((map, has_namespaced_features))
|
||||
}
|
||||
|
||||
/// FeatureValue represents the types of dependencies a feature can have:
|
||||
///
|
||||
/// * Another feature
|
||||
/// * An optional dependency
|
||||
/// * A feature in a dependency
|
||||
///
|
||||
/// The selection between these 3 things happens as part of the construction of the FeatureValue.
|
||||
/// FeatureValue represents the types of dependencies a feature can have.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FeatureValue {
|
||||
/// A feature enabling another feature.
|
||||
Feature(InternedString),
|
||||
Crate(InternedString),
|
||||
CrateFeature(InternedString, InternedString),
|
||||
/// A feature enabling a dependency with `crate:dep_name` syntax.
|
||||
Crate { dep_name: InternedString },
|
||||
/// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
|
||||
CrateFeature {
|
||||
dep_name: InternedString,
|
||||
dep_feature: InternedString,
|
||||
/// If this is true, then the feature used the `crate:` prefix, which
|
||||
/// prevents enabling the feature named `dep_name`.
|
||||
explicit: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl FeatureValue {
|
||||
fn build<T>(feature: InternedString, is_feature: T, namespaced: bool) -> FeatureValue
|
||||
where
|
||||
T: Fn(InternedString) -> bool,
|
||||
{
|
||||
match (feature.find('/'), namespaced) {
|
||||
(Some(pos), _) => {
|
||||
pub fn new(feature: InternedString) -> FeatureValue {
|
||||
match feature.find('/') {
|
||||
Some(pos) => {
|
||||
let (dep, dep_feat) = feature.split_at(pos);
|
||||
let dep_feat = &dep_feat[1..];
|
||||
FeatureValue::CrateFeature(InternedString::new(dep), InternedString::new(dep_feat))
|
||||
}
|
||||
(None, true) if feature.starts_with("crate:") => {
|
||||
FeatureValue::Crate(InternedString::new(&feature[6..]))
|
||||
}
|
||||
(None, true) => FeatureValue::Feature(feature),
|
||||
(None, false) if is_feature(feature) => FeatureValue::Feature(feature),
|
||||
(None, false) => FeatureValue::Crate(feature),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(feature: InternedString, s: &Summary) -> FeatureValue {
|
||||
Self::build(
|
||||
feature,
|
||||
|fs| s.features().contains_key(&fs),
|
||||
s.namespaced_features(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_string(&self, s: &Summary) -> String {
|
||||
use self::FeatureValue::*;
|
||||
match *self {
|
||||
Feature(ref f) => f.to_string(),
|
||||
Crate(ref c) => {
|
||||
if s.namespaced_features() {
|
||||
format!("crate:{}", &c)
|
||||
let (dep, explicit) = if dep.starts_with("crate:") {
|
||||
(&dep[6..], true)
|
||||
} else {
|
||||
c.to_string()
|
||||
(dep, false)
|
||||
};
|
||||
FeatureValue::CrateFeature {
|
||||
dep_name: InternedString::new(dep),
|
||||
dep_feature: InternedString::new(dep_feat),
|
||||
explicit,
|
||||
}
|
||||
}
|
||||
CrateFeature(ref c, ref f) => [c.as_ref(), f.as_ref()].join("/"),
|
||||
None if feature.starts_with("crate:") => FeatureValue::Crate {
|
||||
dep_name: InternedString::new(&feature[6..]),
|
||||
},
|
||||
None => FeatureValue::Feature(feature),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this feature explicitly used `crate:` syntax.
|
||||
pub fn is_explicit_crate(&self) -> bool {
|
||||
matches!(self, FeatureValue::Crate{..} | FeatureValue::CrateFeature{explicit:true, ..})
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for FeatureValue {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
impl fmt::Display for FeatureValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use self::FeatureValue::*;
|
||||
match *self {
|
||||
Feature(ref f) => serializer.serialize_str(f),
|
||||
Crate(ref c) => serializer.serialize_str(c),
|
||||
CrateFeature(ref c, ref f) => {
|
||||
serializer.serialize_str(&[c.as_ref(), f.as_ref()].join("/"))
|
||||
}
|
||||
match self {
|
||||
Feature(feat) => write!(f, "{}", feat),
|
||||
Crate { dep_name } => write!(f, "crate:{}", dep_name),
|
||||
CrateFeature {
|
||||
dep_name,
|
||||
dep_feature,
|
||||
explicit: true,
|
||||
} => write!(f, "crate:{}/{}", dep_name, dep_feature),
|
||||
CrateFeature {
|
||||
dep_name,
|
||||
dep_feature,
|
||||
explicit: false,
|
||||
} => write!(f, "{}/{}", dep_name, dep_feature),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1006,8 +1006,9 @@ fn generate_targets(
|
|||
{
|
||||
let unavailable_features = match target.required_features() {
|
||||
Some(rf) => {
|
||||
warn_on_missing_features(
|
||||
validate_required_features(
|
||||
workspace_resolve,
|
||||
target.name(),
|
||||
rf,
|
||||
pkg.summary(),
|
||||
&mut config.shell(),
|
||||
|
@ -1042,8 +1043,13 @@ fn generate_targets(
|
|||
Ok(units.into_iter().collect())
|
||||
}
|
||||
|
||||
fn warn_on_missing_features(
|
||||
/// Warns if a target's required-features references a feature that doesn't exist.
|
||||
///
|
||||
/// This is a warning because historically this was not validated, and it
|
||||
/// would cause too much breakage to make it an error.
|
||||
fn validate_required_features(
|
||||
resolve: &Option<Resolve>,
|
||||
target_name: &str,
|
||||
required_features: &[String],
|
||||
summary: &Summary,
|
||||
shell: &mut Shell,
|
||||
|
@ -1054,47 +1060,55 @@ fn warn_on_missing_features(
|
|||
};
|
||||
|
||||
for feature in required_features {
|
||||
match FeatureValue::new(feature.into(), summary) {
|
||||
// No need to do anything here, since the feature must exist to be parsed as such
|
||||
FeatureValue::Feature(_) => {}
|
||||
// Possibly mislabeled feature that was not found
|
||||
FeatureValue::Crate(krate) => {
|
||||
if !summary
|
||||
.dependencies()
|
||||
.iter()
|
||||
.any(|dep| dep.name_in_toml() == krate && dep.is_optional())
|
||||
{
|
||||
let fv = FeatureValue::new(feature.into());
|
||||
match &fv {
|
||||
FeatureValue::Feature(f) => {
|
||||
if !summary.features().contains_key(f) {
|
||||
shell.warn(format!(
|
||||
"feature `{}` is not present in [features] section.",
|
||||
krate
|
||||
"invalid feature `{}` in required-features of target `{}`: \
|
||||
`{}` is not present in [features] section",
|
||||
fv, target_name, fv
|
||||
))?;
|
||||
}
|
||||
}
|
||||
FeatureValue::Crate { .. } | FeatureValue::CrateFeature { explicit: true, .. } => {
|
||||
anyhow::bail!(
|
||||
"invalid feature `{}` in required-features of target `{}`: \
|
||||
explicit `crate:` feature values are not allowed in required-features",
|
||||
fv,
|
||||
target_name
|
||||
);
|
||||
}
|
||||
// Handling of dependent_crate/dependent_crate_feature syntax
|
||||
FeatureValue::CrateFeature(krate, feature) => {
|
||||
FeatureValue::CrateFeature {
|
||||
dep_name,
|
||||
dep_feature,
|
||||
explicit: false,
|
||||
} => {
|
||||
match resolve
|
||||
.deps(summary.package_id())
|
||||
.find(|(_dep_id, deps)| deps.iter().any(|dep| dep.name_in_toml() == krate))
|
||||
.find(|(_dep_id, deps)| deps.iter().any(|dep| dep.name_in_toml() == *dep_name))
|
||||
{
|
||||
Some((dep_id, _deps)) => {
|
||||
let dep_summary = resolve.summary(dep_id);
|
||||
if !dep_summary.features().contains_key(&feature)
|
||||
if !dep_summary.features().contains_key(dep_feature)
|
||||
&& !dep_summary
|
||||
.dependencies()
|
||||
.iter()
|
||||
.any(|dep| dep.name_in_toml() == feature && dep.is_optional())
|
||||
.any(|dep| dep.name_in_toml() == *dep_feature && dep.is_optional())
|
||||
{
|
||||
shell.warn(format!(
|
||||
"feature `{}` does not exist in package `{}`.",
|
||||
feature, dep_id
|
||||
"invalid feature `{}` in required-features of target `{}`: \
|
||||
feature `{}` does not exist in package `{}`",
|
||||
fv, target_name, dep_feature, dep_id
|
||||
))?;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
shell.warn(format!(
|
||||
"dependency `{}` specified in required-features as `{}/{}` \
|
||||
does not exist.",
|
||||
krate, krate, feature
|
||||
"invalid feature `{}` in required-features of target `{}`: \
|
||||
dependency `{}` does not exist",
|
||||
fv, target_name, dep_name
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>>>();
|
||||
|
|
|
@ -340,7 +340,11 @@ fn add_pkg(
|
|||
if dep.is_optional() {
|
||||
// If the new feature resolver does not enable this
|
||||
// optional dep, then don't use it.
|
||||
if !node_features.contains(&dep.name_in_toml()) {
|
||||
if !resolved_features.is_dep_activated(
|
||||
package_id,
|
||||
features_for,
|
||||
dep.name_in_toml(),
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -542,10 +546,10 @@ fn add_feature_rec(
|
|||
};
|
||||
for fv in fvs {
|
||||
match fv {
|
||||
FeatureValue::Feature(fv_name) | FeatureValue::Crate(fv_name) => {
|
||||
FeatureValue::Feature(dep_name) => {
|
||||
let feat_index = add_feature(
|
||||
graph,
|
||||
*fv_name,
|
||||
*dep_name,
|
||||
Some(from),
|
||||
package_index,
|
||||
EdgeKind::Feature,
|
||||
|
@ -553,13 +557,18 @@ fn add_feature_rec(
|
|||
add_feature_rec(
|
||||
graph,
|
||||
resolve,
|
||||
*fv_name,
|
||||
*dep_name,
|
||||
package_id,
|
||||
feat_index,
|
||||
package_index,
|
||||
);
|
||||
}
|
||||
FeatureValue::CrateFeature(dep_name, fv_name) => {
|
||||
FeatureValue::Crate { .. } => {}
|
||||
FeatureValue::CrateFeature {
|
||||
dep_name,
|
||||
dep_feature,
|
||||
explicit,
|
||||
} => {
|
||||
let dep_indexes = match graph.dep_name_map[&package_index].get(dep_name) {
|
||||
Some(indexes) => indexes.clone(),
|
||||
None => {
|
||||
|
@ -569,14 +578,14 @@ fn add_feature_rec(
|
|||
feature_name,
|
||||
package_id,
|
||||
dep_name,
|
||||
fv_name
|
||||
dep_feature
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
for (dep_index, is_optional) in dep_indexes {
|
||||
let dep_pkg_id = graph.package_id_for_index(dep_index);
|
||||
if is_optional {
|
||||
if is_optional && !explicit {
|
||||
// Activate the optional dep on self.
|
||||
add_feature(
|
||||
graph,
|
||||
|
@ -586,9 +595,21 @@ fn add_feature_rec(
|
|||
EdgeKind::Feature,
|
||||
);
|
||||
}
|
||||
let feat_index =
|
||||
add_feature(graph, *fv_name, Some(from), dep_index, EdgeKind::Feature);
|
||||
add_feature_rec(graph, resolve, *fv_name, dep_pkg_id, feat_index, dep_index);
|
||||
let feat_index = add_feature(
|
||||
graph,
|
||||
*dep_feature,
|
||||
Some(from),
|
||||
dep_index,
|
||||
EdgeKind::Feature,
|
||||
);
|
||||
add_feature_rec(
|
||||
graph,
|
||||
resolve,
|
||||
*dep_feature,
|
||||
dep_pkg_id,
|
||||
feat_index,
|
||||
dep_index,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -191,29 +191,62 @@ lto = true
|
|||
* Original issue: [#1286](https://github.com/rust-lang/cargo/issues/1286)
|
||||
* Tracking Issue: [#5565](https://github.com/rust-lang/cargo/issues/5565)
|
||||
|
||||
Currently, it is not possible to have a feature and a dependency with the same
|
||||
name in the manifest. If you set `namespaced-features` to `true`, the namespaces
|
||||
for features and dependencies are separated. The effect of this is that, in the
|
||||
feature requirements, dependencies have to be prefixed with `crate:`. Like this:
|
||||
The `namespaced-features` option makes two changes to how features can be
|
||||
specified:
|
||||
|
||||
* Features may now be defined with the same name as a dependency.
|
||||
* Optional dependencies can be explicitly enabled in the `[features]` table
|
||||
with the `crate:` prefix, which enables the dependency without enabling a
|
||||
feature of the same name.
|
||||
|
||||
By default, an optional dependency `foo` will define a feature `foo =
|
||||
["crate:foo"]` *unless* `crate:foo` is mentioned in any other feature, or the
|
||||
`foo` feature is already defined. This helps prevent unnecessary boilerplate
|
||||
of listing every optional dependency, but still allows you to override the
|
||||
implicit feature.
|
||||
|
||||
This allows two use cases that were previously not possible:
|
||||
|
||||
* You can "hide" an optional dependency, so that external users cannot
|
||||
explicitly enable that optional dependency.
|
||||
* There is no longer a need to create "funky" feature names to work around the
|
||||
restriction that features cannot shadow dependency names.
|
||||
|
||||
To enable namespaced-features, use the `-Z namespaced-features` command-line
|
||||
flag.
|
||||
|
||||
An example of hiding an optional dependency:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
namespaced-features = true
|
||||
[dependencies]
|
||||
regex = { version = "1.4.1", optional = true }
|
||||
lazy_static = { version = "1.4.0", optional = true }
|
||||
|
||||
[features]
|
||||
bar = ["crate:baz", "foo"]
|
||||
foo = []
|
||||
|
||||
[dependencies]
|
||||
baz = { version = "0.1", optional = true }
|
||||
regex = ["crate:regex", "crate:lazy_static"]
|
||||
```
|
||||
|
||||
To prevent unnecessary boilerplate from having to explicitly declare features
|
||||
for each optional dependency, implicit features get created for any optional
|
||||
dependencies where a feature of the same name is not defined. However, if
|
||||
a feature of the same name as a dependency is defined, that feature must
|
||||
include the dependency as a requirement, as `foo = ["crate:foo"]`.
|
||||
In this example, the "regex" feature enables both `regex` and `lazy_static`.
|
||||
The `lazy_static` feature does not exist, and a user cannot explicitly enable
|
||||
it. This helps hide internal details of how your package is implemented.
|
||||
|
||||
An example of avoiding "funky" names:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
bigdecimal = "0.1"
|
||||
chrono = "0.4"
|
||||
num-bigint = "0.2"
|
||||
serde = {version = "1.0", optional = true }
|
||||
|
||||
[features]
|
||||
serde = ["crate:serde", "bigdecimal/serde", "chrono/serde", "num-bigint/serde"]
|
||||
```
|
||||
|
||||
In this case, `serde` is a natural name to use for a feature, because it is
|
||||
relevant to your exported API. However, previously you would need to use a
|
||||
name like `serde1` to work around the naming limitation if you wanted to also
|
||||
enable other features.
|
||||
|
||||
### Build-plan
|
||||
* Tracking Issue: [#5579](https://github.com/rust-lang/cargo/issues/5579)
|
||||
|
|
|
@ -29,7 +29,7 @@ fn invalid1() {
|
|||
[ERROR] failed to parse manifest at `[..]`
|
||||
|
||||
Caused by:
|
||||
Feature `bar` includes `baz` which is neither a dependency nor another feature
|
||||
feature `bar` includes `baz` which is neither a dependency nor another feature
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -48,12 +48,15 @@ fn invalid2() {
|
|||
|
||||
[features]
|
||||
bar = ["baz"]
|
||||
baz = []
|
||||
|
||||
[dependencies.bar]
|
||||
path = "foo"
|
||||
path = "bar"
|
||||
"#,
|
||||
)
|
||||
.file("src/main.rs", "")
|
||||
.file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0"))
|
||||
.file("bar/src/lib.rs", "")
|
||||
.build();
|
||||
|
||||
p.cargo("build")
|
||||
|
@ -63,7 +66,7 @@ fn invalid2() {
|
|||
[ERROR] failed to parse manifest at `[..]`
|
||||
|
||||
Caused by:
|
||||
Features and dependencies cannot have the same name: `bar`
|
||||
features and dependencies cannot have the same name: `bar`
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -97,8 +100,8 @@ fn invalid3() {
|
|||
[ERROR] failed to parse manifest at `[..]`
|
||||
|
||||
Caused by:
|
||||
Feature `bar` depends on `baz` which is not an optional dependency.
|
||||
Consider adding `optional = true` to the dependency
|
||||
feature `bar` includes `baz`, but `baz` is not an optional dependency
|
||||
A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition.
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -144,7 +147,7 @@ failed to select a version for `bar` which could resolve this conflict",
|
|||
|
||||
p.cargo("build --features test")
|
||||
.with_status(101)
|
||||
.with_stderr("error: Package `foo v0.0.1 ([..])` does not have these features: `test`")
|
||||
.with_stderr("error: Package `foo v0.0.1 ([..])` does not have the feature `test`")
|
||||
.run();
|
||||
}
|
||||
|
||||
|
@ -174,7 +177,7 @@ fn invalid5() {
|
|||
[ERROR] failed to parse manifest at `[..]`
|
||||
|
||||
Caused by:
|
||||
Dev-dependencies are not allowed to be optional: `bar`
|
||||
dev-dependencies are not allowed to be optional: `bar`
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -205,7 +208,7 @@ fn invalid6() {
|
|||
[ERROR] failed to parse manifest at `[..]`
|
||||
|
||||
Caused by:
|
||||
Feature `foo` requires a feature of `bar` which is not a dependency
|
||||
feature `foo` includes `bar/baz`, but `bar` is not a dependency
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -237,7 +240,7 @@ fn invalid7() {
|
|||
[ERROR] failed to parse manifest at `[..]`
|
||||
|
||||
Caused by:
|
||||
Feature `foo` requires a feature of `bar` which is not a dependency
|
||||
feature `foo` includes `bar/baz`, but `bar` is not a dependency
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -1183,7 +1186,7 @@ fn dep_feature_in_cmd_line() {
|
|||
// Trying to enable features of transitive dependencies is an error
|
||||
p.cargo("build --features bar/some-feat")
|
||||
.with_status(101)
|
||||
.with_stderr("error: Package `foo v0.0.1 ([..])` does not have these features: `bar`")
|
||||
.with_stderr("error: package `foo v0.0.1 ([..])` does not have a dependency named `bar`")
|
||||
.run();
|
||||
|
||||
// Hierarchical feature specification should still be disallowed
|
||||
|
@ -1959,9 +1962,14 @@ fn nonexistent_required_features() {
|
|||
|
||||
p.cargo("build --examples")
|
||||
.with_stderr_contains(
|
||||
"[WARNING] feature `not_present` is not present in [features] section.
|
||||
[WARNING] feature `not_existing` does not exist in package `required_dependency v0.1.0`.
|
||||
[WARNING] dependency `not_specified_dependency` specified in required-features as `not_specified_dependency/some_feature` does not exist.
|
||||
"\
|
||||
[WARNING] invalid feature `not_present` in required-features of target `ololo`: \
|
||||
`not_present` is not present in [features] section
|
||||
[WARNING] invalid feature `required_dependency/not_existing` in required-features \
|
||||
of target `ololo`: feature `not_existing` does not exist in package \
|
||||
`required_dependency v0.1.0`
|
||||
[WARNING] invalid feature `not_specified_dependency/some_feature` in required-features \
|
||||
of target `ololo`: dependency `not_specified_dependency` does not exist
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
|
|
@ -96,6 +96,7 @@ fn inactive_target_optional() {
|
|||
|
||||
[features]
|
||||
foo1 = ["dep1/f2"]
|
||||
foo2 = ["dep2"]
|
||||
"#,
|
||||
)
|
||||
.file(
|
||||
|
@ -103,6 +104,7 @@ fn inactive_target_optional() {
|
|||
r#"
|
||||
fn main() {
|
||||
if cfg!(feature="foo1") { println!("foo1"); }
|
||||
if cfg!(feature="foo2") { println!("foo2"); }
|
||||
if cfg!(feature="dep1") { println!("dep1"); }
|
||||
if cfg!(feature="dep2") { println!("dep2"); }
|
||||
if cfg!(feature="common") { println!("common"); }
|
||||
|
@ -149,7 +151,7 @@ fn inactive_target_optional() {
|
|||
.build();
|
||||
|
||||
p.cargo("run --all-features")
|
||||
.with_stdout("foo1\ndep1\ndep2\ncommon\nf1\nf2\nf3\nf4\n")
|
||||
.with_stdout("foo1\nfoo2\ndep1\ndep2\ncommon\nf1\nf2\nf3\nf4\n")
|
||||
.run();
|
||||
p.cargo("run --features dep1")
|
||||
.with_stdout("dep1\nf1\n")
|
||||
|
@ -166,7 +168,7 @@ fn inactive_target_optional() {
|
|||
|
||||
p.cargo("run -Zfeatures=itarget --all-features")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stdout("foo1\n")
|
||||
.with_stdout("foo1\nfoo2\ndep1\ndep2\ncommon")
|
||||
.run();
|
||||
p.cargo("run -Zfeatures=itarget --features dep1")
|
||||
.masquerade_as_nightly_cargo()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue