Skip to content

Commit 8ae2170

Browse files
committed
Add cargo remove subcommand
1 parent 56918df commit 8ae2170

File tree

6 files changed

+189
-0
lines changed

6 files changed

+189
-0
lines changed

src/bin/cargo/commands/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub fn builtin() -> Vec<App> {
2626
pkgid::cli(),
2727
publish::cli(),
2828
read_manifest::cli(),
29+
remove::cli(),
2930
report::cli(),
3031
run::cli(),
3132
rustc::cli(),
@@ -68,6 +69,7 @@ pub fn builtin_exec(cmd: &str) -> Option<fn(&mut Config, &ArgMatches) -> CliResu
6869
"pkgid" => pkgid::exec,
6970
"publish" => publish::exec,
7071
"read-manifest" => read_manifest::exec,
72+
"remove" => remove::exec,
7173
"report" => report::exec,
7274
"run" => run::exec,
7375
"rustc" => rustc::exec,
@@ -110,6 +112,7 @@ pub mod package;
110112
pub mod pkgid;
111113
pub mod publish;
112114
pub mod read_manifest;
115+
pub mod remove;
113116
pub mod report;
114117
pub mod run;
115118
pub mod rustc;

src/bin/cargo/commands/remove.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use cargo::core::dependency::DepKind;
2+
use cargo::ops::cargo_remove::remove;
3+
use cargo::ops::cargo_remove::RemoveOptions;
4+
use cargo::ops::resolve_ws;
5+
use cargo::util::command_prelude::*;
6+
use cargo::util::toml_mut::manifest::DepTable;
7+
8+
pub fn cli() -> clap::Command<'static> {
9+
clap::Command::new("remove")
10+
// Subcommand aliases are handled in `aliased_command()`.
11+
// .alias("rm")
12+
.setting(clap::AppSettings::DeriveDisplayOrder)
13+
.about("Remove dependencies from a Cargo.toml manifest file")
14+
.args([clap::Arg::new("dependencies")
15+
.action(clap::ArgAction::Append)
16+
.required(true)
17+
.multiple_values(true)
18+
.takes_value(true)
19+
.value_name("DEP_ID")
20+
.help("Dependencies to be removed")])
21+
.arg_package("Package to remove from")
22+
.arg_manifest_path()
23+
.arg_quiet()
24+
.arg_dry_run("Don't actually write the manifest")
25+
.next_help_heading("SECTION")
26+
.args([
27+
clap::Arg::new("dev")
28+
.long("dev")
29+
.conflicts_with("build")
30+
.action(clap::ArgAction::SetTrue)
31+
.group("section")
32+
.help("Remove as development dependency"),
33+
clap::Arg::new("build")
34+
.long("build")
35+
.conflicts_with("dev")
36+
.action(clap::ArgAction::SetTrue)
37+
.group("section")
38+
.help("Remove as build dependency"),
39+
clap::Arg::new("target")
40+
.long("target")
41+
.takes_value(true)
42+
.value_name("TARGET")
43+
.value_parser(clap::builder::NonEmptyStringValueParser::new())
44+
.help("Remove as dependency from the given target platform"),
45+
])
46+
}
47+
48+
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
49+
let dry_run = args.dry_run();
50+
51+
let workspace = args.workspace(config)?;
52+
let packages = args.packages_from_flags()?;
53+
let packages = packages.get_packages(&workspace)?;
54+
let spec = match packages.len() {
55+
0 => {
56+
return Err(CliError::new(
57+
anyhow::format_err!("no packages selected. Please specify one with `-p <PKG_ID>`"),
58+
101,
59+
));
60+
}
61+
1 => packages[0],
62+
len => {
63+
return Err(CliError::new(
64+
anyhow::format_err!(
65+
"{len} packages selected. Please specify one with `-p <PKG_ID>`",
66+
),
67+
101,
68+
));
69+
}
70+
};
71+
72+
let dependencies = args
73+
.get_many::<String>("dependencies")
74+
.expect("required(true)")
75+
.cloned()
76+
.collect();
77+
78+
let section = parse_section(args);
79+
80+
let options = RemoveOptions {
81+
config,
82+
spec,
83+
dependencies,
84+
section,
85+
dry_run,
86+
};
87+
remove(&options)?;
88+
89+
if !dry_run {
90+
// Reload the workspace since we've changed dependencies
91+
let ws = args.workspace(config)?;
92+
resolve_ws(&ws)?;
93+
}
94+
95+
Ok(())
96+
}
97+
98+
fn parse_section(args: &ArgMatches) -> DepTable {
99+
let dev = args.flag("dev");
100+
let build = args.flag("build");
101+
102+
let kind = if dev {
103+
DepKind::Development
104+
} else if build {
105+
DepKind::Build
106+
} else {
107+
DepKind::Normal
108+
};
109+
110+
let mut table = DepTable::new().set_kind(kind);
111+
112+
if let Some(target) = args.get_one::<String>("target") {
113+
assert!(!target.is_empty(), "Target specification may not be empty");
114+
table = table.set_target(target);
115+
}
116+
117+
table
118+
}

