#
cargo/core/
features.rs

    1//! Support for nightly features in Cargo itself.
    2//!
    3//! This file is the version of `feature_gate.rs` in upstream Rust for Cargo
    4//! itself and is intended to be the avenue for which new features in Cargo are
    5//! gated by default and then eventually stabilized. All known stable and
    6//! unstable features are tracked in this file.
    7//!
    8//! If you're reading this then you're likely interested in adding a feature to
    9//! Cargo, and the good news is that it shouldn't be too hard! First determine
    10//! how the feature should be gated:
    11//!
    12//! * Error when the feature is used without the gate
    13//!   * Required if ignoring the feature violates the users intent in non-superficial ways
    14//!   * A low-effort / safe way to protect the user from being broken if the format of the feature changes in
    15//!     incompatible was (can be worked around)
    16//!   * Good for: CLI (gate: `-Zunstable-options` or `-Z` if combined with other changes), `Cargo.toml` (gate: `cargo-features`)
    17//! * Warn that the feature is ignored due to lack of the gate
    18//!   * For if you could opt-in to the unimplemented feature on Cargo today and Cargo would
    19//!     operate just fine
    20//!   * If gate is not enabled, prefer to warn if the format of the feature is incompatible
    21//!     (instead of error or ignore)
    22//!   * Good for: `Cargo.toml`, `.cargo/config.toml`, `config.json` index file (gate: `-Z`)
    23//! * Ignore the feature that is used without a gate
    24//!   * For when ignoring the feature has so little impact that annoying the user is not worth it
    25//!     (e.g. a config field that changes Cargo's terminal output)
    26//!   * For behavior changes without an interface (e.g. the resolver)
    27//!   * Good for: `.cargo/config.toml`, `config.json` index file (gate: `-Z`)
    28//!
    29//! For features that touch multiple parts of Cargo, multiple feature gating strategies (error,
    30//! warn, ignore) and mechanisms (`-Z`, `cargo-features`) may be used.
    31//!
    32//! When adding new tests for your feature, usually the tests should go into a
    33//! new module of the testsuite named after the feature. See
    34//! <https://doc.crates.io/contrib/tests/writing.html> for more information on
    35//! writing tests. Particularly, check out the "Testing Nightly Features"
    36//! section for testing unstable features. Be sure to test the feature gate itself.
    37//!
    38//! After you have added your feature, be sure to update the unstable
    39//! documentation at `src/doc/src/reference/unstable.md` to include a short
    40//! description of how to use your new feature.
    41//!
    42//! And hopefully that's it!
    43//!
    44//! ## `cargo-features`
    45//!
    46//! The steps for adding new Cargo.toml syntax are:
    47//!
    48//! 1. Add the cargo-features unstable gate. Search the code below for "look here" to
    49//!    find the [`features!`] macro invocation and add your feature to the list.
    50//!
    51//! 2. Update the Cargo.toml parsing code to handle your new feature.
    52//!
    53//! 3. Wherever you added the new parsing code, call
    54//!    `features.require(Feature::my_feature_name())?` if the new syntax is
    55//!    used. This will return an error if the user hasn't listed the feature
    56//!    in `cargo-features` or this is not the nightly channel.
    57//!
    58//! ## `-Z unstable-options`
    59//!
    60//! `-Z unstable-options` is intended to force the user to opt-in to new CLI
    61//! flags, options, and new subcommands.
    62//!
    63//! The steps to add a new command-line option are:
    64//!
    65//! 1. Add the option to the CLI parsing code. In the help text, be sure to
    66//!    include `(unstable)` to note that this is an unstable option.
    67//! 2. Where the CLI option is loaded, be sure to call
    68//!    [`CliUnstable::fail_if_stable_opt`]. This will return an error if `-Z
    69//!    unstable options` was not passed.
    70//!
    71//! ## `-Z` options
    72//!
    73//! New `-Z` options cover all other functionality that isn't covered with
    74//! `cargo-features` or `-Z unstable-options`.
    75//!
    76//! The steps to add a new `-Z` option are:
    77//!
    78//! 1. Add the option to the [`CliUnstable`] struct in the macro invocation of
    79//!    [`unstable_cli_options!`]. Flags can take an optional value if you want.
    80//! 2. Update the [`CliUnstable::add`] function to parse the flag.
    81//! 3. Wherever the new functionality is implemented, call
    82//!    [`GlobalContext::cli_unstable`] to get an instance of [`CliUnstable`]
    83//!    and check if the option has been enabled on the [`CliUnstable`] instance.
    84//!    Nightly gating is already handled, so no need to worry about that.
    85//!    If warning when feature is used without the gate, be sure to gracefully degrade (with a
    86//!    warning) when the `Cargo.toml` / `.cargo/config.toml` field usage doesn't match the
    87//!    schema.
    88//! 4. For any `Cargo.toml` fields, strip them in [`prepare_for_publish`] if the gate isn't set
    89//!
    90//! ## Stabilization
    91//!
    92//! For the stabilization process, see
    93//! <https://doc.crates.io/contrib/process/unstable.html#stabilization>.
    94//!
    95//! The steps for stabilizing are roughly:
    96//!
    97//! 1. Update the feature to be stable, based on the kind of feature:
    98//!   1. `cargo-features`: Change the feature to `stable` in the [`features!`]
    99//!      macro invocation below, and include the version and a URL for the
    100//!      documentation.
    101//!   2. `-Z unstable-options`: Find the call to [`fail_if_stable_opt`] and
    102//!      remove it. Be sure to update the man pages if necessary.
    103//!   3. `-Z` flag: Change the parsing code in [`CliUnstable::add`] to call
    104//!      `stabilized_warn` or `stabilized_err` and remove the field from
    105//!      [`CliUnstable`]. Remove the `(unstable)` note in the clap help text if
    106//!      necessary.
    107//! 2. Remove `masquerade_as_nightly_cargo` from any tests, and remove
    108//!    `cargo-features` from `Cargo.toml` test files if any. You can
    109//!     quickly find what needs to be removed by searching for the name
    110//!     of the feature, e.g. `print_im_a_teapot`
    111//! 3. Update the docs in unstable.md to move the section to the bottom
    112//!    and summarize it similar to the other entries. Update the rest of the
    113//!    documentation to add the new feature.
    114//!
    115//! [`GlobalContext::cli_unstable`]: crate::util::context::GlobalContext::cli_unstable
    116//! [`fail_if_stable_opt`]: CliUnstable::fail_if_stable_opt
    117//! [`features!`]: macro.features.html
    118//! [`unstable_cli_options!`]: macro.unstable_cli_options.html
    119//! [`prepare_for_publish`]: crate::util::toml::prepare_for_publish
    120
    121use std::collections::BTreeSet;
    122use std::env;
    123use std::fmt::{self, Write};
    124use std::path::PathBuf;
    125use std::str::FromStr;
    126
    127use anyhow::{bail, Error};
    128use cargo_util::ProcessBuilder;
    129use serde::{Deserialize, Serialize};
    130
    131use crate::core::resolver::ResolveBehavior;
    132use crate::util::errors::CargoResult;
    133use crate::util::indented_lines;
    134use crate::GlobalContext;
    135
    136pub const SEE_CHANNELS: &str =
    137    "See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information \
    138     about Rust release channels.";
    139
    140/// Value of [`allow-features`](CliUnstable::allow_features)
    141pub type AllowFeatures = BTreeSet<String>;
    142
    143/// The edition of the compiler ([RFC 2052])
    144///
    145/// The following sections will guide you how to add and stabilize an edition.
    146///
    147/// ## Adding a new edition
    148///
    149/// - Add the next edition to the enum.
    150/// - Update every match expression that now fails to compile.
    151/// - Update the [`FromStr`] impl.
    152/// - Update [`CLI_VALUES`] to include the new edition.
    153/// - Set [`LATEST_UNSTABLE`] to Some with the new edition.
    154/// - Add an unstable feature to the [`features!`] macro invocation below for the new edition.
    155/// - Gate on that new feature in [`toml`].
    156/// - Update the shell completion files.
    157/// - Update any failing tests (hopefully there are very few).
    158/// - Update unstable.md to add a new section for this new edition (see [this example]).
    159///
    160/// ## Stabilization instructions
    161///
    162/// - Set [`LATEST_UNSTABLE`] to None.
    163/// - Set [`LATEST_STABLE`] to the new version.
    164/// - Update [`is_stable`] to `true`.
    165/// - Set [`first_version`] to the version it will be released.
    166/// - Set the editionNNNN feature to stable in the [`features!`] macro invocation below.
    167/// - Update any tests that are affected.
    168/// - Update the man page for the `--edition` flag.
    169/// - Update unstable.md to move the edition section to the bottom.
    170/// - Update the documentation:
    171///   - Update any features impacted by the edition.
    172///   - Update manifest.md#the-edition-field.
    173///   - Update the `--edition` flag (options-new.md).
    174///   - Rebuild man pages.
    175///
    176/// [RFC 2052]: https://rust-lang.github.io/rfcs/2052-epochs.html
    177/// [`FromStr`]: Edition::from_str
    178/// [`CLI_VALUES`]: Edition::CLI_VALUES
    179/// [`LATEST_UNSTABLE`]: Edition::LATEST_UNSTABLE
    180/// [`LATEST_STABLE`]: Edition::LATEST_STABLE
    181/// [this example]: https://github.com/rust-lang/cargo/blob/3ebb5f15a940810f250b68821149387af583a79e/src/doc/src/reference/unstable.md?plain=1#L1238-L1264
    182/// [`first_version`]: Edition::first_version
    183/// [`is_stable`]: Edition::is_stable
    184/// [`toml`]: crate::util::toml
    185/// [`features!`]: macro.features.html
    186#[derive(
    187    Default, Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize,
    188)]
    189pub enum Edition {
    190    /// The 2015 edition
    191    #[default]
    192    Edition2015,
    193    /// The 2018 edition
    194    Edition2018,
    195    /// The 2021 edition
    196    Edition2021,
    197    /// The 2024 edition
    198    Edition2024,
    199}
    200
    201impl Edition {
    202    /// The latest edition that is unstable.
    203    ///
    204    /// This is `None` if there is no next unstable edition.
    205    pub const LATEST_UNSTABLE: Option<Edition> = None;
    206    /// The latest stable edition.
    207    pub const LATEST_STABLE: Edition = Edition::Edition2024;
    208    pub const ALL: &'static [Edition] = &[
    209        Self::Edition2015,
    210        Self::Edition2018,
    211        Self::Edition2021,
    212        Self::Edition2024,
    213    ];
    214    /// Possible values allowed for the `--edition` CLI flag.
    215    ///
    216    /// This requires a static value due to the way clap works, otherwise I
    217    /// would have built this dynamically.
    218    pub const CLI_VALUES: [&'static str; 4] = ["2015", "2018", "2021", "2024"];
    219
    220    /// Returns the first version that a particular edition was released on
    221    /// stable.
    222    pub(crate) fn first_version(&self) -> Option<semver::Version> {
    223        use Edition::*;
    224        match self {
    225            Edition2015 => None,
    226            Edition2018 => Some(semver::Version::new(1, 31, 0)),
    227            Edition2021 => Some(semver::Version::new(1, 56, 0)),
    228            Edition2024 => Some(semver::Version::new(1, 85, 0)),
    229        }
    230    }
    231
    232    /// Returns `true` if this edition is stable in this release.
    233    pub fn is_stable(&self) -> bool {
    234        use Edition::*;
    235        match self {
    236            Edition2015 => true,
    237            Edition2018 => true,
    238            Edition2021 => true,
    239            Edition2024 => true,
    240        }
    241    }
    242
    243    /// Returns the previous edition from this edition.
    244    ///
    245    /// Returns `None` for 2015.
    246    pub fn previous(&self) -> Option<Edition> {
    247        use Edition::*;
    248        match self {
    249            Edition2015 => None,
    250            Edition2018 => Some(Edition2015),
    251            Edition2021 => Some(Edition2018),
    252            Edition2024 => Some(Edition2021),
    253        }
    254    }
    255
    256    /// Returns the next edition from this edition, returning the last edition
    257    /// if this is already the last one.
    258    pub fn saturating_next(&self) -> Edition {
    259        use Edition::*;
    260        match self {
    261            Edition2015 => Edition2018,
    262            Edition2018 => Edition2021,
    263            Edition2021 => Edition2024,
    264            Edition2024 => Edition2024,
    265        }
    266    }
    267
    268    /// Updates the given [`ProcessBuilder`] to include the appropriate flags
    269    /// for setting the edition.
    270    pub(crate) fn cmd_edition_arg(&self, cmd: &mut ProcessBuilder) {
    271        cmd.arg(format!("--edition={}", self));
    272        if !self.is_stable() {
    273            cmd.arg("-Z").arg("unstable-options");
    274        }
    275    }
    276
    277    /// Whether or not this edition supports the `rust_*_compatibility` lint.
    278    ///
    279    /// Ideally this would not be necessary, but editions may not have any
    280    /// lints, and thus `rustc` doesn't recognize it. Perhaps `rustc` could
    281    /// create an empty group instead?
    282    pub(crate) fn supports_compat_lint(&self) -> bool {
    283        use Edition::*;
    284        match self {
    285            Edition2015 => false,
    286            Edition2018 => true,
    287            Edition2021 => true,
    288            Edition2024 => true,
    289        }
    290    }
    291
    292    /// Whether or not this edition supports the `rust_*_idioms` lint.
    293    ///
    294    /// Ideally this would not be necessary...
    295    pub(crate) fn supports_idiom_lint(&self) -> bool {
    296        use Edition::*;
    297        match self {
    298            Edition2015 => false,
    299            Edition2018 => true,
    300            Edition2021 => false,
    301            Edition2024 => false,
    302        }
    303    }
    304
    305    pub(crate) fn default_resolve_behavior(&self) -> ResolveBehavior {
    306        if *self >= Edition::Edition2024 {
    307            ResolveBehavior::V3
    308        } else if *self >= Edition::Edition2021 {
    309            ResolveBehavior::V2
    310        } else {
    311            ResolveBehavior::V1
    312        }
    313    }
    314}
    315
    316impl fmt::Display for Edition {
    317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    318        match *self {
    319            Edition::Edition2015 => f.write_str("2015"),
    320            Edition::Edition2018 => f.write_str("2018"),
    321            Edition::Edition2021 => f.write_str("2021"),
    322            Edition::Edition2024 => f.write_str("2024"),
    323        }
    324    }
    325}
    326
    327impl FromStr for Edition {
    328    type Err = Error;
    329    fn from_str(s: &str) -> Result<Self, Error> {
    330        match s {
    331            "2015" => Ok(Edition::Edition2015),
    332            "2018" => Ok(Edition::Edition2018),
    333            "2021" => Ok(Edition::Edition2021),
    334            "2024" => Ok(Edition::Edition2024),
    335            s if s.parse().map_or(false, |y: u16| y > 2024 && y < 2050) => bail!(
    336                "this version of Cargo is older than the `{}` edition, \
    337                 and only supports `2015`, `2018`, `2021`, and `2024` editions.",
    338                s
    339            ),
    340            s => bail!(
    341                "supported edition values are `2015`, `2018`, `2021`, or `2024`, \
    342                 but `{}` is unknown",
    343                s
    344            ),
    345        }
    346    }
    347}
    348
    349#[derive(Debug, PartialEq)]
    350enum Status {
    351    Stable,
    352    Unstable,
    353    Removed,
    354}
    355
    356/// A listing of stable and unstable new syntax in Cargo.toml.
    357///
    358/// This generates definitions and impls for [`Features`] and [`Feature`]
    359/// for each new syntax.
    360///
    361/// Note that all feature names in the macro invocation are valid Rust
    362/// identifiers, but the `_` character is translated to `-` when specified in
    363/// the `cargo-features` manifest entry in `Cargo.toml`.
    364///
    365/// See the [module-level documentation](self#new-cargotoml-syntax)
    366/// for the process of adding a new syntax.
    367macro_rules! features {
    368    (
    369        $(
    370            $(#[$attr:meta])*
    371            ($stab:ident, $feature:ident, $version:expr, $docs:expr),
    372        )*
    373    ) => (
    374        /// Unstable feature context for querying if a new Cargo.toml syntax
    375        /// is allowed to use.
    376        ///
    377        /// See the [module-level documentation](self#new-cargotoml-syntax) for the usage.
    378        #[derive(Default, Clone, Debug)]
    379        pub struct Features {
    380            $($feature: bool,)*
    381            /// The current activated features.
    382            activated: Vec<String>,
    383            /// Whether is allowed to use any unstable features.
    384            nightly_features_allowed: bool,
    385            /// Whether the source manifest is from a local package.
    386            is_local: bool,
    387        }
    388
    389        impl Feature {
    390            $(
    391                $(#[$attr])*
    392                #[doc = concat!("\n\n\nSee <https://doc.rust-lang.org/nightly/cargo/", $docs, ">.")]
    393                pub const fn $feature() -> &'static Feature {
    394                    fn get(features: &Features) -> bool {
    395                        stab!($stab) == Status::Stable || features.$feature
    396                    }
    397                    const FEAT: Feature = Feature {
    398                        name: stringify!($feature),
    399                        stability: stab!($stab),
    400                        version: $version,
    401                        docs: $docs,
    402                        get,
    403                    };
    404                    &FEAT
    405                }
    406            )*
    407
    408            /// Whether this feature is allowed to use in the given [`Features`] context.
    409            fn is_enabled(&self, features: &Features) -> bool {
    410                (self.get)(features)
    411            }
    412
    413            pub(crate) fn name(&self) -> &str {
    414                self.name
    415            }
    416        }
    417
    418        impl Features {
    419            fn status(&mut self, feature: &str) -> Option<(&mut bool, &'static Feature)> {
    420                if feature.contains("_") {
    421                    return None;
    422                }
    423                let feature = feature.replace("-", "_");
    424                $(
    425                    if feature == stringify!($feature) {
    426                        return Some((&mut self.$feature, Feature::$feature()));
    427                    }
    428                )*
    429                None
    430            }
    431        }
    432    )
    433}
    434
    435macro_rules! stab {
    436    (stable) => {
    437        Status::Stable
    438    };
    439    (unstable) => {
    440        Status::Unstable
    441    };
    442    (removed) => {
    443        Status::Removed
    444    };
    445}
    446
    447// "look here"
    448features! {
    449    /// A dummy feature that doesn't actually gate anything, but it's used in
    450    /// testing to ensure that we can enable stable features.
    451    (stable, test_dummy_stable, "1.0", ""),
    452
    453    /// A dummy feature that gates the usage of the `im-a-teapot` manifest
    454    /// entry. This is basically just intended for tests.
    455    (unstable, test_dummy_unstable, "", "reference/unstable.html"),
    456
    457    /// Downloading packages from alternative registry indexes.
    458    (stable, alternative_registries, "1.34", "reference/registries.html"),
    459
    460    /// Using editions
    461    (stable, edition, "1.31", "reference/manifest.html#the-edition-field"),
    462
    463    /// Renaming a package in the manifest via the `package` key.
    464    (stable, rename_dependency, "1.31", "reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml"),
    465
    466    /// Whether a lock file is published with this crate.
    467    (removed, publish_lockfile, "1.37", "reference/unstable.html#publish-lockfile"),
    468
    469    /// Overriding profiles for dependencies.
    470    (stable, profile_overrides, "1.41", "reference/profiles.html#overrides"),
    471
    472    /// "default-run" manifest option.
    473    (stable, default_run, "1.37", "reference/manifest.html#the-default-run-field"),
    474
    475    /// Declarative build scripts.
    476    (unstable, metabuild, "", "reference/unstable.html#metabuild"),
    477
    478    /// Specifying the 'public' attribute on dependencies.
    479    (unstable, public_dependency, "", "reference/unstable.html#public-dependency"),
    480
    481    /// Allow to specify profiles other than 'dev', 'release', 'test', etc.
    482    (stable, named_profiles, "1.57", "reference/profiles.html#custom-profiles"),
    483
    484    /// Opt-in new-resolver behavior.
    485    (stable, resolver, "1.51", "reference/resolver.html#resolver-versions"),
    486
    487    /// Allow to specify whether binaries should be stripped.
    488    (stable, strip, "1.58", "reference/profiles.html#strip-option"),
    489
    490    /// Specifying a minimal 'rust-version' attribute for crates.
    491    (stable, rust_version, "1.56", "reference/manifest.html#the-rust-version-field"),
    492
    493    /// Support for 2021 edition.
    494    (stable, edition2021, "1.56", "reference/manifest.html#the-edition-field"),
    495
    496    /// Allow to specify per-package targets (compile kinds).
    497    (unstable, per_package_target, "", "reference/unstable.html#per-package-target"),
    498
    499    /// Allow to specify which codegen backend should be used.
    500    (unstable, codegen_backend, "", "reference/unstable.html#codegen-backend"),
    501
    502    /// Allow specifying different binary name apart from the crate name.
    503    (unstable, different_binary_name, "", "reference/unstable.html#different-binary-name"),
    504
    505    /// Allow specifying rustflags directly in a profile.
    506    (unstable, profile_rustflags, "", "reference/unstable.html#profile-rustflags-option"),
    507
    508    /// Allow workspace members to inherit fields and dependencies from a workspace.
    509    (stable, workspace_inheritance, "1.64", "reference/unstable.html#workspace-inheritance"),
    510
    511    /// Support for 2024 edition.
    512    (stable, edition2024, "1.85", "reference/manifest.html#the-edition-field"),
    513
    514    /// Allow setting trim-paths in a profile to control the sanitisation of file paths in build outputs.
    515    (unstable, trim_paths, "", "reference/unstable.html#profile-trim-paths-option"),
    516
    517    /// Allow multiple packages to participate in the same API namespace
    518    (unstable, open_namespaces, "", "reference/unstable.html#open-namespaces"),
    519
    520    /// Allow paths that resolve relatively to a base specified in the config.
    521    (unstable, path_bases, "", "reference/unstable.html#path-bases"),
    522}
    523
    524/// Status and metadata for a single unstable feature.
    525#[derive(Debug)]
    526pub struct Feature {
    527    /// Feature name. This is valid Rust identifier so no dash only underscore.
    528    name: &'static str,
    529    stability: Status,
    530    /// Version that this feature was stabilized or removed.
    531    version: &'static str,
    532    /// Link to the unstable documentation.
    533    docs: &'static str,
    534    get: fn(&Features) -> bool,
    535}
    536
    537impl Features {
    538    /// Creates a new unstable features context.
    539    pub fn new(
    540        features: &[String],
    541        gctx: &GlobalContext,
    542        warnings: &mut Vec<String>,
    543        is_local: bool,
    544    ) -> CargoResult<Features> {
    545        let mut ret = Features::default();
    546        ret.nightly_features_allowed = gctx.nightly_features_allowed;
    547        ret.is_local = is_local;
    548        for feature in features {
    549            ret.add(feature, gctx, warnings)?;
    550            ret.activated.push(feature.to_string());
    551        }
    552        Ok(ret)
    553    }
    554
    555    fn add(
    556        &mut self,
    557        feature_name: &str,
    558        gctx: &GlobalContext,
    559        warnings: &mut Vec<String>,
    560    ) -> CargoResult<()> {
    561        let nightly_features_allowed = self.nightly_features_allowed;
    562        let Some((slot, feature)) = self.status(feature_name) else {
    563            bail!("unknown cargo feature `{}`", feature_name)
    564        };
    565
    566        if *slot {
    567            bail!(
    568                "the cargo feature `{}` has already been activated",
    569                feature_name
    570            );
    571        }
    572
    573        let see_docs = || {
    574            format!(
    575                "See {} for more information about using this feature.",
    576                cargo_docs_link(feature.docs)
    577            )
    578        };
    579
    580        match feature.stability {
    581            Status::Stable => {
    582                let warning = format!(
    583                    "the cargo feature `{}` has been stabilized in the {} \
    584                         release and is no longer necessary to be listed in the \
    585                         manifest\n  {}",
    586                    feature_name,
    587                    feature.version,
    588                    see_docs()
    589                );
    590                warnings.push(warning);
    591            }
    592            Status::Unstable if !nightly_features_allowed => bail!(
    593                "the cargo feature `{}` requires a nightly version of \
    594                 Cargo, but this is the `{}` channel\n\
    595                 {}\n{}",
    596                feature_name,
    597                channel(),
    598                SEE_CHANNELS,
    599                see_docs()
    600            ),
    601            Status::Unstable => {
    602                if let Some(allow) = &gctx.cli_unstable().allow_features {
    603                    if !allow.contains(feature_name) {
    604                        bail!(
    605                            "the feature `{}` is not in the list of allowed features: [{}]",
    606                            feature_name,
    607                            itertools::join(allow, ", "),
    608                        );
    609                    }
    610                }
    611            }
    612            Status::Removed => {
    613                let mut msg = format!(
    614                    "the cargo feature `{}` has been removed in the {} release\n\n",
    615                    feature_name, feature.version
    616                );
    617                if self.is_local {
    618                    let _ = writeln!(
    619                        msg,
    620                        "Remove the feature from Cargo.toml to remove this error."
    621                    );
    622                } else {
    623                    let _ = writeln!(
    624                        msg,
    625                        "This package cannot be used with this version of Cargo, \
    626                         as the unstable feature `{}` is no longer supported.",
    627                        feature_name
    628                    );
    629                }
    630                let _ = writeln!(msg, "{}", see_docs());
    631                bail!(msg);
    632            }
    633        }
    634
    635        *slot = true;
    636
    637        Ok(())
    638    }
    639
    640    /// Gets the current activated features.
    641    pub fn activated(&self) -> &[String] {
    642        &self.activated
    643    }
    644
    645    /// Checks if the given feature is enabled.
    646    pub fn require(&self, feature: &Feature) -> CargoResult<()> {
    647        if feature.is_enabled(self) {
    648            return Ok(());
    649        }
    650        let feature_name = feature.name.replace("_", "-");
    651        let mut msg = format!(
    652            "feature `{}` is required\n\
    653             \n\
    654             The package requires the Cargo feature called `{}`, but \
    655             that feature is not stabilized in this version of Cargo ({}).\n\
    656            ",
    657            feature_name,
    658            feature_name,
    659            crate::version(),
    660        );
    661
    662        if self.nightly_features_allowed {
    663            if self.is_local {
    664                let _ = writeln!(
    665                    msg,
    666                    "Consider adding `cargo-features = [\"{}\"]` \
    667                     to the top of Cargo.toml (above the [package] table) \
    668                     to tell Cargo you are opting in to use this unstable feature.",
    669                    feature_name
    670                );
    671            } else {
    672                let _ = writeln!(msg, "Consider trying a more recent nightly release.");
    673            }
    674        } else {
    675            let _ = writeln!(
    676                msg,
    677                "Consider trying a newer version of Cargo \
    678                 (this may require the nightly release)."
    679            );
    680        }
    681        let _ = writeln!(
    682            msg,
    683            "See https://doc.rust-lang.org/nightly/cargo/{} for more information \
    684             about the status of this feature.",
    685            feature.docs
    686        );
    687
    688        bail!("{}", msg);
    689    }
    690
    691    /// Whether the given feature is allowed to use in this context.
    692    pub fn is_enabled(&self, feature: &Feature) -> bool {
    693        feature.is_enabled(self)
    694    }
    695}
    696
    697/// Generates `-Z` flags as fields of [`CliUnstable`].
    698///
    699/// See the [module-level documentation](self#-z-options) for details.
    700macro_rules! unstable_cli_options {
    701    (
    702        $(
    703            $(#[$meta:meta])?
    704            $element: ident: $ty: ty$( = ($help:literal))?,
    705        )*
    706    ) => {
    707        /// A parsed representation of all unstable flags that Cargo accepts.
    708        ///
    709        /// Cargo, like `rustc`, accepts a suite of `-Z` flags which are intended for
    710        /// gating unstable functionality to Cargo. These flags are only available on
    711        /// the nightly channel of Cargo.
    712        #[derive(Default, Debug, Deserialize)]
    713        #[serde(default, rename_all = "kebab-case")]
    714        pub struct CliUnstable {
    715            $(
    716                $(#[doc = $help])?
    717                $(#[$meta])?
    718                pub $element: $ty
    719            ),*
    720        }
    721        impl CliUnstable {
    722            /// Returns a list of `(<option-name>, <help-text>)`.
    723            pub fn help() -> Vec<(&'static str, Option<&'static str>)> {
    724                let fields = vec![$((stringify!($element), None$(.or(Some($help)))?)),*];
    725                fields
    726            }
    727        }
    728
    729        #[cfg(test)]
    730        mod test {
    731            #[test]
    732            fn ensure_sorted() {
    733                // This will be printed out if the fields are not sorted.
    734                let location = std::panic::Location::caller();
    735                println!(
    736                    "\nTo fix this test, sort the features inside the macro at {}:{}\n",
    737                    location.file(),
    738                    location.line()
    739                );
    740                let mut expected = vec![$(stringify!($element)),*];
    741                expected[2..].sort();
    742                let expected = format!("{:#?}", expected);
    743                let actual = format!("{:#?}", vec![$(stringify!($element)),*]);
    744                snapbox::assert_data_eq!(actual, expected);
    745            }
    746        }
    747    }
    748}
    749
    750unstable_cli_options!(
    751    // Permanently unstable features:
    752    allow_features: Option<AllowFeatures> = ("Allow *only* the listed unstable features"),
    753    print_im_a_teapot: bool,
    754
    755    // All other unstable features.
    756    // Please keep this list lexicographically ordered.
    757    advanced_env: bool,
    758    asymmetric_token: bool = ("Allows authenticating with asymmetric tokens"),
    759    avoid_dev_deps: bool = ("Avoid installing dev-dependencies if possible"),
    760    binary_dep_depinfo: bool = ("Track changes to dependency artifacts"),
    761    bindeps: bool = ("Allow Cargo packages to depend on bin, cdylib, and staticlib crates, and use the artifacts built by those crates"),
    762    build_dir: bool = ("Enable the `build.build-dir` option in .cargo/config.toml file"),
    763    #[serde(deserialize_with = "deserialize_comma_separated_list")]
    764    build_std: Option<Vec<String>>  = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"),
    765    #[serde(deserialize_with = "deserialize_comma_separated_list")]
    766    build_std_features: Option<Vec<String>>  = ("Configure features enabled for the standard library itself when building the standard library"),
    767    cargo_lints: bool = ("Enable the `[lints.cargo]` table"),
    768    checksum_freshness: bool = ("Use a checksum to determine if output is fresh rather than filesystem mtime"),
    769    codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"),
    770    config_include: bool = ("Enable the `include` key in config files"),
    771    direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"),
    772    doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"),
    773    dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
    774    feature_unification: bool = ("Enable new feature unification modes in workspaces"),
    775    features: Option<Vec<String>>,
    776    gc: bool = ("Track cache usage and \"garbage collect\" unused files"),
    777    #[serde(deserialize_with = "deserialize_git_features")]
    778    git: Option<GitFeatures> = ("Enable support for shallow git fetch operations"),
    779    #[serde(deserialize_with = "deserialize_gitoxide_features")]
    780    gitoxide: Option<GitoxideFeatures> = ("Use gitoxide for the given git interactions, or all of them if no argument is given"),
    781    host_config: bool = ("Enable the `[host]` section in the .cargo/config.toml file"),
    782    minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"),
    783    msrv_policy: bool = ("Enable rust-version aware policy within cargo"),
    784    mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"),
    785    next_lockfile_bump: bool,
    786    no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
    787    package_workspace: bool = ("Handle intra-workspace dependencies when packaging"),
    788    panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
    789    profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"),
    790    public_dependency: bool = ("Respect a dependency's `public` field in Cargo.toml to control public/private dependencies"),
    791    publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
    792    root_dir: Option<PathBuf> = ("Set the root directory relative to which paths are printed (defaults to workspace root)"),
    793    rustdoc_depinfo: bool = ("Use dep-info files in rustdoc rebuild detection"),
    794    rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
    795    rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"),
    796    sbom: bool = ("Enable the `sbom` option in build config in .cargo/config.toml file"),
    797    script: bool = ("Enable support for single-file, `.rs` packages"),
    798    separate_nightlies: bool,
    799    skip_rustdoc_fingerprint: bool,
    800    target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
    801    trim_paths: bool = ("Enable the `trim-paths` option in profiles"),
    802    unstable_options: bool = ("Allow the usage of unstable options"),
    803    warnings: bool = ("Allow use of the build.warnings config key"),
    804);
    805
    806const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
    807    enabled when used on an interactive console.\n\
    808    See https://doc.rust-lang.org/cargo/reference/config.html#termprogresswhen \
    809    for information on controlling the progress bar.";
    810
    811const STABILIZED_OFFLINE: &str = "Offline mode is now available via the \
    812    --offline CLI option";
    813
    814const STABILIZED_CACHE_MESSAGES: &str = "Message caching is now always enabled.";
    815
    816const STABILIZED_INSTALL_UPGRADE: &str = "Packages are now always upgraded if \
    817    they appear out of date.\n\
    818    See https://doc.rust-lang.org/cargo/commands/cargo-install.html for more \
    819    information on how upgrading works.";
    820
    821const STABILIZED_CONFIG_PROFILE: &str = "See \
    822    https://doc.rust-lang.org/cargo/reference/config.html#profile for more \
    823    information about specifying profiles in config.";
    824
    825const STABILIZED_CRATE_VERSIONS: &str = "The crate version is now \
    826    automatically added to the documentation.";
    827
    828const STABILIZED_PACKAGE_FEATURES: &str = "Enhanced feature flag behavior is now \
    829    available in virtual workspaces, and `member/feature-name` syntax is also \
    830    always available. Other extensions require setting `resolver = \"2\"` in \
    831    Cargo.toml.\n\
    832    See https://doc.rust-lang.org/nightly/cargo/reference/features.html#resolver-version-2-command-line-flags \
    833    for more information.";
    834
    835const STABILIZED_FEATURES: &str = "The new feature resolver is now available \
    836    by specifying `resolver = \"2\"` in Cargo.toml.\n\
    837    See https://doc.rust-lang.org/nightly/cargo/reference/features.html#feature-resolver-version-2 \
    838    for more information.";
    839
    840const STABILIZED_EXTRA_LINK_ARG: &str = "Additional linker arguments are now \
    841    supported without passing this flag.";
    842
    843const STABILIZED_CONFIGURABLE_ENV: &str = "The [env] section is now always enabled.";
    844
    845const STABILIZED_PATCH_IN_CONFIG: &str = "The patch-in-config feature is now always enabled.";
    846
    847const STABILIZED_NAMED_PROFILES: &str = "The named-profiles feature is now always enabled.\n\
    848    See https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#custom-profiles \
    849    for more information";
    850
    851const STABILIZED_DOCTEST_IN_WORKSPACE: &str =
    852    "The doctest-in-workspace feature is now always enabled.";
    853
    854const STABILIZED_FUTURE_INCOMPAT_REPORT: &str =
    855    "The future-incompat-report feature is now always enabled.";
    856
    857const STABILIZED_WEAK_DEP_FEATURES: &str = "Weak dependency features are now always available.";
    858
    859const STABILISED_NAMESPACED_FEATURES: &str = "Namespaced features are now always available.";
    860
    861const STABILIZED_TIMINGS: &str = "The -Ztimings option has been stabilized as --timings.";
    862
    863const STABILISED_MULTITARGET: &str = "Multiple `--target` options are now always available.";
    864
    865const STABILIZED_TERMINAL_WIDTH: &str =
    866    "The -Zterminal-width option is now always enabled for terminal output.";
    867
    868const STABILISED_SPARSE_REGISTRY: &str = "The sparse protocol is now the default for crates.io";
    869
    870const STABILIZED_CREDENTIAL_PROCESS: &str =
    871    "Authentication with a credential provider is always available.";
    872
    873const STABILIZED_REGISTRY_AUTH: &str =
    874    "Authenticated registries are available if a credential provider is configured.";
    875
    876const STABILIZED_LINTS: &str = "The `[lints]` table is now always available.";
    877
    878const STABILIZED_CHECK_CFG: &str =
    879    "Compile-time checking of conditional (a.k.a. `-Zcheck-cfg`) is now always enabled.";
    880
    881fn deserialize_comma_separated_list<'de, D>(
    882    deserializer: D,
    883) -> Result<Option<Vec<String>>, D::Error>
    884where
    885    D: serde::Deserializer<'de>,
    886{
    887    let Some(list) = <Option<Vec<String>>>::deserialize(deserializer)? else {
    888        return Ok(None);
    889    };
    890    let v = list
    891        .iter()
    892        .flat_map(|s| s.split(','))
    893        .filter(|s| !s.is_empty())
    894        .map(String::from)
    895        .collect();
    896    Ok(Some(v))
    897}
    898
    899#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
    900#[serde(default)]
    901pub struct GitFeatures {
    902    /// When cloning the index, perform a shallow clone. Maintain shallowness upon subsequent fetches.
    903    pub shallow_index: bool,
    904    /// When cloning git dependencies, perform a shallow clone and maintain shallowness on subsequent fetches.
    905    pub shallow_deps: bool,
    906}
    907
    908impl GitFeatures {
    909    pub fn all() -> Self {
    910        GitFeatures {
    911            shallow_index: true,
    912            shallow_deps: true,
    913        }
    914    }
    915
    916    fn expecting() -> String {
    917        let fields = vec!["`shallow-index`", "`shallow-deps`"];
    918        format!(
    919            "unstable 'git' only takes {} as valid inputs",
    920            fields.join(" and ")
    921        )
    922    }
    923}
    924
    925fn deserialize_git_features<'de, D>(deserializer: D) -> Result<Option<GitFeatures>, D::Error>
    926where
    927    D: serde::de::Deserializer<'de>,
    928{
    929    struct GitFeaturesVisitor;
    930
    931    impl<'de> serde::de::Visitor<'de> for GitFeaturesVisitor {
    932        type Value = Option<GitFeatures>;
    933
    934        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    935            formatter.write_str(&GitFeatures::expecting())
    936        }
    937
    938        fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
    939        where
    940            E: serde::de::Error,
    941        {
    942            if v {
    943                Ok(Some(GitFeatures::all()))
    944            } else {
    945                Ok(None)
    946            }
    947        }
    948
    949        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
    950        where
    951            E: serde::de::Error,
    952        {
    953            Ok(parse_git(s.split(",")).map_err(serde::de::Error::custom)?)
    954        }
    955
    956        fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    957        where
    958            D: serde::de::Deserializer<'de>,
    959        {
    960            let git = GitFeatures::deserialize(deserializer)?;
    961            Ok(Some(git))
    962        }
    963
    964        fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
    965        where
    966            V: serde::de::MapAccess<'de>,
    967        {
    968            let mvd = serde::de::value::MapAccessDeserializer::new(map);
    969            Ok(Some(GitFeatures::deserialize(mvd)?))
    970        }
    971    }
    972
    973    deserializer.deserialize_any(GitFeaturesVisitor)
    974}
    975
    976fn parse_git(it: impl Iterator<Item = impl AsRef<str>>) -> CargoResult<Option<GitFeatures>> {
    977    let mut out = GitFeatures::default();
    978    let GitFeatures {
    979        shallow_index,
    980        shallow_deps,
    981    } = &mut out;
    982
    983    for e in it {
    984        match e.as_ref() {
    985            "shallow-index" => *shallow_index = true,
    986            "shallow-deps" => *shallow_deps = true,
    987            _ => {
    988                bail!(GitFeatures::expecting())
    989            }
    990        }
    991    }
    992    Ok(Some(out))
    993}
    994
    995#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
    996#[serde(default)]
    997pub struct GitoxideFeatures {
    998    /// All fetches are done with `gitoxide`, which includes git dependencies as well as the crates index.
    999    pub fetch: bool,
    1000    /// Checkout git dependencies using `gitoxide` (submodules are still handled by git2 ATM, and filters
    1001    /// like linefeed conversions are unsupported).
    1002    pub checkout: bool,
    1003    /// A feature flag which doesn't have any meaning except for preventing
    1004    /// `__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2=1` builds to enable all safe `gitoxide` features.
    1005    /// That way, `gitoxide` isn't actually used even though it's enabled.
    1006    pub internal_use_git2: bool,
    1007}
    1008
    1009impl GitoxideFeatures {
    1010    pub fn all() -> Self {
    1011        GitoxideFeatures {
    1012            fetch: true,
    1013            checkout: true,
    1014            internal_use_git2: false,
    1015        }
    1016    }
    1017
    1018    /// Features we deem safe for everyday use - typically true when all tests pass with them
    1019    /// AND they are backwards compatible.
    1020    fn safe() -> Self {
    1021        GitoxideFeatures {
    1022            fetch: true,
    1023            checkout: true,
    1024            internal_use_git2: false,
    1025        }
    1026    }
    1027
    1028    fn expecting() -> String {
    1029        let fields = vec!["`fetch`", "`checkout`", "`internal-use-git2`"];
    1030        format!(
    1031            "unstable 'gitoxide' only takes {} as valid inputs, for shallow fetches see `-Zgit=shallow-index,shallow-deps`",
    1032            fields.join(" and ")
    1033        )
    1034    }
    1035}
    1036
    1037fn deserialize_gitoxide_features<'de, D>(
    1038    deserializer: D,
    1039) -> Result<Option<GitoxideFeatures>, D::Error>
    1040where
    1041    D: serde::de::Deserializer<'de>,
    1042{
    1043    struct GitoxideFeaturesVisitor;
    1044
    1045    impl<'de> serde::de::Visitor<'de> for GitoxideFeaturesVisitor {
    1046        type Value = Option<GitoxideFeatures>;
    1047
    1048        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    1049            formatter.write_str(&GitoxideFeatures::expecting())
    1050        }
    1051
    1052        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
    1053        where
    1054            E: serde::de::Error,
    1055        {
    1056            Ok(parse_gitoxide(s.split(",")).map_err(serde::de::Error::custom)?)
    1057        }
    1058
    1059        fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
    1060        where
    1061            E: serde::de::Error,
    1062        {
    1063            if v {
    1064                Ok(Some(GitoxideFeatures::all()))
    1065            } else {
    1066                Ok(None)
    1067            }
    1068        }
    1069
    1070        fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    1071        where
    1072            D: serde::de::Deserializer<'de>,
    1073        {
    1074            let gitoxide = GitoxideFeatures::deserialize(deserializer)?;
    1075            Ok(Some(gitoxide))
    1076        }
    1077
    1078        fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
    1079        where
    1080            V: serde::de::MapAccess<'de>,
    1081        {
    1082            let mvd = serde::de::value::MapAccessDeserializer::new(map);
    1083            Ok(Some(GitoxideFeatures::deserialize(mvd)?))
    1084        }
    1085    }
    1086
    1087    deserializer.deserialize_any(GitoxideFeaturesVisitor)
    1088}
    1089
    1090fn parse_gitoxide(
    1091    it: impl Iterator<Item = impl AsRef<str>>,
    1092) -> CargoResult<Option<GitoxideFeatures>> {
    1093    let mut out = GitoxideFeatures::default();
    1094    let GitoxideFeatures {
    1095        fetch,
    1096        checkout,
    1097        internal_use_git2,
    1098    } = &mut out;
    1099
    1100    for e in it {
    1101        match e.as_ref() {
    1102            "fetch" => *fetch = true,
    1103            "checkout" => *checkout = true,
    1104            "internal-use-git2" => *internal_use_git2 = true,
    1105            _ => {
    1106                bail!(GitoxideFeatures::expecting())
    1107            }
    1108        }
    1109    }
    1110    Ok(Some(out))
    1111}
    1112
    1113impl CliUnstable {
    1114    /// Parses `-Z` flags from the command line, and returns messages that warn
    1115    /// if any flag has alreardy been stabilized.
    1116    pub fn parse(
    1117        &mut self,
    1118        flags: &[String],
    1119        nightly_features_allowed: bool,
    1120    ) -> CargoResult<Vec<String>> {
    1121        if !flags.is_empty() && !nightly_features_allowed {
    1122            bail!(
    1123                "the `-Z` flag is only accepted on the nightly channel of Cargo, \
    1124                 but this is the `{}` channel\n\
    1125                 {}",
    1126                channel(),
    1127                SEE_CHANNELS
    1128            );
    1129        }
    1130        let mut warnings = Vec::new();
    1131        // We read flags twice, first to get allowed-features (if specified),
    1132        // and then to read the remaining unstable flags.
    1133        for flag in flags {
    1134            if flag.starts_with("allow-features=") {
    1135                self.add(flag, &mut warnings)?;
    1136            }
    1137        }
    1138        for flag in flags {
    1139            self.add(flag, &mut warnings)?;
    1140        }
    1141
    1142        if self.gitoxide.is_none() && cargo_use_gitoxide_instead_of_git2() {
    1143            self.gitoxide = GitoxideFeatures::safe().into();
    1144        }
    1145        Ok(warnings)
    1146    }
    1147
    1148    fn add(&mut self, flag: &str, warnings: &mut Vec<String>) -> CargoResult<()> {
    1149        let mut parts = flag.splitn(2, '=');
    1150        let k = parts.next().unwrap();
    1151        let v = parts.next();
    1152
    1153        fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> {
    1154            match value {
    1155                None | Some("yes") => Ok(true),
    1156                Some("no") => Ok(false),
    1157                Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s),
    1158            }
    1159        }
    1160
    1161        /// Parse a comma-separated list
    1162        fn parse_list(value: Option<&str>) -> Vec<String> {
    1163            match value {
    1164                None => Vec::new(),
    1165                Some("") => Vec::new(),
    1166                Some(v) => v.split(',').map(|s| s.to_string()).collect(),
    1167            }
    1168        }
    1169
    1170        // Asserts that there is no argument to the flag.
    1171        fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> {
    1172            if let Some(v) = value {
    1173                bail!("flag -Z{} does not take a value, found: `{}`", key, v);
    1174            }
    1175            Ok(true)
    1176        }
    1177
    1178        let mut stabilized_warn = |key: &str, version: &str, message: &str| {
    1179            warnings.push(format!(
    1180                "flag `-Z {}` has been stabilized in the {} release, \
    1181                 and is no longer necessary\n{}",
    1182                key,
    1183                version,
    1184                indented_lines(message)
    1185            ));
    1186        };
    1187
    1188        // Use this if the behavior now requires another mechanism to enable.
    1189        let stabilized_err = |key: &str, version: &str, message: &str| {
    1190            Err(anyhow::format_err!(
    1191                "flag `-Z {}` has been stabilized in the {} release\n{}",
    1192                key,
    1193                version,
    1194                indented_lines(message)
    1195            ))
    1196        };
    1197
    1198        if let Some(allowed) = &self.allow_features {
    1199            if k != "allow-features" && !allowed.contains(k) {
    1200                bail!(
    1201                    "the feature `{}` is not in the list of allowed features: [{}]",
    1202                    k,
    1203                    itertools::join(allowed, ", ")
    1204                );
    1205            }
    1206        }
    1207
    1208        match k {
    1209            // Permanently unstable features
    1210            // Sorted alphabetically:
    1211            "allow-features" => self.allow_features = Some(parse_list(v).into_iter().collect()),
    1212            "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?,
    1213
    1214            // Stabilized features
    1215            // Sorted by version, then alphabetically:
    1216            "compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS),
    1217            "offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?,
    1218            "cache-messages" => stabilized_warn(k, "1.40", STABILIZED_CACHE_MESSAGES),
    1219            "install-upgrade" => stabilized_warn(k, "1.41", STABILIZED_INSTALL_UPGRADE),
    1220            "config-profile" => stabilized_warn(k, "1.43", STABILIZED_CONFIG_PROFILE),
    1221            "crate-versions" => stabilized_warn(k, "1.47", STABILIZED_CRATE_VERSIONS),
    1222            "features" => {
    1223                // `-Z features` has been stabilized since 1.51,
    1224                // but `-Z features=compare` is still allowed for convenience
    1225                // to validate that the feature resolver resolves features
    1226                // in the same way as the dependency resolver,
    1227                // until we feel confident to remove entirely.
    1228                //
    1229                // See rust-lang/cargo#11168
    1230                let feats = parse_list(v);
    1231                let stab_is_not_empty = feats.iter().any(|feat| {
    1232                    matches!(
    1233                        feat.as_str(),
    1234                        "build_dep" | "host_dep" | "dev_dep" | "itarget" | "all"
    1235                    )
    1236                });
    1237                if stab_is_not_empty || feats.is_empty() {
    1238                    // Make this stabilized_err once -Zfeature support is removed.
    1239                    stabilized_warn(k, "1.51", STABILIZED_FEATURES);
    1240                }
    1241                self.features = Some(feats);
    1242            }
    1243            "package-features" => stabilized_warn(k, "1.51", STABILIZED_PACKAGE_FEATURES),
    1244            "configurable-env" => stabilized_warn(k, "1.56", STABILIZED_CONFIGURABLE_ENV),
    1245            "extra-link-arg" => stabilized_warn(k, "1.56", STABILIZED_EXTRA_LINK_ARG),
    1246            "patch-in-config" => stabilized_warn(k, "1.56", STABILIZED_PATCH_IN_CONFIG),
    1247            "named-profiles" => stabilized_warn(k, "1.57", STABILIZED_NAMED_PROFILES),
    1248            "future-incompat-report" => {
    1249                stabilized_warn(k, "1.59.0", STABILIZED_FUTURE_INCOMPAT_REPORT)
    1250            }
    1251            "namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES),
    1252            "timings" => stabilized_warn(k, "1.60", STABILIZED_TIMINGS),
    1253            "weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES),
    1254            "multitarget" => stabilized_warn(k, "1.64", STABILISED_MULTITARGET),
    1255            "sparse-registry" => stabilized_warn(k, "1.68", STABILISED_SPARSE_REGISTRY),
    1256            "terminal-width" => stabilized_warn(k, "1.68", STABILIZED_TERMINAL_WIDTH),
    1257            "doctest-in-workspace" => stabilized_warn(k, "1.72", STABILIZED_DOCTEST_IN_WORKSPACE),
    1258            "credential-process" => stabilized_warn(k, "1.74", STABILIZED_CREDENTIAL_PROCESS),
    1259            "lints" => stabilized_warn(k, "1.74", STABILIZED_LINTS),
    1260            "registry-auth" => stabilized_warn(k, "1.74", STABILIZED_REGISTRY_AUTH),
    1261            "check-cfg" => stabilized_warn(k, "1.80", STABILIZED_CHECK_CFG),
    1262
    1263            // Unstable features
    1264            // Sorted alphabetically:
    1265            "advanced-env" => self.advanced_env = parse_empty(k, v)?,
    1266            "asymmetric-token" => self.asymmetric_token = parse_empty(k, v)?,
    1267            "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?,
    1268            "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?,
    1269            "bindeps" => self.bindeps = parse_empty(k, v)?,
    1270            "build-dir" => self.build_dir = parse_empty(k, v)?,
    1271            "build-std" => self.build_std = Some(parse_list(v)),
    1272            "build-std-features" => self.build_std_features = Some(parse_list(v)),
    1273            "cargo-lints" => self.cargo_lints = parse_empty(k, v)?,
    1274            "codegen-backend" => self.codegen_backend = parse_empty(k, v)?,
    1275            "config-include" => self.config_include = parse_empty(k, v)?,
    1276            "direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?,
    1277            "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?,
    1278            "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
    1279            "feature-unification" => self.feature_unification = parse_empty(k, v)?,
    1280            "gc" => self.gc = parse_empty(k, v)?,
    1281            "git" => {
    1282                self.git =
    1283                    v.map_or_else(|| Ok(Some(GitFeatures::all())), |v| parse_git(v.split(',')))?
    1284            }
    1285            "gitoxide" => {
    1286                self.gitoxide = v.map_or_else(
    1287                    || Ok(Some(GitoxideFeatures::all())),
    1288                    |v| parse_gitoxide(v.split(',')),
    1289                )?
    1290            }
    1291            "host-config" => self.host_config = parse_empty(k, v)?,
    1292            "next-lockfile-bump" => self.next_lockfile_bump = parse_empty(k, v)?,
    1293            "minimal-versions" => self.minimal_versions = parse_empty(k, v)?,
    1294            "msrv-policy" => self.msrv_policy = parse_empty(k, v)?,
    1295            // can also be set in .cargo/config or with and ENV
    1296            "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,
    1297            "no-index-update" => self.no_index_update = parse_empty(k, v)?,
    1298            "package-workspace" => self.package_workspace = parse_empty(k, v)?,
    1299            "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
    1300            "public-dependency" => self.public_dependency = parse_empty(k, v)?,
    1301            "profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?,
    1302            "trim-paths" => self.trim_paths = parse_empty(k, v)?,
    1303            "publish-timeout" => self.publish_timeout = parse_empty(k, v)?,
    1304            "root-dir" => self.root_dir = v.map(|v| v.into()),
    1305            "rustdoc-depinfo" => self.rustdoc_depinfo = parse_empty(k, v)?,
    1306            "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
    1307            "rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?,
    1308            "sbom" => self.sbom = parse_empty(k, v)?,
    1309            "separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?,
    1310            "checksum-freshness" => self.checksum_freshness = parse_empty(k, v)?,
    1311            "skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(k, v)?,
    1312            "script" => self.script = parse_empty(k, v)?,
    1313            "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?,
    1314            "unstable-options" => self.unstable_options = parse_empty(k, v)?,
    1315            "warnings" => self.warnings = parse_empty(k, v)?,
    1316            _ => bail!(
    1317                "\
    1318            unknown `-Z` flag specified: {k}\n\n\
    1319            For available unstable features, see \
    1320            https://doc.rust-lang.org/nightly/cargo/reference/unstable.html\n\
    1321            If you intended to use an unstable rustc feature, try setting `RUSTFLAGS=\"-Z{k}\"`"
    1322            ),
    1323        }
    1324
    1325        Ok(())
    1326    }
    1327
    1328    /// Generates an error if `-Z unstable-options` was not used for a new,
    1329    /// unstable command-line flag.
    1330    pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> {
    1331        self.fail_if_stable_opt_custom_z(flag, issue, "unstable-options", self.unstable_options)
    1332    }
    1333
    1334    pub fn fail_if_stable_opt_custom_z(
    1335        &self,
    1336        flag: &str,
    1337        issue: u32,
    1338        z_name: &str,
    1339        enabled: bool,
    1340    ) -> CargoResult<()> {
    1341        if !enabled {
    1342            let see = format!(
    1343                "See https://github.com/rust-lang/cargo/issues/{issue} for more \
    1344                 information about the `{flag}` flag."
    1345            );
    1346            // NOTE: a `config` isn't available here, check the channel directly
    1347            let channel = channel();
    1348            if channel == "nightly" || channel == "dev" {
    1349                bail!(
    1350                    "the `{flag}` flag is unstable, pass `-Z {z_name}` to enable it\n\
    1351                     {see}"
    1352                );
    1353            } else {
    1354                bail!(
    1355                    "the `{flag}` flag is unstable, and only available on the nightly channel \
    1356                     of Cargo, but this is the `{channel}` channel\n\
    1357                     {SEE_CHANNELS}\n\
    1358                     {see}"
    1359                );
    1360            }
    1361        }
    1362        Ok(())
    1363    }
    1364
    1365    /// Generates an error if `-Z unstable-options` was not used for a new,
    1366    /// unstable subcommand.
    1367    pub fn fail_if_stable_command(
    1368        &self,
    1369        gctx: &GlobalContext,
    1370        command: &str,
    1371        issue: u32,
    1372        z_name: &str,
    1373        enabled: bool,
    1374    ) -> CargoResult<()> {
    1375        if enabled {
    1376            return Ok(());
    1377        }
    1378        let see = format!(
    1379            "See https://github.com/rust-lang/cargo/issues/{} for more \
    1380            information about the `cargo {}` command.",
    1381            issue, command
    1382        );
    1383        if gctx.nightly_features_allowed {
    1384            bail!(
    1385                "the `cargo {command}` command is unstable, pass `-Z {z_name}` \
    1386                 to enable it\n\
    1387                 {see}",
    1388            );
    1389        } else {
    1390            bail!(
    1391                "the `cargo {}` command is unstable, and only available on the \
    1392                 nightly channel of Cargo, but this is the `{}` channel\n\
    1393                 {}\n\
    1394                 {}",
    1395                command,
    1396                channel(),
    1397                SEE_CHANNELS,
    1398                see
    1399            );
    1400        }
    1401    }
    1402}
    1403
    1404/// Returns the current release channel ("stable", "beta", "nightly", "dev").
    1405pub fn channel() -> String {
    1406    // ALLOWED: For testing cargo itself only.
    1407    #[allow(clippy::disallowed_methods)]
    1408    if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") {
    1409        return override_channel;
    1410    }
    1411    // ALLOWED: the process of rustc bootstrapping reads this through
    1412    // `std::env`. We should make the behavior consistent. Also, we
    1413    // don't advertise this for bypassing nightly.
    1414    #[allow(clippy::disallowed_methods)]
    1415    if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") {
    1416        if staging == "1" {
    1417            return "dev".to_string();
    1418        }
    1419    }
    1420    // Debian: always return dev channel
    1421    String::from("dev")
    1422}
    1423
    1424/// Only for testing and developing. See ["Running with gitoxide as default git backend in tests"][1].
    1425///
    1426/// [1]: https://doc.crates.io/contrib/tests/running.html#running-with-gitoxide-as-default-git-backend-in-tests
    1427// ALLOWED: For testing cargo itself only.
    1428#[allow(clippy::disallowed_methods)]
    1429fn cargo_use_gitoxide_instead_of_git2() -> bool {
    1430    std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1")
    1431}
    1432
    1433/// Generate a link to Cargo documentation for the current release channel
    1434/// `path` is the URL component after `https://doc.rust-lang.org/{channel}/cargo/`
    1435pub fn cargo_docs_link(path: &str) -> String {
    1436    let url_channel = match channel().as_str() {
    1437        "dev" | "nightly" => "nightly/",
    1438        "beta" => "beta/",
    1439        _ => "",
    1440    };
    1441    format!("https://doc.rust-lang.org/{url_channel}cargo/{path}")
    1442}