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::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    /// Do not use this method, it is only for backwards compatibility.
53    /// Use `arg_package_spec_no_all` instead.
54    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    /// Variant of `arg_package_spec` that does not include the `--all` flag
67    /// (but does include `--workspace`). Used to avoid confusion with
68    /// historical uses of `--all`.
69    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        // We use `--manifest-path` instead of `--path`.
313        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    // `cargo add` has a `--path` flag to install a crate from a local path.
325    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                        // allow `Cargo.lock` file
357                        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        // Always conflicts with `--registry`.
434        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    /// Adds a suggestion for the `--silent` or `-s` flags to use the
461    /// `--quiet` flag instead. This is to help with people familiar with
462    /// other tools that use `-s`.
463    ///
464    /// Every command should call this, unless it has its own `-s` short flag.
465    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
557/// Determines whether or not to gate `--profile` as unstable when resolving it.
558pub enum ProfileChecking {
559    /// `cargo rustc` historically has allowed "test", "bench", and "check". This
560    /// variant explicitly allows those.
561    LegacyRustc,
562    /// `cargo check` and `cargo fix` historically has allowed "test". This variant
563    /// explicitly allows that on stable.
564    LegacyTestOnly,
565    /// All other commands, which allow any valid custom named profile.
566    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    /// Returns value of the `name` command-line argument as an absolute path
597    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        // Check for allowed legacy names.
674        // This is an early exit, since it allows combination with `--release`.
675        match (specified_profile, profile_checking) {
676            // `cargo rustc` has legacy handling of these names
677            (Some(name @ ("dev" | "test" | "bench" | "check")), ProfileChecking::LegacyRustc)
678            // `cargo fix` and `cargo check` has legacy handling of this profile name
679            | (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            // `doc` is separate from all the other reservations because
694            // [profile.doc] was historically allowed, but is deprecated and
695            // has no effect. To avoid potentially breaking projects, it is a
696            // warning in Cargo.toml, but since `--profile` is new, we can
697            // reject it completely here.
698            (_, _, 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            // TODO Integrate into 'workspace'
713            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            // As for cargo 0.50.0, this won't occur but if someone sneaks in
864            // we can still provide this informative message for them.
865            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                // Should be guarded by clap
931                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(&registry)?;
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    // This works around before an upstream fix in clap for `UnknownArgumentValueParser` accepting
1004    // generics arguments. `flag()` cannot be used with `--keep-going` at this moment due to
1005    // <https://github.com/clap-rs/clap/issues/5081>.
1006    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        // In general, we try to avoid normalizing paths in Cargo,
1057        // but in this particular case we need it to fix #3586.
1058        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    // `cli_features.all_features` must be true in case that `specs` is empty.
1331    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}