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