Skip to content

Commit 65b2149

Browse files
committed
Auto merge of #11242 - cassaundra:remove-workspace, r=epage
Clean up workspace dependencies after cargo remove ### What does this PR try to resolve? After successful removal of an inherited dependency from a workspace member, clean up the root workspace manifest. This PR is part of the continued working on cargo remove (#11099, see deferred work). ### How should we test and review this PR? Make sure the tests cover all possible use cases. After posting this PR, I will post a short self-review regarding some design concerns. ### Additional information #11194 is currently blocked on this feature.
2 parents 16c9c4e + 38b23d5 commit 65b2149

File tree

34 files changed

+361
-1
lines changed

34 files changed

+361
-1
lines changed

src/bin/cargo/commands/remove.rs

+53
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use cargo::core::dependency::DepKind;
2+
use cargo::core::Workspace;
23
use cargo::ops::cargo_remove::remove;
34
use cargo::ops::cargo_remove::RemoveOptions;
45
use cargo::ops::resolve_ws;
56
use cargo::util::command_prelude::*;
67
use cargo::util::toml_mut::manifest::DepTable;
8+
use cargo::util::toml_mut::manifest::LocalManifest;
9+
use cargo::CargoResult;
710

811
pub fn cli() -> clap::Command {
912
clap::Command::new("remove")
@@ -85,6 +88,9 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
8588
remove(&options)?;
8689

8790
if !dry_run {
91+
// Clean up workspace dependencies
92+
gc_workspace(&workspace, &options.dependencies)?;
93+
8894
// Reload the workspace since we've changed dependencies
8995
let ws = args.workspace(config)?;
9096
resolve_ws(&ws)?;
@@ -114,3 +120,50 @@ fn parse_section(args: &ArgMatches) -> DepTable {
114120

115121
table
116122
}
123+
124+
/// Clean up workspace dependencies which no longer have a reference to them.
125+
fn gc_workspace(workspace: &Workspace<'_>, dependencies: &[String]) -> CargoResult<()> {
126+
let mut manifest: toml_edit::Document =
127+
cargo_util::paths::read(workspace.root_manifest())?.parse()?;
128+
129+
let members = workspace
130+
.members()
131+
.map(|p| LocalManifest::try_new(p.manifest_path()))
132+
.collect::<CargoResult<Vec<_>>>()?;
133+
134+
for dep in dependencies {
135+
if !dep_in_workspace(dep, &members) {
136+
remove_workspace_dep(dep, &mut manifest);
137+
}
138+
}
139+
140+
cargo_util::paths::write(workspace.root_manifest(), manifest.to_string().as_bytes())?;
141+
142+
Ok(())
143+
}
144+
145+
/// Get whether or not a dependency is depended upon in a workspace.
146+
fn dep_in_workspace(dep: &str, members: &[LocalManifest]) -> bool {
147+
members.iter().any(|manifest| {
148+
manifest.get_sections().iter().any(|(_, table)| {
149+
table
150+
.as_table_like()
151+
.unwrap()
152+
.get(dep)
153+
.and_then(|t| t.get("workspace"))
154+
.and_then(|v| v.as_bool())
155+
.unwrap_or(false)
156+
})
157+
})
158+
}
159+
160+
/// Remove a dependency from a workspace manifest.
161+
fn remove_workspace_dep(dep: &str, ws_manifest: &mut toml_edit::Document) {
162+
if let Some(toml_edit::Item::Table(table)) = ws_manifest
163+
.get_mut("workspace")
164+
.and_then(|t| t.get_mut("dependencies"))
165+
{
166+
table.set_implicit(true);
167+
table.remove(dep);
168+
}
169+
}

src/cargo/util/toml_mut/manifest.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ impl std::fmt::Display for Manifest {
246246
}
247247

248248
/// An editable Cargo manifest that is available locally.
249-
#[derive(Debug)]
249+
#[derive(Debug, Clone)]
250250
pub struct LocalManifest {
251251
/// Path to the manifest.
252252
pub path: PathBuf,

tests/testsuite/cargo_remove/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ mod target;
2222
mod target_build;
2323
mod target_dev;
2424
mod update_lock_file;
25+
mod workspace;
26+
mod workspace_non_virtual;
27+
mod workspace_preserved;
2528

2629
fn init_registry() {
2730
cargo_test_support::registry::init();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[workspace]
2+
members = [ "my-package" ]
3+
4+
[workspace.dependencies]
5+
semver = "0.1.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "my-package"
3+
version = "0.1.0"
4+
5+
[[bin]]
6+
name = "main"
7+
path = "src/main.rs"
8+
9+
[build-dependencies]
10+
semver = { workspace = true }
11+
12+
[dependencies]
13+
docopt = "0.6"
14+
rustc-serialize = "0.4"
15+
semver = "0.1"
16+
toml = "0.1"
17+
clippy = "0.4"
18+
19+
[dev-dependencies]
20+
regex = "0.1.1"
21+
serde = "1.0.90"
22+
23+
[features]
24+
std = ["serde/std", "semver/std"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use cargo_test_support::compare::assert_ui;
2+
use cargo_test_support::curr_dir;
3+
use cargo_test_support::CargoCommand;
4+
use cargo_test_support::Project;
5+
6+
use crate::cargo_remove::init_registry;
7+
8+
#[cargo_test]
9+
fn case() {
10+
init_registry();
11+
let project = Project::from_template(curr_dir!().join("in"));
12+
let project_root = project.root();
13+
let cwd = &project_root;
14+
15+
snapbox::cmd::Command::cargo_ui()
16+
.arg("remove")
17+
.args(["--package", "my-package", "--build", "semver"])
18+
.current_dir(cwd)
19+
.assert()
20+
.success()
21+
.stdout_matches_path(curr_dir!().join("stdout.log"))
22+
.stderr_matches_path(curr_dir!().join("stderr.log"));
23+
24+
assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[workspace]
2+
members = [ "my-package" ]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "my-package"
3+
version = "0.1.0"
4+
5+
[[bin]]
6+
name = "main"
7+
path = "src/main.rs"
8+
9+
[dependencies]
10+
docopt = "0.6"
11+
rustc-serialize = "0.4"
12+
semver = "0.1"
13+
toml = "0.1"
14+
clippy = "0.4"
15+
16+
[dev-dependencies]
17+
regex = "0.1.1"
18+
serde = "1.0.90"
19+
20+
[features]
21+
std = ["serde/std", "semver/std"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Removing semver from build-dependencies
2+
Updating `dummy-registry` index

tests/testsuite/cargo_remove/workspace/stdout.log

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[workspace]
2+
members = [ "my-member" ]
3+
4+
[workspace.dependencies]
5+
semver = "0.1.0"
6+
7+
[package]
8+
name = "my-package"
9+
version = "0.1.0"
10+
11+
[[bin]]
12+
name = "main"
13+
path = "src/main.rs"
14+
15+
[build-dependencies]
16+
semver = { workspace = true }
17+
18+
[dependencies]
19+
docopt = "0.6"
20+
rustc-serialize = "0.4"
21+
semver = "0.1"
22+
toml = "0.1"
23+
clippy = "0.4"
24+
25+
[dev-dependencies]
26+
regex = "0.1.1"
27+
serde = "1.0.90"
28+
29+
[features]
30+
std = ["serde/std", "semver/std"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "my-member"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]

tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/src/main.rs

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use cargo_test_support::compare::assert_ui;
2+
use cargo_test_support::curr_dir;
3+
use cargo_test_support::CargoCommand;
4+
use cargo_test_support::Project;
5+
6+
use crate::cargo_remove::init_registry;
7+
8+
#[cargo_test]
9+
fn case() {
10+
init_registry();
11+
let project = Project::from_template(curr_dir!().join("in"));
12+
let project_root = project.root();
13+
let cwd = &project_root;
14+
15+
snapbox::cmd::Command::cargo_ui()
16+
.arg("remove")
17+
.args(["--package", "my-package", "--build", "semver"])
18+
.current_dir(cwd)
19+
.assert()
20+
.success()
21+
.stdout_matches_path(curr_dir!().join("stdout.log"))
22+
.stderr_matches_path(curr_dir!().join("stderr.log"));
23+
24+
assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[workspace]
2+
members = [ "my-member" ]
3+
4+
[package]
5+
name = "my-package"
6+
version = "0.1.0"
7+
8+
[[bin]]
9+
name = "main"
10+
path = "src/main.rs"
11+
12+
[dependencies]
13+
docopt = "0.6"
14+
rustc-serialize = "0.4"
15+
semver = "0.1"
16+
toml = "0.1"
17+
clippy = "0.4"
18+
19+
[dev-dependencies]
20+
regex = "0.1.1"
21+
serde = "1.0.90"
22+
23+
[features]
24+
std = ["serde/std", "semver/std"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "my-member"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]

tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/src/main.rs

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Removing semver from build-dependencies
2+
Updating `dummy-registry` index

tests/testsuite/cargo_remove/workspace_non_virtual/stdout.log

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[workspace]
2+
members = [ "my-package", "my-other-package" ]
3+
4+
[workspace.dependencies]
5+
semver = "0.1.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "my-other-package"
3+
version = "0.1.0"
4+
5+
[[bin]]
6+
name = "main"
7+
path = "src/main.rs"
8+
9+
[dependencies]
10+
docopt = "0.6"
11+
rustc-serialize = "0.4"
12+
semver = "0.1"
13+
toml = "0.1"
14+
clippy = "0.4"
15+
16+
[dev-dependencies]
17+
regex = "0.1.1"
18+
semver = { workspace = true }
19+
serde = "1.0.90"
20+
21+
[features]
22+
std = ["serde/std", "semver/std"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "my-package"
3+
version = "0.1.0"
4+
5+
[[bin]]
6+
name = "main"
7+
path = "src/main.rs"
8+
9+
[build-dependencies]
10+
semver = { workspace = true }
11+
12+
[dependencies]
13+
docopt = "0.6"
14+
rustc-serialize = "0.4"
15+
semver = "0.1"
16+
toml = "0.1"
17+
clippy = "0.4"
18+
19+
[dev-dependencies]
20+
regex = "0.1.1"
21+
serde = "1.0.90"
22+
23+
[features]
24+
std = ["serde/std", "semver/std"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use cargo_test_support::compare::assert_ui;
2+
use cargo_test_support::curr_dir;
3+
use cargo_test_support::CargoCommand;
4+
use cargo_test_support::Project;
5+
6+
use crate::cargo_remove::init_registry;
7+
8+
#[cargo_test]
9+
fn case() {
10+
init_registry();
11+
let project = Project::from_template(curr_dir!().join("in"));
12+
let project_root = project.root();
13+
let cwd = &project_root;
14+
15+
snapbox::cmd::Command::cargo_ui()
16+
.arg("remove")
17+
.args(["--package", "my-package", "--build", "semver"])
18+
.current_dir(cwd)
19+
.assert()
20+
.success()
21+
.stdout_matches_path(curr_dir!().join("stdout.log"))
22+
.stderr_matches_path(curr_dir!().join("stderr.log"));
23+
24+
assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[workspace]
2+
members = [ "my-package", "my-other-package" ]
3+
4+
[workspace.dependencies]
5+
semver = "0.1.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "my-other-package"
3+
version = "0.1.0"
4+
5+
[[bin]]
6+
name = "main"
7+
path = "src/main.rs"
8+
9+
[dependencies]
10+
docopt = "0.6"
11+
rustc-serialize = "0.4"
12+
semver = "0.1"
13+
toml = "0.1"
14+
clippy = "0.4"
15+
16+
[dev-dependencies]
17+
regex = "0.1.1"
18+
semver = { workspace = true }
19+
serde = "1.0.90"
20+
21+
[features]
22+
std = ["serde/std", "semver/std"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)