#
cargo/util/
command_prelude.rs

    1use crate::CargoResult;
    2use crate::core::Dependency;
    3use crate::core::compiler::{
    4    BuildConfig, CompileKind, MessageFormat, RustcTargetData, TimingOutput,
    5};
    6use crate::core::resolver::{CliFeatures, ForceAllTargets, HasDevUnits};
    7use crate::core::{Edition, Package, TargetKind, Workspace, profiles::Profiles, shell};
    8use crate::ops::lockfile::LOCKFILE_NAME;
    9use crate::ops::registry::RegistryOrIndex;
    10use crate::ops::{self, CompileFilter, CompileOptions, NewOptions, Packages, VersionControl};
    11use crate::util::important_paths::find_root_manifest_for_wd;
    12use crate::util::interning::InternedString;
    13use crate::util::is_rustup;
    14use crate::util::restricted_names;
    15use crate::util::toml::is_embedded;
    16use crate::util::{
    17    print_available_benches, print_available_binaries, print_available_examples,
    18    print_available_packages, print_available_tests,
    19};
    20use anyhow::bail;
    21use cargo_util::paths;
    22use cargo_util_schemas::manifest::ProfileName;
    23use cargo_util_schemas::manifest::RegistryName;
    24use cargo_util_schemas::manifest::StringOrVec;
    25use clap::builder::UnknownArgumentValueParser;
    26use clap_complete::ArgValueCandidates;
    27use home::cargo_home_with_cwd;
    28use indexmap::IndexSet;
    29use itertools::Itertools;
    30use semver::Version;
    31use std::collections::{BTreeMap, HashMap, HashSet};
    32use std::ffi::{OsStr, OsString};
    33use std::path::Path;
    34use std::path::PathBuf;
    35
    36pub use crate::core::compiler::UserIntent;
    37pub use crate::{CliError, CliResult, GlobalContext};
    38pub use clap::{Arg, ArgAction, ArgMatches, value_parser};
    39
    40pub use clap::Command;
    41
    42use super::IntoUrl;
    43use super::context::JobsConfig;
    44
    45pub mod heading {
    46    pub const PACKAGE_SELECTION: &str = "Package Selection";
    47    pub const TARGET_SELECTION: &str = "Target Selection";
    48    pub const FEATURE_SELECTION: &str = "Feature Selection";
    49    pub const COMPILATION_OPTIONS: &str = "Compilation Options";
    50    pub const MANIFEST_OPTIONS: &str = "Manifest Options";
    51}
    52
    53pub trait CommandExt: Sized {
    54    fn _arg(self, arg: Arg) -> Self;
    55
    56    /// Do not use this method, it is only for backwards compatibility.
    57    /// Use `arg_package_spec_no_all` instead.
    58    fn arg_package_spec(
    59        self,
    60        package: &'static str,
    61        all: &'static str,
    62        exclude: &'static str,
    63    ) -> Self {
    64        self.arg_package_spec_no_all(
    65            package,
    66            all,
    67            exclude,
    68            ArgValueCandidates::new(get_ws_member_candidates),
    69        )
    70        ._arg(
    71            flag("all", "Alias for --workspace (deprecated)")
    72                .help_heading(heading::PACKAGE_SELECTION),
    73        )
    74    }
    75
    76    /// Variant of `arg_package_spec` that does not include the `--all` flag
    77    /// (but does include `--workspace`). Used to avoid confusion with
    78    /// historical uses of `--all`.
    79    fn arg_package_spec_no_all(
    80        self,
    81        package: &'static str,
    82        all: &'static str,
    83        exclude: &'static str,
    84        package_completion: ArgValueCandidates,
    85    ) -> Self {
    86        let unsupported_short_arg = {
    87            let value_parser = UnknownArgumentValueParser::suggest_arg("--exclude");
    88            Arg::new("unsupported-short-exclude-flag")
    89                .help("")
    90                .short('x')
    91                .value_parser(value_parser)
    92                .action(ArgAction::SetTrue)
    93                .hide(true)
    94        };
    95        self.arg_package_spec_simple(package, package_completion)
    96            ._arg(flag("workspace", all).help_heading(heading::PACKAGE_SELECTION))
    97            ._arg(
    98                multi_opt("exclude", "SPEC", exclude)
    99                    .help_heading(heading::PACKAGE_SELECTION)
    100                    .add(clap_complete::ArgValueCandidates::new(
    101                        get_ws_member_candidates,
    102                    )),
    103            )
    104            ._arg(unsupported_short_arg)
    105    }
    106
    107    fn arg_package_spec_simple(
    108        self,
    109        package: &'static str,
    110        package_completion: ArgValueCandidates,
    111    ) -> Self {
    112        self._arg(
    113            optional_multi_opt("package", "SPEC", package)
    114                .short('p')
    115                .help_heading(heading::PACKAGE_SELECTION)
    116                .add(package_completion),
    117        )
    118    }
    119
    120    fn arg_package(self, package: &'static str) -> Self {
    121        self._arg(
    122            optional_opt("package", package)
    123                .short('p')
    124                .value_name("SPEC")
    125                .help_heading(heading::PACKAGE_SELECTION)
    126                .add(clap_complete::ArgValueCandidates::new(|| {
    127                    get_ws_member_candidates()
    128                })),
    129        )
    130    }
    131
    132    fn arg_parallel(self) -> Self {
    133        self.arg_jobs()._arg(
    134            flag(
    135                "keep-going",
    136                "Do not abort the build as soon as there is an error",
    137            )
    138            .help_heading(heading::COMPILATION_OPTIONS),
    139        )
    140    }
    141
    142    fn arg_jobs(self) -> Self {
    143        self._arg(
    144            opt("jobs", "Number of parallel jobs, defaults to # of CPUs.")
    145                .short('j')
    146                .value_name("N")
    147                .allow_hyphen_values(true)
    148                .help_heading(heading::COMPILATION_OPTIONS),
    149        )
    150    }
    151
    152    fn arg_unsupported_keep_going(self) -> Self {
    153        let msg = "use `--no-fail-fast` to run as many tests as possible regardless of failure";
    154        let value_parser = UnknownArgumentValueParser::suggest(msg);
    155        self._arg(flag("keep-going", "").value_parser(value_parser).hide(true))
    156    }
    157
    158    fn arg_redundant_default_mode(
    159        self,
    160        default_mode: &'static str,
    161        command: &'static str,
    162        supported_mode: &'static str,
    163    ) -> Self {
    164        let msg = format!(
    165            "`--{default_mode}` is the default for `cargo {command}`; instead `--{supported_mode}` is supported"
    166        );
    167        let value_parser = UnknownArgumentValueParser::suggest(msg);
    168        self._arg(
    169            flag(default_mode, "")
    170                .conflicts_with("profile")
    171                .value_parser(value_parser)
    172                .hide(true),
    173        )
    174    }
    175
    176    fn arg_targets_all(
    177        self,
    178        lib: &'static str,
    179        bin: &'static str,
    180        bins: &'static str,
    181        example: &'static str,
    182        examples: &'static str,
    183        test: &'static str,
    184        tests: &'static str,
    185        bench: &'static str,
    186        benches: &'static str,
    187        all: &'static str,
    188    ) -> Self {
    189        self.arg_targets_lib_bin_example(lib, bin, bins, example, examples)
    190            ._arg(flag("tests", tests).help_heading(heading::TARGET_SELECTION))
    191            ._arg(
    192                optional_multi_opt("test", "NAME", test)
    193                    .help_heading(heading::TARGET_SELECTION)
    194                    .add(clap_complete::ArgValueCandidates::new(|| {
    195                        get_crate_candidates(TargetKind::Test).unwrap_or_default()
    196                    })),
    197            )
    198            ._arg(flag("benches", benches).help_heading(heading::TARGET_SELECTION))
    199            ._arg(
    200                optional_multi_opt("bench", "NAME", bench)
    201                    .help_heading(heading::TARGET_SELECTION)
    202                    .add(clap_complete::ArgValueCandidates::new(|| {
    203                        get_crate_candidates(TargetKind::Bench).unwrap_or_default()
    204                    })),
    205            )
    206            ._arg(flag("all-targets", all).help_heading(heading::TARGET_SELECTION))
    207    }
    208
    209    fn arg_targets_lib_bin_example(
    210        self,
    211        lib: &'static str,
    212        bin: &'static str,
    213        bins: &'static str,
    214        example: &'static str,
    215        examples: &'static str,
    216    ) -> Self {
    217        self._arg(flag("lib", lib).help_heading(heading::TARGET_SELECTION))
    218            ._arg(flag("bins", bins).help_heading(heading::TARGET_SELECTION))
    219            ._arg(
    220                optional_multi_opt("bin", "NAME", bin)
    221                    .help_heading(heading::TARGET_SELECTION)
    222                    .add(clap_complete::ArgValueCandidates::new(|| {
    223                        get_crate_candidates(TargetKind::Bin).unwrap_or_default()
    224                    })),
    225            )
    226            ._arg(flag("examples", examples).help_heading(heading::TARGET_SELECTION))
    227            ._arg(
    228                optional_multi_opt("example", "NAME", example)
    229                    .help_heading(heading::TARGET_SELECTION)
    230                    .add(clap_complete::ArgValueCandidates::new(|| {
    231                        get_crate_candidates(TargetKind::ExampleBin).unwrap_or_default()
    232                    })),
    233            )
    234    }
    235
    236    fn arg_targets_bins_examples(
    237        self,
    238        bin: &'static str,
    239        bins: &'static str,
    240        example: &'static str,
    241        examples: &'static str,
    242    ) -> Self {
    243        self._arg(
    244            optional_multi_opt("bin", "NAME", bin)
    245                .help_heading(heading::TARGET_SELECTION)
    246                .add(clap_complete::ArgValueCandidates::new(|| {
    247                    get_crate_candidates(TargetKind::Bin).unwrap_or_default()
    248                })),
    249        )
    250        ._arg(flag("bins", bins).help_heading(heading::TARGET_SELECTION))
    251        ._arg(
    252            optional_multi_opt("example", "NAME", example)
    253                .help_heading(heading::TARGET_SELECTION)
    254                .add(clap_complete::ArgValueCandidates::new(|| {
    255                    get_crate_candidates(TargetKind::ExampleBin).unwrap_or_default()
    256                })),
    257        )
    258        ._arg(flag("examples", examples).help_heading(heading::TARGET_SELECTION))
    259    }
    260
    261    fn arg_targets_bin_example(self, bin: &'static str, example: &'static str) -> Self {
    262        self._arg(
    263            optional_multi_opt("bin", "NAME", bin)
    264                .help_heading(heading::TARGET_SELECTION)
    265                .add(clap_complete::ArgValueCandidates::new(|| {
    266                    get_crate_candidates(TargetKind::Bin).unwrap_or_default()
    267                })),
    268        )
    269        ._arg(
    270            optional_multi_opt("example", "NAME", example)
    271                .help_heading(heading::TARGET_SELECTION)
    272                .add(clap_complete::ArgValueCandidates::new(|| {
    273                    get_crate_candidates(TargetKind::ExampleBin).unwrap_or_default()
    274                })),
    275        )
    276    }
    277
    278    fn arg_features(self) -> Self {
    279        self._arg(
    280            multi_opt(
    281                "features",
    282                "FEATURES",
    283                "Space or comma separated list of features to activate",
    284            )
    285            .short('F')
    286            .help_heading(heading::FEATURE_SELECTION)
    287            .add(clap_complete::ArgValueCandidates::new(|| {
    288                get_feature_candidates().unwrap_or_default()
    289            })),
    290        )
    291        ._arg(
    292            flag("all-features", "Activate all available features")
    293                .help_heading(heading::FEATURE_SELECTION),
    294        )
    295        ._arg(
    296            flag(
    297                "no-default-features",
    298                "Do not activate the `default` feature",
    299            )
    300            .help_heading(heading::FEATURE_SELECTION),
    301        )
    302    }
    303
    304    fn arg_release(self, release: &'static str) -> Self {
    305        self._arg(
    306            flag("release", release)
    307                .short('r')
    308                .conflicts_with("profile")
    309                .help_heading(heading::COMPILATION_OPTIONS),
    310        )
    311    }
    312
    313    fn arg_profile(self, profile: &'static str) -> Self {
    314        self._arg(
    315            opt("profile", profile)
    316                .value_name("PROFILE-NAME")
    317                .help_heading(heading::COMPILATION_OPTIONS)
    318                .add(clap_complete::ArgValueCandidates::new(|| {
    319                    let candidates = get_profile_candidates();
    320                    candidates
    321                })),
    322        )
    323    }
    324
    325    fn arg_doc(self, doc: &'static str) -> Self {
    326        self._arg(flag("doc", doc))
    327    }
    328
    329    fn arg_target_triple(self, target: &'static str) -> Self {
    330        self.arg_target_triple_with_candidates(target, ArgValueCandidates::new(get_target_triples))
    331    }
    332
    333    fn arg_target_triple_with_candidates(
    334        self,
    335        target: &'static str,
    336        target_completion: ArgValueCandidates,
    337    ) -> Self {
    338        let unsupported_short_arg = {
    339            let value_parser = UnknownArgumentValueParser::suggest_arg("--target");
    340            Arg::new("unsupported-short-target-flag")
    341                .help("")
    342                .short('t')
    343                .value_parser(value_parser)
    344                .action(ArgAction::SetTrue)
    345                .hide(true)
    346        };
    347        self._arg(
    348            optional_multi_opt("target", "TRIPLE", target)
    349                .help_heading(heading::COMPILATION_OPTIONS)
    350                .add(target_completion),
    351        )
    352        ._arg(unsupported_short_arg)
    353    }
    354
    355    fn arg_target_dir(self) -> Self {
    356        self._arg(
    357            opt("target-dir", "Directory for all generated artifacts")
    358                .value_name("DIRECTORY")
    359                .help_heading(heading::COMPILATION_OPTIONS),
    360        )
    361    }
    362
    363    fn arg_manifest_path(self) -> Self {
    364        // We use `--manifest-path` instead of `--path`.
    365        let unsupported_path_arg = {
    366            let value_parser = UnknownArgumentValueParser::suggest_arg("--manifest-path");
    367            flag("unsupported-path-flag", "")
    368                .long("path")
    369                .value_parser(value_parser)
    370                .hide(true)
    371        };
    372        self.arg_manifest_path_without_unsupported_path_tip()
    373            ._arg(unsupported_path_arg)
    374    }
    375
    376    // `cargo add` has a `--path` flag to install a crate from a local path.
    377    fn arg_manifest_path_without_unsupported_path_tip(self) -> Self {
    378        self._arg(
    379            opt("manifest-path", "Path to Cargo.toml")
    380                .value_name("PATH")
    381                .help_heading(heading::MANIFEST_OPTIONS)
    382                .add(clap_complete::engine::ArgValueCompleter::new(
    383                    clap_complete::engine::PathCompleter::any().filter(|path: &Path| {
    384                        if path.file_name() == Some(OsStr::new("Cargo.toml")) {
    385                            return true;
    386                        }
    387                        if is_embedded(path) {
    388                            return true;
    389                        }
    390                        false
    391                    }),
    392                )),
    393        )
    394    }
    395
    396    fn arg_lockfile_path(self) -> Self {
    397        self._arg(
    398            opt("lockfile-path", "Path to Cargo.lock (unstable)")
    399                .value_name("PATH")
    400                .help_heading(heading::MANIFEST_OPTIONS)
    401                .add(clap_complete::engine::ArgValueCompleter::new(
    402                    clap_complete::engine::PathCompleter::any().filter(|path: &Path| {
    403                        let file_name = match path.file_name() {
    404                            Some(name) => name,
    405                            None => return false,
    406                        };
    407
    408                        // allow `Cargo.lock` file
    409                        file_name == OsStr::new("Cargo.lock")
    410                    }),
    411                )),
    412        )
    413    }
    414
    415    fn arg_message_format(self) -> Self {
    416        self._arg(
    417            multi_opt("message-format", "FMT", "Error format")
    418                .value_parser([
    419                    "human",
    420                    "short",
    421                    "json",
    422                    "json-diagnostic-short",
    423                    "json-diagnostic-rendered-ansi",
    424                    "json-render-diagnostics",
    425                ])
    426                .value_delimiter(',')
    427                .ignore_case(true),
    428        )
    429    }
    430
    431    fn arg_unit_graph(self) -> Self {
    432        self._arg(
    433            flag("unit-graph", "Output build graph in JSON (unstable)")
    434                .help_heading(heading::COMPILATION_OPTIONS),
    435        )
    436    }
    437
    438    fn arg_new_opts(self) -> Self {
    439        self._arg(
    440            opt(
    441                "vcs",
    442                "Initialize a new repository for the given version \
    443                 control system, overriding \
    444                 a global configuration.",
    445            )
    446            .value_name("VCS")
    447            .value_parser(["git", "hg", "pijul", "fossil", "none"]),
    448        )
    449        ._arg(flag("bin", "Use a binary (application) template [default]"))
    450        ._arg(flag("lib", "Use a library template"))
    451        ._arg(
    452            opt("edition", "Edition to set for the crate generated")
    453                .value_parser(Edition::CLI_VALUES)
    454                .value_name("YEAR"),
    455        )
    456        ._arg(
    457            opt(
    458                "name",
    459                "Set the resulting package name, defaults to the directory name",
    460            )
    461            .value_name("NAME"),
    462        )
    463    }
    464
    465    fn arg_registry(self, help: &'static str) -> Self {
    466        self._arg(opt("registry", help).value_name("REGISTRY").add(
    467            clap_complete::ArgValueCandidates::new(|| {
    468                let candidates = get_registry_candidates();
    469                candidates.unwrap_or_default()
    470            }),
    471        ))
    472    }
    473
    474    fn arg_index(self, help: &'static str) -> Self {
    475        // Always conflicts with `--registry`.
    476        self._arg(
    477            opt("index", help)
    478                .value_name("INDEX")
    479                .conflicts_with("registry"),
    480        )
    481    }
    482
    483    fn arg_dry_run(self, dry_run: &'static str) -> Self {
    484        self._arg(flag("dry-run", dry_run).short('n'))
    485    }
    486
    487    fn arg_ignore_rust_version(self) -> Self {
    488        self.arg_ignore_rust_version_with_help("Ignore `rust-version` specification in packages")
    489    }
    490
    491    fn arg_ignore_rust_version_with_help(self, help: &'static str) -> Self {
    492        self._arg(flag("ignore-rust-version", help).help_heading(heading::MANIFEST_OPTIONS))
    493    }
    494
    495    fn arg_future_incompat_report(self) -> Self {
    496        self._arg(flag(
    497            "future-incompat-report",
    498            "Outputs a future incompatibility report at the end of the build",
    499        ))
    500    }
    501
    502    /// Adds a suggestion for the `--silent` or `-s` flags to use the
    503    /// `--quiet` flag instead. This is to help with people familiar with
    504    /// other tools that use `-s`.
    505    ///
    506    /// Every command should call this, unless it has its own `-s` short flag.
    507    fn arg_silent_suggestion(self) -> Self {
    508        let value_parser = UnknownArgumentValueParser::suggest_arg("--quiet");
    509        self._arg(
    510            flag("silent", "")
    511                .short('s')
    512                .value_parser(value_parser)
    513                .hide(true),
    514        )
    515    }
    516
    517    fn arg_timings(self) -> Self {
    518        self._arg(
    519            optional_opt(
    520                "timings",
    521                "Timing output formats (unstable) (comma separated): html, json",
    522            )
    523            .value_name("FMTS")
    524            .require_equals(true)
    525            .help_heading(heading::COMPILATION_OPTIONS),
    526        )
    527    }
    528
    529    fn arg_artifact_dir(self) -> Self {
    530        let unsupported_short_arg = {
    531            let value_parser = UnknownArgumentValueParser::suggest_arg("--artifact-dir");
    532            Arg::new("unsupported-short-artifact-dir-flag")
    533                .help("")
    534                .short('O')
    535                .value_parser(value_parser)
    536                .action(ArgAction::SetTrue)
    537                .hide(true)
    538        };
    539
    540        self._arg(
    541            opt(
    542                "artifact-dir",
    543                "Copy final artifacts to this directory (unstable)",
    544            )
    545            .value_name("PATH")
    546            .help_heading(heading::COMPILATION_OPTIONS),
    547        )
    548        ._arg(unsupported_short_arg)
    549        ._arg(
    550            opt(
    551                "out-dir",
    552                "Copy final artifacts to this directory (deprecated; use --artifact-dir instead)",
    553            )
    554            .value_name("PATH")
    555            .conflicts_with("artifact-dir")
    556            .hide(true),
    557        )
    558    }
    559
    560    fn arg_compile_time_deps(self) -> Self {
    561        self._arg(flag("compile-time-deps", "").hide(true))
    562    }
    563}
    564
    565impl CommandExt for Command {
    566    fn _arg(self, arg: Arg) -> Self {
    567        self.arg(arg)
    568    }
    569}
    570
    571pub fn flag(name: &'static str, help: &'static str) -> Arg {
    572    Arg::new(name)
    573        .long(name)
    574        .help(help)
    575        .action(ArgAction::SetTrue)
    576}
    577
    578pub fn opt(name: &'static str, help: &'static str) -> Arg {
    579    Arg::new(name).long(name).help(help).action(ArgAction::Set)
    580}
    581
    582pub fn optional_opt(name: &'static str, help: &'static str) -> Arg {
    583    opt(name, help).num_args(0..=1)
    584}
    585
    586pub fn optional_multi_opt(name: &'static str, value_name: &'static str, help: &'static str) -> Arg {
    587    opt(name, help)
    588        .value_name(value_name)
    589        .num_args(0..=1)
    590        .action(ArgAction::Append)
    591}
    592
    593pub fn multi_opt(name: &'static str, value_name: &'static str, help: &'static str) -> Arg {
    594    opt(name, help)
    595        .value_name(value_name)
    596        .action(ArgAction::Append)
    597}
    598
    599pub fn subcommand(name: &'static str) -> Command {
    600    Command::new(name)
    601}
    602
    603/// Determines whether or not to gate `--profile` as unstable when resolving it.
    604pub enum ProfileChecking {
    605    /// `cargo rustc` historically has allowed "test", "bench", and "check". This
    606    /// variant explicitly allows those.
    607    LegacyRustc,
    608    /// `cargo check` and `cargo fix` historically has allowed "test". This variant
    609    /// explicitly allows that on stable.
    610    LegacyTestOnly,
    611    /// All other commands, which allow any valid custom named profile.
    612    Custom,
    613}
    614
    615pub trait ArgMatchesExt {
    616    fn value_of_u32(&self, name: &str) -> CargoResult<Option<u32>> {
    617        let arg = match self._value_of(name) {
    618            None => None,
    619            Some(arg) => Some(arg.parse::<u32>().map_err(|_| {
    620                clap::Error::raw(
    621                    clap::error::ErrorKind::ValueValidation,
    622                    format!("Invalid value: could not parse `{}` as a number", arg),
    623                )
    624            })?),
    625        };
    626        Ok(arg)
    627    }
    628
    629    fn value_of_i32(&self, name: &str) -> CargoResult<Option<i32>> {
    630        let arg = match self._value_of(name) {
    631            None => None,
    632            Some(arg) => Some(arg.parse::<i32>().map_err(|_| {
    633                clap::Error::raw(
    634                    clap::error::ErrorKind::ValueValidation,
    635                    format!("Invalid value: could not parse `{}` as a number", arg),
    636                )
    637            })?),
    638        };
    639        Ok(arg)
    640    }
    641
    642    /// Returns value of the `name` command-line argument as an absolute path
    643    fn value_of_path(&self, name: &str, gctx: &GlobalContext) -> Option<PathBuf> {
    644        self._value_of(name).map(|path| gctx.cwd().join(path))
    645    }
    646
    647    fn root_manifest(&self, gctx: &GlobalContext) -> CargoResult<PathBuf> {
    648        root_manifest(self._value_of("manifest-path").map(Path::new), gctx)
    649    }
    650
    651    fn lockfile_path(&self, gctx: &GlobalContext) -> CargoResult<Option<PathBuf>> {
    652        lockfile_path(self._value_of("lockfile-path").map(Path::new), gctx)
    653    }
    654
    655    #[tracing::instrument(skip_all)]
    656    fn workspace<'a>(&self, gctx: &'a GlobalContext) -> CargoResult<Workspace<'a>> {
    657        let root = self.root_manifest(gctx)?;
    658        let lockfile_path = self.lockfile_path(gctx)?;
    659        let mut ws = Workspace::new(&root, gctx)?;
    660        ws.set_resolve_honors_rust_version(self.honor_rust_version());
    661        if gctx.cli_unstable().avoid_dev_deps {
    662            ws.set_require_optional_deps(false);
    663        }
    664        ws.set_requested_lockfile_path(lockfile_path);
    665        Ok(ws)
    666    }
    667
    668    fn jobs(&self) -> CargoResult<Option<JobsConfig>> {
    669        let arg = match self._value_of("jobs") {
    670            None => None,
    671            Some(arg) => match arg.parse::<i32>() {
    672                Ok(j) => Some(JobsConfig::Integer(j)),
    673                Err(_) => Some(JobsConfig::String(arg.to_string())),
    674            },
    675        };
    676
    677        Ok(arg)
    678    }
    679
    680    fn verbose(&self) -> u32 {
    681        self._count("verbose")
    682    }
    683
    684    fn dry_run(&self) -> bool {
    685        self.flag("dry-run")
    686    }
    687
    688    fn keep_going(&self) -> bool {
    689        self.maybe_flag("keep-going")
    690    }
    691
    692    fn honor_rust_version(&self) -> Option<bool> {
    693        self.flag("ignore-rust-version").then_some(false)
    694    }
    695
    696    fn targets(&self) -> CargoResult<Vec<String>> {
    697        if self.is_present_with_zero_values("target") {
    698            let cmd = if is_rustup() {
    699                "rustup target list"
    700            } else {
    701                "rustc --print target-list"
    702            };
    703            bail!(
    704                "\"--target\" takes a target architecture as an argument.
    705
    706Run `{cmd}` to see possible targets."
    707            );
    708        }
    709        Ok(self._values_of("target"))
    710    }
    711
    712    fn get_profile_name(
    713        &self,
    714        default: &str,
    715        profile_checking: ProfileChecking,
    716    ) -> CargoResult<InternedString> {
    717        let specified_profile = self._value_of("profile");
    718
    719        // Check for allowed legacy names.
    720        // This is an early exit, since it allows combination with `--release`.
    721        match (specified_profile, profile_checking) {
    722            // `cargo rustc` has legacy handling of these names
    723            (Some(name @ ("dev" | "test" | "bench" | "check")), ProfileChecking::LegacyRustc)
    724            // `cargo fix` and `cargo check` has legacy handling of this profile name
    725            | (Some(name @ "test"), ProfileChecking::LegacyTestOnly) => {
    726                return Ok(name.into());
    727            }
    728            _ => {}
    729        }
    730
    731        let name = match (
    732            self.maybe_flag("release"),
    733            self.maybe_flag("debug"),
    734            specified_profile,
    735        ) {
    736            (false, false, None) => default,
    737            (true, _, None) => "release",
    738            (_, true, None) => "dev",
    739            // `doc` is separate from all the other reservations because
    740            // [profile.doc] was historically allowed, but is deprecated and
    741            // has no effect. To avoid potentially breaking projects, it is a
    742            // warning in Cargo.toml, but since `--profile` is new, we can
    743            // reject it completely here.
    744            (_, _, Some("doc")) => {
    745                bail!("profile `doc` is reserved and not allowed to be explicitly specified")
    746            }
    747            (_, _, Some(name)) => {
    748                ProfileName::new(name)?;
    749                name
    750            }
    751        };
    752
    753        Ok(name.into())
    754    }
    755
    756    fn packages_from_flags(&self) -> CargoResult<Packages> {
    757        Packages::from_flags(
    758            // TODO Integrate into 'workspace'
    759            self.flag("workspace") || self.flag("all"),
    760            self._values_of("exclude"),
    761            self._values_of("package"),
    762        )
    763    }
    764
    765    fn compile_options(
    766        &self,
    767        gctx: &GlobalContext,
    768        intent: UserIntent,
    769        workspace: Option<&Workspace<'_>>,
    770        profile_checking: ProfileChecking,
    771    ) -> CargoResult<CompileOptions> {
    772        let spec = self.packages_from_flags()?;
    773        let mut message_format = None;
    774        let default_json = MessageFormat::Json {
    775            short: false,
    776            ansi: false,
    777            render_diagnostics: false,
    778        };
    779        let two_kinds_of_msg_format_err = "cannot specify two kinds of `message-format` arguments";
    780        for fmt in self._values_of("message-format") {
    781            for fmt in fmt.split(',') {
    782                let fmt = fmt.to_ascii_lowercase();
    783                match fmt.as_str() {
    784                    "json" => {
    785                        if message_format.is_some() {
    786                            bail!(two_kinds_of_msg_format_err);
    787                        }
    788                        message_format = Some(default_json);
    789                    }
    790                    "human" => {
    791                        if message_format.is_some() {
    792                            bail!(two_kinds_of_msg_format_err);
    793                        }
    794                        message_format = Some(MessageFormat::Human);
    795                    }
    796                    "short" => {
    797                        if message_format.is_some() {
    798                            bail!(two_kinds_of_msg_format_err);
    799                        }
    800                        message_format = Some(MessageFormat::Short);
    801                    }
    802                    "json-render-diagnostics" => {
    803                        if message_format.is_none() {
    804                            message_format = Some(default_json);
    805                        }
    806                        match &mut message_format {
    807                            Some(MessageFormat::Json {
    808                                render_diagnostics, ..
    809                            }) => *render_diagnostics = true,
    810                            _ => bail!(two_kinds_of_msg_format_err),
    811                        }
    812                    }
    813                    "json-diagnostic-short" => {
    814                        if message_format.is_none() {
    815                            message_format = Some(default_json);
    816                        }
    817                        match &mut message_format {
    818                            Some(MessageFormat::Json { short, .. }) => *short = true,
    819                            _ => bail!(two_kinds_of_msg_format_err),
    820                        }
    821                    }
    822                    "json-diagnostic-rendered-ansi" => {
    823                        if message_format.is_none() {
    824                            message_format = Some(default_json);
    825                        }
    826                        match &mut message_format {
    827                            Some(MessageFormat::Json { ansi, .. }) => *ansi = true,
    828                            _ => bail!(two_kinds_of_msg_format_err),
    829                        }
    830                    }
    831                    s => bail!("invalid message format specifier: `{}`", s),
    832                }
    833            }
    834        }
    835
    836        let mut build_config = BuildConfig::new(
    837            gctx,
    838            self.jobs()?,
    839            self.keep_going(),
    840            &self.targets()?,
    841            intent,
    842        )?;
    843        build_config.message_format = message_format.unwrap_or(MessageFormat::Human);
    844        build_config.requested_profile = self.get_profile_name("dev", profile_checking)?;
    845        build_config.unit_graph = self.flag("unit-graph");
    846        build_config.future_incompat_report = self.flag("future-incompat-report");
    847        build_config.compile_time_deps_only = self.flag("compile-time-deps");
    848
    849        if self._contains("timings") {
    850            for timing_output in self._values_of("timings") {
    851                for timing_output in timing_output.split(',') {
    852                    let timing_output = timing_output.to_ascii_lowercase();
    853                    let timing_output = match timing_output.as_str() {
    854                        "html" => {
    855                            gctx.cli_unstable()
    856                                .fail_if_stable_opt("--timings=html", 7405)?;
    857                            TimingOutput::Html
    858                        }
    859                        "json" => {
    860                            gctx.cli_unstable()
    861                                .fail_if_stable_opt("--timings=json", 7405)?;
    862                            TimingOutput::Json
    863                        }
    864                        s => bail!("invalid timings output specifier: `{}`", s),
    865                    };
    866                    build_config.timing_outputs.push(timing_output);
    867                }
    868            }
    869            if build_config.timing_outputs.is_empty() {
    870                build_config.timing_outputs.push(TimingOutput::Html);
    871            }
    872        }
    873
    874        if build_config.unit_graph {
    875            gctx.cli_unstable()
    876                .fail_if_stable_opt("--unit-graph", 8002)?;
    877        }
    878        if build_config.compile_time_deps_only {
    879            gctx.cli_unstable()
    880                .fail_if_stable_opt("--compile-time-deps", 14434)?;
    881        }
    882
    883        let opts = CompileOptions {
    884            build_config,
    885            cli_features: self.cli_features()?,
    886            spec,
    887            filter: CompileFilter::from_raw_arguments(
    888                self.flag("lib"),
    889                self._values_of("bin"),
    890                self.flag("bins"),
    891                self._values_of("test"),
    892                self.flag("tests"),
    893                self._values_of("example"),
    894                self.flag("examples"),
    895                self._values_of("bench"),
    896                self.flag("benches"),
    897                self.flag("all-targets"),
    898            ),
    899            target_rustdoc_args: None,
    900            target_rustc_args: None,
    901            target_rustc_crate_types: None,
    902            rustdoc_document_private_items: false,
    903            honor_rust_version: self.honor_rust_version(),
    904        };
    905
    906        if let Some(ws) = workspace {
    907            self.check_optional_opts(ws, &opts)?;
    908        } else if self.is_present_with_zero_values("package") {
    909            // As for cargo 0.50.0, this won't occur but if someone sneaks in
    910            // we can still provide this informative message for them.
    911            anyhow::bail!(
    912                "\"--package <SPEC>\" requires a SPEC format value, \
    913                which can be any package ID specifier in the dependency graph.\n\
    914                Run `cargo help pkgid` for more information about SPEC format."
    915            )
    916        }
    917
    918        Ok(opts)
    919    }
    920
    921    fn cli_features(&self) -> CargoResult<CliFeatures> {
    922        CliFeatures::from_command_line(
    923            &self._values_of("features"),
    924            self.flag("all-features"),
    925            !self.flag("no-default-features"),
    926        )
    927    }
    928
    929    fn compile_options_for_single_package(
    930        &self,
    931        gctx: &GlobalContext,
    932        intent: UserIntent,
    933        workspace: Option<&Workspace<'_>>,
    934        profile_checking: ProfileChecking,
    935    ) -> CargoResult<CompileOptions> {
    936        let mut compile_opts = self.compile_options(gctx, intent, workspace, profile_checking)?;
    937        let spec = self._values_of("package");
    938        if spec.iter().any(restricted_names::is_glob_pattern) {
    939            anyhow::bail!("Glob patterns on package selection are not supported.")
    940        }
    941        compile_opts.spec = Packages::Packages(spec);
    942        Ok(compile_opts)
    943    }
    944
    945    fn new_options(&self, gctx: &GlobalContext) -> CargoResult<NewOptions> {
    946        let vcs = self._value_of("vcs").map(|vcs| match vcs {
    947            "git" => VersionControl::Git,
    948            "hg" => VersionControl::Hg,
    949            "pijul" => VersionControl::Pijul,
    950            "fossil" => VersionControl::Fossil,
    951            "none" => VersionControl::NoVcs,
    952            vcs => panic!("Impossible vcs: {:?}", vcs),
    953        });
    954        NewOptions::new(
    955            vcs,
    956            self.flag("bin"),
    957            self.flag("lib"),
    958            self.value_of_path("path", gctx).unwrap(),
    959            self._value_of("name").map(|s| s.to_string()),
    960            self._value_of("edition").map(|s| s.to_string()),
    961            self.registry(gctx)?,
    962        )
    963    }
    964
    965    fn registry_or_index(&self, gctx: &GlobalContext) -> CargoResult<Option<RegistryOrIndex>> {
    966        let registry = self._value_of("registry");
    967        let index = self._value_of("index");
    968        let result = match (registry, index) {
    969            (None, None) => gctx.default_registry()?.map(RegistryOrIndex::Registry),
    970            (None, Some(i)) => Some(RegistryOrIndex::Index(i.into_url()?)),
    971            (Some(r), None) => {
    972                RegistryName::new(r)?;
    973                Some(RegistryOrIndex::Registry(r.to_string()))
    974            }
    975            (Some(_), Some(_)) => {
    976                // Should be guarded by clap
    977                unreachable!("both `--index` and `--registry` should not be set at the same time")
    978            }
    979        };
    980        Ok(result)
    981    }
    982
    983    fn registry(&self, gctx: &GlobalContext) -> CargoResult<Option<String>> {
    984        match self._value_of("registry").map(|s| s.to_string()) {
    985            None => gctx.default_registry(),
    986            Some(registry) => {
    987                RegistryName::new(&registry)?;
    988                Ok(Some(registry))
    989            }
    990        }
    991    }
    992
    993    fn check_optional_opts(
    994        &self,
    995        workspace: &Workspace<'_>,
    996        compile_opts: &CompileOptions,
    997    ) -> CargoResult<()> {
    998        if self.is_present_with_zero_values("package") {
    999            print_available_packages(workspace)?
    1000        }
    1001
    1002        if self.is_present_with_zero_values("example") {
    1003            print_available_examples(workspace, compile_opts)?;
    1004        }
    1005
    1006        if self.is_present_with_zero_values("bin") {
    1007            print_available_binaries(workspace, compile_opts)?;
    1008        }
    1009
    1010        if self.is_present_with_zero_values("bench") {
    1011            print_available_benches(workspace, compile_opts)?;
    1012        }
    1013
    1014        if self.is_present_with_zero_values("test") {
    1015            print_available_tests(workspace, compile_opts)?;
    1016        }
    1017
    1018        Ok(())
    1019    }
    1020
    1021    fn is_present_with_zero_values(&self, name: &str) -> bool {
    1022        self._contains(name) && self._value_of(name).is_none()
    1023    }
    1024
    1025    fn flag(&self, name: &str) -> bool;
    1026
    1027    fn maybe_flag(&self, name: &str) -> bool;
    1028
    1029    fn _value_of(&self, name: &str) -> Option<&str>;
    1030
    1031    fn _values_of(&self, name: &str) -> Vec<String>;
    1032
    1033    fn _value_of_os(&self, name: &str) -> Option<&OsStr>;
    1034
    1035    fn _values_of_os(&self, name: &str) -> Vec<OsString>;
    1036
    1037    fn _count(&self, name: &str) -> u32;
    1038
    1039    fn _contains(&self, name: &str) -> bool;
    1040}
    1041
    1042impl<'a> ArgMatchesExt for ArgMatches {
    1043    fn flag(&self, name: &str) -> bool {
    1044        ignore_unknown(self.try_get_one::<bool>(name))
    1045            .copied()
    1046            .unwrap_or(false)
    1047    }
    1048
    1049    // This works around before an upstream fix in clap for `UnknownArgumentValueParser` accepting
    1050    // generics arguments. `flag()` cannot be used with `--keep-going` at this moment due to
    1051    // <https://github.com/clap-rs/clap/issues/5081>.
    1052    fn maybe_flag(&self, name: &str) -> bool {
    1053        self.try_get_one::<bool>(name)
    1054            .ok()
    1055            .flatten()
    1056            .copied()
    1057            .unwrap_or_default()
    1058    }
    1059
    1060    fn _value_of(&self, name: &str) -> Option<&str> {
    1061        ignore_unknown(self.try_get_one::<String>(name)).map(String::as_str)
    1062    }
    1063
    1064    fn _value_of_os(&self, name: &str) -> Option<&OsStr> {
    1065        ignore_unknown(self.try_get_one::<OsString>(name)).map(OsString::as_os_str)
    1066    }
    1067
    1068    fn _values_of(&self, name: &str) -> Vec<String> {
    1069        ignore_unknown(self.try_get_many::<String>(name))
    1070            .unwrap_or_default()
    1071            .cloned()
    1072            .collect()
    1073    }
    1074
    1075    fn _values_of_os(&self, name: &str) -> Vec<OsString> {
    1076        ignore_unknown(self.try_get_many::<OsString>(name))
    1077            .unwrap_or_default()
    1078            .cloned()
    1079            .collect()
    1080    }
    1081
    1082    fn _count(&self, name: &str) -> u32 {
    1083        *ignore_unknown(self.try_get_one::<u8>(name)).expect("defaulted by clap") as u32
    1084    }
    1085
    1086    fn _contains(&self, name: &str) -> bool {
    1087        ignore_unknown(self.try_contains_id(name))
    1088    }
    1089}
    1090
    1091pub fn values(args: &ArgMatches, name: &str) -> Vec<String> {
    1092    args._values_of(name)
    1093}
    1094
    1095pub fn values_os(args: &ArgMatches, name: &str) -> Vec<OsString> {
    1096    args._values_of_os(name)
    1097}
    1098
    1099pub fn root_manifest(manifest_path: Option<&Path>, gctx: &GlobalContext) -> CargoResult<PathBuf> {
    1100    if let Some(manifest_path) = manifest_path {
    1101        let path = gctx.cwd().join(manifest_path);
    1102        // In general, we try to avoid normalizing paths in Cargo,
    1103        // but in this particular case we need it to fix #3586.
    1104        let path = paths::normalize_path(&path);
    1105        if !path.ends_with("Cargo.toml") && !crate::util::toml::is_embedded(&path) {
    1106            anyhow::bail!(
    1107                "the manifest-path must be a path to a Cargo.toml file: `{}`",
    1108                path.display()
    1109            )
    1110        }
    1111        if !path.exists() {
    1112            anyhow::bail!("manifest path `{}` does not exist", manifest_path.display())
    1113        }
    1114        if path.is_dir() {
    1115            anyhow::bail!(
    1116                "manifest path `{}` is a directory but expected a file",
    1117                manifest_path.display()
    1118            )
    1119        }
    1120        if crate::util::toml::is_embedded(&path) && !gctx.cli_unstable().script {
    1121            anyhow::bail!("embedded manifest `{}` requires `-Zscript`", path.display())
    1122        }
    1123        Ok(path)
    1124    } else {
    1125        find_root_manifest_for_wd(gctx.cwd())
    1126    }
    1127}
    1128
    1129pub fn lockfile_path(
    1130    lockfile_path: Option<&Path>,
    1131    gctx: &GlobalContext,
    1132) -> CargoResult<Option<PathBuf>> {
    1133    let Some(lockfile_path) = lockfile_path else {
    1134        return Ok(None);
    1135    };
    1136
    1137    gctx.cli_unstable()
    1138        .fail_if_stable_opt("--lockfile-path", 14421)?;
    1139
    1140    let path = gctx.cwd().join(lockfile_path);
    1141
    1142    if !path.ends_with(LOCKFILE_NAME) {
    1143        bail!(
    1144            "the lockfile-path must be a path to a {LOCKFILE_NAME} file (please rename your lock file to {LOCKFILE_NAME})"
    1145        )
    1146    }
    1147    if path.is_dir() {
    1148        bail!(
    1149            "lockfile path `{}` is a directory but expected a file",
    1150            lockfile_path.display()
    1151        )
    1152    }
    1153
    1154    return Ok(Some(path));
    1155}
    1156
    1157pub fn get_registry_candidates() -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
    1158    let gctx = new_gctx_for_completions()?;
    1159
    1160    if let Ok(Some(registries)) =
    1161        gctx.get::<Option<HashMap<String, HashMap<String, String>>>>("registries")
    1162    {
    1163        Ok(registries
    1164            .keys()
    1165            .map(|name| clap_complete::CompletionCandidate::new(name.to_owned()))
    1166            .collect())
    1167    } else {
    1168        Ok(vec![])
    1169    }
    1170}
    1171
    1172fn get_profile_candidates() -> Vec<clap_complete::CompletionCandidate> {
    1173    match get_workspace_profile_candidates() {
    1174        Ok(candidates) if !candidates.is_empty() => candidates,
    1175        // fallback to default profile candidates
    1176        _ => default_profile_candidates(),
    1177    }
    1178}
    1179
    1180fn get_workspace_profile_candidates() -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
    1181    let gctx = new_gctx_for_completions()?;
    1182    let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?;
    1183    let profiles = Profiles::new(&ws, "dev".into())?;
    1184
    1185    let mut candidates = Vec::new();
    1186    for name in profiles.profile_names() {
    1187        let Ok(profile_instance) = Profiles::new(&ws, name) else {
    1188            continue;
    1189        };
    1190        let base_profile = profile_instance.base_profile();
    1191
    1192        let mut description = String::from(if base_profile.opt_level.as_str() == "0" {
    1193            "unoptimized"
    1194        } else {
    1195            "optimized"
    1196        });
    1197
    1198        if base_profile.debuginfo.is_turned_on() {
    1199            description.push_str(" + debuginfo");
    1200        }
    1201
    1202        candidates
    1203            .push(clap_complete::CompletionCandidate::new(&name).help(Some(description.into())));
    1204    }
    1205
    1206    Ok(candidates)
    1207}
    1208
    1209fn default_profile_candidates() -> Vec<clap_complete::CompletionCandidate> {
    1210    vec![
    1211        clap_complete::CompletionCandidate::new("dev").help(Some("unoptimized + debuginfo".into())),
    1212        clap_complete::CompletionCandidate::new("release").help(Some("optimized".into())),
    1213        clap_complete::CompletionCandidate::new("test")
    1214            .help(Some("unoptimized + debuginfo".into())),
    1215        clap_complete::CompletionCandidate::new("bench").help(Some("optimized".into())),
    1216    ]
    1217}
    1218
    1219fn get_feature_candidates() -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
    1220    let gctx = new_gctx_for_completions()?;
    1221
    1222    let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?;
    1223    let mut feature_candidates = Vec::new();
    1224
    1225    // Process all packages in the workspace
    1226    for package in ws.members() {
    1227        let package_name = package.name();
    1228
    1229        // Add direct features with package info
    1230        for feature_name in package.summary().features().keys() {
    1231            let order = if ws.current_opt().map(|p| p.name()) == Some(package_name) {
    1232                0
    1233            } else {
    1234                1
    1235            };
    1236            feature_candidates.push(
    1237                clap_complete::CompletionCandidate::new(feature_name)
    1238                    .display_order(Some(order))
    1239                    .help(Some(format!("from {}", package_name).into())),
    1240            );
    1241        }
    1242    }
    1243
    1244    Ok(feature_candidates)
    1245}
    1246
    1247fn get_crate_candidates(kind: TargetKind) -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
    1248    let gctx = new_gctx_for_completions()?;
    1249
    1250    let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?;
    1251
    1252    let targets = ws
    1253        .members()
    1254        .flat_map(|pkg| pkg.targets().into_iter().cloned().map(|t| (pkg.name(), t)))
    1255        .filter(|(_, target)| *target.kind() == kind)
    1256        .map(|(pkg_name, target)| {
    1257            let order = if ws.current_opt().map(|p| p.name()) == Some(pkg_name) {
    1258                0
    1259            } else {
    1260                1
    1261            };
    1262            clap_complete::CompletionCandidate::new(target.name())
    1263                .display_order(Some(order))
    1264                .help(Some(format!("from {}", pkg_name).into()))
    1265        })
    1266        .collect::<Vec<_>>();
    1267
    1268    Ok(targets)
    1269}
    1270
    1271fn get_target_triples() -> Vec<clap_complete::CompletionCandidate> {
    1272    let mut candidates = Vec::new();
    1273
    1274    if let Ok(targets) = get_target_triples_from_rustup() {
    1275        candidates = targets;
    1276    }
    1277
    1278    if candidates.is_empty() {
    1279        if let Ok(targets) = get_target_triples_from_rustc() {
    1280            candidates = targets;
    1281        }
    1282    }
    1283
    1284    // Allow tab-completion for `host-tuple` as the desired target.
    1285    candidates.insert(
    1286        0,
    1287        clap_complete::CompletionCandidate::new("host-tuple").help(Some(
    1288            concat!("alias for: ", env!("RUST_HOST_TARGET")).into(),
    1289        )),
    1290    );
    1291
    1292    candidates
    1293}
    1294
    1295pub fn get_target_triples_with_all() -> Vec<clap_complete::CompletionCandidate> {
    1296    let mut candidates = vec![
    1297        clap_complete::CompletionCandidate::new("all").help(Some("Include all targets".into())),
    1298    ];
    1299    candidates.extend(get_target_triples());
    1300    candidates
    1301}
    1302
    1303fn get_target_triples_from_rustup() -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
    1304    let output = std::process::Command::new("rustup")
    1305        .arg("target")
    1306        .arg("list")
    1307        .output()?;
    1308
    1309    if !output.status.success() {
    1310        return Ok(vec![]);
    1311    }
    1312
    1313    let stdout = String::from_utf8(output.stdout)?;
    1314
    1315    Ok(stdout
    1316        .lines()
    1317        .map(|line| {
    1318            let target = line.split_once(' ');
    1319            match target {
    1320                None => clap_complete::CompletionCandidate::new(line.to_owned()).hide(true),
    1321                Some((target, _installed)) => clap_complete::CompletionCandidate::new(target),
    1322            }
    1323        })
    1324        .collect())
    1325}
    1326
    1327fn get_target_triples_from_rustc() -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
    1328    let gctx = new_gctx_for_completions()?;
    1329
    1330    let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx);
    1331
    1332    let rustc = gctx.load_global_rustc(ws.as_ref().ok())?;
    1333
    1334    let (stdout, _stderr) =
    1335        rustc.cached_output(rustc.process().arg("--print").arg("target-list"), 0)?;
    1336
    1337    Ok(stdout
    1338        .lines()
    1339        .map(|line| clap_complete::CompletionCandidate::new(line.to_owned()))
    1340        .collect())
    1341}
    1342
    1343pub fn get_ws_member_candidates() -> Vec<clap_complete::CompletionCandidate> {
    1344    get_ws_member_packages()
    1345        .unwrap_or_default()
    1346        .into_iter()
    1347        .map(|pkg| {
    1348            clap_complete::CompletionCandidate::new(pkg.name().as_str()).help(
    1349                pkg.manifest()
    1350                    .metadata()
    1351                    .description
    1352                    .to_owned()
    1353                    .map(From::from),
    1354            )
    1355        })
    1356        .collect::<Vec<_>>()
    1357}
    1358
    1359fn get_ws_member_packages() -> CargoResult<Vec<Package>> {
    1360    let gctx = new_gctx_for_completions()?;
    1361    let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?;
    1362    let packages = ws.members().map(Clone::clone).collect::<Vec<_>>();
    1363    Ok(packages)
    1364}
    1365
    1366pub fn get_pkg_id_spec_candidates() -> Vec<clap_complete::CompletionCandidate> {
    1367    let mut candidates = vec![];
    1368
    1369    let package_map = HashMap::<&str, Vec<Package>>::new();
    1370    let package_map =
    1371        get_packages()
    1372            .unwrap_or_default()
    1373            .into_iter()
    1374            .fold(package_map, |mut map, package| {
    1375                map.entry(package.name().as_str())
    1376                    .or_insert_with(Vec::new)
    1377                    .push(package);
    1378                map
    1379            });
    1380
    1381    let unique_name_candidates = package_map
    1382        .iter()
    1383        .filter(|(_name, packages)| packages.len() == 1)
    1384        .map(|(name, packages)| {
    1385            clap_complete::CompletionCandidate::new(name.to_string()).help(
    1386                packages[0]
    1387                    .manifest()
    1388                    .metadata()
    1389                    .description
    1390                    .to_owned()
    1391                    .map(From::from),
    1392            )
    1393        })
    1394        .collect::<Vec<_>>();
    1395
    1396    let duplicate_name_pairs = package_map
    1397        .iter()
    1398        .filter(|(_name, packages)| packages.len() > 1)
    1399        .collect::<Vec<_>>();
    1400
    1401    let mut duplicate_name_candidates = vec![];
    1402    for (name, packages) in duplicate_name_pairs {
    1403        let mut version_count: HashMap<&Version, usize> = HashMap::new();
    1404
    1405        for package in packages {
    1406            *version_count.entry(package.version()).or_insert(0) += 1;
    1407        }
    1408
    1409        for package in packages {
    1410            if let Some(&count) = version_count.get(package.version()) {
    1411                if count == 1 {
    1412                    duplicate_name_candidates.push(
    1413                        clap_complete::CompletionCandidate::new(format!(
    1414                            "{}@{}",
    1415                            name,
    1416                            package.version()
    1417                        ))
    1418                        .help(
    1419                            package
    1420                                .manifest()
    1421                                .metadata()
    1422                                .description
    1423                                .to_owned()
    1424                                .map(From::from),
    1425                        ),
    1426                    );
    1427                } else {
    1428                    duplicate_name_candidates.push(
    1429                        clap_complete::CompletionCandidate::new(format!(
    1430                            "{}",
    1431                            package.package_id().to_spec()
    1432                        ))
    1433                        .help(
    1434                            package
    1435                                .manifest()
    1436                                .metadata()
    1437                                .description
    1438                                .to_owned()
    1439                                .map(From::from),
    1440                        ),
    1441                    )
    1442                }
    1443            }
    1444        }
    1445    }
    1446
    1447    candidates.extend(unique_name_candidates);
    1448    candidates.extend(duplicate_name_candidates);
    1449
    1450    candidates
    1451}
    1452
    1453pub fn get_pkg_name_candidates() -> Vec<clap_complete::CompletionCandidate> {
    1454    let packages: BTreeMap<_, _> = get_packages()
    1455        .unwrap_or_default()
    1456        .into_iter()
    1457        .map(|package| {
    1458            (
    1459                package.name(),
    1460                package.manifest().metadata().description.clone(),
    1461            )
    1462        })
    1463        .collect();
    1464
    1465    packages
    1466        .into_iter()
    1467        .map(|(name, description)| {
    1468            clap_complete::CompletionCandidate::new(name.as_str()).help(description.map(From::from))
    1469        })
    1470        .collect()
    1471}
    1472
    1473fn get_packages() -> CargoResult<Vec<Package>> {
    1474    let gctx = new_gctx_for_completions()?;
    1475
    1476    let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?;
    1477
    1478    let requested_kinds = CompileKind::from_requested_targets(ws.gctx(), &[])?;
    1479    let mut target_data = RustcTargetData::new(&ws, &requested_kinds)?;
    1480    // `cli_features.all_features` must be true in case that `specs` is empty.
    1481    let cli_features = CliFeatures::new_all(true);
    1482    let has_dev_units = HasDevUnits::Yes;
    1483    let force_all_targets = ForceAllTargets::No;
    1484    let dry_run = true;
    1485
    1486    let ws_resolve = ops::resolve_ws_with_opts(
    1487        &ws,
    1488        &mut target_data,
    1489        &requested_kinds,
    1490        &cli_features,
    1491        &[],
    1492        has_dev_units,
    1493        force_all_targets,
    1494        dry_run,
    1495    )?;
    1496
    1497    let packages = ws_resolve
    1498        .pkg_set
    1499        .packages()
    1500        .map(Clone::clone)
    1501        .collect::<Vec<_>>();
    1502
    1503    Ok(packages)
    1504}
    1505
    1506pub fn get_direct_dependencies_pkg_name_candidates() -> Vec<clap_complete::CompletionCandidate> {
    1507    let (current_package_deps, all_package_deps) = match get_dependencies_from_metadata() {
    1508        Ok(v) => v,
    1509        Err(_) => return Vec::new(),
    1510    };
    1511
    1512    let current_package_deps_package_names = current_package_deps
    1513        .into_iter()
    1514        .map(|dep| dep.package_name().to_string())
    1515        .sorted();
    1516    let all_package_deps_package_names = all_package_deps
    1517        .into_iter()
    1518        .map(|dep| dep.package_name().to_string())
    1519        .sorted();
    1520
    1521    let mut package_names_set = IndexSet::new();
    1522    package_names_set.extend(current_package_deps_package_names);
    1523    package_names_set.extend(all_package_deps_package_names);
    1524
    1525    package_names_set
    1526        .into_iter()
    1527        .map(|name| name.into())
    1528        .collect_vec()
    1529}
    1530
    1531fn get_dependencies_from_metadata() -> CargoResult<(Vec<Dependency>, Vec<Dependency>)> {
    1532    let cwd = std::env::current_dir()?;
    1533    let gctx = GlobalContext::new(shell::Shell::new(), cwd.clone(), cargo_home_with_cwd(&cwd)?);
    1534    let ws = Workspace::new(&find_root_manifest_for_wd(&cwd)?, &gctx)?;
    1535    let current_package = ws.current().ok();
    1536
    1537    let current_package_dependencies = ws
    1538        .current()
    1539        .map(|current| current.dependencies())
    1540        .unwrap_or_default()
    1541        .to_vec();
    1542    let all_other_packages_dependencies = ws
    1543        .members()
    1544        .filter(|&member| Some(member) != current_package)
    1545        .flat_map(|pkg| pkg.dependencies().into_iter().cloned())
    1546        .collect::<HashSet<_>>()
    1547        .into_iter()
    1548        .collect::<Vec<_>>();
    1549
    1550    Ok((
    1551        current_package_dependencies,
    1552        all_other_packages_dependencies,
    1553    ))
    1554}
    1555
    1556pub fn new_gctx_for_completions() -> CargoResult<GlobalContext> {
    1557    let cwd = std::env::current_dir()?;
    1558    let mut gctx = GlobalContext::new(shell::Shell::new(), cwd.clone(), cargo_home_with_cwd(&cwd)?);
    1559
    1560    let verbose = 0;
    1561    let quiet = true;
    1562    let color = None;
    1563    let frozen = false;
    1564    let locked = true;
    1565    let offline = false;
    1566    let target_dir = None;
    1567    let unstable_flags = &[];
    1568    let cli_config = &[];
    1569
    1570    gctx.configure(
    1571        verbose,
    1572        quiet,
    1573        color,
    1574        frozen,
    1575        locked,
    1576        offline,
    1577        &target_dir,
    1578        unstable_flags,
    1579        cli_config,
    1580    )?;
    1581
    1582    Ok(gctx)
    1583}
    1584
    1585#[track_caller]
    1586pub fn ignore_unknown<T: Default>(r: Result<T, clap::parser::MatchesError>) -> T {
    1587    match r {
    1588        Ok(t) => t,
    1589        Err(clap::parser::MatchesError::UnknownArgument { .. }) => Default::default(),
    1590        Err(e) => {
    1591            panic!("Mismatch between definition and access: {}", e);
    1592        }
    1593    }
    1594}
    1595
    1596#[derive(PartialEq, Eq, PartialOrd, Ord)]
    1597pub enum CommandInfo {
    1598    BuiltIn { about: Option<String> },
    1599    External { path: PathBuf },
    1600    Alias { target: StringOrVec },
    1601}