diff --git a/src/bin/cargo/commands/remove.rs b/src/bin/cargo/commands/remove.rs index 56d60c8b161e..26112b90edd7 100644 --- a/src/bin/cargo/commands/remove.rs +++ b/src/bin/cargo/commands/remove.rs @@ -1,9 +1,12 @@ use cargo::core::dependency::DepKind; +use cargo::core::Workspace; use cargo::ops::cargo_remove::remove; use cargo::ops::cargo_remove::RemoveOptions; use cargo::ops::resolve_ws; use cargo::util::command_prelude::*; use cargo::util::toml_mut::manifest::DepTable; +use cargo::util::toml_mut::manifest::LocalManifest; +use cargo::CargoResult; pub fn cli() -> clap::Command { clap::Command::new("remove") @@ -85,6 +88,9 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { remove(&options)?; if !dry_run { + // Clean up workspace dependencies + gc_workspace(&workspace, &options.dependencies)?; + // Reload the workspace since we've changed dependencies let ws = args.workspace(config)?; resolve_ws(&ws)?; @@ -114,3 +120,50 @@ fn parse_section(args: &ArgMatches) -> DepTable { table } + +/// Clean up workspace dependencies which no longer have a reference to them. +fn gc_workspace(workspace: &Workspace<'_>, dependencies: &[String]) -> CargoResult<()> { + let mut manifest: toml_edit::Document = + cargo_util::paths::read(workspace.root_manifest())?.parse()?; + + let members = workspace + .members() + .map(|p| LocalManifest::try_new(p.manifest_path())) + .collect::>>()?; + + for dep in dependencies { + if !dep_in_workspace(dep, &members) { + remove_workspace_dep(dep, &mut manifest); + } + } + + cargo_util::paths::write(workspace.root_manifest(), manifest.to_string().as_bytes())?; + + Ok(()) +} + +/// Get whether or not a dependency is depended upon in a workspace. +fn dep_in_workspace(dep: &str, members: &[LocalManifest]) -> bool { + members.iter().any(|manifest| { + manifest.get_sections().iter().any(|(_, table)| { + table + .as_table_like() + .unwrap() + .get(dep) + .and_then(|t| t.get("workspace")) + .and_then(|v| v.as_bool()) + .unwrap_or(false) + }) + }) +} + +/// Remove a dependency from a workspace manifest. +fn remove_workspace_dep(dep: &str, ws_manifest: &mut toml_edit::Document) { + if let Some(toml_edit::Item::Table(table)) = ws_manifest + .get_mut("workspace") + .and_then(|t| t.get_mut("dependencies")) + { + table.set_implicit(true); + table.remove(dep); + } +} diff --git a/src/cargo/util/toml_mut/manifest.rs b/src/cargo/util/toml_mut/manifest.rs index 9d98d581b636..8c88333608ad 100644 --- a/src/cargo/util/toml_mut/manifest.rs +++ b/src/cargo/util/toml_mut/manifest.rs @@ -246,7 +246,7 @@ impl std::fmt::Display for Manifest { } /// An editable Cargo manifest that is available locally. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LocalManifest { /// Path to the manifest. pub path: PathBuf, diff --git a/tests/testsuite/cargo_remove/mod.rs b/tests/testsuite/cargo_remove/mod.rs index 75178531ee33..4aa2dbe585b6 100644 --- a/tests/testsuite/cargo_remove/mod.rs +++ b/tests/testsuite/cargo_remove/mod.rs @@ -22,6 +22,9 @@ mod target; mod target_build; mod target_dev; mod update_lock_file; +mod workspace; +mod workspace_non_virtual; +mod workspace_preserved; fn init_registry() { cargo_test_support::registry::init(); diff --git a/tests/testsuite/cargo_remove/workspace/in/Cargo.toml b/tests/testsuite/cargo_remove/workspace/in/Cargo.toml new file mode 100644 index 000000000000..fd5e80a8ba1f --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace/in/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ "my-package" ] + +[workspace.dependencies] +semver = "0.1.0" diff --git a/tests/testsuite/cargo_remove/workspace/in/my-package/Cargo.toml b/tests/testsuite/cargo_remove/workspace/in/my-package/Cargo.toml new file mode 100644 index 000000000000..6690d593ba19 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace/in/my-package/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "my-package" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[build-dependencies] +semver = { workspace = true } + +[dependencies] +docopt = "0.6" +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] diff --git a/tests/testsuite/cargo_remove/workspace/in/my-package/src/main.rs b/tests/testsuite/cargo_remove/workspace/in/my-package/src/main.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace/in/my-package/src/main.rs @@ -0,0 +1 @@ + diff --git a/tests/testsuite/cargo_remove/workspace/mod.rs b/tests/testsuite/cargo_remove/workspace/mod.rs new file mode 100644 index 000000000000..225fbec00e88 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace/mod.rs @@ -0,0 +1,25 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +use crate::cargo_remove::init_registry; + +#[cargo_test] +fn case() { + init_registry(); + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("remove") + .args(["--package", "my-package", "--build", "semver"]) + .current_dir(cwd) + .assert() + .success() + .stdout_matches_path(curr_dir!().join("stdout.log")) + .stderr_matches_path(curr_dir!().join("stderr.log")); + + assert_ui().subset_matches(curr_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_remove/workspace/out/Cargo.toml b/tests/testsuite/cargo_remove/workspace/out/Cargo.toml new file mode 100644 index 000000000000..83a6a04d0716 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace/out/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = [ "my-package" ] diff --git a/tests/testsuite/cargo_remove/workspace/out/my-package/Cargo.toml b/tests/testsuite/cargo_remove/workspace/out/my-package/Cargo.toml new file mode 100644 index 000000000000..402780535e2a --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace/out/my-package/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "my-package" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[dependencies] +docopt = "0.6" +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] diff --git a/tests/testsuite/cargo_remove/workspace/out/my-package/src/main.rs b/tests/testsuite/cargo_remove/workspace/out/my-package/src/main.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace/out/my-package/src/main.rs @@ -0,0 +1 @@ + diff --git a/tests/testsuite/cargo_remove/workspace/stderr.log b/tests/testsuite/cargo_remove/workspace/stderr.log new file mode 100644 index 000000000000..f037ebe28cec --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace/stderr.log @@ -0,0 +1,2 @@ + Removing semver from build-dependencies + Updating `dummy-registry` index diff --git a/tests/testsuite/cargo_remove/workspace/stdout.log b/tests/testsuite/cargo_remove/workspace/stdout.log new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/testsuite/cargo_remove/workspace_non_virtual/in/Cargo.toml b/tests/testsuite/cargo_remove/workspace_non_virtual/in/Cargo.toml new file mode 100644 index 000000000000..dbac8ab442ea --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_non_virtual/in/Cargo.toml @@ -0,0 +1,30 @@ +[workspace] +members = [ "my-member" ] + +[workspace.dependencies] +semver = "0.1.0" + +[package] +name = "my-package" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[build-dependencies] +semver = { workspace = true } + +[dependencies] +docopt = "0.6" +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] diff --git a/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/Cargo.toml b/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/Cargo.toml new file mode 100644 index 000000000000..bb78904ffe6a --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "my-member" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/src/main.rs b/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/src/main.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/testsuite/cargo_remove/workspace_non_virtual/mod.rs b/tests/testsuite/cargo_remove/workspace_non_virtual/mod.rs new file mode 100644 index 000000000000..225fbec00e88 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_non_virtual/mod.rs @@ -0,0 +1,25 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +use crate::cargo_remove::init_registry; + +#[cargo_test] +fn case() { + init_registry(); + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("remove") + .args(["--package", "my-package", "--build", "semver"]) + .current_dir(cwd) + .assert() + .success() + .stdout_matches_path(curr_dir!().join("stdout.log")) + .stderr_matches_path(curr_dir!().join("stderr.log")); + + assert_ui().subset_matches(curr_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_remove/workspace_non_virtual/out/Cargo.toml b/tests/testsuite/cargo_remove/workspace_non_virtual/out/Cargo.toml new file mode 100644 index 000000000000..9a3261484d12 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_non_virtual/out/Cargo.toml @@ -0,0 +1,24 @@ +[workspace] +members = [ "my-member" ] + +[package] +name = "my-package" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[dependencies] +docopt = "0.6" +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] diff --git a/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/Cargo.toml b/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/Cargo.toml new file mode 100644 index 000000000000..bb78904ffe6a --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "my-member" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/src/main.rs b/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/src/main.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/testsuite/cargo_remove/workspace_non_virtual/stderr.log b/tests/testsuite/cargo_remove/workspace_non_virtual/stderr.log new file mode 100644 index 000000000000..f037ebe28cec --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_non_virtual/stderr.log @@ -0,0 +1,2 @@ + Removing semver from build-dependencies + Updating `dummy-registry` index diff --git a/tests/testsuite/cargo_remove/workspace_non_virtual/stdout.log b/tests/testsuite/cargo_remove/workspace_non_virtual/stdout.log new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/testsuite/cargo_remove/workspace_preserved/in/Cargo.toml b/tests/testsuite/cargo_remove/workspace_preserved/in/Cargo.toml new file mode 100644 index 000000000000..f1992ac886c9 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/in/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ "my-package", "my-other-package" ] + +[workspace.dependencies] +semver = "0.1.0" diff --git a/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/Cargo.toml b/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/Cargo.toml new file mode 100644 index 000000000000..d65972868cc6 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "my-other-package" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[dependencies] +docopt = "0.6" +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +semver = { workspace = true } +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] diff --git a/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/src/main.rs b/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/src/main.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/src/main.rs @@ -0,0 +1 @@ + diff --git a/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/Cargo.toml b/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/Cargo.toml new file mode 100644 index 000000000000..6690d593ba19 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "my-package" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[build-dependencies] +semver = { workspace = true } + +[dependencies] +docopt = "0.6" +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] diff --git a/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/src/main.rs b/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/src/main.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/src/main.rs @@ -0,0 +1 @@ + diff --git a/tests/testsuite/cargo_remove/workspace_preserved/mod.rs b/tests/testsuite/cargo_remove/workspace_preserved/mod.rs new file mode 100644 index 000000000000..225fbec00e88 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/mod.rs @@ -0,0 +1,25 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +use crate::cargo_remove::init_registry; + +#[cargo_test] +fn case() { + init_registry(); + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("remove") + .args(["--package", "my-package", "--build", "semver"]) + .current_dir(cwd) + .assert() + .success() + .stdout_matches_path(curr_dir!().join("stdout.log")) + .stderr_matches_path(curr_dir!().join("stderr.log")); + + assert_ui().subset_matches(curr_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_remove/workspace_preserved/out/Cargo.toml b/tests/testsuite/cargo_remove/workspace_preserved/out/Cargo.toml new file mode 100644 index 000000000000..f1992ac886c9 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/out/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ "my-package", "my-other-package" ] + +[workspace.dependencies] +semver = "0.1.0" diff --git a/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/Cargo.toml b/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/Cargo.toml new file mode 100644 index 000000000000..d65972868cc6 --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "my-other-package" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[dependencies] +docopt = "0.6" +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +semver = { workspace = true } +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] diff --git a/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/src/main.rs b/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/src/main.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/src/main.rs @@ -0,0 +1 @@ + diff --git a/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/Cargo.toml b/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/Cargo.toml new file mode 100644 index 000000000000..402780535e2a --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "my-package" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[dependencies] +docopt = "0.6" +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] diff --git a/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/src/main.rs b/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/src/main.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/src/main.rs @@ -0,0 +1 @@ + diff --git a/tests/testsuite/cargo_remove/workspace_preserved/stderr.log b/tests/testsuite/cargo_remove/workspace_preserved/stderr.log new file mode 100644 index 000000000000..f037ebe28cec --- /dev/null +++ b/tests/testsuite/cargo_remove/workspace_preserved/stderr.log @@ -0,0 +1,2 @@ + Removing semver from build-dependencies + Updating `dummy-registry` index diff --git a/tests/testsuite/cargo_remove/workspace_preserved/stdout.log b/tests/testsuite/cargo_remove/workspace_preserved/stdout.log new file mode 100644 index 000000000000..e69de29bb2d1