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