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