Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions doc/book/src/reference/specifying-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,11 +637,16 @@ dependency in the workspace's [`[workspace.dependencies]`][workspace.dependencie
After that, add it to the `[dependencies]` table with `workspace = true`.

Along with the `workspace` key, dependencies can also include these keys:
- [`optional`][optional]: Note that the`[workspace.dependencies]` table is not allowed to specify `optional`.
- [`optional`][optional]: Note that the `[workspace.dependencies]` table is not allowed to specify `optional`.
- [`features`][features]: These are additive with the features declared in the `[workspace.dependencies]`
- [`default-features`][default-features] (Edition 2024 packages, requires Rust 1.99+):
Overrides the value set in `[workspace.dependencies]`.
If neither the package nor the workspace specifies `default-features`, it defaults to `true`.
Before Rust 1.99, or in earlier editions, package-level `default-features = false`
may be ignored or rejected unless the workspace dependency also disables default features.

Other than `optional` and `features`, inherited dependencies cannot use any other
dependency key (such as `version` or `default-features`).
Other than `optional`, `features`, and `default-features`, inherited dependencies
cannot use any other dependency key (such as `version`).

Dependencies in the `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]`, and
`[target."...".dependencies]` sections support the ability to reference the
Expand All @@ -651,9 +656,11 @@ Dependencies in the `[dependencies]`, `[dev-dependencies]`, `[build-dependencies
[package]
name = "bar"
version = "0.2.0"
edition = "2024"

[dependencies]
regex = { workspace = true, features = ["unicode"] }
serde = { workspace = true, default-features = false }

[build-dependencies]
cc.workspace = true
Expand All @@ -669,3 +676,4 @@ rand = { workspace = true, optional = true }
[workspace.dependencies]: workspaces.md#the-dependencies-table
[optional]: features.md#optional-dependencies
[features]: features.md
[default-features]: features.md#dependency-features
7 changes: 7 additions & 0 deletions doc/book/src/reference/workspaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ inherited by members of a workspace.
Specifying a workspace dependency is similar to [package dependencies][specifying-dependencies] except:
- Dependencies from this table cannot be declared as `optional`
- [`features`][features] declared in this table are additive with the `features` from `[dependencies]`
- `default-features` declared on a package dependency overrides the value set
here in Edition 2024 packages (requires Rust 1.99+). Before Rust 1.99, or in
earlier editions, package-level `default-features = false` may be ignored or
rejected unless the workspace dependency also disables default features.

You can then [inherit the workspace dependency as a package dependency][inheriting-a-dependency-from-a-workspace]

Expand All @@ -212,16 +216,19 @@ members = ["bar"]
cc = "1.0.73"
rand = "0.8.5"
regex = { version = "1.6.0", default-features = false, features = ["std"] }
serde = { version = "1.0.190", default-features = true }
```

