Skip to content

Commit 8022dda

Browse files
committed
Clean up workspace dependencies after cargo remove
1 parent b592ba4 commit 8022dda

File tree

22 files changed

+272
-6
lines changed

22 files changed

+272
-6
lines changed

src/bin/cargo/commands/remove.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use cargo::core::dependency::DepKind;
2+
use cargo::core::Dependency;
3+
use cargo::core::Package;
24
use cargo::ops::cargo_remove::remove;
35
use cargo::ops::cargo_remove::RemoveOptions;
46
use cargo::ops::resolve_ws;
57
use cargo::util::command_prelude::*;
68
use cargo::util::toml_mut::manifest::DepTable;
9+
use cargo::util::toml_mut::manifest::LocalManifest;
10+
use cargo::CargoResult;
711

812
pub fn cli() -> clap::Command {
913
clap::Command::new("remove")
@@ -77,6 +81,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
7781

7882
let options = RemoveOptions {
7983
config,
84+
workspace: &workspace,
8085
spec,
8186
dependencies,
8287
section,
@@ -85,6 +90,9 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
8590
remove(&options)?;
8691

8792
if !dry_run {
93+
// Clean up workspace dependencies
94+
gc_workspace(&options)?;
95+
8896
// Reload the workspace since we've changed dependencies
8997
let ws = args.workspace(config)?;
9098
resolve_ws(&ws)?;
@@ -114,3 +122,81 @@ fn parse_section(args: &ArgMatches) -> DepTable {
114122

115123
table
116124
}
125+
126+
/// Clean up workspace dependencies which no longer have a reference to them.
127+
fn gc_workspace(options: &RemoveOptions<'_>) -> CargoResult<()> {
128+
let mut manifest: toml_edit::Document =
129+
cargo_util::paths::read(options.workspace.root_manifest())?.parse()?;
130+
131+
let members = options
132+
.workspace
133+
.members()
134+
.map(|p| LocalManifest::try_new(p.manifest_path()).map(|m| (p, m)))
135+
.collect::<CargoResult<Vec<_>>>()?;
136+
137+
for dep in &options.dependencies {
138+
if !dep_in_workspace(dep, &members) {
139+
remove_workspace_dep(dep, &mut manifest);
140+
}
141+
}
142+
143+
if !options.dry_run {
144+
cargo_util::paths::write(
145+
options.workspace.root_manifest(),
146+
manifest.to_string().as_bytes(),
147+
)?;
148+
}
149+
Ok(())
150+
}
151+
152+
/// Get whether or not a dependency is marked as `workspace`.
153+
fn dep_is_workspace(dep: &str, dep_table: &[String], manifest: &LocalManifest) -> bool {
154+
if let Ok(toml_edit::Item::Table(table)) = manifest.get_table(dep_table) {
155+
let value = table.get(dep).and_then(|i| i.get("workspace"));
156+
if let Some(toml_edit::Item::Value(value)) = value {
157+
return value.as_bool() == Some(true);
158+
}
159+
}
160+
161+
false
162+
}
163+
164+
/// Get whether or not a dependency is depended upon in a workspace.
165+
fn dep_in_workspace(dep: &str, members: &[(&Package, LocalManifest)]) -> bool {
166+
members.iter().any(|(pkg, manifest)| {
167+
pkg.dependencies()
168+
.iter()
169+
.filter(|d| d.name_in_toml() == dep)
170+
.map(|d| (d, dep_to_table(d)))
171+
.any(|(d, t)| {
172+
// Information about workspace dependencies is not preserved at this stage, so we
173+
// have to manually read from the TOML manifest
174+
let dep_table = t
175+
.to_table()
176+
.into_iter()
177+
.map(String::from)
178+
.collect::<Vec<_>>();
179+
dep_is_workspace(&d.package_name(), &dep_table, manifest)
180+
})
181+
})
182+
}
183+
184+
/// Find the corresponding [`DepTable`] for a [`Dependency`].
185+
fn dep_to_table(dep: &Dependency) -> DepTable {
186+
let mut dep_table = DepTable::new().set_kind(dep.kind());
187+
if let Some(target) = dep.platform().map(|p| p.to_string()) {
188+
dep_table = dep_table.set_target(target);
189+
}
190+
dep_table
191+
}
192+
193+
/// Remove a dependency from a workspace manifest.
194+
fn remove_workspace_dep(dep: &str, ws_manifest: &mut toml_edit::Document) {
195+
if let Some(toml_edit::Item::Table(table)) = ws_manifest
196+
.get_mut("workspace")
197+
.and_then(|t| t.get_mut("dependencies"))
198+
{
199+
table.set_implicit(true);
200+
table.remove(dep);
201+
}
202+
}

src/cargo/ops/cargo_remove.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Core of cargo-remove command
22
33
use crate::core::Package;
4+
use crate::core::Workspace;
45
use crate::util::toml_mut::manifest::DepTable;
56
use crate::util::toml_mut::manifest::LocalManifest;
67
use crate::CargoResult;
@@ -11,6 +12,8 @@ use crate::Config;
1112
pub struct RemoveOptions<'a> {
1213
/// Configuration information for Cargo operations
1314
pub config: &'a Config,
15+
/// Workspace in which the operation is occurring.
16+
pub workspace: &'a Workspace<'a>,
1417
/// Package to remove dependencies from
1518
pub spec: &'a Package,
1619
/// Dependencies to remove
@@ -46,19 +49,20 @@ pub fn remove(options: &RemoveOptions<'_>) -> CargoResult<()> {
4649

4750
manifest.remove_from_table(&dep_table, dep)?;
4851

49-
// Now that we have removed the crate, if that was the last reference to that
50-
// crate, then we need to drop any explicitly activated features on
51-
// that crate.
52+
// Now that we have removed the crate, if that was the last reference to that crate, then we
53+
// need to drop any explicitly activated features on that crate.
5254
manifest.gc_dep(dep);
5355
}
5456

57+
if !options.dry_run {
58+
manifest.write()?;
59+
}
60+
5561
if options.dry_run {
5662
options
5763
.config
5864
.shell()
5965
.warn("aborting remove due to dry run")?;
60-
} else {
61-
manifest.write()?;
6266
}
6367

6468
Ok(())

src/cargo/util/toml_mut/manifest.rs

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ mod target;
2222
mod target_build;
2323
mod target_dev;
2424
mod update_lock_file;
25+
mod virtual_workspace;
26+
mod workspace;
2527

2628
fn init_registry() {
2729
cargo_test_support::registry::init();
Lines changed: 5 additions & 0 deletions
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"
Lines changed: 24 additions & 0 deletions
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"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 25 additions & 0 deletions
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+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[workspace]
2+
members = [ "my-package" ]
Lines changed: 21 additions & 0 deletions
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"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 2 additions & 0 deletions
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/virtual_workspace/stdout.log

Whitespace-only changes.
Lines changed: 30 additions & 0 deletions
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"]
Lines changed: 6 additions & 0 deletions
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/in/my-member/src/main.rs

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
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+
}
Lines changed: 24 additions & 0 deletions
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"]
Lines changed: 6 additions & 0 deletions
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/out/my-member/src/main.rs

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
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.

0 commit comments

Comments
 (0)