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