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    #[serde(deserialize_with = "deserialize_comma_separated_list")]
763    build_std: Option<Vec<String>>  = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"),
764    #[serde(deserialize_with = "deserialize_comma_separated_list")]
765    build_std_features: Option<Vec<String>>  = ("Configure features enabled for the standard library itself when building the standard library"),
766    cargo_lints: bool = ("Enable the `[lints.cargo]` table"),
767    checksum_freshness: bool = ("Use a checksum to determine if output is fresh rather than filesystem mtime"),
768    codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"),
769    config_include: bool = ("Enable the `include` key in config files"),
770    direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"),
771    doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"),
772    dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
773    feature_unification: bool = ("Enable new feature unification modes in workspaces"),
774    features: Option<Vec<String>>,
775    gc: bool = ("Track cache usage and \"garbage collect\" unused files"),
776    #[serde(deserialize_with = "deserialize_git_features")]
777    git: Option<GitFeatures> = ("Enable support for shallow git fetch operations"),
778    #[serde(deserialize_with = "deserialize_gitoxide_features")]
779    gitoxide: Option<GitoxideFeatures> = ("Use gitoxide for the given git interactions, or all of them if no argument is given"),
780    host_config: bool = ("Enable the `[host]` section in the .cargo/config.toml file"),
781    minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"),
782    msrv_policy: bool = ("Enable rust-version aware policy within cargo"),
783    mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"),
784    next_lockfile_bump: bool,
785    no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
786    package_workspace: bool = ("Handle intra-workspace dependencies when packaging"),
787    panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
788    profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"),
789    public_dependency: bool = ("Respect a dependency's `public` field in Cargo.toml to control public/private dependencies"),
790    publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
791    root_dir: Option<PathBuf> = ("Set the root directory relative to which paths are printed (defaults to workspace root)"),
792    rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
793    rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"),
794    script: bool = ("Enable support for single-file, `.rs` packages"),
795    separate_nightlies: bool,
796    skip_rustdoc_fingerprint: bool,
797    target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
798    trim_paths: bool = ("Enable the `trim-paths` option in profiles"),
799    unstable_options: bool = ("Allow the usage of unstable options"),
800    warnings: bool = ("Allow use of the build.warnings config key"),
801);
802
803const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
804    enabled when used on an interactive console.\n\
805    See https://doc.rust-lang.org/cargo/reference/config.html#termprogresswhen \
806    for information on controlling the progress bar.";
807
808const STABILIZED_OFFLINE: &str = "Offline mode is now available via the \
809    --offline CLI option";
810
811const STABILIZED_CACHE_MESSAGES: &str = "Message caching is now always enabled.";
812
813const STABILIZED_INSTALL_UPGRADE: &str = "Packages are now always upgraded if \
814    they appear out of date.\n\
815    See https://doc.rust-lang.org/cargo/commands/cargo-install.html for more \
816    information on how upgrading works.";
817
818const STABILIZED_CONFIG_PROFILE: &str = "See \
819    https://doc.rust-lang.org/cargo/reference/config.html#profile for more \
820    information about specifying profiles in config.";
821
822const STABILIZED_CRATE_VERSIONS: &str = "The crate version is now \
823    automatically added to the documentation.";
824
825const STABILIZED_PACKAGE_FEATURES: &str = "Enhanced feature flag behavior is now \
826    available in virtual workspaces, and `member/feature-name` syntax is also \
827    always available. Other extensions require setting `resolver = \"2\"` in \
828    Cargo.toml.\n\
829    See https://doc.rust-lang.org/nightly/cargo/reference/features.html#resolver-version-2-command-line-flags \
830    for more information.";
831
832const STABILIZED_FEATURES: &str = "The new feature resolver is now available \
833    by specifying `resolver = \"2\"` in Cargo.toml.\n\
834    See https://doc.rust-lang.org/nightly/cargo/reference/features.html#feature-resolver-version-2 \
835    for more information.";
836
837const STABILIZED_EXTRA_LINK_ARG: &str = "Additional linker arguments are now \
838    supported without passing this flag.";
839
840const STABILIZED_CONFIGURABLE_ENV: &str = "The [env] section is now always enabled.";
841
842const STABILIZED_PATCH_IN_CONFIG: &str = "The patch-in-config feature is now always enabled.";
843
844const STABILIZED_NAMED_PROFILES: &str = "The named-profiles feature is now always enabled.\n\
845    See https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#custom-profiles \
846    for more information";
847
848const STABILIZED_DOCTEST_IN_WORKSPACE: &str =
849    "The doctest-in-workspace feature is now always enabled.";
850
851const STABILIZED_FUTURE_INCOMPAT_REPORT: &str =
852    "The future-incompat-report feature is now always enabled.";
853
854const STABILIZED_WEAK_DEP_FEATURES: &str = "Weak dependency features are now always available.";
855
856const STABILISED_NAMESPACED_FEATURES: &str = "Namespaced features are now always available.";
857
858const STABILIZED_TIMINGS: &str = "The -Ztimings option has been stabilized as --timings.";
859
860const STABILISED_MULTITARGET: &str = "Multiple `--target` options are now always available.";
861
862const STABILIZED_TERMINAL_WIDTH: &str =
863    "The -Zterminal-width option is now always enabled for terminal output.";
864
865const STABILISED_SPARSE_REGISTRY: &str = "The sparse protocol is now the default for crates.io";
866
867const STABILIZED_CREDENTIAL_PROCESS: &str =
868    "Authentication with a credential provider is always available.";
869
870const STABILIZED_REGISTRY_AUTH: &str =
871    "Authenticated registries are available if a credential provider is configured.";
872
873const STABILIZED_LINTS: &str = "The `[lints]` table is now always available.";
874
875const STABILIZED_CHECK_CFG: &str =
876    "Compile-time checking of conditional (a.k.a. `-Zcheck-cfg`) is now always enabled.";
877
878fn deserialize_comma_separated_list<'de, D>(
879    deserializer: D,
880) -> Result<Option<Vec<String>>, D::Error>
881where
882    D: serde::Deserializer<'de>,
883{
884    let Some(list) = <Option<Vec<String>>>::deserialize(deserializer)? else {
885        return Ok(None);
886    };
887    let v = list
888        .iter()
889        .flat_map(|s| s.split(','))
890        .filter(|s| !s.is_empty())
891        .map(String::from)
892        .collect();
893    Ok(Some(v))
894}
895
896#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
897#[serde(default)]
898pub struct GitFeatures {
899    /// When cloning the index, perform a shallow clone. Maintain shallowness upon subsequent fetches.
900    pub shallow_index: bool,
901    /// When cloning git dependencies, perform a shallow clone and maintain shallowness on subsequent fetches.
902    pub shallow_deps: bool,
903}
904
905impl GitFeatures {
906    pub fn all() -> Self {
907        GitFeatures {
908            shallow_index: true,
909            shallow_deps: true,
910        }
911    }
912
913    fn expecting() -> String {
914        let fields = vec!["`shallow-index`", "`shallow-deps`"];
915        format!(
916            "unstable 'git' only takes {} as valid inputs",
917            fields.join(" and ")
918        )
919    }
920}
921
922fn deserialize_git_features<'de, D>(deserializer: D) -> Result<Option<GitFeatures>, D::Error>
923where
924    D: serde::de::Deserializer<'de>,
925{
926    struct GitFeaturesVisitor;
927
928    impl<'de> serde::de::Visitor<'de> for GitFeaturesVisitor {
929        type Value = Option<GitFeatures>;
930
931        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
932            formatter.write_str(&GitFeatures::expecting())
933        }
934
935        fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
936        where
937            E: serde::de::Error,
938        {
939            if v {
940                Ok(Some(GitFeatures::all()))
941            } else {
942                Ok(None)
943            }
944        }
945
946        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
947        where
948            E: serde::de::Error,
949        {
950            Ok(parse_git(s.split(",")).map_err(serde::de::Error::custom)?)
951        }
952
953        fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
954        where
955            D: serde::de::Deserializer<'de>,
956        {
957            let git = GitFeatures::deserialize(deserializer)?;
958            Ok(Some(git))
959        }
960
961        fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
962        where
963            V: serde::de::MapAccess<'de>,
964        {
965            let mvd = serde::de::value::MapAccessDeserializer::new(map);
966            Ok(Some(GitFeatures::deserialize(mvd)?))
967        }
968    }
969
970    deserializer.deserialize_any(GitFeaturesVisitor)
971}
972
973fn parse_git(it: impl Iterator<Item = impl AsRef<str>>) -> CargoResult<Option<GitFeatures>> {
974    let mut out = GitFeatures::default();
975    let GitFeatures {
976        shallow_index,
977        shallow_deps,
978    } = &mut out;
979
980    for e in it {
981        match e.as_ref() {
982            "shallow-index" => *shallow_index = true,
983            "shallow-deps" => *shallow_deps = true,
984            _ => {
985                bail!(GitFeatures::expecting())
986            }
987        }
988    }
989    Ok(Some(out))
990}
991
992#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
993#[serde(default)]
994pub struct GitoxideFeatures {
995    /// All fetches are done with `gitoxide`, which includes git dependencies as well as the crates index.
996    pub fetch: bool,
997    /// Checkout git dependencies using `gitoxide` (submodules are still handled by git2 ATM, and filters
998    /// like linefeed conversions are unsupported).
999    pub checkout: bool,
1000    /// A feature flag which doesn't have any meaning except for preventing
1001    /// `__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2=1` builds to enable all safe `gitoxide` features.
1002    /// That way, `gitoxide` isn't actually used even though it's enabled.
1003    pub internal_use_git2: bool,
1004}
1005
1006impl GitoxideFeatures {
1007    pub fn all() -> Self {
1008        GitoxideFeatures {
1009            fetch: true,
1010            checkout: true,
1011            internal_use_git2: false,
1012        }
1013    }
1014
1015    /// Features we deem safe for everyday use - typically true when all tests pass with them
1016    /// AND they are backwards compatible.
1017    fn safe() -> Self {
1018        GitoxideFeatures {
1019            fetch: true,
1020            checkout: true,
1021            internal_use_git2: false,
1022        }
1023    }
1024
1025    fn expecting() -> String {
1026        let fields = vec!["`fetch`", "`checkout`", "`internal-use-git2`"];
1027        format!(
1028            "unstable 'gitoxide' only takes {} as valid inputs, for shallow fetches see `-Zgit=shallow-index,shallow-deps`",
1029            fields.join(" and ")
1030        )
1031    }
1032}
1033
1034fn deserialize_gitoxide_features<'de, D>(
1035    deserializer: D,
1036) -> Result<Option<GitoxideFeatures>, D::Error>
1037where
1038    D: serde::de::Deserializer<'de>,
1039{
1040    struct GitoxideFeaturesVisitor;
1041
1042    impl<'de> serde::de::Visitor<'de> for GitoxideFeaturesVisitor {
1043        type Value = Option<GitoxideFeatures>;
1044
1045        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1046            formatter.write_str(&GitoxideFeatures::expecting())
1047        }
1048
1049        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1050        where
1051            E: serde::de::Error,
1052        {
1053            Ok(parse_gitoxide(s.split(",")).map_err(serde::de::Error::custom)?)
1054        }
1055
1056        fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1057        where
1058            E: serde::de::Error,
1059        {
1060            if v {
1061                Ok(Some(GitoxideFeatures::all()))
1062            } else {
1063                Ok(None)
1064            }
1065        }
1066
1067        fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1068        where
1069            D: serde::de::Deserializer<'de>,
1070        {
1071            let gitoxide = GitoxideFeatures::deserialize(deserializer)?;
1072            Ok(Some(gitoxide))
1073        }
1074
1075        fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
1076        where
1077            V: serde::de::MapAccess<'de>,
1078        {
1079            let mvd = serde::de::value::MapAccessDeserializer::new(map);
1080            Ok(Some(GitoxideFeatures::deserialize(mvd)?))
1081        }
1082    }
1083
1084    deserializer.deserialize_any(GitoxideFeaturesVisitor)
1085}
1086
1087fn parse_gitoxide(
1088    it: impl Iterator<Item = impl AsRef<str>>,
1089) -> CargoResult<Option<GitoxideFeatures>> {
1090    let mut out = GitoxideFeatures::default();
1091    let GitoxideFeatures {
1092        fetch,
1093        checkout,
1094        internal_use_git2,
1095    } = &mut out;
1096
1097    for e in it {
1098        match e.as_ref() {
1099            "fetch" => *fetch = true,
1100            "checkout" => *checkout = true,
1101            "internal-use-git2" => *internal_use_git2 = true,
1102            _ => {
1103                bail!(GitoxideFeatures::expecting())
1104            }
1105        }
1106    }
1107    Ok(Some(out))
1108}
1109
1110impl CliUnstable {
1111    /// Parses `-Z` flags from the command line, and returns messages that warn
1112    /// if any flag has alreardy been stabilized.
1113    pub fn parse(
1114        &mut self,
1115        flags: &[String],
1116        nightly_features_allowed: bool,
1117    ) -> CargoResult<Vec<String>> {
1118        if !flags.is_empty() && !nightly_features_allowed {
1119            bail!(
1120                "the `-Z` flag is only accepted on the nightly channel of Cargo, \
1121                 but this is the `{}` channel\n\
1122                 {}",
1123                channel(),
1124                SEE_CHANNELS
1125            );
1126        }
1127        let mut warnings = Vec::new();
1128        // We read flags twice, first to get allowed-features (if specified),
1129        // and then to read the remaining unstable flags.
1130        for flag in flags {
1131            if flag.starts_with("allow-features=") {
1132                self.add(flag, &mut warnings)?;
1133            }
1134        }
1135        for flag in flags {
1136            self.add(flag, &mut warnings)?;
1137        }
1138
1139        if self.gitoxide.is_none() && cargo_use_gitoxide_instead_of_git2() {
1140            self.gitoxide = GitoxideFeatures::safe().into();
1141        }
1142        Ok(warnings)
1143    }
1144
1145    fn add(&mut self, flag: &str, warnings: &mut Vec<String>) -> CargoResult<()> {
1146        let mut parts = flag.splitn(2, '=');
1147        let k = parts.next().unwrap();
1148        let v = parts.next();
1149
1150        fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> {
1151            match value {
1152                None | Some("yes") => Ok(true),
1153                Some("no") => Ok(false),
1154                Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s),
1155            }
1156        }
1157
1158        /// Parse a comma-separated list
1159        fn parse_list(value: Option<&str>) -> Vec<String> {
1160            match value {
1161                None => Vec::new(),
1162                Some("") => Vec::new(),
1163                Some(v) => v.split(',').map(|s| s.to_string()).collect(),
1164            }
1165        }
1166
1167        // Asserts that there is no argument to the flag.
1168        fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> {
1169            if let Some(v) = value {
1170                bail!("flag -Z{} does not take a value, found: `{}`", key, v);
1171            }
1172            Ok(true)
1173        }
1174
1175        let mut stabilized_warn = |key: &str, version: &str, message: &str| {
1176            warnings.push(format!(
1177                "flag `-Z {}` has been stabilized in the {} release, \
1178                 and is no longer necessary\n{}",
1179                key,
1180                version,
1181                indented_lines(message)
1182            ));
1183        };
1184
1185        // Use this if the behavior now requires another mechanism to enable.
1186        let stabilized_err = |key: &str, version: &str, message: &str| {
1187            Err(anyhow::format_err!(
1188                "flag `-Z {}` has been stabilized in the {} release\n{}",
1189                key,
1190                version,
1191                indented_lines(message)
1192            ))
1193        };
1194
1195        if let Some(allowed) = &self.allow_features {
1196            if k != "allow-features" && !allowed.contains(k) {
1197                bail!(
1198                    "the feature `{}` is not in the list of allowed features: [{}]",
1199                    k,
1200                    itertools::join(allowed, ", ")
1201                );
1202            }
1203        }
1204
1205        match k {
1206            // Permanently unstable features
1207            // Sorted alphabetically:
1208            "allow-features" => self.allow_features = Some(parse_list(v).into_iter().collect()),
1209            "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?,
1210
1211            // Stabilized features
1212            // Sorted by version, then alphabetically:
1213            "compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS),
1214            "offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?,
1215            "cache-messages" => stabilized_warn(k, "1.40", STABILIZED_CACHE_MESSAGES),
1216            "install-upgrade" => stabilized_warn(k, "1.41", STABILIZED_INSTALL_UPGRADE),
1217            "config-profile" => stabilized_warn(k, "1.43", STABILIZED_CONFIG_PROFILE),
1218            "crate-versions" => stabilized_warn(k, "1.47", STABILIZED_CRATE_VERSIONS),
1219            "features" => {
1220                // `-Z features` has been stabilized since 1.51,
1221                // but `-Z features=compare` is still allowed for convenience
1222                // to validate that the feature resolver resolves features
1223                // in the same way as the dependency resolver,
1224                // until we feel confident to remove entirely.
1225                //
1226                // See rust-lang/cargo#11168
1227                let feats = parse_list(v);
1228                let stab_is_not_empty = feats.iter().any(|feat| {
1229                    matches!(
1230                        feat.as_str(),
1231                        "build_dep" | "host_dep" | "dev_dep" | "itarget" | "all"
1232                    )
1233                });
1234                if stab_is_not_empty || feats.is_empty() {
1235                    // Make this stabilized_err once -Zfeature support is removed.
1236                    stabilized_warn(k, "1.51", STABILIZED_FEATURES);
1237                }
1238                self.features = Some(feats);
1239            }
1240            "package-features" => stabilized_warn(k, "1.51", STABILIZED_PACKAGE_FEATURES),
1241            "configurable-env" => stabilized_warn(k, "1.56", STABILIZED_CONFIGURABLE_ENV),
1242            "extra-link-arg" => stabilized_warn(k, "1.56", STABILIZED_EXTRA_LINK_ARG),
1243            "patch-in-config" => stabilized_warn(k, "1.56", STABILIZED_PATCH_IN_CONFIG),
1244            "named-profiles" => stabilized_warn(k, "1.57", STABILIZED_NAMED_PROFILES),
1245            "future-incompat-report" => {
1246                stabilized_warn(k, "1.59.0", STABILIZED_FUTURE_INCOMPAT_REPORT)
1247            }
1248            "namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES),
1249            "timings" => stabilized_warn(k, "1.60", STABILIZED_TIMINGS),
1250            "weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES),
1251            "multitarget" => stabilized_warn(k, "1.64", STABILISED_MULTITARGET),
1252            "sparse-registry" => stabilized_warn(k, "1.68", STABILISED_SPARSE_REGISTRY),
1253            "terminal-width" => stabilized_warn(k, "1.68", STABILIZED_TERMINAL_WIDTH),
1254            "doctest-in-workspace" => stabilized_warn(k, "1.72", STABILIZED_DOCTEST_IN_WORKSPACE),
1255            "credential-process" => stabilized_warn(k, "1.74", STABILIZED_CREDENTIAL_PROCESS),
1256            "lints" => stabilized_warn(k, "1.74", STABILIZED_LINTS),
1257            "registry-auth" => stabilized_warn(k, "1.74", STABILIZED_REGISTRY_AUTH),
1258            "check-cfg" => stabilized_warn(k, "1.80", STABILIZED_CHECK_CFG),
1259
1260            // Unstable features
1261            // Sorted alphabetically:
1262            "advanced-env" => self.advanced_env = parse_empty(k, v)?,
1263            "asymmetric-token" => self.asymmetric_token = parse_empty(k, v)?,
1264            "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?,
1265            "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?,
1266            "bindeps" => self.bindeps = parse_empty(k, v)?,
1267            "build-std" => self.build_std = Some(parse_list(v)),
1268            "build-std-features" => self.build_std_features = Some(parse_list(v)),
1269            "cargo-lints" => self.cargo_lints = parse_empty(k, v)?,
1270            "codegen-backend" => self.codegen_backend = parse_empty(k, v)?,
1271            "config-include" => self.config_include = parse_empty(k, v)?,
1272            "direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?,
1273            "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?,
1274            "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
1275            "feature-unification" => self.feature_unification = parse_empty(k, v)?,
1276            "gc" => self.gc = parse_empty(k, v)?,
1277            "git" => {
1278                self.git = v.map_or_else(
1279                    || Ok(Some(GitFeatures::all())),
1280                    |v| parse_git(v.split(',')),
1281                )?
1282            }
1283            "gitoxide" => {
1284                self.gitoxide = v.map_or_else(
1285                    || Ok(Some(GitoxideFeatures::all())),
1286                    |v| parse_gitoxide(v.split(',')),
1287                )?
1288            }
1289            "host-config" => self.host_config = parse_empty(k, v)?,
1290            "next-lockfile-bump" => self.next_lockfile_bump = parse_empty(k, v)?,
1291            "minimal-versions" => self.minimal_versions = parse_empty(k, v)?,
1292            "msrv-policy" => self.msrv_policy = parse_empty(k, v)?,
1293            // can also be set in .cargo/config or with and ENV
1294            "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,
1295            "no-index-update" => self.no_index_update = parse_empty(k, v)?,
1296            "package-workspace" => self.package_workspace= parse_empty(k, v)?,
1297            "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
1298            "public-dependency" => self.public_dependency = parse_empty(k, v)?,
1299            "profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?,
1300            "trim-paths" => self.trim_paths = parse_empty(k, v)?,
1301            "publish-timeout" => self.publish_timeout = parse_empty(k, v)?,
1302            "root-dir" => self.root_dir = v.map(|v| v.into()),
1303            "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
1304            "rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?,
1305            "separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?,
1306            "checksum-freshness" => self.checksum_freshness = parse_empty(k, v)?,
1307            "skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(k, v)?,
1308            "script" => self.script = parse_empty(k, v)?,
1309            "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?,
1310            "unstable-options" => self.unstable_options = parse_empty(k, v)?,
1311            "warnings" => self.warnings = parse_empty(k, v)?,
1312            _ => bail!("\
1313            unknown `-Z` flag specified: {k}\n\n\
1314            For available unstable features, see https://doc.rust-lang.org/nightly/cargo/reference/unstable.html\n\
1315            If you intended to use an unstable rustc feature, try setting `RUSTFLAGS=\"-Z{k}\"`"),
1316        }
1317
1318        Ok(())
1319    }
1320
1321    /// Generates an error if `-Z unstable-options` was not used for a new,
1322    /// unstable command-line flag.
1323    pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> {
1324        self.fail_if_stable_opt_custom_z(flag, issue, "unstable-options", self.unstable_options)
1325    }
1326
1327    pub fn fail_if_stable_opt_custom_z(
1328        &self,
1329        flag: &str,
1330        issue: u32,
1331        z_name: &str,
1332        enabled: bool,
1333    ) -> CargoResult<()> {
1334        if !enabled {
1335            let see = format!(
1336                "See https://github.com/rust-lang/cargo/issues/{issue} for more \
1337                 information about the `{flag}` flag."
1338            );
1339            // NOTE: a `config` isn't available here, check the channel directly
1340            let channel = channel();
1341            if channel == "nightly" || channel == "dev" {
1342                bail!(
1343                    "the `{flag}` flag is unstable, pass `-Z {z_name}` to enable it\n\
1344                     {see}"
1345                );
1346            } else {
1347                bail!(
1348                    "the `{flag}` flag is unstable, and only available on the nightly channel \
1349                     of Cargo, but this is the `{channel}` channel\n\
1350                     {SEE_CHANNELS}\n\
1351                     {see}"
1352                );
1353            }
1354        }
1355        Ok(())
1356    }
1357
1358    /// Generates an error if `-Z unstable-options` was not used for a new,
1359    /// unstable subcommand.
1360    pub fn fail_if_stable_command(
1361        &self,
1362        gctx: &GlobalContext,
1363        command: &str,
1364        issue: u32,
1365        z_name: &str,
1366        enabled: bool,
1367    ) -> CargoResult<()> {
1368        if enabled {
1369            return Ok(());
1370        }
1371        let see = format!(
1372            "See https://github.com/rust-lang/cargo/issues/{} for more \
1373            information about the `cargo {}` command.",
1374            issue, command
1375        );
1376        if gctx.nightly_features_allowed {
1377            bail!(
1378                "the `cargo {command}` command is unstable, pass `-Z {z_name}` \
1379                 to enable it\n\
1380                 {see}",
1381            );
1382        } else {
1383            bail!(
1384                "the `cargo {}` command is unstable, and only available on the \
1385                 nightly channel of Cargo, but this is the `{}` channel\n\
1386                 {}\n\
1387                 {}",
1388                command,
1389                channel(),
1390                SEE_CHANNELS,
1391                see
1392            );
1393        }
1394    }
1395}
1396
1397/// Returns the current release channel ("stable", "beta", "nightly", "dev").
1398pub fn channel() -> String {
1399    // ALLOWED: For testing cargo itself only.
1400    #[allow(clippy::disallowed_methods)]
1401    if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") {
1402        return override_channel;
1403    }
1404    // ALLOWED: the process of rustc bootstrapping reads this through
1405    // `std::env`. We should make the behavior consistent. Also, we
1406    // don't advertise this for bypassing nightly.
1407    #[allow(clippy::disallowed_methods)]
1408    if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") {
1409        if staging == "1" {
1410            return "dev".to_string();
1411        }
1412    }
1413    crate::version()
1414        .release_channel
1415        .unwrap_or_else(|| String::from("dev"))
1416}
1417
1418/// Only for testing and developing. See ["Running with gitoxide as default git backend in tests"][1].
1419///
1420/// [1]: https://doc.crates.io/contrib/tests/running.html#running-with-gitoxide-as-default-git-backend-in-tests
1421// ALLOWED: For testing cargo itself only.
1422#[allow(clippy::disallowed_methods)]
1423fn cargo_use_gitoxide_instead_of_git2() -> bool {
1424    std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1")
1425}
1426
1427/// Generate a link to Cargo documentation for the current release channel
1428/// `path` is the URL component after `https://doc.rust-lang.org/{channel}/cargo/`
1429pub fn cargo_docs_link(path: &str) -> String {
1430    let url_channel = match channel().as_str() {
1431        "dev" | "nightly" => "nightly/",
1432        "beta" => "beta/",
1433        _ => "",
1434    };
1435    format!("https://doc.rust-lang.org/{url_channel}cargo/{path}")
1436}