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