Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import cargo remove into cargo #11099

Merged
merged 4 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/bin/cargo/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub fn builtin() -> Vec<Command> {
pkgid::cli(),
publish::cli(),
read_manifest::cli(),
remove::cli(),
report::cli(),
run::cli(),
rustc::cli(),
Expand Down Expand Up @@ -68,6 +69,7 @@ pub fn builtin_exec(cmd: &str) -> Option<fn(&mut Config, &ArgMatches) -> CliResu
"pkgid" => pkgid::exec,
"publish" => publish::exec,
"read-manifest" => read_manifest::exec,
"remove" => remove::exec,
"report" => report::exec,
"run" => run::exec,
"rustc" => rustc::exec,
Expand Down Expand Up @@ -110,6 +112,7 @@ pub mod package;
pub mod pkgid;
pub mod publish;
pub mod read_manifest;
pub mod remove;
pub mod report;
pub mod run;
pub mod rustc;
Expand Down
116 changes: 116 additions & 0 deletions src/bin/cargo/commands/remove.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use cargo::core::dependency::DepKind;
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;

pub fn cli() -> clap::Command {
clap::Command::new("remove")
// Subcommand aliases are handled in `aliased_command()`.
// .alias("rm")
.about("Remove dependencies from a Cargo.toml manifest file")
.args([clap::Arg::new("dependencies")
.action(clap::ArgAction::Append)
.required(true)
.num_args(1..)
.value_name("DEP_ID")
.help("Dependencies to be removed")])
.arg_package("Package to remove from")
.arg_manifest_path()
.arg_quiet()
.arg_dry_run("Don't actually write the manifest")
.next_help_heading("SECTION")
.args([
clap::Arg::new("dev")
.long("dev")
.conflicts_with("build")
.action(clap::ArgAction::SetTrue)
.group("section")
.help("Remove as development dependency"),
clap::Arg::new("build")
.long("build")
.conflicts_with("dev")
.action(clap::ArgAction::SetTrue)
.group("section")
.help("Remove as build dependency"),
clap::Arg::new("target")
.long("target")
.num_args(1)
.value_name("TARGET")
.value_parser(clap::builder::NonEmptyStringValueParser::new())
.help("Remove as dependency from the given target platform"),
])
}

pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
let dry_run = args.dry_run();

let workspace = args.workspace(config)?;
let packages = args.packages_from_flags()?;
let packages = packages.get_packages(&workspace)?;
let spec = match packages.len() {
0 => {
return Err(CliError::new(
anyhow::format_err!("no packages selected. Please specify one with `-p <PKG_ID>`"),
101,
epage marked this conversation as resolved.
Show resolved Hide resolved
));
}
1 => packages[0],
len => {
return Err(CliError::new(
anyhow::format_err!(
"{len} packages selected. Please specify one with `-p <PKG_ID>`",
),
101,
));
epage marked this conversation as resolved.
Show resolved Hide resolved
}
};

let dependencies = args
.get_many::<String>("dependencies")
.expect("required(true)")
.cloned()
.collect();

let section = parse_section(args);

let options = RemoveOptions {
config,
spec,
dependencies,
section,
dry_run,
};
remove(&options)?;

if !dry_run {
// Reload the workspace since we've changed dependencies
let ws = args.workspace(config)?;
resolve_ws(&ws)?;
}

Ok(())
}

