Implement weak dependency features.

This commit is contained in:
Eric Huss 2020-10-25 16:34:03 -07:00
parent 28af36face
commit 9ffcf69093
12 changed files with 800 additions and 50 deletions

View File

@ -43,6 +43,7 @@ Available unstable (nightly-only) flags:
-Z doctest-xcompile -- Compile and run doctests for non-host target using runner config
-Z terminal-width -- Provide a terminal width to rustc for error truncation
-Z namespaced-features -- Allow features with `dep:` prefix
-Z weak-dep-features -- Allow `dep_name?/feature` feature syntax
Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
);

View File

@ -358,6 +358,7 @@ pub struct CliUnstable {
pub rustdoc_map: bool,
pub terminal_width: Option<Option<usize>>,
pub namespaced_features: bool,
pub weak_dep_features: bool,
}
fn deserialize_build_std<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
@ -464,6 +465,7 @@ impl CliUnstable {
"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)?,
"weak-dep-features" => self.weak_dep_features = parse_empty(k, v)?,
_ => bail!("unknown `-Z` flag specified: {}", k),
}

View File

@ -494,6 +494,10 @@ impl Requirements<'_> {
dep_name,
dep_feature,
dep_prefix,
// Weak features are always activated in the dependency
// resolver. They will be narrowed inside the new feature
// resolver.
weak: _,
} => self.require_dep_feature(*dep_name, *dep_feature, *dep_prefix)?,
};
Ok(())

View File

