use std::collections::BTreeSet;
use std::env;
use std::fmt::{self, Write};
use std::str::FromStr;
use anyhow::{bail, Error};
use cargo_util::ProcessBuilder;
use serde::{Deserialize, Serialize};
use crate::core::resolver::ResolveBehavior;
use crate::util::errors::CargoResult;
use crate::util::{indented_lines, iter_join};
use crate::Config;
pub const HIDDEN: &str = "";
pub const SEE_CHANNELS: &str =
"See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information \
about Rust release channels.";
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)]
pub enum Edition {
Edition2015,
Edition2018,
Edition2021,
Edition2024,
}
impl Edition {
pub const LATEST_UNSTABLE: Option<Edition> = Some(Edition::Edition2024);
pub const LATEST_STABLE: Edition = Edition::Edition2021;
pub const CLI_VALUES: [&'static str; 4] = ["2015", "2018", "2021", "2024"];
pub(crate) fn first_version(&self) -> Option<semver::Version> {
use Edition::*;
match self {
Edition2015 => None,
Edition2018 => Some(semver::Version::new(1, 31, 0)),
Edition2021 => Some(semver::Version::new(1, 56, 0)),
Edition2024 => None,
}
}
pub fn is_stable(&self) -> bool {
use Edition::*;
match self {
Edition2015 => true,
Edition2018 => true,
Edition2021 => true,
Edition2024 => false,
}
}
pub fn previous(&self) -> Option<Edition> {
use Edition::*;
match self {
Edition2015 => None,
Edition2018 => Some(Edition2015),
Edition2021 => Some(Edition2018),
Edition2024 => Some(Edition2021),
}
}
pub fn saturating_next(&self) -> Edition {
use Edition::*;
match self {
Edition2015 => Edition2018,
Edition2018 => Edition2021,
Edition2021 => Edition2024,
Edition2024 => Edition2024,
}
}
pub(crate) fn cmd_edition_arg(&self, cmd: &mut ProcessBuilder) {
if *self != Edition::Edition2015 {
cmd.arg(format!("--edition={}", self));
}
if !self.is_stable() {
cmd.arg("-Z").arg("unstable-options");
}
}
pub(crate) fn supports_compat_lint(&self) -> bool {
use Edition::*;
match self {
Edition2015 => false,
Edition2018 => true,
Edition2021 => true,
Edition2024 => false,
}
}
pub(crate) fn supports_idiom_lint(&self) -> bool {
use Edition::*;
match self {
Edition2015 => false,
Edition2018 => true,
Edition2021 => false,
Edition2024 => false,
}
}
pub(crate) fn default_resolve_behavior(&self) -> ResolveBehavior {
if *self >= Edition::Edition2021 {
ResolveBehavior::V2
} else {
ResolveBehavior::V1
}
}
}
impl fmt::Display for Edition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Edition::Edition2015 => f.write_str("2015"),
Edition::Edition2018 => f.write_str("2018"),
Edition::Edition2021 => f.write_str("2021"),
Edition::Edition2024 => f.write_str("2024"),
}
}
}
impl FromStr for Edition {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s {
"2015" => Ok(Edition::Edition2015),
"2018" => Ok(Edition::Edition2018),
"2021" => Ok(Edition::Edition2021),
"2024" => Ok(Edition::Edition2024),
s if s.parse().map_or(false, |y: u16| y > 2024 && y < 2050) => bail!(
"this version of Cargo is older than the `{}` edition, \
and only supports `2015`, `2018`, `2021`, and `2024` editions.",
s
),
s => bail!(
"supported edition values are `2015`, `2018`, `2021`, or `2024`, \
but `{}` is unknown",
s
),
}
}
}
#[derive(PartialEq)]
enum Status {
Stable,
Unstable,
Removed,
}
macro_rules! features {
(
$(($stab:ident, $feature:ident, $version:expr, $docs:expr),)*
) => (
#[derive(Default, Clone, Debug)]
pub struct Features {
$($feature: bool,)*
activated: Vec<String>,
nightly_features_allowed: bool,
is_local: bool,
}
impl Feature {
$(
pub fn $feature() -> &'static Feature {
fn get(features: &Features) -> bool {
stab!($stab) == Status::Stable || features.$feature
}
static FEAT: Feature = Feature {
name: stringify!($feature),
stability: stab!($stab),
version: $version,
docs: $docs,
get,
};
&FEAT
}
)*
fn is_enabled(&self, features: &Features) -> bool {
(self.get)(features)
}
}
impl Features {
fn status(&mut self, feature: &str) -> Option<(&mut bool, &'static Feature)> {
if feature.contains("_") {
return None
}
let feature = feature.replace("-", "_");
$(
if feature == stringify!($feature) {
return Some((&mut self.$feature, Feature::$feature()))
}
)*
None
}
}
)
}
macro_rules! stab {
(stable) => {
Status::Stable
};
(unstable) => {
Status::Unstable
};
(removed) => {
Status::Removed
};
}
features! {
(stable, test_dummy_stable, "1.0", ""),
(unstable, test_dummy_unstable, "", "reference/unstable.html"),
(stable, alternative_registries, "1.34", "reference/registries.html"),
(stable, edition, "1.31", "reference/manifest.html#the-edition-field"),
(stable, rename_dependency, "1.31", "reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml"),
(removed, publish_lockfile, "1.37", "reference/unstable.html#publish-lockfile"),
(stable, profile_overrides, "1.41", "reference/profiles.html#overrides"),
(stable, default_run, "1.37", "reference/manifest.html#the-default-run-field"),
(unstable, metabuild, "", "reference/unstable.html#metabuild"),
(unstable, public_dependency, "", "reference/unstable.html#public-dependency"),
(stable, named_profiles, "1.57", "reference/profiles.html#custom-profiles"),
(stable, resolver, "1.51", "reference/resolver.html#resolver-versions"),
(stable, strip, "1.58", "reference/profiles.html#strip-option"),
(stable, rust_version, "1.56", "reference/manifest.html#the-rust-version-field"),
(stable, edition2021, "1.56", "reference/manifest.html#the-edition-field"),
(unstable, per_package_target, "", "reference/unstable.html#per-package-target"),
(unstable, codegen_backend, "", "reference/unstable.html#codegen-backend"),
(unstable, different_binary_name, "", "reference/unstable.html#different-binary-name"),
(unstable, profile_rustflags, "", "reference/unstable.html#profile-rustflags-option"),
(stable, workspace_inheritance, "1.64", "reference/unstable.html#workspace-inheritance"),
(unstable, edition2024, "", "reference/unstable.html#edition-2024"),
(unstable, trim_paths, "", "reference/unstable.html#profile-trim-paths-option"),
}
pub struct Feature {
name: &'static str,
stability: Status,
version: &'static str,
docs: &'static str,
get: fn(&Features) -> bool,
}
impl Features {
pub fn new(
features: &[String],
config: &Config,
warnings: &mut Vec<String>,
is_local: bool,
) -> CargoResult<Features> {
let mut ret = Features::default();
ret.nightly_features_allowed = config.nightly_features_allowed;
ret.is_local = is_local;
for feature in features {
ret.add(feature, config, warnings)?;
ret.activated.push(feature.to_string());
}
Ok(ret)
}
fn add(
&mut self,
feature_name: &str,
config: &Config,
warnings: &mut Vec<String>,
) -> CargoResult<()> {
let nightly_features_allowed = self.nightly_features_allowed;
let is_local = self.is_local;
let Some((slot, feature)) = self.status(feature_name) else {
bail!("unknown cargo feature `{}`", feature_name)
};
if *slot {
bail!(
"the cargo feature `{}` has already been activated",
feature_name
);
}
let see_docs = || {
format!(
"See {} for more information about using this feature.",
cargo_docs_link(feature.docs)
)
};
match feature.stability {
Status::Stable => {
if is_local {
let warning = format!(
"the cargo feature `{}` has been stabilized in the {} \
release and is no longer necessary to be listed in the \
manifest\n {}",
feature_name,
feature.version,
see_docs()
);
warnings.push(warning);
}
}
Status::Unstable if !nightly_features_allowed => bail!(
"the cargo feature `{}` requires a nightly version of \
Cargo, but this is the `{}` channel\n\
{}\n{}",
feature_name,
channel(),
SEE_CHANNELS,
see_docs()
),
Status::Unstable => {
if let Some(allow) = &config.cli_unstable().allow_features {
if !allow.contains(feature_name) {
bail!(
"the feature `{}` is not in the list of allowed features: [{}]",
feature_name,
iter_join(allow, ", "),
);
}
}
}
Status::Removed => {
let mut msg = format!(
"the cargo feature `{}` has been removed in the {} release\n\n",
feature_name, feature.version
);
if self.is_local {
let _ = writeln!(
msg,
"Remove the feature from Cargo.toml to remove this error."
);
} else {
let _ = writeln!(
msg,
"This package cannot be used with this version of Cargo, \
as the unstable feature `{}` is no longer supported.",
feature_name
);
}
let _ = writeln!(msg, "{}", see_docs());
bail!(msg);
}
}
*slot = true;
Ok(())
}
pub fn activated(&self) -> &[String] {
&self.activated
}
pub fn require(&self, feature: &Feature) -> CargoResult<()> {
if feature.is_enabled(self) {
return Ok(());
}
let feature_name = feature.name.replace("_", "-");
let mut msg = format!(
"feature `{}` is required\n\
\n\
The package requires the Cargo feature called `{}`, but \
that feature is not stabilized in this version of Cargo ({}).\n\
",
feature_name,
feature_name,
crate::version(),
);
if self.nightly_features_allowed {
if self.is_local {
let _ = writeln!(
msg,
"Consider adding `cargo-features = [\"{}\"]` \
to the top of Cargo.toml (above the [package] table) \
to tell Cargo you are opting in to use this unstable feature.",
feature_name
);
} else {
let _ = writeln!(msg, "Consider trying a more recent nightly release.");
}
} else {
let _ = writeln!(
msg,
"Consider trying a newer version of Cargo \
(this may require the nightly release)."
);
}
let _ = writeln!(
msg,
"See https://doc.rust-lang.org/nightly/cargo/{} for more information \
about the status of this feature.",
feature.docs
);
bail!("{}", msg);
}
pub fn is_enabled(&self, feature: &Feature) -> bool {
feature.is_enabled(self)
}
}
macro_rules! unstable_cli_options {
(
$(
$(#[$meta:meta])?
$element: ident: $ty: ty = ($help: expr ),
)*
) => {
#[derive(Default, Debug, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct CliUnstable {
$(
$(#[$meta])?
pub $element: $ty
),*
}
impl CliUnstable {
pub fn help() -> Vec<(&'static str, &'static str)> {
let fields = vec![$((stringify!($element), $help)),*];
fields
}
}
#[cfg(test)]
mod test {
#[test]
fn ensure_sorted() {
let location = std::panic::Location::caller();
println!(
"\nTo fix this test, sort the features inside the macro at {}:{}\n",
location.file(),
location.line()
);
let mut expected = vec![$(stringify!($element)),*];
expected[2..].sort();
snapbox::assert_eq(
format!("{:#?}", expected),
format!("{:#?}", vec![$(stringify!($element)),*])
);
}
}
}
}
unstable_cli_options!(
allow_features: Option<BTreeSet<String>> = ("Allow *only* the listed unstable features"),
print_im_a_teapot: bool = (HIDDEN),
advanced_env: bool = (HIDDEN),
asymmetric_token: bool = ("Allows authenticating with asymmetric tokens"),
avoid_dev_deps: bool = ("Avoid installing dev-dependencies if possible"),
binary_dep_depinfo: bool = ("Track changes to dependency artifacts"),
bindeps: bool = ("Allow Cargo packages to depend on bin, cdylib, and staticlib crates, and use the artifacts built by those crates"),
#[serde(deserialize_with = "deserialize_build_std")]
build_std: Option<Vec<String>> = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"),
build_std_features: Option<Vec<String>> = ("Configure features enabled for the standard library itself when building the standard library"),
check_cfg: bool = ("Enable compile-time checking of `cfg` names/values/features"),
codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"),
config_include: bool = ("Enable the `include` key in config files"),
direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"),
doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"),
dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
features: Option<Vec<String>> = (HIDDEN),
gc: bool = ("Track cache usage and \"garbage collect\" unused files"),
gitoxide: Option<GitoxideFeatures> = ("Use gitoxide for the given git interactions, or all of them if no argument is given"),
host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"),
lints: bool = ("Pass `[lints]` to the linting tools"),
minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"),
msrv_policy: bool = ("Enable rust-version aware policy within cargo"),
mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"),
next_lockfile_bump: bool = (HIDDEN),
no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"),
publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"),
script: bool = ("Enable support for single-file, `.rs` packages"),
separate_nightlies: bool = (HIDDEN),
skip_rustdoc_fingerprint: bool = (HIDDEN),
target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
trim_paths: bool = ("Enable the `trim-paths` option in profiles"),
unstable_options: bool = ("Allow the usage of unstable options"),
);
const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
enabled when used on an interactive console.\n\
See https://doc.rust-lang.org/cargo/reference/config.html#termprogresswhen \
for information on controlling the progress bar.";
const STABILIZED_OFFLINE: &str = "Offline mode is now available via the \
--offline CLI option";
const STABILIZED_CACHE_MESSAGES: &str = "Message caching is now always enabled.";
const STABILIZED_INSTALL_UPGRADE: &str = "Packages are now always upgraded if \
they appear out of date.\n\
See https://doc.rust-lang.org/cargo/commands/cargo-install.html for more \
information on how upgrading works.";
const STABILIZED_CONFIG_PROFILE: &str = "See \
https://doc.rust-lang.org/cargo/reference/config.html#profile for more \
information about specifying profiles in config.";
const STABILIZED_CRATE_VERSIONS: &str = "The crate version is now \
automatically added to the documentation.";
const STABILIZED_PACKAGE_FEATURES: &str = "Enhanced feature flag behavior is now \
available in virtual workspaces, and `member/feature-name` syntax is also \
always available. Other extensions require setting `resolver = \"2\"` in \
Cargo.toml.\n\
See https://doc.rust-lang.org/nightly/cargo/reference/features.html#resolver-version-2-command-line-flags \
for more information.";
const STABILIZED_FEATURES: &str = "The new feature resolver is now available \
by specifying `resolver = \"2\"` in Cargo.toml.\n\
See https://doc.rust-lang.org/nightly/cargo/reference/features.html#feature-resolver-version-2 \
for more information.";
const STABILIZED_EXTRA_LINK_ARG: &str = "Additional linker arguments are now \
supported without passing this flag.";
const STABILIZED_CONFIGURABLE_ENV: &str = "The [env] section is now always enabled.";
const STABILIZED_PATCH_IN_CONFIG: &str = "The patch-in-config feature is now always enabled.";
const STABILIZED_NAMED_PROFILES: &str = "The named-profiles feature is now always enabled.\n\
See https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#custom-profiles \
for more information";
const STABILIZED_DOCTEST_IN_WORKSPACE: &str =
"The doctest-in-workspace feature is now always enabled.";
const STABILIZED_FUTURE_INCOMPAT_REPORT: &str =
"The future-incompat-report feature is now always enabled.";
const STABILIZED_WEAK_DEP_FEATURES: &str = "Weak dependency features are now always available.";
const STABILISED_NAMESPACED_FEATURES: &str = "Namespaced features are now always available.";
const STABILIZED_TIMINGS: &str = "The -Ztimings option has been stabilized as --timings.";
const STABILISED_MULTITARGET: &str = "Multiple `--target` options are now always available.";
const STABILIZED_TERMINAL_WIDTH: &str =
"The -Zterminal-width option is now always enabled for terminal output.";
const STABILISED_SPARSE_REGISTRY: &str = "The sparse protocol is now the default for crates.io";
const STABILIZED_CREDENTIAL_PROCESS: &str =
"Authentication with a credential provider is always available.";
const STABILIZED_REGISTRY_AUTH: &str =
"Authenticated registries are available if a credential provider is configured.";
fn deserialize_build_std<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
where
D: serde::Deserializer<'de>,
{
let Some(crates) = <Option<Vec<String>>>::deserialize(deserializer)? else {
return Ok(None);
};
let v = crates.join(",");
Ok(Some(
crate::core::compiler::standard_lib::parse_unstable_flag(Some(&v)),
))
}
#[derive(Debug, Copy, Clone, Default, Deserialize)]
pub struct GitoxideFeatures {
pub fetch: bool,
pub shallow_index: bool,
pub shallow_deps: bool,
pub checkout: bool,
pub internal_use_git2: bool,
}
impl GitoxideFeatures {
fn all() -> Self {
GitoxideFeatures {
fetch: true,
shallow_index: true,
checkout: true,
shallow_deps: true,
internal_use_git2: false,
}
}
fn safe() -> Self {
GitoxideFeatures {
fetch: true,
shallow_index: false,
checkout: true,
shallow_deps: false,
internal_use_git2: false,
}
}
}
fn parse_gitoxide(
it: impl Iterator<Item = impl AsRef<str>>,
) -> CargoResult<Option<GitoxideFeatures>> {
let mut out = GitoxideFeatures::default();
let GitoxideFeatures {
fetch,
shallow_index,
checkout,
shallow_deps,
internal_use_git2,
} = &mut out;
for e in it {
match e.as_ref() {
"fetch" => *fetch = true,
"shallow-index" => *shallow_index = true,
"shallow-deps" => *shallow_deps = true,
"checkout" => *checkout = true,
"internal-use-git2" => *internal_use_git2 = true,
_ => {
bail!("unstable 'gitoxide' only takes `fetch`, 'shallow-index', 'shallow-deps' and 'checkout' as valid inputs")
}
}
}
Ok(Some(out))
}
impl CliUnstable {
pub fn parse(
&mut self,
flags: &[String],
nightly_features_allowed: bool,
) -> CargoResult<Vec<String>> {
if !flags.is_empty() && !nightly_features_allowed {
bail!(
"the `-Z` flag is only accepted on the nightly channel of Cargo, \
but this is the `{}` channel\n\
{}",
channel(),
SEE_CHANNELS
);
}
let mut warnings = Vec::new();
for flag in flags {
if flag.starts_with("allow-features=") {
self.add(flag, &mut warnings)?;
}
}
for flag in flags {
self.add(flag, &mut warnings)?;
}
if self.gitoxide.is_none() && cargo_use_gitoxide_instead_of_git2() {
self.gitoxide = GitoxideFeatures::safe().into();
}
Ok(warnings)
}
fn add(&mut self, flag: &str, warnings: &mut Vec<String>) -> CargoResult<()> {
let mut parts = flag.splitn(2, '=');
let k = parts.next().unwrap();
let v = parts.next();
fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> {
match value {
None | Some("yes") => Ok(true),
Some("no") => Ok(false),
Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s),
}
}
fn parse_features(value: Option<&str>) -> Vec<String> {
match value {
None => Vec::new(),
Some("") => Vec::new(),
Some(v) => v.split(',').map(|s| s.to_string()).collect(),
}
}
fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> {
if let Some(v) = value {
bail!("flag -Z{} does not take a value, found: `{}`", key, v);
}
Ok(true)
}
let mut stabilized_warn = |key: &str, version: &str, message: &str| {
warnings.push(format!(
"flag `-Z {}` has been stabilized in the {} release, \
and is no longer necessary\n{}",
key,
version,
indented_lines(message)
));
};
let stabilized_err = |key: &str, version: &str, message: &str| {
Err(anyhow::format_err!(
"flag `-Z {}` has been stabilized in the {} release\n{}",
key,
version,
indented_lines(message)
))
};
if let Some(allowed) = &self.allow_features {
if k != "allow-features" && !allowed.contains(k) {
bail!(
"the feature `{}` is not in the list of allowed features: [{}]",
k,
iter_join(allowed, ", ")
);
}
}
match k {
"allow-features" => self.allow_features = Some(parse_features(v).into_iter().collect()),
"print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?,
"compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS),
"offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?,
"cache-messages" => stabilized_warn(k, "1.40", STABILIZED_CACHE_MESSAGES),
"install-upgrade" => stabilized_warn(k, "1.41", STABILIZED_INSTALL_UPGRADE),
"config-profile" => stabilized_warn(k, "1.43", STABILIZED_CONFIG_PROFILE),
"crate-versions" => stabilized_warn(k, "1.47", STABILIZED_CRATE_VERSIONS),
"features" => {
let feats = parse_features(v);
let stab_is_not_empty = feats.iter().any(|feat| {
matches!(
feat.as_str(),
"build_dep" | "host_dep" | "dev_dep" | "itarget" | "all"
)
});
if stab_is_not_empty || feats.is_empty() {
stabilized_warn(k, "1.51", STABILIZED_FEATURES);
}
self.features = Some(feats);
}
"package-features" => stabilized_warn(k, "1.51", STABILIZED_PACKAGE_FEATURES),
"configurable-env" => stabilized_warn(k, "1.56", STABILIZED_CONFIGURABLE_ENV),
"extra-link-arg" => stabilized_warn(k, "1.56", STABILIZED_EXTRA_LINK_ARG),
"patch-in-config" => stabilized_warn(k, "1.56", STABILIZED_PATCH_IN_CONFIG),
"named-profiles" => stabilized_warn(k, "1.57", STABILIZED_NAMED_PROFILES),
"future-incompat-report" => {
stabilized_warn(k, "1.59.0", STABILIZED_FUTURE_INCOMPAT_REPORT)
}
"namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES),
"timings" => stabilized_warn(k, "1.60", STABILIZED_TIMINGS),
"weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES),
"multitarget" => stabilized_warn(k, "1.64", STABILISED_MULTITARGET),
"sparse-registry" => stabilized_warn(k, "1.68", STABILISED_SPARSE_REGISTRY),
"terminal-width" => stabilized_warn(k, "1.68", STABILIZED_TERMINAL_WIDTH),
"doctest-in-workspace" => stabilized_warn(k, "1.72", STABILIZED_DOCTEST_IN_WORKSPACE),
"credential-process" => stabilized_warn(k, "1.74", STABILIZED_CREDENTIAL_PROCESS),
"registry-auth" => stabilized_warn(k, "1.74", STABILIZED_REGISTRY_AUTH),
"advanced-env" => self.advanced_env = parse_empty(k, v)?,
"asymmetric-token" => self.asymmetric_token = parse_empty(k, v)?,
"avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?,
"binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?,
"bindeps" => self.bindeps = parse_empty(k, v)?,
"build-std" => {
self.build_std = Some(crate::core::compiler::standard_lib::parse_unstable_flag(v))
}
"build-std-features" => self.build_std_features = Some(parse_features(v)),
"check-cfg" => {
self.check_cfg = parse_empty(k, v)?;
}
"codegen-backend" => self.codegen_backend = parse_empty(k, v)?,
"config-include" => self.config_include = parse_empty(k, v)?,
"direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?,
"doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?,
"dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
"gc" => self.gc = parse_empty(k, v)?,
"gitoxide" => {
self.gitoxide = v.map_or_else(
|| Ok(Some(GitoxideFeatures::all())),
|v| parse_gitoxide(v.split(',')),
)?
}
"host-config" => self.host_config = parse_empty(k, v)?,
"lints" => self.lints = parse_empty(k, v)?,
"next-lockfile-bump" => self.next_lockfile_bump = parse_empty(k, v)?,
"minimal-versions" => self.minimal_versions = parse_empty(k, v)?,
"msrv-policy" => self.msrv_policy = parse_empty(k, v)?,
"mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,
"no-index-update" => self.no_index_update = parse_empty(k, v)?,
"panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
"profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?,
"trim-paths" => self.trim_paths = parse_empty(k, v)?,
"publish-timeout" => self.publish_timeout = parse_empty(k, v)?,
"rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
"rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?,
"separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?,
"skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(k, v)?,
"script" => self.script = parse_empty(k, v)?,
"target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?,
"unstable-options" => self.unstable_options = parse_empty(k, v)?,
_ => bail!("\
unknown `-Z` flag specified: {k}\n\n\
For available unstable features, see https://doc.rust-lang.org/nightly/cargo/reference/unstable.html\n\
If you intended to use an unstable rustc feature, try setting `RUSTFLAGS=\"-Z{k}\"`"),
}
Ok(())
}
pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> {
self.fail_if_stable_opt_custom_z(flag, issue, "unstable-options", self.unstable_options)
}
pub fn fail_if_stable_opt_custom_z(
&self,
flag: &str,
issue: u32,
z_name: &str,
enabled: bool,
) -> CargoResult<()> {
if !enabled {
let see = format!(
"See https://github.com/rust-lang/cargo/issues/{issue} for more \
information about the `{flag}` flag."
);
let channel = channel();
if channel == "nightly" || channel == "dev" {
bail!(
"the `{flag}` flag is unstable, pass `-Z {z_name}` to enable it\n\
{see}"
);
} else {
bail!(
"the `{flag}` flag is unstable, and only available on the nightly channel \
of Cargo, but this is the `{channel}` channel\n\
{SEE_CHANNELS}\n\
{see}"
);
}
}
Ok(())
}
pub fn fail_if_stable_command(
&self,
config: &Config,
command: &str,
issue: u32,
z_name: &str,
enabled: bool,
) -> CargoResult<()> {
if enabled {
return Ok(());
}
let see = format!(
"See https://github.com/rust-lang/cargo/issues/{} for more \
information about the `cargo {}` command.",
issue, command
);
if config.nightly_features_allowed {
bail!(
"the `cargo {command}` command is unstable, pass `-Z {z_name}` \
to enable it\n\
{see}",
);
} else {
bail!(
"the `cargo {}` command is unstable, and only available on the \
nightly channel of Cargo, but this is the `{}` channel\n\
{}\n\
{}",
command,
channel(),
SEE_CHANNELS,
see
);
}
}
}
pub fn channel() -> String {
#[allow(clippy::disallowed_methods)]
if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") {
return override_channel;
}
#[allow(clippy::disallowed_methods)]
if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") {
if staging == "1" {
return "dev".to_string();
}
}
crate::version()
.release_channel
.unwrap_or_else(|| String::from("dev"))
}
#[allow(clippy::disallowed_methods)]
fn cargo_use_gitoxide_instead_of_git2() -> bool {
std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1")
}
pub fn cargo_docs_link(path: &str) -> String {
let url_channel = match channel().as_str() {
"dev" | "nightly" => "nightly/",
"beta" => "beta/",
_ => "",
};
format!("https://doc.rust-lang.org/{url_channel}cargo/{path}")
}