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