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}