| use crate::CargoResult; | |
| use crate::compiler::{BuildConfig, CompileKind, MessageFormat, RustcTargetData}; | |
| use crate::context::VersionControl; | |
| use crate::ops::registry::RegistryOrIndex; | |
| use crate::ops::{self, CompileFilter, CompileOptions, NewOptions, Packages}; | |
| use crate::resolver::{CliFeatures, ForceAllTargets, HasDevUnits}; | |
| use crate::util::important_paths::find_root_manifest_for_wd; | |
| use crate::util::interning::InternedString; | |
| use crate::util::is_rustup; | |
| use crate::util::restricted_names; | |
| use crate::util::{ | |
| print_available_benches, print_available_binaries, print_available_examples, | |
| print_available_packages, print_available_tests, | |
| }; | |
| use crate::workspace::Dependency; | |
| use crate::workspace::parser::is_embedded; | |
| use crate::workspace::{Edition, Package, TargetKind, Workspace, profiles::Profiles}; | |
| use anyhow::bail; | |
| use cargo_util::collections::{HashMap, HashSet}; | |
| use cargo_util::collections::{IndexMap, IndexSet}; | |
| use cargo_util::paths; | |
| use cargo_util_schemas::manifest::ProfileName; | |
| use cargo_util_schemas::manifest::RegistryName; | |
| use cargo_util_terminal as shell; | |
| use cargo_util_terminal::report::Level; | |
| use clap::builder::PossibleValuesParser; | |
| use clap::builder::UnknownArgumentValueParser; | |
| use clap_complete::ArgValueCandidates; | |
| use home::cargo_home_with_cwd; | |
| use itertools::Itertools; | |
| use semver::Version; | |
| use std::collections::BTreeMap; | |
| use std::ffi::{OsStr, OsString}; | |
| use std::path::Path; | |
| use std::path::PathBuf; | |
| pub use crate::compiler::UserIntent; | |
| pub use crate::{CliError, CliResult, GlobalContext}; | |
| pub use clap::{Arg, ArgAction, ArgMatches, value_parser}; | |
| pub use clap::Command; | |
| use super::IntoUrl; | |
| use crate::context::JobsConfig; | |
| pub mod heading { | |
| pub const PACKAGE_SELECTION: &str = "Package Selection"; | |
| pub const TARGET_SELECTION: &str = "Target Selection"; | |
| pub const FEATURE_SELECTION: &str = "Feature Selection"; | |
| pub const COMPILATION_OPTIONS: &str = "Compilation Options"; | |
| pub const MANIFEST_OPTIONS: &str = "Manifest Options"; | |
| } | |
| pub trait CommandExt: Sized { | |
| fn _arg(self, arg: Arg) -> Self; | |
| /// Do not use this method, it is only for backwards compatibility. | |
| /// Use `arg_package_spec_no_all` instead. | |
| fn arg_package_spec( | |
| self, | |
| package: &'static str, | |
| all: &'static str, | |
| exclude: &'static str, | |
| ) -> Self { | |
| self.arg_package_spec_no_all( | |
| package, | |
| all, | |
| exclude, | |
| ArgValueCandidates::new(get_ws_member_candidates), | |
| ) | |
| ._arg( | |
| flag("all", "Alias for --workspace (deprecated)") | |
| .help_heading(heading::PACKAGE_SELECTION), | |
| ) | |
| } | |
| /// Variant of `arg_package_spec` that does not include the `--all` flag | |
| /// (but does include `--workspace`). Used to avoid confusion with | |
| /// historical uses of `--all`. | |
| fn arg_package_spec_no_all( | |
| self, | |
| package: &'static str, | |
| all: &'static str, | |
| exclude: &'static str, | |
| package_completion: ArgValueCandidates, | |
| ) -> Self { | |
| let unsupported_short_arg = { | |
| let value_parser = UnknownArgumentValueParser::suggest_arg("--exclude"); | |
| Arg::new("unsupported-short-exclude-flag") | |
| .help("") | |
| .short('x') | |
| .value_parser(value_parser) | |
| .action(ArgAction::SetTrue) | |
| .hide(true) | |
| }; | |
| self.arg_package_spec_simple(package, package_completion) | |
| ._arg(flag("workspace", all).help_heading(heading::PACKAGE_SELECTION)) | |
| ._arg( | |
| multi_opt("exclude", "SPEC", exclude) | |
| .help_heading(heading::PACKAGE_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new( | |
| get_ws_member_candidates, | |
| )), | |
| ) | |
| ._arg(unsupported_short_arg) | |
| } | |
| fn arg_package_spec_simple( | |
| self, | |
| package: &'static str, | |
| package_completion: ArgValueCandidates, | |
| ) -> Self { | |
| self._arg( | |
| optional_multi_opt("package", "SPEC", package) | |
| .short('p') | |
| .help_heading(heading::PACKAGE_SELECTION) | |
| .add(package_completion), | |
| ) | |
| } | |
| fn arg_package(self, package: &'static str) -> Self { | |
| self._arg( | |
| optional_opt("package", package) | |
| .short('p') | |
| .value_name("SPEC") | |
| .help_heading(heading::PACKAGE_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_ws_member_candidates() | |
| })), | |
| ) | |
| } | |
| fn arg_parallel(self) -> Self { | |
| self.arg_jobs()._arg( | |
| flag( | |
| "keep-going", | |
| "Do not abort the build as soon as there is an error", | |
| ) | |
| .help_heading(heading::COMPILATION_OPTIONS), | |
| ) | |
| } | |
| fn arg_jobs(self) -> Self { | |
| self._arg( | |
| opt("jobs", "Number of parallel jobs, defaults to # of CPUs.") | |
| .short('j') | |
| .value_name("N") | |
| .allow_hyphen_values(true) | |
| .help_heading(heading::COMPILATION_OPTIONS), | |
| ) | |
| } | |
| fn arg_unsupported_keep_going(self) -> Self { | |
| let msg = "use `--no-fail-fast` to run as many tests as possible regardless of failure"; | |
| let value_parser = UnknownArgumentValueParser::suggest(msg); | |
| self._arg(flag("keep-going", "").value_parser(value_parser).hide(true)) | |
| } | |
| fn arg_redundant_default_mode( | |
| self, | |
| default_mode: &'static str, | |
| command: &'static str, | |
| supported_mode: &'static str, | |
| ) -> Self { | |
| let msg = format!( | |
| "`--{default_mode}` is the default for `cargo {command}`; instead `--{supported_mode}` is supported" | |
| ); | |
| let value_parser = UnknownArgumentValueParser::suggest(msg); | |
| self._arg( | |
| flag(default_mode, "") | |
| .conflicts_with("profile") | |
| .value_parser(value_parser) | |
| .hide(true), | |
| ) | |
| } | |
| fn arg_targets_all( | |
| self, | |
| lib: &'static str, | |
| bin: &'static str, | |
| bins: &'static str, | |
| example: &'static str, | |
| examples: &'static str, | |
| test: &'static str, | |
| tests: &'static str, | |
| bench: &'static str, | |
| benches: &'static str, | |
| all: &'static str, | |
| ) -> Self { | |
| self.arg_targets_lib_bin_example(lib, bin, bins, example, examples) | |
| ._arg(flag("tests", tests).help_heading(heading::TARGET_SELECTION)) | |
| ._arg( | |
| optional_multi_opt("test", "NAME", test) | |
| .help_heading(heading::TARGET_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_crate_candidates(TargetKind::Test).unwrap_or_default() | |
| })), | |
| ) | |
| ._arg(flag("benches", benches).help_heading(heading::TARGET_SELECTION)) | |
| ._arg( | |
| optional_multi_opt("bench", "NAME", bench) | |
| .help_heading(heading::TARGET_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_crate_candidates(TargetKind::Bench).unwrap_or_default() | |
| })), | |
| ) | |
| ._arg(flag("all-targets", all).help_heading(heading::TARGET_SELECTION)) | |
| } | |
| fn arg_targets_lib_bin_example( | |
| self, | |
| lib: &'static str, | |
| bin: &'static str, | |
| bins: &'static str, | |
| example: &'static str, | |
| examples: &'static str, | |
| ) -> Self { | |
| self._arg(flag("lib", lib).help_heading(heading::TARGET_SELECTION)) | |
| ._arg(flag("bins", bins).help_heading(heading::TARGET_SELECTION)) | |
| ._arg( | |
| optional_multi_opt("bin", "NAME", bin) | |
| .help_heading(heading::TARGET_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_crate_candidates(TargetKind::Bin).unwrap_or_default() | |
| })), | |
| ) | |
| ._arg(flag("examples", examples).help_heading(heading::TARGET_SELECTION)) | |
| ._arg( | |
| optional_multi_opt("example", "NAME", example) | |
| .help_heading(heading::TARGET_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_crate_candidates(TargetKind::ExampleBin).unwrap_or_default() | |
| })), | |
| ) | |
| } | |
| fn arg_targets_bins_examples( | |
| self, | |
| bin: &'static str, | |
| bins: &'static str, | |
| example: &'static str, | |
| examples: &'static str, | |
| ) -> Self { | |
| self._arg( | |
| optional_multi_opt("bin", "NAME", bin) | |
| .help_heading(heading::TARGET_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_crate_candidates(TargetKind::Bin).unwrap_or_default() | |
| })), | |
| ) | |
| ._arg(flag("bins", bins).help_heading(heading::TARGET_SELECTION)) | |
| ._arg( | |
| optional_multi_opt("example", "NAME", example) | |
| .help_heading(heading::TARGET_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_crate_candidates(TargetKind::ExampleBin).unwrap_or_default() | |
| })), | |
| ) | |
| ._arg(flag("examples", examples).help_heading(heading::TARGET_SELECTION)) | |
| } | |
| fn arg_targets_bin_example(self, bin: &'static str, example: &'static str) -> Self { | |
| self._arg( | |
| optional_multi_opt("bin", "NAME", bin) | |
| .help_heading(heading::TARGET_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_crate_candidates(TargetKind::Bin).unwrap_or_default() | |
| })), | |
| ) | |
| ._arg( | |
| optional_multi_opt("example", "NAME", example) | |
| .help_heading(heading::TARGET_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_crate_candidates(TargetKind::ExampleBin).unwrap_or_default() | |
| })), | |
| ) | |
| } | |
| fn arg_features(self) -> Self { | |
| self._arg( | |
| multi_opt( | |
| "features", | |
| "FEATURES", | |
| "Space or comma separated list of features to activate", | |
| ) | |
| .short('F') | |
| .help_heading(heading::FEATURE_SELECTION) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| get_feature_candidates().unwrap_or_default() | |
| })), | |
| ) | |
| ._arg( | |
| flag("all-features", "Activate all available features") | |
| .help_heading(heading::FEATURE_SELECTION), | |
| ) | |
| ._arg( | |
| flag( | |
| "no-default-features", | |
| "Do not activate the `default` feature", | |
| ) | |
| .help_heading(heading::FEATURE_SELECTION), | |
| ) | |
| } | |
| fn arg_release(self, release: &'static str) -> Self { | |
| self._arg( | |
| flag("release", release) | |
| .short('r') | |
| .conflicts_with("profile") | |
| .help_heading(heading::COMPILATION_OPTIONS), | |
| ) | |
| } | |
| fn arg_profile(self, profile: &'static str) -> Self { | |
| self._arg( | |
| opt("profile", profile) | |
| .value_name("PROFILE-NAME") | |
| .help_heading(heading::COMPILATION_OPTIONS) | |
| .add(clap_complete::ArgValueCandidates::new(|| { | |
| let candidates = get_profile_candidates(); | |
| candidates | |
| })), | |
| ) | |
| } | |
| fn arg_doc(self, doc: &'static str) -> Self { | |
| self._arg(flag("doc", doc)) | |
| } | |
| fn arg_target_tuple(self, target: &'static str) -> Self { | |
| self.arg_target_tuple_with_candidates(target, ArgValueCandidates::new(get_target_tuples)) | |
| } | |
| fn arg_target_tuple_with_candidates( | |
| self, | |
| target: &'static str, | |
| target_completion: ArgValueCandidates, | |
| ) -> Self { | |
| let unsupported_short_arg = { | |
| let value_parser = UnknownArgumentValueParser::suggest_arg("--target"); | |
| Arg::new("unsupported-short-target-flag") | |
| .help("") | |
| .short('t') | |
| .value_parser(value_parser) | |
| .action(ArgAction::SetTrue) | |
| .hide(true) | |
| }; | |
| self._arg( | |
| optional_multi_opt("target", "TUPLE", target) | |
| .help_heading(heading::COMPILATION_OPTIONS) | |
| .add(target_completion), | |
| ) | |
| ._arg(unsupported_short_arg) | |
| } | |
| fn arg_target_dir(self) -> Self { | |
| self._arg( | |
| opt("target-dir", "Directory for all generated artifacts") | |
| .value_name("DIRECTORY") | |
| .help_heading(heading::COMPILATION_OPTIONS), | |
| ) | |
| } | |
| fn arg_manifest_path(self) -> Self { | |
| // We use `--manifest-path` instead of `--path`. | |
| let unsupported_path_arg = { | |
| let value_parser = UnknownArgumentValueParser::suggest_arg("--manifest-path"); | |
| flag("unsupported-path-flag", "") | |
| .long("path") | |
| .value_parser(value_parser) | |
| .hide(true) | |
| }; | |
| self.arg_manifest_path_without_unsupported_path_tip() | |
| ._arg(unsupported_path_arg) | |
| } | |
| // `cargo add` has a `--path` flag to install a crate from a local path. | |
| fn arg_manifest_path_without_unsupported_path_tip(self) -> Self { | |
| self._arg( | |
| opt("manifest-path", "Path to Cargo.toml") | |
| .short('m') | |
| .value_name("PATH") | |
| .help_heading(heading::MANIFEST_OPTIONS) | |
| .add(clap_complete::engine::ArgValueCompleter::new( | |
| clap_complete::engine::PathCompleter::any().filter(|path: &Path| { | |
| if path.file_name() == Some(OsStr::new("Cargo.toml")) { | |
| return true; | |
| } | |
| if is_embedded(path) { | |
| return true; | |
| } | |
| false | |
| }), | |
| )), | |
| ) | |
| } | |
| fn arg_message_format(self) -> Self { | |
| self._arg( | |
| multi_opt("message-format", "FMT", "Error format") | |
| .value_parser([ | |
| "human", | |
| "short", | |
| "json", | |
| "json-diagnostic-short", | |
| "json-diagnostic-rendered-ansi", | |
| "json-render-diagnostics", | |
| ]) | |
| .value_delimiter(',') | |
| .ignore_case(true), | |
| ) | |
| } | |
| fn arg_unit_graph(self) -> Self { | |
| self._arg( | |
| flag("unit-graph", "Output build graph in JSON (unstable)") | |
| .help_heading(heading::COMPILATION_OPTIONS), | |
| ) | |
| } | |
| fn arg_new_opts(self) -> Self { | |
| self._arg( | |
| opt( | |
| "vcs", | |
| "Initialize a new repository for the given version \ | |
| control system, overriding \ | |
| a global configuration.", | |
| ) | |
| .value_name("VCS") | |
| .value_parser(PossibleValuesParser::new( | |
| VersionControl::VALUES.iter().map(|v| v.as_str()), | |
| )), | |
| ) | |
| ._arg(flag("bin", "Use a binary (application) template [default]")) | |
| ._arg(flag("lib", "Use a library template")) | |
| ._arg( | |
| opt("edition", "Edition to set for the crate generated") | |
| .value_parser(Edition::CLI_VALUES) | |
| .value_name("YEAR"), | |
| ) | |
| ._arg( | |
| opt( | |
| "name", | |
| "Set the resulting package name, defaults to the directory name", | |
| ) | |
| .value_name("NAME"), | |
| ) | |
| } | |
| fn arg_registry(self, help: &'static str) -> Self { | |
| self._arg(opt("registry", help).value_name("REGISTRY").add( | |
| clap_complete::ArgValueCandidates::new(|| { | |
| let candidates = get_registry_candidates(); | |
| candidates.unwrap_or_default() | |
| }), | |
| )) | |
| } | |
| fn arg_index(self, help: &'static str) -> Self { | |
| // Always conflicts with `--registry`. | |
| self._arg( | |
| opt("index", help) | |
| .value_name("INDEX") | |
| .conflicts_with("registry"), | |
| ) | |
| } | |
| fn arg_dry_run(self, dry_run: &'static str) -> Self { | |
| self._arg(flag("dry-run", dry_run).short('n')) | |
| } | |
| fn arg_ignore_rust_version(self) -> Self { | |
| self.arg_ignore_rust_version_with_help("Ignore `rust-version` specification in packages") | |
| } | |
| fn arg_ignore_rust_version_with_help(self, help: &'static str) -> Self { | |
| self._arg(flag("ignore-rust-version", help).help_heading(heading::MANIFEST_OPTIONS)) | |
| } | |
| fn arg_future_incompat_report(self) -> Self { | |
| self._arg(flag( | |
| "future-incompat-report", | |
| "Outputs a future incompatibility report at the end of the build", | |
| )) | |
| } | |
| /// Adds a suggestion for the `--silent` or `-s` flags to use the | |
| /// `--quiet` flag instead. This is to help with people familiar with | |
| /// other tools that use `-s`. | |
| /// | |
| /// Every command should call this, unless it has its own `-s` short flag. | |
| fn arg_silent_suggestion(self) -> Self { | |
| let value_parser = UnknownArgumentValueParser::suggest_arg("--quiet"); | |
| self._arg( | |
| flag("silent", "") | |
| .short('s') | |
| .value_parser(value_parser) | |
| .hide(true), | |
| ) | |
| } | |
| fn arg_timings(self) -> Self { | |
| self._arg( | |
| flag( | |
| "timings", | |
| "Output a build timing report at the end of the build", | |
| ) | |
| .help_heading(heading::COMPILATION_OPTIONS), | |
| ) | |
| } | |
| fn arg_artifact_dir(self) -> Self { | |
| let unsupported_short_arg = { | |
| let value_parser = UnknownArgumentValueParser::suggest_arg("--artifact-dir"); | |
| Arg::new("unsupported-short-artifact-dir-flag") | |
| .help("") | |
| .short('O') | |
| .value_parser(value_parser) | |
| .action(ArgAction::SetTrue) | |
| .hide(true) | |
| }; | |
| self._arg( | |
| opt( | |
| "artifact-dir", | |
| "Copy final artifacts to this directory (unstable)", | |
| ) | |
| .value_name("PATH") | |
| .help_heading(heading::COMPILATION_OPTIONS), | |
| ) | |
| ._arg(unsupported_short_arg) | |
| ._arg({ | |
| let value_parser = UnknownArgumentValueParser::suggest_arg("--artifact-dir"); | |
| Arg::new("unsupported-out-dir-flag") | |
| .help("") | |
| .long("out-dir") | |
| .value_name("PATH") | |
| .value_parser(value_parser) | |
| .action(ArgAction::SetTrue) | |
| .hide(true) | |
| }) | |
| } | |
| fn arg_compile_time_deps(self) -> Self { | |
| self._arg(flag("compile-time-deps", "").hide(true)) | |
| } | |
| } | |
| impl CommandExt for Command { | |
| fn _arg(self, arg: Arg) -> Self { | |
| self.arg(arg) | |
| } | |
| } | |
| pub fn flag(name: &'static str, help: &'static str) -> Arg { | |
| Arg::new(name) | |
| .long(name) | |
| .help(help) | |
| .action(ArgAction::SetTrue) | |
| } | |
| pub fn opt(name: &'static str, help: &'static str) -> Arg { | |
| Arg::new(name).long(name).help(help).action(ArgAction::Set) | |
| } | |
| pub fn optional_opt(name: &'static str, help: &'static str) -> Arg { | |
| opt(name, help).num_args(0..=1) | |
| } | |
| pub fn optional_multi_opt(name: &'static str, value_name: &'static str, help: &'static str) -> Arg { | |
| opt(name, help) | |
| .value_name(value_name) | |
| .num_args(0..=1) | |
| .action(ArgAction::Append) | |
| } | |
| pub fn multi_opt(name: &'static str, value_name: &'static str, help: &'static str) -> Arg { | |
| opt(name, help) | |
| .value_name(value_name) | |
| .action(ArgAction::Append) | |
| } | |
| pub fn subcommand(name: &'static str) -> Command { | |
| Command::new(name) | |
| } | |
| /// Determines whether or not to gate `--profile` as unstable when resolving it. | |
| pub enum ProfileChecking { | |
| /// `cargo rustc` historically has allowed "test", "bench", and "check". This | |
| /// variant explicitly allows those. | |
| LegacyRustc, | |
| /// `cargo check` and `cargo fix` historically has allowed "test". This variant | |
| /// explicitly allows that on stable. | |
| LegacyTestOnly, | |
| /// All other commands, which allow any valid custom named profile. | |
| Custom, | |
| } | |
| pub trait ArgMatchesExt { | |
| fn value_of_u32(&self, name: &str) -> CargoResult<Option<u32>> { | |
| let arg = match self._value_of(name) { | |
| None => None, | |
| Some(arg) => Some(arg.parse::<u32>().map_err(|_| { | |
| clap::Error::raw( | |
| clap::error::ErrorKind::ValueValidation, | |
| format!("invalid value: could not parse `{}` as a number", arg), | |
| ) | |
| })?), | |
| }; | |
| Ok(arg) | |
| } | |
| fn value_of_i32(&self, name: &str) -> CargoResult<Option<i32>> { | |
| let arg = match self._value_of(name) { | |
| None => None, | |
| Some(arg) => Some(arg.parse::<i32>().map_err(|_| { | |
| clap::Error::raw( | |
| clap::error::ErrorKind::ValueValidation, | |
| format!("invalid value: could not parse `{}` as a number", arg), | |
| ) | |
| })?), | |
| }; | |
| Ok(arg) | |
| } | |
| /// Returns value of the `name` command-line argument as an absolute path | |
| fn value_of_path(&self, name: &str, gctx: &GlobalContext) -> Option<PathBuf> { | |
| self._value_of(name).map(|path| gctx.cwd().join(path)) | |
| } | |
| fn root_manifest(&self, gctx: &GlobalContext) -> CargoResult<PathBuf> { | |
| root_manifest(self._value_of("manifest-path").map(Path::new), gctx) | |
| } | |
| #[tracing::instrument(skip_all)] | |
| fn workspace<'a>(&self, gctx: &'a GlobalContext) -> CargoResult<Workspace<'a>> { | |
| let root = self.root_manifest(gctx)?; | |
| let mut ws = Workspace::new(&root, gctx)?; | |
| ws.set_resolve_honors_rust_version(self.honor_rust_version()); | |
| if gctx.cli_unstable().avoid_dev_deps { | |
| ws.set_require_optional_deps(false); | |
| } | |
| Ok(ws) | |
| } | |
| fn jobs(&self) -> CargoResult<Option<JobsConfig>> { | |
| let arg = match self._value_of("jobs") { | |
| None => None, | |
| Some(arg) => match arg.parse::<i32>() { | |
| Ok(j) => Some(JobsConfig::Integer(j)), | |
| Err(_) => Some(JobsConfig::String(arg.to_string())), | |
| }, | |
| }; | |
| Ok(arg) | |
| } | |
| fn verbose(&self) -> u32 { | |
| self._count("verbose") | |
| } | |
| fn dry_run(&self) -> bool { | |
| self.flag("dry-run") | |
| } | |
| fn keep_going(&self) -> bool { | |
| self.maybe_flag("keep-going") | |
| } | |
| fn honor_rust_version(&self) -> Option<bool> { | |
| self.flag("ignore-rust-version").then_some(false) | |
| } | |
| fn targets(&self) -> CargoResult<Vec<String>> { | |
| if self.is_present_with_zero_values("target") { | |
| let cmd = if is_rustup() { | |
| "rustup target list" | |
| } else { | |
| "rustc --print target-list" | |
| }; | |
| bail!( | |
| "\"--target\" takes a target architecture as an argument. | |
| Run `{cmd}` to see possible targets." | |
| ); | |
| } | |
| Ok(self._values_of("target")) | |
| } | |
| fn get_profile_name( | |
| &self, | |
| default: &str, | |
| profile_checking: ProfileChecking, | |
| ) -> CargoResult<InternedString> { | |
| let specified_profile = self._value_of("profile"); | |
| // Check for allowed legacy names. | |
| // This is an early exit, since it allows combination with `--release`. | |
| match (specified_profile, profile_checking) { | |
| // `cargo rustc` has legacy handling of these names | |
| (Some(name @ ("dev" | "test" | "bench" | "check")), ProfileChecking::LegacyRustc) | |
| // `cargo fix` and `cargo check` has legacy handling of this profile name | |
| | (Some(name @ "test"), ProfileChecking::LegacyTestOnly) => { | |
| return Ok(name.into()); | |
| } | |
| _ => {} | |
| } | |
| let name = match ( | |
| self.maybe_flag("release"), | |
| self.maybe_flag("debug"), | |
| specified_profile, | |
| ) { | |
| (false, false, None) => default, | |
| (true, _, None) => "release", | |
| (_, true, None) => "debug", | |
| // `doc` is separate from all the other reservations because | |
| // [profile.doc] was historically allowed, but is deprecated and | |
| // has no effect. To avoid potentially breaking projects, it is a | |
| // warning in Cargo.toml, but since `--profile` is new, we can | |
| // reject it completely here. | |
| (_, _, Some("doc")) => { | |
| bail!("profile `doc` is reserved and not allowed to be explicitly specified") | |
| } | |
| (_, _, Some(name)) => { | |
| ProfileName::new(name)?; | |
| name | |
| } | |
| }; | |
| Ok(name.into()) | |
| } | |
| fn packages_from_flags(&self) -> CargoResult<Packages> { | |
| Packages::from_flags( | |
| // TODO Integrate into 'workspace' | |
| self.flag("workspace") || self.flag("all"), | |
| self._values_of("exclude"), | |
| self._values_of("package"), | |
| ) | |
| } | |
| fn compile_options( | |
| &self, | |
| gctx: &GlobalContext, | |
| intent: UserIntent, | |
| workspace: Option<&Workspace<'_>>, | |
| profile_checking: ProfileChecking, | |
| ) -> CargoResult<CompileOptions> { | |
| let spec = self.packages_from_flags()?; | |
| let mut message_format = None; | |
| let default_json = MessageFormat::Json { | |
| short: false, | |
| ansi: false, | |
| render_diagnostics: false, | |
| }; | |
| let two_kinds_of_msg_format_err = "cannot specify two kinds of `message-format` arguments"; | |
| for fmt in self._values_of("message-format") { | |
| for fmt in fmt.split(',') { | |
| let fmt = fmt.to_ascii_lowercase(); | |
| match fmt.as_str() { | |
| "json" => { | |
| if message_format.is_some() { | |
| bail!(two_kinds_of_msg_format_err); | |
| } | |
| message_format = Some(default_json); | |
| } | |
| "human" => { | |
| if message_format.is_some() { | |
| bail!(two_kinds_of_msg_format_err); | |
| } | |
| message_format = Some(MessageFormat::Human); | |
| } | |
| "short" => { | |
| if message_format.is_some() { | |
| bail!(two_kinds_of_msg_format_err); | |
| } | |
| message_format = Some(MessageFormat::Short); | |
| } | |
| "json-render-diagnostics" => { | |
| if message_format.is_none() { | |
| message_format = Some(default_json); | |
| } | |
| match &mut message_format { | |
| Some(MessageFormat::Json { | |
| render_diagnostics, .. | |
| }) => *render_diagnostics = true, | |
| _ => bail!(two_kinds_of_msg_format_err), | |
| } | |
| } | |
| "json-diagnostic-short" => { | |
| if message_format.is_none() { | |
| message_format = Some(default_json); | |
| } | |
| match &mut message_format { | |
| Some(MessageFormat::Json { short, .. }) => *short = true, | |
| _ => bail!(two_kinds_of_msg_format_err), | |
| } | |
| } | |
| "json-diagnostic-rendered-ansi" => { | |
| if message_format.is_none() { | |
| message_format = Some(default_json); | |
| } | |
| match &mut message_format { | |
| Some(MessageFormat::Json { ansi, .. }) => *ansi = true, | |
| _ => bail!(two_kinds_of_msg_format_err), | |
| } | |
| } | |
| s => bail!("invalid message format specifier: `{}`", s), | |
| } | |
| } | |
| } | |
| let mut build_config = BuildConfig::new( | |
| gctx, | |
| self.jobs()?, | |
| self.keep_going(), | |
| &self.targets()?, | |
| intent, | |
| )?; | |
| build_config.message_format = message_format.unwrap_or(MessageFormat::Human); | |
| let default_profile = gctx | |
| .build_config()? | |
| .profile | |
| .as_ref() | |
| .map(|p| p.as_ref()) | |
| .unwrap_or_else(|| "dev"); | |
| build_config.requested_profile = | |
| self.get_profile_name(default_profile, profile_checking)?; | |
| build_config.unit_graph = self.flag("unit-graph"); | |
| build_config.future_incompat_report = self.flag("future-incompat-report"); | |
| build_config.compile_time_deps_only = self.flag("compile-time-deps"); | |
| build_config.timing_report = self.flag("timings"); | |
| if build_config.unit_graph { | |
| gctx.cli_unstable() | |
| .fail_if_stable_opt("--unit-graph", 8002)?; | |
| } | |
| if build_config.compile_time_deps_only { | |
| gctx.cli_unstable() | |
| .fail_if_stable_opt("--compile-time-deps", 14434)?; | |
| } | |
| let opts = CompileOptions { | |
| build_config, | |
| cli_features: self.cli_features()?, | |
| spec, | |
| filter: CompileFilter::from_raw_arguments( | |
| self.flag("lib"), | |
| self._values_of("bin"), | |
| self.flag("bins"), | |
| self._values_of("test"), | |
| self.flag("tests"), | |
| self._values_of("example"), | |
| self.flag("examples"), | |
| self._values_of("bench"), | |
| self.flag("benches"), | |
| self.flag("all-targets"), | |
| ), | |
| target_rustdoc_args: None, | |
| target_rustc_args: None, | |
| target_rustc_crate_types: None, | |
| rustdoc_document_private_items: false, | |
| honor_rust_version: self.honor_rust_version(), | |
| }; | |
| if let Some(ws) = workspace { | |
| self.check_optional_opts(ws, &opts)?; | |
| } else if self.is_present_with_zero_values("package") { | |
| // As for cargo 0.50.0, this won't occur but if someone sneaks in | |
| // we can still provide this informative message for them. | |
| anyhow::bail!( | |
| "\"--package <SPEC>\" requires a SPEC format value, \ | |
| which can be any package ID specifier in the dependency graph.\n\ | |
| Run `cargo help pkgid` for more information about SPEC format." | |
| ) | |
| } | |
| Ok(opts) | |
| } | |
| fn cli_features(&self) -> CargoResult<CliFeatures> { | |
| CliFeatures::from_command_line( | |
| &self._values_of("features"), | |
| self.flag("all-features"), | |
| !self.flag("no-default-features"), | |
| ) | |
| } | |
| fn compile_options_for_single_package( | |
| &self, | |
| gctx: &GlobalContext, | |
| intent: UserIntent, | |
| workspace: Option<&Workspace<'_>>, | |
| profile_checking: ProfileChecking, | |
| ) -> CargoResult<CompileOptions> { | |
| let mut compile_opts = self.compile_options(gctx, intent, workspace, profile_checking)?; | |
| let spec = self._values_of("package"); | |
| if spec.iter().any(restricted_names::is_glob_pattern) { | |
| anyhow::bail!("glob patterns on package selection are not supported.") | |
| } | |
| compile_opts.spec = Packages::Packages(spec); | |
| Ok(compile_opts) | |
| } | |
| fn new_options(&self, gctx: &GlobalContext) -> CargoResult<NewOptions> { | |
| let vcs = self._value_of("vcs").map(|vcs| { | |
| vcs.parse::<VersionControl>() | |
| .expect("clap ensures only valid values are present") | |
| }); | |
| NewOptions::new( | |
| vcs, | |
| self.flag("bin"), | |
| self.flag("lib"), | |
| self.value_of_path("path", gctx).unwrap(), | |
| self._value_of("name").map(|s| s.to_string()), | |
| self._value_of("edition").map(|s| s.to_string()), | |
| self.registry(gctx)?, | |
| ) | |
| } | |
| fn registry_or_index(&self, gctx: &GlobalContext) -> CargoResult<Option<RegistryOrIndex>> { | |
| let registry = self._value_of("registry"); | |
| let index = self._value_of("index"); | |
| let result = match (registry, index) { | |
| (None, None) => gctx.default_registry()?.map(RegistryOrIndex::Registry), | |
| (None, Some(i)) => Some(RegistryOrIndex::Index(i.into_url()?)), | |
| (Some(r), None) => { | |
| RegistryName::new(r)?; | |
| Some(RegistryOrIndex::Registry(r.to_string())) | |
| } | |
| (Some(_), Some(_)) => { | |
| // Should be guarded by clap | |
| unreachable!("both `--index` and `--registry` should not be set at the same time") | |
| } | |
| }; | |
| Ok(result) | |
| } | |
| fn registry(&self, gctx: &GlobalContext) -> CargoResult<Option<String>> { | |
| match self._value_of("registry").map(|s| s.to_string()) { | |
| None => gctx.default_registry(), | |
| Some(registry) => { | |
| RegistryName::new(®istry)?; | |
| Ok(Some(registry)) | |
| } | |
| } | |
| } | |
| fn check_optional_opts( | |
| &self, | |
| workspace: &Workspace<'_>, | |
| compile_opts: &CompileOptions, | |
| ) -> CargoResult<()> { | |
| if self.is_present_with_zero_values("package") { | |
| print_available_packages(workspace)? | |
| } | |
| if self.is_present_with_zero_values("example") { | |
| print_available_examples(workspace, compile_opts)?; | |
| } | |
| if self.is_present_with_zero_values("bin") { | |
| print_available_binaries(workspace, compile_opts)?; | |
| } | |
| if self.is_present_with_zero_values("bench") { | |
| print_available_benches(workspace, compile_opts)?; | |
| } | |
| if self.is_present_with_zero_values("test") { | |
| print_available_tests(workspace, compile_opts)?; | |
| } | |
| Ok(()) | |
| } | |
| fn is_present_with_zero_values(&self, name: &str) -> bool { | |
| self._contains(name) && self._value_of(name).is_none() | |
| } | |
| fn flag(&self, name: &str) -> bool; | |
| fn maybe_flag(&self, name: &str) -> bool; | |
| fn _value_of(&self, name: &str) -> Option<&str>; | |
| fn _values_of(&self, name: &str) -> Vec<String>; | |
| fn _value_of_os(&self, name: &str) -> Option<&OsStr>; | |
| fn _values_of_os(&self, name: &str) -> Vec<OsString>; | |
| fn _count(&self, name: &str) -> u32; | |
| fn _contains(&self, name: &str) -> bool; | |
| } | |
| impl<'a> ArgMatchesExt for ArgMatches { | |
| fn flag(&self, name: &str) -> bool { | |
| ignore_unknown(self.try_get_one::<bool>(name)) | |
| .copied() | |
| .unwrap_or(false) | |
| } | |
| // This works around before an upstream fix in clap for `UnknownArgumentValueParser` accepting | |
| // generics arguments. `flag()` cannot be used with `--keep-going` at this moment due to | |
| // <https://github.com/clap-rs/clap/issues/5081>. | |
| fn maybe_flag(&self, name: &str) -> bool { | |
| self.try_get_one::<bool>(name) | |
| .ok() | |
| .flatten() | |
| .copied() | |
| .unwrap_or_default() | |
| } | |
| fn _value_of(&self, name: &str) -> Option<&str> { | |
| ignore_unknown(self.try_get_one::<String>(name)).map(String::as_str) | |
| } | |
| fn _value_of_os(&self, name: &str) -> Option<&OsStr> { | |
| ignore_unknown(self.try_get_one::<OsString>(name)).map(OsString::as_os_str) | |
| } | |
| fn _values_of(&self, name: &str) -> Vec<String> { | |
| ignore_unknown(self.try_get_many::<String>(name)) | |
| .unwrap_or_default() | |
| .cloned() | |
| .collect() | |
| } | |
| fn _values_of_os(&self, name: &str) -> Vec<OsString> { | |
| ignore_unknown(self.try_get_many::<OsString>(name)) | |
| .unwrap_or_default() | |
| .cloned() | |
| .collect() | |
| } | |
| fn _count(&self, name: &str) -> u32 { | |
| *ignore_unknown(self.try_get_one::<u8>(name)).expect("defaulted by clap") as u32 | |
| } | |
| fn _contains(&self, name: &str) -> bool { | |
| ignore_unknown(self.try_contains_id(name)) | |
| } | |
| } | |
| pub fn values(args: &ArgMatches, name: &str) -> Vec<String> { | |
| args._values_of(name) | |
| } | |
| pub fn values_os(args: &ArgMatches, name: &str) -> Vec<OsString> { | |
| args._values_of_os(name) | |
| } | |
| pub fn root_manifest(manifest_path: Option<&Path>, gctx: &GlobalContext) -> CargoResult<PathBuf> { | |
| if let Some(manifest_path) = manifest_path { | |
| let path = gctx.cwd().join(manifest_path); | |
| // In general, we try to avoid normalizing paths in Cargo, | |
| // but in this particular case we need it to fix #3586. | |
| let path = paths::normalize_path(&path); | |
| if !path.exists() { | |
| anyhow::bail!("manifest path `{}` does not exist", manifest_path.display()) | |
| } else if path.is_dir() { | |
| let child_path = path.join("Cargo.toml"); | |
| let suggested_path = if child_path.exists() { | |
| format!("\nhelp: {} exists", child_path.display()) | |
| } else { | |
| "".to_string() | |
| }; | |
| anyhow::bail!( | |
| "manifest path `{}` is a directory but expected a file{suggested_path}", | |
| manifest_path.display() | |
| ) | |
| } else if !path.ends_with("Cargo.toml") && !crate::workspace::parser::is_embedded(&path) { | |
| if gctx.cli_unstable().script { | |
| anyhow::bail!( | |
| "the manifest-path must be a path to a Cargo.toml or script file: `{}`", | |
| path.display() | |
| ) | |
| } else { | |
| anyhow::bail!( | |
| "the manifest-path must be a path to a Cargo.toml file: `{}`", | |
| path.display() | |
| ) | |
| } | |
| } | |
| if crate::workspace::parser::is_embedded(&path) && !gctx.cli_unstable().script { | |
| anyhow::bail!("embedded manifest `{}` requires `-Zscript`", path.display()) | |
| } | |
| Ok(path) | |
| } else { | |
| find_root_manifest_for_wd(gctx.cwd()) | |
| } | |
| } | |
| pub fn get_registry_candidates() -> CargoResult<Vec<clap_complete::CompletionCandidate>> { | |
| let gctx = new_gctx_for_completions()?; | |
| if let Ok(Some(registries)) = | |
| gctx.get::<Option<HashMap<String, HashMap<String, String>>>>(["registries"]) | |
| { | |
| Ok(registries | |
| .keys() | |
| .map(|name| clap_complete::CompletionCandidate::new(name.to_owned())) | |
| .collect()) | |
| } else { | |
| Ok(vec![]) | |
| } | |
| } | |
| fn get_profile_candidates() -> Vec<clap_complete::CompletionCandidate> { | |
| match get_workspace_profile_candidates() { | |
| Ok(candidates) if !candidates.is_empty() => candidates, | |
| // fallback to default profile candidates | |
| _ => default_profile_candidates(), | |
| } | |
| } | |
| fn get_workspace_profile_candidates() -> CargoResult<Vec<clap_complete::CompletionCandidate>> { | |
| let gctx = new_gctx_for_completions()?; | |
| let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?; | |
| let profiles = Profiles::new(&ws, "dev".into())?; | |
| let mut candidates = Vec::new(); | |
| for name in profiles.profile_names() { | |
| let Ok(profile_instance) = Profiles::new(&ws, name) else { | |
| continue; | |
| }; | |
| let base_profile = profile_instance.base_profile(); | |
| let mut description = String::from(if base_profile.opt_level.as_str() == "0" { | |
| "unoptimized" | |
| } else { | |
| "optimized" | |
| }); | |
| if base_profile.debuginfo.is_turned_on() { | |
| description.push_str(" + debuginfo"); | |
| } | |
| candidates | |
| .push(clap_complete::CompletionCandidate::new(&name).help(Some(description.into()))); | |
| } | |
| Ok(candidates) | |
| } | |
| fn default_profile_candidates() -> Vec<clap_complete::CompletionCandidate> { | |
| vec![ | |
| clap_complete::CompletionCandidate::new("dev").help(Some("unoptimized + debuginfo".into())), | |
| clap_complete::CompletionCandidate::new("release").help(Some("optimized".into())), | |
| clap_complete::CompletionCandidate::new("test") | |
| .help(Some("unoptimized + debuginfo".into())), | |
| clap_complete::CompletionCandidate::new("bench").help(Some("optimized".into())), | |
| ] | |
| } | |
| fn get_feature_candidates() -> CargoResult<Vec<clap_complete::CompletionCandidate>> { | |
| let gctx = new_gctx_for_completions()?; | |
| let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?; | |
| let mut feature_candidates: IndexMap<&str, (usize, Vec<&str>)> = IndexMap::default(); | |
| // Process all packages in the workspace | |
| for package in ws.members() { | |
| let package_name = package.name(); | |
| let order = if ws.current_opt().map(|p| p.name()) == Some(package_name) { | |
| 0 | |
| } else { | |
| 1 | |
| }; | |
| // Add direct features with package info | |
| for feature_name in package.summary().features().keys() { | |
| let (min_order, packages) = feature_candidates | |
| .entry(feature_name.as_str()) | |
| .or_insert((order, Vec::new())); | |
| *min_order = (*min_order).min(order); | |
| packages.push(package_name.as_str()); | |
| } | |
| } | |
| Ok(feature_candidates | |
| .into_iter() | |
| .map(|(feature_name, (order, packages))| { | |
| clap_complete::CompletionCandidate::new(feature_name) | |
| .display_order(Some(order)) | |
| .help(Some(format!("from {}", packages.join(", ")).into())) | |
| }) | |
| .collect()) | |
| } | |
| fn get_crate_candidates(kind: TargetKind) -> CargoResult<Vec<clap_complete::CompletionCandidate>> { | |
| let gctx = new_gctx_for_completions()?; | |
| let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?; | |
| let targets = ws | |
| .members() | |
| .flat_map(|pkg| pkg.targets().into_iter().cloned().map(|t| (pkg.name(), t))) | |
| .filter(|(_, target)| *target.kind() == kind) | |
| .map(|(pkg_name, target)| { | |
| let order = if ws.current_opt().map(|p| p.name()) == Some(pkg_name) { | |
| 0 | |
| } else { | |
| 1 | |
| }; | |
| clap_complete::CompletionCandidate::new(target.name()) | |
| .display_order(Some(order)) | |
| .help(Some(format!("from {}", pkg_name).into())) | |
| }) | |
| .collect::<Vec<_>>(); | |
| Ok(targets) | |
| } | |
| fn get_target_tuples() -> Vec<clap_complete::CompletionCandidate> { | |
| let mut candidates = Vec::new(); | |
| if let Ok(targets) = get_target_tuples_from_rustup() { | |
| candidates = targets; | |
| } | |
| if candidates.is_empty() { | |
| if let Ok(targets) = get_target_tuples_from_rustc() { | |
| candidates = targets; | |
| } | |
| } | |
| // Allow tab-completion for `host-tuple` as the desired target. | |
| candidates.insert( | |
| 0, | |
| clap_complete::CompletionCandidate::new("host-tuple").help(Some( | |
| concat!("alias for: ", env!("RUST_HOST_TARGET")).into(), | |
| )), | |
| ); | |
| candidates | |
| } | |
| pub fn get_target_tuples_with_all() -> Vec<clap_complete::CompletionCandidate> { | |
| let mut candidates = vec![ | |
| clap_complete::CompletionCandidate::new("all").help(Some("Include all targets".into())), | |
| ]; | |
| candidates.extend(get_target_tuples()); | |
| candidates | |
| } | |
| fn get_target_tuples_from_rustup() -> CargoResult<Vec<clap_complete::CompletionCandidate>> { | |
| let output = std::process::Command::new("rustup") | |
| .arg("target") | |
| .arg("list") | |
| .output()?; | |
| if !output.status.success() { | |
| return Ok(vec![]); | |
| } | |
| let stdout = String::from_utf8(output.stdout)?; | |
| Ok(stdout | |
| .lines() | |
| .map(|line| { | |
| let target = line.split_once(' '); | |
| match target { | |
| None => clap_complete::CompletionCandidate::new(line.to_owned()).hide(true), | |
| Some((target, _installed)) => clap_complete::CompletionCandidate::new(target), | |
| } | |
| }) | |
| .collect()) | |
| } | |
| fn get_target_tuples_from_rustc() -> CargoResult<Vec<clap_complete::CompletionCandidate>> { | |
| let gctx = new_gctx_for_completions()?; | |
| let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx); | |
| let rustc = gctx.load_global_rustc(ws.as_ref().ok())?; | |
| let (stdout, _stderr) = | |
| rustc.cached_output(rustc.process().arg("--print").arg("target-list"), 0)?; | |
| Ok(stdout | |
| .lines() | |
| .map(|line| clap_complete::CompletionCandidate::new(line.to_owned())) | |
| .collect()) | |
| } | |
| pub fn get_ws_member_candidates() -> Vec<clap_complete::CompletionCandidate> { | |
| get_ws_member_packages() | |
| .unwrap_or_default() | |
| .into_iter() | |
| .map(|pkg| { | |
| clap_complete::CompletionCandidate::new(pkg.name().as_str()).help( | |
| pkg.manifest() | |
| .metadata() | |
| .description | |
| .to_owned() | |
| .map(From::from), | |
| ) | |
| }) | |
| .collect::<Vec<_>>() | |
| } | |
| fn get_ws_member_packages() -> CargoResult<Vec<Package>> { | |
| let gctx = new_gctx_for_completions()?; | |
| let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?; | |
| let packages = ws.members().map(Clone::clone).collect::<Vec<_>>(); | |
| Ok(packages) | |
| } | |
| pub fn get_pkg_id_spec_candidates() -> Vec<clap_complete::CompletionCandidate> { | |
| let mut candidates = vec![]; | |
| let package_map = HashMap::<&str, Vec<Package>>::default(); | |
| let package_map = | |
| get_packages() | |
| .unwrap_or_default() | |
| .into_iter() | |
| .fold(package_map, |mut map, package| { | |
| map.entry(package.name().as_str()) | |
| .or_insert_with(Vec::new) | |
| .push(package); | |
| map | |
| }); | |
| let unique_name_candidates = package_map | |
| .iter() | |
| .filter(|(_name, packages)| packages.len() == 1) | |
| .map(|(name, packages)| { | |
| clap_complete::CompletionCandidate::new(name.to_string()).help( | |
| packages[0] | |
| .manifest() | |
| .metadata() | |
| .description | |
| .to_owned() | |
| .map(From::from), | |
| ) | |
| }) | |
| .collect::<Vec<_>>(); | |
| let duplicate_name_pairs = package_map | |
| .iter() | |
| .filter(|(_name, packages)| packages.len() > 1) | |
| .collect::<Vec<_>>(); | |
| let mut duplicate_name_candidates = vec![]; | |
| for (name, packages) in duplicate_name_pairs { | |
| let mut version_count: HashMap<&Version, usize> = HashMap::default(); | |
| for package in packages { | |
| *version_count.entry(package.version()).or_insert(0) += 1; | |
| } | |
| for package in packages { | |
| if let Some(&count) = version_count.get(package.version()) { | |
| if count == 1 { | |
| duplicate_name_candidates.push( | |
| clap_complete::CompletionCandidate::new(format!( | |
| "{}@{}", | |
| name, | |
| package.version() | |
| )) | |
| .help( | |
| package | |
| .manifest() | |
| .metadata() | |
| .description | |
| .to_owned() | |
| .map(From::from), | |
| ), | |
| ); | |
| } else { | |
| duplicate_name_candidates.push( | |
| clap_complete::CompletionCandidate::new(format!( | |
| "{}", | |
| package.package_id().to_spec() | |
| )) | |
| .help( | |
| package | |
| .manifest() | |
| .metadata() | |
| .description | |
| .to_owned() | |
| .map(From::from), | |
| ), | |
| ) | |
| } | |
| } | |
| } | |
| } | |
| candidates.extend(unique_name_candidates); | |
| candidates.extend(duplicate_name_candidates); | |
| candidates | |
| } | |
| pub fn get_pkg_name_candidates() -> Vec<clap_complete::CompletionCandidate> { | |
| let packages: BTreeMap<_, _> = get_packages() | |
| .unwrap_or_default() | |
| .into_iter() | |
| .map(|package| { | |
| ( | |
| package.name(), | |
| package.manifest().metadata().description.clone(), | |
| ) | |
| }) | |
| .collect(); | |
| packages | |
| .into_iter() | |
| .map(|(name, description)| { | |
| clap_complete::CompletionCandidate::new(name.as_str()).help(description.map(From::from)) | |
| }) | |
| .collect() | |
| } | |
| fn get_packages() -> CargoResult<Vec<Package>> { | |
| let gctx = new_gctx_for_completions()?; | |
| let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?; | |
| let requested_kinds = CompileKind::from_requested_targets(ws.gctx(), &[])?; | |
| let mut target_data = RustcTargetData::new(&ws, &requested_kinds)?; | |
| // `cli_features.all_features` must be true in case that `specs` is empty. | |
| let cli_features = CliFeatures::new_all(true); | |
| let has_dev_units = HasDevUnits::Yes; | |
| let force_all_targets = ForceAllTargets::No; | |
| let dry_run = true; | |
| let ws_resolve = ops::resolve_ws_with_opts( | |
| &ws, | |
| &mut target_data, | |
| &requested_kinds, | |
| &cli_features, | |
| &[], | |
| has_dev_units, | |
| force_all_targets, | |
| dry_run, | |
| )?; | |
| let packages = ws_resolve | |
| .pkg_set | |
| .packages() | |
| .map(Clone::clone) | |
| .collect::<Vec<_>>(); | |
| Ok(packages) | |
| } | |
| pub fn get_direct_dependencies_pkg_name_candidates() -> Vec<clap_complete::CompletionCandidate> { | |
| let (current_package_deps, all_package_deps) = match get_dependencies_from_metadata() { | |
| Ok(v) => v, | |
| Err(_) => return Vec::new(), | |
| }; | |
| let current_package_deps_package_names = current_package_deps | |
| .into_iter() | |
| .map(|dep| dep.package_name().to_string()) | |
| .sorted(); | |
| let all_package_deps_package_names = all_package_deps | |
| .into_iter() | |
| .map(|dep| dep.package_name().to_string()) | |
| .sorted(); | |
| let mut package_names_set = IndexSet::default(); | |
| package_names_set.extend(current_package_deps_package_names); | |
| package_names_set.extend(all_package_deps_package_names); | |
| package_names_set | |
| .into_iter() | |
| .map(|name| name.into()) | |
| .collect_vec() | |
| } | |
| fn get_dependencies_from_metadata() -> CargoResult<(Vec<Dependency>, Vec<Dependency>)> { | |
| let cwd = std::env::current_dir()?; | |
| let gctx = GlobalContext::new(shell::Shell::new(), cwd.clone(), cargo_home_with_cwd(&cwd)?); | |
| let ws = Workspace::new(&find_root_manifest_for_wd(&cwd)?, &gctx)?; | |
| let current_package = ws.current().ok(); | |
| let current_package_dependencies = ws | |
| .current() | |
| .map(|current| current.dependencies()) | |
| .unwrap_or_default() | |
| .to_vec(); | |
| let all_other_packages_dependencies = ws | |
| .members() | |
| .filter(|&member| Some(member) != current_package) | |
| .flat_map(|pkg| pkg.dependencies().into_iter().cloned()) | |
| .collect::<HashSet<_>>() | |
| .into_iter() | |
| .collect::<Vec<_>>(); | |
| Ok(( | |
| current_package_dependencies, | |
| all_other_packages_dependencies, | |
| )) | |
| } | |
| pub fn new_gctx_for_completions() -> CargoResult<GlobalContext> { | |
| let cwd = std::env::current_dir()?; | |
| let mut gctx = GlobalContext::new(shell::Shell::new(), cwd.clone(), cargo_home_with_cwd(&cwd)?); | |
| let verbose = 0; | |
| let quiet = true; | |
| let color = None; | |
| let frozen = false; | |
| let locked = true; | |
| let offline = false; | |
| let target_dir = None; | |
| let unstable_flags = &[]; | |
| let cli_config = &[]; | |
| gctx.configure( | |
| verbose, | |
| quiet, | |
| color, | |
| frozen, | |
| locked, | |
| offline, | |
| &target_dir, | |
| unstable_flags, | |
| cli_config, | |
| )?; | |
| Ok(gctx) | |
| } | |
| #[track_caller] | |
| pub fn ignore_unknown<T: Default>(r: Result<T, clap::parser::MatchesError>) -> T { | |
| match r { | |
| Ok(t) => t, | |
| Err(clap::parser::MatchesError::UnknownArgument { .. }) => Default::default(), | |
| Err(e) => { | |
| panic!("Mismatch between definition and access: {}", e); | |
| } | |
| } | |
| } | |
| /// Warns that an occurrence of a flag without a value is ignored. | |
| pub fn warn_ignored_zero_values(gctx: &GlobalContext, name: &str) -> CargoResult<()> { | |
| gctx.shell().print_report( | |
| &[Level::WARNING | |
| .secondary_title(format!("`--{name}` without a value is ignored")) | |
| .elements([ | |
| Level::NOTE.message( | |
| "this was previously accepted but will become a hard error in the future", | |
| ), | |
| Level::HELP.message(format!("pass a value to `--{name}` or remove it")), | |
| ])], | |
| false, | |
| ) | |
| } |