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