Auto merge of #10269 - ehuss:stabilized-new-features, r=alexcrichton

Stabilize namespaced and weak dependency features.

This stabilizes the namespaced and weak dependency features.  Support is now enabled on crates.io, so this should be ready to go.

As a part of this change, the new feature resolver is now enabled all of the time. This is fairly risky, since there are likely edge cases that haven't been exercised.
NOTE: Projects using `resolver="1"` *should* continue to have the same behavior, the old resolver behavior is emulated.

Closes #8813
Closes #8832
This commit is contained in:
bors 2022-01-12 15:15:09 +00:00
commit e77c0719fd
19 changed files with 227 additions and 609 deletions

View File

@ -20,7 +20,7 @@ atty = "0.2"
bytesize = "1.0"
cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" }
cargo-util = { path = "crates/cargo-util", version = "0.1.2" }
crates-io = { path = "crates/crates-io", version = "0.33.1" }
crates-io = { path = "crates/crates-io", version = "0.34.0" }
crossbeam-utils = "0.8"
curl = { version = "0.4.41", features = ["http2"] }
curl-sys = "0.4.50"

View File

@ -1,6 +1,6 @@
[package]
name = "crates-io"
version = "0.33.1"
version = "0.34.0"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-lang/cargo"

View File

@ -55,8 +55,6 @@ pub struct NewCrate {
pub repository: Option<String>,
pub badges: BTreeMap<String, BTreeMap<String, String>>,
pub links: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub v: Option<u32>,
}
#[derive(Serialize)]

View File

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

View File

