fix: Remove implicit feature removal

Due to problems we ran into with #14016, we're removing implicit
features from the 2024 edition to give ourselves more time to design it
as we should.

I could have added a new flag for this or made an EditionNext but I
decided to remove it in the hopes to avoid any path dependency in
solving this the next time.
This commit is contained in:
Ed Page 2024-10-01 11:48:31 -05:00
parent 2fd7321ee4
commit 7be5a2146b
11 changed files with 13 additions and 1286 deletions

View File

@ -24,9 +24,7 @@ use crate::sources::{PathSource, SourceConfigMap, CRATES_IO_INDEX, CRATES_IO_REG
use crate::util::edit_distance; use crate::util::edit_distance;
use crate::util::errors::{CargoResult, ManifestError}; use crate::util::errors::{CargoResult, ManifestError};
use crate::util::interning::InternedString; use crate::util::interning::InternedString;
use crate::util::lints::{ use crate::util::lints::{analyze_cargo_lints_table, check_im_a_teapot};
analyze_cargo_lints_table, check_im_a_teapot, check_implicit_features, unused_dependencies,
};
use crate::util::toml::{read_manifest, InheritableFields}; use crate::util::toml::{read_manifest, InheritableFields};
use crate::util::{ use crate::util::{
context::CargoResolverConfig, context::ConfigRelativePath, context::IncompatibleRustVersions, context::CargoResolverConfig, context::ConfigRelativePath, context::IncompatibleRustVersions,
@ -1240,8 +1238,6 @@ impl<'gctx> Workspace<'gctx> {
self.gctx, self.gctx,
)?; )?;
check_im_a_teapot(pkg, &path, &cargo_lints, &mut error_count, self.gctx)?; check_im_a_teapot(pkg, &path, &cargo_lints, &mut error_count, self.gctx)?;
check_implicit_features(pkg, &path, &cargo_lints, &mut error_count, self.gctx)?;
unused_dependencies(pkg, &path, &cargo_lints, &mut error_count, self.gctx)?;
if error_count > 0 { if error_count > 0 {
Err(crate::util::errors::AlreadyPrintedError::new(anyhow!( Err(crate::util::errors::AlreadyPrintedError::new(anyhow!(
"encountered {error_count} errors(s) while running lints" "encountered {error_count} errors(s) while running lints"

View File

@ -55,13 +55,12 @@ use crate::core::compiler::CompileKind;
use crate::core::compiler::RustcTargetData; use crate::core::compiler::RustcTargetData;
use crate::core::resolver::features::{DiffMap, FeatureOpts, FeatureResolver, FeaturesFor}; use crate::core::resolver::features::{DiffMap, FeatureOpts, FeatureResolver, FeaturesFor};
use crate::core::resolver::{HasDevUnits, Resolve, ResolveBehavior}; use crate::core::resolver::{HasDevUnits, Resolve, ResolveBehavior};
use crate::core::PackageIdSpecQuery as _;
use crate::core::{Edition, MaybePackage, Package, PackageId, Workspace}; use crate::core::{Edition, MaybePackage, Package, PackageId, Workspace};
use crate::core::{FeatureValue, PackageIdSpecQuery as _};
use crate::ops::resolve::WorkspaceResolve; use crate::ops::resolve::WorkspaceResolve;
use crate::ops::{self, CompileOptions}; use crate::ops::{self, CompileOptions};
use crate::util::diagnostic_server::{Message, RustfixDiagnosticServer}; use crate::util::diagnostic_server::{Message, RustfixDiagnosticServer};
use crate::util::errors::CargoResult; use crate::util::errors::CargoResult;
use crate::util::interning::InternedString;
use crate::util::GlobalContext; use crate::util::GlobalContext;
use crate::util::{existing_vcs_repo, LockServer, LockServerClient}; use crate::util::{existing_vcs_repo, LockServer, LockServerClient};
use crate::{drop_eprint, drop_eprintln}; use crate::{drop_eprint, drop_eprintln};
@ -287,7 +286,6 @@ fn migrate_manifests(ws: &Workspace<'_>, pkgs: &[&Package]) -> CargoResult<()> {
fixes += rename_dep_fields_2024(workspace, "dependencies"); fixes += rename_dep_fields_2024(workspace, "dependencies");
} }
fixes += add_feature_for_unused_deps(pkg, root, ws.gctx());
fixes += rename_table(root, "project", "package"); fixes += rename_table(root, "project", "package");
if let Some(target) = root.get_mut("lib").and_then(|t| t.as_table_like_mut()) { if let Some(target) = root.get_mut("lib").and_then(|t| t.as_table_like_mut()) {
fixes += rename_target_fields_2024(target); fixes += rename_target_fields_2024(target);
@ -437,79 +435,6 @@ fn rename_table(parent: &mut dyn toml_edit::TableLike, old: &str, new: &str) ->
1 1
} }
fn add_feature_for_unused_deps(
pkg: &Package,
parent: &mut dyn toml_edit::TableLike,
gctx: &GlobalContext,
) -> usize {
let manifest = pkg.manifest();
let activated_opt_deps = manifest
.normalized_toml()
.features()
.map(|map| {
map.values()
.flatten()
.filter_map(|f| match FeatureValue::new(InternedString::new(f)) {
FeatureValue::Dep { dep_name } => Some(dep_name.as_str()),
_ => None,
})
.collect::<HashSet<_>>()
})
.unwrap_or_default();
let mut fixes = 0;
for dep in manifest.dependencies() {
let dep_name_in_toml = dep.name_in_toml();
if dep.is_optional() && !activated_opt_deps.contains(dep_name_in_toml.as_str()) {
if let Some(features) = parent
.entry("features")
.or_insert(toml_edit::table())
.as_table_like_mut()
{
let activate_dep = format!("dep:{dep_name_in_toml}");
let strong_dep_feature_prefix = format!("{dep_name_in_toml}/");
features
.entry(dep_name_in_toml.as_str())
.or_insert_with(|| {
fixes += 1;
toml_edit::Item::Value(toml_edit::Value::Array(
toml_edit::Array::from_iter([&activate_dep]),
))
});
// Ensure `dep:dep_name` is present for `dep_name/feature_name` since `dep:` is the
// only way to guarantee an optional dependency is available for use.
//
// The way we avoid implicitly creating features in Edition2024 is we remove the
// dependency from `normalized_toml` if there is no `dep:` syntax as that is the only
// syntax that suppresses the creation of the implicit feature.
for (feature_name, activations) in features.iter_mut() {
let Some(activations) = activations.as_array_mut() else {
let _ = gctx.shell().warn(format_args!("skipping fix of feature `{feature_name}` in package `{}`: unsupported feature schema", pkg.name()));
continue;
};
if activations
.iter()
.any(|a| a.as_str().map(|a| a == activate_dep).unwrap_or(false))
{
continue;
}
let Some(activate_dep_pos) = activations.iter().position(|a| {
a.as_str()
.map(|a| a.starts_with(&strong_dep_feature_prefix))
.unwrap_or(false)
}) else {
continue;
};
fixes += 1;
activations.insert(activate_dep_pos, &activate_dep);
}
}
}
}
fixes
}
fn check_resolver_change<'gctx>( fn check_resolver_change<'gctx>(
ws: &Workspace<'gctx>, ws: &Workspace<'gctx>,
target_data: &mut RustcTargetData<'gctx>, target_data: &mut RustcTargetData<'gctx>,

View File

@ -1,24 +1,15 @@
use crate::core::dependency::DepKind; use crate::core::{Edition, Feature, Features, Manifest, Package};
use crate::core::FeatureValue::Dep;
use crate::core::{Edition, Feature, FeatureValue, Features, Manifest, Package};
use crate::util::interning::InternedString;
use crate::{CargoResult, GlobalContext}; use crate::{CargoResult, GlobalContext};
use annotate_snippets::{Level, Snippet}; use annotate_snippets::{Level, Snippet};
use cargo_util_schemas::manifest::{TomlLintLevel, TomlToolLints}; use cargo_util_schemas::manifest::{TomlLintLevel, TomlToolLints};
use pathdiff::diff_paths; use pathdiff::diff_paths;
use std::collections::HashSet;
use std::fmt::Display; use std::fmt::Display;
use std::ops::Range; use std::ops::Range;
use std::path::Path; use std::path::Path;
use toml_edit::ImDocument; use toml_edit::ImDocument;
const LINT_GROUPS: &[LintGroup] = &[TEST_DUMMY_UNSTABLE]; const LINT_GROUPS: &[LintGroup] = &[TEST_DUMMY_UNSTABLE];
pub const LINTS: &[Lint] = &[ pub const LINTS: &[Lint] = &[IM_A_TEAPOT, UNKNOWN_LINTS];
IMPLICIT_FEATURES,
IM_A_TEAPOT,
UNKNOWN_LINTS,
UNUSED_OPTIONAL_DEPENDENCY,
];
pub fn analyze_cargo_lints_table( pub fn analyze_cargo_lints_table(
pkg: &Package, pkg: &Package,
@ -487,127 +478,6 @@ pub fn check_im_a_teapot(
Ok(()) Ok(())
} }
const IMPLICIT_FEATURES: Lint = Lint {
name: "implicit_features",
desc: "implicit features for optional dependencies is deprecated and will be unavailable in the 2024 edition",
groups: &[],
default_level: LintLevel::Allow,
edition_lint_opts: None,
feature_gate: None,
docs: Some(r#"
### What it does
Checks for implicit features for optional dependencies
### Why it is bad
By default, cargo will treat any optional dependency as a [feature]. As of
cargo 1.60, these can be disabled by declaring a feature that activates the
optional dependency as `dep:<name>` (see [RFC #3143]).
In the 2024 edition, `cargo` will stop exposing optional dependencies as
features implicitly, requiring users to add `foo = ["dep:foo"]` if they
still want it exposed.
For more information, see [RFC #3491]
### Example
```toml
edition = "2021"
[dependencies]
bar = { version = "0.1.0", optional = true }
[features]
# No explicit feature activation for `bar`
```
Instead, the dependency should have an explicit feature:
```toml
edition = "2021"
[dependencies]
bar = { version = "0.1.0", optional = true }
[features]
bar = ["dep:bar"]
```
[feature]: https://doc.rust-lang.org/cargo/reference/features.html
[RFC #3143]: https://rust-lang.github.io/rfcs/3143-cargo-weak-namespaced-features.html
[RFC #3491]: https://rust-lang.github.io/rfcs/3491-remove-implicit-features.html
"#
),
};
pub fn check_implicit_features(
pkg: &Package,
path: &Path,
pkg_lints: &TomlToolLints,
error_count: &mut usize,
gctx: &GlobalContext,
) -> CargoResult<()> {
let manifest = pkg.manifest();
let edition = manifest.edition();
// In Edition 2024+, instead of creating optional features, the dependencies are unused.
// See `UNUSED_OPTIONAL_DEPENDENCY`
if edition >= Edition::Edition2024 {
return Ok(());
}
let (lint_level, reason) =
IMPLICIT_FEATURES.level(pkg_lints, edition, manifest.unstable_features());
if lint_level == LintLevel::Allow {
return Ok(());
}
let activated_opt_deps = manifest
.normalized_toml()
.features()
.map(|map| {
map.values()
.flatten()
.filter_map(|f| match FeatureValue::new(InternedString::new(f)) {
Dep { dep_name } => Some(dep_name.as_str()),
_ => None,
})
.collect::<HashSet<_>>()
})
.unwrap_or_default();
let mut emitted_source = None;
for dep in manifest.dependencies() {
let dep_name_in_toml = dep.name_in_toml();
if !dep.is_optional() || activated_opt_deps.contains(dep_name_in_toml.as_str()) {
continue;
}
if lint_level == LintLevel::Forbid || lint_level == LintLevel::Deny {
*error_count += 1;
}
let mut toml_path = vec![dep.kind().kind_table(), dep_name_in_toml.as_str()];
let platform = dep.platform().map(|p| p.to_string());
if let Some(platform) = platform.as_ref() {
toml_path.insert(0, platform);
toml_path.insert(0, "target");
}
let level = lint_level.to_diagnostic_level();
let manifest_path = rel_cwd_manifest_path(path, gctx);
let mut message = level.title(IMPLICIT_FEATURES.desc).snippet(
Snippet::source(manifest.contents())
.origin(&manifest_path)
.annotation(level.span(get_span(manifest.document(), &toml_path, false).unwrap()))
.fold(true),
);
if emitted_source.is_none() {
emitted_source = Some(format!(
"`cargo::{}` is set to `{lint_level}` {reason}",
IMPLICIT_FEATURES.name
));
message = message.footer(Level::Note.title(emitted_source.as_ref().unwrap()));
}
gctx.shell().print_message(message)?;
}
Ok(())
}
const UNKNOWN_LINTS: Lint = Lint { const UNKNOWN_LINTS: Lint = Lint {
name: "unknown_lints", name: "unknown_lints",
desc: "unknown lint", desc: "unknown lint",
@ -735,151 +605,6 @@ fn output_unknown_lints(
Ok(()) Ok(())
} }
const UNUSED_OPTIONAL_DEPENDENCY: Lint = Lint {
name: "unused_optional_dependency",
desc: "unused optional dependency",
groups: &[],
default_level: LintLevel::Warn,
edition_lint_opts: None,
feature_gate: None,
docs: Some(
r#"
### What it does
Checks for optional dependencies that are not activated by any feature
### Why it is bad
Starting in the 2024 edition, `cargo` no longer implicitly creates features
for optional dependencies (see [RFC #3491]). This means that any optional
dependency not specified with `"dep:<name>"` in some feature is now unused.
This change may be surprising to users who have been using the implicit
features `cargo` has been creating for optional dependencies.
### Example
```toml
edition = "2024"
[dependencies]
bar = { version = "0.1.0", optional = true }
[features]
# No explicit feature activation for `bar`
```
Instead, the dependency should be removed or activated in a feature:
```toml
edition = "2024"
[dependencies]
bar = { version = "0.1.0", optional = true }
[features]
bar = ["dep:bar"]
```
[RFC #3491]: https://rust-lang.github.io/rfcs/3491-remove-implicit-features.html
"#,
),
};
pub fn unused_dependencies(
pkg: &Package,
path: &Path,
pkg_lints: &TomlToolLints,
error_count: &mut usize,
gctx: &GlobalContext,
) -> CargoResult<()> {
let manifest = pkg.manifest();
let edition = manifest.edition();
// Unused optional dependencies can only exist on edition 2024+
if edition < Edition::Edition2024 {
return Ok(());
}
let (lint_level, reason) =
UNUSED_OPTIONAL_DEPENDENCY.level(pkg_lints, edition, manifest.unstable_features());
if lint_level == LintLevel::Allow {
return Ok(());
}
let mut emitted_source = None;
let original_toml = manifest.original_toml();
// Unused dependencies were stripped from the manifest, leaving only the used ones
let used_dependencies = manifest
.dependencies()
.into_iter()
.map(|d| d.name_in_toml().to_string())
.collect::<HashSet<String>>();
let mut orig_deps = vec![
(
original_toml.dependencies.as_ref(),
vec![DepKind::Normal.kind_table()],
),
(
original_toml.dev_dependencies.as_ref(),
vec![DepKind::Development.kind_table()],
),
(
original_toml.build_dependencies.as_ref(),
vec![DepKind::Build.kind_table()],
),
];
for (name, platform) in original_toml.target.iter().flatten() {
orig_deps.push((
platform.dependencies.as_ref(),
vec!["target", name, DepKind::Normal.kind_table()],
));
orig_deps.push((
platform.dev_dependencies.as_ref(),
vec!["target", name, DepKind::Development.kind_table()],
));
orig_deps.push((
platform.build_dependencies.as_ref(),
vec!["target", name, DepKind::Normal.kind_table()],
));
}
for (deps, toml_path) in orig_deps {
if let Some(deps) = deps {
for name in deps.keys() {
if !used_dependencies.contains(name.as_str()) {
if lint_level == LintLevel::Forbid || lint_level == LintLevel::Deny {
*error_count += 1;
}
let toml_path = toml_path
.iter()
.map(|s| *s)
.chain(std::iter::once(name.as_str()))
.collect::<Vec<_>>();
let level = lint_level.to_diagnostic_level();
let manifest_path = rel_cwd_manifest_path(path, gctx);
let mut message = level.title(UNUSED_OPTIONAL_DEPENDENCY.desc).snippet(
Snippet::source(manifest.contents())
.origin(&manifest_path)
.annotation(level.span(
get_span(manifest.document(), toml_path.as_slice(), false).unwrap(),
))
.fold(true),
);
if emitted_source.is_none() {
emitted_source = Some(format!(
"`cargo::{}` is set to `{lint_level}` {reason}",
UNUSED_OPTIONAL_DEPENDENCY.name
));
message =
message.footer(Level::Note.title(emitted_source.as_ref().unwrap()));
}
let help = format!(
"remove the dependency or activate it in a feature with `dep:{name}`"
);
message = message.footer(Level::Help.title(&help));
gctx.shell().print_message(message)?;
}
}
}
}
Ok(())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use itertools::Itertools; use itertools::Itertools;

View File

@ -1,5 +1,5 @@
use annotate_snippets::{Level, Snippet}; use annotate_snippets::{Level, Snippet};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc; use std::rc::Rc;
@ -24,7 +24,6 @@ use crate::core::compiler::{CompileKind, CompileTarget};
use crate::core::dependency::{Artifact, ArtifactTarget, DepKind}; use crate::core::dependency::{Artifact, ArtifactTarget, DepKind};
use crate::core::manifest::{ManifestMetadata, TargetSourcePath}; use crate::core::manifest::{ManifestMetadata, TargetSourcePath};
use crate::core::resolver::ResolveBehavior; use crate::core::resolver::ResolveBehavior;
use crate::core::FeatureValue::Dep;
use crate::core::{find_workspace_root, resolve_relative_path, CliUnstable, FeatureValue}; use crate::core::{find_workspace_root, resolve_relative_path, CliUnstable, FeatureValue};
use crate::core::{Dependency, Manifest, Package, PackageId, Summary, Target}; use crate::core::{Dependency, Manifest, Package, PackageId, Summary, Target};
use crate::core::{Edition, EitherManifest, Feature, Features, VirtualManifest, Workspace}; use crate::core::{Edition, EitherManifest, Feature, Features, VirtualManifest, Workspace};
@ -372,26 +371,11 @@ fn normalize_toml(
errors, errors,
)?); )?);
let activated_opt_deps = normalized_toml
.features
.as_ref()
.map(|map| {
map.values()
.flatten()
.filter_map(|f| match FeatureValue::new(InternedString::new(f)) {
Dep { dep_name } => Some(dep_name.as_str()),
_ => None,
})
.collect::<HashSet<_>>()
})
.unwrap_or_default();
normalized_toml.dependencies = normalize_dependencies( normalized_toml.dependencies = normalize_dependencies(
gctx, gctx,
edition, edition,
&features, &features,
original_toml.dependencies.as_ref(), original_toml.dependencies.as_ref(),
&activated_opt_deps,
None, None,
&inherit, &inherit,
&workspace_root, &workspace_root,
@ -412,7 +396,6 @@ fn normalize_toml(
edition, edition,
&features, &features,
original_toml.dev_dependencies(), original_toml.dev_dependencies(),
&activated_opt_deps,
Some(DepKind::Development), Some(DepKind::Development),
&inherit, &inherit,
&workspace_root, &workspace_root,
@ -433,7 +416,6 @@ fn normalize_toml(
edition, edition,
&features, &features,
original_toml.build_dependencies(), original_toml.build_dependencies(),
&activated_opt_deps,
Some(DepKind::Build), Some(DepKind::Build),
&inherit, &inherit,
&workspace_root, &workspace_root,
@ -447,7 +429,6 @@ fn normalize_toml(
edition, edition,
&features, &features,
platform.dependencies.as_ref(), platform.dependencies.as_ref(),
&activated_opt_deps,
None, None,
&inherit, &inherit,
&workspace_root, &workspace_root,
@ -468,7 +449,6 @@ fn normalize_toml(
edition, edition,
&features, &features,
platform.dev_dependencies(), platform.dev_dependencies(),
&activated_opt_deps,
Some(DepKind::Development), Some(DepKind::Development),
&inherit, &inherit,
&workspace_root, &workspace_root,
@ -489,7 +469,6 @@ fn normalize_toml(
edition, edition,
&features, &features,
platform.build_dependencies(), platform.build_dependencies(),
&activated_opt_deps,
Some(DepKind::Build), Some(DepKind::Build),
&inherit, &inherit,
&workspace_root, &workspace_root,
@ -756,7 +735,6 @@ fn normalize_dependencies<'a>(
edition: Edition, edition: Edition,
features: &Features, features: &Features,
orig_deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>, orig_deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
activated_opt_deps: &HashSet<&str>,
kind: Option<DepKind>, kind: Option<DepKind>,
inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>, inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>,
workspace_root: &dyn Fn() -> CargoResult<&'a Path>, workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
@ -818,18 +796,10 @@ fn normalize_dependencies<'a>(
.with_context(|| format!("resolving path dependency {name_in_toml}"))?; .with_context(|| format!("resolving path dependency {name_in_toml}"))?;
} }
// if the dependency is not optional, it is always used deps.insert(
// if the dependency is optional and activated, it is used name_in_toml.clone(),
// if the dependency is optional and not activated, it is not used manifest::InheritableDependency::Value(resolved.clone()),
let is_dep_activated = );
!resolved.is_optional() || activated_opt_deps.contains(name_in_toml.as_str());
// If the edition is less than 2024, we don't need to check for unused optional dependencies
if edition < Edition::Edition2024 || is_dep_activated {
deps.insert(
name_in_toml.clone(),
manifest::InheritableDependency::Value(resolved.clone()),
);
}
} }
Ok(Some(deps)) Ok(Some(deps))
} }

View File

@ -2,60 +2,10 @@
Note: [Cargo's linting system is unstable](unstable.md#lintscargo) and can only be used on nightly toolchains Note: [Cargo's linting system is unstable](unstable.md#lintscargo) and can only be used on nightly toolchains
## Allowed-by-default
These lints are all set to the 'allow' level by default.
- [`implicit_features`](#implicit_features)
## Warn-by-default ## Warn-by-default
These lints are all set to the 'warn' level by default. These lints are all set to the 'warn' level by default.
- [`unknown_lints`](#unknown_lints) - [`unknown_lints`](#unknown_lints)
- [`unused_optional_dependency`](#unused_optional_dependency)
## `implicit_features`
Set to `allow` by default
### What it does
Checks for implicit features for optional dependencies
### Why it is bad
By default, cargo will treat any optional dependency as a [feature]. As of
cargo 1.60, these can be disabled by declaring a feature that activates the
optional dependency as `dep:<name>` (see [RFC #3143]).
In the 2024 edition, `cargo` will stop exposing optional dependencies as
features implicitly, requiring users to add `foo = ["dep:foo"]` if they
still want it exposed.
For more information, see [RFC #3491]
### Example
```toml
edition = "2021"
[dependencies]
bar = { version = "0.1.0", optional = true }
[features]
# No explicit feature activation for `bar`
```
Instead, the dependency should have an explicit feature:
```toml
edition = "2021"
[dependencies]
bar = { version = "0.1.0", optional = true }
[features]
bar = ["dep:bar"]
```
[feature]: https://doc.rust-lang.org/cargo/reference/features.html
[RFC #3143]: https://rust-lang.github.io/rfcs/3143-cargo-weak-namespaced-features.html
[RFC #3491]: https://rust-lang.github.io/rfcs/3491-remove-implicit-features.html
## `unknown_lints` ## `unknown_lints`
Set to `warn` by default Set to `warn` by default
@ -76,41 +26,3 @@ this-lint-does-not-exist = "warn"
``` ```
## `unused_optional_dependency`
Set to `warn` by default
### What it does
Checks for optional dependencies that are not activated by any feature
### Why it is bad
Starting in the 2024 edition, `cargo` no longer implicitly creates features
for optional dependencies (see [RFC #3491]). This means that any optional
dependency not specified with `"dep:<name>"` in some feature is now unused.
This change may be surprising to users who have been using the implicit
features `cargo` has been creating for optional dependencies.
### Example
```toml
edition = "2024"
[dependencies]
bar = { version = "0.1.0", optional = true }
[features]
# No explicit feature activation for `bar`
```
Instead, the dependency should be removed or activated in a feature:
```toml
edition = "2024"
[dependencies]
bar = { version = "0.1.0", optional = true }
[features]
bar = ["dep:bar"]
```
[RFC #3491]: https://rust-lang.github.io/rfcs/3491-remove-implicit-features.html

View File

@ -1846,63 +1846,6 @@ fn features_option_given_twice() {
p.cargo("check --features a --features b").run(); p.cargo("check --features a --features b").run();
} }
#[cargo_test(nightly, reason = "edition2024 is not stable")]
fn strong_dep_feature_edition2024() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[features]
optional_dep = ["optional_dep/foo"]
[dependencies]
optional_dep = { path = "optional_dep", optional = true }
"#,
)
.file(
"src/main.rs",
r#"
fn main() {}
"#,
)
.file(
"optional_dep/Cargo.toml",
r#"
[package]
name = "optional_dep"
[features]
foo = []
"#,
)
.file(
"optional_dep/src/lib.rs",
r#"
"#,
)
.build();
p.cargo("metadata")
.masquerade_as_nightly_cargo(&["edition2024"])
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] feature `optional_dep` includes `optional_dep/foo`, but `optional_dep` is not a dependency
--> Cargo.toml:9:32
|
9 | optional_dep = ["optional_dep/foo"]
| ^^^^^^^^^^^^^^^^^^^^
|
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
"#]])
.run();
}
#[cargo_test] #[cargo_test]
fn multi_multi_features() { fn multi_multi_features() {
let p = project() let p = project()

View File

@ -2596,212 +2596,6 @@ a = {path = "a", default-features = false}
); );
} }
#[cargo_test]
fn add_feature_for_unused_dep() {
Package::new("regular-dep", "0.1.0").publish();
Package::new("build-dep", "0.1.0").publish();
Package::new("target-dep", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
regular-dep = { version = "0.1.0", optional = true }
[build-dependencies]
build-dep = { version = "0.1.0", optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
target-dep = { version = "0.1.0", optional = true }
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("fix --edition --allow-no-vcs")
.masquerade_as_nightly_cargo(&["edition2024"])
.with_stderr_data(str![[r#"
[MIGRATING] Cargo.toml from 2021 edition to 2024
[FIXED] Cargo.toml (3 fixes)
[UPDATING] `dummy-registry` index
[LOCKING] 3 packages to latest compatible versions
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[MIGRATING] src/lib.rs from 2021 edition to 2024
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
assert_e2e().eq(
p.read_file("Cargo.toml"),
str![[r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
regular-dep = { version = "0.1.0", optional = true }
[build-dependencies]
build-dep = { version = "0.1.0", optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
target-dep = { version = "0.1.0", optional = true }
[features]
regular-dep = ["dep:regular-dep"]
build-dep = ["dep:build-dep"]
target-dep = ["dep:target-dep"]
"#]],
);
}
#[cargo_test]
fn add_feature_for_unused_dep_existing_table() {
Package::new("dep", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
dep = { version = "0.1.0", optional = true }
[features]
existing = []
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("fix --edition --allow-no-vcs")
.masquerade_as_nightly_cargo(&["edition2024"])
.with_stderr_data(str![[r#"
[MIGRATING] Cargo.toml from 2021 edition to 2024
[FIXED] Cargo.toml (1 fix)
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[MIGRATING] src/lib.rs from 2021 edition to 2024
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
assert_e2e().eq(
p.read_file("Cargo.toml"),
str![[r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
dep = { version = "0.1.0", optional = true }
[features]
existing = []
dep = ["dep:dep"]
"#]],
);
}
#[cargo_test]
fn activate_dep_for_dep_feature() {
Package::new("dep-feature", "0.1.0")
.feature("a", &[])
.feature("b", &[])
.publish();
Package::new("dep-and-dep-feature", "0.1.0")
.feature("a", &[])
.feature("b", &[])
.publish();
Package::new("renamed-feature", "0.1.0")
.feature("a", &[])
.feature("b", &[])
.publish();
Package::new("unrelated-feature", "0.1.0")
.feature("a", &[])
.feature("b", &[])
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
dep-feature = { version = "0.1.0", optional = true }
dep-and-dep-feature = { version = "0.1.0", optional = true }
renamed-feature = { version = "0.1.0", optional = true }
unrelated-feature = { version = "0.1.0", optional = true }
[features]
dep-feature = ["dep-feature/a", "dep-feature/b"]
dep-and-dep-feature = ["dep:dep-and-dep-feature", "dep-and-dep-feature/a", "dep-and-dep-feature/b"]
renamed = ["renamed-feature/a", "renamed-feature/b"]
unrelated-feature = []
unrelated-dep-feature = ["unrelated-feature/a", "unrelated-feature/b"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("fix --edition --allow-no-vcs")
.masquerade_as_nightly_cargo(&["edition2024"])
.with_stderr_data(str![[r#"
[MIGRATING] Cargo.toml from 2021 edition to 2024
[FIXED] Cargo.toml (4 fixes)
[UPDATING] `dummy-registry` index
[LOCKING] 4 packages to latest compatible versions
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[MIGRATING] src/lib.rs from 2021 edition to 2024
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
assert_e2e().eq(
p.read_file("Cargo.toml"),
str![[r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
dep-feature = { version = "0.1.0", optional = true }
dep-and-dep-feature = { version = "0.1.0", optional = true }
renamed-feature = { version = "0.1.0", optional = true }
unrelated-feature = { version = "0.1.0", optional = true }
[features]
dep-feature = [ "dep:dep-feature","dep-feature/a", "dep-feature/b"]
dep-and-dep-feature = ["dep:dep-and-dep-feature", "dep-and-dep-feature/a", "dep-and-dep-feature/b"]
renamed = [ "dep:renamed-feature","renamed-feature/a", "renamed-feature/b"]
unrelated-feature = []
unrelated-dep-feature = [ "dep:unrelated-feature","unrelated-feature/a", "unrelated-feature/b"]
renamed-feature = ["dep:renamed-feature"]
"#]],
);
}
#[cargo_test] #[cargo_test]
fn remove_ignored_default_features() { fn remove_ignored_default_features() {
Package::new("dep_simple", "0.1.0").publish(); Package::new("dep_simple", "0.1.0").publish();

View File

@ -1,136 +0,0 @@
use cargo_test_support::prelude::*;
use cargo_test_support::project;
use cargo_test_support::registry::Package;
use cargo_test_support::str;
#[cargo_test]
fn default() {
Package::new("bar", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
bar = { version = "0.1.0", optional = true }
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}
#[cargo_test]
fn warn() {
Package::new("bar", "0.1.0").publish();
Package::new("baz", "0.1.0").publish();
Package::new("target-dep", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
bar = { version = "0.1.0", optional = true }
[build-dependencies]
baz = { version = "0.1.0", optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
target-dep = { version = "0.1.0", optional = true }
[lints.cargo]
implicit_features = "warn"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints"])
.with_stderr_data(str![[r#"
[WARNING] implicit features for optional dependencies is deprecated and will be unavailable in the 2024 edition
--> Cargo.toml:8:1
|
8 | bar = { version = "0.1.0", optional = true }
| ---
|
= [NOTE] `cargo::implicit_features` is set to `warn` in `[lints]`
[WARNING] implicit features for optional dependencies is deprecated and will be unavailable in the 2024 edition
--> Cargo.toml:11:1
|
11 | baz = { version = "0.1.0", optional = true }
| ---
|
[WARNING] implicit features for optional dependencies is deprecated and will be unavailable in the 2024 edition
--> Cargo.toml:14:1
|
14 | target-dep = { version = "0.1.0", optional = true }
| ----------
|
[UPDATING] `dummy-registry` index
[LOCKING] 3 packages to latest compatible versions
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}
#[cargo_test(nightly, reason = "edition2024 is not stable")]
fn implicit_features_edition_2024() {
Package::new("bar", "0.1.0").publish();
Package::new("baz", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
bar = { version = "0.1.0", optional = true }
baz = { version = "0.1.0", optional = true }
[features]
baz = ["dep:baz"]
[lints.cargo]
unused_optional_dependency = "allow"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest Rust 1.[..] compatible version
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}

View File

@ -4,10 +4,8 @@ use cargo_test_support::registry::Package;
use cargo_test_support::str; use cargo_test_support::str;
mod error; mod error;
mod implicit_features;
mod inherited; mod inherited;
mod unknown_lints; mod unknown_lints;
mod unused_optional_dependencies;
mod warning; mod warning;
#[cargo_test] #[cargo_test]
@ -177,16 +175,19 @@ fn cap_lints() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ["test-dummy-unstable"]
[package] [package]
name = "bar" name = "bar"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
im-a-teapot = true
[dependencies] [dependencies]
baz = { version = "0.1.0", optional = true } baz = { version = "0.1.0", optional = true }
[lints.cargo] [lints.cargo]
implicit_features = "warn" im_a_teapot = "warn"
"#, "#,
) )
.file("src/lib.rs", "") .file("src/lib.rs", "")
@ -202,9 +203,6 @@ edition = "2021"
[dependencies] [dependencies]
bar = "0.1.0" bar = "0.1.0"
[lints.cargo]
implicit_features = "warn"
"#, "#,
) )
.file("src/lib.rs", "") .file("src/lib.rs", "")

View File

@ -1,349 +0,0 @@
use cargo_test_support::prelude::*;
use cargo_test_support::project;
use cargo_test_support::registry::Package;
use cargo_test_support::str;
#[cargo_test(nightly, reason = "edition2024 is not stable")]
fn default() {
Package::new("bar", "0.1.0").publish();
Package::new("baz", "0.1.0").publish();
Package::new("target-dep", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
bar = { version = "0.1.0", optional = true }
[build-dependencies]
baz = { version = "0.1.0", optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
target-dep = { version = "0.1.0", optional = true }
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
.with_stderr_data(str![[r#"
[WARNING] unused optional dependency
--> Cargo.toml:9:1
|
9 | bar = { version = "0.1.0", optional = true }
| ---
|
= [NOTE] `cargo::unused_optional_dependency` is set to `warn` by default
= [HELP] remove the dependency or activate it in a feature with `dep:bar`
[WARNING] unused optional dependency
--> Cargo.toml:12:1
|
12 | baz = { version = "0.1.0", optional = true }
| ---
|
= [HELP] remove the dependency or activate it in a feature with `dep:baz`
[WARNING] unused optional dependency
--> Cargo.toml:15:1
|
15 | target-dep = { version = "0.1.0", optional = true }
| ----------
|
= [HELP] remove the dependency or activate it in a feature with `dep:target-dep`
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}
#[cargo_test]
fn edition_2021() {
Package::new("bar", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
bar = { version = "0.1.0", optional = true }
[lints.cargo]
implicit_features = "allow"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}
#[cargo_test(nightly, reason = "edition2024 is not stable")]
fn renamed_deps() {
Package::new("bar", "0.1.0").publish();
Package::new("bar", "0.2.0").publish();
Package::new("target-dep", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
bar = { version = "0.1.0", optional = true }
[build-dependencies]
baz = { version = "0.2.0", package = "bar", optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
target-dep = { version = "0.1.0", optional = true }
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
.with_stderr_data(str![[r#"
[WARNING] unused optional dependency
--> Cargo.toml:9:1
|
9 | bar = { version = "0.1.0", optional = true }
| ---
|
= [NOTE] `cargo::unused_optional_dependency` is set to `warn` by default
= [HELP] remove the dependency or activate it in a feature with `dep:bar`
[WARNING] unused optional dependency
--> Cargo.toml:12:1
|
12 | baz = { version = "0.2.0", package = "bar", optional = true }
| ---
|
= [HELP] remove the dependency or activate it in a feature with `dep:baz`
[WARNING] unused optional dependency
--> Cargo.toml:15:1
|
15 | target-dep = { version = "0.1.0", optional = true }
| ----------
|
= [HELP] remove the dependency or activate it in a feature with `dep:target-dep`
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}
#[cargo_test(nightly, reason = "edition2024 is not stable")]
fn shadowed_optional_dep_is_unused_in_2024() {
Package::new("optional-dep", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
optional-dep = { version = "0.1.0", optional = true }
[features]
optional-dep = []
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
.with_stderr_data(str![[r#"
[WARNING] unused optional dependency
--> Cargo.toml:9:1
|
9 | optional-dep = { version = "0.1.0", optional = true }
| ------------
|
= [NOTE] `cargo::unused_optional_dependency` is set to `warn` by default
= [HELP] remove the dependency or activate it in a feature with `dep:optional-dep`
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}
#[cargo_test(nightly, reason = "edition2024 is not stable")]
fn inactive_weak_optional_dep() {
Package::new("dep_name", "0.1.0")
.feature("dep_feature", &[])
.publish();
// `dep_name`` is included as a weak optional dependency throught speficying the `dep_name?/dep_feature` in feature table.
// In edition2024, `dep_name` need to be add `dep:dep_name` to feature table to activate it.
// This test explain the conclusion mentioned above
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
dep_name = { version = "0.1.0", optional = true }
[features]
foo_feature = ["dep:dep_name", "dep_name?/dep_feature"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
.run();
// This test proves no regression when dep_name isn't included
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
[features]
foo_feature = ["dep_name?/dep_feature"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] feature `foo_feature` includes `dep_name?/dep_feature`, but `dep_name` is not a dependency
--> Cargo.toml:11:27
|
11 | foo_feature = ["dep_name?/dep_feature"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
"#]])
.run();
// Ensure that a weak dependency feature requires the existence of a `dep:` feature in edition 2024.
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
dep_name = { version = "0.1.0", optional = true }
[features]
foo_feature = ["dep_name?/dep_feature"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] feature `foo_feature` includes `dep_name?/dep_feature`, but `dep_name` is not a dependency
--> Cargo.toml:12:31
|
9 | dep_name = { version = "0.1.0", optional = true }
| -------- `dep_name` is an unused optional dependency since no feature enables it
10 |
11 | [features]
12 | foo_feature = ["dep_name?/dep_feature"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= [HELP] enable the dependency with `dep:dep_name`
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
"#]])
.run();
// Check target.'cfg(unix)'.dependencies can work
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[target.'cfg(unix)'.dependencies]
dep_name = { version = "0.1.0", optional = true }
[features]
foo_feature = ["dep_name?/dep_feature"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] feature `foo_feature` includes `dep_name?/dep_feature`, but `dep_name` is not a dependency
--> Cargo.toml:12:27
|
9 | dep_name = { version = "0.1.0", optional = true }
| -------- `dep_name` is an unused optional dependency since no feature enables it
10 |
11 | [features]
12 | foo_feature = ["dep_name?/dep_feature"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= [HELP] enable the dependency with `dep:dep_name`
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
"#]])
.run();
}

View File

@ -3499,57 +3499,6 @@ fn versionless_package() {
.run(); .run();
} }
#[cargo_test(nightly, reason = "edition2024 is not stable")]
fn unused_deps_edition_2024() {
let registry = RegistryBuilder::new().http_api().http_index().build();
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition2024"]
[package]
name = "foo"
version = "0.0.1"
authors = []
license = "MIT"
description = "foo"
edition = "2024"
[dependencies]
bar = { version = "0.1.0", optional = true }
[build-dependencies]
baz = { version = "0.1.0", optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
target-dep = { version = "0.1.0", optional = true }
"#,
)
.file("src/main.rs", "")
.build();
p.cargo("publish --no-verify")
.masquerade_as_nightly_cargo(&["edition2024"])
.replace_crates_io(registry.index_url())
.with_stderr_data(str![[r#"
[UPDATING] crates.io index
[WARNING] manifest has no documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
[UPLOADED] foo v0.0.1 to registry `crates-io`
[NOTE] waiting for `foo v0.0.1` to be available at registry `crates-io`.
You may press ctrl-c to skip waiting; the crate should be available shortly.
[PUBLISHED] foo v0.0.1 at registry `crates-io`
"#]])
.run();
validate_upload_foo();
}
// A workspace with three projects that depend on one another (level1 -> level2 -> level3). // A workspace with three projects that depend on one another (level1 -> level2 -> level3).
// level1 is a binary package, to test lockfile generation. // level1 is a binary package, to test lockfile generation.
fn workspace_with_local_deps_project() -> Project { fn workspace_with_local_deps_project() -> Project {