cargo/util/
command_prelude.rs

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