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::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 )
279 }
280
281 fn arg_doc(self, doc: &'static str) -> Self {
282 self._arg(flag("doc", doc))
283 }
284
285 fn arg_target_triple(self, target: &'static str) -> Self {
286 let unsupported_short_arg = {
287 let value_parser = UnknownArgumentValueParser::suggest_arg("--target");
288 Arg::new("unsupported-short-target-flag")
289 .help("")
290 .short('t')
291 .value_parser(value_parser)
292 .action(ArgAction::SetTrue)
293 .hide(true)
294 };
295 self._arg(
296 optional_multi_opt("target", "TRIPLE", target)
297 .help_heading(heading::COMPILATION_OPTIONS)
298 .add(clap_complete::ArgValueCandidates::new(get_target_triples)),
299 )
300 ._arg(unsupported_short_arg)
301 }
302
303 fn arg_target_dir(self) -> Self {
304 self._arg(
305 opt("target-dir", "Directory for all generated artifacts")
306 .value_name("DIRECTORY")
307 .help_heading(heading::COMPILATION_OPTIONS),
308 )
309 }
310
311 fn arg_manifest_path(self) -> Self {
312 let unsupported_path_arg = {
314 let value_parser = UnknownArgumentValueParser::suggest_arg("--manifest-path");
315 flag("unsupported-path-flag", "")
316 .long("path")
317 .value_parser(value_parser)
318 .hide(true)
319 };
320 self.arg_manifest_path_without_unsupported_path_tip()
321 ._arg(unsupported_path_arg)
322 }
323
324 fn arg_manifest_path_without_unsupported_path_tip(self) -> Self {
326 self._arg(
327 opt("manifest-path", "Path to Cargo.toml")
328 .value_name("PATH")
329 .help_heading(heading::MANIFEST_OPTIONS)
330 .add(clap_complete::engine::ArgValueCompleter::new(
331 clap_complete::engine::PathCompleter::any().filter(|path: &Path| {
332 if path.file_name() == Some(OsStr::new("Cargo.toml")) {
333 return true;
334 }
335 if is_embedded(path) {
336 return true;
337 }
338 false
339 }),
340 )),
341 )
342 }
343
344 fn arg_lockfile_path(self) -> Self {
345 self._arg(
346 opt("lockfile-path", "Path to Cargo.lock (unstable)")
347 .value_name("PATH")
348 .help_heading(heading::MANIFEST_OPTIONS)
349 .add(clap_complete::engine::ArgValueCompleter::new(
350 clap_complete::engine::PathCompleter::any().filter(|path: &Path| {
351 let file_name = match path.file_name() {
352 Some(name) => name,
353 None => return false,
354 };
355
356 file_name == OsStr::new("Cargo.lock")
358 }),
359 )),
360 )
361 }
362
363 fn arg_message_format(self) -> Self {
364 self._arg(
365 multi_opt("message-format", "FMT", "Error format")
366 .value_parser([
367 "human",
368 "short",
369 "json",
370 "json-diagnostic-short",
371 "json-diagnostic-rendered-ansi",
372 "json-render-diagnostics",
373 ])
374 .value_delimiter(',')
375 .ignore_case(true),
376 )
377 }
378
379 fn arg_build_plan(self) -> Self {
380 self._arg(
381 flag("build-plan", "Output the build plan in JSON (unstable)")
382 .help_heading(heading::COMPILATION_OPTIONS),
383 )
384 }
385
386 fn arg_unit_graph(self) -> Self {
387 self._arg(
388 flag("unit-graph", "Output build graph in JSON (unstable)")
389 .help_heading(heading::COMPILATION_OPTIONS),
390 )
391 }
392
393 fn arg_new_opts(self) -> Self {
394 self._arg(
395 opt(
396 "vcs",
397 "Initialize a new repository for the given version \
398 control system, overriding \
399 a global configuration.",
400 )
401 .value_name("VCS")
402 .value_parser(["git", "hg", "pijul", "fossil", "none"]),
403 )
404 ._arg(
405 flag("bin", "Use a binary (application) template [default]")
406 .add(clap_complete::ArgValueCandidates::new(get_bin_candidates)),
407 )
408 ._arg(flag("lib", "Use a library template"))
409 ._arg(
410 opt("edition", "Edition to set for the crate generated")
411 .value_parser(Edition::CLI_VALUES)
412 .value_name("YEAR"),
413 )
414 ._arg(
415 opt(
416 "name",
417 "Set the resulting package name, defaults to the directory name",
418 )
419 .value_name("NAME"),
420 )
421 }
422
423 fn arg_registry(self, help: &'static str) -> Self {
424 self._arg(opt("registry", help).value_name("REGISTRY").add(
425 clap_complete::ArgValueCandidates::new(|| {
426 let candidates = get_registry_candidates();
427 candidates.unwrap_or_default()
428 }),
429 ))
430 }
431
432 fn arg_index(self, help: &'static str) -> Self {
433 self._arg(
435 opt("index", help)
436 .value_name("INDEX")
437 .conflicts_with("registry"),
438 )
439 }
440
441 fn arg_dry_run(self, dry_run: &'static str) -> Self {
442 self._arg(flag("dry-run", dry_run).short('n'))
443 }
444
445 fn arg_ignore_rust_version(self) -> Self {
446 self.arg_ignore_rust_version_with_help("Ignore `rust-version` specification in packages")
447 }
448
449 fn arg_ignore_rust_version_with_help(self, help: &'static str) -> Self {
450 self._arg(flag("ignore-rust-version", help).help_heading(heading::MANIFEST_OPTIONS))
451 }
452
453 fn arg_future_incompat_report(self) -> Self {
454 self._arg(flag(
455 "future-incompat-report",
456 "Outputs a future incompatibility report at the end of the build",
457 ))
458 }
459
460 fn arg_silent_suggestion(self) -> Self {
466 let value_parser = UnknownArgumentValueParser::suggest_arg("--quiet");
467 self._arg(
468 flag("silent", "")
469 .short('s')
470 .value_parser(value_parser)
471 .hide(true),
472 )
473 }
474
475 fn arg_timings(self) -> Self {
476 self._arg(
477 optional_opt(
478 "timings",
479 "Timing output formats (unstable) (comma separated): html, json",
480 )
481 .value_name("FMTS")
482 .require_equals(true)
483 .help_heading(heading::COMPILATION_OPTIONS),
484 )
485 }
486
487 fn arg_artifact_dir(self) -> Self {
488 let unsupported_short_arg = {
489 let value_parser = UnknownArgumentValueParser::suggest_arg("--artifact-dir");
490 Arg::new("unsupported-short-artifact-dir-flag")
491 .help("")
492 .short('O')
493 .value_parser(value_parser)
494 .action(ArgAction::SetTrue)
495 .hide(true)
496 };
497
498 self._arg(
499 opt(
500 "artifact-dir",
501 "Copy final artifacts to this directory (unstable)",
502 )
503 .value_name("PATH")
504 .help_heading(heading::COMPILATION_OPTIONS),
505 )
506 ._arg(unsupported_short_arg)
507 ._arg(
508 opt(
509 "out-dir",
510 "Copy final artifacts to this directory (deprecated; use --artifact-dir instead)",
511 )
512 .value_name("PATH")
513 .conflicts_with("artifact-dir")
514 .hide(true),
515 )
516 }
517}
518
519impl CommandExt for Command {
520 fn _arg(self, arg: Arg) -> Self {
521 self.arg(arg)
522 }
523}
524
525pub fn flag(name: &'static str, help: &'static str) -> Arg {
526 Arg::new(name)
527 .long(name)
528 .help(help)
529 .action(ArgAction::SetTrue)
530}
531
532pub fn opt(name: &'static str, help: &'static str) -> Arg {
533 Arg::new(name).long(name).help(help).action(ArgAction::Set)
534}
535
536pub fn optional_opt(name: &'static str, help: &'static str) -> Arg {
537 opt(name, help).num_args(0..=1)
538}
539
540pub fn optional_multi_opt(name: &'static str, value_name: &'static str, help: &'static str) -> Arg {
541 opt(name, help)
542 .value_name(value_name)
543 .num_args(0..=1)
544 .action(ArgAction::Append)
545}
546
547pub fn multi_opt(name: &'static str, value_name: &'static str, help: &'static str) -> Arg {
548 opt(name, help)
549 .value_name(value_name)
550 .action(ArgAction::Append)
551}
552
553pub fn subcommand(name: &'static str) -> Command {
554 Command::new(name)
555}
556
557pub enum ProfileChecking {
559 LegacyRustc,
562 LegacyTestOnly,
565 Custom,
567}
568
569pub trait ArgMatchesExt {
570 fn value_of_u32(&self, name: &str) -> CargoResult<Option<u32>> {
571 let arg = match self._value_of(name) {
572 None => None,
573 Some(arg) => Some(arg.parse::<u32>().map_err(|_| {
574 clap::Error::raw(
575 clap::error::ErrorKind::ValueValidation,
576 format!("Invalid value: could not parse `{}` as a number", arg),
577 )
578 })?),
579 };
580 Ok(arg)
581 }
582
583 fn value_of_i32(&self, name: &str) -> CargoResult<Option<i32>> {
584 let arg = match self._value_of(name) {
585 None => None,
586 Some(arg) => Some(arg.parse::<i32>().map_err(|_| {
587 clap::Error::raw(
588 clap::error::ErrorKind::ValueValidation,
589 format!("Invalid value: could not parse `{}` as a number", arg),
590 )
591 })?),
592 };
593 Ok(arg)
594 }
595
596 fn value_of_path(&self, name: &str, gctx: &GlobalContext) -> Option<PathBuf> {
598 self._value_of(name).map(|path| gctx.cwd().join(path))
599 }
600
601 fn root_manifest(&self, gctx: &GlobalContext) -> CargoResult<PathBuf> {
602 root_manifest(self._value_of("manifest-path").map(Path::new), gctx)
603 }
604
605 fn lockfile_path(&self, gctx: &GlobalContext) -> CargoResult<Option<PathBuf>> {
606 lockfile_path(self._value_of("lockfile-path").map(Path::new), gctx)
607 }
608
609 #[tracing::instrument(skip_all)]
610 fn workspace<'a>(&self, gctx: &'a GlobalContext) -> CargoResult<Workspace<'a>> {
611 let root = self.root_manifest(gctx)?;
612 let lockfile_path = self.lockfile_path(gctx)?;
613 let mut ws = Workspace::new(&root, gctx)?;
614 ws.set_resolve_honors_rust_version(self.honor_rust_version());
615 if gctx.cli_unstable().avoid_dev_deps {
616 ws.set_require_optional_deps(false);
617 }
618 ws.set_requested_lockfile_path(lockfile_path);
619 Ok(ws)
620 }
621
622 fn jobs(&self) -> CargoResult<Option<JobsConfig>> {
623 let arg = match self._value_of("jobs") {
624 None => None,
625 Some(arg) => match arg.parse::<i32>() {
626 Ok(j) => Some(JobsConfig::Integer(j)),
627 Err(_) => Some(JobsConfig::String(arg.to_string())),
628 },
629 };
630
631 Ok(arg)
632 }
633
634 fn verbose(&self) -> u32 {
635 self._count("verbose")
636 }
637
638 fn dry_run(&self) -> bool {
639 self.flag("dry-run")
640 }
641
642 fn keep_going(&self) -> bool {
643 self.maybe_flag("keep-going")
644 }
645
646 fn honor_rust_version(&self) -> Option<bool> {
647 self.flag("ignore-rust-version").then_some(false)
648 }
649
650 fn targets(&self) -> CargoResult<Vec<String>> {
651 if self.is_present_with_zero_values("target") {
652 let cmd = if is_rustup() {
653 "rustup target list"
654 } else {
655 "rustc --print target-list"
656 };
657 bail!(
658 "\"--target\" takes a target architecture as an argument.
659
660Run `{cmd}` to see possible targets."
661 );
662 }
663 Ok(self._values_of("target"))
664 }
665
666 fn get_profile_name(
667 &self,
668 default: &str,
669 profile_checking: ProfileChecking,
670 ) -> CargoResult<InternedString> {
671 let specified_profile = self._value_of("profile");
672
673 match (specified_profile, profile_checking) {
676 (Some(name @ ("dev" | "test" | "bench" | "check")), ProfileChecking::LegacyRustc)
678 | (Some(name @ "test"), ProfileChecking::LegacyTestOnly) => {
680 return Ok(InternedString::new(name));
681 }
682 _ => {}
683 }
684
685 let name = match (
686 self.maybe_flag("release"),
687 self.maybe_flag("debug"),
688 specified_profile,
689 ) {
690 (false, false, None) => default,
691 (true, _, None) => "release",
692 (_, true, None) => "dev",
693 (_, _, Some("doc")) => {
699 bail!("profile `doc` is reserved and not allowed to be explicitly specified")
700 }
701 (_, _, Some(name)) => {
702 ProfileName::new(name)?;
703 name
704 }
705 };
706
707 Ok(InternedString::new(name))
708 }
709
710 fn packages_from_flags(&self) -> CargoResult<Packages> {
711 Packages::from_flags(
712 self.flag("workspace") || self.flag("all"),
714 self._values_of("exclude"),
715 self._values_of("package"),
716 )
717 }
718
719 fn compile_options(
720 &self,
721 gctx: &GlobalContext,
722 mode: CompileMode,
723 workspace: Option<&Workspace<'_>>,
724 profile_checking: ProfileChecking,
725 ) -> CargoResult<CompileOptions> {
726 let spec = self.packages_from_flags()?;
727 let mut message_format = None;
728 let default_json = MessageFormat::Json {
729 short: false,
730 ansi: false,
731 render_diagnostics: false,
732 };
733 let two_kinds_of_msg_format_err = "cannot specify two kinds of `message-format` arguments";
734 for fmt in self._values_of("message-format") {
735 for fmt in fmt.split(',') {
736 let fmt = fmt.to_ascii_lowercase();
737 match fmt.as_str() {
738 "json" => {
739 if message_format.is_some() {
740 bail!(two_kinds_of_msg_format_err);
741 }
742 message_format = Some(default_json);
743 }
744 "human" => {
745 if message_format.is_some() {
746 bail!(two_kinds_of_msg_format_err);
747 }
748 message_format = Some(MessageFormat::Human);
749 }
750 "short" => {
751 if message_format.is_some() {
752 bail!(two_kinds_of_msg_format_err);
753 }
754 message_format = Some(MessageFormat::Short);
755 }
756 "json-render-diagnostics" => {
757 if message_format.is_none() {
758 message_format = Some(default_json);
759 }
760 match &mut message_format {
761 Some(MessageFormat::Json {
762 render_diagnostics, ..
763 }) => *render_diagnostics = true,
764 _ => bail!(two_kinds_of_msg_format_err),
765 }
766 }
767 "json-diagnostic-short" => {
768 if message_format.is_none() {
769 message_format = Some(default_json);
770 }
771 match &mut message_format {
772 Some(MessageFormat::Json { short, .. }) => *short = true,
773 _ => bail!(two_kinds_of_msg_format_err),
774 }
775 }
776 "json-diagnostic-rendered-ansi" => {
777 if message_format.is_none() {
778 message_format = Some(default_json);
779 }
780 match &mut message_format {
781 Some(MessageFormat::Json { ansi, .. }) => *ansi = true,
782 _ => bail!(two_kinds_of_msg_format_err),
783 }
784 }
785 s => bail!("invalid message format specifier: `{}`", s),
786 }
787 }
788 }
789
790 let mut build_config = BuildConfig::new(
791 gctx,
792 self.jobs()?,
793 self.keep_going(),
794 &self.targets()?,
795 mode,
796 )?;
797 build_config.message_format = message_format.unwrap_or(MessageFormat::Human);
798 build_config.requested_profile = self.get_profile_name("dev", profile_checking)?;
799 build_config.build_plan = self.flag("build-plan");
800 build_config.unit_graph = self.flag("unit-graph");
801 build_config.future_incompat_report = self.flag("future-incompat-report");
802
803 if self._contains("timings") {
804 for timing_output in self._values_of("timings") {
805 for timing_output in timing_output.split(',') {
806 let timing_output = timing_output.to_ascii_lowercase();
807 let timing_output = match timing_output.as_str() {
808 "html" => {
809 gctx.cli_unstable()
810 .fail_if_stable_opt("--timings=html", 7405)?;
811 TimingOutput::Html
812 }
813 "json" => {
814 gctx.cli_unstable()
815 .fail_if_stable_opt("--timings=json", 7405)?;
816 TimingOutput::Json
817 }
818 s => bail!("invalid timings output specifier: `{}`", s),
819 };
820 build_config.timing_outputs.push(timing_output);
821 }
822 }
823 if build_config.timing_outputs.is_empty() {
824 build_config.timing_outputs.push(TimingOutput::Html);
825 }
826 }
827
828 if build_config.build_plan {
829 gctx.cli_unstable()
830 .fail_if_stable_opt("--build-plan", 5579)?;
831 };
832 if build_config.unit_graph {
833 gctx.cli_unstable()
834 .fail_if_stable_opt("--unit-graph", 8002)?;
835 }
836
837 let opts = CompileOptions {
838 build_config,
839 cli_features: self.cli_features()?,
840 spec,
841 filter: CompileFilter::from_raw_arguments(
842 self.flag("lib"),
843 self._values_of("bin"),
844 self.flag("bins"),
845 self._values_of("test"),
846 self.flag("tests"),
847 self._values_of("example"),
848 self.flag("examples"),
849 self._values_of("bench"),
850 self.flag("benches"),
851 self.flag("all-targets"),
852 ),
853 target_rustdoc_args: None,
854 target_rustc_args: None,
855 target_rustc_crate_types: None,
856 rustdoc_document_private_items: false,
857 honor_rust_version: self.honor_rust_version(),
858 };
859
860 if let Some(ws) = workspace {
861 self.check_optional_opts(ws, &opts)?;
862 } else if self.is_present_with_zero_values("package") {
863 anyhow::bail!(
866 "\"--package <SPEC>\" requires a SPEC format value, \
867 which can be any package ID specifier in the dependency graph.\n\
868 Run `cargo help pkgid` for more information about SPEC format."
869 )
870 }
871
872 Ok(opts)
873 }
874
875 fn cli_features(&self) -> CargoResult<CliFeatures> {
876 CliFeatures::from_command_line(
877 &self._values_of("features"),
878 self.flag("all-features"),
879 !self.flag("no-default-features"),
880 )
881 }
882
883 fn compile_options_for_single_package(
884 &self,
885 gctx: &GlobalContext,
886 mode: CompileMode,
887 workspace: Option<&Workspace<'_>>,
888 profile_checking: ProfileChecking,
889 ) -> CargoResult<CompileOptions> {
890 let mut compile_opts = self.compile_options(gctx, mode, workspace, profile_checking)?;
891 let spec = self._values_of("package");
892 if spec.iter().any(restricted_names::is_glob_pattern) {
893 anyhow::bail!("Glob patterns on package selection are not supported.")
894 }
895 compile_opts.spec = Packages::Packages(spec);
896 Ok(compile_opts)
897 }
898
899 fn new_options(&self, gctx: &GlobalContext) -> CargoResult<NewOptions> {
900 let vcs = self._value_of("vcs").map(|vcs| match vcs {
901 "git" => VersionControl::Git,
902 "hg" => VersionControl::Hg,
903 "pijul" => VersionControl::Pijul,
904 "fossil" => VersionControl::Fossil,
905 "none" => VersionControl::NoVcs,
906 vcs => panic!("Impossible vcs: {:?}", vcs),
907 });
908 NewOptions::new(
909 vcs,
910 self.flag("bin"),
911 self.flag("lib"),
912 self.value_of_path("path", gctx).unwrap(),
913 self._value_of("name").map(|s| s.to_string()),
914 self._value_of("edition").map(|s| s.to_string()),
915 self.registry(gctx)?,
916 )
917 }
918
919 fn registry_or_index(&self, gctx: &GlobalContext) -> CargoResult<Option<RegistryOrIndex>> {
920 let registry = self._value_of("registry");
921 let index = self._value_of("index");
922 let result = match (registry, index) {
923 (None, None) => gctx.default_registry()?.map(RegistryOrIndex::Registry),
924 (None, Some(i)) => Some(RegistryOrIndex::Index(i.into_url()?)),
925 (Some(r), None) => {
926 RegistryName::new(r)?;
927 Some(RegistryOrIndex::Registry(r.to_string()))
928 }
929 (Some(_), Some(_)) => {
930 unreachable!("both `--index` and `--registry` should not be set at the same time")
932 }
933 };
934 Ok(result)
935 }
936
937 fn registry(&self, gctx: &GlobalContext) -> CargoResult<Option<String>> {
938 match self._value_of("registry").map(|s| s.to_string()) {
939 None => gctx.default_registry(),
940 Some(registry) => {
941 RegistryName::new(®istry)?;
942 Ok(Some(registry))
943 }
944 }
945 }
946
947 fn check_optional_opts(
948 &self,
949 workspace: &Workspace<'_>,
950 compile_opts: &CompileOptions,
951 ) -> CargoResult<()> {
952 if self.is_present_with_zero_values("package") {
953 print_available_packages(workspace)?
954 }
955
956 if self.is_present_with_zero_values("example") {
957 print_available_examples(workspace, compile_opts)?;
958 }
959
960 if self.is_present_with_zero_values("bin") {
961 print_available_binaries(workspace, compile_opts)?;
962 }
963
964 if self.is_present_with_zero_values("bench") {
965 print_available_benches(workspace, compile_opts)?;
966 }
967
968 if self.is_present_with_zero_values("test") {
969 print_available_tests(workspace, compile_opts)?;
970 }
971
972 Ok(())
973 }
974
975 fn is_present_with_zero_values(&self, name: &str) -> bool {
976 self._contains(name) && self._value_of(name).is_none()
977 }
978
979 fn flag(&self, name: &str) -> bool;
980
981 fn maybe_flag(&self, name: &str) -> bool;
982
983 fn _value_of(&self, name: &str) -> Option<&str>;
984
985 fn _values_of(&self, name: &str) -> Vec<String>;
986
987 fn _value_of_os(&self, name: &str) -> Option<&OsStr>;
988
989 fn _values_of_os(&self, name: &str) -> Vec<OsString>;
990
991 fn _count(&self, name: &str) -> u32;
992
993 fn _contains(&self, name: &str) -> bool;
994}
995
996impl<'a> ArgMatchesExt for ArgMatches {
997 fn flag(&self, name: &str) -> bool {
998 ignore_unknown(self.try_get_one::<bool>(name))
999 .copied()
1000 .unwrap_or(false)
1001 }
1002
1003 fn maybe_flag(&self, name: &str) -> bool {
1007 self.try_get_one::<bool>(name)
1008 .ok()
1009 .flatten()
1010 .copied()
1011 .unwrap_or_default()
1012 }
1013
1014 fn _value_of(&self, name: &str) -> Option<&str> {
1015 ignore_unknown(self.try_get_one::<String>(name)).map(String::as_str)
1016 }
1017
1018 fn _value_of_os(&self, name: &str) -> Option<&OsStr> {
1019 ignore_unknown(self.try_get_one::<OsString>(name)).map(OsString::as_os_str)
1020 }
1021
1022 fn _values_of(&self, name: &str) -> Vec<String> {
1023 ignore_unknown(self.try_get_many::<String>(name))
1024 .unwrap_or_default()
1025 .cloned()
1026 .collect()
1027 }
1028
1029 fn _values_of_os(&self, name: &str) -> Vec<OsString> {
1030 ignore_unknown(self.try_get_many::<OsString>(name))
1031 .unwrap_or_default()
1032 .cloned()
1033 .collect()
1034 }
1035
1036 fn _count(&self, name: &str) -> u32 {
1037 *ignore_unknown(self.try_get_one::<u8>(name)).expect("defaulted by clap") as u32
1038 }
1039
1040 fn _contains(&self, name: &str) -> bool {
1041 ignore_unknown(self.try_contains_id(name))
1042 }
1043}
1044
1045pub fn values(args: &ArgMatches, name: &str) -> Vec<String> {
1046 args._values_of(name)
1047}
1048
1049pub fn values_os(args: &ArgMatches, name: &str) -> Vec<OsString> {
1050 args._values_of_os(name)
1051}
1052
1053pub fn root_manifest(manifest_path: Option<&Path>, gctx: &GlobalContext) -> CargoResult<PathBuf> {
1054 if let Some(manifest_path) = manifest_path {
1055 let path = gctx.cwd().join(manifest_path);
1056 let path = paths::normalize_path(&path);
1059 if !path.ends_with("Cargo.toml") && !crate::util::toml::is_embedded(&path) {
1060 anyhow::bail!("the manifest-path must be a path to a Cargo.toml file")
1061 }
1062 if !path.exists() {
1063 anyhow::bail!("manifest path `{}` does not exist", manifest_path.display())
1064 }
1065 if path.is_dir() {
1066 anyhow::bail!(
1067 "manifest path `{}` is a directory but expected a file",
1068 manifest_path.display()
1069 )
1070 }
1071 if crate::util::toml::is_embedded(&path) && !gctx.cli_unstable().script {
1072 anyhow::bail!("embedded manifest `{}` requires `-Zscript`", path.display())
1073 }
1074 Ok(path)
1075 } else {
1076 find_root_manifest_for_wd(gctx.cwd())
1077 }
1078}
1079
1080pub fn lockfile_path(
1081 lockfile_path: Option<&Path>,
1082 gctx: &GlobalContext,
1083) -> CargoResult<Option<PathBuf>> {
1084 let Some(lockfile_path) = lockfile_path else {
1085 return Ok(None);
1086 };
1087
1088 gctx.cli_unstable()
1089 .fail_if_stable_opt("--lockfile-path", 14421)?;
1090
1091 let path = gctx.cwd().join(lockfile_path);
1092
1093 if !path.ends_with(LOCKFILE_NAME) {
1094 bail!("the lockfile-path must be a path to a {LOCKFILE_NAME} file (please rename your lock file to {LOCKFILE_NAME})")
1095 }
1096 if path.is_dir() {
1097 bail!(
1098 "lockfile path `{}` is a directory but expected a file",
1099 lockfile_path.display()
1100 )
1101 }
1102
1103 return Ok(Some(path));
1104}
1105
1106pub fn get_registry_candidates() -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
1107 let gctx = new_gctx_for_completions()?;
1108
1109 if let Ok(Some(registries)) =
1110 gctx.get::<Option<HashMap<String, HashMap<String, String>>>>("registries")
1111 {
1112 Ok(registries
1113 .keys()
1114 .map(|name| clap_complete::CompletionCandidate::new(name.to_owned()))
1115 .collect())
1116 } else {
1117 Ok(vec![])
1118 }
1119}
1120
1121fn get_example_candidates() -> Vec<clap_complete::CompletionCandidate> {
1122 get_targets_from_metadata()
1123 .unwrap_or_default()
1124 .into_iter()
1125 .filter_map(|target| match target.kind() {
1126 TargetKind::ExampleBin => Some(clap_complete::CompletionCandidate::new(target.name())),
1127 _ => None,
1128 })
1129 .collect::<Vec<_>>()
1130}
1131
1132fn get_bench_candidates() -> Vec<clap_complete::CompletionCandidate> {
1133 get_targets_from_metadata()
1134 .unwrap_or_default()
1135 .into_iter()
1136 .filter_map(|target| match target.kind() {
1137 TargetKind::Bench => Some(clap_complete::CompletionCandidate::new(target.name())),
1138 _ => None,
1139 })
1140 .collect::<Vec<_>>()
1141}
1142
1143fn get_test_candidates() -> Vec<clap_complete::CompletionCandidate> {
1144 get_targets_from_metadata()
1145 .unwrap_or_default()
1146 .into_iter()
1147 .filter_map(|target| match target.kind() {
1148 TargetKind::Test => Some(clap_complete::CompletionCandidate::new(target.name())),
1149 _ => None,
1150 })
1151 .collect::<Vec<_>>()
1152}
1153
1154fn get_bin_candidates() -> Vec<clap_complete::CompletionCandidate> {
1155 get_targets_from_metadata()
1156 .unwrap_or_default()
1157 .into_iter()
1158 .filter_map(|target| match target.kind() {
1159 TargetKind::Bin => Some(clap_complete::CompletionCandidate::new(target.name())),
1160 _ => None,
1161 })
1162 .collect::<Vec<_>>()
1163}
1164
1165fn get_targets_from_metadata() -> CargoResult<Vec<Target>> {
1166 let cwd = std::env::current_dir()?;
1167 let gctx = GlobalContext::new(shell::Shell::new(), cwd.clone(), cargo_home_with_cwd(&cwd)?);
1168 let ws = Workspace::new(&find_root_manifest_for_wd(&cwd)?, &gctx)?;
1169
1170 let packages = ws.members().collect::<Vec<_>>();
1171
1172 let targets = packages
1173 .into_iter()
1174 .flat_map(|pkg| pkg.targets().into_iter().cloned())
1175 .collect::<Vec<_>>();
1176
1177 Ok(targets)
1178}
1179
1180fn get_target_triples() -> Vec<clap_complete::CompletionCandidate> {
1181 let mut candidates = Vec::new();
1182
1183 if let Ok(targets) = get_target_triples_from_rustup() {
1184 candidates = targets;
1185 }
1186
1187 if candidates.is_empty() {
1188 if let Ok(targets) = get_target_triples_from_rustc() {
1189 candidates = targets;
1190 }
1191 }
1192
1193 candidates
1194}
1195
1196fn get_target_triples_from_rustup() -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
1197 let output = std::process::Command::new("rustup")
1198 .arg("target")
1199 .arg("list")
1200 .output()?;
1201
1202 if !output.status.success() {
1203 return Ok(vec![]);
1204 }
1205
1206 let stdout = String::from_utf8(output.stdout)?;
1207
1208 Ok(stdout
1209 .lines()
1210 .map(|line| {
1211 let target = line.split_once(' ');
1212 match target {
1213 None => clap_complete::CompletionCandidate::new(line.to_owned()).hide(true),
1214 Some((target, _installed)) => clap_complete::CompletionCandidate::new(target),
1215 }
1216 })
1217 .collect())
1218}
1219
1220fn get_target_triples_from_rustc() -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
1221 let cwd = std::env::current_dir()?;
1222 let gctx = GlobalContext::new(shell::Shell::new(), cwd.clone(), cargo_home_with_cwd(&cwd)?);
1223 let ws = Workspace::new(&find_root_manifest_for_wd(&PathBuf::from(&cwd))?, &gctx);
1224
1225 let rustc = gctx.load_global_rustc(ws.as_ref().ok())?;
1226
1227 let (stdout, _stderr) =
1228 rustc.cached_output(rustc.process().arg("--print").arg("target-list"), 0)?;
1229
1230 Ok(stdout
1231 .lines()
1232 .map(|line| clap_complete::CompletionCandidate::new(line.to_owned()))
1233 .collect())
1234}
1235
1236pub fn get_pkg_id_spec_candidates() -> Vec<clap_complete::CompletionCandidate> {
1237 let mut candidates = vec![];
1238
1239 let package_map = HashMap::<&str, Vec<Package>>::new();
1240 let package_map =
1241 get_packages()
1242 .unwrap_or_default()
1243 .into_iter()
1244 .fold(package_map, |mut map, package| {
1245 map.entry(package.name().as_str())
1246 .or_insert_with(Vec::new)
1247 .push(package);
1248 map
1249 });
1250
1251 let unique_name_candidates = package_map
1252 .iter()
1253 .filter(|(_name, packages)| packages.len() == 1)
1254 .map(|(name, packages)| {
1255 clap_complete::CompletionCandidate::new(name.to_string()).help(
1256 packages[0]
1257 .manifest()
1258 .metadata()
1259 .description
1260 .to_owned()
1261 .map(From::from),
1262 )
1263 })
1264 .collect::<Vec<_>>();
1265
1266 let duplicate_name_pairs = package_map
1267 .iter()
1268 .filter(|(_name, packages)| packages.len() > 1)
1269 .collect::<Vec<_>>();
1270
1271 let mut duplicate_name_candidates = vec![];
1272 for (name, packages) in duplicate_name_pairs {
1273 let mut version_count: HashMap<&Version, usize> = HashMap::new();
1274
1275 for package in packages {
1276 *version_count.entry(package.version()).or_insert(0) += 1;
1277 }
1278
1279 for package in packages {
1280 if let Some(&count) = version_count.get(package.version()) {
1281 if count == 1 {
1282 duplicate_name_candidates.push(
1283 clap_complete::CompletionCandidate::new(format!(
1284 "{}@{}",
1285 name,
1286 package.version()
1287 ))
1288 .help(
1289 package
1290 .manifest()
1291 .metadata()
1292 .description
1293 .to_owned()
1294 .map(From::from),
1295 ),
1296 );
1297 } else {
1298 duplicate_name_candidates.push(
1299 clap_complete::CompletionCandidate::new(format!(
1300 "{}",
1301 package.package_id().to_spec()
1302 ))
1303 .help(
1304 package
1305 .manifest()
1306 .metadata()
1307 .description
1308 .to_owned()
1309 .map(From::from),
1310 ),
1311 )
1312 }
1313 }
1314 }
1315 }
1316
1317 candidates.extend(unique_name_candidates);
1318 candidates.extend(duplicate_name_candidates);
1319
1320 candidates
1321}
1322
1323fn get_packages() -> CargoResult<Vec<Package>> {
1324 let gctx = new_gctx_for_completions()?;
1325
1326 let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?;
1327
1328 let requested_kinds = CompileKind::from_requested_targets(ws.gctx(), &[])?;
1329 let mut target_data = RustcTargetData::new(&ws, &requested_kinds)?;
1330 let cli_features = CliFeatures::new_all(true);
1332 let has_dev_units = HasDevUnits::Yes;
1333 let force_all_targets = ForceAllTargets::No;
1334 let dry_run = true;
1335
1336 let ws_resolve = ops::resolve_ws_with_opts(
1337 &ws,
1338 &mut target_data,
1339 &requested_kinds,
1340 &cli_features,
1341 &[],
1342 has_dev_units,
1343 force_all_targets,
1344 dry_run,
1345 )?;
1346
1347 let packages = ws_resolve
1348 .pkg_set
1349 .packages()
1350 .map(Clone::clone)
1351 .collect::<Vec<_>>();
1352
1353 Ok(packages)
1354}
1355
1356pub fn new_gctx_for_completions() -> CargoResult<GlobalContext> {
1357 let cwd = std::env::current_dir()?;
1358 let mut gctx = GlobalContext::new(shell::Shell::new(), cwd.clone(), cargo_home_with_cwd(&cwd)?);
1359
1360 let verbose = 0;
1361 let quiet = true;
1362 let color = None;
1363 let frozen = false;
1364 let locked = true;
1365 let offline = false;
1366 let target_dir = None;
1367 let unstable_flags = &[];
1368 let cli_config = &[];
1369
1370 gctx.configure(
1371 verbose,
1372 quiet,
1373 color,
1374 frozen,
1375 locked,
1376 offline,
1377 &target_dir,
1378 unstable_flags,
1379 cli_config,
1380 )?;
1381
1382 Ok(gctx)
1383}
1384
1385#[track_caller]
1386pub fn ignore_unknown<T: Default>(r: Result<T, clap::parser::MatchesError>) -> T {
1387 match r {
1388 Ok(t) => t,
1389 Err(clap::parser::MatchesError::UnknownArgument { .. }) => Default::default(),
1390 Err(e) => {
1391 panic!("Mismatch between definition and access: {}", e);
1392 }
1393 }
1394}
1395
1396#[derive(PartialEq, Eq, PartialOrd, Ord)]
1397pub enum CommandInfo {
1398 BuiltIn { about: Option<String> },
1399 External { path: PathBuf },
1400 Alias { target: StringOrVec },
1401}