Auto merge of #9574 - ehuss:weak-namespaced, r=alexcrichton

Unify weak and namespaced features.

This unifies weak and namespaced features in order to simplify the syntax and semantics.  Previously there were four different ways to specify the feature of a dependency:

* `package-name/feature-name` — Enables feature `package-name` on self and enables `feature-name` on the dependency. (Today's behavior.)
* `package-name?/feature-name` — Only enables `feature-name` on the given package if it that package is enabled and will also activates a feature named `package-name` (which must be defined implicitly or explicitly).
* `dep:package-name/feature-name` — Enables dependency `package-name`, and enables `feature-name` on that dependency. This does NOT enable a feature named "package-name".
* `dep:package-name?/feature-name` — Only enables `feature-name` on the given package if it that package is enabled.  This does NOT enable a feature named "package-name".

This changes it so there are only two:

* `package-name/feature-name` — Today's behavior.
* `package-name?/feature-name` — Only enables `feature-name` on the given package if it that package is enabled.  This does NOT enable a feature named "package-name" (the same behavior as `dep:package-name?/feature-name` above).

This is a fairly subtle change, and in most cases probably won't be noticed.  However, it simplifies things which helps with writing documentation and explaining how it works.
This commit is contained in:
bors 2021-06-22 21:02:31 +00:00
commit a2589dda38
8 changed files with 74 additions and 205 deletions

View File

@ -405,12 +405,12 @@ impl Requirements<'_> {
&mut self, &mut self,
package: InternedString, package: InternedString,
feat: InternedString, feat: InternedString,
dep_prefix: bool, weak: bool,
) -> Result<(), RequirementError> { ) -> Result<(), RequirementError> {
// If `package` is indeed an optional dependency then we activate the // If `package` is indeed an optional dependency then we activate the
// feature named `package`, but otherwise if `package` is a required // feature named `package`, but otherwise if `package` is a required
// dependency then there's no feature associated with it. // dependency then there's no feature associated with it.
if !dep_prefix if !weak
&& self && self
.summary .summary
.dependencies() .dependencies()
@ -456,12 +456,11 @@ impl Requirements<'_> {
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name, dep_name,
dep_feature, dep_feature,
dep_prefix,
// Weak features are always activated in the dependency // Weak features are always activated in the dependency
// resolver. They will be narrowed inside the new feature // resolver. They will be narrowed inside the new feature
// resolver. // resolver.
weak: _, weak,
} => self.require_dep_feature(*dep_name, *dep_feature, *dep_prefix)?, } => self.require_dep_feature(*dep_name, *dep_feature, *weak)?,
}; };
Ok(()) Ok(())
} }

View File

@ -244,10 +244,7 @@ impl CliFeatures {
match feature { match feature {
// Maybe call validate_feature_name here once it is an error? // Maybe call validate_feature_name here once it is an error?
FeatureValue::Feature(_) => {} FeatureValue::Feature(_) => {}
FeatureValue::Dep { .. } FeatureValue::Dep { .. } => {
| FeatureValue::DepFeature {
dep_prefix: true, ..
} => {
bail!( bail!(
"feature `{}` is not allowed to use explicit `dep:` syntax", "feature `{}` is not allowed to use explicit `dep:` syntax",
feature feature
@ -441,10 +438,8 @@ pub struct FeatureResolver<'a, 'cfg> {
/// ///
/// The key is the `(package, for_host, dep_name)` of the package whose /// The key is the `(package, for_host, dep_name)` of the package whose
/// dependency will trigger the addition of new features. The value is the /// dependency will trigger the addition of new features. The value is the
/// set of `(feature, dep_prefix)` features to activate (`dep_prefix` is a /// set of features to activate.
/// bool that indicates if `dep:` prefix was used). deferred_weak_dependencies: HashMap<(PackageId, bool, InternedString), HashSet<InternedString>>,
deferred_weak_dependencies:
HashMap<(PackageId, bool, InternedString), HashSet<(InternedString, bool)>>,
} }
impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
@ -591,17 +586,9 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name, dep_name,
dep_feature, dep_feature,
dep_prefix,
weak, weak,
} => { } => {
self.activate_dep_feature( self.activate_dep_feature(pkg_id, for_host, *dep_name, *dep_feature, *weak)?;
pkg_id,
for_host,
*dep_name,
*dep_feature,
*dep_prefix,
*weak,
)?;
} }
} }
Ok(()) Ok(())
@ -675,7 +662,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
continue; continue;
} }
if let Some(to_enable) = &to_enable { if let Some(to_enable) = &to_enable {
for (dep_feature, dep_prefix) in to_enable { for dep_feature in to_enable {
log::trace!( log::trace!(
"activate deferred {} {} -> {}/{}", "activate deferred {} {} -> {}/{}",
pkg_id.name(), pkg_id.name(),
@ -683,9 +670,6 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
dep_name, dep_name,
dep_feature dep_feature
); );
if !dep_prefix {
self.activate_rec(pkg_id, for_host, dep_name)?;
}
let fv = FeatureValue::new(*dep_feature); let fv = FeatureValue::new(*dep_feature);
self.activate_fv(dep_pkg_id, dep_for_host, &fv)?; self.activate_fv(dep_pkg_id, dep_for_host, &fv)?;
} }
@ -704,7 +688,6 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
for_host: bool, for_host: bool,
dep_name: InternedString, dep_name: InternedString,
dep_feature: InternedString, dep_feature: InternedString,
dep_prefix: bool,
weak: bool, weak: bool,
) -> CargoResult<()> { ) -> CargoResult<()> {
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) { for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
@ -733,16 +716,16 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
self.deferred_weak_dependencies self.deferred_weak_dependencies
.entry((pkg_id, for_host, dep_name)) .entry((pkg_id, for_host, dep_name))
.or_default() .or_default()
.insert((dep_feature, dep_prefix)); .insert(dep_feature);
continue; continue;
} }
// Activate the dependency on self. // Activate the dependency on self.
let fv = FeatureValue::Dep { dep_name }; let fv = FeatureValue::Dep { dep_name };
self.activate_fv(pkg_id, for_host, &fv)?; self.activate_fv(pkg_id, for_host, &fv)?;
if !dep_prefix { if !weak {
// To retain compatibility with old behavior, // The old behavior before weak dependencies were
// this also enables a feature of the same // added is to also enables a feature of the same
// name. // name.
self.activate_rec(pkg_id, for_host, dep_name)?; self.activate_rec(pkg_id, for_host, dep_name)?;
} }

