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! To do this you'll
10//! want to follow these steps:
11//!
12//! 1. Add your feature. Do this by searching for "look here" in this file and
13//!    expanding the macro invocation that lists all features with your new
14//!    feature.
15//!
16//! 2. Find the appropriate place to place the feature gate in Cargo itself. If
17//!    you're extending the manifest format you'll likely just want to modify
18//!    the `Manifest::feature_gate` function, but otherwise you may wish to
19//!    place the feature gate elsewhere in Cargo.
20//!
21//! 3. To actually perform the feature gate, you'll want to have code that looks
22//!    like:
23//!
24//! ```rust,compile_fail
25//! use core::{Feature, Features};
26//!
27//! let feature = Feature::launch_into_space();
28//! package.manifest().features().require(feature).chain_err(|| {
29//!     "launching Cargo into space right now is unstable and may result in \
30//!      unintended damage to your codebase, use with caution"
31//! })?;
32//! ```
33//!
34//! Notably you'll notice the `require` function called with your `Feature`, and
35//! then you use `chain_err` to tack on more context for why the feature was
36//! required when the feature isn't activated.
37//!
38//! 4. Update the unstable documentation at
39//!    `src/doc/src/reference/unstable.md` to include a short description of
40//!    how to use your new feature. When the feature is stabilized, be sure
41//!    that the Cargo Guide or Reference is updated to fully document the
42//!    feature and remove the entry from the Unstable section.
43//!
44//! And hopefully that's it! Bear with us though that this is, at the time of
45//! this writing, a very new feature in Cargo. If the process differs from this
46//! we'll be sure to update this documentation!
47
48use std::cell::Cell;
49use std::env;
50use std::fmt;
51use std::str::FromStr;
52
53use anyhow::{bail, Error};
54use serde::{Deserialize, Serialize};
55
56use crate::util::errors::CargoResult;
57
58pub const SEE_CHANNELS: &str =
59    "See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information \
60     about Rust release channels.";
61
62/// The edition of the compiler (RFC 2052)
63#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)]
64pub enum Edition {
65    /// The 2015 edition
66    Edition2015,
67    /// The 2018 edition
68    Edition2018,
69}
70
71impl fmt::Display for Edition {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match *self {
74            Edition::Edition2015 => f.write_str("2015"),
75            Edition::Edition2018 => f.write_str("2018"),
76        }
77    }
78}
79impl FromStr for Edition {
80    type Err = Error;
81    fn from_str(s: &str) -> Result<Self, Error> {
82        match s {
83            "2015" => Ok(Edition::Edition2015),
84            "2018" => Ok(Edition::Edition2018),
85            s => bail!(
86                "supported edition values are `2015` or `2018`, but `{}` \
87                 is unknown",
88                s
89            ),
90        }
91    }
92}
93
94#[derive(PartialEq)]
95enum Status {
96    Stable,
97    Unstable,
98}
99
100macro_rules! features {
101    (
102        pub struct Features {
103            $([$stab:ident] $feature:ident: bool,)*
104        }
105    ) => (
106        #[derive(Default, Clone, Debug)]
107        pub struct Features {
108            $($feature: bool,)*
109            activated: Vec<String>,
110        }
111
112        impl Feature {
113            $(
114                pub fn $feature() -> &'static Feature {
115                    fn get(features: &Features) -> bool {
116                        stab!($stab) == Status::Stable || features.$feature
117                    }
118                    static FEAT: Feature = Feature {
119                        name: stringify!($feature),
120                        get,
121                    };
122                    &FEAT
123                }
124            )*
125
126            fn is_enabled(&self, features: &Features) -> bool {
127                (self.get)(features)
128            }
129        }
130
131        impl Features {
132            fn status(&mut self, feature: &str) -> Option<(&mut bool, Status)> {
133                if feature.contains("_") {
134                    return None
135                }
136                let feature = feature.replace("-", "_");
137                $(
138                    if feature == stringify!($feature) {
139                        return Some((&mut self.$feature, stab!($stab)))
140                    }
141                )*
142                None
143            }
144        }
145    )
146}
147
148macro_rules! stab {
149    (stable) => {
150        Status::Stable
151    };
152    (unstable) => {
153        Status::Unstable
154    };
155}
156
157// A listing of all features in Cargo.
158//
159// "look here"
160//
161// This is the macro that lists all stable and unstable features in Cargo.
162// You'll want to add to this macro whenever you add a feature to Cargo, also
163// following the directions above.
164//
165// Note that all feature names here are valid Rust identifiers, but the `_`
166// character is translated to `-` when specified in the `cargo-features`
167// manifest entry in `Cargo.toml`.
168features! {
169    pub struct Features {
170
171        // A dummy feature that doesn't actually gate anything, but it's used in
172        // testing to ensure that we can enable stable features.
173        [stable] test_dummy_stable: bool,
174
175        // A dummy feature that gates the usage of the `im-a-teapot` manifest
176        // entry. This is basically just intended for tests.
177        [unstable] test_dummy_unstable: bool,
178
179        // Downloading packages from alternative registry indexes.
180        [stable] alternative_registries: bool,
181
182        // Using editions
183        [stable] edition: bool,
184
185        // Renaming a package in the manifest via the `package` key
186        [stable] rename_dependency: bool,
187
188        // Whether a lock file is published with this crate
189        // This is deprecated, and will likely be removed in a future version.
190        [unstable] publish_lockfile: bool,
191
192        // Overriding profiles for dependencies.
193        [stable] profile_overrides: bool,
194
195        // Separating the namespaces for features and dependencies
196        [unstable] namespaced_features: bool,
197
198        // "default-run" manifest option,
199        [stable] default_run: bool,
200
201        // Declarative build scripts.
202        [unstable] metabuild: bool,
203
204        // Specifying the 'public' attribute on dependencies
205        [unstable] public_dependency: bool,
206
207        // Allow to specify profiles other than 'dev', 'release', 'test', etc.
208        [unstable] named_profiles: bool,
209    }
210}
211
212pub struct Feature {
213    name: &'static str,
214    get: fn(&Features) -> bool,
215}
216
217impl Features {
218    pub fn new(features: &[String], warnings: &mut Vec<String>) -> CargoResult<Features> {
219        let mut ret = Features::default();
220        for feature in features {
221            ret.add(feature, warnings)?;
222            ret.activated.push(feature.to_string());
223        }
224        Ok(ret)
225    }
226
227    fn add(&mut self, feature: &str, warnings: &mut Vec<String>) -> CargoResult<()> {
228        let (slot, status) = match self.status(feature) {
229            Some(p) => p,
230            None => bail!("unknown cargo feature `{}`", feature),
231        };
232
233        if *slot {
234            bail!("the cargo feature `{}` has already been activated", feature);
235        }
236
237        match status {
238            Status::Stable => {
239                let warning = format!(
240                    "the cargo feature `{}` is now stable \
241                     and is no longer necessary to be listed \
242                     in the manifest",
243                    feature
244                );
245                warnings.push(warning);
246            }
247            Status::Unstable if !nightly_features_allowed() => bail!(
248                "the cargo feature `{}` requires a nightly version of \
249                 Cargo, but this is the `{}` channel\n\
250                 {}",
251                feature,
252                channel(),
253                SEE_CHANNELS
254            ),
255            Status::Unstable => {}
256        }
257
258        *slot = true;
259
260        Ok(())
261    }
262
263    pub fn activated(&self) -> &[String] {
264        &self.activated
265    }
266
267    pub fn require(&self, feature: &Feature) -> CargoResult<()> {
268        if feature.is_enabled(self) {
269            Ok(())
270        } else {
271            let feature = feature.name.replace("_", "-");
272            let mut msg = format!("feature `{}` is required", feature);
273
274            if nightly_features_allowed() {
275                let s = format!(
276                    "\n\nconsider adding `cargo-features = [\"{0}\"]` \
277                     to the manifest",
278                    feature
279                );
280                msg.push_str(&s);
281            } else {
282                let s = format!(
283                    "\n\n\
284                     this Cargo does not support nightly features, but if you\n\
285                     switch to nightly channel you can add\n\
286                     `cargo-features = [\"{}\"]` to enable this feature",
287                    feature
288                );
289                msg.push_str(&s);
290            }
291            bail!("{}", msg);
292        }
293    }
294
295    pub fn is_enabled(&self, feature: &Feature) -> bool {
296        feature.is_enabled(self)
297    }
298}
299
300/// A parsed representation of all unstable flags that Cargo accepts.
301///
302/// Cargo, like `rustc`, accepts a suite of `-Z` flags which are intended for
303/// gating unstable functionality to Cargo. These flags are only available on
304/// the nightly channel of Cargo.
305///
306/// This struct doesn't have quite the same convenience macro that the features
307/// have above, but the procedure should still be relatively stable for adding a
308/// new unstable flag:
309///
310/// 1. First, add a field to this `CliUnstable` structure. All flags are allowed
311///    to have a value as the `-Z` flags are either of the form `-Z foo` or
312///    `-Z foo=bar`, and it's up to you how to parse `bar`.
313///
314/// 2. Add an arm to the match statement in `CliUnstable::add` below to match on
315///    your new flag. The key (`k`) is what you're matching on and the value is
316///    in `v`.
317///
318/// 3. (optional) Add a new parsing function to parse your datatype. As of now
319///    there's an example for `bool`, but more can be added!
320///
321/// 4. In Cargo use `config.cli_unstable()` to get a reference to this structure
322///    and then test for your flag or your value and act accordingly.
323///
324/// If you have any trouble with this, please let us know!
325#[derive(Default, Debug)]
326pub struct CliUnstable {
327    pub print_im_a_teapot: bool,
328    pub unstable_options: bool,
329    pub no_index_update: bool,
330    pub avoid_dev_deps: bool,
331    pub minimal_versions: bool,
332    pub package_features: bool,
333    pub advanced_env: bool,
334    pub config_include: bool,
335    pub dual_proc_macros: bool,
336    pub mtime_on_use: bool,
337    pub named_profiles: bool,
338    pub binary_dep_depinfo: bool,
339    pub build_std: Option<Vec<String>>,
340    pub timings: Option<Vec<String>>,
341    pub doctest_xcompile: bool,
342    pub panic_abort_tests: bool,
343    pub jobserver_per_rustc: bool,
344    pub features: Option<Vec<String>>,
345    pub crate_versions: bool,
346}
347
348impl CliUnstable {
349    pub fn parse(&mut self, flags: &[String]) -> CargoResult<()> {
350        if !flags.is_empty() && !nightly_features_allowed() {
351            bail!(
352                "the `-Z` flag is only accepted on the nightly channel of Cargo, \
353                 but this is the `{}` channel\n\
354                 {}",
355                channel(),
356                SEE_CHANNELS
357            );
358        }
359        for flag in flags {
360            self.add(flag)?;
361        }
362        Ok(())
363    }
364
365    fn add(&mut self, flag: &str) -> CargoResult<()> {
366        let mut parts = flag.splitn(2, '=');
367        let k = parts.next().unwrap();
368        let v = parts.next();
369
370        fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> {
371            match value {
372                None | Some("yes") => Ok(true),
373                Some("no") => Ok(false),
374                Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s),
375            }
376        }
377
378        fn parse_timings(value: Option<&str>) -> Vec<String> {
379            match value {
380                None => vec!["html".to_string(), "info".to_string()],
381                Some(v) => v.split(',').map(|s| s.to_string()).collect(),
382            }
383        }
384
385        fn parse_features(value: Option<&str>) -> Vec<String> {
386            match value {
387                None => Vec::new(),
388                Some(v) => v.split(',').map(|s| s.to_string()).collect(),
389            }
390        }
391
392        // Asserts that there is no argument to the flag.
393        fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> {
394            if let Some(v) = value {
395                bail!("flag -Z{} does not take a value, found: `{}`", key, v);
396            }
397            Ok(true)
398        };
399
400        match k {
401            "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?,
402            "unstable-options" => self.unstable_options = parse_empty(k, v)?,
403            "no-index-update" => self.no_index_update = parse_empty(k, v)?,
404            "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?,
405            "minimal-versions" => self.minimal_versions = parse_empty(k, v)?,
406            "package-features" => self.package_features = parse_empty(k, v)?,
407            "advanced-env" => self.advanced_env = parse_empty(k, v)?,
408            "config-include" => self.config_include = parse_empty(k, v)?,
409            "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
410            // can also be set in .cargo/config or with and ENV
411            "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,
412            "named-profiles" => self.named_profiles = parse_empty(k, v)?,
413            "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?,
414            "build-std" => {
415                self.build_std = Some(crate::core::compiler::standard_lib::parse_unstable_flag(v))
416            }
417            "timings" => self.timings = Some(parse_timings(v)),
418            "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?,
419            "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
420            "jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?,
421            "features" => self.features = Some(parse_features(v)),
422            "crate-versions" => self.crate_versions = parse_empty(k, v)?,
423            _ => bail!("unknown `-Z` flag specified: {}", k),
424        }
425
426        Ok(())
427    }
428
429    /// Generates an error if `-Z unstable-options` was not used.
430    /// Intended to be used when a user passes a command-line flag that
431    /// requires `-Z unstable-options`.
432    pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> {
433        if !self.unstable_options {
434            let see = format!(
435                "See https://github.com/rust-lang/cargo/issues/{} for more \
436                 information about the `{}` flag.",
437                issue, flag
438            );
439            if nightly_features_allowed() {
440                bail!(
441                    "the `{}` flag is unstable, pass `-Z unstable-options` to enable it\n\
442                     {}",
443                    flag,
444                    see
445                );
446            } else {
447                bail!(
448                    "the `{}` flag is unstable, and only available on the nightly channel \
449                     of Cargo, but this is the `{}` channel\n\
450                     {}\n\
451                     {}",
452                    flag,
453                    channel(),
454                    SEE_CHANNELS,
455                    see
456                );
457            }
458        }
459        Ok(())
460    }
461}
462
463/// Returns the current release channel ("stable", "beta", "nightly", "dev").
464pub fn channel() -> String {
465    if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") {
466        return override_channel;
467    }
468    if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") {
469        if staging == "1" {
470            return "dev".to_string();
471        }
472    }
473    crate::version()
474        .cfg_info
475        .map(|c| c.release_channel)
476        .unwrap_or_else(|| String::from("dev"))
477}
478
479thread_local!(
480    static NIGHTLY_FEATURES_ALLOWED: Cell<bool> = Cell::new(false);
481    static ENABLE_NIGHTLY_FEATURES: Cell<bool> = Cell::new(false);
482);
483
484/// This is a little complicated.
485/// This should return false if:
486/// - this is an artifact of the rustc distribution process for "stable" or for "beta"
487/// - this is an `#[test]` that does not opt in with `enable_nightly_features`
488/// - this is a integration test that uses `ProcessBuilder`
489///      that does not opt in with `masquerade_as_nightly_cargo`
490/// This should return true if:
491/// - this is an artifact of the rustc distribution process for "nightly"
492/// - this is being used in the rustc distribution process internally
493/// - this is a cargo executable that was built from source
494/// - this is an `#[test]` that called `enable_nightly_features`
495/// - this is a integration test that uses `ProcessBuilder`
496///       that called `masquerade_as_nightly_cargo`
497pub fn nightly_features_allowed() -> bool {
498    if ENABLE_NIGHTLY_FEATURES.with(|c| c.get()) {
499        return true;
500    }
501    match &channel()[..] {
502        "nightly" | "dev" => NIGHTLY_FEATURES_ALLOWED.with(|c| c.get()),
503        _ => false,
504    }
505}
506
507/// Allows nightly features to be enabled for this thread, but only if the
508/// development channel is nightly or dev.
509///
510/// Used by cargo main to ensure that a cargo build from source has nightly features
511pub fn maybe_allow_nightly_features() {
512    NIGHTLY_FEATURES_ALLOWED.with(|c| c.set(true));
513}
514
515/// Forcibly enables nightly features for this thread.
516///
517/// Used by tests to allow the use of nightly features.
518pub fn enable_nightly_features() {
519    ENABLE_NIGHTLY_FEATURES.with(|c| c.set(true));
520}