//! Support for nightly features in Cargo itself. //! //! This file is the version of `feature_gate.rs` in upstream Rust for Cargo //! itself and is intended to be the avenue for which new features in Cargo are //! gated by default and then eventually stabilized. All known stable and //! unstable features are tracked in this file. //! //! If you're reading this then you're likely interested in adding a feature to //! Cargo, and the good news is that it shouldn't be too hard! First determine //! how the feature should be gated: //! //! * New syntax in Cargo.toml should use `cargo-features`. //! * New CLI options should use `-Z unstable-options`. //! * New functionality that may not have an interface, or the interface has //! not yet been designed, or for more complex features that affect multiple //! parts of Cargo should use a new `-Z` flag. //! //! See below for more details. //! //! When adding new tests for your feature, usually the tests should go into a //! new module of the testsuite. See //! for more information on //! writing tests. Particularly, check out the "Testing Nightly Features" //! section for testing unstable features. //! //! After you have added your feature, be sure to update the unstable //! documentation at `src/doc/src/reference/unstable.md` to include a short //! description of how to use your new feature. //! //! And hopefully that's it! //! //! ## New Cargo.toml syntax //! //! The steps for adding new Cargo.toml syntax are: //! //! 1. Add the cargo-features unstable gate. Search below for "look here" to //! find the [`features!`] macro invocation and add your feature to the list. //! //! 2. Update the Cargo.toml parsing code to handle your new feature. //! //! 3. Wherever you added the new parsing code, call //! `features.require(Feature::my_feature_name())?` if the new syntax is //! used. This will return an error if the user hasn't listed the feature //! in `cargo-features` or this is not the nightly channel. //! //! ## `-Z unstable-options` //! //! `-Z unstable-options` is intended to force the user to opt-in to new CLI //! flags, options, and new subcommands. //! //! The steps to add a new command-line option are: //! //! 1. Add the option to the CLI parsing code. In the help text, be sure to //! include `(unstable)` to note that this is an unstable option. //! 2. Where the CLI option is loaded, be sure to call //! [`CliUnstable::fail_if_stable_opt`]. This will return an error if `-Z //! unstable options` was not passed. //! //! ## `-Z` options //! //! The steps to add a new `-Z` option are: //! //! 1. Add the option to the [`CliUnstable`] struct below. Flags can take an //! optional value if you want. //! 2. Update the [`CliUnstable::add`] function to parse the flag. //! 3. Wherever the new functionality is implemented, call //! [`Config::cli_unstable`] to get an instance of [`CliUnstable`] //! and check if the option has been enabled on the [`CliUnstable`] instance. //! Nightly gating is already handled, so no need to worry about that. //! //! ## Stabilization //! //! For the stabilization process, see //! . //! //! The steps for stabilizing are roughly: //! //! 1. Update the feature to be stable, based on the kind of feature: //! 1. `cargo-features`: Change the feature to `stable` in the [`features!`] //! macro invocation below, and include the version and a URL for the //! documentation. //! 2. `-Z unstable-options`: Find the call to [`fail_if_stable_opt`] and //! remove it. Be sure to update the man pages if necessary. //! 3. `-Z` flag: Change the parsing code in [`CliUnstable::add`] to call //! `stabilized_warn` or `stabilized_err` and remove the field from //! [`CliUnstable`]. Remove the `(unstable)` note in the clap help text if //! necessary. //! 2. Remove `masquerade_as_nightly_cargo` from any tests, and remove //! `cargo-features` from `Cargo.toml` test files if any. You can //! quickly find what needs to be removed by searching for the name //! of the feature, e.g. `print_im_a_teapot` //! 3. Update the docs in unstable.md to move the section to the bottom //! and summarize it similar to the other entries. Update the rest of the //! documentation to add the new feature. //! //! [`Config::cli_unstable`]: crate::util::config::Config::cli_unstable //! [`fail_if_stable_opt`]: CliUnstable::fail_if_stable_opt //! [`features!`]: macro.features.html 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."; /// The edition of the compiler ([RFC 2052]) /// /// The following sections will guide you how to add and stabilize an edition. /// /// ## Adding a new edition /// /// - Add the next edition to the enum. /// - Update every match expression that now fails to compile. /// - Update the [`FromStr`] impl. /// - Update [`CLI_VALUES`] to include the new edition. /// - Set [`LATEST_UNSTABLE`] to Some with the new edition. /// - Add an unstable feature to the [`features!`] macro invocation below for the new edition. /// - Gate on that new feature in [`TomlManifest::to_real_manifest`]. /// - Update the shell completion files. /// - Update any failing tests (hopefully there are very few). /// - Update unstable.md to add a new section for this new edition (see [this example]). /// /// ## Stabilization instructions /// /// - Set [`LATEST_UNSTABLE`] to None. /// - Set [`LATEST_STABLE`] to the new version. /// - Update [`is_stable`] to `true`. /// - Set the editionNNNN feature to stable in the [`features!`] macro invocation below. /// - Update any tests that are affected. /// - Update the man page for the `--edition` flag. /// - Update unstable.md to move the edition section to the bottom. /// - Update the documentation: /// - Update any features impacted by the edition. /// - Update manifest.md#the-edition-field. /// - Update the `--edition` flag (options-new.md). /// - Rebuild man pages. /// /// [RFC 2052]: https://rust-lang.github.io/rfcs/2052-epochs.html /// [`FromStr`]: Edition::from_str /// [`CLI_VALUES`]: Edition::CLI_VALUES /// [`LATEST_UNSTABLE`]: Edition::LATEST_UNSTABLE /// [`LATEST_STABLE`]: Edition::LATEST_STABLE /// [this example]: https://github.com/rust-lang/cargo/blob/3ebb5f15a940810f250b68821149387af583a79e/src/doc/src/reference/unstable.md?plain=1#L1238-L1264 /// [`is_stable`]: Edition::is_stable /// [`TomlManifest::to_real_manifest`]: crate::util::toml::TomlManifest::to_real_manifest /// [`features!`]: macro.features.html #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)] pub enum Edition { /// The 2015 edition Edition2015, /// The 2018 edition Edition2018, /// The 2021 edition Edition2021, } impl Edition { /// The latest edition that is unstable. /// /// This is `None` if there is no next unstable edition. pub const LATEST_UNSTABLE: Option = None; /// The latest stable edition. pub const LATEST_STABLE: Edition = Edition::Edition2021; /// Possible values allowed for the `--edition` CLI flag. /// /// This requires a static value due to the way clap works, otherwise I /// would have built this dynamically. pub const CLI_VALUES: [&'static str; 3] = ["2015", "2018", "2021"]; /// Returns the first version that a particular edition was released on /// stable. pub(crate) fn first_version(&self) -> Option { use Edition::*; match self { Edition2015 => None, Edition2018 => Some(semver::Version::new(1, 31, 0)), Edition2021 => Some(semver::Version::new(1, 56, 0)), } } /// Returns `true` if this edition is stable in this release. pub fn is_stable(&self) -> bool { use Edition::*; match self { Edition2015 => true, Edition2018 => true, Edition2021 => true, } } /// Returns the previous edition from this edition. /// /// Returns `None` for 2015. pub fn previous(&self) -> Option { use Edition::*; match self { Edition2015 => None, Edition2018 => Some(Edition2015), Edition2021 => Some(Edition2018), } } /// Returns the next edition from this edition, returning the last edition /// if this is already the last one. pub fn saturating_next(&self) -> Edition { use Edition::*; match self { Edition2015 => Edition2018, Edition2018 => Edition2021, Edition2021 => Edition2021, } } /// Updates the given [`ProcessBuilder`] to include the appropriate flags /// for setting the edition. 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"); } } /// Whether or not this edition supports the `rust_*_compatibility` lint. /// /// Ideally this would not be necessary, but editions may not have any /// lints, and thus `rustc` doesn't recognize it. Perhaps `rustc` could /// create an empty group instead? pub(crate) fn supports_compat_lint(&self) -> bool { use Edition::*; match self { Edition2015 => false, Edition2018 => true, Edition2021 => true, } } /// Whether or not this edition supports the `rust_*_idioms` lint. /// /// Ideally this would not be necessary... pub(crate) fn supports_idiom_lint(&self) -> bool { use Edition::*; match self { Edition2015 => false, Edition2018 => true, Edition2021 => 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"), } } } impl FromStr for Edition { type Err = Error; fn from_str(s: &str) -> Result { match s { "2015" => Ok(Edition::Edition2015), "2018" => Ok(Edition::Edition2018), "2021" => Ok(Edition::Edition2021), s if s.parse().map_or(false, |y: u16| y > 2021 && y < 2050) => bail!( "this version of Cargo is older than the `{}` edition, \ and only supports `2015`, `2018`, and `2021` editions.", s ), s => bail!( "supported edition values are `2015`, `2018`, or `2021`, \ 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, 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 }; } // A listing of all features in Cargo. // // "look here" // // This is the macro that lists all stable and unstable features in Cargo. // You'll want to add to this macro whenever you add a feature to Cargo, also // following the directions above. // // Note that all feature names here are valid Rust identifiers, but the `_` // character is translated to `-` when specified in the `cargo-features` // manifest entry in `Cargo.toml`. features! { // A dummy feature that doesn't actually gate anything, but it's used in // testing to ensure that we can enable stable features. (stable, test_dummy_stable, "1.0", ""), // A dummy feature that gates the usage of the `im-a-teapot` manifest // entry. This is basically just intended for tests. (unstable, test_dummy_unstable, "", "reference/unstable.html"), // Downloading packages from alternative registry indexes. (stable, alternative_registries, "1.34", "reference/registries.html"), // Using editions (stable, edition, "1.31", "reference/manifest.html#the-edition-field"), // Renaming a package in the manifest via the `package` key (stable, rename_dependency, "1.31", "reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml"), // Whether a lock file is published with this crate (removed, publish_lockfile, "1.37", "reference/unstable.html#publish-lockfile"), // Overriding profiles for dependencies. (stable, profile_overrides, "1.41", "reference/profiles.html#overrides"), // "default-run" manifest option, (stable, default_run, "1.37", "reference/manifest.html#the-default-run-field"), // Declarative build scripts. (unstable, metabuild, "", "reference/unstable.html#metabuild"), // Specifying the 'public' attribute on dependencies (unstable, public_dependency, "", "reference/unstable.html#public-dependency"), // Allow to specify profiles other than 'dev', 'release', 'test', etc. (stable, named_profiles, "1.57", "reference/profiles.html#custom-profiles"), // Opt-in new-resolver behavior. (stable, resolver, "1.51", "reference/resolver.html#resolver-versions"), // Allow to specify whether binaries should be stripped. (stable, strip, "1.58", "reference/profiles.html#strip-option"), // Specifying a minimal 'rust-version' attribute for crates (stable, rust_version, "1.56", "reference/manifest.html#the-rust-version-field"), // Support for 2021 edition. (stable, edition2021, "1.56", "reference/manifest.html#the-edition-field"), // Allow to specify per-package targets (compile kinds) (unstable, per_package_target, "", "reference/unstable.html#per-package-target"), // Allow to specify which codegen backend should be used. (unstable, codegen_backend, "", "reference/unstable.html#codegen-backend"), // Allow specifying different binary name apart from the crate name (unstable, different_binary_name, "", "reference/unstable.html#different-binary-name"), // Allow specifying rustflags directly in a profile (unstable, profile_rustflags, "", "reference/unstable.html#profile-rustflags-option"), // Allow specifying rustflags directly in a profile (stable, workspace_inheritance, "1.64", "reference/unstable.html#workspace-inheritance"), } 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, is_local: bool, ) -> CargoResult { 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, ) -> CargoResult<()> { let nightly_features_allowed = self.nightly_features_allowed; let is_local = self.is_local; let (slot, feature) = match self.status(feature_name) { Some(p) => p, None => bail!("unknown cargo feature `{}`", feature_name), }; if *slot { bail!( "the cargo feature `{}` has already been activated", feature_name ); } let see_docs = || { let url_channel = match channel().as_str() { "dev" | "nightly" => "nightly/", "beta" => "beta/", _ => "", }; format!( "See https://doc.rust-lang.org/{}cargo/{} for more information \ about using this feature.", url_channel, feature.docs ) }; match feature.stability { Status::Stable => { // The user can't do anything about non-local packages. // Warnings are usually suppressed, but just being cautious here. 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 { drop(writeln!( msg, "Remove the feature from Cargo.toml to remove this error." )); } else { drop(writeln!( msg, "This package cannot be used with this version of Cargo, \ as the unstable feature `{}` is no longer supported.", feature_name )); } drop(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 { drop(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 { drop(writeln!( msg, "Consider trying a more recent nightly release." )); } } else { drop(writeln!( msg, "Consider trying a newer version of Cargo \ (this may require the nightly release)." )); } drop(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 ), )* ) => { /// A parsed representation of all unstable flags that Cargo accepts. /// /// Cargo, like `rustc`, accepts a suite of `-Z` flags which are intended for /// gating unstable functionality to Cargo. These flags are only available on /// the nightly channel of Cargo. #[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 } } } } unstable_cli_options!( // Permanently unstable features: allow_features: Option> = ("Allow *only* the listed unstable features"), print_im_a_teapot: bool = (HIDDEN), // All other unstable features. // Please keep this list lexiographically ordered. advanced_env: bool = (HIDDEN), 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> = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"), build_std_features: Option> = ("Configure features enabled for the standard library itself when building the standard library"), config_include: bool = ("Enable the `include` key in config files"), credential_process: bool = ("Add a config setting to fetch registry authentication tokens by calling an external process"), #[serde(deserialize_with = "deserialize_check_cfg")] check_cfg: Option<(/*features:*/ bool, /*well_known_names:*/ bool, /*well_known_values:*/ bool, /*output:*/ bool)> = ("Specify scope of compile-time checking of `cfg` names/values"), doctest_in_workspace: bool = ("Compile doctests with paths relative to the workspace root"), 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> = (HIDDEN), jobserver_per_rustc: bool = (HIDDEN), minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"), mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"), 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"), host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"), sparse_registry: bool = ("Support plain-HTTP-based crate registries"), target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"), rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"), separate_nightlies: bool = (HIDDEN), terminal_width: Option> = ("Provide a terminal width to rustc for error truncation"), unstable_options: bool = ("Allow the usage of unstable options"), // TODO(wcrichto): move scrape example configuration into Cargo.toml before stabilization // See: https://github.com/rust-lang/cargo/pull/9525#discussion_r728470927 rustdoc_scrape_examples: Option = ("Allow rustdoc to scrape examples from reverse-dependencies for documentation"), skip_rustdoc_fingerprint: bool = (HIDDEN), ); 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_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."; fn deserialize_build_std<'de, D>(deserializer: D) -> Result>, D::Error> where D: serde::Deserializer<'de>, { let crates = match >>::deserialize(deserializer)? { Some(list) => list, None => return Ok(None), }; let v = crates.join(","); Ok(Some( crate::core::compiler::standard_lib::parse_unstable_flag(Some(&v)), )) } fn deserialize_check_cfg<'de, D>( deserializer: D, ) -> Result, D::Error> where D: serde::Deserializer<'de>, { use serde::de::Error; let crates = match >>::deserialize(deserializer)? { Some(list) => list, None => return Ok(None), }; parse_check_cfg(crates.into_iter()).map_err(D::Error::custom) } fn parse_check_cfg( it: impl Iterator>, ) -> CargoResult> { let mut features = false; let mut well_known_names = false; let mut well_known_values = false; let mut output = false; for e in it { match e.as_ref() { "features" => features = true, "names" => well_known_names = true, "values" => well_known_values = true, "output" => output = true, _ => bail!("unstable check-cfg only takes `features`, `names`, `values` or `output` as valid inputs"), } } Ok(Some(( features, well_known_names, well_known_values, output, ))) } impl CliUnstable { pub fn parse( &mut self, flags: &[String], nightly_features_allowed: bool, ) -> CargoResult> { 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(); // We read flags twice, first to get allowed-features (if specified), // and then to read the remaining unstable flags. for flag in flags { if flag.starts_with("allow-features=") { self.add(flag, &mut warnings)?; } } for flag in flags { self.add(flag, &mut warnings)?; } Ok(warnings) } fn add(&mut self, flag: &str, warnings: &mut Vec) -> 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 { 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 { match value { None => Vec::new(), Some("") => Vec::new(), Some(v) => v.split(',').map(|s| s.to_string()).collect(), } } // Asserts that there is no argument to the flag. fn parse_empty(key: &str, value: Option<&str>) -> CargoResult { if let Some(v) = value { bail!("flag -Z{} does not take a value, found: `{}`", key, v); } Ok(true) } fn parse_usize_opt(value: Option<&str>) -> CargoResult> { Ok(match value { Some(value) => match value.parse::() { Ok(value) => Some(value), Err(e) => bail!("expected a number, found: {}", e), }, None => None, }) } 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) )); }; // Use this if the behavior now requires another mechanism to enable. 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 { "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?, "allow-features" => self.allow_features = Some(parse_features(v).into_iter().collect()), "unstable-options" => self.unstable_options = parse_empty(k, v)?, "no-index-update" => self.no_index_update = parse_empty(k, v)?, "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?, "minimal-versions" => self.minimal_versions = parse_empty(k, v)?, "advanced-env" => self.advanced_env = parse_empty(k, v)?, "config-include" => self.config_include = parse_empty(k, v)?, "check-cfg" => { self.check_cfg = v.map_or(Ok(None), |v| parse_check_cfg(v.split(',')))? } "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?, // can also be set in .cargo/config or with and ENV "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?, "named-profiles" => stabilized_warn(k, "1.57", STABILIZED_NAMED_PROFILES), "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)), "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?, "doctest-in-workspace" => self.doctest_in_workspace = parse_empty(k, v)?, "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, "jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?, "host-config" => self.host_config = parse_empty(k, v)?, "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?, "features" => { // `-Z features` has been stabilized since 1.51, // but `-Z features=compare` is still allowed for convenience // to validate that the feature resolver resolves features // in the same way as the dependency resolver, // until we feel confident to remove entirely. // // See rust-lang/cargo#11168 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() { // Make this stabilized_err once -Zfeature support is removed. stabilized_warn(k, "1.51", STABILIZED_FEATURES); } self.features = Some(feats); } "separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?, "multitarget" => stabilized_warn(k, "1.64", STABILISED_MULTITARGET), "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?, "terminal-width" => self.terminal_width = Some(parse_usize_opt(v)?), "sparse-registry" => self.sparse_registry = parse_empty(k, v)?, "namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES), "weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES), "credential-process" => self.credential_process = parse_empty(k, v)?, "rustdoc-scrape-examples" => { if let Some(s) = v { self.rustdoc_scrape_examples = Some(s.to_string()) } else { bail!( r#"-Z rustdoc-scrape-examples must take "all" or "examples" as an argument"# ) } } "skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(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), "package-features" => stabilized_warn(k, "1.51", STABILIZED_PACKAGE_FEATURES), "extra-link-arg" => stabilized_warn(k, "1.56", STABILIZED_EXTRA_LINK_ARG), "configurable-env" => stabilized_warn(k, "1.56", STABILIZED_CONFIGURABLE_ENV), "patch-in-config" => stabilized_warn(k, "1.56", STABILIZED_PATCH_IN_CONFIG), "future-incompat-report" => { stabilized_warn(k, "1.59.0", STABILIZED_FUTURE_INCOMPAT_REPORT) } "timings" => stabilized_warn(k, "1.60", STABILIZED_TIMINGS), _ => bail!("unknown `-Z` flag specified: {}", k), } Ok(()) } /// Generates an error if `-Z unstable-options` was not used for a new, /// unstable command-line flag. pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> { if !self.unstable_options { let see = format!( "See https://github.com/rust-lang/cargo/issues/{} for more \ information about the `{}` flag.", issue, flag ); // NOTE: a `config` isn't available here, check the channel directly let channel = channel(); if channel == "nightly" || channel == "dev" { bail!( "the `{}` flag is unstable, pass `-Z unstable-options` to enable it\n\ {}", flag, see ); } else { bail!( "the `{}` flag is unstable, and only available on the nightly channel \ of Cargo, but this is the `{}` channel\n\ {}\n\ {}", flag, channel, SEE_CHANNELS, see ); } } Ok(()) } /// Generates an error if `-Z unstable-options` was not used for a new, /// unstable subcommand. pub fn fail_if_stable_command( &self, config: &Config, command: &str, issue: u32, ) -> CargoResult<()> { if self.unstable_options { 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 is unstable, pass `-Z unstable-options` to enable it\n\ {}", command, 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 ); } } } /// Returns the current release channel ("stable", "beta", "nightly", "dev"). pub fn channel() -> String { if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") { return override_channel; } 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")) }