@ -177,6 +177,10 @@ impl FeatureOpts {
if let ForceAllTargets::Yes = force_all_targets {
opts.ignore_inactive_targets = false;
}
if unstable_flags.weak_dep_features {
// Force this ON because it only works with the new resolver.
opts.new_resolver = true;
}
Ok(opts)
}
}
@ -311,6 +315,15 @@ pub struct FeatureResolver<'a, 'cfg> {
/// This has to be separate from `FeatureOpts.decouple_host_deps` because
/// `for_host` tracking is also needed for `itarget` to work properly.
track_for_host: bool,
/// `dep_name?/feat_name` features that will be activated if `dep_name` is
/// ever activated.
///
/// The key is the `(package, for_host, dep_name)` of the package whose
/// dependency will trigger the addition of new features. The value is the
/// set of `(feature, dep_prefix)` features to activate (`dep_prefix` is a
/// bool that indicates if `dep:` prefix was used).
deferred_weak_dependencies:
HashMap<(PackageId, bool, InternedString), HashSet<(InternedString, bool)>>,
}
impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
@ -353,6 +366,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
activated_dependencies: HashMap::new(),
processed_deps: HashSet::new(),
track_for_host,
deferred_weak_dependencies: HashMap::new(),
};
r.do_resolve(specs, requested_features)?;
log::debug!("features={:#?}", r.activated_features);
@ -399,6 +413,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
for_host: bool,
fvs: &[FeatureValue],
) -> CargoResult<()> {
log::trace!("activate_pkg {} {}", pkg_id.name(), for_host);
// Add an empty entry to ensure everything is covered. This is intended for
// finding bugs where the resolver missed something it should have visited.
// Remove this in the future if `activated_features` uses an empty default.
@ -446,56 +461,28 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
for_host: bool,
fv: &FeatureValue,
) -> CargoResult<()> {
log::trace!("activate_fv {} {} {}", pkg_id.name(), for_host, fv);
match fv {
FeatureValue::Feature(f) => {
self.activate_rec(pkg_id, for_host, *f)?;
}
FeatureValue::Dep { dep_name } => {
// Mark this dependency as activated.
self.activated_dependencies
.entry((pkg_id, self.opts.decouple_host_deps && for_host))
.or_default()
.insert(*dep_name);
// Activate the optional dep.
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
for (dep, dep_for_host) in deps {
if dep.name_in_toml() != *dep_name {
continue;
}
let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
self.activate_pkg(dep_pkg_id, dep_for_host, &fvs)?;
}
}
self.activate_dependency(pkg_id, for_host, *dep_name)?;
}
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_prefix,
weak,
} => {
// 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 {
if dep.name_in_toml() != *dep_name {
continue;
}
if dep.is_optional() {
// Activate the dependency on self.
let fv = FeatureValue::Dep {
dep_name: *dep_name,
};
self.activate_fv(pkg_id, for_host, &fv)?;
if !dep_prefix {
// To retain compatibility with old behavior,
// this also enables a feature of the same
// name.
self.activate_rec(pkg_id, for_host, *dep_name)?;
}
}
// Activate the feature on the dependency.
let fv = FeatureValue::new(*dep_feature);
self.activate_fv(dep_pkg_id, dep_for_host, &fv)?;
}
}
self.activate_dep_feature(
pkg_id,
for_host,
*dep_name,
*dep_feature,
*dep_prefix,
*weak,
)?;
}
}
Ok(())
@ -509,6 +496,12 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
for_host: bool,
feature_to_enable: InternedString,
) -> CargoResult<()> {
log::trace!(
"activate_rec {} {} feat={}",
pkg_id.name(),
for_host,
feature_to_enable
);
let enabled = self
.activated_features
.entry((pkg_id, self.opts.decouple_host_deps && for_host))
@ -539,6 +532,110 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
Ok(())
}
/// Activate a dependency (`dep:dep_name` syntax).
fn activate_dependency(
&mut self,
pkg_id: PackageId,
for_host: bool,
dep_name: InternedString,
) -> CargoResult<()> {
// Mark this dependency as activated.
let save_for_host = self.opts.decouple_host_deps && for_host;
self.activated_dependencies
.entry((pkg_id, save_for_host))
.or_default()
.insert(dep_name);
// Check for any deferred features.
let to_enable = self
.deferred_weak_dependencies
.remove(&(pkg_id, for_host, 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 {
if dep.name_in_toml() != dep_name {
continue;
}
if let Some(to_enable) = &to_enable {
for (dep_feature, dep_prefix) in to_enable {
log::trace!(
"activate deferred {} {} -> {}/{}",
pkg_id.name(),
for_host,
dep_name,
dep_feature
);
if !dep_prefix {
self.activate_rec(pkg_id, for_host, dep_name)?;
}
let fv = FeatureValue::new(*dep_feature);
self.activate_fv(dep_pkg_id, dep_for_host, &fv)?;
}
}
let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
self.activate_pkg(dep_pkg_id, dep_for_host, &fvs)?;
}
}
Ok(())
}
/// Activate a feature within a dependency (`dep_name/feat_name` syntax).
fn activate_dep_feature(
&mut self,
pkg_id: PackageId,
for_host: bool,
dep_name: InternedString,
dep_feature: InternedString,
dep_prefix: bool,
weak: bool,
) -> CargoResult<()> {
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
for (dep, dep_for_host) in deps {
if dep.name_in_toml() != dep_name {
continue;
}
if dep.is_optional() {
let save_for_host = self.opts.decouple_host_deps && for_host;
if weak
&& !self
.activated_dependencies
.get(&(pkg_id, save_for_host))
.map(|deps| deps.contains(&dep_name))
.unwrap_or(false)
{
// This is weak, but not yet activated. Defer in case
// something comes along later and enables it.
log::trace!(
"deferring feature {} {} -> {}/{}",
pkg_id.name(),
for_host,
dep_name,
dep_feature
);
self.deferred_weak_dependencies
.entry((pkg_id, for_host, dep_name))
.or_default()
.insert((dep_feature, dep_prefix));
continue;
}
// Activate the dependency on self.
let fv = FeatureValue::Dep { dep_name };
self.activate_fv(pkg_id, for_host, &fv)?;
if !dep_prefix {
// To retain compatibility with old behavior,
// this also enables a feature of the same
// name.
self.activate_rec(pkg_id, for_host, dep_name)?;
}
}
// Activate the feature on the dependency.
let fv = FeatureValue::new(dep_feature);
self.activate_fv(dep_pkg_id, dep_for_host, &fv)?;
}
}
Ok(())
}
/// Returns Vec of FeatureValues from a Dependency definition.
fn fvs_from_dependency(&self, dep_id: PackageId, dep: &Dependency) -> Vec<FeatureValue> {
let summary = self.resolve.summary(dep_id);

View File

@ -86,7 +86,11 @@ impl Summary {
/// Returns an error if this Summary is using an unstable feature that is
/// not enabled.
pub fn unstable_gate(&self, namespaced_features: bool) -> CargoResult<()> {
pub fn unstable_gate(
&self,
namespaced_features: bool,
weak_dep_features: bool,
) -> CargoResult<()> {
if !namespaced_features {
if self.inner.has_namespaced_features {
bail!(
@ -101,6 +105,22 @@ impl Summary {
)
}
}
if !weak_dep_features {
for (feat_name, features) in self.features() {
for fv in features {
if matches!(fv, FeatureValue::DepFeature{weak: true, ..}) {
bail!(
"optional dependency features with `?` syntax are only \
allowed on the nightly channel and requires the \
`-Z weak-dep-features` flag on the command line\n\
Feature `{}` had feature value `{}`.",
feat_name,
fv
);
}
}
}
}
Ok(())
}
@ -293,7 +313,7 @@ fn build_feature_map(
);
}
}
DepFeature { dep_name, .. } => {
DepFeature { dep_name, weak, .. } => {
// Validation of the feature name will be performed in the resolver.
if !is_any_dep {
bail!(
@ -303,6 +323,12 @@ fn build_feature_map(
dep_name
);
}
if *weak && !is_optional_dep {
bail!("feature `{}` includes `{}` with a `?`, but `{}` is not an optional dependency\n\
A non-optional dependency of the same name is defined; \
consider removing the `?` or changing the dependency to be optional",
feature, fv, dep_name);
}
}
}
}
@ -346,6 +372,10 @@ pub enum FeatureValue {
/// 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
/// not automatically enable the dependency unless the dependency is
/// activated through some other means.
weak: bool,
},
}
@ -360,10 +390,16 @@ impl FeatureValue {
} else {
(dep, false)
};
let (dep, weak) = if let Some(dep) = dep.strip_suffix('?') {
(dep, true)
} else {
(dep, false)
};
FeatureValue::DepFeature {
dep_name: InternedString::new(dep),
dep_feature: InternedString::new(dep_feat),
dep_prefix,
weak,
}
}
None => {
@ -393,13 +429,13 @@ impl fmt::Display for FeatureValue {
DepFeature {
dep_name,
dep_feature,
dep_prefix: true,
} => write!(f, "dep:{}/{}", dep_name, dep_feature),
DepFeature {
dep_name,
dep_feature,
dep_prefix: false,
} => write!(f, "{}/{}", dep_name, dep_feature),
dep_prefix,
weak,
} => {
let dep_prefix = if *dep_prefix { "dep:" } else { "" };
let weak = if *weak { "?" } else { "" };
write!(f, "{}{}{}/{}", dep_prefix, dep_name, weak, dep_feature)
}
}
}
}

