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