fn parse_section(args: &ArgMatches) -> DepTable {
weihanglo marked this conversation as resolved.
Show resolved Hide resolved
let dev = args.flag("dev");
let build = args.flag("build");

let kind = if dev {
DepKind::Development
} else if build {
DepKind::Build
} else {
DepKind::Normal
};

let mut table = DepTable::new().set_kind(kind);

if let Some(target) = args.get_one::<String>("target") {
assert!(!target.is_empty(), "Target specification may not be empty");
table = table.set_target(target);
}

table
}
3 changes: 2 additions & 1 deletion src/bin/cargo/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ fn main() {

/// Table for defining the aliases which come builtin in `Cargo`.
/// The contents are structured as: `(alias, aliased_command, description)`.
const BUILTIN_ALIASES: [(&str, &str, &str); 5] = [
const BUILTIN_ALIASES: [(&str, &str, &str); 6] = [
("b", "build", "alias: build"),
("c", "check", "alias: check"),
("d", "doc", "alias: doc"),
("r", "run", "alias: run"),
("t", "test", "alias: test"),
("rm", "remove", "alias: remove"),
];

/// Function which contains the list of all of the builtin aliases and it's
Expand Down
65 changes: 65 additions & 0 deletions src/cargo/ops/cargo_remove.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Core of cargo-remove command

use crate::core::Package;
use crate::util::toml_mut::manifest::DepTable;
use crate::util::toml_mut::manifest::LocalManifest;
use crate::CargoResult;
use crate::Config;

/// Remove a dependency from a Cargo.toml manifest file.
#[derive(Debug)]
pub struct RemoveOptions<'a> {
/// Configuration information for Cargo operations
pub config: &'a Config,
/// Package to remove dependencies from
pub spec: &'a Package,
/// Dependencies to remove
pub dependencies: Vec<String>,
/// Which dependency section to remove these from
pub section: DepTable,
/// Whether or not to actually write the manifest
pub dry_run: bool,
}

/// Remove dependencies from a manifest
pub fn remove(options: &RemoveOptions<'_>) -> CargoResult<()> {
let dep_table = options
.section
.to_table()
.into_iter()
.map(String::from)
.collect::<Vec<_>>();

let manifest_path = options.spec.manifest_path().to_path_buf();
let mut manifest = LocalManifest::try_new(&manifest_path)?;

for dep in &options.dependencies {
let section = if dep_table.len() >= 3 {
format!("{} for target `{}`", &dep_table[2], &dep_table[1])
} else {
dep_table[0].clone()
};
options
.config
.shell()
.status("Removing", format!("{dep} from {section}"))?;

manifest.remove_from_table(&dep_table, dep)?;

// Now that we have removed the crate, if that was the last reference to that
// crate, then we need to drop any explicitly activated features on
// that crate.
manifest.gc_dep(dep);
}

if options.dry_run {
options
.config
.shell()
.warn("aborting remove due to dry run")?;
} else {
manifest.write()?;
}

Ok(())
}
1 change: 1 addition & 0 deletions src/cargo/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ mod cargo_output_metadata;
mod cargo_package;
mod cargo_pkgid;
mod cargo_read_manifest;
pub mod cargo_remove;
mod cargo_run;
mod cargo_test;
mod cargo_uninstall;
Expand Down
29 changes: 29 additions & 0 deletions src/cargo/util/toml_mut/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,26 @@ impl LocalManifest {
Ok(())
}

/// Remove entry from a Cargo.toml.
pub fn remove_from_table(&mut self, table_path: &[String], name: &str) -> CargoResult<()> {
let parent_table = self.get_table_mut(table_path)?;

let dep = parent_table
.get_mut(name)
.filter(|t| !t.is_none())
.ok_or_else(|| non_existent_dependency_err(name, table_path.join(".")))?;

// remove the dependency
*dep = toml_edit::Item::None;

// remove table if empty
if parent_table.as_table_like().unwrap().is_empty() {
*parent_table = toml_edit::Item::None;
}

Ok(())
}

/// Remove references to `dep_key` if its no longer present.
pub fn gc_dep(&mut self, dep_key: &str) {
let explicit_dep_activation = self.is_explicit_dep_activation(dep_key);
Expand Down Expand Up @@ -504,6 +524,8 @@ fn fix_feature_activations(
}
}
}

feature_values.fmt();
}

pub fn str_or_1_len_table(item: &toml_edit::Item) -> bool {
Expand All @@ -517,3 +539,10 @@ fn parse_manifest_err() -> anyhow::Error {
fn non_existent_table_err(table: impl std::fmt::Display) -> anyhow::Error {
anyhow::format_err!("the table `{table}` could not be found.")
}

fn non_existent_dependency_err(
name: impl std::fmt::Display,
table: impl std::fmt::Display,
) -> anyhow::Error {
anyhow::format_err!("the dependency `{name}` could not be found in `{table}`.")
}
1 change: 1 addition & 0 deletions tests/testsuite/cargo_remove/avoid_empty_tables/in
25 changes: 25 additions & 0 deletions tests/testsuite/cargo_remove/avoid_empty_tables/mod.rs
Original file line number Diff line number Diff line change
@@ -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(["clippy"])
.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);
}
23 changes: 23 additions & 0 deletions tests/testsuite/cargo_remove/avoid_empty_tables/out/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "cargo-remove-test-fixture"
version = "0.1.0"

[[bin]]
name = "main"
path = "src/main.rs"

[build-dependencies]
semver = "0.1.0"

[dependencies]
docopt = "0.6"
rustc-serialize = "0.4"
semver = "0.1"
toml = "0.1"

[dev-dependencies]
regex = "0.1.1"
serde = "1.0.90"

[features]
std = ["serde/std", "semver/std"]
2 changes: 2 additions & 0 deletions tests/testsuite/cargo_remove/avoid_empty_tables/stderr.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Removing clippy from dependencies
Updating `dummy-registry` index
Empty file.
1 change: 1 addition & 0 deletions tests/testsuite/cargo_remove/build/in
25 changes: 25 additions & 0 deletions tests/testsuite/cargo_remove/build/mod.rs
Original file line number Diff line number Diff line change
@@ -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(["--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);
}
21 changes: 21 additions & 0 deletions tests/testsuite/cargo_remove/build/out/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "cargo-remove-test-fixture"
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"]
2 changes: 2 additions & 0 deletions tests/testsuite/cargo_remove/build/stderr.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Removing semver from build-dependencies
Updating `dummy-registry` index
Empty file.
1 change: 1 addition & 0 deletions tests/testsuite/cargo_remove/dev/in
Loading