| use anyhow::{anyhow, Context as _}; |
| use cargo::core::{features, CliUnstable}; |
| use cargo::util::context::TermConfig; |
| use cargo::{drop_print, drop_println, CargoResult}; |
| use clap::builder::UnknownArgumentValueParser; |
| 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 crate::util::is_rustup; |
| use cargo::core::shell::ColorChoice; |
| use cargo::util::style; |
| |
| #[tracing::instrument(skip_all)] |
| pub fn main(gctx: &mut GlobalContext) -> CliResult { |
| // 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 args = cli(gctx).try_get_matches()?; |
| |
| // Update the process-level notion of cwd |
| if let Some(new_cwd) = args.get_one::<std::path::PathBuf>("directory") { |
| // This is a temporary hack. |
| // This cannot access `GlobalContext`, 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")?; |
| gctx.reload_cwd()?; |
| } |
| |
| let (expanded_args, global_args) = expand_aliases(gctx, args, vec![])?; |
| |
| let is_verbose = expanded_args.verbose() > 0; |
| |
| if expanded_args |
| .get_one::<String>("unstable-features") |
| .map(String::as_str) |
| == Some("help") |
| { |
| // Don't let config errors get in the way of parsing arguments |
| let _ = configure_gctx(gctx, &expanded_args, None, global_args, None); |
| print_zhelp(gctx); |
| } else if expanded_args.flag("version") { |
| // Don't let config errors get in the way of parsing arguments |
| let _ = configure_gctx(gctx, &expanded_args, None, global_args, None); |
| let version = get_version_string(is_verbose); |
| drop_print!(gctx, "{}", version); |
| } else if let Some(code) = expanded_args.get_one::<String>("explain") { |
| // Don't let config errors get in the way of parsing arguments |
| let _ = configure_gctx(gctx, &expanded_args, None, global_args, None); |
| let mut procss = gctx.load_global_rustc(None)?.process(); |
| procss.arg("--explain").arg(code).exec()?; |
| } else if expanded_args.flag("list") { |
| // Don't let config errors get in the way of parsing arguments |
| let _ = configure_gctx(gctx, &expanded_args, None, global_args, None); |
| print_list(gctx, is_verbose); |
| } else { |
| let (cmd, subcommand_args) = match expanded_args.subcommand() { |
| Some((cmd, args)) => (cmd, args), |
| _ => { |
| // No subcommand provided. |
| cli(gctx).print_help()?; |
| return Ok(()); |
| } |
| }; |
| let exec = Exec::infer(cmd)?; |
| configure_gctx( |
| gctx, |
| &expanded_args, |
| Some(subcommand_args), |
| global_args, |
| Some(&exec), |
| )?; |
| super::init_git(gctx); |
| |
| exec.exec(gctx, subcommand_args)?; |
| } |
| Ok(()) |
| } |
| |
| fn print_zhelp(gctx: &GlobalContext) { |
| let header = style::HEADER; |
| let literal = style::LITERAL; |
| let placeholder = style::PLACEHOLDER; |
| |
| let options = CliUnstable::help(); |
| let max_length = options |
| .iter() |
| .filter(|(_, help)| help.is_some()) |
| .map(|(option_name, _)| option_name.len()) |
| .max() |
| .unwrap_or(0); |
| let z_flags = options |
| .iter() |
| .filter(|(_, help)| help.is_some()) |
| .map(|(opt, help)| { |
| let opt = opt.replace("_", "-"); |
| let help = help.unwrap(); |
| format!(" {literal}-Z {opt:<max_length$}{literal:#} {help}") |
| }) |
| .join("\n"); |
| drop_println!( |
| gctx, |
| "\ |
| {header}Available unstable (nightly-only) flags:{header:#} |
| |
| {z_flags} |
| |
| Run with `{literal}cargo -Z{literal:#} {placeholder}[FLAG] [COMMAND]{placeholder:#}`", |
| ); |
| if !gctx.nightly_features_allowed { |
| drop_println!( |
| gctx, |
| "\nUnstable flags are only available on the nightly channel \ |
| of Cargo, but this is the `{}` channel.\n\ |
| {}", |
| features::channel(), |
| features::SEE_CHANNELS |
| ); |
| } |
| drop_println!( |
| gctx, |
| "\nSee https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \ |
| for more information about these flags." |
| ); |
| } |
| |
| fn print_list(gctx: &GlobalContext, is_verbose: bool) { |
| // 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). |
| let known_external_command_descriptions = 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.", |
| ), |
| ]); |
| drop_println!( |
| gctx, |
| color_print::cstr!("<green,bold>Installed Commands:</>") |
| ); |
| for (name, command) in list_commands(gctx) { |
| let known_external_desc = known_external_command_descriptions.get(name.as_str()); |
| let literal = style::LITERAL; |
| 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!(gctx, " {literal}{name:<20}{literal:#} {summary}"); |
| } |
| CommandInfo::External { path } => { |
| if let Some(desc) = known_external_desc { |
| drop_println!(gctx, " {literal}{name:<20}{literal:#} {desc}"); |
| } else if is_verbose { |
| drop_println!( |
| gctx, |
| " {literal}{name:<20}{literal:#} {}", |
| path.display() |
| ); |
| } else { |
| drop_println!(gctx, " {literal}{name}{literal:#}"); |
| } |
| } |
| CommandInfo::Alias { target } => { |
| drop_println!( |
| gctx, |
| " {literal}{name:<20}{literal:#} alias: {}", |
| target.iter().join(" ") |
| ); |
| } |
| } |
| } |
| } |
| |
| 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). |
| #[tracing::instrument(skip_all)] |
| fn expand_aliases( |
| gctx: &mut GlobalContext, |
| args: ArgMatches, |
| mut already_expanded: Vec<String>, |
| ) -> Result<(ArgMatches, GlobalArgs), CliError> { |
| if let Some((cmd, sub_args)) = args.subcommand() { |
| let exec = commands::builtin_exec(cmd); |
| let aliased_cmd = super::aliased_command(gctx, cmd); |
| |
| match (exec, aliased_cmd) { |
| (Some(_), Ok(Some(_))) => { |
| // User alias conflicts with a built-in subcommand |
| gctx.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) = sub_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(gctx, cmd) { |
| gctx.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(), |
| ))?; |
| } |
| } |
| if commands::run::is_manifest_command(cmd) { |
| if gctx.cli_unstable().script { |
| return Ok((args, GlobalArgs::default())); |
| } else { |
| gctx.shell().warn(format_args!( |
| "\ |
| user-defined alias `{cmd}` has the appearance of a manifest-command |
| This was previously accepted but will be phased out when `-Zscript` is stabilized. |
| For more information, see issue #12207 <https://github.com/rust-lang/cargo/issues/12207>." |
| ))?; |
| } |
| } |
| |
| let mut alias = alias |
| .into_iter() |
| .map(|s| OsString::from(s)) |
| .collect::<Vec<_>>(); |
| alias.extend( |
| sub_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(sub_args); |
| let new_args = cli(gctx).no_binary_name(true).try_get_matches_from(alias)?; |
| |
| let Some(new_cmd) = new_args.subcommand_name() else { |
| return Err(anyhow!( |
| "subcommand is required, add a subcommand to the command alias `alias.{cmd}`" |
| ) |
| .into()); |
| }; |
| |
| 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(gctx, new_args, already_expanded)?; |
| return Ok((expanded_args, global_args)); |
| } |
| (None, Err(e)) => return Err(e.into()), |
| } |
| }; |
| |
| Ok((args, GlobalArgs::default())) |
| } |
| |
| #[tracing::instrument(skip_all)] |
| fn configure_gctx( |
| gctx: &mut GlobalContext, |
| args: &ArgMatches, |
| subcommand_args: Option<&ArgMatches>, |
| global_args: GlobalArgs, |
| exec: Option<&Exec>, |
| ) -> CliResult { |
| let arg_target_dir = &subcommand_args.and_then(|a| a.value_of_path("target-dir", gctx)); |
| let mut verbose = global_args.verbose + args.verbose(); |
| // quiet is unusual because it is redefined in some subcommands in order |
| // to provide custom help text. |
| let mut quiet = args.flag("quiet") |
| || subcommand_args.map(|a| a.flag("quiet")).unwrap_or_default() |
| || global_args.quiet; |
| if matches!(exec, Some(Exec::Manifest(_))) && !quiet { |
| // Verbosity is shifted quieter for `Exec::Manifest` as it is can be used as if you ran |
| // `cargo install` and we especially shouldn't pollute programmatic output. |
| // |
| // For now, interactive output has the same default output as `cargo run` but that is |
| // subject to change. |
| if let Some(lower) = verbose.checked_sub(1) { |
| verbose = lower; |
| } else if !gctx.shell().is_err_tty() { |
| // Don't pollute potentially-scripted output |
| quiet = true; |
| } |
| } |
| 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()); |
| } |
| gctx.configure( |
| verbose, |
| quiet, |
| color, |
| frozen, |
| locked, |
| offline, |
| arg_target_dir, |
| &unstable_flags, |
| &config_args, |
| )?; |
| Ok(()) |
| } |
| |
| enum Exec { |
| Builtin(commands::Exec), |
| Manifest(String), |
| External(String), |
| } |
| |
| impl Exec { |
| /// Precedence isn't the most obvious from this function because |
| /// - Some is determined by `expand_aliases` |
| /// - Some is enforced by `avoid_ambiguity_between_builtins_and_manifest_commands` |
| /// |
| /// In actuality, it is: |
| /// 1. built-ins xor manifest-command |
| /// 2. aliases |
| /// 3. external subcommands |
| fn infer(cmd: &str) -> CargoResult<Self> { |
| if let Some(exec) = commands::builtin_exec(cmd) { |
| Ok(Self::Builtin(exec)) |
| } else if commands::run::is_manifest_command(cmd) { |
| Ok(Self::Manifest(cmd.to_owned())) |
| } else { |
| Ok(Self::External(cmd.to_owned())) |
| } |
| } |
| |
| #[tracing::instrument(skip_all)] |
| fn exec(self, gctx: &mut GlobalContext, subcommand_args: &ArgMatches) -> CliResult { |
| match self { |
| Self::Builtin(exec) => exec(gctx, subcommand_args), |
| Self::Manifest(cmd) => { |
| let ext_path = super::find_external_subcommand(gctx, &cmd); |
| if !gctx.cli_unstable().script && ext_path.is_some() { |
| gctx.shell().warn(format_args!( |
| "\ |
| external subcommand `{cmd}` has the appearance of a manifest-command |
| This was previously accepted but will be phased out when `-Zscript` is stabilized. |
| For more information, see issue #12207 <https://github.com/rust-lang/cargo/issues/12207>.", |
| ))?; |
| Self::External(cmd).exec(gctx, subcommand_args) |
| } else { |
| let ext_args: Vec<OsString> = subcommand_args |
| .get_many::<OsString>("") |
| .unwrap_or_default() |
| .cloned() |
| .collect(); |
| commands::run::exec_manifest_command(gctx, &cmd, &ext_args) |
| } |
| } |
| Self::External(cmd) => { |
| let mut ext_args = vec![OsStr::new(&cmd)]; |
| ext_args.extend( |
| subcommand_args |
| .get_many::<OsString>("") |
| .unwrap_or_default() |
| .map(OsString::as_os_str), |
| ); |
| super::execute_external_subcommand(gctx, &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(), |
| } |
| } |
| } |
| |
| #[tracing::instrument(skip_all)] |
| pub fn cli(gctx: &GlobalContext) -> Command { |
| // Don't let config errors get in the way of parsing arguments |
| let term = gctx.get::<TermConfig>("term").unwrap_or_default(); |
| let color = term |
| .color |
| .and_then(|c| c.parse().ok()) |
| .unwrap_or(ColorChoice::CargoAuto); |
| let color = match color { |
| ColorChoice::Always => clap::ColorChoice::Always, |
| ColorChoice::Never => clap::ColorChoice::Never, |
| ColorChoice::CargoAuto => clap::ColorChoice::Auto, |
| }; |
| |
| let usage = if is_rustup() { |
| color_print::cstr!("<cyan,bold>cargo</> <cyan>[+toolchain] [OPTIONS] [COMMAND]</>\n <cyan,bold>cargo</> <cyan>[+toolchain] [OPTIONS]</> <cyan,bold>-Zscript</> <cyan><<MANIFEST_RS>> [ARGS]...</>") |
| } else { |
| color_print::cstr!("<cyan,bold>cargo</> <cyan>[OPTIONS] [COMMAND]</>\n <cyan,bold>cargo</> <cyan>[OPTIONS]</> <cyan,bold>-Zscript</> <cyan><<MANIFEST_RS>> [ARGS]...</>") |
| }; |
| |
| let styles = { |
| clap::builder::styling::Styles::styled() |
| .header(style::HEADER) |
| .usage(style::USAGE) |
| .literal(style::LITERAL) |
| .placeholder(style::PLACEHOLDER) |
| .error(style::ERROR) |
| .valid(style::VALID) |
| .invalid(style::INVALID) |
| }; |
| |
| Command::new("cargo") |
| // Subcommands all count their args' display order independently (from 0), |
| // which makes their args interspersed with global args. This puts global args last. |
| // |
| // We also want these to come before auto-generated `--help` |
| .next_display_order(800) |
| .allow_external_subcommands(true) |
| .color(color) |
| .styles(styles) |
| // Provide a custom help subcommand for calling into man pages |
| .disable_help_subcommand(true) |
| .override_usage(usage) |
| .help_template(color_print::cstr!( |
| "\ |
| Rust's package manager |
| |
| <green,bold>Usage:</> {usage} |
| |
| <green,bold>Options:</> |
| {options} |
| |
| <green,bold>Commands:</> |
| <cyan,bold>build</>, <cyan,bold>b</> Compile the current package |
| <cyan,bold>check</>, <cyan,bold>c</> Analyze the current package and report errors, but don't build object files |
| <cyan,bold>clean</> Remove the target directory |
| <cyan,bold>doc</>, <cyan,bold>d</> Build this package's and its dependencies' documentation |
| <cyan,bold>new</> Create a new cargo package |
| <cyan,bold>init</> Create a new cargo package in an existing directory |
| <cyan,bold>add</> Add dependencies to a manifest file |
| <cyan,bold>remove</> Remove dependencies from a manifest file |
| <cyan,bold>run</>, <cyan,bold>r</> Run a binary or example of the local package |
| <cyan,bold>test</>, <cyan,bold>t</> Run the tests |
| <cyan,bold>bench</> Run the benchmarks |
| <cyan,bold>update</> Update dependencies listed in Cargo.lock |
| <cyan,bold>search</> Search registry for crates |
| <cyan,bold>publish</> Package and upload this package to the registry |
| <cyan,bold>install</> Install a Rust binary |
| <cyan,bold>uninstall</> Uninstall a Rust binary |
| <cyan>...</> See all commands with <cyan,bold>--list</> |
| |
| See '<cyan,bold>cargo help</> <cyan><<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", |
| "Provide a detailed explanation of a rustc error message", |
| ) |
| .value_name("CODE"), |
| ) |
| .arg( |
| opt( |
| "verbose", |
| "Use verbose output (-vv very verbose/build.rs output)", |
| ) |
| .short('v') |
| .action(ArgAction::Count) |
| .global(true), |
| ) |
| .arg(flag("quiet", "Do not print cargo log messages").short('q').global(true)) |
| .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("locked", "Assert that `Cargo.lock` will remain unchanged") |
| .help_heading(heading::MANIFEST_OPTIONS) |
| .global(true), |
| ) |
| .arg( |
| flag("offline", "Run without accessing the network") |
| .help_heading(heading::MANIFEST_OPTIONS) |
| .global(true), |
| ) |
| .arg( |
| flag("frozen", "Equivalent to specifying both --locked and --offline") |
| .help_heading(heading::MANIFEST_OPTIONS) |
| .global(true), |
| ) |
| // Better suggestion for the unsupported short config flag. |
| .arg( Arg::new("unsupported-short-config-flag") |
| .help("") |
| .short('c') |
| .value_parser(UnknownArgumentValueParser::suggest_arg("--config")) |
| .action(ArgAction::SetTrue) |
| .global(true) |
| .hide(true)) |
| .arg(multi_opt("config", "KEY=VALUE", "Override a configuration value").global(true)) |
| // Better suggestion for the unsupported lowercase unstable feature flag. |
| .arg( Arg::new("unsupported-lowercase-unstable-feature-flag") |
| .help("") |
| .short('z') |
| .value_parser(UnknownArgumentValueParser::suggest_arg("-Z")) |
| .action(ArgAction::SetTrue) |
| .global(true) |
| .hide(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()) |
| } |
| |
| #[test] |
| fn verify_cli() { |
| let gctx = GlobalContext::default().unwrap(); |
| cli(&gctx).debug_assert(); |
| } |
| |
| #[test] |
| fn avoid_ambiguity_between_builtins_and_manifest_commands() { |
| for cmd in commands::builtin() { |
| let name = cmd.get_name(); |
| assert!( |
| !commands::run::is_manifest_command(&name), |
| "built-in command {name} is ambiguous with manifest-commands" |
| ) |
| } |
| } |