Skip to content

Commit b292a75

Browse files
committed
Clean profile, patch, and replace in cargo remove
After a successful removal of a dependency, clean up the profile, patch, and replace sections to remove all references to the dependency.
1 parent 65b2149 commit b292a75

File tree

24 files changed

+331
-37
lines changed

24 files changed

+331
-37
lines changed

src/bin/cargo/commands/remove.rs

+90-34
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use cargo::core::dependency::DepKind;
2+
use cargo::core::PackageIdSpec;
23
use cargo::core::Workspace;
34
use cargo::ops::cargo_remove::remove;
45
use cargo::ops::cargo_remove::RemoveOptions;
56
use cargo::ops::resolve_ws;
67
use cargo::util::command_prelude::*;
8+
use cargo::util::toml_mut::dependency::Dependency;
9+
use cargo::util::toml_mut::dependency::Source;
710
use cargo::util::toml_mut::manifest::DepTable;
811
use cargo::util::toml_mut::manifest::LocalManifest;
912
use cargo::CargoResult;
@@ -74,22 +77,22 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
7477
.get_many::<String>("dependencies")
7578
.expect("required(true)")
7679
.cloned()
77-
.collect();
80+
.collect::<Vec<_>>();
7881

7982
let section = parse_section(args);
8083

8184
let options = RemoveOptions {
8285
config,
8386
spec,
84-
dependencies,
87+
dependencies: &dependencies,
8588
section,
8689
dry_run,
8790
};
8891
remove(&options)?;
8992

9093
if !dry_run {
91-
// Clean up workspace dependencies
92-
gc_workspace(&workspace, &options.dependencies)?;
94+
// Clean up the workspace
95+
gc_workspace(&workspace)?;
9396

9497
// Reload the workspace since we've changed dependencies
9598
let ws = args.workspace(config)?;
@@ -121,8 +124,9 @@ fn parse_section(args: &ArgMatches) -> DepTable {
121124
table
122125
}
123126

124-
/// Clean up workspace dependencies which no longer have a reference to them.
125-
fn gc_workspace(workspace: &Workspace<'_>, dependencies: &[String]) -> CargoResult<()> {
127+
/// Clean up the workspace.dependencies, profile, patch, and replace sections of the root manifest
128+
/// by removing dependencies which no longer have a reference to them.
129+
fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> {
126130
let mut manifest: toml_edit::Document =
127131
cargo_util::paths::read(workspace.root_manifest())?.parse()?;
128132

@@ -131,39 +135,91 @@ fn gc_workspace(workspace: &Workspace<'_>, dependencies: &[String]) -> CargoResu
131135
.map(|p| LocalManifest::try_new(p.manifest_path()))
132136
.collect::<CargoResult<Vec<_>>>()?;
133137

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)
138+
let dependencies = members
139+
.iter()
140+
.flat_map(|manifest| {
141+
manifest.get_sections().into_iter().flat_map(|(_, table)| {
142+
table
143+
.as_table_like()
144+
.unwrap()
145+
.iter()
146+
.map(|(key, item)| Dependency::from_toml(&manifest.path, key, item))
147+
.collect::<Vec<_>>()
148+
})
156149
})
157-
})
158-
}
150+
.collect::<CargoResult<Vec<_>>>()?;
159151

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
152+
// clean up workspace.dependencies
153+
if let Some(toml_edit::Item::Table(deps_table)) = manifest
163154
.get_mut("workspace")
164155
.and_then(|t| t.get_mut("dependencies"))
165156
{
157+
deps_table.set_implicit(true);
158+
for (key, item) in deps_table.iter_mut() {
159+
if !dependencies.iter().any(|d| {
160+
d.toml_key() == key.get() && matches!(d.source(), Some(Source::Workspace(_)))
161+
}) {
162+
*item = toml_edit::Item::None;
163+
}
164+
}
165+
}
166+
167+
// clean up the profile section
168+
if let Some(toml_edit::Item::Table(profile_section_table)) = manifest.get_mut("profile") {
169+
profile_section_table.set_implicit(true);
170+
for (_, item) in profile_section_table.iter_mut() {
171+
if let toml_edit::Item::Table(profile_table) = item {
172+
profile_table.set_implicit(true);
173+
if let Some(toml_edit::Item::Table(package_table)) =
174+
profile_table.get_mut("package")
175+
{
176+
package_table.set_implicit(true);
177+
for (key, item) in package_table.iter_mut() {
178+
if let Ok(spec) = PackageIdSpec::parse(key.get()) {
179+
if !dependencies.iter().any(|d| d.name == spec.name().as_str()) {
180+
*item = toml_edit::Item::None;
181+
}
182+
}
183+
}
184+
}
185+
}
186+
}
187+
}
188+
189+
// clean up patch section
190+
if let Some(toml_edit::Item::Table(patch_section_table)) = manifest.get_mut("patch") {
191+
patch_section_table.set_implicit(true);
192+
for (_, item) in patch_section_table.iter_mut() {
193+
if let toml_edit::Item::Table(patch_table) = item {
194+
patch_table.set_implicit(true);
195+
for (key, item) in patch_table.iter_mut() {
196+
if !dependencies.iter().any(|d| d.name == key.get()) {
197+
*item = toml_edit::Item::None;
198+
}
199+
}
200+
}
201+
}
202+
}
203+
204+
// clean up replace section
205+
if let Some(toml_edit::Item::Table(table)) = manifest.get_mut("replace") {
166206
table.set_implicit(true);
167-
table.remove(dep);
207+
for (key, item) in table.iter_mut() {
208+
if let Ok(spec) = PackageIdSpec::parse(key.get()) {
209+
if !dependencies.iter().any(|d| {
210+
d.name == spec.name().as_str()
211+
&& d.version()
212+
.and_then(|v| semver::VersionReq::parse(v).ok())
213+
.and_then(|vq| spec.version().map(|v| vq.matches(v)))
214+
.unwrap_or(true)
215+
}) {
216+
*item = toml_edit::Item::None;
217+
}
218+
}
219+
}
168220
}
221+
222+
cargo_util::paths::write(workspace.root_manifest(), manifest.to_string().as_bytes())?;
223+
224+
Ok(())
169225
}

