cargo/util/
command_prelude.rs

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    /// Do not use this method, it is only for backwards compatibility.
52    /// Use `arg_package_spec_no_all` instead.
53    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    /// Variant of `arg_package_spec` that does not include the `--all` flag
66    /// (but does include `--workspace`). Used to avoid confusion with
67    /// historical uses of `--all`.
68    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        // We use `--manifest-path` instead of `--path`.
312        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    // `cargo add` has a `--path` flag to install a crate from a local path.
324    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        // Always conflicts with `--registry`.
399        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    /// Adds a suggestion for the `--silent` or `-s` flags to use the
426    /// `--quiet` flag instead. This is to help with people familiar with
427    /// other tools that use `-s`.
428    ///
429    /// Every command should call this, unless it has its own `-s` short flag.
430    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
522/// Determines whether or not to gate `--profile` as unstable when resolving it.
523pub enum ProfileChecking {
524    /// `cargo rustc` historically has allowed "test", "bench", and "check". This
525    /// variant explicitly allows those.
526    LegacyRustc,
527    /// `cargo check` and `cargo fix` historically has allowed "test". This variant
528    /// explicitly allows that on stable.
529    LegacyTestOnly,
530    /// All other commands, which allow any valid custom named profile.
531    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    /// Returns value of the `name` command-line argument as an absolute path
562    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        // Check for allowed legacy names.
639        // This is an early exit, since it allows combination with `--release`.
640        match (specified_profile, profile_checking) {
641            // `cargo rustc` has legacy handling of these names
642            (Some(name @ ("dev" | "test" | "bench" | "check")), ProfileChecking::LegacyRustc)
643            // `cargo fix` and `cargo check` has legacy handling of this profile name
644            | (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            // `doc` is separate from all the other reservations because
659            // [profile.doc] was historically allowed, but is deprecated and
660            // has no effect. To avoid potentially breaking projects, it is a
661            // warning in Cargo.toml, but since `--profile` is new, we can
662            // reject it completely here.
663            (_, _, 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            // TODO Integrate into 'workspace'
678            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            // As for cargo 0.50.0, this won't occur but if someone sneaks in
829            // we can still provide this informative message for them.
830            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                // Should be guarded by clap
896                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(&registry)?;
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    // This works around before an upstream fix in clap for `UnknownArgumentValueParser` accepting
969    // generics arguments. `flag()` cannot be used with `--keep-going` at this moment due to
970    // <https://github.com/clap-rs/clap/issues/5081>.
971    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        // In general, we try to avoid normalizing paths in Cargo,
1022        // but in this particular case we need it to fix #3586.
1023        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    // `cli_features.all_features` must be true in case that `specs` is empty.
1296    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}