Skip to main content

cargo/util/
command_prelude.rs

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