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 //! * New syntax in Cargo.toml should use `cargo-features`.
13 //! * New CLI options should use `-Z unstable-options`.
14 //! * New functionality that may not have an interface, or the interface has
15 //!   not yet been designed, or for more complex features that affect multiple
16 //!   parts of Cargo should use a new `-Z` flag.
17 //!
18 //! See below for more details.
19 //!
20 //! When adding new tests for your feature, usually the tests should go into a
21 //! new module of the testsuite. See
22 //! <https://doc.crates.io/contrib/tests/writing.html> for more information on
23 //! writing tests. Particularly, check out the "Testing Nightly Features"
24 //! section for testing unstable features.
25 //!
26 //! After you have added your feature, be sure to update the unstable
27 //! documentation at `src/doc/src/reference/unstable.md` to include a short
28 //! description of how to use your new feature.
29 //!
30 //! And hopefully that's it!
31 //!
32 //! ## New Cargo.toml syntax
33 //!
34 //! The steps for adding new Cargo.toml syntax are:
35 //!
36 //! 1. Add the cargo-features unstable gate. Search below for "look here" to
37 //!    find the `features!` macro and add your feature to the list.
38 //!
39 //! 2. Update the Cargo.toml parsing code to handle your new feature.
40 //!
41 //! 3. Wherever you added the new parsing code, call
42 //!    `features.require(Feature::my_feature_name())?` if the new syntax is
43 //!    used. This will return an error if the user hasn't listed the feature
44 //!    in `cargo-features` or this is not the nightly channel.
45 //!
46 //! ## `-Z unstable-options`
47 //!
48 //! `-Z unstable-options` is intended to force the user to opt-in to new CLI
49 //! flags, options, and new subcommands.
50 //!
51 //! The steps to add a new command-line option are:
52 //!
53 //! 1. Add the option to the CLI parsing code. In the help text, be sure to
54 //!    include `(unstable)` to note that this is an unstable option.
55 //! 2. Where the CLI option is loaded, be sure to call
56 //!    [`CliUnstable::fail_if_stable_opt`]. This will return an error if `-Z
57 //!    unstable options` was not passed.
58 //!
59 //! ## `-Z` options
60 //!
61 //! The steps to add a new `-Z` option are:
62 //!
63 //! 1. Add the option to the [`CliUnstable`] struct below. Flags can take an
64 //!    optional value if you want.
65 //! 2. Update the [`CliUnstable::add`][CliUnstable] function to parse the flag.
66 //! 3. Wherever the new functionality is implemented, call
67 //!    [`Config::cli_unstable`][crate::util::config::Config::cli_unstable] to
68 //!    get an instance of `CliUnstable` and check if the option has been
69 //!    enabled on the `CliUnstable` instance. Nightly gating is already
70 //!    handled, so no need to worry about that.
71 //!
72 //! ## Stabilization
73 //!
74 //! For the stabilization process, see
75 //! <https://doc.crates.io/contrib/process/unstable.html#stabilization>.
76 //!
77 //! The steps for stabilizing are roughly:
78 //!
79 //! 1. Update the feature to be stable, based on the kind of feature:
80 //!   1. `cargo-features`: Change the feature to `stable` in the `features!`
81 //!      macro below.
82 //!   2. `-Z unstable-options`: Find the call to `fail_if_stable_opt` and
83 //!      remove it. Be sure to update the man pages if necessary.
84 //!   3. `-Z` flag: Change the parsing code in [`CliUnstable::add`][CliUnstable]
85 //!      to call `stabilized_warn` or `stabilized_err` and remove the field from
86 //!      `CliUnstable. Remove the `(unstable)` note in the clap help text if
87 //!      necessary.
88 //! 2. Remove `masquerade_as_nightly_cargo` from any tests, and remove
89 //!    `cargo-features` from `Cargo.toml` test files if any.
90 //! 3. Remove the docs from unstable.md and update the redirect at the bottom
91 //!    of that page. Update the rest of the documentation to add the new
92 //!    feature.
93 
94 use std::collections::BTreeSet;
95 use std::env;
96 use std::fmt;
97 use std::str::FromStr;
98 
99 use anyhow::{bail, Error};
100 use cargo_util::ProcessBuilder;
101 use serde::{Deserialize, Serialize};
102 
103 use crate::util::errors::CargoResult;
104 use crate::util::{indented_lines, iter_join};
105 use crate::Config;
106 
107 pub const HIDDEN: &str = "";
108 pub const SEE_CHANNELS: &str =
109     "See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information \
110      about Rust release channels.";
111 
112 /// The edition of the compiler (RFC 2052)
113 #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)]
114 pub enum Edition {
115     /// The 2015 edition
116     Edition2015,
117     /// The 2018 edition
118     Edition2018,
119     /// The 2021 edition
120     Edition2021,
121 }
122 
123 // Adding a new edition:
124 // - Add the next edition to the enum.
125 // - Update every match expression that now fails to compile.
126 // - Update the `FromStr` impl.
127 // - Update CLI_VALUES to include the new edition.
128 // - Set LATEST_UNSTABLE to Some with the new edition.
129 // - Add an unstable feature to the features! macro below for the new edition.
130 // - Gate on that new feature in TomlManifest::to_real_manifest.
131 // - Update the shell completion files.
132 // - Update any failing tests (hopefully there are very few).
133 //
134 // Stabilization instructions:
135 // - Set LATEST_UNSTABLE to None.
136 // - Set LATEST_STABLE to the new version.
137 // - Update `is_stable` to `true`.
138 // - Set the editionNNNN feature to stable in the features macro below.
139 // - Update the man page for the --edition flag.
140 impl Edition {
141     /// The latest edition that is unstable.
142     ///
143     /// This is `None` if there is no next unstable edition.
144     pub const LATEST_UNSTABLE: Option<Edition> = Some(Edition::Edition2021);
145     /// The latest stable edition.
146     pub const LATEST_STABLE: Edition = Edition::Edition2018;
147     /// Possible values allowed for the `--edition` CLI flag.
148     ///
149     /// This requires a static value due to the way clap works, otherwise I
150     /// would have built this dynamically.
151     pub const CLI_VALUES: &'static [&'static str] = &["2015", "2018", "2021"];
152 
153     /// Returns the first version that a particular edition was released on
154     /// stable.
first_version(&self) -> Option<semver::Version>155     pub(crate) fn first_version(&self) -> Option<semver::Version> {
156         use Edition::*;
157         match self {
158             Edition2015 => None,
159             Edition2018 => Some(semver::Version::new(1, 31, 0)),
160             // FIXME: This will likely be 1.56, update when that seems more likely.
161             Edition2021 => Some(semver::Version::new(1, 62, 0)),
162         }
163     }
164 
165     /// Returns `true` if this edition is stable in this release.
is_stable(&self) -> bool166     pub fn is_stable(&self) -> bool {
167         use Edition::*;
168         match self {
169             Edition2015 => true,
170             Edition2018 => true,
171             Edition2021 => false,
172         }
173     }
174 
175     /// Returns the previous edition from this edition.
176     ///
177     /// Returns `None` for 2015.
previous(&self) -> Option<Edition>178     pub fn previous(&self) -> Option<Edition> {
179         use Edition::*;
180         match self {
181             Edition2015 => None,
182             Edition2018 => Some(Edition2015),
183             Edition2021 => Some(Edition2018),
184         }
185     }
186 
187     /// Returns the next edition from this edition, returning the last edition
188     /// if this is already the last one.
saturating_next(&self) -> Edition189     pub fn saturating_next(&self) -> Edition {
190         use Edition::*;
191         match self {
192             Edition2015 => Edition2018,
193             Edition2018 => Edition2021,
194             Edition2021 => Edition2021,
195         }
196     }
197 
198     /// Updates the given [`ProcessBuilder`] to include the appropriate flags
199     /// for setting the edition.
cmd_edition_arg(&self, cmd: &mut ProcessBuilder)200     pub(crate) fn cmd_edition_arg(&self, cmd: &mut ProcessBuilder) {
201         if *self != Edition::Edition2015 {
202             cmd.arg(format!("--edition={}", self));
203         }
204         if !self.is_stable() {
205             cmd.arg("-Z").arg("unstable-options");
206         }
207     }
208 
209     /// Whether or not this edition supports the `rust_*_compatibility` lint.
210     ///
211     /// Ideally this would not be necessary, but currently 2021 does not have
212     /// any lints, and thus `rustc` doesn't recognize it. Perhaps `rustc`
213     /// could create an empty group instead?
supports_compat_lint(&self) -> bool214     pub(crate) fn supports_compat_lint(&self) -> bool {
215         use Edition::*;
216         match self {
217             Edition2015 => false,
218             Edition2018 => true,
219             Edition2021 => false,
220         }
221     }
222 
223     /// Whether or not this edition supports the `rust_*_idioms` lint.
224     ///
225     /// Ideally this would not be necessary...
supports_idiom_lint(&self) -> bool226     pub(crate) fn supports_idiom_lint(&self) -> bool {
227         use Edition::*;
228         match self {
229             Edition2015 => false,
230             Edition2018 => true,
231             Edition2021 => false,
232         }
233     }
234 }
235 
236 impl fmt::Display for Edition {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result237     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238         match *self {
239             Edition::Edition2015 => f.write_str("2015"),
240             Edition::Edition2018 => f.write_str("2018"),
241             Edition::Edition2021 => f.write_str("2021"),
242         }
243     }
244 }
245 impl FromStr for Edition {
246     type Err = Error;
from_str(s: &str) -> Result<Self, Error>247     fn from_str(s: &str) -> Result<Self, Error> {
248         match s {
249             "2015" => Ok(Edition::Edition2015),
250             "2018" => Ok(Edition::Edition2018),
251             "2021" => Ok(Edition::Edition2021),
252             s if s.parse().map_or(false, |y: u16| y > 2021 && y < 2050) => bail!(
253                 "this version of Cargo is older than the `{}` edition, \
254                  and only supports `2015`, `2018`, and `2021` editions.",
255                 s
256             ),
257             s => bail!(
258                 "supported edition values are `2015`, `2018`, or `2021`, \
259                  but `{}` is unknown",
260                 s
261             ),
262         }
263     }
264 }
265 
266 #[derive(PartialEq)]
267 enum Status {
268     Stable,
269     Unstable,
270     Removed,
271 }
272 
273 macro_rules! features {
274     (
275         $(($stab:ident, $feature:ident, $version:expr, $docs:expr),)*
276     ) => (
277         #[derive(Default, Clone, Debug)]
278         pub struct Features {
279             $($feature: bool,)*
280             activated: Vec<String>,
281             nightly_features_allowed: bool,
282         }
283 
284         impl Feature {
285             $(
286                 pub fn $feature() -> &'static Feature {
287                     fn get(features: &Features) -> bool {
288                         stab!($stab) == Status::Stable || features.$feature
289                     }
290                     static FEAT: Feature = Feature {
291                         name: stringify!($feature),
292                         stability: stab!($stab),
293                         version: $version,
294                         docs: $docs,
295                         get,
296                     };
297                     &FEAT
298                 }
299             )*
300 
301             fn is_enabled(&self, features: &Features) -> bool {
302                 (self.get)(features)
303             }
304         }
305 
306         impl Features {
307             fn status(&mut self, feature: &str) -> Option<(&mut bool, &'static Feature)> {
308                 if feature.contains("_") {
309                     return None
310                 }
311                 let feature = feature.replace("-", "_");
312                 $(
313                     if feature == stringify!($feature) {
314                         return Some((&mut self.$feature, Feature::$feature()))
315                     }
316                 )*
317                 None
318             }
319         }
320     )
321 }
322 
323 macro_rules! stab {
324     (stable) => {
325         Status::Stable
326     };
327     (unstable) => {
328         Status::Unstable
329     };
330     (removed) => {
331         Status::Removed
332     };
333 }
334 
335 // A listing of all features in Cargo.
336 //
337 // "look here"
338 //
339 // This is the macro that lists all stable and unstable features in Cargo.
340 // You'll want to add to this macro whenever you add a feature to Cargo, also
341 // following the directions above.
342 //
343 // Note that all feature names here are valid Rust identifiers, but the `_`
344 // character is translated to `-` when specified in the `cargo-features`
345 // manifest entry in `Cargo.toml`.
346 features! {
347     // A dummy feature that doesn't actually gate anything, but it's used in
348     // testing to ensure that we can enable stable features.
349     (stable, test_dummy_stable, "1.0", ""),
350 
351     // A dummy feature that gates the usage of the `im-a-teapot` manifest
352     // entry. This is basically just intended for tests.
353     (unstable, test_dummy_unstable, "", "reference/unstable.html"),
354 
355     // Downloading packages from alternative registry indexes.
356     (stable, alternative_registries, "1.34", "reference/registries.html"),
357 
358     // Using editions
359     (stable, edition, "1.31", "reference/manifest.html#the-edition-field"),
360 
361     // Renaming a package in the manifest via the `package` key
362     (stable, rename_dependency, "1.31", "reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml"),
363 
364     // Whether a lock file is published with this crate
365     (removed, publish_lockfile, "", PUBLISH_LOCKFILE_REMOVED),
366 
367     // Overriding profiles for dependencies.
368     (stable, profile_overrides, "1.41", "reference/profiles.html#overrides"),
369 
370     // "default-run" manifest option,
371     (stable, default_run, "1.37", "reference/manifest.html#the-default-run-field"),
372 
373     // Declarative build scripts.
374     (unstable, metabuild, "", "reference/unstable.html#metabuild"),
375 
376     // Specifying the 'public' attribute on dependencies
377     (unstable, public_dependency, "", "reference/unstable.html#public-dependency"),
378 
379     // Allow to specify profiles other than 'dev', 'release', 'test', etc.
380     (unstable, named_profiles, "", "reference/unstable.html#custom-named-profiles"),
381 
382     // Opt-in new-resolver behavior.
383     (stable, resolver, "1.51", "reference/resolver.html#resolver-versions"),
384 
385     // Allow to specify whether binaries should be stripped.
386     (unstable, strip, "", "reference/unstable.html#profile-strip-option"),
387 
388     // Specifying a minimal 'rust-version' attribute for crates
389     (unstable, rust_version, "", "reference/unstable.html#rust-version"),
390 
391     // Support for 2021 edition.
392     (unstable, edition2021, "", "reference/unstable.html#edition-2021"),
393 
394     // Allow to specify per-package targets (compile kinds)
395     (unstable, per_package_target, "", "reference/unstable.html#per-package-target"),
396 }
397 
398 const PUBLISH_LOCKFILE_REMOVED: &str = "The publish-lockfile key in Cargo.toml \
399     has been removed. The Cargo.lock file is always included when a package is \
400     published if the package contains a binary target. `cargo install` requires \
401     the `--locked` flag to use the Cargo.lock file.\n\
402     See https://doc.rust-lang.org/cargo/commands/cargo-package.html and \
403     https://doc.rust-lang.org/cargo/commands/cargo-install.html for more \
404     information.";
405 
406 pub struct Feature {
407     name: &'static str,
408     stability: Status,
409     version: &'static str,
410     docs: &'static str,
411     get: fn(&Features) -> bool,
412 }
413 
414 impl Features {
new( features: &[String], config: &Config, warnings: &mut Vec<String>, ) -> CargoResult<Features>415     pub fn new(
416         features: &[String],
417         config: &Config,
418         warnings: &mut Vec<String>,
419     ) -> CargoResult<Features> {
420         let mut ret = Features::default();
421         ret.nightly_features_allowed = config.nightly_features_allowed;
422         for feature in features {
423             ret.add(feature, config, warnings)?;
424             ret.activated.push(feature.to_string());
425         }
426         Ok(ret)
427     }
428 
add( &mut self, feature_name: &str, config: &Config, warnings: &mut Vec<String>, ) -> CargoResult<()>429     fn add(
430         &mut self,
431         feature_name: &str,
432         config: &Config,
433         warnings: &mut Vec<String>,
434     ) -> CargoResult<()> {
435         let nightly_features_allowed = self.nightly_features_allowed;
436         let (slot, feature) = match self.status(feature_name) {
437             Some(p) => p,
438             None => bail!("unknown cargo feature `{}`", feature_name),
439         };
440 
441         if *slot {
442             bail!(
443                 "the cargo feature `{}` has already been activated",
444                 feature_name
445             );
446         }
447 
448         let see_docs = || {
449             let url_channel = match channel().as_str() {
450                 "dev" | "nightly" => "nightly/",
451                 "beta" => "beta/",
452                 _ => "",
453             };
454             format!(
455                 "See https://doc.rust-lang.org/{}cargo/{} for more information \
456                 about using this feature.",
457                 url_channel, feature.docs
458             )
459         };
460 
461         match feature.stability {
462             Status::Stable => {
463                 let warning = format!(
464                     "the cargo feature `{}` has been stabilized in the {} \
465                      release and is no longer necessary to be listed in the \
466                      manifest\n  {}",
467                     feature_name,
468                     feature.version,
469                     see_docs()
470                 );
471                 warnings.push(warning);
472             }
473             Status::Unstable if !nightly_features_allowed => bail!(
474                 "the cargo feature `{}` requires a nightly version of \
475                  Cargo, but this is the `{}` channel\n\
476                  {}\n{}",
477                 feature_name,
478                 channel(),
479                 SEE_CHANNELS,
480                 see_docs()
481             ),
482             Status::Unstable => {
483                 if let Some(allow) = &config.cli_unstable().allow_features {
484                     if !allow.contains(feature_name) {
485                         bail!(
486                             "the feature `{}` is not in the list of allowed features: [{}]",
487                             feature_name,
488                             iter_join(allow, ", "),
489                         );
490                     }
491                 }
492             }
493             Status::Removed => bail!(
494                 "the cargo feature `{}` has been removed\n\
495                 Remove the feature from Cargo.toml to remove this error.\n\
496                 {}",
497                 feature_name,
498                 feature.docs
499             ),
500         }
501 
502         *slot = true;
503 
504         Ok(())
505     }
506 
activated(&self) -> &[String]507     pub fn activated(&self) -> &[String] {
508         &self.activated
509     }
510 
require(&self, feature: &Feature) -> CargoResult<()>511     pub fn require(&self, feature: &Feature) -> CargoResult<()> {
512         if feature.is_enabled(self) {
513             Ok(())
514         } else {
515             let feature = feature.name.replace("_", "-");
516             let mut msg = format!("feature `{}` is required", feature);
517 
518             if self.nightly_features_allowed {
519                 let s = format!(
520                     "\n\nconsider adding `cargo-features = [\"{0}\"]` \
521                      to the manifest",
522                     feature
523                 );
524                 msg.push_str(&s);
525             } else {
526                 let s = format!(
527                     "\n\n\
528                      this Cargo does not support nightly features, but if you\n\
529                      switch to nightly channel you can add\n\
530                      `cargo-features = [\"{}\"]` to enable this feature",
531                     feature
532                 );
533                 msg.push_str(&s);
534             }
535             bail!("{}", msg);
536         }
537     }
538 
is_enabled(&self, feature: &Feature) -> bool539     pub fn is_enabled(&self, feature: &Feature) -> bool {
540         feature.is_enabled(self)
541     }
542 }
543 
544 macro_rules! unstable_cli_options {
545     (
546         $(
547             $(#[$meta:meta])?
548             $element: ident: $ty: ty = ($help: expr ),
549         )*
550     ) => {
551         /// A parsed representation of all unstable flags that Cargo accepts.
552         ///
553         /// Cargo, like `rustc`, accepts a suite of `-Z` flags which are intended for
554         /// gating unstable functionality to Cargo. These flags are only available on
555         /// the nightly channel of Cargo.
556         #[derive(Default, Debug, Deserialize)]
557         #[serde(default, rename_all = "kebab-case")]
558         pub struct CliUnstable {
559             $(
560                 $(#[$meta])?
561                 pub $element: $ty
562             ),*
563         }
564         impl CliUnstable {
565             pub fn help() -> Vec<(&'static str, &'static str)> {
566                 let fields = vec![$((stringify!($element), $help)),*];
567                 fields
568             }
569         }
570     }
571 }
572 
573 unstable_cli_options!(
574     // Permanently unstable features:
575     allow_features: Option<BTreeSet<String>> = ("Allow *only* the listed unstable features"),
576     print_im_a_teapot: bool= (HIDDEN),
577 
578     // All other unstable features.
579     // Please keep this list lexiographically ordered.
580     advanced_env: bool = (HIDDEN),
581     avoid_dev_deps: bool = ("Avoid installing dev-dependencies if possible"),
582     binary_dep_depinfo: bool = ("Track changes to dependency artifacts"),
583     #[serde(deserialize_with = "deserialize_build_std")]
584     build_std: Option<Vec<String>>  = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"),
585     build_std_features: Option<Vec<String>>  = ("Configure features enabled for the standard library itself when building the standard library"),
586     config_include: bool = ("Enable the `include` key in config files"),
587     configurable_env: bool = ("Enable the [env] section in the .cargo/config.toml file"),
588     credential_process: bool = ("Add a config setting to fetch registry authentication tokens by calling an external process"),
589     doctest_in_workspace: bool = ("Compile doctests with paths relative to the workspace root"),
590     doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"),
591     dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
592     future_incompat_report: bool = ("Enable creation of a future-incompat report for all dependencies"),
593     extra_link_arg: bool = ("Allow `cargo:rustc-link-arg` in build scripts"),
594     features: Option<Vec<String>>  = (HIDDEN),
595     jobserver_per_rustc: bool = (HIDDEN),
596     minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"),
597     mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"),
598     multitarget: bool = ("Allow passing multiple `--target` flags to the cargo subcommand selected"),
599     named_profiles: bool = ("Allow defining custom profiles"),
600     namespaced_features: bool = ("Allow features with `dep:` prefix"),
601     no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
602     panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
603     host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"),
604     target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
605     patch_in_config: bool = ("Allow `[patch]` sections in .cargo/config.toml files"),
606     rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
607     separate_nightlies: bool = (HIDDEN),
608     terminal_width: Option<Option<usize>>  = ("Provide a terminal width to rustc for error truncation"),
609     timings: Option<Vec<String>>  = ("Display concurrency information"),
610     unstable_options: bool = ("Allow the usage of unstable options"),
611     weak_dep_features: bool = ("Allow `dep_name?/feature` feature syntax"),
612     skip_rustdoc_fingerprint: bool = (HIDDEN),
613 );
614 
615 const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
616     enabled when used on an interactive console.\n\
617     See https://doc.rust-lang.org/cargo/reference/config.html#termprogresswhen \
618     for information on controlling the progress bar.";
619 
620 const STABILIZED_OFFLINE: &str = "Offline mode is now available via the \
621     --offline CLI option";
622 
623 const STABILIZED_CACHE_MESSAGES: &str = "Message caching is now always enabled.";
624 
625 const STABILIZED_INSTALL_UPGRADE: &str = "Packages are now always upgraded if \
626     they appear out of date.\n\
627     See https://doc.rust-lang.org/cargo/commands/cargo-install.html for more \
628     information on how upgrading works.";
629 
630 const STABILIZED_CONFIG_PROFILE: &str = "See \
631     https://doc.rust-lang.org/cargo/reference/config.html#profile for more \
632     information about specifying profiles in config.";
633 
634 const STABILIZED_CRATE_VERSIONS: &str = "The crate version is now \
635     automatically added to the documentation.";
636 
637 const STABILIZED_PACKAGE_FEATURES: &str = "Enhanced feature flag behavior is now \
638     available in virtual workspaces, and `member/feature-name` syntax is also \
639     always available. Other extensions require setting `resolver = \"2\"` in \
640     Cargo.toml.\n\
641     See https://doc.rust-lang.org/nightly/cargo/reference/features.html#resolver-version-2-command-line-flags \
642     for more information.";
643 
644 const STABILIZED_FEATURES: &str = "The new feature resolver is now available \
645     by specifying `resolver = \"2\"` in Cargo.toml.\n\
646     See https://doc.rust-lang.org/nightly/cargo/reference/features.html#feature-resolver-version-2 \
647     for more information.";
648 
deserialize_build_std<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error> where D: serde::Deserializer<'de>,649 fn deserialize_build_std<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
650 where
651     D: serde::Deserializer<'de>,
652 {
653     let crates = match <Option<Vec<String>>>::deserialize(deserializer)? {
654         Some(list) => list,
655         None => return Ok(None),
656     };
657     let v = crates.join(",");
658     Ok(Some(
659         crate::core::compiler::standard_lib::parse_unstable_flag(Some(&v)),
660     ))
661 }
662 
663 impl CliUnstable {
parse( &mut self, flags: &[String], nightly_features_allowed: bool, ) -> CargoResult<Vec<String>>664     pub fn parse(
665         &mut self,
666         flags: &[String],
667         nightly_features_allowed: bool,
668     ) -> CargoResult<Vec<String>> {
669         if !flags.is_empty() && !nightly_features_allowed {
670             bail!(
671                 "the `-Z` flag is only accepted on the nightly channel of Cargo, \
672                  but this is the `{}` channel\n\
673                  {}",
674                 channel(),
675                 SEE_CHANNELS
676             );
677         }
678         let mut warnings = Vec::new();
679         // We read flags twice, first to get allowed-features (if specified),
680         // and then to read the remaining unstable flags.
681         for flag in flags {
682             if flag.starts_with("allow-features=") {
683                 self.add(flag, &mut warnings)?;
684             }
685         }
686         for flag in flags {
687             self.add(flag, &mut warnings)?;
688         }
689         Ok(warnings)
690     }
691 
add(&mut self, flag: &str, warnings: &mut Vec<String>) -> CargoResult<()>692     fn add(&mut self, flag: &str, warnings: &mut Vec<String>) -> CargoResult<()> {
693         let mut parts = flag.splitn(2, '=');
694         let k = parts.next().unwrap();
695         let v = parts.next();
696 
697         fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> {
698             match value {
699                 None | Some("yes") => Ok(true),
700                 Some("no") => Ok(false),
701                 Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s),
702             }
703         }
704 
705         fn parse_timings(value: Option<&str>) -> Vec<String> {
706             match value {
707                 None => vec!["html".to_string(), "info".to_string()],
708                 Some(v) => v.split(',').map(|s| s.to_string()).collect(),
709             }
710         }
711 
712         fn parse_features(value: Option<&str>) -> Vec<String> {
713             match value {
714                 None => Vec::new(),
715                 Some("") => Vec::new(),
716                 Some(v) => v.split(',').map(|s| s.to_string()).collect(),
717             }
718         }
719 
720         // Asserts that there is no argument to the flag.
721         fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> {
722             if let Some(v) = value {
723                 bail!("flag -Z{} does not take a value, found: `{}`", key, v);
724             }
725             Ok(true)
726         }
727 
728         fn parse_usize_opt(value: Option<&str>) -> CargoResult<Option<usize>> {
729             Ok(match value {
730                 Some(value) => match value.parse::<usize>() {
731                     Ok(value) => Some(value),
732                     Err(e) => bail!("expected a number, found: {}", e),
733                 },
734                 None => None,
735             })
736         }
737 
738         let mut stabilized_warn = |key: &str, version: &str, message: &str| {
739             warnings.push(format!(
740                 "flag `-Z {}` has been stabilized in the {} release, \
741                  and is no longer necessary\n{}",
742                 key,
743                 version,
744                 indented_lines(message)
745             ));
746         };
747 
748         // Use this if the behavior now requires another mechanism to enable.
749         let stabilized_err = |key: &str, version: &str, message: &str| {
750             Err(anyhow::format_err!(
751                 "flag `-Z {}` has been stabilized in the {} release\n{}",
752                 key,
753                 version,
754                 indented_lines(message)
755             ))
756         };
757 
758         if let Some(allowed) = &self.allow_features {
759             if k != "allow-features" && !allowed.contains(k) {
760                 bail!(
761                     "the feature `{}` is not in the list of allowed features: [{}]",
762                     k,
763                     iter_join(allowed, ", ")
764                 );
765             }
766         }
767 
768         match k {
769             "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?,
770             "allow-features" => self.allow_features = Some(parse_features(v).into_iter().collect()),
771             "unstable-options" => self.unstable_options = parse_empty(k, v)?,
772             "no-index-update" => self.no_index_update = parse_empty(k, v)?,
773             "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?,
774             "minimal-versions" => self.minimal_versions = parse_empty(k, v)?,
775             "advanced-env" => self.advanced_env = parse_empty(k, v)?,
776             "config-include" => self.config_include = parse_empty(k, v)?,
777             "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
778             // can also be set in .cargo/config or with and ENV
779             "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,
780             "named-profiles" => self.named_profiles = parse_empty(k, v)?,
781             "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?,
782             "build-std" => {
783                 self.build_std = Some(crate::core::compiler::standard_lib::parse_unstable_flag(v))
784             }
785             "build-std-features" => self.build_std_features = Some(parse_features(v)),
786             "timings" => self.timings = Some(parse_timings(v)),
787             "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?,
788             "doctest-in-workspace" => self.doctest_in_workspace = parse_empty(k, v)?,
789             "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
790             "jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?,
791             "configurable-env" => self.configurable_env = parse_empty(k, v)?,
792             "host-config" => self.host_config = parse_empty(k, v)?,
793             "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?,
794             "patch-in-config" => self.patch_in_config = parse_empty(k, v)?,
795             "features" => {
796                 // For now this is still allowed (there are still some
797                 // unstable options like "compare"). This should be removed at
798                 // some point, and migrate to a new -Z flag for any future
799                 // things.
800                 let feats = parse_features(v);
801                 let stab: Vec<_> = feats
802                     .iter()
803                     .filter(|feat| {
804                         matches!(
805                             feat.as_str(),
806                             "build_dep" | "host_dep" | "dev_dep" | "itarget" | "all"
807                         )
808                     })
809                     .collect();
810                 if !stab.is_empty() || feats.is_empty() {
811                     // Make this stabilized_err once -Zfeature support is removed.
812                     stabilized_warn(k, "1.51", STABILIZED_FEATURES);
813                 }
814                 self.features = Some(feats);
815             }
816             "separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?,
817             "multitarget" => self.multitarget = parse_empty(k, v)?,
818             "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
819             "terminal-width" => self.terminal_width = Some(parse_usize_opt(v)?),
820             "namespaced-features" => self.namespaced_features = parse_empty(k, v)?,
821             "weak-dep-features" => self.weak_dep_features = parse_empty(k, v)?,
822             "extra-link-arg" => self.extra_link_arg = parse_empty(k, v)?,
823             "credential-process" => self.credential_process = parse_empty(k, v)?,
824             "skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(k, v)?,
825             "compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS),
826             "offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?,
827             "cache-messages" => stabilized_warn(k, "1.40", STABILIZED_CACHE_MESSAGES),
828             "install-upgrade" => stabilized_warn(k, "1.41", STABILIZED_INSTALL_UPGRADE),
829             "config-profile" => stabilized_warn(k, "1.43", STABILIZED_CONFIG_PROFILE),
830             "crate-versions" => stabilized_warn(k, "1.47", STABILIZED_CRATE_VERSIONS),
831             "package-features" => stabilized_warn(k, "1.51", STABILIZED_PACKAGE_FEATURES),
832             "future-incompat-report" => self.future_incompat_report = parse_empty(k, v)?,
833             _ => bail!("unknown `-Z` flag specified: {}", k),
834         }
835 
836         Ok(())
837     }
838 
839     /// Generates an error if `-Z unstable-options` was not used for a new,
840     /// unstable command-line flag.
fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()>841     pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> {
842         if !self.unstable_options {
843             let see = format!(
844                 "See https://github.com/rust-lang/cargo/issues/{} for more \
845                  information about the `{}` flag.",
846                 issue, flag
847             );
848             // NOTE: a `config` isn't available here, check the channel directly
849             let channel = channel();
850             if channel == "nightly" || channel == "dev" {
851                 bail!(
852                     "the `{}` flag is unstable, pass `-Z unstable-options` to enable it\n\
853                      {}",
854                     flag,
855                     see
856                 );
857             } else {
858                 bail!(
859                     "the `{}` flag is unstable, and only available on the nightly channel \
860                      of Cargo, but this is the `{}` channel\n\
861                      {}\n\
862                      {}",
863                     flag,
864                     channel,
865                     SEE_CHANNELS,
866                     see
867                 );
868             }
869         }
870         Ok(())
871     }
872 
873     /// Generates an error if `-Z unstable-options` was not used for a new,
874     /// unstable subcommand.
fail_if_stable_command( &self, config: &Config, command: &str, issue: u32, ) -> CargoResult<()>875     pub fn fail_if_stable_command(
876         &self,
877         config: &Config,
878         command: &str,
879         issue: u32,
880     ) -> CargoResult<()> {
881         if self.unstable_options {
882             return Ok(());
883         }
884         let see = format!(
885             "See https://github.com/rust-lang/cargo/issues/{} for more \
886             information about the `cargo {}` command.",
887             issue, command
888         );
889         if config.nightly_features_allowed {
890             bail!(
891                 "the `cargo {}` command is unstable, pass `-Z unstable-options` to enable it\n\
892                  {}",
893                 command,
894                 see
895             );
896         } else {
897             bail!(
898                 "the `cargo {}` command is unstable, and only available on the \
899                  nightly channel of Cargo, but this is the `{}` channel\n\
900                  {}\n\
901                  {}",
902                 command,
903                 channel(),
904                 SEE_CHANNELS,
905                 see
906             );
907         }
908     }
909 }
910 
911 /// Returns the current release channel ("stable", "beta", "nightly", "dev").
channel() -> String912 pub fn channel() -> String {
913     if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") {
914         return override_channel;
915     }
916     if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") {
917         if staging == "1" {
918             return "dev".to_string();
919         }
920     }
921     crate::version()
922         .cfg_info
923         .map(|c| c.release_channel)
924         .unwrap_or_else(|| String::from("dev"))
925 }
926