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