src/bin/cargo/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const BUILTIN_ALIASES: [(&str, &str, &str); 5] = [
4444
("d", "doc", "alias: doc"),
4545
("r", "run", "alias: run"),
4646
("t", "test", "alias: test"),
47+
("rm", "remove", "alias: remove"),
4748
];
4849

4950
/// Function which contains the list of all of the builtin aliases and it's

src/cargo/ops/cargo_remove.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//! Core of cargo-remove command
2+
3+
use crate::core::Package;
4+
use crate::util::toml_mut::manifest::DepTable;
5+
use crate::util::toml_mut::manifest::LocalManifest;
6+
use crate::CargoResult;
7+
use crate::Config;
8+
9+
/// Remove a dependency from a Cargo.toml manifest file.
10+
#[derive(Debug)]
11+
pub struct RemoveOptions<'a> {
12+
/// Configuration information for Cargo operations
13+
pub config: &'a Config,
14+
/// Package to remove dependencies from
15+
pub spec: &'a Package,
16+
/// Dependencies to remove
17+
pub dependencies: Vec<String>,
18+
/// Which dependency section to remove these from
19+
pub section: DepTable,
20+
/// Whether or not to actually write the manifest
21+
pub dry_run: bool,
22+
}
23+
24+
/// Remove dependencies from a manifest
25+
pub fn remove(options: &RemoveOptions<'_>) -> CargoResult<()> {
26+
let dep_table = options
27+
.section
28+
.to_table()
29+
.into_iter()
30+
.map(String::from)
31+
.collect::<Vec<_>>();
32+
33+
let manifest_path = options.spec.manifest_path().to_path_buf();
34+
let mut manifest = LocalManifest::try_new(&manifest_path)?;
35+
36+
for dep in &options.dependencies {
37+
let section = if dep_table.len() >= 3 {
38+
format!("{} for target `{}`", &dep_table[2], &dep_table[1])
39+
} else {
40+
dep_table[0].clone()
41+
};
42+
options
43+
.config
44+
.shell()
45+
.status("Removing", format!("{dep} from {section}"))?;
46+
47+
manifest.remove_from_table(&dep_table, dep)?;
48+
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+
manifest.gc_dep(dep);
53+
}
54+
55+
if options.dry_run {
56+
options
57+
.config
58+
.shell()
59+
.warn("aborting remove due to dry run")?;
60+
} else {
61+
manifest.write()?;
62+
}
63+
64+
Ok(())
65+
}

src/cargo/ops/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ mod cargo_output_metadata;
4545
mod cargo_package;
4646
mod cargo_pkgid;
4747
mod cargo_read_manifest;
48+
pub mod cargo_remove;
4849
mod cargo_run;
4950
mod cargo_test;
5051
mod cargo_uninstall;

tests/testsuite/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod cargo_command;
2525
mod cargo_config;
2626
mod cargo_env_config;
2727
mod cargo_features;
28+
mod cargo_remove;
2829
mod cargo_targets;
2930
mod cfg;
3031
mod check;

0 commit comments

Comments
 (0)