@ -645,7 +645,6 @@ unstable_cli_options!(
minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"),
mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"),
multitarget: bool = ("Allow passing multiple `--target` flags to the cargo subcommand selected"),
namespaced_features: bool = ("Allow features with `dep:` prefix"),
no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"),
@ -655,7 +654,6 @@ unstable_cli_options!(
terminal_width: Option<Option<usize>> = ("Provide a terminal width to rustc for error truncation"),
timings: Option<Vec<String>> = ("Display concurrency information"),
unstable_options: bool = ("Allow the usage of unstable options"),
weak_dep_features: bool = ("Allow `dep_name?/feature` feature syntax"),
// TODO(wcrichto): move scrape example configuration into Cargo.toml before stabilization
// See: https://github.com/rust-lang/cargo/pull/9525#discussion_r728470927
rustdoc_scrape_examples: Option<String> = ("Allow rustdoc to scrape examples from reverse-dependencies for documentation"),
@ -710,6 +708,10 @@ const STABILIZED_NAMED_PROFILES: &str = "The named-profiles feature is now alway
const STABILIZED_FUTURE_INCOMPAT_REPORT: &str =
"The future-incompat-report feature is now always enabled.";
const STABILIZED_WEAK_DEP_FEATURES: &str = "Weak dependency features are now always available.";
const STABILISED_NAMESPACED_FEATURES: &str = "Namespaced features are now always available.";
fn deserialize_build_std<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
where
D: serde::Deserializer<'de>,
@ -876,8 +878,8 @@ 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)?,
"weak-dep-features" => self.weak_dep_features = parse_empty(k, v)?,
"namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES),
"weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES),
"credential-process" => self.credential_process = parse_empty(k, v)?,
"rustdoc-scrape-examples" => {
if let Some(s) = v {

View File

@ -208,7 +208,7 @@ impl Package {
self.targets().iter().any(|t| t.is_example() || t.is_bin())
}
pub fn serialized(&self, config: &Config) -> SerializedPackage {
pub fn serialized(&self) -> SerializedPackage {
let summary = self.manifest().summary();
let package_id = summary.package_id();
let manmeta = self.manifest().metadata();
@ -222,27 +222,19 @@ impl Package {
.filter(|t| t.src_path().is_path())
.cloned()
.collect();
let features = if config.cli_unstable().namespaced_features {
// Convert Vec<FeatureValue> to Vec<InternedString>
summary
.features()
.iter()
.map(|(k, v)| {
(
*k,
v.iter()
.map(|fv| InternedString::new(&fv.to_string()))
.collect(),
)
})
.collect()
} else {
self.manifest()
.original()
.features()
.cloned()
.unwrap_or_default()
};
// Convert Vec<FeatureValue> to Vec<InternedString>
let features = summary
.features()
.iter()
.map(|(k, v)| {
(
*k,
v.iter()
.map(|fv| InternedString::new(&fv.to_string()))
.collect(),
)
})
.collect();
SerializedPackage {
name: package_id.name(),

View File

@ -1,19 +1,14 @@
//! Feature resolver.
//!
//! This is a new feature resolver that runs independently of the main
//! dependency resolver. It is enabled when the user specifies `resolver =
//! "2"` in `Cargo.toml`.
//! dependency resolver. It has several options which can enable new feature
//! resolution behavior.
//!
//! One of its key characteristics is that it can avoid unifying features for
//! shared dependencies in some situations. See `FeatureOpts` for the
//! different behaviors that can be enabled. If no extra options are enabled,
//! then it should behave exactly the same as the dependency resolver's
//! feature resolution. This can be verified by setting the
//! `__CARGO_FORCE_NEW_FEATURES=compare` environment variable and running
//! Cargo's test suite (or building other projects), and checking if it
//! panics. Note: the `features2` tests will fail because they intentionally
//! compare the old vs new behavior, so forcing the old behavior will
//! naturally fail the tests.
//! feature resolution.
//!
//! The preferred way to engage this new resolver is via
//! `resolve_ws_with_opts`.
@ -59,22 +54,12 @@ pub struct ResolvedFeatures {
///
/// The value is the `name_in_toml` of the dependencies.
activated_dependencies: ActivateMap,
/// This is only here for legacy support when the new resolver is not enabled.
///
/// 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 the new resolver is not enabled.
///
/// This is the set of optional dependencies enabled for each package.
legacy_dependencies: Option<HashMap<PackageId, HashSet<InternedString>>>,
opts: FeatureOpts,
}
/// Options for how the feature resolver works.
#[derive(Default)]
pub struct FeatureOpts {
/// Use the new resolver instead of the old one.
new_resolver: bool,
/// Build deps and proc-macros will not share share features with other dep kinds.
decouple_host_deps: bool,
/// Dev dep features will not be activated unless needed.
@ -132,7 +117,6 @@ impl FeatureOpts {
let mut opts = FeatureOpts::default();
let unstable_flags = ws.config().cli_unstable();
let mut enable = |feat_opts: &Vec<String>| {
opts.new_resolver = true;
for opt in feat_opts {
match opt.as_ref() {
"build_dep" | "host_dep" => opts.decouple_host_deps = true,
@ -159,15 +143,6 @@ impl FeatureOpts {
enable(&vec!["all".to_string()]).unwrap();
}
}
// This env var is intended for testing only.
if let Ok(env_opts) = std::env::var("__CARGO_FORCE_NEW_FEATURES") {
if env_opts == "1" {
opts.new_resolver = true;
} else {
let env_opts = env_opts.split(',').map(|s| s.to_string()).collect();
enable(&env_opts)?;
}
}
if let HasDevUnits::Yes = has_dev_units {
// Dev deps cannot be decoupled when they are in use.
opts.decouple_dev_deps = false;
@ -175,10 +150,6 @@ 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)
}
@ -187,7 +158,6 @@ impl FeatureOpts {
match behavior {
ResolveBehavior::V1 => FeatureOpts::default(),
ResolveBehavior::V2 => FeatureOpts {
new_resolver: true,
decouple_host_deps: true,
decouple_dev_deps: has_dev_units == HasDevUnits::No,
ignore_inactive_targets: true,
@ -306,18 +276,11 @@ impl ResolvedFeatures {
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)
}
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
@ -336,15 +299,11 @@ impl ResolvedFeatures {
pkg_id: PackageId,
features_for: FeaturesFor,
) -> CargoResult<Vec<InternedString>> {
if let Some(legacy) = &self.legacy_features {
Ok(legacy.get(&pkg_id).map_or_else(Vec::new, |v| v.clone()))
let is_build = self.opts.decouple_host_deps && features_for == FeaturesFor::HostDep;
if let Some(fs) = self.activated_features.get(&(pkg_id, is_build)) {
Ok(fs.iter().cloned().collect())
} else {
let is_build = self.opts.decouple_host_deps && features_for == FeaturesFor::HostDep;
if let Some(fs) = self.activated_features.get(&(pkg_id, is_build)) {
Ok(fs.iter().cloned().collect())
} else {
bail!("features did not find {:?} {:?}", pkg_id, is_build)
}
bail!("features did not find {:?} {:?}", pkg_id, is_build)
}
}
@ -352,14 +311,16 @@ impl ResolvedFeatures {
///
/// Used by `cargo fix --edition` to display any differences.
pub fn compare_legacy(&self, legacy: &ResolvedFeatures) -> DiffMap {
let legacy_features = legacy.legacy_features.as_ref().unwrap();
self.activated_features
.iter()
.filter_map(|((pkg_id, for_host), new_features)| {
let old_features = match legacy_features.get(pkg_id) {
Some(feats) => feats.iter().cloned().collect(),
None => BTreeSet::new(),
};
let old_features = legacy
.activated_features
.get(&(*pkg_id, *for_host))
// The new features may have for_host entries where the old one does not.
.or_else(|| legacy.activated_features.get(&(*pkg_id, false)))
.map(|feats| feats.iter().cloned().collect())
.unwrap_or_else(|| BTreeSet::new());
// The new resolver should never add features.
assert_eq!(new_features.difference(&old_features).next(), None);
let removed_features: BTreeSet<_> =
@ -427,17 +388,6 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
) -> CargoResult<ResolvedFeatures> {
use crate::util::profile;
let _p = profile::start("resolve features");
if !opts.new_resolver {
// Legacy mode.
return Ok(ResolvedFeatures {
activated_features: HashMap::new(),
activated_dependencies: HashMap::new(),
legacy_features: Some(resolve.features_clone()),
legacy_dependencies: Some(compute_legacy_deps(resolve)),
opts,
});
}
let track_for_host = opts.decouple_host_deps || opts.ignore_inactive_targets;
let mut r = FeatureResolver {
ws,
@ -460,8 +410,6 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
Ok(ResolvedFeatures {
activated_features: r.activated_features,
activated_dependencies: r.activated_dependencies,
legacy_features: None,
legacy_dependencies: None,
opts: r.opts,
})
}
@ -826,19 +774,3 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
.proc_macro()
}
}
/// Computes a map of PackageId to the set of optional dependencies that are
/// enabled for that dep (when the new resolver is not enabled).
fn compute_legacy_deps(resolve: &Resolve) -> HashMap<PackageId, HashSet<InternedString>> {
let mut result: HashMap<PackageId, HashSet<InternedString>> = HashMap::new();
for pkg_id in resolve.iter() {
for (_dep_id, deps) in resolve.deps(pkg_id) {
for dep in deps {
if dep.is_optional() {
result.entry(pkg_id).or_default().insert(dep.name_in_toml());
}
}
}
}
result
}

View File

@ -23,8 +23,6 @@ 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>,
}
@ -37,15 +35,11 @@ impl Summary {
features: &BTreeMap<InternedString, Vec<InternedString>>,
links: Option<impl Into<InternedString>>,
) -> CargoResult<Summary> {
// ****CAUTION**** If you change anything here than may raise a new
// ****CAUTION**** If you change anything here that may raise a new
// error, be sure to coordinate that change with either the index
// schema field or the SummariesCache version.
let mut has_overlapping_features = None;
for dep in dependencies.iter() {
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() {
bail!(
"dev-dependencies are not allowed to be optional: `{}`",
@ -53,8 +47,7 @@ impl Summary {
)
}
}
let (feature_map, has_namespaced_features) =
build_feature_map(config, pkg_id, features, &dependencies)?;
let feature_map = build_feature_map(config, pkg_id, features, &dependencies)?;
Ok(Summary {
inner: Rc::new(Inner {
package_id: pkg_id,
@ -62,8 +55,6 @@ impl Summary {
features: Rc::new(feature_map),
checksum: None,
links: links.map(|l| l.into()),
has_namespaced_features,
has_overlapping_features,
}),
})
}
@ -87,46 +78,6 @@ impl Summary {
&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,
weak_dep_features: bool,
) -> CargoResult<()> {
if !namespaced_features {
if self.inner.has_namespaced_features {
bail!(
"namespaced features with the `dep:` prefix are only allowed on \
the nightly channel and requires the `-Z namespaced-features` flag on the command-line"
);
}
if let Some(dep_name) = self.inner.has_overlapping_features {
bail!(
"features and dependencies cannot have the same name: `{}`",
dep_name
)
}
}
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(())
}
pub fn checksum(&self) -> Option<&str> {
self.inner.checksum.as_deref()
}
@ -181,16 +132,12 @@ impl Hash for Summary {
/// Checks features for errors, bailing out a CargoResult:Err if invalid,
/// and creates FeatureValues for each feature.
///
/// The returned `bool` indicates whether or not the `[features]` table
/// included a `dep:` prefixed namespaced feature (used for gating on
/// nightly).
fn build_feature_map(
config: &Config,
pkg_id: PackageId,
features: &BTreeMap<InternedString, Vec<InternedString>>,
dependencies: &[Dependency],
) -> CargoResult<(FeatureMap, bool)> {
) -> CargoResult<FeatureMap> {
use self::FeatureValue::*;
let mut dep_map = HashMap::new();
for dep in dependencies.iter() {
@ -210,7 +157,6 @@ fn build_feature_map(
(*feature, fvs)
})
.collect();
let has_namespaced_features = map.values().flatten().any(|fv| fv.has_dep_prefix());
// Add implicit features for optional dependencies if they weren't
// explicitly listed anywhere.
@ -372,7 +318,7 @@ fn build_feature_map(
);
}
Ok((map, has_namespaced_features))
Ok(map)
}
/// FeatureValue represents the types of dependencies a feature can have.

View File

@ -31,9 +31,8 @@ pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> Cargo
VERSION
);
}
let config = ws.config();
let (packages, resolve) = if opt.no_deps {
let packages = ws.members().map(|pkg| pkg.serialized(config)).collect();
let packages = ws.members().map(|pkg| pkg.serialized()).collect();
(packages, None)
} else {
let (packages, resolve) = build_resolve_graph(ws, opt)?;
@ -152,11 +151,10 @@ fn build_resolve_graph(
);
}
// Get a Vec of Packages.
let config = ws.config();
let actual_packages = package_map
.into_iter()
.filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
.map(|pkg| pkg.serialized(config))
.map(|pkg| pkg.serialized())
.collect();
let mr = MetadataResolve {

View File

@ -287,7 +287,6 @@ fn transmit(
license_file: license_file.clone(),
badges: badges.clone(),
links: links.clone(),
v: None,
},
tarball,
)

View File

@ -293,8 +293,6 @@ 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
@ -309,11 +307,6 @@ impl<'cfg> RegistryIndex<'cfg> {
// minimize the amount of work being done here and parse as little as
// necessary.
let raw_data = &summaries.raw_data;
let max_version = if namespaced_features || weak_dep_features {
INDEX_V_MAX
} else {
1
};
Ok(summaries
.versions
.iter_mut()
@ -328,7 +321,7 @@ impl<'cfg> RegistryIndex<'cfg> {
},
)
.filter(move |is| {
if is.v > max_version {
if is.v > INDEX_V_MAX {
debug!(
"unsupported schema version {} ({} {})",
is.v,
@ -339,11 +332,6 @@ impl<'cfg> RegistryIndex<'cfg> {
} else {
true
}
})
.filter(move |is| {
is.summary
.unstable_gate(namespaced_features, weak_dep_features)
.is_ok()
}))
}

View File

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

View File

@ -116,21 +116,33 @@ an external package to handle GIF images. This can be expressed like this:
gif = { version = "0.11.1", optional = true }
```
Optional dependencies implicitly define a feature of the same name as the
dependency. This means that the same `cfg(feature = "gif")` syntax can be used
in the code, and the dependency can be enabled just like a feature such as
`--features gif` (see [Command-line feature
options](#command-line-feature-options) below).
By default, this optional dependency implicitly defines a feature that looks
like this:
> **Note**: A feature in the `[feature]` table cannot use the same name as a
> dependency. Experimental support for enabling this and other extensions is
> available on the nightly channel via [namespaced
> features](unstable.md#namespaced-features).
```toml
[features]
gif = ["dep:gif"]
```
Explicitly defined features can enable optional dependencies, too. Just
include the name of the optional dependency in the feature list. For example,
let's say in order to support the AVIF image format, our library needs two
other dependencies to be enabled:
This means that this dependency will only be included if the `gif`
feature is enabled.
The same `cfg(feature = "gif")` syntax can be used in the code, and the
dependency can be enabled just like any feature such as `--features gif` (see
[Command-line feature options](#command-line-feature-options) below).
In some cases, you may not want to expose a feature that has the same name
as the optional dependency.
For example, perhaps the optional dependency is an internal detail, or you
want to group multiple optional dependencies together, or you just want to use
a better name.
If you specify the optional dependency with the `dep:` prefix anywhere
in the `[features]` table, that disables the implicit feature.
> **Note**: The `dep:` syntax is only available starting with Rust 1.60.
> Previous versions can only use the implicit feature name.
For example, let's say in order to support the AVIF image format, our library
needs two other dependencies to be enabled:
```toml
[dependencies]
@ -138,10 +150,13 @@ ravif = { version = "0.6.3", optional = true }
rgb = { version = "0.8.25", optional = true }
[features]
avif = ["ravif", "rgb"]
avif = ["dep:ravif", "dep:rgb"]
```
In this example, the `avif` feature will enable the two listed dependencies.
This also avoids creating the implicit `ravif` and `rgb` features, since we
don't want users to enable those individually as they are internal details to
our crate.
> **Note**: Another way to optionally include a dependency is to use
> [platform-specific dependencies]. Instead of using features, these are
@ -185,10 +200,31 @@ jpeg-decoder = { version = "0.1.20", default-features = false }
parallel = ["jpeg-decoder/rayon"]
```
> **Note**: The `"package-name/feature-name"` syntax will also enable
> `package-name` if it is an optional dependency. Experimental support for
> disabling that behavior is available on the nightly channel via [weak
> dependency features](unstable.md#weak-dependency-features).
The `"package-name/feature-name"` syntax will also enable `package-name`
if it is an optional dependency. Often this is not what you want.
You can add a `?` as in `"package-name?/feature-name"` which will only enable
the given feature if something else enables the optional dependency.
> **Note**: The `?` syntax is only available starting with Rust 1.60.
For example, let's say we have added some serialization support to our
library, and it requires enabling a corresponding feature in some optional
dependencies.
That can be done like this:
```toml
[dependencies]
serde = { version = "1.0.133", optional = true }
rgb = { version = "0.8.25", optional = true }
[features]
serde = ["dep:serde", "rgb?/serde"]
```
In this example, enabling the `serde` feature will enable the serde
dependency.
It will also enable the `serde` feature for the `rgb` dependency, but only if
something else has enabled the `rgb` dependency.
### Command-line feature options

View File

@ -263,7 +263,45 @@ explaining the format of the entry.
"yanked": false,
// The `links` string value from the package's manifest, or null if not
// specified. This field is optional and defaults to null.
"links": null
"links": null,
// An unsigned 32-bit integer value indicating the schema version of this
// entry.
//
// If this not specified, it should be interpreted as the default of 1.
//
// Cargo (starting with version 1.51) will ignore versions it does not
// recognize. This provides a method to safely introduce changes to index
// entries and allow older versions of cargo to ignore newer entries it
// doesn't understand. Versions older than 1.51 ignore this field, and
// thus may misinterpret the meaning of the index entry.
//
// The current values are:
//
// * 1: The schema as documented here, not including newer additions.
// This is honored in Rust version 1.51 and newer.
// * 2: The addition of the `features2` field.
// This is honored in Rust version 1.60 and newer.
"v": 2,
// This optional field contains features with new, extended syntax.
// Specifically, namespaced features (`dep:`) and weak dependencies
// (`pkg?/feat`).
//
// This is separated from `features` because versions older than 1.19
// will fail to load due to not being able to parse the new syntax, even
// with a `Cargo.lock` file.
//
// Cargo will merge any values listed here with the "features" field.
//
// If this field is included, the "v" field should be set to at least 2.
//
// Registries are not required to use this field for extended feature
// syntax, they are allowed to include those in the "features" field.
// Using this is only necessary if the registry wants to support cargo
// versions older than 1.19, which in practice is only crates.io since
// those older versions do not support other registries.
"features2": {
"serde": ["dep:serde", "chrono?/serde"]
}
}
```

View File

@ -69,8 +69,6 @@ Each new feature described below should explain how to use it.
* [avoid-dev-deps](#avoid-dev-deps) — Prevents the resolver from including dev-dependencies during resolution.
* [minimal-versions](#minimal-versions) — Forces the resolver to use the lowest compatible version instead of the highest.
* [public-dependency](#public-dependency) — Allows dependencies to be classified as either public or private.
* [Namespaced features](#namespaced-features) — Separates optional dependencies into a separate namespace from regular features, and allows feature names to be the same as some dependency name.
* [Weak dependency features](#weak-dependency-features) — Allows setting features for dependencies without enabling optional dependencies.
* Output behavior
* [out-dir](#out-dir) — Adds a directory where artifacts are copied to.
* [terminal-width](#terminal-width) — Tells rustc the width of the terminal so that long diagnostic messages can be truncated to be more readable.
@ -253,68 +251,6 @@ dir-name = "lto" # Emits to target/lto instead of target/release-lto
lto = true
```
### Namespaced features
* Original issue: [#1286](https://github.com/rust-lang/cargo/issues/1286)
* Tracking Issue: [#5565](https://github.com/rust-lang/cargo/issues/5565)
The `namespaced-features` option makes two changes to how features can be
specified:
* Features may now be defined with the same name as a dependency.
* Optional dependencies can be explicitly enabled in the `[features]` table
with the `dep:` prefix, which enables the dependency without enabling a
feature of the same name.
By default, an optional dependency `foo` will define a feature `foo =
["dep:foo"]` *unless* `dep:foo` is mentioned in any other feature, or the
`foo` feature is already defined. This helps prevent unnecessary boilerplate
of listing every optional dependency, but still allows you to override the
implicit feature.
This allows two use cases that were previously not possible:
* You can "hide" an optional dependency, so that external users cannot
explicitly enable that optional dependency.
* There is no longer a need to create "funky" feature names to work around the
restriction that features cannot shadow dependency names.
To enable namespaced-features, use the `-Z namespaced-features` command-line
flag.
An example of hiding an optional dependency:
```toml
[dependencies]
regex = { version = "1.4.1", optional = true }
lazy_static = { version = "1.4.0", optional = true }
[features]
regex = ["dep:regex", "dep:lazy_static"]
```
In this example, the "regex" feature enables both `regex` and `lazy_static`.
The `lazy_static` feature does not exist, and a user cannot explicitly enable
it. This helps hide internal details of how your package is implemented.
An example of avoiding "funky" names:
```toml
[dependencies]
bigdecimal = "0.1"
chrono = "0.4"
num-bigint = "0.2"
serde = {version = "1.0", optional = true }
[features]
serde = ["dep:serde", "bigdecimal/serde", "chrono/serde", "num-bigint/serde"]
```
In this case, `serde` is a natural name to use for a feature, because it is
relevant to your exported API. However, previously you would need to use a
name like `serde1` to work around the naming limitation if you wanted to also
enable other features.
### Build-plan
* Tracking Issue: [#5579](https://github.com/rust-lang/cargo/issues/5579)
@ -917,29 +853,6 @@ 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.
### per-package-target
* Tracking Issue: [#9406](https://github.com/rust-lang/cargo/pull/9406)
* Original Pull Request: [#9030](https://github.com/rust-lang/cargo/pull/9030)
@ -1394,3 +1307,13 @@ The profile `strip` option has been stabilized in the 1.59 release. See the
Support for generating a future-incompat report has been stabilized
in the 1.59 release. See the [future incompat report chapter](future-incompat-report.md)
for more information.
### Namespaced features
Namespaced features has been stabilized in the 1.60 release.
See the [Features chapter](features.md#optional-dependencies) for more information.
### Weak dependency features
Weak dependency features has been stabilized in the 1.60 release.
See the [Features chapter](features.md#dependency-features) for more information.

View File

@ -36,7 +36,8 @@ Caused by:
}
#[cargo_test]
fn invalid2() {
fn same_name() {
// Feature with the same name as a dependency.
let p = project()
.file(
"Cargo.toml",
@ -59,14 +60,24 @@ fn invalid2() {
.file("bar/src/lib.rs", "")
.build();
p.cargo("build")
.with_status(101)
.with_stderr(
p.cargo("tree -f")
.arg("{p} [{f}]")
.with_stderr("")
.with_stdout(
"\
[ERROR] failed to parse manifest at `[..]`
foo v0.0.1 ([..]) []
bar v1.0.0 ([..]) []
",
)
.run();
Caused by:
features and dependencies cannot have the same name: `bar`
p.cargo("tree --features bar -f")
.arg("{p} [{f}]")
.with_stderr("")
.with_stdout(
"\
foo v0.0.1 ([..]) [bar,baz]
bar v1.0.0 ([..]) []
",
)
.run();

View File

@ -4,99 +4,6 @@ use super::features2::switch_to_resolver_2;
use cargo_test_support::registry::{Dependency, Package};
use cargo_test_support::{project, publish};
#[cargo_test]
fn gated() {
// Need namespaced-features to use `dep:` syntax.
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", optional = true }
[features]
foo = ["dep:bar"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
Caused by:
namespaced features with the `dep:` prefix are only allowed on the nightly channel \
and requires the `-Z namespaced-features` flag on the command-line
",
)
.run();
}
#[cargo_test]
fn dependency_gate_ignored() {
// Dependencies with `dep:` features are ignored in the registry if not on nightly.
Package::new("baz", "1.0.0").publish();
Package::new("bar", "1.0.0")
.add_dep(Dependency::new("baz", "1.0").optional(true))
.feature("feat", &["dep:baz"])
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = "1.0"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[UPDATING] [..]
[ERROR] no matching package named `bar` found
location searched: registry `crates-io`
required by package `foo v0.1.0 ([..]/foo)`
",
)
.run();
// Publish a version without namespaced features, it should ignore 1.0.0
// and use this instead.
Package::new("bar", "1.0.1")
.add_dep(Dependency::new("baz", "1.0").optional(true))
.feature("feat", &["baz"])
.publish();
p.cargo("check")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar [..]
[CHECKING] bar v1.0.1
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn dependency_with_crate_syntax() {
// Registry dependency uses dep: syntax.
@ -120,8 +27,7 @@ fn dependency_with_crate_syntax() {
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("check")
.with_stderr(
"\
[UPDATING] [..]
@ -156,8 +62,7 @@ fn namespaced_invalid_feature() {
.file("src/main.rs", "")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("build")
.with_status(101)
.with_stderr(
"\
@ -188,8 +93,7 @@ fn namespaced_invalid_dependency() {
.file("src/main.rs", "")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("build")
.with_status(101)
.with_stderr(
"\
@ -223,8 +127,8 @@ fn namespaced_non_optional_dependency() {
.file("src/main.rs", "")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("build")
.with_status(101)
.with_stderr(
"\
@ -261,8 +165,7 @@ fn namespaced_implicit_feature() {
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("check")
.with_stderr(
"\
[UPDATING] [..]
@ -271,8 +174,7 @@ fn namespaced_implicit_feature() {
",
)
.run();
p.cargo("check -Z namespaced-features --features baz")
.masquerade_as_nightly_cargo()
p.cargo("check --features baz")
.with_stderr(
"\
[DOWNLOADING] crates ...
@ -307,8 +209,7 @@ fn namespaced_shadowed_dep() {
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("build")
.with_status(101)
.with_stderr(
"\
@ -344,9 +245,7 @@ fn namespaced_shadowed_non_optional() {
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
.run();
p.cargo("check").run();
}
#[cargo_test]
@ -370,7 +269,7 @@ fn namespaced_implicit_non_optional() {
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build -Z namespaced-features").masquerade_as_nightly_cargo().with_status(101).with_stderr(
p.cargo("build").with_status(101).with_stderr(
"\
[ERROR] failed to parse manifest at `[..]`
@ -411,8 +310,7 @@ fn namespaced_same_name() {
)
.build();
p.cargo("run -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("run")
.with_stderr(
"\
[UPDATING] [..]
@ -424,8 +322,7 @@ fn namespaced_same_name() {
.with_stdout("")
.run();
p.cargo("run -Z namespaced-features --features baz")
.masquerade_as_nightly_cargo()
p.cargo("run --features baz")
.with_stderr(
"\
[DOWNLOADING] crates ...
@ -473,8 +370,7 @@ fn no_implicit_feature() {
)
.build();
p.cargo("run -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("run")
.with_stderr(
"\
[UPDATING] [..]
@ -486,8 +382,7 @@ fn no_implicit_feature() {
.with_stdout("")
.run();
p.cargo("run -Z namespaced-features --features regex")
.masquerade_as_nightly_cargo()
p.cargo("run --features regex")
.with_stderr_unordered(
"\
[DOWNLOADING] crates ...
@ -503,8 +398,7 @@ fn no_implicit_feature() {
.with_stdout("regex")
.run();
p.cargo("run -Z namespaced-features --features lazy_static")
.masquerade_as_nightly_cargo()
p.cargo("run --features lazy_static")
.with_stderr(
"\
[ERROR] Package `foo v0.1.0 [..]` does not have feature `lazy_static`. \
@ -538,8 +432,7 @@ fn crate_syntax_bad_name() {
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features --features dep:bar")
.masquerade_as_nightly_cargo()
p.cargo("check --features dep:bar")
.with_status(101)
.with_stderr(
"\
@ -574,8 +467,7 @@ fn crate_syntax_in_dep() {
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("check")
.with_status(101)
.with_stderr(
"\
@ -608,8 +500,7 @@ fn crate_syntax_cli() {
.file("src/lib.rs", "")
.build();
p.cargo("check -Z namespaced-features --features dep:bar")
.masquerade_as_nightly_cargo()
p.cargo("check --features dep:bar")
.with_status(101)
.with_stderr(
"\
@ -619,8 +510,7 @@ fn crate_syntax_cli() {
.run();
switch_to_resolver_2(&p);
p.cargo("check -Z namespaced-features --features dep:bar")
.masquerade_as_nightly_cargo()
p.cargo("check --features dep:bar")
.with_status(101)
.with_stderr(
"\
@ -653,8 +543,7 @@ fn crate_required_features() {
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("check -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("check")
.with_status(101)
.with_stderr(
"\
@ -685,8 +574,7 @@ fn json_exposed() {
.file("src/lib.rs", "")
.build();
p.cargo("metadata -Z namespaced-features --no-deps")
.masquerade_as_nightly_cargo()
p.cargo("metadata --no-deps")
.with_json(
r#"
{
@ -775,8 +663,7 @@ fn crate_feature_with_explicit() {
)
.build();
p.cargo("check -Z namespaced-features --features f1")
.masquerade_as_nightly_cargo()
p.cargo("check --features f1")
.with_stderr(
"\
[UPDATING] [..]
@ -814,8 +701,7 @@ fn optional_explicit_without_crate() {
.file("src/lib.rs", "")
.build();
p.cargo("build -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("build")
.with_status(101)
.with_stderr(
"\
@ -856,13 +742,11 @@ fn tree() {
.file("src/lib.rs", "")
.build();
p.cargo("tree -e features -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features")
.with_stdout("foo v0.1.0 ([ROOT]/foo)")
.run();
p.cargo("tree -e features --features a -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features --features a")
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
@ -876,8 +760,7 @@ foo v0.1.0 ([ROOT]/foo)
)
.run();
p.cargo("tree -e features --features a -i bar -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features --features a -i bar")
.with_stdout(
"\
bar v1.0.0
@ -895,8 +778,7 @@ bar v1.0.0
)
.run();
p.cargo("tree -e features --features bar -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features --features bar")
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
@ -910,8 +792,7 @@ foo v0.1.0 ([ROOT]/foo)
)
.run();
p.cargo("tree -e features --features bar -i bar -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features --features bar -i bar")
.with_stdout(
"\
bar v1.0.0
@ -948,13 +829,11 @@ fn tree_no_implicit() {
.file("src/lib.rs", "")
.build();
p.cargo("tree -e features -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features")
.with_stdout("foo v0.1.0 ([ROOT]/foo)")
.run();
p.cargo("tree -e features --all-features -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features --all-features")
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
@ -964,8 +843,7 @@ foo v0.1.0 ([ROOT]/foo)
)
.run();
p.cargo("tree -e features -i bar --all-features -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features -i bar --all-features")
.with_stdout(
"\
bar v1.0.0
@ -1116,8 +994,7 @@ fn publish() {
.file("src/lib.rs", "")
.build();
p.cargo("publish --token sekrit -Z namespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("publish --token sekrit")
.with_stderr(
"\
[UPDATING] [..]

View File

@ -558,8 +558,7 @@ fn index_cache_rebuild() {
fs::remove_file(p.root().join("Cargo.lock")).unwrap();
// This should rebuild the cache and use 1.0.1.
p.cargo("check -Znamespaced-features")
.masquerade_as_nightly_cargo()
p.cargo("check")
.with_stderr(
"\
[UPDATING] [..]

View File

@ -20,100 +20,6 @@ fn require(enabled_features: &[&str], disabled_features: &[&str]) -> String {
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 `crates-io`
required by package `foo v0.1.0 ([..]/foo)`
",
)
.run();
// Publish a version without the ? feature, it should ignore 1.0.0
// and 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")
@ -140,8 +46,7 @@ fn simple() {
// 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()
p.cargo("check --features f1")
.with_stderr(
"\
[UPDATING] [..]
@ -153,8 +58,7 @@ fn simple() {
)
.run();
p.cargo("check -Z weak-dep-features --features f1,bar")
.masquerade_as_nightly_cargo()
p.cargo("check --features f1,bar")
.with_stderr(
"\
[CHECKING] bar v1.0.0
@ -196,8 +100,7 @@ fn deferred() {
.file("src/lib.rs", "")
.build();
p.cargo("check -Z weak-dep-features")
.masquerade_as_nightly_cargo()
p.cargo("check")
.with_stderr(
"\
[UPDATING] [..]
@ -238,8 +141,7 @@ fn not_optional_dep() {
.file("src/lib.rs", "")
.build();
p.cargo("check -Z weak-dep-features")
.masquerade_as_nightly_cargo()
p.cargo("check")
.with_status(101)
.with_stderr("\
error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
@ -275,8 +177,7 @@ fn optional_cli_syntax() {
.build();
// Does not build bar.
p.cargo("check --features bar?/feat -Z weak-dep-features")
.masquerade_as_nightly_cargo()
p.cargo("check --features bar?/feat")
.with_stderr(
"\
[UPDATING] [..]
@ -289,8 +190,7 @@ fn optional_cli_syntax() {
.run();
// Builds bar.
p.cargo("check --features bar?/feat,bar -Z weak-dep-features")
.masquerade_as_nightly_cargo()
p.cargo("check --features bar?/feat,bar")
.with_stderr(
"\
[CHECKING] bar v1.0.0
@ -304,8 +204,7 @@ fn optional_cli_syntax() {
switch_to_resolver_2(&p);
p.build_dir().rm_rf();
// Does not build bar.
p.cargo("check --features bar?/feat -Z weak-dep-features")
.masquerade_as_nightly_cargo()
p.cargo("check --features bar?/feat")
.with_stderr(
"\
[CHECKING] foo v0.1.0 [..]
@ -315,8 +214,7 @@ fn optional_cli_syntax() {
.run();
// Builds bar.
p.cargo("check --features bar?/feat,bar -Z weak-dep-features")
.masquerade_as_nightly_cargo()
p.cargo("check --features bar?/feat,bar")
.with_stderr(
"\
[CHECKING] bar v1.0.0
@ -351,8 +249,7 @@ fn required_features() {
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("check -Z weak-dep-features")
.masquerade_as_nightly_cargo()
p.cargo("check")
.with_status(101)
.with_stderr(
"\
@ -366,7 +263,7 @@ optional dependency with `?` is not allowed in required-features
#[cargo_test]
fn weak_with_host_decouple() {
// -Z weak-opt-features with new resolver
// weak-dep-features with new resolver
//
// foo v0.1.0
// └── common v1.0.0
@ -447,8 +344,7 @@ fn weak_with_host_decouple() {
)
.build();
p.cargo("run -Z weak-dep-features")
.masquerade_as_nightly_cargo()
p.cargo("run")
.with_stderr(
"\
[UPDATING] [..]
@ -493,8 +389,7 @@ fn weak_namespaced() {
.file("src/lib.rs", &require(&["f1"], &["f2", "bar"]))
.build();
p.cargo("check -Z weak-dep-features -Z namespaced-features --features f1")
.masquerade_as_nightly_cargo()
p.cargo("check --features f1")
.with_stderr(
"\
[UPDATING] [..]
@ -506,21 +401,18 @@ fn weak_namespaced() {
)
.run();
p.cargo("tree -Z weak-dep-features -Z namespaced-features -f")
p.cargo("tree -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")
p.cargo("tree --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")
p.cargo("tree --features f1,f2 -f")
.arg("{p} feats:{f}")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo) feats:f1,f2
@ -532,8 +424,7 @@ foo v0.1.0 ([ROOT]/foo) feats:f1,f2
// "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()
p.cargo("check --features f1,f2")
.with_stderr(
"\
[CHECKING] bar v1.0.0
@ -568,13 +459,11 @@ fn tree() {
.file("src/lib.rs", &require(&["f1"], &[]))
.build();
p.cargo("tree -Z weak-dep-features --features f1")
.masquerade_as_nightly_cargo()
p.cargo("tree --features f1")
.with_stdout("foo v0.1.0 ([ROOT]/foo)")
.run();
p.cargo("tree -Z weak-dep-features --features f1,bar")
.masquerade_as_nightly_cargo()
p.cargo("tree --features f1,bar")
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
@ -583,8 +472,7 @@ foo v0.1.0 ([ROOT]/foo)
)
.run();
p.cargo("tree -Z weak-dep-features --features f1,bar -e features")
.masquerade_as_nightly_cargo()
p.cargo("tree --features f1,bar -e features")
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
@ -594,8 +482,7 @@ foo v0.1.0 ([ROOT]/foo)
)
.run();
p.cargo("tree -Z weak-dep-features --features f1,bar -e features -i bar")
.masquerade_as_nightly_cargo()
p.cargo("tree --features f1,bar -e features -i bar")
.with_stdout(
"\
bar v1.0.0
@ -610,20 +497,17 @@ bar v1.0.0
)
.run();
p.cargo("tree -Z weak-dep-features -e features --features bar?/feat")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features --features bar?/feat")
.with_stdout("foo v0.1.0 ([ROOT]/foo)")
.run();
// This is a little strange in that it produces no output.
// Maybe `cargo tree` should print a note about why?
p.cargo("tree -Z weak-dep-features -e features -i bar --features bar?/feat")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features -i bar --features bar?/feat")
.with_stdout("")
.run();
p.cargo("tree -Z weak-dep-features -e features -i bar --features bar?/feat,bar")
.masquerade_as_nightly_cargo()
p.cargo("tree -e features -i bar --features bar?/feat,bar")
.with_stdout(
"\
bar v1.0.0
@ -663,8 +547,7 @@ fn publish() {
.file("src/lib.rs", "")
.build();
p.cargo("publish --token sekrit -Z weak-dep-features")
.masquerade_as_nightly_cargo()
p.cargo("publish --token sekrit")
.with_stderr(
"\
[UPDATING] [..]