mirror of https://github.com/rust-lang/cargo
Implement weak dependency features.
This commit is contained in:
parent
28af36face
commit
9ffcf69093
|
@ -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]'"
|
||||
);
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -120,6 +120,7 @@ mod vendor;
|
|||
mod verify_project;
|
||||
mod version;
|
||||
mod warn_on_failure;
|
||||
mod weak_dep_features;
|
||||
mod workspaces;
|
||||
mod yank;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue