cargo/util/
command_prelude.rs

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