```toml
# [PROJECT_DIR]/bar/Cargo.toml
[package]
name = "bar"
version = "0.2.0"
edition = "2024"

[dependencies]
regex = { workspace = true, features = ["unicode"] }
serde = { workspace = true, default-features = false }

[build-dependencies]
cc.workspace = true
Expand Down
26 changes: 21 additions & 5 deletions src/ops/cargo_add/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::util::OptVersionReq;
use crate::util::cache_lock::CacheLockMode;
use crate::util::edit_distance;
use crate::util::style;
use crate::workspace::Edition;
use crate::workspace::Feature;
use crate::workspace::FeatureValue;
use crate::workspace::Features;
Expand Down Expand Up @@ -503,7 +504,20 @@ fn resolve_dependency(
}

if let Some(Source::Workspace(_)) = dependency.source() {
check_invalid_ws_keys(dependency.toml_key(), arg)?;
check_invalid_ws_keys(dependency.toml_key(), arg, spec.manifest().edition())?;
if spec.manifest().edition() >= Edition::Edition2024 && arg.default_features == Some(true) {
let ws_dep = find_workspace_dep(
dependency.toml_key(),
ws,
ws.root_manifest(),
ws.unstable_features(),
)?;
// Only write `default-features = true` when the workspace dependency
// explicitly disables default features.
if ws_dep.default_features() == Some(false) {
dependency.default_features = Some(true);
}
}
}

let version_required = dependency.source().and_then(|s| s.as_registry()).is_some();
Expand Down Expand Up @@ -703,14 +717,16 @@ fn fuzzy_lookup(

/// When { workspace = true } you cannot define other keys that configure
/// the source of the dependency such as `version`, `registry`, `registry-index`,
/// `path`, `git`, `branch`, `tag`, `rev`, or `package`. You can also not define
/// `default-features`.
/// `path`, `git`, `branch`, `tag`, `rev`, or `package`.
/// Prior to Edition 2024 (RFC 3945) `default-features` was also forbidden;
/// from Edition 2024 onwards a package-level `default-features` overrides the
/// workspace value, so it is allowed.
///
/// Only `default-features`, `registry` and `rename` need to be checked
/// for currently. This is because `git` and its associated keys, `path`, and
/// `version` should all bee checked before this is called. `rename` is checked
/// for as it turns into `package`
fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp) -> CargoResult<()> {
fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp, edition: Edition) -> CargoResult<()> {
fn err_msg(toml_key: &str, flag: &str, field: &str) -> String {
format!(
"cannot override workspace dependency with `{flag}`, \
Expand All @@ -719,7 +735,7 @@ fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp) -> CargoResult<()> {
)
}

if arg.default_features.is_some() {
if arg.default_features.is_some() && edition < Edition::Edition2024 {
anyhow::bail!(
"{}",
err_msg(toml_key, "--default-features", "default-features")
Expand Down
10 changes: 5 additions & 5 deletions src/workspace/editor/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,8 @@ impl Dependency {
///
/// Returns a tuple with the dependency's name and either the version as a
/// `String` or the path/git repository as an `InlineTable`.
/// (If the dependency is set as `optional` or `default-features` is set to
/// `false`, an `InlineTable` is returned in any case.)
/// (If the dependency is set as `optional` or `default-features` is
/// explicitly set, an `InlineTable` is returned in any case.)
///
/// # Panic
///
Expand All @@ -414,7 +414,7 @@ impl Dependency {
self.public.unwrap_or(false),
self.optional.unwrap_or(false),
self.features.as_ref(),
self.default_features.unwrap_or(true),
self.default_features,
self.source.as_ref(),
self.registry.as_ref(),
self.rename.as_ref(),
Expand All @@ -424,12 +424,12 @@ impl Dependency {
false,
false,
None,
true,
None,

@epage epage Jun 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a test case for

$ cargo init
$ cargo add foo --default-features

ie explicit --default-features with the foo = "1.0" case

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we don’t have a case that compiles with cargo init, especially with edition 2024. So I added a new one.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just dropped it, as I had a conversation with @epage offline. He didn’t mean to literally have cargo init here, but to start with a fresh Cargo.toml. A test would exist to make sure --default-features doesn’t set default-features = true when it isn’t needed, as tests/testsuite/cargo_add/default_features already covered this. So I dropped my last commit.

Some(Source::Registry(RegistrySource { version: v })),
None,
None,
) => toml_edit::value(v),
(false, false, None, true, Some(Source::Workspace(WorkspaceSource {})), None, None) => {
(false, false, None, None, Some(Source::Workspace(WorkspaceSource {})), None, None) => {
let mut table = toml_edit::InlineTable::default();
table.set_dotted(true);
table.insert("workspace", true.into());
Expand Down
61 changes: 31 additions & 30 deletions src/workspace/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1216,25 +1216,31 @@ fn inner_dependency_inherit_with<'a>(
} = &pkg_dep;
let default_features = default_features.or(*default_features2);

match (default_features, merged_dep.default_features()) {
// member: default-features = true and
// workspace: default-features = false should turn on
// default-features
(Some(true), Some(false)) => {
merged_dep.default_features = Some(true);
}
// member: default-features = false and
// workspace: default-features = true should ignore member
// default-features
(Some(false), Some(true)) => {
deprecated_ws_default_features(name, Some(true), edition, warnings)?;
}
// member: default-features = false and
// workspace: dep = "1.0" should ignore member default-features
(Some(false), None) => {
deprecated_ws_default_features(name, None, edition, warnings)?;
// RFC 3945: Allow workspace members to override the workspace dependency's
// `default-features` setting.
if edition >= Edition::Edition2024 {
merged_dep.default_features = default_features.or(merged_dep.default_features);
} else {
match (default_features, merged_dep.default_features()) {
// member: default-features = true and
// workspace: default-features = false should turn on
// default-features
(Some(true), Some(false)) => {
merged_dep.default_features = Some(true);
}
// member: default-features = false and
// workspace: default-features = true should ignore member
// default-features
(Some(false), Some(true)) => {
deprecated_ws_default_features(name, Some(true), warnings);
}
// member: default-features = false and
// workspace: dep = "1.0" should ignore member default-features
(Some(false), None) => {
deprecated_ws_default_features(name, None, warnings);
}
_ => {}
}
_ => {}
}
merged_dep.features = match (merged_dep.features.clone(), features.clone()) {
(Some(dep_feat), Some(inherit_feat)) => Some(
Expand All @@ -1255,24 +1261,19 @@ fn inner_dependency_inherit_with<'a>(
fn deprecated_ws_default_features(
label: &str,
ws_def_feat: Option<bool>,
edition: Edition,
warnings: &mut Vec<String>,
) -> CargoResult<()> {
) {
let ws_def_feat = match ws_def_feat {
Some(true) => "true",
Some(false) => "false",
None => "not specified",
};
if Edition::Edition2024 <= edition {
anyhow::bail!("`default-features = false` cannot override workspace's `default-features`");
} else {
warnings.push(format!(
"`default-features` is ignored for {label}, since `default-features` was \
{ws_def_feat} for `workspace.dependencies.{label}`, \
this could become a hard error in the future"
));
}
Ok(())
warnings.push(format!(
"`default-features` is ignored for {label}, since `default-features` was \
{ws_def_feat} for `workspace.dependencies.{label}`; \
overriding workspace `default-features` to false requires Rust 1.99+ \
and the 2024 edition"
));
}

#[tracing::instrument(skip_all)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[workspace]
members = ["foo", "bar"]
resolver = "3"

[workspace.dependencies]
foo = { version = "0.0.0", path = "./foo", default-features = false }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "bar"
version = "0.0.0"
edition = "2024"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[package]
name = "foo"
version = "0.0.0"
edition = "2024"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::prelude::*;
use cargo_test_support::Project;
use cargo_test_support::compare::assert_ui;
use cargo_test_support::current_dir;
use cargo_test_support::file;
use cargo_test_support::str;

#[cargo_test]
fn case() {
cargo_test_support::registry::init();
let project = Project::from_template(current_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;

snapbox::cmd::Command::cargo_ui()
.arg("add")
.args(["foo", "--default-features", "-p", "bar"])
.current_dir(cwd)
.assert()
.success()
.stdout_eq(str![""])
.stderr_eq(file!["stderr.term.svg"]);

assert_ui().subset_matches(current_dir!().join("out"), &project_root);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[workspace]
members = ["foo", "bar"]
resolver = "3"

[workspace.dependencies]
foo = { version = "0.0.0", path = "./foo", default-features = false }
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "bar"
version = "0.0.0"
edition = "2024"

[dependencies]
foo = { workspace = true, default-features = true }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[package]
name = "foo"
version = "0.0.0"
edition = "2024"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[workspace]
members = ["foo", "bar"]
resolver = "3"

[workspace.dependencies]
foo = { version = "0.0.0", path = "./foo", default-features = true }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "bar"
version = "0.0.0"
edition = "2024"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[package]
name = "foo"
version = "0.0.0"
edition = "2024"
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::prelude::*;
use cargo_test_support::Project;
use cargo_test_support::compare::assert_ui;
use cargo_test_support::current_dir;
use cargo_test_support::file;
use cargo_test_support::str;

#[cargo_test]
fn case() {
cargo_test_support::registry::init();
let project = Project::from_template(current_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;

snapbox::cmd::Command::cargo_ui()
.arg("add")
.args(["foo", "--no-default-features", "-p", "bar"])
.current_dir(cwd)
.assert()
.success()
.stdout_eq(str![""])
.stderr_eq(file!["stderr.term.svg"]);

assert_ui().subset_matches(current_dir!().join("out"), &project_root);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[workspace]
members = ["foo", "bar"]
resolver = "3"

[workspace.dependencies]
foo = { version = "0.0.0", path = "./foo", default-features = true }
Loading