Skip to content
Merged
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 @@ -203,6 +203,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 @@ -216,16 +220,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,
Comment thread
epage marked this conversation as resolved.
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 @@ -1223,25 +1223,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 @@ -1262,24 +1268,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