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