src/cargo/ops/cargo_remove.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct RemoveOptions<'a> {
1414
/// Package to remove dependencies from
1515
pub spec: &'a Package,
1616
/// Dependencies to remove
17-
pub dependencies: Vec<String>,
17+
pub dependencies: &'a [String],
1818
/// Which dependency section to remove these from
1919
pub section: DepTable,
2020
/// Whether or not to actually write the manifest
@@ -33,7 +33,7 @@ pub fn remove(options: &RemoveOptions<'_>) -> CargoResult<()> {
3333
let manifest_path = options.spec.manifest_path().to_path_buf();
3434
let mut manifest = LocalManifest::try_new(&manifest_path)?;
3535

36-
for dep in &options.dependencies {
36+
for dep in options.dependencies {
3737
let section = if dep_table.len() >= 3 {
3838
format!("{} for target `{}`", &dep_table[2], &dep_table[1])
3939
} else {

src/cargo/util/toml_mut/manifest.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ fn fix_feature_activations(
500500
})
501501
.collect();
502502

503-
// Remove found idx in revers order so we don't invalidate the idx.
503+
// Remove found idx in reverse order so we don't invalidate the idx.
504504
for idx in remove_list.iter().rev() {
505505
feature_values.remove(*idx);
506506
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use cargo_test_support::basic_manifest;
2+
use cargo_test_support::compare::assert_ui;
3+
use cargo_test_support::curr_dir;
4+
use cargo_test_support::git;
5+
use cargo_test_support::project;
6+
use cargo_test_support::CargoCommand;
7+
8+
use crate::cargo_remove::init_registry;
9+
10+
#[cargo_test]
11+
fn case() {
12+
init_registry();
13+
14+
let git_project1 = git::new("bar1", |project| {
15+
project
16+
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
17+
.file("src/lib.rs", "")
18+
})
19+
.url();
20+
21+
let git_project2 = git::new("bar2", |project| {
22+
project
23+
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
24+
.file("src/lib.rs", "")
25+
})
26+
.url();
27+
28+
let in_project = project()
29+
.file(
30+
"Cargo.toml",
31+
&format!(
32+
"[package]\n\
33+
name = \"my-project\"\n\
34+
version = \"0.1.0\"\n\
35+
\n\
36+
[dependencies]\n\
37+
bar = {{ git = \"{git_project1}\" }}\n\
38+
\n\
39+
[patch.\"{git_project1}\"]\n\
40+
bar = {{ git = \"{git_project2}\" }}\n",
41+
),
42+
)
43+
.file("src/lib.rs", "")
44+
.build();
45+
46+
snapbox::cmd::Command::cargo_ui()
47+
.arg("remove")
48+
.args(["bar"])
49+
.current_dir(&in_project.root())
50+
.assert()
51+
.success()
52+
.stdout_matches_path(curr_dir!().join("stdout.log"))
53+
.stderr_matches_path(curr_dir!().join("stderr.log"));
54+
55+
assert_ui().subset_matches(curr_dir!().join("out"), &in_project.root());
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[package]
2+
name = "my-project"
3+
version = "0.1.0"

tests/testsuite/cargo_remove/gc_patch/out/src/lib.rs

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Removing bar from dependencies

tests/testsuite/cargo_remove/gc_patch/stdout.log

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[package]
2+
name = "cargo-remove-test-fixture"
3+
version = "0.1.0"
4+
5+
[[bin]]
6+
name = "main"
7+
path = "src/main.rs"
8+
9+
[build-dependencies]
10+
semver = "0.1.0"
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+
docopt = "0.6"
23+
24+
[features]
25+
std = ["serde/std", "semver/std"]
26+
27+
[profile.dev.package.docopt]
28+
opt-level = 3
29+
30+
[profile.dev.package.toml]
31+
opt-level = 3
32+
33+
[profile.release.package.toml]
34+
opt-level = 1
35+
overflow-checks = false
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(["toml"])
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,27 @@
1+
[package]
2+
name = "cargo-remove-test-fixture"
3+
version = "0.1.0"
4+
5+
[[bin]]
6+
name = "main"
7+
path = "src/main.rs"
8+
9+
[build-dependencies]
10+
semver = "0.1.0"
11+
12+
[dependencies]
13+
docopt = "0.6"
14+
rustc-serialize = "0.4"
15+
semver = "0.1"
16+
clippy = "0.4"
17+
18+
[dev-dependencies]
19+
regex = "0.1.1"
20+
serde = "1.0.90"
21+
docopt = "0.6"
22+
23+
[features]
24+
std = ["serde/std", "semver/std"]
25+
26+
[profile.dev.package.docopt]
27+
opt-level = 3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Removing toml from dependencies
2+
Updating `dummy-registry` index

tests/testsuite/cargo_remove/gc_profile/stdout.log

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[workspace]
2+
members = [ "my-package" ]
3+
4+
[replace]
5+
"toml:0.1.0" = { path = "../toml" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 = "0.1.0"
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+
docopt = "0.6"
23+
24+
[features]
25+
std = ["serde/std", "semver/std"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)