View File

@ -218,12 +218,7 @@ fn build_feature_map(
.values() .values()
.flatten() .flatten()
.filter_map(|fv| match fv { .filter_map(|fv| match fv {
Dep { dep_name } Dep { dep_name } => Some(*dep_name),
| DepFeature {
dep_name,
dep_prefix: true,
..
} => Some(*dep_name),
_ => None, _ => None,
}) })
.collect(); .collect();
@ -391,9 +386,6 @@ pub enum FeatureValue {
DepFeature { DepFeature {
dep_name: InternedString, dep_name: InternedString,
dep_feature: 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,
/// If `true`, indicates the `?` syntax is used, which means this will /// If `true`, indicates the `?` syntax is used, which means this will
/// not automatically enable the dependency unless the dependency is /// not automatically enable the dependency unless the dependency is
/// activated through some other means. /// activated through some other means.
@ -407,11 +399,6 @@ impl FeatureValue {
Some(pos) => { Some(pos) => {
let (dep, dep_feat) = feature.split_at(pos); let (dep, dep_feat) = feature.split_at(pos);
let dep_feat = &dep_feat[1..]; let dep_feat = &dep_feat[1..];
let (dep, dep_prefix) = if let Some(dep) = dep.strip_prefix("dep:") {
(dep, true)
} else {
(dep, false)
};
let (dep, weak) = if let Some(dep) = dep.strip_suffix('?') { let (dep, weak) = if let Some(dep) = dep.strip_suffix('?') {
(dep, true) (dep, true)
} else { } else {
@ -420,7 +407,6 @@ impl FeatureValue {
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name: InternedString::new(dep), dep_name: InternedString::new(dep),
dep_feature: InternedString::new(dep_feat), dep_feature: InternedString::new(dep_feat),
dep_prefix,
weak, weak,
} }
} }
@ -438,14 +424,7 @@ impl FeatureValue {
/// Returns `true` if this feature explicitly used `dep:` syntax. /// Returns `true` if this feature explicitly used `dep:` syntax.
pub fn has_dep_prefix(&self) -> bool { pub fn has_dep_prefix(&self) -> bool {
matches!( matches!(self, FeatureValue::Dep { .. })
self,
FeatureValue::Dep { .. }
| FeatureValue::DepFeature {
dep_prefix: true,
..
}
)
} }
} }
@ -458,12 +437,10 @@ impl fmt::Display for FeatureValue {
DepFeature { DepFeature {
dep_name, dep_name,
dep_feature, dep_feature,
dep_prefix,
weak, weak,
} => { } => {
let dep_prefix = if *dep_prefix { "dep:" } else { "" };
let weak = if *weak { "?" } else { "" }; let weak = if *weak { "?" } else { "" };
write!(f, "{}{}{}/{}", dep_prefix, dep_name, weak, dep_feature) write!(f, "{}{}/{}", dep_name, weak, dep_feature)
} }
} }
} }

View File

@ -1163,14 +1163,10 @@ impl<'cfg> Workspace<'cfg> {
} }
} }
// This should be enforced by CliFeatures. // This should be enforced by CliFeatures.
FeatureValue::Dep { .. } FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
| FeatureValue::DepFeature {
dep_prefix: true, ..
} => panic!("unexpected dep: syntax {}", feature),
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name, dep_name,
dep_feature, dep_feature,
dep_prefix: _,
weak: _, weak: _,
} => { } => {
if dependencies.contains_key(dep_name) { if dependencies.contains_key(dep_name) {
@ -1283,14 +1279,10 @@ impl<'cfg> Workspace<'cfg> {
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
FeatureValue::Dep { .. } FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
| FeatureValue::DepFeature {
dep_prefix: true, ..
} => panic!("unexpected dep: syntax {}", feature),
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name, dep_name,
dep_feature, dep_feature,
dep_prefix: _,
weak: _, weak: _,
} => { } => {
// Finds set of `pkg/feat` that are very similar to current `pkg/feat`. // Finds set of `pkg/feat` that are very similar to current `pkg/feat`.
@ -1446,14 +1438,10 @@ impl<'cfg> Workspace<'cfg> {
cwd_features.insert(feature.clone()); cwd_features.insert(feature.clone());
} }
// This should be enforced by CliFeatures. // This should be enforced by CliFeatures.
FeatureValue::Dep { .. } FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
| FeatureValue::DepFeature {
dep_prefix: true, ..
} => panic!("unexpected dep: syntax {}", feature),
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name, dep_name,
dep_feature, dep_feature,
dep_prefix: _,
weak: _, weak: _,
} => { } => {
// I think weak can be ignored here. // I think weak can be ignored here.

View File

@ -1225,10 +1225,7 @@ fn validate_required_features(
))?; ))?;
} }
} }
FeatureValue::Dep { .. } FeatureValue::Dep { .. } => {
| FeatureValue::DepFeature {
dep_prefix: true, ..
} => {
anyhow::bail!( anyhow::bail!(
"invalid feature `{}` in required-features of target `{}`: \ "invalid feature `{}` in required-features of target `{}`: \
`dep:` prefixed feature values are not allowed in required-features", `dep:` prefixed feature values are not allowed in required-features",
@ -1248,7 +1245,6 @@ fn validate_required_features(
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name, dep_name,
dep_feature, dep_feature,
dep_prefix: false,
weak: false, weak: false,
} => { } => {
match resolve match resolve

View File

@ -488,7 +488,6 @@ fn add_cli_features(
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name, dep_name,
dep_feature, dep_feature,
dep_prefix: _,
weak, weak,
} => { } => {
let dep_connections = match graph.dep_name_map[&package_index].get(&dep_name) { let dep_connections = match graph.dep_name_map[&package_index].get(&dep_name) {
@ -596,12 +595,12 @@ fn add_feature_rec(
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name, dep_name,
dep_feature, dep_feature,
dep_prefix, // Note: `weak` is mostly handled when the graph is built in
// `weak` is ignored, because it will be skipped if the // `is_dep_activated` which is responsible for skipping
// corresponding dependency is not found in the map, which // unactivated weak dependencies. Here it is only used to
// means it wasn't activated. Skipping is handled by // determine if the feature of the dependency name is
// `is_dep_activated` when the graph was built. // activated on self.
weak: _, weak,
} => { } => {
let dep_indexes = match graph.dep_name_map[&package_index].get(dep_name) { let dep_indexes = match graph.dep_name_map[&package_index].get(dep_name) {
Some(indexes) => indexes.clone(), Some(indexes) => indexes.clone(),
@ -619,7 +618,7 @@ fn add_feature_rec(
}; };
for (dep_index, is_optional) in dep_indexes { for (dep_index, is_optional) in dep_indexes {
let dep_pkg_id = graph.package_id_for_index(dep_index); let dep_pkg_id = graph.package_id_for_index(dep_index);
if is_optional && !dep_prefix { if is_optional && !weak {
// Activate the optional dep on self. // Activate the optional dep on self.
add_feature( add_feature(
graph, graph,

View File

@ -516,61 +516,6 @@ syntax in the features table, so it does not have an implicit feature with that
.run(); .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] #[cargo_test]
fn crate_syntax_bad_name() { fn crate_syntax_bad_name() {
// "dep:bar" = [] // "dep:bar" = []
@ -904,7 +849,6 @@ fn tree() {
[features] [features]
a = ["bar/feat2"] a = ["bar/feat2"]
b = ["dep:bar/feat2"]
bar = ["dep:bar"] bar = ["dep:bar"]
"#, "#,
) )
@ -950,38 +894,6 @@ bar v1.0.0
) )
.run(); .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") p.cargo("tree -e features --features bar -Z namespaced-features")
.masquerade_as_nightly_cargo() .masquerade_as_nightly_cargo()
.with_stdout( .with_stdout(

View File

@ -468,29 +468,12 @@ fn weak_with_host_decouple() {
} }
#[cargo_test] #[cargo_test]
fn deferred_with_namespaced() { fn weak_namespaced() {
// Interaction with -Z namespaced-features using dep: syntax. // Behavior with a dep: dependency.
//
// `bar` is deferred with bar?/feat
// `bar2` is deferred with dep:bar2?/feat
Package::new("bar", "1.0.0") Package::new("bar", "1.0.0")
.feature("feat", &[]) .feature("feat", &[])
.file("src/lib.rs", &require(&["feat"], &[])) .file("src/lib.rs", &require(&["feat"], &[]))
.publish(); .publish();
Package::new("bar2", "1.0.0")
.feature("feat", &[])
.file("src/lib.rs", &require(&["feat"], &[]))
.publish();
Package::new("bar_includer", "1.0.0")
.add_dep(Dependency::new("bar", "1.0").optional(true))
.add_dep(Dependency::new("bar2", "1.0").optional(true))
.feature("feat", &["bar?/feat", "dep:bar2?/feat"])
.feature("feat2", &["dep:bar2"])
.file("src/lib.rs", &require(&["bar"], &["bar2"]))
.publish();
Package::new("bar_activator", "1.0.0")
.feature_dep("bar_includer", "1.0", &["bar", "feat2"])
.publish();
let p = project() let p = project()
.file( .file(
"Cargo.toml", "Cargo.toml",
@ -500,27 +483,60 @@ fn deferred_with_namespaced() {
version = "0.1.0" version = "0.1.0"
[dependencies] [dependencies]
bar_includer = { version = "1.0", features = ["feat"] } bar = { version = "1.0", optional = true }
bar_activator = "1.0"
[features]
f1 = ["bar?/feat"]
f2 = ["dep:bar"]
"#, "#,
) )
.file("src/lib.rs", "") .file("src/lib.rs", &require(&["f1"], &["f2", "bar"]))
.build(); .build();
p.cargo("check -Z weak-dep-features -Z namespaced-features") p.cargo("check -Z weak-dep-features -Z namespaced-features --features f1")
.masquerade_as_nightly_cargo() .masquerade_as_nightly_cargo()
.with_stderr_unordered( .with_stderr(
"\ "\
[UPDATING] [..] [UPDATING] [..]
[DOWNLOADING] crates ... [DOWNLOADING] crates ...
[DOWNLOADED] [..] [DOWNLOADED] bar v1.0.0 [..]
[DOWNLOADED] [..] [CHECKING] foo v0.1.0 [..]
[DOWNLOADED] [..] [FINISHED] [..]
[DOWNLOADED] [..] ",
)
.run();
p.cargo("tree -Z weak-dep-features -Z namespaced-features -f")
.arg("{p} feats:{f}")
.masquerade_as_nightly_cargo()
.with_stdout("foo v0.1.0 ([ROOT]/foo) feats:")
.run();
p.cargo("tree -Z weak-dep-features -Z namespaced-features --features f1 -f")
.arg("{p} feats:{f}")
.masquerade_as_nightly_cargo()
.with_stdout("foo v0.1.0 ([ROOT]/foo) feats:f1")
.run();
p.cargo("tree -Z weak-dep-features -Z namespaced-features --features f1,f2 -f")
.arg("{p} feats:{f}")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo) feats:f1,f2
bar v1.0.0 feats:feat
",
)
.run();
// "bar" remains not-a-feature
p.change_file("src/lib.rs", &require(&["f1", "f2"], &["bar"]));
p.cargo("check -Z weak-dep-features -Z namespaced-features --features f1,f2")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[CHECKING] bar v1.0.0 [CHECKING] bar v1.0.0
[CHECKING] bar2 v1.0.0
[CHECKING] bar_includer v1.0.0
[CHECKING] bar_activator v1.0.0
[CHECKING] foo v0.1.0 [..] [CHECKING] foo v0.1.0 [..]
[FINISHED] [..] [FINISHED] [..]
", ",
@ -586,7 +602,6 @@ bar v1.0.0
bar feature \"default\" bar feature \"default\"
foo v0.1.0 ([ROOT]/foo) foo v0.1.0 ([ROOT]/foo)
foo feature \"bar\" (command-line) foo feature \"bar\" (command-line)
foo feature \"f1\" (command-line)
foo feature \"default\" (command-line) foo feature \"default\" (command-line)
foo feature \"f1\" (command-line) foo feature \"f1\" (command-line)
bar feature \"feat\" bar feature \"feat\"