summaryrefslogtreecommitdiffstats
path: root/src/bin
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/bin/cargo/cli.rs563
-rw-r--r--src/bin/cargo/commands/add.rs362
-rw-r--r--src/bin/cargo/commands/bench.rs77
-rw-r--r--src/bin/cargo/commands/build.rs73
-rw-r--r--src/bin/cargo/commands/check.rs56
-rw-r--r--src/bin/cargo/commands/clean.rs37
-rw-r--r--src/bin/cargo/commands/config.rs55
-rw-r--r--src/bin/cargo/commands/doc.rs61
-rw-r--r--src/bin/cargo/commands/fetch.rs24
-rw-r--r--src/bin/cargo/commands/fix.rs92
-rw-r--r--src/bin/cargo/commands/generate_lockfile.rs17
-rw-r--r--src/bin/cargo/commands/git_checkout.rs14
-rw-r--r--src/bin/cargo/commands/help.rs147
-rw-r--r--src/bin/cargo/commands/init.rs22
-rw-r--r--src/bin/cargo/commands/install.rs197
-rw-r--r--src/bin/cargo/commands/locate_project.rs93
-rw-r--r--src/bin/cargo/commands/login.rs46
-rw-r--r--src/bin/cargo/commands/logout.rs23
-rw-r--r--src/bin/cargo/commands/metadata.rs56
-rw-r--r--src/bin/cargo/commands/mod.rs128
-rw-r--r--src/bin/cargo/commands/new.rs30
-rw-r--r--src/bin/cargo/commands/owner.rs51
-rw-r--r--src/bin/cargo/commands/package.rs62
-rw-r--r--src/bin/cargo/commands/pkgid.rs28
-rw-r--r--src/bin/cargo/commands/publish.rs55
-rw-r--r--src/bin/cargo/commands/read_manifest.rs20
-rw-r--r--src/bin/cargo/commands/remove.rs344
-rw-r--r--src/bin/cargo/commands/report.rs49
-rw-r--r--src/bin/cargo/commands/run.rs103
-rw-r--r--src/bin/cargo/commands/rustc.rs100
-rw-r--r--src/bin/cargo/commands/rustdoc.rs66
-rw-r--r--src/bin/cargo/commands/search.rs37
-rw-r--r--src/bin/cargo/commands/test.rs113
-rw-r--r--src/bin/cargo/commands/tree.rs305
-rw-r--r--src/bin/cargo/commands/uninstall.rs34
-rw-r--r--src/bin/cargo/commands/update.rs46
-rw-r--r--src/bin/cargo/commands/vendor.rs100
-rw-r--r--src/bin/cargo/commands/verify_project.rs26
-rw-r--r--src/bin/cargo/commands/version.rs16
-rw-r--r--src/bin/cargo/commands/yank.rs65
-rw-r--r--src/bin/cargo/main.rs322
41 files changed, 4115 insertions, 0 deletions
diff --git a/src/bin/cargo/cli.rs b/src/bin/cargo/cli.rs
new file mode 100644
index 0000000..2d0107f
--- /dev/null
+++ b/src/bin/cargo/cli.rs
@@ -0,0 +1,563 @@
+use anyhow::{anyhow, Context as _};
+use cargo::core::shell::Shell;
+use cargo::core::{features, CliUnstable};
+use cargo::{self, drop_print, drop_println, CliResult, Config};
+use clap::{Arg, ArgMatches};
+use itertools::Itertools;
+use std::collections::HashMap;
+use std::ffi::OsStr;
+use std::ffi::OsString;
+use std::fmt::Write;
+
+use super::commands;
+use super::list_commands;
+use crate::command_prelude::*;
+use cargo::core::features::HIDDEN;
+
+lazy_static::lazy_static! {
+ // Maps from commonly known external commands (not builtin to cargo) to their
+ // description, for the help page. Reserved for external subcommands that are
+ // core within the rust ecosystem (esp ones that might become internal in the future).
+ static ref KNOWN_EXTERNAL_COMMAND_DESCRIPTIONS: HashMap<&'static str, &'static str> = HashMap::from([
+ ("clippy", "Checks a package to catch common mistakes and improve your Rust code."),
+ ("fmt", "Formats all bin and lib files of the current crate using rustfmt."),
+ ]);
+}
+
+pub fn main(config: &mut LazyConfig) -> CliResult {
+ let args = cli().try_get_matches()?;
+
+ // Update the process-level notion of cwd
+ // This must be completed before config is initialized
+ assert_eq!(config.is_init(), false);
+ if let Some(new_cwd) = args.get_one::<std::path::PathBuf>("directory") {
+ // This is a temporary hack. This cannot access `Config`, so this is a bit messy.
+ // This does not properly parse `-Z` flags that appear after the subcommand.
+ // The error message is not as helpful as the standard one.
+ let nightly_features_allowed = matches!(&*features::channel(), "nightly" | "dev");
+ if !nightly_features_allowed
+ || (nightly_features_allowed
+ && !args
+ .get_many("unstable-features")
+ .map(|mut z| z.any(|value: &String| value == "unstable-options"))
+ .unwrap_or(false))
+ {
+ return Err(anyhow::format_err!(
+ "the `-C` flag is unstable, \
+ pass `-Z unstable-options` on the nightly channel to enable it"
+ )
+ .into());
+ }
+ std::env::set_current_dir(&new_cwd).context("could not change to requested directory")?;
+ }
+
+ // CAUTION: Be careful with using `config` until it is configured below.
+ // In general, try to avoid loading config values unless necessary (like
+ // the [alias] table).
+ let config = config.get_mut();
+
+ let (expanded_args, global_args) = expand_aliases(config, args, vec![])?;
+
+ if expanded_args
+ .get_one::<String>("unstable-features")
+ .map(String::as_str)
+ == Some("help")
+ {
+ let options = CliUnstable::help();
+ let non_hidden_options: Vec<(String, String)> = options
+ .iter()
+ .filter(|(_, help_message)| *help_message != HIDDEN)
+ .map(|(name, help)| (name.to_string(), help.to_string()))
+ .collect();
+ let longest_option = non_hidden_options
+ .iter()
+ .map(|(option_name, _)| option_name.len())
+ .max()
+ .unwrap_or(0);
+ let help_lines: Vec<String> = non_hidden_options
+ .iter()
+ .map(|(option_name, option_help_message)| {
+ let option_name_kebab_case = option_name.replace("_", "-");
+ let padding = " ".repeat(longest_option - option_name.len()); // safe to subtract
+ format!(
+ " -Z {}{} -- {}",
+ option_name_kebab_case, padding, option_help_message
+ )
+ })
+ .collect();
+ let joined = help_lines.join("\n");
+ drop_println!(
+ config,
+ "
+Available unstable (nightly-only) flags:
+
+{}
+
+Run with 'cargo -Z [FLAG] [COMMAND]'",
+ joined
+ );
+ if !config.nightly_features_allowed {
+ drop_println!(
+ config,
+ "\nUnstable flags are only available on the nightly channel \
+ of Cargo, but this is the `{}` channel.\n\
+ {}",
+ features::channel(),
+ features::SEE_CHANNELS
+ );
+ }
+ drop_println!(
+ config,
+ "\nSee https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
+ for more information about these flags."
+ );
+ return Ok(());
+ }
+
+ let is_verbose = expanded_args.verbose() > 0;
+ if expanded_args.flag("version") {
+ let version = get_version_string(is_verbose);
+ drop_print!(config, "{}", version);
+ return Ok(());
+ }
+
+ if let Some(code) = expanded_args.get_one::<String>("explain") {
+ let mut procss = config.load_global_rustc(None)?.process();
+ procss.arg("--explain").arg(code).exec()?;
+ return Ok(());
+ }
+
+ if expanded_args.flag("list") {
+ drop_println!(config, "Installed Commands:");
+ for (name, command) in list_commands(config) {
+ let known_external_desc = KNOWN_EXTERNAL_COMMAND_DESCRIPTIONS.get(name.as_str());
+ match command {
+ CommandInfo::BuiltIn { about } => {
+ assert!(
+ known_external_desc.is_none(),
+ "KNOWN_EXTERNAL_COMMANDS shouldn't contain builtin \"{}\"",
+ name
+ );
+ let summary = about.unwrap_or_default();
+ let summary = summary.lines().next().unwrap_or(&summary); // display only the first line
+ drop_println!(config, " {:<20} {}", name, summary);
+ }
+ CommandInfo::External { path } => {
+ if let Some(desc) = known_external_desc {
+ drop_println!(config, " {:<20} {}", name, desc);
+ } else if is_verbose {
+ drop_println!(config, " {:<20} {}", name, path.display());
+ } else {
+ drop_println!(config, " {}", name);
+ }
+ }
+ CommandInfo::Alias { target } => {
+ drop_println!(
+ config,
+ " {:<20} alias: {}",
+ name,
+ target.iter().join(" ")
+ );
+ }
+ }
+ }
+ return Ok(());
+ }
+
+ let (cmd, subcommand_args) = match expanded_args.subcommand() {
+ Some((cmd, args)) => (cmd, args),
+ _ => {
+ // No subcommand provided.
+ cli().print_help()?;
+ return Ok(());
+ }
+ };
+ config_configure(config, &expanded_args, subcommand_args, global_args)?;
+ super::init_git(config);
+
+ execute_subcommand(config, cmd, subcommand_args)
+}
+
+pub fn get_version_string(is_verbose: bool) -> String {
+ let version = cargo::version();
+ let mut version_string = format!("cargo {}\n", version);
+ if is_verbose {
+ version_string.push_str(&format!("release: {}\n", version.version));
+ if let Some(ref ci) = version.commit_info {
+ version_string.push_str(&format!("commit-hash: {}\n", ci.commit_hash));
+ version_string.push_str(&format!("commit-date: {}\n", ci.commit_date));
+ }
+ writeln!(version_string, "host: {}", env!("RUST_HOST_TARGET")).unwrap();
+ add_libgit2(&mut version_string);
+ add_curl(&mut version_string);
+ add_ssl(&mut version_string);
+ writeln!(version_string, "os: {}", os_info::get()).unwrap();
+ }
+ version_string
+}
+
+fn add_libgit2(version_string: &mut String) {
+ let git2_v = git2::Version::get();
+ let lib_v = git2_v.libgit2_version();
+ let vendored = if git2_v.vendored() {
+ format!("vendored")
+ } else {
+ format!("system")
+ };
+ writeln!(
+ version_string,
+ "libgit2: {}.{}.{} (sys:{} {})",
+ lib_v.0,
+ lib_v.1,
+ lib_v.2,
+ git2_v.crate_version(),
+ vendored
+ )
+ .unwrap();
+}
+
+fn add_curl(version_string: &mut String) {
+ let curl_v = curl::Version::get();
+ let vendored = if curl_v.vendored() {
+ format!("vendored")
+ } else {
+ format!("system")
+ };
+ writeln!(
+ version_string,
+ "libcurl: {} (sys:{} {} ssl:{})",
+ curl_v.version(),
+ curl_sys::rust_crate_version(),
+ vendored,
+ curl_v.ssl_version().unwrap_or("none")
+ )
+ .unwrap();
+}
+
+fn add_ssl(version_string: &mut String) {
+ #[cfg(feature = "openssl")]
+ {
+ writeln!(version_string, "ssl: {}", openssl::version::version()).unwrap();
+ }
+ #[cfg(not(feature = "openssl"))]
+ {
+ let _ = version_string; // Silence unused warning.
+ }
+}
+
+/// Expands aliases recursively to collect all the command line arguments.
+///
+/// [`GlobalArgs`] need to be extracted before expanding aliases because the
+/// clap code for extracting a subcommand discards global options
+/// (appearing before the subcommand).
+fn expand_aliases(
+ config: &mut Config,
+ args: ArgMatches,
+ mut already_expanded: Vec<String>,
+) -> Result<(ArgMatches, GlobalArgs), CliError> {
+ if let Some((cmd, args)) = args.subcommand() {
+ let exec = commands::builtin_exec(cmd);
+ let aliased_cmd = super::aliased_command(config, cmd);
+
+ match (exec, aliased_cmd) {
+ (Some(_), Ok(Some(_))) => {
+ // User alias conflicts with a built-in subcommand
+ config.shell().warn(format!(
+ "user-defined alias `{}` is ignored, because it is shadowed by a built-in command",
+ cmd,
+ ))?;
+ }
+ (Some(_), Ok(None) | Err(_)) => {
+ // Here we ignore errors from aliasing as we already favor built-in command,
+ // and alias doesn't involve in this context.
+
+ if let Some(values) = args.get_many::<OsString>("") {
+ // Command is built-in and is not conflicting with alias, but contains ignored values.
+ return Err(anyhow::format_err!(
+ "\
+trailing arguments after built-in command `{}` are unsupported: `{}`
+
+To pass the arguments to the subcommand, remove `--`",
+ cmd,
+ values.map(|s| s.to_string_lossy()).join(" "),
+ )
+ .into());
+ }
+ }
+ (None, Ok(None)) => {}
+ (None, Ok(Some(alias))) => {
+ // Check if a user-defined alias is shadowing an external subcommand
+ // (binary of the form `cargo-<subcommand>`)
+ // Currently this is only a warning, but after a transition period this will become
+ // a hard error.
+ if super::builtin_aliases_execs(cmd).is_none() {
+ if let Some(path) = super::find_external_subcommand(config, cmd) {
+ config.shell().warn(format!(
+ "\
+user-defined alias `{}` is shadowing an external subcommand found at: `{}`
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #10049 <https://github.com/rust-lang/cargo/issues/10049>.",
+ cmd,
+ path.display(),
+ ))?;
+ }
+ }
+
+ let mut alias = alias
+ .into_iter()
+ .map(|s| OsString::from(s))
+ .collect::<Vec<_>>();
+ alias.extend(args.get_many::<OsString>("").unwrap_or_default().cloned());
+ // new_args strips out everything before the subcommand, so
+ // capture those global options now.
+ // Note that an alias to an external command will not receive
+ // these arguments. That may be confusing, but such is life.
+ let global_args = GlobalArgs::new(args);
+ let new_args = cli().no_binary_name(true).try_get_matches_from(alias)?;
+
+ let new_cmd = new_args.subcommand_name().expect("subcommand is required");
+ already_expanded.push(cmd.to_string());
+ if already_expanded.contains(&new_cmd.to_string()) {
+ // Crash if the aliases are corecursive / unresolvable
+ return Err(anyhow!(
+ "alias {} has unresolvable recursive definition: {} -> {}",
+ already_expanded[0],
+ already_expanded.join(" -> "),
+ new_cmd,
+ )
+ .into());
+ }
+
+ let (expanded_args, _) = expand_aliases(config, new_args, already_expanded)?;
+ return Ok((expanded_args, global_args));
+ }
+ (None, Err(e)) => return Err(e.into()),
+ }
+ };
+
+ Ok((args, GlobalArgs::default()))
+}
+
+fn config_configure(
+ config: &mut Config,
+ args: &ArgMatches,
+ subcommand_args: &ArgMatches,
+ global_args: GlobalArgs,
+) -> CliResult {
+ let arg_target_dir = &subcommand_args.value_of_path("target-dir", config);
+ let verbose = global_args.verbose + args.verbose();
+ // quiet is unusual because it is redefined in some subcommands in order
+ // to provide custom help text.
+ let quiet = args.flag("quiet") || subcommand_args.flag("quiet") || global_args.quiet;
+ let global_color = global_args.color; // Extract so it can take reference.
+ let color = args
+ .get_one::<String>("color")
+ .map(String::as_str)
+ .or_else(|| global_color.as_deref());
+ let frozen = args.flag("frozen") || global_args.frozen;
+ let locked = args.flag("locked") || global_args.locked;
+ let offline = args.flag("offline") || global_args.offline;
+ let mut unstable_flags = global_args.unstable_flags;
+ if let Some(values) = args.get_many::<String>("unstable-features") {
+ unstable_flags.extend(values.cloned());
+ }
+ let mut config_args = global_args.config_args;
+ if let Some(values) = args.get_many::<String>("config") {
+ config_args.extend(values.cloned());
+ }
+ config.configure(
+ verbose,
+ quiet,
+ color,
+ frozen,
+ locked,
+ offline,
+ arg_target_dir,
+ &unstable_flags,
+ &config_args,
+ )?;
+ Ok(())
+}
+
+fn execute_subcommand(config: &mut Config, cmd: &str, subcommand_args: &ArgMatches) -> CliResult {
+ if let Some(exec) = commands::builtin_exec(cmd) {
+ return exec(config, subcommand_args);
+ }
+
+ let mut ext_args: Vec<&OsStr> = vec![OsStr::new(cmd)];
+ ext_args.extend(
+ subcommand_args
+ .get_many::<OsString>("")
+ .unwrap_or_default()
+ .map(OsString::as_os_str),
+ );
+ super::execute_external_subcommand(config, cmd, &ext_args)
+}
+
+#[derive(Default)]
+struct GlobalArgs {
+ verbose: u32,
+ quiet: bool,
+ color: Option<String>,
+ frozen: bool,
+ locked: bool,
+ offline: bool,
+ unstable_flags: Vec<String>,
+ config_args: Vec<String>,
+}
+
+impl GlobalArgs {
+ fn new(args: &ArgMatches) -> GlobalArgs {
+ GlobalArgs {
+ verbose: args.verbose(),
+ quiet: args.flag("quiet"),
+ color: args.get_one::<String>("color").cloned(),
+ frozen: args.flag("frozen"),
+ locked: args.flag("locked"),
+ offline: args.flag("offline"),
+ unstable_flags: args
+ .get_many::<String>("unstable-features")
+ .unwrap_or_default()
+ .cloned()
+ .collect(),
+ config_args: args
+ .get_many::<String>("config")
+ .unwrap_or_default()
+ .cloned()
+ .collect(),
+ }
+ }
+}
+
+pub fn cli() -> Command {
+ let is_rustup = std::env::var_os("RUSTUP_HOME").is_some();
+ let usage = if is_rustup {
+ "cargo [+toolchain] [OPTIONS] [COMMAND]"
+ } else {
+ "cargo [OPTIONS] [COMMAND]"
+ };
+ Command::new("cargo")
+ .allow_external_subcommands(true)
+ // Doesn't mix well with our list of common cargo commands. See clap-rs/clap#3108 for
+ // opening clap up to allow us to style our help template
+ .disable_colored_help(true)
+ // Provide a custom help subcommand for calling into man pages
+ .disable_help_subcommand(true)
+ .override_usage(usage)
+ .help_template(
+ "\
+Rust's package manager
+
+Usage: {usage}
+
+Options:
+{options}
+
+Some common cargo commands are (see all commands with --list):
+ build, b Compile the current package
+ check, c Analyze the current package and report errors, but don't build object files
+ clean Remove the target directory
+ doc, d Build this package's and its dependencies' documentation
+ new Create a new cargo package
+ init Create a new cargo package in an existing directory
+ add Add dependencies to a manifest file
+ remove Remove dependencies from a manifest file
+ run, r Run a binary or example of the local package
+ test, t Run the tests
+ bench Run the benchmarks
+ update Update dependencies listed in Cargo.lock
+ search Search registry for crates
+ publish Package and upload this package to the registry
+ install Install a Rust binary. Default location is $HOME/.cargo/bin
+ uninstall Uninstall a Rust binary
+
+See 'cargo help <command>' for more information on a specific command.\n",
+ )
+ .arg(flag("version", "Print version info and exit").short('V'))
+ .arg(flag("list", "List installed commands"))
+ .arg(opt("explain", "Run `rustc --explain CODE`").value_name("CODE"))
+ .arg(
+ opt(
+ "verbose",
+ "Use verbose output (-vv very verbose/build.rs output)",
+ )
+ .short('v')
+ .action(ArgAction::Count)
+ .global(true),
+ )
+ .arg_quiet()
+ .arg(
+ opt("color", "Coloring: auto, always, never")
+ .value_name("WHEN")
+ .global(true),
+ )
+ .arg(
+ Arg::new("directory")
+ .help("Change to DIRECTORY before doing anything (nightly-only)")
+ .short('C')
+ .value_name("DIRECTORY")
+ .value_hint(clap::ValueHint::DirPath)
+ .value_parser(clap::builder::ValueParser::path_buf()),
+ )
+ .arg(flag("frozen", "Require Cargo.lock and cache are up to date").global(true))
+ .arg(flag("locked", "Require Cargo.lock is up to date").global(true))
+ .arg(flag("offline", "Run without accessing the network").global(true))
+ .arg(multi_opt("config", "KEY=VALUE", "Override a configuration value").global(true))
+ .arg(
+ Arg::new("unstable-features")
+ .help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details")
+ .short('Z')
+ .value_name("FLAG")
+ .action(ArgAction::Append)
+ .global(true),
+ )
+ .subcommands(commands::builtin())
+}
+
+/// Delay loading [`Config`] until access.
+///
+/// In the common path, the [`Config`] is dependent on CLI parsing and shouldn't be loaded until
+/// after that is done but some other paths (like fix or earlier errors) might need access to it,
+/// so this provides a way to share the instance and the implementation across these different
+/// accesses.
+pub struct LazyConfig {
+ config: Option<Config>,
+}
+
+impl LazyConfig {
+ pub fn new() -> Self {
+ Self { config: None }
+ }
+
+ /// Check whether the config is loaded
+ ///
+ /// This is useful for asserts in case the environment needs to be setup before loading
+ pub fn is_init(&self) -> bool {
+ self.config.is_some()
+ }
+
+ /// Get the config, loading it if needed
+ ///
+ /// On error, the process is terminated
+ pub fn get(&mut self) -> &Config {
+ self.get_mut()
+ }
+
+ /// Get the config, loading it if needed
+ ///
+ /// On error, the process is terminated
+ pub fn get_mut(&mut self) -> &mut Config {
+ self.config.get_or_insert_with(|| match Config::default() {
+ Ok(cfg) => cfg,
+ Err(e) => {
+ let mut shell = Shell::new();
+ cargo::exit_with_error(e.into(), &mut shell)
+ }
+ })
+ }
+}
+
+#[test]
+fn verify_cli() {
+ cli().debug_assert();
+}
diff --git a/src/bin/cargo/commands/add.rs b/src/bin/cargo/commands/add.rs
new file mode 100644
index 0000000..39f0e18
--- /dev/null
+++ b/src/bin/cargo/commands/add.rs
@@ -0,0 +1,362 @@
+use cargo::sources::CRATES_IO_REGISTRY;
+use cargo::util::print_available_packages;
+use indexmap::IndexMap;
+use indexmap::IndexSet;
+
+use cargo::core::dependency::DepKind;
+use cargo::core::FeatureValue;
+use cargo::ops::cargo_add::add;
+use cargo::ops::cargo_add::AddOptions;
+use cargo::ops::cargo_add::DepOp;
+use cargo::ops::resolve_ws;
+use cargo::util::command_prelude::*;
+use cargo::util::interning::InternedString;
+use cargo::util::toml_mut::manifest::DepTable;
+use cargo::CargoResult;
+
+pub fn cli() -> Command {
+ clap::Command::new("add")
+ .about("Add dependencies to a Cargo.toml manifest file")
+ .override_usage(
+ "\
+ cargo add [OPTIONS] <DEP>[@<VERSION>] ...
+ cargo add [OPTIONS] --path <PATH> ...
+ cargo add [OPTIONS] --git <URL> ..."
+ )
+ .after_help("Run `cargo help add` for more detailed information.\n")
+ .group(clap::ArgGroup::new("selected").multiple(true).required(true))
+ .args([
+ clap::Arg::new("crates")
+ .value_name("DEP_ID")
+ .num_args(0..)
+ .help("Reference to a package to add as a dependency")
+ .long_help(
+ "Reference to a package to add as a dependency
+
+You can reference a package by:
+- `<name>`, like `cargo add serde` (latest version will be used)
+- `<name>@<version-req>`, like `cargo add serde@1` or `cargo add serde@=1.0.38`"
+ )
+ .group("selected"),
+ flag("no-default-features",
+ "Disable the default features"),
+ flag("default-features",
+ "Re-enable the default features")
+ .overrides_with("no-default-features"),
+ clap::Arg::new("features")
+ .short('F')
+ .long("features")
+ .value_name("FEATURES")
+ .action(ArgAction::Append)
+ .help("Space or comma separated list of features to activate"),
+ flag("optional",
+ "Mark the dependency as optional")
+ .long_help("Mark the dependency as optional
+
+The package name will be exposed as feature of your crate.")
+ .conflicts_with("dev"),
+ flag("no-optional",
+ "Mark the dependency as required")
+ .long_help("Mark the dependency as required
+
+The package will be removed from your features.")
+ .conflicts_with("dev")
+ .overrides_with("optional"),
+ clap::Arg::new("rename")
+ .long("rename")
+ .action(ArgAction::Set)
+ .value_name("NAME")
+ .help("Rename the dependency")
+ .long_help("Rename the dependency
+
+Example uses:
+- Depending on multiple versions of a crate
+- Depend on crates with the same name from different registries"),
+ ])
+ .arg_manifest_path()
+ .arg_package("Package to modify")
+ .arg_quiet()
+ .arg_dry_run("Don't actually write the manifest")
+ .next_help_heading("Source")
+ .args([
+ clap::Arg::new("path")
+ .long("path")
+ .action(ArgAction::Set)
+ .value_name("PATH")
+ .help("Filesystem path to local crate to add")
+ .group("selected")
+ .conflicts_with("git"),
+ clap::Arg::new("git")
+ .long("git")
+ .action(ArgAction::Set)
+ .value_name("URI")
+ .help("Git repository location")
+ .long_help("Git repository location
+
+Without any other information, cargo will use latest commit on the main branch.")
+ .group("selected"),
+ clap::Arg::new("branch")
+ .long("branch")
+ .action(ArgAction::Set)
+ .value_name("BRANCH")
+ .help("Git branch to download the crate from")
+ .requires("git")
+ .group("git-ref"),
+ clap::Arg::new("tag")
+ .long("tag")
+ .action(ArgAction::Set)
+ .value_name("TAG")
+ .help("Git tag to download the crate from")
+ .requires("git")
+ .group("git-ref"),
+ clap::Arg::new("rev")
+ .long("rev")
+ .action(ArgAction::Set)
+ .value_name("REV")
+ .help("Git reference to download the crate from")
+ .long_help("Git reference to download the crate from
+
+This is the catch all, handling hashes to named references in remote repositories.")
+ .requires("git")
+ .group("git-ref"),
+ clap::Arg::new("registry")
+ .long("registry")
+ .action(ArgAction::Set)
+ .value_name("NAME")
+ .help("Package registry for this dependency"),
+ ])
+ .next_help_heading("Section")
+ .args([
+ flag("dev",
+ "Add as development dependency")
+ .long_help("Add as development dependency
+
+Dev-dependencies are not used when compiling a package for building, but are used for compiling tests, examples, and benchmarks.
+
+These dependencies are not propagated to other packages which depend on this package.")
+ .group("section"),
+ flag("build",
+ "Add as build dependency")
+ .long_help("Add as build dependency
+
+Build-dependencies are the only dependencies available for use by build scripts (`build.rs` files).")
+ .group("section"),
+ clap::Arg::new("target")
+ .long("target")
+ .action(ArgAction::Set)
+ .value_name("TARGET")
+ .value_parser(clap::builder::NonEmptyStringValueParser::new())
+ .help("Add as dependency to the given target platform")
+ ])
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let dry_run = args.dry_run();
+ let section = parse_section(args);
+
+ let ws = args.workspace(config)?;
+
+ if args.is_present_with_zero_values("package") {
+ print_available_packages(&ws)?;
+ }
+
+ let packages = args.packages_from_flags()?;
+ let packages = packages.get_packages(&ws)?;
+ let spec = match packages.len() {
+ 0 => {
+ return Err(CliError::new(
+ anyhow::format_err!(
+ "no packages selected to modify. Please specify one with `-p <PKGID>`"
+ ),
+ 101,
+ ));
+ }
+ 1 => packages[0],
+ _ => {
+ let names = packages.iter().map(|p| p.name()).collect::<Vec<_>>();
+ return Err(CliError::new(
+ anyhow::format_err!(
+ "`cargo add` could not determine which package to modify. \
+ Use the `--package` option to specify a package. \n\
+ available packages: {}",
+ names.join(", ")
+ ),
+ 101,
+ ));
+ }
+ };
+
+ let dependencies = parse_dependencies(config, args)?;
+
+ let options = AddOptions {
+ config,
+ spec,
+ dependencies,
+ section,
+ dry_run,
+ };
+ add(&ws, &options)?;
+
+ if !dry_run {
+ // Reload the workspace since we've changed dependencies
+ let ws = args.workspace(config)?;
+ resolve_ws(&ws)?;
+ }
+
+ Ok(())
+}
+
+fn parse_dependencies(config: &Config, matches: &ArgMatches) -> CargoResult<Vec<DepOp>> {
+ let path = matches.get_one::<String>("path");
+ let git = matches.get_one::<String>("git");
+ let branch = matches.get_one::<String>("branch");
+ let rev = matches.get_one::<String>("rev");
+ let tag = matches.get_one::<String>("tag");
+ let rename = matches.get_one::<String>("rename");
+ let registry = match matches.registry(config)? {
+ Some(reg) if reg == CRATES_IO_REGISTRY => None,
+ reg => reg,
+ };
+ let default_features = default_features(matches);
+ let optional = optional(matches);
+
+ let mut crates = matches
+ .get_many::<String>("crates")
+ .into_iter()
+ .flatten()
+ .map(|c| (Some(c.clone()), None))
+ .collect::<IndexMap<_, _>>();
+ let mut infer_crate_name = false;
+ if crates.is_empty() {
+ if path.is_some() || git.is_some() {
+ crates.insert(None, None);
+ infer_crate_name = true;
+ } else {
+ unreachable!("clap should ensure we have some source selected");
+ }
+ }
+ for feature in matches
+ .get_many::<String>("features")
+ .into_iter()
+ .flatten()
+ .map(String::as_str)
+ .flat_map(parse_feature)
+ {
+ let parsed_value = FeatureValue::new(InternedString::new(feature));
+ match parsed_value {
+ FeatureValue::Feature(_) => {
+ if 1 < crates.len() {
+ let candidates = crates
+ .keys()
+ .map(|c| {
+ format!(
+ "`{}/{}`",
+ c.as_deref().expect("only none when there is 1"),
+ feature
+ )
+ })
+ .collect::<Vec<_>>();
+ anyhow::bail!("feature `{feature}` must be qualified by the dependency it's being activated for, like {}", candidates.join(", "));
+ }
+ crates
+ .first_mut()
+ .expect("always at least one crate")
+ .1
+ .get_or_insert_with(IndexSet::new)
+ .insert(feature.to_owned());
+ }
+ FeatureValue::Dep { .. } => {
+ anyhow::bail!("feature `{feature}` is not allowed to use explicit `dep:` syntax",)
+ }
+ FeatureValue::DepFeature {
+ dep_name,
+ dep_feature,
+ ..
+ } => {
+ if infer_crate_name {
+ anyhow::bail!("`{feature}` is unsupported when inferring the crate name, use `{dep_feature}`");
+ }
+ if dep_feature.contains('/') {
+ anyhow::bail!("multiple slashes in feature `{feature}` is not allowed");
+ }
+ crates.get_mut(&Some(dep_name.as_str().to_owned())).ok_or_else(|| {
+ anyhow::format_err!("feature `{dep_feature}` activated for crate `{dep_name}` but the crate wasn't specified")
+ })?
+ .get_or_insert_with(IndexSet::new)
+ .insert(dep_feature.as_str().to_owned());
+ }
+ }
+ }
+
+ let mut deps: Vec<DepOp> = Vec::new();
+ for (crate_spec, features) in crates {
+ let dep = DepOp {
+ crate_spec,
+ rename: rename.map(String::from),
+ features,
+ default_features,
+ optional,
+ registry: registry.clone(),
+ path: path.map(String::from),
+ git: git.map(String::from),
+ branch: branch.map(String::from),
+ rev: rev.map(String::from),
+ tag: tag.map(String::from),
+ };
+ deps.push(dep);
+ }
+
+ if deps.len() > 1 && rename.is_some() {
+ anyhow::bail!("cannot specify multiple crates with `--rename`");
+ }
+
+ Ok(deps)
+}
+
+fn default_features(matches: &ArgMatches) -> Option<bool> {
+ resolve_bool_arg(
+ matches.flag("default-features"),
+ matches.flag("no-default-features"),
+ )
+}
+
+fn optional(matches: &ArgMatches) -> Option<bool> {
+ resolve_bool_arg(matches.flag("optional"), matches.flag("no-optional"))
+}
+
+fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
+ match (yes, no) {
+ (true, false) => Some(true),
+ (false, true) => Some(false),
+ (false, false) => None,
+ (_, _) => unreachable!("clap should make this impossible"),
+ }
+}
+
+fn parse_section(matches: &ArgMatches) -> DepTable {
+ let kind = if matches.flag("dev") {
+ DepKind::Development
+ } else if matches.flag("build") {
+ DepKind::Build
+ } else {
+ DepKind::Normal
+ };
+
+ let mut table = DepTable::new().set_kind(kind);
+
+ if let Some(target) = matches.get_one::<String>("target") {
+ assert!(!target.is_empty(), "Target specification may not be empty");
+ table = table.set_target(target);
+ }
+
+ table
+}
+
+/// Split feature flag list
+fn parse_feature(feature: &str) -> impl Iterator<Item = &str> {
+ // Not re-using `CliFeatures` because it uses a BTreeSet and loses user's ordering
+ feature
+ .split_whitespace()
+ .flat_map(|s| s.split(','))
+ .filter(|s| !s.is_empty())
+}
diff --git a/src/bin/cargo/commands/bench.rs b/src/bin/cargo/commands/bench.rs
new file mode 100644
index 0000000..3739d88
--- /dev/null
+++ b/src/bin/cargo/commands/bench.rs
@@ -0,0 +1,77 @@
+use crate::command_prelude::*;
+use cargo::ops::{self, TestOptions};
+
+pub fn cli() -> Command {
+ subcommand("bench")
+ .about("Execute all benchmarks of a local package")
+ .arg_quiet()
+ .arg(
+ Arg::new("BENCHNAME")
+ .action(ArgAction::Set)
+ .help("If specified, only run benches containing this string in their names"),
+ )
+ .arg(
+ Arg::new("args")
+ .help("Arguments for the bench binary")
+ .num_args(0..)
+ .last(true),
+ )
+ .arg_targets_all(
+ "Benchmark only this package's library",
+ "Benchmark only the specified binary",
+ "Benchmark all binaries",
+ "Benchmark only the specified example",
+ "Benchmark all examples",
+ "Benchmark only the specified test target",
+ "Benchmark all tests",
+ "Benchmark only the specified bench target",
+ "Benchmark all benches",
+ "Benchmark all targets",
+ )
+ .arg(flag("no-run", "Compile, but don't run benchmarks"))
+ .arg_package_spec(
+ "Package to run benchmarks for",
+ "Benchmark all packages in the workspace",
+ "Exclude packages from the benchmark",
+ )
+ .arg_jobs()
+ .arg_profile("Build artifacts with the specified profile")
+ .arg_features()
+ .arg_target_triple("Build for the target triple")
+ .arg_target_dir()
+ .arg_manifest_path()
+ .arg_ignore_rust_version()
+ .arg_message_format()
+ .arg(flag(
+ "no-fail-fast",
+ "Run all benchmarks regardless of failure",
+ ))
+ .arg_unit_graph()
+ .arg_timings()
+ .after_help("Run `cargo help bench` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ let mut compile_opts = args.compile_options(
+ config,
+ CompileMode::Bench,
+ Some(&ws),
+ ProfileChecking::Custom,
+ )?;
+
+ compile_opts.build_config.requested_profile =
+ args.get_profile_name(config, "bench", ProfileChecking::Custom)?;
+
+ let ops = TestOptions {
+ no_run: args.flag("no-run"),
+ no_fail_fast: args.flag("no-fail-fast"),
+ compile_opts,
+ };
+
+ let bench_args = args.get_one::<String>("BENCHNAME").into_iter();
+ let bench_args = bench_args.chain(args.get_many::<String>("args").unwrap_or_default());
+ let bench_args = bench_args.map(String::as_str).collect::<Vec<_>>();
+
+ ops::run_benches(&ws, &ops, &bench_args)
+}
diff --git a/src/bin/cargo/commands/build.rs b/src/bin/cargo/commands/build.rs
new file mode 100644
index 0000000..a78da38
--- /dev/null
+++ b/src/bin/cargo/commands/build.rs
@@ -0,0 +1,73 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("build")
+ // subcommand aliases are handled in aliased_command()
+ // .alias("b")
+ .about("Compile a local package and all of its dependencies")
+ .arg_quiet()
+ .arg_package_spec(
+ "Package to build (see `cargo help pkgid`)",
+ "Build all packages in the workspace",
+ "Exclude packages from the build",
+ )
+ .arg_jobs()
+ .arg_targets_all(
+ "Build only this package's library",
+ "Build only the specified binary",
+ "Build all binaries",
+ "Build only the specified example",
+ "Build all examples",
+ "Build only the specified test target",
+ "Build all tests",
+ "Build only the specified bench target",
+ "Build all benches",
+ "Build all targets",
+ )
+ .arg_release("Build artifacts in release mode, with optimizations")
+ .arg_profile("Build artifacts with the specified profile")
+ .arg_features()
+ .arg_target_triple("Build for the target triple")
+ .arg_target_dir()
+ .arg(
+ opt(
+ "out-dir",
+ "Copy final artifacts to this directory (unstable)",
+ )
+ .value_name("PATH"),
+ )
+ .arg_manifest_path()
+ .arg_ignore_rust_version()
+ .arg_message_format()
+ .arg_build_plan()
+ .arg_unit_graph()
+ .arg_future_incompat_report()
+ .arg_timings()
+ .after_help("Run `cargo help build` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ let mut compile_opts = args.compile_options(
+ config,
+ CompileMode::Build,
+ Some(&ws),
+ ProfileChecking::Custom,
+ )?;
+
+ if let Some(out_dir) = args.value_of_path("out-dir", config) {
+ compile_opts.build_config.export_dir = Some(out_dir);
+ } else if let Some(out_dir) = config.build_config()?.out_dir.as_ref() {
+ let out_dir = out_dir.resolve_path(config);
+ compile_opts.build_config.export_dir = Some(out_dir);
+ }
+ if compile_opts.build_config.export_dir.is_some() {
+ config
+ .cli_unstable()
+ .fail_if_stable_opt("--out-dir", 6790)?;
+ }
+ ops::compile(&ws, &compile_opts)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/check.rs b/src/bin/cargo/commands/check.rs
new file mode 100644
index 0000000..c9f6e0b
--- /dev/null
+++ b/src/bin/cargo/commands/check.rs
@@ -0,0 +1,56 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("check")
+ // subcommand aliases are handled in aliased_command()
+ // .alias("c")
+ .about("Check a local package and all of its dependencies for errors")
+ .arg_quiet()
+ .arg_package_spec(
+ "Package(s) to check",
+ "Check all packages in the workspace",
+ "Exclude packages from the check",
+ )
+ .arg_jobs()
+ .arg_targets_all(
+ "Check only this package's library",
+ "Check only the specified binary",
+ "Check all binaries",
+ "Check only the specified example",
+ "Check all examples",
+ "Check only the specified test target",
+ "Check all tests",
+ "Check only the specified bench target",
+ "Check all benches",
+ "Check all targets",
+ )
+ .arg_release("Check artifacts in release mode, with optimizations")
+ .arg_profile("Check artifacts with the specified profile")
+ .arg_features()
+ .arg_target_triple("Check for the target triple")
+ .arg_target_dir()
+ .arg_manifest_path()
+ .arg_ignore_rust_version()
+ .arg_message_format()
+ .arg_unit_graph()
+ .arg_future_incompat_report()
+ .arg_timings()
+ .after_help("Run `cargo help check` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ // This is a legacy behavior that causes `cargo check` to pass `--test`.
+ let test = matches!(
+ args.get_one::<String>("profile").map(String::as_str),
+ Some("test")
+ );
+ let mode = CompileMode::Check { test };
+ let compile_opts =
+ args.compile_options(config, mode, Some(&ws), ProfileChecking::LegacyTestOnly)?;
+
+ ops::compile(&ws, &compile_opts)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/clean.rs b/src/bin/cargo/commands/clean.rs
new file mode 100644
index 0000000..162461c
--- /dev/null
+++ b/src/bin/cargo/commands/clean.rs
@@ -0,0 +1,37 @@
+use crate::command_prelude::*;
+
+use cargo::ops::{self, CleanOptions};
+use cargo::util::print_available_packages;
+
+pub fn cli() -> Command {
+ subcommand("clean")
+ .about("Remove artifacts that cargo has generated in the past")
+ .arg_quiet()
+ .arg_package_spec_simple("Package to clean artifacts for")
+ .arg_manifest_path()
+ .arg_target_triple("Target triple to clean output for")
+ .arg_target_dir()
+ .arg_release("Whether or not to clean release artifacts")
+ .arg_profile("Clean artifacts of the specified profile")
+ .arg_doc("Whether or not to clean just the documentation directory")
+ .after_help("Run `cargo help clean` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+
+ if args.is_present_with_zero_values("package") {
+ print_available_packages(&ws)?;
+ }
+
+ let opts = CleanOptions {
+ config,
+ spec: values(args, "package"),
+ targets: args.targets(),
+ requested_profile: args.get_profile_name(config, "dev", ProfileChecking::Custom)?,
+ profile_specified: args.contains_id("profile") || args.flag("release"),
+ doc: args.flag("doc"),
+ };
+ ops::clean(&ws, &opts)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/config.rs b/src/bin/cargo/commands/config.rs
new file mode 100644
index 0000000..84c5e92
--- /dev/null
+++ b/src/bin/cargo/commands/config.rs
@@ -0,0 +1,55 @@
+use crate::command_prelude::*;
+use cargo::ops::cargo_config;
+
+pub fn cli() -> Command {
+ subcommand("config")
+ .about("Inspect configuration values")
+ .subcommand_required(true)
+ .arg_required_else_help(true)
+ .subcommand(
+ subcommand("get")
+ .arg(
+ Arg::new("key")
+ .action(ArgAction::Set)
+ .help("The config key to display"),
+ )
+ .arg(
+ opt("format", "Display format")
+ .value_parser(cargo_config::ConfigFormat::POSSIBLE_VALUES)
+ .default_value("toml"),
+ )
+ .arg(flag(
+ "show-origin",
+ "Display where the config value is defined",
+ ))
+ .arg(
+ opt("merged", "Whether or not to merge config values")
+ .value_parser(["yes", "no"])
+ .default_value("yes"),
+ ),
+ )
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ config
+ .cli_unstable()
+ .fail_if_stable_command(config, "config", 9301)?;
+ match args.subcommand() {
+ Some(("get", args)) => {
+ let opts = cargo_config::GetOptions {
+ key: args.get_one::<String>("key").map(String::as_str),
+ format: args.get_one::<String>("format").unwrap().parse()?,
+ show_origin: args.flag("show-origin"),
+ merged: args.get_one::<String>("merged").map(String::as_str) == Some("yes"),
+ };
+ cargo_config::get(config, &opts)?;
+ }
+ Some((cmd, _)) => {
+ unreachable!("unexpected command {}", cmd)
+ }
+ None => {
+ unreachable!("unexpected command")
+ }
+ }
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/doc.rs b/src/bin/cargo/commands/doc.rs
new file mode 100644
index 0000000..932058a
--- /dev/null
+++ b/src/bin/cargo/commands/doc.rs
@@ -0,0 +1,61 @@
+use crate::command_prelude::*;
+
+use cargo::ops::{self, DocOptions};
+
+pub fn cli() -> Command {
+ subcommand("doc")
+ // subcommand aliases are handled in aliased_command()
+ // .alias("d")
+ .about("Build a package's documentation")
+ .arg_quiet()
+ .arg(flag(
+ "open",
+ "Opens the docs in a browser after the operation",
+ ))
+ .arg_package_spec(
+ "Package to document",
+ "Document all packages in the workspace",
+ "Exclude packages from the build",
+ )
+ .arg(flag(
+ "no-deps",
+ "Don't build documentation for dependencies",
+ ))
+ .arg(flag("document-private-items", "Document private items"))
+ .arg_jobs()
+ .arg_targets_lib_bin_example(
+ "Document only this package's library",
+ "Document only the specified binary",
+ "Document all binaries",
+ "Document only the specified example",
+ "Document all examples",
+ )
+ .arg_release("Build artifacts in release mode, with optimizations")
+ .arg_profile("Build artifacts with the specified profile")
+ .arg_features()
+ .arg_target_triple("Build for the target triple")
+ .arg_target_dir()
+ .arg_manifest_path()
+ .arg_message_format()
+ .arg_ignore_rust_version()
+ .arg_unit_graph()
+ .arg_timings()
+ .after_help("Run `cargo help doc` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ let mode = CompileMode::Doc {
+ deps: !args.flag("no-deps"),
+ };
+ let mut compile_opts =
+ args.compile_options(config, mode, Some(&ws), ProfileChecking::Custom)?;
+ compile_opts.rustdoc_document_private_items = args.flag("document-private-items");
+
+ let doc_opts = DocOptions {
+ open_result: args.flag("open"),
+ compile_opts,
+ };
+ ops::doc(&ws, &doc_opts)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/fetch.rs b/src/bin/cargo/commands/fetch.rs
new file mode 100644
index 0000000..2fbbc47
--- /dev/null
+++ b/src/bin/cargo/commands/fetch.rs
@@ -0,0 +1,24 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+use cargo::ops::FetchOptions;
+
+pub fn cli() -> Command {
+ subcommand("fetch")
+ .about("Fetch dependencies of a package from the network")
+ .arg_quiet()
+ .arg_manifest_path()
+ .arg_target_triple("Fetch dependencies for the target triple")
+ .after_help("Run `cargo help fetch` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+
+ let opts = FetchOptions {
+ config,
+ targets: args.targets(),
+ };
+ let _ = ops::fetch(&ws, &opts)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/fix.rs b/src/bin/cargo/commands/fix.rs
new file mode 100644
index 0000000..5238d58
--- /dev/null
+++ b/src/bin/cargo/commands/fix.rs
@@ -0,0 +1,92 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("fix")
+ .about("Automatically fix lint warnings reported by rustc")
+ .arg_quiet()
+ .arg_package_spec(
+ "Package(s) to fix",
+ "Fix all packages in the workspace",
+ "Exclude packages from the fixes",
+ )
+ .arg_jobs()
+ .arg_targets_all(
+ "Fix only this package's library",
+ "Fix only the specified binary",
+ "Fix all binaries",
+ "Fix only the specified example",
+ "Fix all examples",
+ "Fix only the specified test target",
+ "Fix all tests",
+ "Fix only the specified bench target",
+ "Fix all benches",
+ "Fix all targets (default)",
+ )
+ .arg_release("Fix artifacts in release mode, with optimizations")
+ .arg_profile("Build artifacts with the specified profile")
+ .arg_features()
+ .arg_target_triple("Fix for the target triple")
+ .arg_target_dir()
+ .arg_manifest_path()
+ .arg_message_format()
+ .arg(flag(
+ "broken-code",
+ "Fix code even if it already has compiler errors",
+ ))
+ .arg(flag("edition", "Fix in preparation for the next edition"))
+ .arg(flag(
+ "edition-idioms",
+ "Fix warnings to migrate to the idioms of an edition",
+ ))
+ .arg(flag(
+ "allow-no-vcs",
+ "Fix code even if a VCS was not detected",
+ ))
+ .arg(flag(
+ "allow-dirty",
+ "Fix code even if the working directory is dirty",
+ ))
+ .arg(flag(
+ "allow-staged",
+ "Fix code even if the working directory has staged changes",
+ ))
+ .arg_ignore_rust_version()
+ .arg_timings()
+ .after_help("Run `cargo help fix` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ // This is a legacy behavior that causes `cargo fix` to pass `--test`.
+ let test = matches!(
+ args.get_one::<String>("profile").map(String::as_str),
+ Some("test")
+ );
+ let mode = CompileMode::Check { test };
+
+ // Unlike other commands default `cargo fix` to all targets to fix as much
+ // code as we can.
+ let mut opts =
+ args.compile_options(config, mode, Some(&ws), ProfileChecking::LegacyTestOnly)?;
+
+ if !opts.filter.is_specific() {
+ // cargo fix with no target selection implies `--all-targets`.
+ opts.filter = ops::CompileFilter::new_all_targets();
+ }
+
+ ops::fix(
+ &ws,
+ &mut ops::FixOptions {
+ edition: args.flag("edition"),
+ idioms: args.flag("edition-idioms"),
+ compile_opts: opts,
+ allow_dirty: args.flag("allow-dirty"),
+ allow_no_vcs: args.flag("allow-no-vcs"),
+ allow_staged: args.flag("allow-staged"),
+ broken_code: args.flag("broken-code"),
+ },
+ )?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/generate_lockfile.rs b/src/bin/cargo/commands/generate_lockfile.rs
new file mode 100644
index 0000000..7d06aad
--- /dev/null
+++ b/src/bin/cargo/commands/generate_lockfile.rs
@@ -0,0 +1,17 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("generate-lockfile")
+ .about("Generate the lockfile for a package")
+ .arg_quiet()
+ .arg_manifest_path()
+ .after_help("Run `cargo help generate-lockfile` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ ops::generate_lockfile(&ws)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/git_checkout.rs b/src/bin/cargo/commands/git_checkout.rs
new file mode 100644
index 0000000..90be9bc
--- /dev/null
+++ b/src/bin/cargo/commands/git_checkout.rs
@@ -0,0 +1,14 @@
+use crate::command_prelude::*;
+
+const REMOVED: &str = "The `git-checkout` command has been removed.";
+
+pub fn cli() -> Command {
+ subcommand("git-checkout")
+ .about("This command has been removed")
+ .hide(true)
+ .override_help(REMOVED)
+}
+
+pub fn exec(_config: &mut Config, _args: &ArgMatches) -> CliResult {
+ Err(anyhow::format_err!(REMOVED).into())
+}
diff --git a/src/bin/cargo/commands/help.rs b/src/bin/cargo/commands/help.rs
new file mode 100644
index 0000000..2839b93
--- /dev/null
+++ b/src/bin/cargo/commands/help.rs
@@ -0,0 +1,147 @@
+use crate::aliased_command;
+use crate::command_prelude::*;
+use cargo::util::errors::CargoResult;
+use cargo::{drop_println, Config};
+use cargo_util::paths::resolve_executable;
+use flate2::read::GzDecoder;
+use std::ffi::OsStr;
+use std::ffi::OsString;
+use std::io::Read;
+use std::io::Write;
+use std::path::Path;
+
+const COMPRESSED_MAN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/man.tgz"));
+
+pub fn cli() -> Command {
+ subcommand("help")
+ .about("Displays help for a cargo subcommand")
+ .arg(Arg::new("COMMAND").action(ArgAction::Set))
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let subcommand = args.get_one::<String>("COMMAND");
+ if let Some(subcommand) = subcommand {
+ if !try_help(config, subcommand)? {
+ match check_builtin(&subcommand) {
+ Some(s) => {
+ crate::execute_internal_subcommand(
+ config,
+ &[OsStr::new(s), OsStr::new("--help")],
+ )?;
+ }
+ None => {
+ crate::execute_external_subcommand(
+ config,
+ subcommand,
+ &[OsStr::new(subcommand), OsStr::new("--help")],
+ )?;
+ }
+ }
+ }
+ } else {
+ let mut cmd = crate::cli::cli();
+ let _ = cmd.print_help();
+ }
+ Ok(())
+}
+
+fn try_help(config: &Config, subcommand: &str) -> CargoResult<bool> {
+ let subcommand = match check_alias(config, subcommand) {
+ // If this alias is more than a simple subcommand pass-through, show the alias.
+ Some(argv) if argv.len() > 1 => {
+ let alias = argv.join(" ");
+ drop_println!(config, "`{}` is aliased to `{}`", subcommand, alias);
+ return Ok(true);
+ }
+ // Otherwise, resolve the alias into its subcommand.
+ Some(argv) => {
+ // An alias with an empty argv can be created via `"empty-alias" = ""`.
+ let first = argv.get(0).map(String::as_str).unwrap_or(subcommand);
+ first.to_string()
+ }
+ None => subcommand.to_string(),
+ };
+
+ let subcommand = match check_builtin(&subcommand) {
+ Some(s) => s,
+ None => return Ok(false),
+ };
+
+ if resolve_executable(Path::new("man")).is_ok() {
+ let man = match extract_man(subcommand, "1") {
+ Some(man) => man,
+ None => return Ok(false),
+ };
+ write_and_spawn(subcommand, &man, "man")?;
+ } else {
+ let txt = match extract_man(subcommand, "txt") {
+ Some(txt) => txt,
+ None => return Ok(false),
+ };
+ if resolve_executable(Path::new("less")).is_ok() {
+ write_and_spawn(subcommand, &txt, "less")?;
+ } else if resolve_executable(Path::new("more")).is_ok() {
+ write_and_spawn(subcommand, &txt, "more")?;
+ } else {
+ drop(std::io::stdout().write_all(&txt));
+ }
+ }
+ Ok(true)
+}
+
+/// Checks if the given subcommand is an alias.
+///
+/// Returns None if it is not an alias.
+fn check_alias(config: &Config, subcommand: &str) -> Option<Vec<String>> {
+ aliased_command(config, subcommand).ok().flatten()
+}
+
+/// Checks if the given subcommand is a built-in command (not via an alias).
+///
+/// Returns None if it is not a built-in command.
+fn check_builtin(subcommand: &str) -> Option<&str> {
+ super::builtin_exec(subcommand).map(|_| subcommand)
+}
+
+/// Extracts the given man page from the compressed archive.
+///
+/// Returns None if the command wasn't found.
+fn extract_man(subcommand: &str, extension: &str) -> Option<Vec<u8>> {
+ let extract_name = OsString::from(format!("cargo-{}.{}", subcommand, extension));
+ let gz = GzDecoder::new(COMPRESSED_MAN);
+ let mut ar = tar::Archive::new(gz);
+ // Unwraps should be safe here, since this is a static archive generated
+ // by our build script. It should never be an invalid format!
+ for entry in ar.entries().unwrap() {
+ let mut entry = entry.unwrap();
+ let path = entry.path().unwrap();
+ if path.file_name().unwrap() != extract_name {
+ continue;
+ }
+ let mut result = Vec::new();
+ entry.read_to_end(&mut result).unwrap();
+ return Some(result);
+ }
+ None
+}
+
+/// Write the contents of a man page to disk and spawn the given command to
+/// display it.
+fn write_and_spawn(name: &str, contents: &[u8], command: &str) -> CargoResult<()> {
+ let prefix = format!("cargo-{}.", name);
+ let mut tmp = tempfile::Builder::new().prefix(&prefix).tempfile()?;
+ let f = tmp.as_file_mut();
+ f.write_all(contents)?;
+ f.flush()?;
+ let path = tmp.path();
+ // Use a path relative to the temp directory so that it can work on
+ // cygwin/msys systems which don't handle windows-style paths.
+ let mut relative_name = std::ffi::OsString::from("./");
+ relative_name.push(path.file_name().unwrap());
+ let mut cmd = std::process::Command::new(command)
+ .arg(relative_name)
+ .current_dir(path.parent().unwrap())
+ .spawn()?;
+ drop(cmd.wait());
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/init.rs b/src/bin/cargo/commands/init.rs
new file mode 100644
index 0000000..b280d4f
--- /dev/null
+++ b/src/bin/cargo/commands/init.rs
@@ -0,0 +1,22 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("init")
+ .about("Create a new cargo package in an existing directory")
+ .arg_quiet()
+ .arg(Arg::new("path").action(ArgAction::Set).default_value("."))
+ .arg(opt("registry", "Registry to use").value_name("REGISTRY"))
+ .arg_new_opts()
+ .after_help("Run `cargo help init` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let opts = args.new_options(config)?;
+ let project_kind = ops::init(&opts, config)?;
+ config
+ .shell()
+ .status("Created", format!("{} package", project_kind))?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/install.rs b/src/bin/cargo/commands/install.rs
new file mode 100644
index 0000000..790bfd2
--- /dev/null
+++ b/src/bin/cargo/commands/install.rs
@@ -0,0 +1,197 @@
+use crate::command_prelude::*;
+
+use cargo::core::{GitReference, SourceId, Workspace};
+use cargo::ops;
+use cargo::util::IntoUrl;
+
+use cargo_util::paths;
+
+pub fn cli() -> Command {
+ subcommand("install")
+ .about("Install a Rust binary. Default location is $HOME/.cargo/bin")
+ .arg_quiet()
+ .arg(
+ Arg::new("crate")
+ .value_parser(clap::builder::NonEmptyStringValueParser::new())
+ .num_args(0..),
+ )
+ .arg(
+ opt("version", "Specify a version to install")
+ .alias("vers")
+ .value_name("VERSION")
+ .requires("crate"),
+ )
+ .arg(
+ opt("git", "Git URL to install the specified crate from")
+ .value_name("URL")
+ .conflicts_with_all(&["path", "index", "registry"]),
+ )
+ .arg(
+ opt("branch", "Branch to use when installing from git")
+ .value_name("BRANCH")
+ .requires("git"),
+ )
+ .arg(
+ opt("tag", "Tag to use when installing from git")
+ .value_name("TAG")
+ .requires("git"),
+ )
+ .arg(
+ opt("rev", "Specific commit to use when installing from git")
+ .value_name("SHA")
+ .requires("git"),
+ )
+ .arg(
+ opt("path", "Filesystem path to local crate to install")
+ .value_name("PATH")
+ .conflicts_with_all(&["git", "index", "registry"]),
+ )
+ .arg(flag(
+ "list",
+ "list all installed packages and their versions",
+ ))
+ .arg_jobs()
+ .arg(flag("force", "Force overwriting existing crates or binaries").short('f'))
+ .arg(flag("no-track", "Do not save tracking information"))
+ .arg_features()
+ .arg_profile("Install artifacts with the specified profile")
+ .arg(flag(
+ "debug",
+ "Build in debug mode (with the 'dev' profile) instead of release mode",
+ ))
+ .arg_targets_bins_examples(
+ "Install only the specified binary",
+ "Install all binaries",
+ "Install only the specified example",
+ "Install all examples",
+ )
+ .arg_target_triple("Build for the target triple")
+ .arg_target_dir()
+ .arg(opt("root", "Directory to install packages into").value_name("DIR"))
+ .arg(
+ opt("index", "Registry index to install from")
+ .value_name("INDEX")
+ .requires("crate")
+ .conflicts_with_all(&["git", "path", "registry"]),
+ )
+ .arg(
+ opt("registry", "Registry to use")
+ .value_name("REGISTRY")
+ .requires("crate")
+ .conflicts_with_all(&["git", "path", "index"]),
+ )
+ .arg_message_format()
+ .arg_timings()
+ .after_help("Run `cargo help install` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let path = args.value_of_path("path", config);
+ if let Some(path) = &path {
+ config.reload_rooted_at(path)?;
+ } else {
+ // TODO: Consider calling set_search_stop_path(home).
+ config.reload_rooted_at(config.home().clone().into_path_unlocked())?;
+ }
+
+ // In general, we try to avoid normalizing paths in Cargo,
+ // but in these particular cases we need it to fix rust-lang/cargo#10283.
+ // (Handle `SourceId::for_path` and `Workspace::new`,
+ // but not `Config::reload_rooted_at` which is always cwd)
+ let path = path.map(|p| paths::normalize_path(&p));
+
+ let version = args.get_one::<String>("version").map(String::as_str);
+ let krates = args
+ .get_many::<String>("crate")
+ .unwrap_or_default()
+ .map(|k| resolve_crate(k, version))
+ .collect::<crate::CargoResult<Vec<_>>>()?;
+
+ let mut from_cwd = false;
+
+ let source = if let Some(url) = args.get_one::<String>("git") {
+ let url = url.into_url()?;
+ let gitref = if let Some(branch) = args.get_one::<String>("branch") {
+ GitReference::Branch(branch.clone())
+ } else if let Some(tag) = args.get_one::<String>("tag") {
+ GitReference::Tag(tag.clone())
+ } else if let Some(rev) = args.get_one::<String>("rev") {
+ GitReference::Rev(rev.clone())
+ } else {
+ GitReference::DefaultBranch
+ };
+ SourceId::for_git(&url, gitref)?
+ } else if let Some(path) = &path {
+ SourceId::for_path(path)?
+ } else if krates.is_empty() {
+ from_cwd = true;
+ SourceId::for_path(config.cwd())?
+ } else if let Some(index) = args.get_one::<String>("index") {
+ SourceId::for_registry(&index.into_url()?)?
+ } else if let Some(registry) = args.registry(config)? {
+ SourceId::alt_registry(config, &registry)?
+ } else {
+ SourceId::crates_io(config)?
+ };
+
+ let root = args.get_one::<String>("root").map(String::as_str);
+
+ // We only provide workspace information for local crate installation from
+ // one of the following sources:
+ // - From current working directory (only work for edition 2015).
+ // - From a specific local file path (from `--path` arg).
+ //
+ // This workspace information is for emitting helpful messages from
+ // `ArgMatchesExt::compile_options` and won't affect the actual compilation.
+ let workspace = if from_cwd {
+ args.workspace(config).ok()
+ } else if let Some(path) = &path {
+ Workspace::new(&path.join("Cargo.toml"), config).ok()
+ } else {
+ None
+ };
+
+ let mut compile_opts = args.compile_options(
+ config,
+ CompileMode::Build,
+ workspace.as_ref(),
+ ProfileChecking::Custom,
+ )?;
+
+ compile_opts.build_config.requested_profile =
+ args.get_profile_name(config, "release", ProfileChecking::Custom)?;
+
+ if args.flag("list") {
+ ops::install_list(root, config)?;
+ } else {
+ ops::install(
+ config,
+ root,
+ krates,
+ source,
+ from_cwd,
+ &compile_opts,
+ args.flag("force"),
+ args.flag("no-track"),
+ )?;
+ }
+ Ok(())
+}
+
+fn resolve_crate<'k>(
+ mut krate: &'k str,
+ mut version: Option<&'k str>,
+) -> crate::CargoResult<(&'k str, Option<&'k str>)> {
+ if let Some((k, v)) = krate.split_once('@') {
+ if version.is_some() {
+ anyhow::bail!("cannot specify both `@{v}` and `--version`");
+ }
+ if k.is_empty() {
+ // by convention, arguments starting with `@` are response files
+ anyhow::bail!("missing crate name for `@{v}`");
+ }
+ krate = k;
+ version = Some(v);
+ }
+ Ok((krate, version))
+}
diff --git a/src/bin/cargo/commands/locate_project.rs b/src/bin/cargo/commands/locate_project.rs
new file mode 100644
index 0000000..26c35cd
--- /dev/null
+++ b/src/bin/cargo/commands/locate_project.rs
@@ -0,0 +1,93 @@
+use crate::command_prelude::*;
+use anyhow::bail;
+use cargo::{drop_println, CargoResult};
+use serde::Serialize;
+
+pub fn cli() -> Command {
+ subcommand("locate-project")
+ .about("Print a JSON representation of a Cargo.toml file's location")
+ .arg_quiet()
+ .arg_manifest_path()
+ .arg(
+ opt(
+ "message-format",
+ "Output representation [possible values: json, plain]",
+ )
+ .value_name("FMT"),
+ )
+ .arg(flag("workspace", "Locate Cargo.toml of the workspace root"))
+ .after_help("Run `cargo help locate-project` for more detailed information.\n")
+}
+
+#[derive(Serialize)]
+pub struct ProjectLocation<'a> {
+ root: &'a str,
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let root_manifest;
+ let workspace;
+ let root = match WhatToFind::parse(args) {
+ WhatToFind::CurrentManifest => {
+ root_manifest = args.root_manifest(config)?;
+ &root_manifest
+ }
+ WhatToFind::Workspace => {
+ workspace = args.workspace(config)?;
+ workspace.root_manifest()
+ }
+ };
+
+ let root = root
+ .to_str()
+ .ok_or_else(|| {
+ anyhow::format_err!(
+ "your package path contains characters \
+ not representable in Unicode"
+ )
+ })
+ .map_err(|e| CliError::new(e, 1))?;
+
+ let location = ProjectLocation { root };
+
+ match MessageFormat::parse(args)? {
+ MessageFormat::Json => config.shell().print_json(&location)?,
+ MessageFormat::Plain => drop_println!(config, "{}", location.root),
+ }
+
+ Ok(())
+}
+
+enum WhatToFind {
+ CurrentManifest,
+ Workspace,
+}
+
+impl WhatToFind {
+ fn parse(args: &ArgMatches) -> Self {
+ if args.flag("workspace") {
+ WhatToFind::Workspace
+ } else {
+ WhatToFind::CurrentManifest
+ }
+ }
+}
+
+enum MessageFormat {
+ Json,
+ Plain,
+}
+
+impl MessageFormat {
+ fn parse(args: &ArgMatches) -> CargoResult<Self> {
+ let fmt = match args.get_one::<String>("message-format") {
+ Some(fmt) => fmt,
+ None => return Ok(MessageFormat::Json),
+ };
+ match fmt.to_ascii_lowercase().as_str() {
+ "json" => Ok(MessageFormat::Json),
+ "plain" => Ok(MessageFormat::Plain),
+ s => bail!("invalid message format specifier: `{}`", s),
+ }
+ }
+}
diff --git a/src/bin/cargo/commands/login.rs b/src/bin/cargo/commands/login.rs
new file mode 100644
index 0000000..dac0457
--- /dev/null
+++ b/src/bin/cargo/commands/login.rs
@@ -0,0 +1,46 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("login")
+ .about(
+ "Save an api token from the registry locally. \
+ If token is not specified, it will be read from stdin.",
+ )
+ .arg_quiet()
+ .arg(Arg::new("token").action(ArgAction::Set))
+ .arg(opt("registry", "Registry to use").value_name("REGISTRY"))
+ .arg(
+ flag(
+ "generate-keypair",
+ "Generate a public/secret keypair (unstable)",
+ )
+ .conflicts_with("token"),
+ )
+ .arg(
+ flag("secret-key", "Prompt for secret key (unstable)")
+ .conflicts_with_all(&["generate-keypair", "token"]),
+ )
+ .arg(
+ opt(
+ "key-subject",
+ "Set the key subject for this registry (unstable)",
+ )
+ .value_name("SUBJECT")
+ .conflicts_with("token"),
+ )
+ .after_help("Run `cargo help login` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ ops::registry_login(
+ config,
+ args.get_one::<String>("token").map(|s| s.as_str().into()),
+ args.get_one("registry").map(String::as_str),
+ args.flag("generate-keypair"),
+ args.flag("secret-key"),
+ args.get_one("key-subject").map(String::as_str),
+ )?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/logout.rs b/src/bin/cargo/commands/logout.rs
new file mode 100644
index 0000000..bc16ee5
--- /dev/null
+++ b/src/bin/cargo/commands/logout.rs
@@ -0,0 +1,23 @@
+use crate::command_prelude::*;
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("logout")
+ .about("Remove an API token from the registry locally")
+ .arg_quiet()
+ .arg(opt("registry", "Registry to use").value_name("REGISTRY"))
+ .after_help("Run `cargo help logout` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ if !config.cli_unstable().credential_process {
+ config
+ .cli_unstable()
+ .fail_if_stable_command(config, "logout", 8933)?;
+ }
+ ops::registry_logout(
+ config,
+ args.get_one::<String>("registry").map(String::as_str),
+ )?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/metadata.rs b/src/bin/cargo/commands/metadata.rs
new file mode 100644
index 0000000..fdf5965
--- /dev/null
+++ b/src/bin/cargo/commands/metadata.rs
@@ -0,0 +1,56 @@
+use crate::command_prelude::*;
+use cargo::ops::{self, OutputMetadataOptions};
+
+pub fn cli() -> Command {
+ subcommand("metadata")
+ .about(
+ "Output the resolved dependencies of a package, \
+ the concrete used versions including overrides, \
+ in machine-readable format",
+ )
+ .arg_quiet()
+ .arg_features()
+ .arg(multi_opt(
+ "filter-platform",
+ "TRIPLE",
+ "Only include resolve dependencies matching the given target-triple",
+ ))
+ .arg(flag(
+ "no-deps",
+ "Output information only about the workspace members \
+ and don't fetch dependencies",
+ ))
+ .arg_manifest_path()
+ .arg(
+ opt("format-version", "Format version")
+ .value_name("VERSION")
+ .value_parser(["1"]),
+ )
+ .after_help("Run `cargo help metadata` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+
+ let version = match args.get_one::<String>("format-version") {
+ None => {
+ config.shell().warn(
+ "please specify `--format-version` flag explicitly \
+ to avoid compatibility problems",
+ )?;
+ 1
+ }
+ Some(version) => version.parse().unwrap(),
+ };
+
+ let options = OutputMetadataOptions {
+ cli_features: args.cli_features()?,
+ no_deps: args.flag("no-deps"),
+ filter_platforms: args._values_of("filter-platform"),
+ version,
+ };
+
+ let result = ops::output_metadata(&ws, &options)?;
+ config.shell().print_json(&result)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/mod.rs b/src/bin/cargo/commands/mod.rs
new file mode 100644
index 0000000..da31092
--- /dev/null
+++ b/src/bin/cargo/commands/mod.rs
@@ -0,0 +1,128 @@
+use crate::command_prelude::*;
+
+pub fn builtin() -> Vec<Command> {
+ vec![
+ add::cli(),
+ bench::cli(),
+ build::cli(),
+ check::cli(),
+ clean::cli(),
+ config::cli(),
+ doc::cli(),
+ fetch::cli(),
+ fix::cli(),
+ generate_lockfile::cli(),
+ git_checkout::cli(),
+ help::cli(),
+ init::cli(),
+ install::cli(),
+ locate_project::cli(),
+ login::cli(),
+ logout::cli(),
+ metadata::cli(),
+ new::cli(),
+ owner::cli(),
+ package::cli(),
+ pkgid::cli(),
+ publish::cli(),
+ read_manifest::cli(),
+ remove::cli(),
+ report::cli(),
+ run::cli(),
+ rustc::cli(),
+ rustdoc::cli(),
+ search::cli(),
+ test::cli(),
+ tree::cli(),
+ uninstall::cli(),
+ update::cli(),
+ vendor::cli(),
+ verify_project::cli(),
+ version::cli(),
+ yank::cli(),
+ ]
+}
+
+pub fn builtin_exec(cmd: &str) -> Option<fn(&mut Config, &ArgMatches) -> CliResult> {
+ let f = match cmd {
+ "add" => add::exec,
+ "bench" => bench::exec,
+ "build" => build::exec,
+ "check" => check::exec,
+ "clean" => clean::exec,
+ "config" => config::exec,
+ "doc" => doc::exec,
+ "fetch" => fetch::exec,
+ "fix" => fix::exec,
+ "generate-lockfile" => generate_lockfile::exec,
+ "git-checkout" => git_checkout::exec,
+ "help" => help::exec,
+ "init" => init::exec,
+ "install" => install::exec,
+ "locate-project" => locate_project::exec,
+ "login" => login::exec,
+ "logout" => logout::exec,
+ "metadata" => metadata::exec,
+ "new" => new::exec,
+ "owner" => owner::exec,
+ "package" => package::exec,
+ "pkgid" => pkgid::exec,
+ "publish" => publish::exec,
+ "read-manifest" => read_manifest::exec,
+ "remove" => remove::exec,
+ "report" => report::exec,
+ "run" => run::exec,
+ "rustc" => rustc::exec,
+ "rustdoc" => rustdoc::exec,
+ "search" => search::exec,
+ "test" => test::exec,
+ "tree" => tree::exec,
+ "uninstall" => uninstall::exec,
+ "update" => update::exec,
+ "vendor" => vendor::exec,
+ "verify-project" => verify_project::exec,
+ "version" => version::exec,
+ "yank" => yank::exec,
+ _ => return None,
+ };
+ Some(f)
+}
+
+pub mod add;
+pub mod bench;
+pub mod build;
+pub mod check;
+pub mod clean;
+pub mod config;
+pub mod doc;
+pub mod fetch;
+pub mod fix;
+pub mod generate_lockfile;
+pub mod git_checkout;
+pub mod help;
+pub mod init;
+pub mod install;
+pub mod locate_project;
+pub mod login;
+pub mod logout;
+pub mod metadata;
+pub mod new;
+pub mod owner;
+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;
+pub mod rustdoc;
+pub mod search;
+pub mod test;
+pub mod tree;
+pub mod uninstall;
+pub mod update;
+pub mod vendor;
+pub mod verify_project;
+pub mod version;
+pub mod yank;
diff --git a/src/bin/cargo/commands/new.rs b/src/bin/cargo/commands/new.rs
new file mode 100644
index 0000000..18cf93d
--- /dev/null
+++ b/src/bin/cargo/commands/new.rs
@@ -0,0 +1,30 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("new")
+ .about("Create a new cargo package at <path>")
+ .arg_quiet()
+ .arg(Arg::new("path").action(ArgAction::Set).required(true))
+ .arg(opt("registry", "Registry to use").value_name("REGISTRY"))
+ .arg_new_opts()
+ .after_help("Run `cargo help new` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let opts = args.new_options(config)?;
+
+ ops::new(&opts, config)?;
+ let path = args.get_one::<String>("path").unwrap();
+ let package_name = if let Some(name) = args.get_one::<String>("name") {
+ name
+ } else {
+ path
+ };
+ config.shell().status(
+ "Created",
+ format!("{} `{}` package", opts.kind, package_name),
+ )?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/owner.rs b/src/bin/cargo/commands/owner.rs
new file mode 100644
index 0000000..493072b
--- /dev/null
+++ b/src/bin/cargo/commands/owner.rs
@@ -0,0 +1,51 @@
+use crate::command_prelude::*;
+
+use cargo::ops::{self, OwnersOptions};
+use cargo::util::auth::Secret;
+
+pub fn cli() -> Command {
+ subcommand("owner")
+ .about("Manage the owners of a crate on the registry")
+ .arg_quiet()
+ .arg(Arg::new("crate").action(ArgAction::Set))
+ .arg(
+ multi_opt(
+ "add",
+ "LOGIN",
+ "Name of a user or team to invite as an owner",
+ )
+ .short('a'),
+ )
+ .arg(
+ multi_opt(
+ "remove",
+ "LOGIN",
+ "Name of a user or team to remove as an owner",
+ )
+ .short('r'),
+ )
+ .arg(flag("list", "List owners of a crate").short('l'))
+ .arg(opt("index", "Registry index to modify owners for").value_name("INDEX"))
+ .arg(opt("token", "API token to use when authenticating").value_name("TOKEN"))
+ .arg(opt("registry", "Registry to use").value_name("REGISTRY"))
+ .after_help("Run `cargo help owner` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let registry = args.registry(config)?;
+ let opts = OwnersOptions {
+ krate: args.get_one::<String>("crate").cloned(),
+ token: args.get_one::<String>("token").cloned().map(Secret::from),
+ index: args.get_one::<String>("index").cloned(),
+ to_add: args
+ .get_many::<String>("add")
+ .map(|xs| xs.cloned().collect()),
+ to_remove: args
+ .get_many::<String>("remove")
+ .map(|xs| xs.cloned().collect()),
+ list: args.flag("list"),
+ registry,
+ };
+ ops::modify_owners(config, &opts)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/package.rs b/src/bin/cargo/commands/package.rs
new file mode 100644
index 0000000..ac6b1fe
--- /dev/null
+++ b/src/bin/cargo/commands/package.rs
@@ -0,0 +1,62 @@
+use crate::command_prelude::*;
+
+use cargo::ops::{self, PackageOpts};
+
+pub fn cli() -> Command {
+ subcommand("package")
+ .about("Assemble the local package into a distributable tarball")
+ .arg_quiet()
+ .arg(
+ flag(
+ "list",
+ "Print files included in a package without making one",
+ )
+ .short('l'),
+ )
+ .arg(flag(
+ "no-verify",
+ "Don't verify the contents by building them",
+ ))
+ .arg(flag(
+ "no-metadata",
+ "Ignore warnings about a lack of human-usable metadata",
+ ))
+ .arg(flag(
+ "allow-dirty",
+ "Allow dirty working directories to be packaged",
+ ))
+ .arg_target_triple("Build for the target triple")
+ .arg_target_dir()
+ .arg_features()
+ .arg_package_spec_no_all(
+ "Package(s) to assemble",
+ "Assemble all packages in the workspace",
+ "Don't assemble specified packages",
+ )
+ .arg_manifest_path()
+ .arg_jobs()
+ .after_help("Run `cargo help package` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ let specs = args.packages_from_flags()?;
+
+ ops::package(
+ &ws,
+ &PackageOpts {
+ config,
+ verify: !args.flag("no-verify"),
+ list: args.flag("list"),
+ check_metadata: !args.flag("no-metadata"),
+ allow_dirty: args.flag("allow-dirty"),
+ to_package: specs,
+ targets: args.targets(),
+ jobs: args.jobs()?,
+ keep_going: args.keep_going(),
+ cli_features: args.cli_features()?,
+ },
+ )?;
+
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/pkgid.rs b/src/bin/cargo/commands/pkgid.rs
new file mode 100644
index 0000000..664db75
--- /dev/null
+++ b/src/bin/cargo/commands/pkgid.rs
@@ -0,0 +1,28 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+use cargo::util::print_available_packages;
+
+pub fn cli() -> Command {
+ subcommand("pkgid")
+ .about("Print a fully qualified package specification")
+ .arg_quiet()
+ .arg(Arg::new("spec").action(ArgAction::Set))
+ .arg_package("Argument to get the package ID specifier for")
+ .arg_manifest_path()
+ .after_help("Run `cargo help pkgid` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ if args.is_present_with_zero_values("package") {
+ print_available_packages(&ws)?
+ }
+ let spec = args
+ .get_one::<String>("spec")
+ .or_else(|| args.get_one::<String>("package"))
+ .map(String::as_str);
+ let spec = ops::pkgid(&ws, spec)?;
+ cargo::drop_println!(config, "{}", spec);
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/publish.rs b/src/bin/cargo/commands/publish.rs
new file mode 100644
index 0000000..c831d39
--- /dev/null
+++ b/src/bin/cargo/commands/publish.rs
@@ -0,0 +1,55 @@
+use crate::command_prelude::*;
+
+use cargo::ops::{self, PublishOpts};
+
+pub fn cli() -> Command {
+ subcommand("publish")
+ .about("Upload a package to the registry")
+ .arg_quiet()
+ .arg_index()
+ .arg(opt("token", "Token to use when uploading").value_name("TOKEN"))
+ .arg(flag(
+ "no-verify",
+ "Don't verify the contents by building them",
+ ))
+ .arg(flag(
+ "allow-dirty",
+ "Allow dirty working directories to be packaged",
+ ))
+ .arg_target_triple("Build for the target triple")
+ .arg_target_dir()
+ .arg_package("Package to publish")
+ .arg_manifest_path()
+ .arg_features()
+ .arg_jobs()
+ .arg_dry_run("Perform all checks without uploading")
+ .arg(opt("registry", "Registry to publish to").value_name("REGISTRY"))
+ .after_help("Run `cargo help publish` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let registry = args.registry(config)?;
+ let ws = args.workspace(config)?;
+ let index = args.index()?;
+
+ ops::publish(
+ &ws,
+ &PublishOpts {
+ config,
+ token: args
+ .get_one::<String>("token")
+ .map(|s| s.to_string().into()),
+ index,
+ verify: !args.flag("no-verify"),
+ allow_dirty: args.flag("allow-dirty"),
+ to_publish: args.packages_from_flags()?,
+ targets: args.targets(),
+ jobs: args.jobs()?,
+ keep_going: args.keep_going(),
+ dry_run: args.dry_run(),
+ registry,
+ cli_features: args.cli_features()?,
+ },
+ )?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/read_manifest.rs b/src/bin/cargo/commands/read_manifest.rs
new file mode 100644
index 0000000..a1f42bf
--- /dev/null
+++ b/src/bin/cargo/commands/read_manifest.rs
@@ -0,0 +1,20 @@
+use crate::command_prelude::*;
+
+pub fn cli() -> Command {
+ subcommand("read-manifest")
+ .about(
+ "\
+Print a JSON representation of a Cargo.toml manifest.
+
+Deprecated, use `cargo metadata --no-deps` instead.\
+",
+ )
+ .arg_quiet()
+ .arg_manifest_path()
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ config.shell().print_json(&ws.current()?.serialized())?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/remove.rs b/src/bin/cargo/commands/remove.rs
new file mode 100644
index 0000000..50bc8b7
--- /dev/null
+++ b/src/bin/cargo/commands/remove.rs
@@ -0,0 +1,344 @@
+use cargo::core::dependency::DepKind;
+use cargo::core::PackageIdSpec;
+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::print_available_packages;
+use cargo::util::toml_mut::dependency::Dependency;
+use cargo::util::toml_mut::dependency::MaybeWorkspace;
+use cargo::util::toml_mut::dependency::Source;
+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")
+ // 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)?;
+
+ if args.is_present_with_zero_values("package") {
+ print_available_packages(&workspace)?;
+ }
+
+ 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 to modify. Please specify one with `-p <PKGID>`"
+ ),
+ 101,
+ ));
+ }
+ 1 => packages[0],
+ _ => {
+ let names = packages.iter().map(|p| p.name()).collect::<Vec<_>>();
+ return Err(CliError::new(
+ anyhow::format_err!(
+ "`cargo remove` could not determine which package to modify. \
+ Use the `--package` option to specify a package. \n\
+ available packages: {}",
+ names.join(", ")
+ ),
+ 101,
+ ));
+ }
+ };
+
+ let dependencies = args
+ .get_many::<String>("dependencies")
+ .expect("required(true)")
+ .cloned()
+ .collect::<Vec<_>>();
+
+ let section = parse_section(args);
+
+ let options = RemoveOptions {
+ config,
+ spec,
+ dependencies,
+ section,
+ dry_run,
+ };
+ remove(&options)?;
+
+ if !dry_run {
+ // Clean up the workspace
+ gc_workspace(&workspace)?;
+
+ // Reload the workspace since we've changed dependencies
+ let ws = args.workspace(config)?;
+ resolve_ws(&ws)?;
+ }
+
+ Ok(())
+}
+
+fn parse_section(args: &ArgMatches) -> DepTable {
+ 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
+}
+
+/// Clean up the workspace.dependencies, profile, patch, and replace sections of the root manifest
+/// by removing dependencies which no longer have a reference to them.
+fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> {
+ let mut manifest: toml_edit::Document =
+ cargo_util::paths::read(workspace.root_manifest())?.parse()?;
+ let mut is_modified = true;
+
+ let members = workspace
+ .members()
+ .map(|p| LocalManifest::try_new(p.manifest_path()))
+ .collect::<CargoResult<Vec<_>>>()?;
+
+ let mut dependencies = members
+ .iter()
+ .flat_map(|manifest| {
+ manifest.get_sections().into_iter().flat_map(|(_, table)| {
+ table
+ .as_table_like()
+ .unwrap()
+ .iter()
+ .map(|(key, item)| Dependency::from_toml(&manifest.path, key, item))
+ .collect::<Vec<_>>()
+ })
+ })
+ .collect::<CargoResult<Vec<_>>>()?;
+
+ // Clean up the workspace.dependencies section and replace instances of
+ // workspace dependencies with their definitions
+ if let Some(toml_edit::Item::Table(deps_table)) = manifest
+ .get_mut("workspace")
+ .and_then(|t| t.get_mut("dependencies"))
+ {
+ deps_table.set_implicit(true);
+ for (key, item) in deps_table.iter_mut() {
+ let ws_dep = Dependency::from_toml(&workspace.root(), key.get(), item)?;
+
+ // search for uses of this workspace dependency
+ let mut is_used = false;
+ for dep in dependencies.iter_mut().filter(|d| {
+ d.toml_key() == key.get() && matches!(d.source(), Some(Source::Workspace(_)))
+ }) {
+ // HACK: Replace workspace references in `dependencies` to simplify later GC steps:
+ // 1. Avoid having to look it up again to determine the dependency source / spec
+ // 2. The entry might get deleted, preventing us from looking it up again
+ //
+ // This does lose extra information, like features enabled, but that shouldn't be a
+ // problem for GC
+ *dep = ws_dep.clone();
+
+ is_used = true;
+ }
+
+ if !is_used {
+ *item = toml_edit::Item::None;
+ is_modified = true;
+ }
+ }
+ }
+
+ // Clean up the profile section
+ //
+ // Example tables:
+ // - profile.dev.package.foo
+ // - profile.release.package."*"
+ // - profile.release.package."foo:2.1.0"
+ if let Some(toml_edit::Item::Table(profile_section_table)) = manifest.get_mut("profile") {
+ profile_section_table.set_implicit(true);
+
+ for (_, item) in profile_section_table.iter_mut() {
+ if let toml_edit::Item::Table(profile_table) = item {
+ profile_table.set_implicit(true);
+
+ if let Some(toml_edit::Item::Table(package_table)) =
+ profile_table.get_mut("package")
+ {
+ package_table.set_implicit(true);
+
+ for (key, item) in package_table.iter_mut() {
+ if !spec_has_match(
+ &PackageIdSpec::parse(key.get())?,
+ &dependencies,
+ workspace.config(),
+ )? {
+ *item = toml_edit::Item::None;
+ is_modified = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Clean up the patch section
+ if let Some(toml_edit::Item::Table(patch_section_table)) = manifest.get_mut("patch") {
+ patch_section_table.set_implicit(true);
+
+ // The key in each of the subtables is a source (either a registry or a URL)
+ for (source, item) in patch_section_table.iter_mut() {
+ if let toml_edit::Item::Table(patch_table) = item {
+ patch_table.set_implicit(true);
+
+ for (key, item) in patch_table.iter_mut() {
+ let package_name =
+ Dependency::from_toml(&workspace.root_manifest(), key.get(), item)?.name;
+ if !source_has_match(
+ &package_name,
+ source.get(),
+ &dependencies,
+ workspace.config(),
+ )? {
+ *item = toml_edit::Item::None;
+ }
+ }
+ }
+ }
+ }
+
+ // Clean up the replace section
+ if let Some(toml_edit::Item::Table(table)) = manifest.get_mut("replace") {
+ table.set_implicit(true);
+
+ for (key, item) in table.iter_mut() {
+ if !spec_has_match(
+ &PackageIdSpec::parse(key.get())?,
+ &dependencies,
+ workspace.config(),
+ )? {
+ *item = toml_edit::Item::None;
+ is_modified = true;
+ }
+ }
+ }
+
+ if is_modified {
+ cargo_util::paths::write(workspace.root_manifest(), manifest.to_string().as_bytes())?;
+ }
+
+ Ok(())
+}
+
+/// Check whether or not a package ID spec matches any non-workspace dependencies.
+fn spec_has_match(
+ spec: &PackageIdSpec,
+ dependencies: &[Dependency],
+ config: &Config,
+) -> CargoResult<bool> {
+ for dep in dependencies {
+ if spec.name().as_str() != &dep.name {
+ continue;
+ }
+
+ let version_matches = match (spec.version(), dep.version()) {
+ (Some(v), Some(vq)) => semver::VersionReq::parse(vq)?.matches(v),
+ (Some(_), None) => false,
+ (None, None | Some(_)) => true,
+ };
+ if !version_matches {
+ continue;
+ }
+
+ match dep.source_id(config)? {
+ MaybeWorkspace::Other(source_id) => {
+ if spec.url().map(|u| u == source_id.url()).unwrap_or(true) {
+ return Ok(true);
+ }
+ }
+ MaybeWorkspace::Workspace(_) => {}
+ }
+ }
+
+ Ok(false)
+}
+
+/// Check whether or not a source (URL or registry name) matches any non-workspace dependencies.
+fn source_has_match(
+ name: &str,
+ source: &str,
+ dependencies: &[Dependency],
+ config: &Config,
+) -> CargoResult<bool> {
+ for dep in dependencies {
+ if &dep.name != name {
+ continue;
+ }
+
+ match dep.source_id(config)? {
+ MaybeWorkspace::Other(source_id) => {
+ if source_id.is_registry() {
+ if source_id.display_registry_name() == source
+ || source_id.url().as_str() == source
+ {
+ return Ok(true);
+ }
+ } else if source_id.is_git() {
+ if source_id.url().as_str() == source {
+ return Ok(true);
+ }
+ }
+ }
+ MaybeWorkspace::Workspace(_) => {}
+ }
+ }
+
+ Ok(false)
+}
diff --git a/src/bin/cargo/commands/report.rs b/src/bin/cargo/commands/report.rs
new file mode 100644
index 0000000..275a8f7
--- /dev/null
+++ b/src/bin/cargo/commands/report.rs
@@ -0,0 +1,49 @@
+use crate::command_prelude::*;
+use cargo::core::compiler::future_incompat::{OnDiskReports, REPORT_PREAMBLE};
+use cargo::drop_println;
+
+pub fn cli() -> Command {
+ subcommand("report")
+ .about("Generate and display various kinds of reports")
+ .after_help("Run `cargo help report` for more detailed information.\n")
+ .subcommand_required(true)
+ .arg_required_else_help(true)
+ .subcommand(
+ subcommand("future-incompatibilities")
+ .alias("future-incompat")
+ .about("Reports any crates which will eventually stop compiling")
+ .arg(
+ opt(
+ "id",
+ "identifier of the report generated by a Cargo command invocation",
+ )
+ .value_name("id"),
+ )
+ .arg_package("Package to display a report for"),
+ )
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ match args.subcommand() {
+ Some(("future-incompatibilities", args)) => report_future_incompatibilities(config, args),
+ Some((cmd, _)) => {
+ unreachable!("unexpected command {}", cmd)
+ }
+ None => {
+ unreachable!("unexpected command")
+ }
+ }
+}
+
+fn report_future_incompatibilities(config: &Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ let reports = OnDiskReports::load(&ws)?;
+ let id = args
+ .value_of_u32("id")?
+ .unwrap_or_else(|| reports.last_id());
+ let krate = args.get_one::<String>("package").map(String::as_str);
+ let report = reports.get_report(id, config, krate)?;
+ drop_println!(config, "{}", REPORT_PREAMBLE);
+ drop(config.shell().print_ansi_stdout(report.as_bytes()));
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/run.rs b/src/bin/cargo/commands/run.rs
new file mode 100644
index 0000000..cde754c
--- /dev/null
+++ b/src/bin/cargo/commands/run.rs
@@ -0,0 +1,103 @@
+use crate::command_prelude::*;
+use crate::util::restricted_names::is_glob_pattern;
+use cargo::core::Verbosity;
+use cargo::ops::{self, CompileFilter, Packages};
+use cargo_util::ProcessError;
+
+pub fn cli() -> Command {
+ subcommand("run")
+ // subcommand aliases are handled in aliased_command()
+ // .alias("r")
+ .about("Run a binary or example of the local package")
+ .arg_quiet()
+ .arg(
+ Arg::new("args")
+ .help("Arguments for the binary or example to run")
+ .value_parser(value_parser!(std::ffi::OsString))
+ .num_args(0..)
+ .trailing_var_arg(true),
+ )
+ .arg_targets_bin_example(
+ "Name of the bin target to run",
+ "Name of the example target to run",
+ )
+ .arg_package("Package with the target to run")
+ .arg_jobs()
+ .arg_release("Build artifacts in release mode, with optimizations")
+ .arg_profile("Build artifacts with the specified profile")
+ .arg_features()
+ .arg_target_triple("Build for the target triple")
+ .arg_target_dir()
+ .arg_manifest_path()
+ .arg_message_format()
+ .arg_unit_graph()
+ .arg_ignore_rust_version()
+ .arg_timings()
+ .after_help("Run `cargo help run` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+
+ let mut compile_opts = args.compile_options(
+ config,
+ CompileMode::Build,
+ Some(&ws),
+ ProfileChecking::Custom,
+ )?;
+
+ // Disallow `spec` to be an glob pattern
+ if let Packages::Packages(opt_in) = &compile_opts.spec {
+ if let Some(pattern) = opt_in.iter().find(|s| is_glob_pattern(s)) {
+ return Err(anyhow::anyhow!(
+ "`cargo run` does not support glob pattern `{}` on package selection",
+ pattern,
+ )
+ .into());
+ }
+ }
+
+ if !args.contains_id("example") && !args.contains_id("bin") {
+ let default_runs: Vec<_> = compile_opts
+ .spec
+ .get_packages(&ws)?
+ .iter()
+ .filter_map(|pkg| pkg.manifest().default_run())
+ .collect();
+ if let [bin] = &default_runs[..] {
+ compile_opts.filter = CompileFilter::single_bin(bin.to_string());
+ } else {
+ // ops::run will take care of errors if len pkgs != 1.
+ compile_opts.filter = CompileFilter::Default {
+ // Force this to false because the code in ops::run is not
+ // able to pre-check features before compilation starts to
+ // enforce that only 1 binary is built.
+ required_features_filterable: false,
+ };
+ }
+ };
+
+ ops::run(&ws, &compile_opts, &values_os(args, "args")).map_err(|err| {
+ let proc_err = match err.downcast_ref::<ProcessError>() {
+ Some(e) => e,
+ None => return CliError::new(err, 101),
+ };
+
+ // If we never actually spawned the process then that sounds pretty
+ // bad and we always want to forward that up.
+ let exit_code = match proc_err.code {
+ Some(exit) => exit,
+ None => return CliError::new(err, 101),
+ };
+
+ // If `-q` was passed then we suppress extra error information about
+ // a failed process, we assume the process itself printed out enough
+ // information about why it failed so we don't do so as well
+ let is_quiet = config.shell().verbosity() == Verbosity::Quiet;
+ if is_quiet {
+ CliError::code(exit_code)
+ } else {
+ CliError::new(err, exit_code)
+ }
+ })
+}
diff --git a/src/bin/cargo/commands/rustc.rs b/src/bin/cargo/commands/rustc.rs
new file mode 100644
index 0000000..de73eb8
--- /dev/null
+++ b/src/bin/cargo/commands/rustc.rs
@@ -0,0 +1,100 @@
+use crate::command_prelude::*;
+use cargo::ops;
+use cargo::util::interning::InternedString;
+
+const PRINT_ARG_NAME: &str = "print";
+const CRATE_TYPE_ARG_NAME: &str = "crate-type";
+
+pub fn cli() -> Command {
+ subcommand("rustc")
+ .about("Compile a package, and pass extra options to the compiler")
+ .arg_quiet()
+ .arg(
+ Arg::new("args")
+ .num_args(0..)
+ .help("Extra rustc flags")
+ .trailing_var_arg(true),
+ )
+ .arg_package("Package to build")
+ .arg_jobs()
+ .arg_targets_all(
+ "Build only this package's library",
+ "Build only the specified binary",
+ "Build all binaries",
+ "Build only the specified example",
+ "Build all examples",
+ "Build only the specified test target",
+ "Build all tests",
+ "Build only the specified bench target",
+ "Build all benches",
+ "Build all targets",
+ )
+ .arg_release("Build artifacts in release mode, with optimizations")
+ .arg_profile("Build artifacts with the specified profile")
+ .arg_features()
+ .arg_target_triple("Target triple which compiles will be for")
+ .arg(
+ opt(
+ PRINT_ARG_NAME,
+ "Output compiler information without compiling",
+ )
+ .value_name("INFO"),
+ )
+ .arg(multi_opt(
+ CRATE_TYPE_ARG_NAME,
+ "CRATE-TYPE",
+ "Comma separated list of types of crates for the compiler to emit",
+ ))
+ .arg_target_dir()
+ .arg_manifest_path()
+ .arg_message_format()
+ .arg_unit_graph()
+ .arg_ignore_rust_version()
+ .arg_future_incompat_report()
+ .arg_timings()
+ .after_help("Run `cargo help rustc` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ // This is a legacy behavior that changes the behavior based on the profile.
+ // If we want to support this more formally, I think adding a --mode flag
+ // would be warranted.
+ let mode = match args.get_one::<String>("profile").map(String::as_str) {
+ Some("test") => CompileMode::Test,
+ Some("bench") => CompileMode::Bench,
+ Some("check") => CompileMode::Check { test: false },
+ _ => CompileMode::Build,
+ };
+ let mut compile_opts = args.compile_options_for_single_package(
+ config,
+ mode,
+ Some(&ws),
+ ProfileChecking::LegacyRustc,
+ )?;
+ if compile_opts.build_config.requested_profile == "check" {
+ compile_opts.build_config.requested_profile = InternedString::new("dev");
+ }
+ let target_args = values(args, "args");
+ compile_opts.target_rustc_args = if target_args.is_empty() {
+ None
+ } else {
+ Some(target_args)
+ };
+ if let Some(opt_value) = args.get_one::<String>(PRINT_ARG_NAME) {
+ config
+ .cli_unstable()
+ .fail_if_stable_opt(PRINT_ARG_NAME, 9357)?;
+ ops::print(&ws, &compile_opts, opt_value)?;
+ return Ok(());
+ }
+ let crate_types = values(args, CRATE_TYPE_ARG_NAME);
+ compile_opts.target_rustc_crate_types = if crate_types.is_empty() {
+ None
+ } else {
+ Some(crate_types)
+ };
+ ops::compile(&ws, &compile_opts)?;
+
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/rustdoc.rs b/src/bin/cargo/commands/rustdoc.rs
new file mode 100644
index 0000000..e87f435
--- /dev/null
+++ b/src/bin/cargo/commands/rustdoc.rs
@@ -0,0 +1,66 @@
+use cargo::ops::{self, DocOptions};
+
+use crate::command_prelude::*;
+
+pub fn cli() -> Command {
+ subcommand("rustdoc")
+ .about("Build a package's documentation, using specified custom flags.")
+ .arg_quiet()
+ .arg(
+ Arg::new("args")
+ .help("Extra rustdoc flags")
+ .num_args(0..)
+ .trailing_var_arg(true),
+ )
+ .arg(flag(
+ "open",
+ "Opens the docs in a browser after the operation",
+ ))
+ .arg_package("Package to document")
+ .arg_jobs()
+ .arg_targets_all(
+ "Build only this package's library",
+ "Build only the specified binary",
+ "Build all binaries",
+ "Build only the specified example",
+ "Build all examples",
+ "Build only the specified test target",
+ "Build all tests",
+ "Build only the specified bench target",
+ "Build all benches",
+ "Build all targets",
+ )
+ .arg_release("Build artifacts in release mode, with optimizations")
+ .arg_profile("Build artifacts with the specified profile")
+ .arg_features()
+ .arg_target_triple("Build for the target triple")
+ .arg_target_dir()
+ .arg_manifest_path()
+ .arg_message_format()
+ .arg_unit_graph()
+ .arg_ignore_rust_version()
+ .arg_timings()
+ .after_help("Run `cargo help rustdoc` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+ let mut compile_opts = args.compile_options_for_single_package(
+ config,
+ CompileMode::Doc { deps: false },
+ Some(&ws),
+ ProfileChecking::Custom,
+ )?;
+ let target_args = values(args, "args");
+ compile_opts.target_rustdoc_args = if target_args.is_empty() {
+ None
+ } else {
+ Some(target_args)
+ };
+ let doc_opts = DocOptions {
+ open_result: args.flag("open"),
+ compile_opts,
+ };
+ ops::doc(&ws, &doc_opts)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/search.rs b/src/bin/cargo/commands/search.rs
new file mode 100644
index 0000000..c55d932
--- /dev/null
+++ b/src/bin/cargo/commands/search.rs
@@ -0,0 +1,37 @@
+use crate::command_prelude::*;
+
+use std::cmp::min;
+
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("search")
+ .about("Search packages in crates.io")
+ .arg_quiet()
+ .arg(Arg::new("query").num_args(0..))
+ .arg_index()
+ .arg(
+ opt(
+ "limit",
+ "Limit the number of results (default: 10, max: 100)",
+ )
+ .value_name("LIMIT"),
+ )
+ .arg(opt("registry", "Registry to use").value_name("REGISTRY"))
+ .after_help("Run `cargo help search` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let registry = args.registry(config)?;
+ let index = args.index()?;
+ let limit = args.value_of_u32("limit")?;
+ let limit = min(100, limit.unwrap_or(10));
+ let query: Vec<&str> = args
+ .get_many::<String>("query")
+ .unwrap_or_default()
+ .map(String::as_str)
+ .collect();
+ let query: String = query.join("+");
+ ops::search(&query, config, index, limit, registry)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/test.rs b/src/bin/cargo/commands/test.rs
new file mode 100644
index 0000000..607655a
--- /dev/null
+++ b/src/bin/cargo/commands/test.rs
@@ -0,0 +1,113 @@
+use crate::command_prelude::*;
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("test")
+ // Subcommand aliases are handled in `aliased_command()`.
+ // .alias("t")
+ .about("Execute all unit and integration tests and build examples of a local package")
+ .arg(
+ Arg::new("TESTNAME")
+ .action(ArgAction::Set)
+ .help("If specified, only run tests containing this string in their names"),
+ )
+ .arg(
+ Arg::new("args")
+ .help("Arguments for the test binary")
+ .num_args(0..)
+ .last(true),
+ )
+ .arg(
+ flag(
+ "quiet",
+ "Display one character per test instead of one line",
+ )
+ .short('q'),
+ )
+ .arg_targets_all(
+ "Test only this package's library unit tests",
+ "Test only the specified binary",
+ "Test all binaries",
+ "Test only the specified example",
+ "Test all examples",
+ "Test only the specified test target",
+ "Test all tests",
+ "Test only the specified bench target",
+ "Test all benches",
+ "Test all targets",
+ )
+ .arg(flag("doc", "Test only this library's documentation"))
+ .arg(flag("no-run", "Compile, but don't run tests"))
+ .arg(flag("no-fail-fast", "Run all tests regardless of failure"))
+ .arg_package_spec(
+ "Package to run tests for",
+ "Test all packages in the workspace",
+ "Exclude packages from the test",
+ )
+ .arg_jobs()
+ .arg_release("Build artifacts in release mode, with optimizations")
+ .arg_profile("Build artifacts with the specified profile")
+ .arg_features()
+ .arg_target_triple("Build for the target triple")
+ .arg_target_dir()
+ .arg_manifest_path()
+ .arg_ignore_rust_version()
+ .arg_message_format()
+ .arg_unit_graph()
+ .arg_future_incompat_report()
+ .arg_timings()
+ .after_help(
+ "Run `cargo help test` for more detailed information.\n\
+ Run `cargo test -- --help` for test binary options.\n",
+ )
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+
+ let mut compile_opts = args.compile_options(
+ config,
+ CompileMode::Test,
+ Some(&ws),
+ ProfileChecking::Custom,
+ )?;
+
+ compile_opts.build_config.requested_profile =
+ args.get_profile_name(config, "test", ProfileChecking::Custom)?;
+
+ // `TESTNAME` is actually an argument of the test binary, but it's
+ // important, so we explicitly mention it and reconfigure.
+ let test_name = args.get_one::<String>("TESTNAME");
+ let test_args = args.get_one::<String>("TESTNAME").into_iter();
+ let test_args = test_args.chain(args.get_many::<String>("args").unwrap_or_default());
+ let test_args = test_args.map(String::as_str).collect::<Vec<_>>();
+
+ let no_run = args.flag("no-run");
+ let doc = args.flag("doc");
+ if doc {
+ if compile_opts.filter.is_specific() {
+ return Err(
+ anyhow::format_err!("Can't mix --doc with other target selecting options").into(),
+ );
+ }
+ if no_run {
+ return Err(anyhow::format_err!("Can't skip running doc tests with --no-run").into());
+ }
+ compile_opts.build_config.mode = CompileMode::Doctest;
+ compile_opts.filter = ops::CompileFilter::lib_only();
+ } else if test_name.is_some() && !compile_opts.filter.is_specific() {
+ // If arg `TESTNAME` is provided, assumed that the user knows what
+ // exactly they wants to test, so we use `all_test_targets` to
+ // avoid compiling unnecessary targets such as examples, which are
+ // included by the logic of default target filter.
+ compile_opts.filter = ops::CompileFilter::all_test_targets();
+ }
+
+ let ops = ops::TestOptions {
+ no_run,
+ no_fail_fast: args.flag("no-fail-fast"),
+ compile_opts,
+ };
+
+ ops::run_tests(&ws, &ops, &test_args)
+}
diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs
new file mode 100644
index 0000000..0a75178
--- /dev/null
+++ b/src/bin/cargo/commands/tree.rs
@@ -0,0 +1,305 @@
+use crate::cli;
+use crate::command_prelude::*;
+use anyhow::{bail, format_err};
+use cargo::core::dependency::DepKind;
+use cargo::ops::tree::{self, EdgeKind};
+use cargo::ops::Packages;
+use cargo::util::print_available_packages;
+use cargo::util::CargoResult;
+use std::collections::HashSet;
+use std::str::FromStr;
+
+pub fn cli() -> Command {
+ subcommand("tree")
+ .about("Display a tree visualization of a dependency graph")
+ .arg_quiet()
+ .arg_manifest_path()
+ .arg_package_spec_no_all(
+ "Package to be used as the root of the tree",
+ "Display the tree for all packages in the workspace",
+ "Exclude specific workspace members",
+ )
+ .arg(
+ flag("all", "Deprecated, use --no-dedupe instead")
+ .short('a')
+ .hide(true),
+ )
+ .arg(flag("all-targets", "Deprecated, use --target=all instead").hide(true))
+ .arg_features()
+ .arg_target_triple(
+ "Filter dependencies matching the given target-triple (default host platform). \
+ Pass `all` to include all targets.",
+ )
+ .arg(flag("no-dev-dependencies", "Deprecated, use -e=no-dev instead").hide(true))
+ .arg(
+ multi_opt(
+ "edges",
+ "KINDS",
+ "The kinds of dependencies to display \
+ (features, normal, build, dev, all, \
+ no-normal, no-build, no-dev, no-proc-macro)",
+ )
+ .short('e'),
+ )
+ .arg(
+ optional_multi_opt(
+ "invert",
+ "SPEC",
+ "Invert the tree direction and focus on the given package",
+ )
+ .short('i'),
+ )
+ .arg(multi_opt(
+ "prune",
+ "SPEC",
+ "Prune the given package from the display of the dependency tree",
+ ))
+ .arg(opt("depth", "Maximum display depth of the dependency tree").value_name("DEPTH"))
+ .arg(flag("no-indent", "Deprecated, use --prefix=none instead").hide(true))
+ .arg(flag("prefix-depth", "Deprecated, use --prefix=depth instead").hide(true))
+ .arg(
+ opt(
+ "prefix",
+ "Change the prefix (indentation) of how each entry is displayed",
+ )
+ .value_name("PREFIX")
+ .value_parser(["depth", "indent", "none"])
+ .default_value("indent"),
+ )
+ .arg(flag(
+ "no-dedupe",
+ "Do not de-duplicate (repeats all shared dependencies)",
+ ))
+ .arg(
+ flag(
+ "duplicates",
+ "Show only dependencies which come in multiple versions (implies -i)",
+ )
+ .short('d')
+ .alias("duplicate"),
+ )
+ .arg(
+ opt("charset", "Character set to use in output: utf8, ascii")
+ .value_name("CHARSET")
+ .value_parser(["utf8", "ascii"])
+ .default_value("utf8"),
+ )
+ .arg(
+ opt("format", "Format string used for printing dependencies")
+ .value_name("FORMAT")
+ .short('f')
+ .default_value("{p}"),
+ )
+ .arg(
+ // Backwards compatibility with old cargo-tree.
+ flag("version", "Print version info and exit")
+ .short('V')
+ .hide(true),
+ )
+ .after_help("Run `cargo help tree` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ if args.flag("version") {
+ let verbose = args.verbose() > 0;
+ let version = cli::get_version_string(verbose);
+ cargo::drop_print!(config, "{}", version);
+ return Ok(());
+ }
+ let prefix = if args.flag("no-indent") {
+ config
+ .shell()
+ .warn("the --no-indent flag has been changed to --prefix=none")?;
+ "none"
+ } else if args.flag("prefix-depth") {
+ config
+ .shell()
+ .warn("the --prefix-depth flag has been changed to --prefix=depth")?;
+ "depth"
+ } else {
+ args.get_one::<String>("prefix").unwrap().as_str()
+ };
+ let prefix = tree::Prefix::from_str(prefix).map_err(|e| anyhow::anyhow!("{}", e))?;
+
+ let no_dedupe = args.flag("no-dedupe") || args.flag("all");
+ if args.flag("all") {
+ config.shell().warn(
+ "The `cargo tree` --all flag has been changed to --no-dedupe, \
+ and may be removed in a future version.\n\
+ If you are looking to display all workspace members, use the --workspace flag.",
+ )?;
+ }
+
+ let targets = if args.flag("all-targets") {
+ config
+ .shell()
+ .warn("the --all-targets flag has been changed to --target=all")?;
+ vec!["all".to_string()]
+ } else {
+ args._values_of("target")
+ };
+ let target = tree::Target::from_cli(targets);
+
+ let (edge_kinds, no_proc_macro) = parse_edge_kinds(config, args)?;
+ let graph_features = edge_kinds.contains(&EdgeKind::Feature);
+
+ let pkgs_to_prune = args._values_of("prune");
+
+ let packages = args.packages_from_flags()?;
+ let mut invert = args
+ .get_many::<String>("invert")
+ .map_or_else(|| Vec::new(), |is| is.map(|s| s.to_string()).collect());
+ if args.is_present_with_zero_values("invert") {
+ match &packages {
+ Packages::Packages(ps) => {
+ // Backwards compatibility with old syntax of `cargo tree -i -p foo`.
+ invert.extend(ps.clone());
+ }
+ _ => {
+ return Err(format_err!(
+ "The `-i` flag requires a package name.\n\
+\n\
+The `-i` flag is used to inspect the reverse dependencies of a specific\n\
+package. It will invert the tree and display the packages that depend on the\n\
+given package.\n\
+\n\
+Note that in a workspace, by default it will only display the package's\n\
+reverse dependencies inside the tree of the workspace member in the current\n\
+directory. The --workspace flag can be used to extend it so that it will show\n\
+the package's reverse dependencies across the entire workspace. The -p flag\n\
+can be used to display the package's reverse dependencies only with the\n\
+subtree of the package given to -p.\n\
+"
+ )
+ .into());
+ }
+ }
+ }
+
+ let ws = args.workspace(config)?;
+
+ if args.is_present_with_zero_values("package") {
+ print_available_packages(&ws)?;
+ }
+
+ let charset = tree::Charset::from_str(args.get_one::<String>("charset").unwrap())
+ .map_err(|e| anyhow::anyhow!("{}", e))?;
+ let opts = tree::TreeOptions {
+ cli_features: args.cli_features()?,
+ packages,
+ target,
+ edge_kinds,
+ invert,
+ pkgs_to_prune,
+ prefix,
+ no_dedupe,
+ duplicates: args.flag("duplicates"),
+ charset,
+ format: args.get_one::<String>("format").cloned().unwrap(),
+ graph_features,
+ max_display_depth: args.value_of_u32("depth")?.unwrap_or(u32::MAX),
+ no_proc_macro,
+ };
+
+ if opts.graph_features && opts.duplicates {
+ return Err(format_err!("the `-e features` flag does not support `--duplicates`").into());
+ }
+
+ tree::build_and_print(&ws, &opts)?;
+ Ok(())
+}
+
+/// Parses `--edges` option.
+///
+/// Returns a tuple of `EdgeKind` map and `no_proc_marco` flag.
+fn parse_edge_kinds(config: &Config, args: &ArgMatches) -> CargoResult<(HashSet<EdgeKind>, bool)> {
+ let (kinds, no_proc_macro) = {
+ let mut no_proc_macro = false;
+ let mut kinds = args.get_many::<String>("edges").map_or_else(
+ || Vec::new(),
+ |es| {
+ es.flat_map(|e| e.split(','))
+ .filter(|e| {
+ no_proc_macro = *e == "no-proc-macro";
+ !no_proc_macro
+ })
+ .collect()
+ },
+ );
+
+ if args.flag("no-dev-dependencies") {
+ config
+ .shell()
+ .warn("the --no-dev-dependencies flag has changed to -e=no-dev")?;
+ kinds.push("no-dev");
+ }
+
+ if kinds.is_empty() {
+ kinds.extend(&["normal", "build", "dev"]);
+ }
+
+ (kinds, no_proc_macro)
+ };
+
+ let mut result = HashSet::new();
+ let insert_defaults = |result: &mut HashSet<EdgeKind>| {
+ result.insert(EdgeKind::Dep(DepKind::Normal));
+ result.insert(EdgeKind::Dep(DepKind::Build));
+ result.insert(EdgeKind::Dep(DepKind::Development));
+ };
+ let unknown = |k| {
+ bail!(
+ "unknown edge kind `{}`, valid values are \
+ \"normal\", \"build\", \"dev\", \
+ \"no-normal\", \"no-build\", \"no-dev\", \"no-proc-macro\", \
+ \"features\", or \"all\"",
+ k
+ )
+ };
+ if kinds.iter().any(|k| k.starts_with("no-")) {
+ insert_defaults(&mut result);
+ for kind in &kinds {
+ match *kind {
+ "no-normal" => result.remove(&EdgeKind::Dep(DepKind::Normal)),
+ "no-build" => result.remove(&EdgeKind::Dep(DepKind::Build)),
+ "no-dev" => result.remove(&EdgeKind::Dep(DepKind::Development)),
+ "features" => result.insert(EdgeKind::Feature),
+ "normal" | "build" | "dev" | "all" => {
+ bail!(
+ "`{}` dependency kind cannot be mixed with \
+ \"no-normal\", \"no-build\", or \"no-dev\" \
+ dependency kinds",
+ kind
+ )
+ }
+ k => return unknown(k),
+ };
+ }
+ return Ok((result, no_proc_macro));
+ }
+ for kind in &kinds {
+ match *kind {
+ "all" => {
+ insert_defaults(&mut result);
+ result.insert(EdgeKind::Feature);
+ }
+ "features" => {
+ result.insert(EdgeKind::Feature);
+ }
+ "normal" => {
+ result.insert(EdgeKind::Dep(DepKind::Normal));
+ }
+ "build" => {
+ result.insert(EdgeKind::Dep(DepKind::Build));
+ }
+ "dev" => {
+ result.insert(EdgeKind::Dep(DepKind::Development));
+ }
+ k => return unknown(k),
+ }
+ }
+ if kinds.len() == 1 && kinds[0] == "features" {
+ insert_defaults(&mut result);
+ }
+ Ok((result, no_proc_macro))
+}
diff --git a/src/bin/cargo/commands/uninstall.rs b/src/bin/cargo/commands/uninstall.rs
new file mode 100644
index 0000000..46654b6
--- /dev/null
+++ b/src/bin/cargo/commands/uninstall.rs
@@ -0,0 +1,34 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+
+pub fn cli() -> Command {
+ subcommand("uninstall")
+ .about("Remove a Rust binary")
+ .arg_quiet()
+ .arg(Arg::new("spec").num_args(0..))
+ .arg_package_spec_simple("Package to uninstall")
+ .arg(multi_opt("bin", "NAME", "Only uninstall the binary NAME"))
+ .arg(opt("root", "Directory to uninstall packages from").value_name("DIR"))
+ .after_help("Run `cargo help uninstall` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let root = args.get_one::<String>("root").map(String::as_str);
+
+ if args.is_present_with_zero_values("package") {
+ return Err(anyhow::anyhow!(
+ "\"--package <SPEC>\" requires a SPEC format value.\n\
+ Run `cargo help pkgid` for more information about SPEC format."
+ )
+ .into());
+ }
+
+ let specs = args
+ .get_many::<String>("spec")
+ .unwrap_or_else(|| args.get_many::<String>("package").unwrap_or_default())
+ .map(String::as_str)
+ .collect();
+ ops::uninstall(root, specs, &values(args, "bin"), config)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/update.rs b/src/bin/cargo/commands/update.rs
new file mode 100644
index 0000000..da33e8d
--- /dev/null
+++ b/src/bin/cargo/commands/update.rs
@@ -0,0 +1,46 @@
+use crate::command_prelude::*;
+
+use cargo::ops::{self, UpdateOptions};
+use cargo::util::print_available_packages;
+
+pub fn cli() -> Command {
+ subcommand("update")
+ .about("Update dependencies as recorded in the local lock file")
+ .arg_quiet()
+ .arg(flag("workspace", "Only update the workspace packages").short('w'))
+ .arg_package_spec_simple("Package to update")
+ .arg(flag(
+ "aggressive",
+ "Force updating all dependencies of SPEC as well when used with -p",
+ ))
+ .arg_dry_run("Don't actually write the lockfile")
+ .arg(
+ opt(
+ "precise",
+ "Update a single dependency to exactly PRECISE when used with -p",
+ )
+ .value_name("PRECISE")
+ .requires("package"),
+ )
+ .arg_manifest_path()
+ .after_help("Run `cargo help update` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let ws = args.workspace(config)?;
+
+ if args.is_present_with_zero_values("package") {
+ print_available_packages(&ws)?;
+ }
+
+ let update_opts = UpdateOptions {
+ aggressive: args.flag("aggressive"),
+ precise: args.get_one::<String>("precise").map(String::as_str),
+ to_update: values(args, "package"),
+ dry_run: args.dry_run(),
+ workspace: args.flag("workspace"),
+ config,
+ };
+ ops::update_lockfile(&ws, &update_opts)?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/vendor.rs b/src/bin/cargo/commands/vendor.rs
new file mode 100644
index 0000000..1fd79ec
--- /dev/null
+++ b/src/bin/cargo/commands/vendor.rs
@@ -0,0 +1,100 @@
+use crate::command_prelude::*;
+use cargo::ops;
+use std::path::PathBuf;
+
+pub fn cli() -> Command {
+ subcommand("vendor")
+ .about("Vendor all dependencies for a project locally")
+ .arg_quiet()
+ .arg_manifest_path()
+ .arg(
+ Arg::new("path")
+ .action(ArgAction::Set)
+ .value_parser(clap::value_parser!(PathBuf))
+ .help("Where to vendor crates (`vendor` by default)"),
+ )
+ .arg(flag(
+ "no-delete",
+ "Don't delete older crates in the vendor directory",
+ ))
+ .arg(
+ Arg::new("tomls")
+ .short('s')
+ .long("sync")
+ .help("Additional `Cargo.toml` to sync and vendor")
+ .value_name("TOML")
+ .value_parser(clap::value_parser!(PathBuf))
+ .action(clap::ArgAction::Append),
+ )
+ .arg(flag(
+ "respect-source-config",
+ "Respect `[source]` config in `.cargo/config`",
+ ))
+ .arg(flag(
+ "versioned-dirs",
+ "Always include version in subdir name",
+ ))
+ .arg(flag("no-merge-sources", "Not supported").hide(true))
+ .arg(flag("relative-path", "Not supported").hide(true))
+ .arg(flag("only-git-deps", "Not supported").hide(true))
+ .arg(flag("disallow-duplicates", "Not supported").hide(true))
+ .after_help("Run `cargo help vendor` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ // We're doing the vendoring operation ourselves, so we don't actually want
+ // to respect any of the `source` configuration in Cargo itself. That's
+ // intended for other consumers of Cargo, but we want to go straight to the
+ // source, e.g. crates.io, to fetch crates.
+ if !args.flag("respect-source-config") {
+ config.values_mut()?.remove("source");
+ }
+
+ // When we moved `cargo vendor` into Cargo itself we didn't stabilize a few
+ // flags, so try to provide a helpful error message in that case to ensure
+ // that users currently using the flag aren't tripped up.
+ let crates_io_cargo_vendor_flag = if args.flag("no-merge-sources") {
+ Some("--no-merge-sources")
+ } else if args.flag("relative-path") {
+ Some("--relative-path")
+ } else if args.flag("only-git-deps") {
+ Some("--only-git-deps")
+ } else if args.flag("disallow-duplicates") {
+ Some("--disallow-duplicates")
+ } else {
+ None
+ };
+ if let Some(flag) = crates_io_cargo_vendor_flag {
+ return Err(anyhow::format_err!(
+ "\
+the crates.io `cargo vendor` command has now been merged into Cargo itself
+and does not support the flag `{}` currently; to continue using the flag you
+can execute `cargo-vendor vendor ...`, and if you would like to see this flag
+supported in Cargo itself please feel free to file an issue at
+https://github.com/rust-lang/cargo/issues/new
+",
+ flag
+ )
+ .into());
+ }
+
+ let ws = args.workspace(config)?;
+ let path = args
+ .get_one::<PathBuf>("path")
+ .cloned()
+ .unwrap_or_else(|| PathBuf::from("vendor"));
+ ops::vendor(
+ &ws,
+ &ops::VendorOptions {
+ no_delete: args.flag("no-delete"),
+ destination: &path,
+ versioned_dirs: args.flag("versioned-dirs"),
+ extra: args
+ .get_many::<PathBuf>("tomls")
+ .unwrap_or_default()
+ .cloned()
+ .collect(),
+ },
+ )?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/verify_project.rs b/src/bin/cargo/commands/verify_project.rs
new file mode 100644
index 0000000..4d54926
--- /dev/null
+++ b/src/bin/cargo/commands/verify_project.rs
@@ -0,0 +1,26 @@
+use crate::command_prelude::*;
+
+use std::collections::HashMap;
+use std::process;
+
+pub fn cli() -> Command {
+ subcommand("verify-project")
+ .about("Check correctness of crate manifest")
+ .arg_quiet()
+ .arg_manifest_path()
+ .after_help("Run `cargo help verify-project` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ if let Err(e) = args.workspace(config) {
+ config
+ .shell()
+ .print_json(&HashMap::from([("invalid", e.to_string())]))?;
+ process::exit(1)
+ }
+
+ config
+ .shell()
+ .print_json(&HashMap::from([("success", "true")]))?;
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/version.rs b/src/bin/cargo/commands/version.rs
new file mode 100644
index 0000000..ac1681f
--- /dev/null
+++ b/src/bin/cargo/commands/version.rs
@@ -0,0 +1,16 @@
+use crate::cli;
+use crate::command_prelude::*;
+
+pub fn cli() -> Command {
+ subcommand("version")
+ .about("Show version information")
+ .arg_quiet()
+ .after_help("Run `cargo help version` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let verbose = args.verbose() > 0;
+ let version = cli::get_version_string(verbose);
+ cargo::drop_print!(config, "{}", version);
+ Ok(())
+}
diff --git a/src/bin/cargo/commands/yank.rs b/src/bin/cargo/commands/yank.rs
new file mode 100644
index 0000000..3dee522
--- /dev/null
+++ b/src/bin/cargo/commands/yank.rs
@@ -0,0 +1,65 @@
+use crate::command_prelude::*;
+
+use cargo::ops;
+use cargo::util::auth::Secret;
+
+pub fn cli() -> Command {
+ subcommand("yank")
+ .about("Remove a pushed crate from the index")
+ .arg_quiet()
+ .arg(Arg::new("crate").action(ArgAction::Set))
+ .arg(
+ opt("version", "The version to yank or un-yank")
+ .alias("vers")
+ .value_name("VERSION"),
+ )
+ .arg(flag(
+ "undo",
+ "Undo a yank, putting a version back into the index",
+ ))
+ .arg(opt("index", "Registry index to yank from").value_name("INDEX"))
+ .arg(opt("token", "API token to use when authenticating").value_name("TOKEN"))
+ .arg(opt("registry", "Registry to use").value_name("REGISTRY"))
+ .after_help("Run `cargo help yank` for more detailed information.\n")
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let registry = args.registry(config)?;
+
+ let (krate, version) = resolve_crate(
+ args.get_one::<String>("crate").map(String::as_str),
+ args.get_one::<String>("version").map(String::as_str),
+ )?;
+ if version.is_none() {
+ return Err(anyhow::format_err!("`--version` is required").into());
+ }
+
+ ops::yank(
+ config,
+ krate.map(|s| s.to_string()),
+ version.map(|s| s.to_string()),
+ args.get_one::<String>("token").cloned().map(Secret::from),
+ args.get_one::<String>("index").cloned(),
+ args.flag("undo"),
+ registry,
+ )?;
+ Ok(())
+}
+
+fn resolve_crate<'k>(
+ mut krate: Option<&'k str>,
+ mut version: Option<&'k str>,
+) -> crate::CargoResult<(Option<&'k str>, Option<&'k str>)> {
+ if let Some((k, v)) = krate.and_then(|k| k.split_once('@')) {
+ if version.is_some() {
+ anyhow::bail!("cannot specify both `@{v}` and `--version`");
+ }
+ if k.is_empty() {
+ // by convention, arguments starting with `@` are response files
+ anyhow::bail!("missing crate name for `@{v}`");
+ }
+ krate = Some(k);
+ version = Some(v);
+ }
+ Ok((krate, version))
+}
diff --git a/src/bin/cargo/main.rs b/src/bin/cargo/main.rs
new file mode 100644
index 0000000..28cd18d
--- /dev/null
+++ b/src/bin/cargo/main.rs
@@ -0,0 +1,322 @@
+#![warn(rust_2018_idioms)] // while we're getting used to 2018
+#![allow(clippy::all)]
+
+use cargo::util::toml::StringOrVec;
+use cargo::util::CliError;
+use cargo::util::{self, closest_msg, command_prelude, CargoResult, CliResult, Config};
+use cargo_util::{ProcessBuilder, ProcessError};
+use std::collections::BTreeMap;
+use std::env;
+use std::ffi::OsStr;
+use std::fs;
+use std::path::{Path, PathBuf};
+
+mod cli;
+mod commands;
+
+use crate::command_prelude::*;
+
+fn main() {
+ #[cfg(feature = "pretty-env-logger")]
+ pretty_env_logger::init_custom_env("CARGO_LOG");
+ #[cfg(not(feature = "pretty-env-logger"))]
+ env_logger::init_from_env("CARGO_LOG");
+
+ let mut config = cli::LazyConfig::new();
+
+ let result = if let Some(lock_addr) = cargo::ops::fix_get_proxy_lock_addr() {
+ cargo::ops::fix_exec_rustc(config.get(), &lock_addr).map_err(|e| CliError::from(e))
+ } else {
+ let _token = cargo::util::job::setup();
+ cli::main(&mut config)
+ };
+
+ match result {
+ Err(e) => cargo::exit_with_error(e, &mut config.get_mut().shell()),
+ Ok(()) => {}
+ }
+}
+
+/// 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); 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
+/// corresponding execs represented as &str.
+fn builtin_aliases_execs(cmd: &str) -> Option<&(&str, &str, &str)> {
+ BUILTIN_ALIASES.iter().find(|alias| alias.0 == cmd)
+}
+
+/// Resolve the aliased command from the [`Config`] with a given command string.
+///
+/// The search fallback chain is:
+///
+/// 1. Get the aliased command as a string.
+/// 2. If an `Err` occurs (missing key, type mismatch, or any possible error),
+/// try to get it as an array again.
+/// 3. If still cannot find any, finds one insides [`BUILTIN_ALIASES`].
+fn aliased_command(config: &Config, command: &str) -> CargoResult<Option<Vec<String>>> {
+ let alias_name = format!("alias.{}", command);
+ let user_alias = match config.get_string(&alias_name) {
+ Ok(Some(record)) => Some(
+ record
+ .val
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect(),
+ ),
+ Ok(None) => None,
+ Err(_) => config.get::<Option<Vec<String>>>(&alias_name)?,
+ };
+
+ let result = user_alias.or_else(|| {
+ builtin_aliases_execs(command).map(|command_str| vec![command_str.1.to_string()])
+ });
+ Ok(result)
+}
+
+/// List all runnable commands
+fn list_commands(config: &Config) -> BTreeMap<String, CommandInfo> {
+ let prefix = "cargo-";
+ let suffix = env::consts::EXE_SUFFIX;
+ let mut commands = BTreeMap::new();
+ for dir in search_directories(config) {
+ let entries = match fs::read_dir(dir) {
+ Ok(entries) => entries,
+ _ => continue,
+ };
+ for entry in entries.filter_map(|e| e.ok()) {
+ let path = entry.path();
+ let filename = match path.file_name().and_then(|s| s.to_str()) {
+ Some(filename) => filename,
+ _ => continue,
+ };
+ if !filename.starts_with(prefix) || !filename.ends_with(suffix) {
+ continue;
+ }
+ if is_executable(entry.path()) {
+ let end = filename.len() - suffix.len();
+ commands.insert(
+ filename[prefix.len()..end].to_string(),
+ CommandInfo::External { path: path.clone() },
+ );
+ }
+ }
+ }
+
+ for cmd in commands::builtin() {
+ commands.insert(
+ cmd.get_name().to_string(),
+ CommandInfo::BuiltIn {
+ about: cmd.get_about().map(|s| s.to_string()),
+ },
+ );
+ }
+
+ // Add the builtin_aliases and them descriptions to the
+ // `commands` `BTreeMap`.
+ for command in &BUILTIN_ALIASES {
+ commands.insert(
+ command.0.to_string(),
+ CommandInfo::BuiltIn {
+ about: Some(command.2.to_string()),
+ },
+ );
+ }
+
+ // Add the user-defined aliases
+ if let Ok(aliases) = config.get::<BTreeMap<String, StringOrVec>>("alias") {
+ for (name, target) in aliases.iter() {
+ commands.insert(
+ name.to_string(),
+ CommandInfo::Alias {
+ target: target.clone(),
+ },
+ );
+ }
+ }
+
+ // `help` is special, so it needs to be inserted separately.
+ commands.insert(
+ "help".to_string(),
+ CommandInfo::BuiltIn {
+ about: Some("Displays help for a cargo subcommand".to_string()),
+ },
+ );
+
+ commands
+}
+
+fn find_external_subcommand(config: &Config, cmd: &str) -> Option<PathBuf> {
+ let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX);
+ search_directories(config)
+ .iter()
+ .map(|dir| dir.join(&command_exe))
+ .find(|file| is_executable(file))
+}
+
+fn execute_external_subcommand(config: &Config, cmd: &str, args: &[&OsStr]) -> CliResult {
+ let path = find_external_subcommand(config, cmd);
+ let command = match path {
+ Some(command) => command,
+ None => {
+ let err = if cmd.starts_with('+') {
+ anyhow::format_err!(
+ "no such command: `{}`\n\n\t\
+ Cargo does not handle `+toolchain` directives.\n\t\
+ Did you mean to invoke `cargo` through `rustup` instead?",
+ cmd
+ )
+ } else {
+ let suggestions = list_commands(config);
+ let did_you_mean = closest_msg(cmd, suggestions.keys(), |c| c);
+
+ anyhow::format_err!(
+ "no such command: `{}`{}\n\n\t\
+ View all installed commands with `cargo --list`",
+ cmd,
+ did_you_mean
+ )
+ };
+
+ return Err(CliError::new(err, 101));
+ }
+ };
+ execute_subcommand(config, Some(&command), args)
+}
+
+fn execute_internal_subcommand(config: &Config, args: &[&OsStr]) -> CliResult {
+ execute_subcommand(config, None, args)
+}
+
+// This function is used to execute a subcommand. It is used to execute both
+// internal and external subcommands.
+// If `cmd_path` is `None`, then the subcommand is an internal subcommand.
+fn execute_subcommand(config: &Config, cmd_path: Option<&PathBuf>, args: &[&OsStr]) -> CliResult {
+ let cargo_exe = config.cargo_exe()?;
+ let mut cmd = match cmd_path {
+ Some(cmd_path) => ProcessBuilder::new(cmd_path),
+ None => ProcessBuilder::new(&cargo_exe),
+ };
+ cmd.env(cargo::CARGO_ENV, cargo_exe).args(args);
+ if let Some(client) = config.jobserver_from_env() {
+ cmd.inherit_jobserver(client);
+ }
+ let err = match cmd.exec_replace() {
+ Ok(()) => return Ok(()),
+ Err(e) => e,
+ };
+
+ if let Some(perr) = err.downcast_ref::<ProcessError>() {
+ if let Some(code) = perr.code {
+ return Err(CliError::code(code));
+ }
+ }
+ Err(CliError::new(err, 101))
+}
+
+#[cfg(unix)]
+fn is_executable<P: AsRef<Path>>(path: P) -> bool {
+ use std::os::unix::prelude::*;
+ fs::metadata(path)
+ .map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
+ .unwrap_or(false)
+}
+#[cfg(windows)]
+fn is_executable<P: AsRef<Path>>(path: P) -> bool {
+ path.as_ref().is_file()
+}
+
+fn search_directories(config: &Config) -> Vec<PathBuf> {
+ let mut path_dirs = if let Some(val) = config.get_env_os("PATH") {
+ env::split_paths(&val).collect()
+ } else {
+ vec![]
+ };
+
+ let home_bin = config.home().clone().into_path_unlocked().join("bin");
+
+ // If any of that PATH elements contains `home_bin`, do not
+ // add it again. This is so that the users can control priority
+ // of it using PATH, while preserving the historical
+ // behavior of preferring it over system global directories even
+ // when not in PATH at all.
+ // See https://github.com/rust-lang/cargo/issues/11020 for details.
+ //
+ // Note: `p == home_bin` will ignore trailing slash, but we don't
+ // `canonicalize` the paths.
+ if !path_dirs.iter().any(|p| p == &home_bin) {
+ path_dirs.insert(0, home_bin);
+ };
+
+ path_dirs
+}
+
+/// Initialize libgit2.
+fn init_git(config: &Config) {
+ // Disabling the owner validation in git can, in theory, lead to code execution
+ // vulnerabilities. However, libgit2 does not launch executables, which is the foundation of
+ // the original security issue. Meanwhile, issues with refusing to load git repos in
+ // `CARGO_HOME` for example will likely be very frustrating for users. So, we disable the
+ // validation.
+ //
+ // For further discussion of Cargo's current interactions with git, see
+ //
+ // https://github.com/rust-lang/rfcs/pull/3279
+ //
+ // and in particular the subsection on "Git support".
+ //
+ // Note that we only disable this when Cargo is run as a binary. If Cargo is used as a library,
+ // this code won't be invoked. Instead, developers will need to explicitly disable the
+ // validation in their code. This is inconvenient, but won't accidentally open consuming
+ // applications up to security issues if they use git2 to open repositories elsewhere in their
+ // code.
+ unsafe {
+ git2::opts::set_verify_owner_validation(false)
+ .expect("set_verify_owner_validation should never fail");
+ }
+
+ init_git_transports(config);
+}
+
+/// Configure libgit2 to use libcurl if necessary.
+///
+/// If the user has a non-default network configuration, then libgit2 will be
+/// configured to use libcurl instead of the built-in networking support so
+/// that those configuration settings can be used.
+fn init_git_transports(config: &Config) {
+ // Only use a custom transport if any HTTP options are specified,
+ // such as proxies or custom certificate authorities. The custom
+ // transport, however, is not as well battle-tested.
+
+ match cargo::ops::needs_custom_http_transport(config) {
+ Ok(true) => {}
+ _ => return,
+ }
+
+ let handle = match cargo::ops::http_handle(config) {
+ Ok(handle) => handle,
+ Err(..) => return,
+ };
+
+ // The unsafety of the registration function derives from two aspects:
+ //
+ // 1. This call must be synchronized with all other registration calls as
+ // well as construction of new transports.
+ // 2. The argument is leaked.
+ //
+ // We're clear on point (1) because this is only called at the start of this
+ // binary (we know what the state of the world looks like) and we're mostly
+ // clear on point (2) because we'd only free it after everything is done
+ // anyway
+ unsafe {
+ git2_curl::register(handle);
+ }
+}