View File

@ -1082,11 +1082,20 @@ fn validate_required_features(
target_name
);
}
FeatureValue::DepFeature { weak: true, .. } => {
anyhow::bail!(
"invalid feature `{}` in required-features of target `{}`: \
optional dependency with `?` is not allowed in required-features",
fv,
target_name
);
}
// Handling of dependent_crate/dependent_crate_feature syntax
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_prefix: false,
weak: false,
} => {
match resolve
.deps(summary.package_id())

View File

@ -568,6 +568,11 @@ fn add_feature_rec(
dep_name,
dep_feature,
dep_prefix,
// `weak` is ignored, because it will be skipped if the
// corresponding dependency is not found in the map, which
// means it wasn't activated. Skipping is handled by
// `is_dep_activated` when the graph was built.
weak: _,
} => {
let dep_indexes = match graph.dep_name_map[&package_index].get(dep_name) {
Some(indexes) => indexes.clone(),

View File

@ -272,6 +272,7 @@ impl<'cfg> RegistryIndex<'cfg> {
let source_id = self.source_id;
let config = self.config;
let namespaced_features = self.config.cli_unstable().namespaced_features;
let weak_dep_features = self.config.cli_unstable().weak_dep_features;
// First up actually parse what summaries we have available. If Cargo
// has run previously this will parse a Cargo-specific cache file rather
@ -299,7 +300,11 @@ impl<'cfg> RegistryIndex<'cfg> {
}
},
)
.filter(move |is| is.summary.unstable_gate(namespaced_features).is_ok()))
.filter(move |is| {
is.summary
.unstable_gate(namespaced_features, weak_dep_features)
.is_ok()
}))
}
fn load_summaries(

View File

@ -1197,7 +1197,8 @@ impl TomlManifest {
me.features.as_ref().unwrap_or(&empty_features),
project.links.as_deref(),
)?;
summary.unstable_gate(config.cli_unstable().namespaced_features)?;
let unstable = config.cli_unstable();
summary.unstable_gate(unstable.namespaced_features, unstable.weak_dep_features)?;
let metadata = ManifestMetadata {
description: project.description.clone(),

View File

@ -906,3 +906,26 @@ error[E0308]: mismatched types
error: aborting due to previous error
```
### Weak dependency features
* Tracking Issue: [#8832](https://github.com/rust-lang/cargo/issues/8832)
The `-Z weak-dep-features` command-line options enables the ability to use
`dep_name?/feat_name` syntax in the `[features]` table. The `?` indicates that
the optional dependency `dep_name` will not be automatically enabled. The
feature `feat_name` will only be added if something else enables the
`dep_name` dependency.
Example:
```toml
[dependencies]
serde = { version = "1.0.117", optional = true, default-features = false }
[features]
std = ["serde?/std"]
```
In this example, the `std` feature enables the `std` feature on the `serde`
dependency. However, unlike the normal `serde/std` syntax, it will not enable
the optional dependency `serde` unless something else has included it.

View File

@ -120,6 +120,7 @@ mod vendor;
mod verify_project;
mod version;
mod warn_on_failure;
mod weak_dep_features;
mod workspaces;
mod yank;

View File

@ -0,0 +1,566 @@
//! Tests for weak-dep-features.
use cargo_test_support::project;
use cargo_test_support::registry::{Dependency, Package};
use std::fmt::Write;
// Helper to create lib.rs files that check features.
fn require(enabled_features: &[&str], disabled_features: &[&str]) -> String {
let mut s = String::new();
for feature in enabled_features {
write!(s, "#[cfg(not(feature=\"{feature}\"))] compile_error!(\"expected feature {feature} to be enabled\");\n",
feature=feature).unwrap();
}
for feature in disabled_features {
write!(s, "#[cfg(feature=\"{feature}\")] compile_error!(\"did not expect feature {feature} to be enabled\");\n",
feature=feature).unwrap();
}
s
}
#[cargo_test]
fn gated() {
// Need -Z weak-dep-features to enable.
Package::new("bar", "1.0.0").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 = ["bar?/feat"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.with_status(101)
.with_stderr(
"\
error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
Caused by:
optional dependency features with `?` syntax are only allowed on the nightly \
channel and requires the `-Z weak-dep-features` flag on the command line
Feature `f1` had feature value `bar?/feat`.
",
)
.run();
}
#[cargo_test]
fn dependency_gate_ignored() {
// Dependencies with ? features in the registry are ignored in the
// registry if not on nightly.
Package::new("baz", "1.0.0").feature("feat", &[]).publish();
Package::new("bar", "1.0.0")
.add_dep(Dependency::new("baz", "1.0").optional(true))
.feature("feat", &["baz?/feat"])
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = "1.0"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[UPDATING] [..]
[ERROR] no matching package named `bar` found
location searched: registry `https://github.com/rust-lang/crates.io-index`
required by package `foo v0.1.0 ([..]/foo)`
",
)
.run();
// Publish a version without the ? feature, it should ignore 1.0.0
// an use this instead.
Package::new("bar", "1.0.1")
.add_dep(Dependency::new("baz", "1.0").optional(true))
.feature("feat", &["baz"])
.publish();
p.cargo("check")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar [..]
[CHECKING] bar v1.0.1
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn simple() {
Package::new("bar", "1.0.0")
.feature("feat", &[])
.file("src/lib.rs", &require(&["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 = ["bar?/feat"]
"#,
)
.file("src/lib.rs", &require(&["f1"], &[]))
.build();
// It's a bit unfortunate that this has to download `bar`, but avoiding
// that is extremely difficult.
p.cargo("check -Z weak-dep-features --features f1")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 [..]
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
p.cargo("check -Z weak-dep-features --features f1,bar")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[CHECKING] bar v1.0.0
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn deferred() {
// A complex chain that requires deferring enabling the feature due to
// another dependency getting enabled.
Package::new("bar", "1.0.0")
.feature("feat", &[])
.file("src/lib.rs", &require(&["feat"], &[]))
.publish();
Package::new("dep", "1.0.0")
.add_dep(Dependency::new("bar", "1.0").optional(true))
.feature("feat", &["bar?/feat"])
.publish();
Package::new("bar_activator", "1.0.0")
.feature_dep("dep", "1.0", &["bar"])
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
dep = { version = "1.0", features = ["feat"] }
bar_activator = "1.0"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Z weak-dep-features")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] dep v1.0.0 [..]
[DOWNLOADED] bar_activator v1.0.0 [..]
[DOWNLOADED] bar v1.0.0 [..]
[CHECKING] bar v1.0.0
[CHECKING] dep v1.0.0
[CHECKING] bar_activator v1.0.0
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn not_optional_dep() {
// Attempt to use dep_name?/feat where dep_name is not optional.
Package::new("dep", "1.0.0").feature("feat", &[]).publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
dep = "1.0"
[features]
feat = ["dep?/feat"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Z weak-dep-features")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr("\
error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
Caused by:
feature `feat` includes `dep?/feat` with a `?`, but `dep` is not an optional dependency
A non-optional dependency of the same name is defined; consider removing the `?` or changing the dependency to be optional
")
.run();
}
#[cargo_test]
fn optional_cli_syntax() {
// --features bar?/feat
Package::new("bar", "1.0.0")
.feature("feat", &[])
.file("src/lib.rs", &require(&["feat"], &[]))
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", optional = true }
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check --features bar?/feat -Z weak-dep-features")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 [..]
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
p.cargo("check --features bar?/feat,bar -Z weak-dep-features")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[CHECKING] bar v1.0.0
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn required_features() {
// required-features doesn't allow ?
Package::new("bar", "1.0.0").feature("feat", &[]).publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", optional = true }
[[bin]]
name = "foo"
required-features = ["bar?/feat"]
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("check -Z weak-dep-features")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[UPDATING] [..]
[ERROR] invalid feature `bar?/feat` in required-features of target `foo`: \
optional dependency with `?` is not allowed in required-features
",
)
.run();
}
#[cargo_test]
fn weak_with_host_decouple() {
// -Z weak-opt-features with -Z features=host
//
// foo v0.1.0
// └── common v1.0.0
// └── bar v1.0.0 <-- does not have `feat` enabled
// [build-dependencies]
// └── bar_activator v1.0.0
// └── common v1.0.0
// └── bar v1.0.0 <-- does have `feat` enabled
Package::new("bar", "1.0.0")
.feature("feat", &[])
.file(
"src/lib.rs",
r#"
pub fn feat() -> bool {
cfg!(feature = "feat")
}
"#,
)
.publish();
Package::new("common", "1.0.0")
.add_dep(Dependency::new("bar", "1.0").optional(true))
.feature("feat", &["bar?/feat"])
.file(
"src/lib.rs",
r#"
#[cfg(feature = "bar")]
pub fn feat() -> bool { bar::feat() }
#[cfg(not(feature = "bar"))]
pub fn feat() -> bool { false }
"#,
)
.publish();
Package::new("bar_activator", "1.0.0")
.feature_dep("common", "1.0", &["bar", "feat"])
.file(
"src/lib.rs",
r#"
pub fn feat() -> bool {
common::feat()
}
"#,
)
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
common = { version = "1.0", features = ["feat"] }
[build-dependencies]
bar_activator = "1.0"
"#,
)
.file(
"src/main.rs",
r#"
fn main() {
assert!(!common::feat());
}
"#,
)
.file(
"build.rs",
r#"
fn main() {
assert!(bar_activator::feat());
}
"#,
)
.build();
p.cargo("run -Z weak-dep-features -Z features=host_dep")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] [..]
[DOWNLOADED] [..]
[DOWNLOADED] [..]
[COMPILING] bar v1.0.0
[COMPILING] common v1.0.0
[COMPILING] bar_activator v1.0.0
[COMPILING] foo v0.1.0 [..]
[FINISHED] [..]
[RUNNING] `target/debug/foo[EXE]`
",
)
.run();
}
#[cargo_test]
fn deferred_with_namespaced() {
// Interaction with -Z namespaced-features using dep: syntax.
//
// `bar` is deferred with bar?/feat
// `bar2` is deferred with dep:bar2?/feat
Package::new("bar", "1.0.0")
.feature("feat", &[])
.file("src/lib.rs", &require(&["feat"], &[]))
.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()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar_includer = { version = "1.0", features = ["feat"] }
bar_activator = "1.0"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Z weak-dep-features -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stderr_unordered(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] [..]
[DOWNLOADED] [..]
[DOWNLOADED] [..]
[DOWNLOADED] [..]
[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 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn tree() {
Package::new("bar", "1.0.0")
.feature("feat", &[])
.file("src/lib.rs", &require(&["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 = ["bar?/feat"]
"#,
)
.file("src/lib.rs", &require(&["f1"], &[]))
.build();
p.cargo("tree -Z weak-dep-features --features f1")
.masquerade_as_nightly_cargo()
.with_stdout("foo v0.1.0 ([ROOT]/foo)")
.run();
p.cargo("tree -Z weak-dep-features --features f1,bar")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
bar v1.0.0
",
)
.run();
p.cargo("tree -Z weak-dep-features --features f1,bar -e features")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
bar feature \"default\"
bar v1.0.0
",
)
.run();
p.cargo("tree -Z weak-dep-features --features f1,bar -e features -i bar")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
bar v1.0.0
bar feature \"default\"
foo v0.1.0 ([ROOT]/foo)
foo feature \"bar\" (command-line)
foo feature \"f1\" (command-line)
foo feature \"default\" (command-line)
foo feature \"f1\" (command-line)
bar feature \"feat\"
foo feature \"f1\" (command-line)
",
)
.run();
}