]>
Commit | Line | Data |
---|---|---|
f7c91ba6 | 1 | //! Support for nightly features in Cargo itself. |
3181ef24 AC |
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 | //! | |
71ef41a9 | 21 | //! 3. To actually perform the feature gate, you'll want to have code that looks |
3181ef24 AC |
22 | //! like: |
23 | //! | |
ca176eed | 24 | //! ```rust,compile_fail |
3181ef24 AC |
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 | //! | |
d89cd903 | 34 | //! Notably you'll notice the `require` function called with your `Feature`, and |
3181ef24 AC |
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 | //! | |
740525f2 EH |
38 | //! 4. Update the unstable documentation at |
39 | //! `src/doc/src/reference/unstable.md` to include a short description of | |
f7c91ba6 | 40 | //! how to use your new feature. When the feature is stabilized, be sure |
740525f2 EH |
41 | //! that the Cargo Guide or Reference is updated to fully document the |
42 | //! feature and remove the entry from the Unstable section. | |
43 | //! | |
3181ef24 AC |
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 | ||
75bb1906 | 48 | use std::cell::Cell; |
3181ef24 | 49 | use std::env; |
2d1af7b5 | 50 | use std::fmt; |
1d82d2b3 | 51 | use std::str::FromStr; |
3181ef24 | 52 | |
3a18c89a | 53 | use anyhow::{bail, Error}; |
9ed82b57 | 54 | use serde::{Deserialize, Serialize}; |
84130089 | 55 | |
04ddd4d0 | 56 | use crate::util::errors::CargoResult; |
3181ef24 | 57 | |
35b843ef EH |
58 | pub 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 | ||
3bbe93ce | 62 | /// The edition of the compiler (RFC 2052) |
1e682848 | 63 | #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)] |
3bbe93ce KN |
64 | pub enum Edition { |
65 | /// The 2015 edition | |
66 | Edition2015, | |
67 | /// The 2018 edition | |
68 | Edition2018, | |
1d82d2b3 MG |
69 | } |
70 | ||
3bbe93ce | 71 | impl fmt::Display for Edition { |
b8b7faee | 72 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
2d1af7b5 | 73 | match *self { |
3bbe93ce KN |
74 | Edition::Edition2015 => f.write_str("2015"), |
75 | Edition::Edition2018 => f.write_str("2018"), | |
2d1af7b5 MG |
76 | } |
77 | } | |
78 | } | |
3bbe93ce | 79 | impl FromStr for Edition { |
84130089 AC |
80 | type Err = Error; |
81 | fn from_str(s: &str) -> Result<Self, Error> { | |
1d82d2b3 | 82 | match s { |
3bbe93ce KN |
83 | "2015" => Ok(Edition::Edition2015), |
84 | "2018" => Ok(Edition::Edition2018), | |
50db59e0 | 85 | s => bail!( |
fecb7246 AC |
86 | "supported edition values are `2015` or `2018`, but `{}` \ |
87 | is unknown", | |
88 | s | |
89 | ), | |
1d82d2b3 MG |
90 | } |
91 | } | |
92 | } | |
93 | ||
3d029039 | 94 | #[derive(PartialEq)] |
3181ef24 AC |
95 | enum Status { |
96 | Stable, | |
97 | Unstable, | |
98 | } | |
99 | ||
100 | macro_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 { | |
f26fc37d | 115 | fn get(features: &Features) -> bool { |
3d029039 | 116 | stab!($stab) == Status::Stable || features.$feature |
3181ef24 AC |
117 | } |
118 | static FEAT: Feature = Feature { | |
119 | name: stringify!($feature), | |
676edacf | 120 | get, |
3181ef24 AC |
121 | }; |
122 | &FEAT | |
123 | } | |
124 | )* | |
f26fc37d AC |
125 | |
126 | fn is_enabled(&self, features: &Features) -> bool { | |
127 | (self.get)(features) | |
128 | } | |
3181ef24 AC |
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 | ||
148 | macro_rules! stab { | |
a4947c2b EH |
149 | (stable) => { |
150 | Status::Stable | |
151 | }; | |
152 | (unstable) => { | |
153 | Status::Unstable | |
154 | }; | |
3181ef24 AC |
155 | } |
156 | ||
f5723e51 AR |
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`. | |
3181ef24 AC |
168 | features! { |
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, | |
d89cd903 WB |
178 | |
179 | // Downloading packages from alternative registry indexes. | |
737382d7 | 180 | [stable] alternative_registries: bool, |
fa5be237 | 181 | |
3bbe93ce | 182 | // Using editions |
3d029039 | 183 | [stable] edition: bool, |
79942fea AC |
184 | |
185 | // Renaming a package in the manifest via the `package` key | |
1533a049 | 186 | [stable] rename_dependency: bool, |
a4a3302d AC |
187 | |
188 | // Whether a lock file is published with this crate | |
34307c61 | 189 | // This is deprecated, and will likely be removed in a future version. |
a4a3302d | 190 | [unstable] publish_lockfile: bool, |
575d6e81 EH |
191 | |
192 | // Overriding profiles for dependencies. | |
dda81d31 | 193 | [stable] profile_overrides: bool, |
0b6f4206 DO |
194 | |
195 | // Separating the namespaces for features and dependencies | |
196 | [unstable] namespaced_features: bool, | |
c955c60e RJ |
197 | |
198 | // "default-run" manifest option, | |
7d7fe679 | 199 | [stable] default_run: bool, |
2be857af EH |
200 | |
201 | // Declarative build scripts. | |
202 | [unstable] metabuild: bool, | |
87f1a4b2 AH |
203 | |
204 | // Specifying the 'public' attribute on dependencies | |
205 | [unstable] public_dependency: bool, | |
756ab7d5 DA |
206 | |
207 | // Allow to specify profiles other than 'dev', 'release', 'test', etc. | |
208 | [unstable] named_profiles: bool, | |
3181ef24 AC |
209 | } |
210 | } | |
211 | ||
212 | pub struct Feature { | |
213 | name: &'static str, | |
f26fc37d | 214 | get: fn(&Features) -> bool, |
3181ef24 AC |
215 | } |
216 | ||
217 | impl Features { | |
1e682848 | 218 | pub fn new(features: &[String], warnings: &mut Vec<String>) -> CargoResult<Features> { |
3181ef24 AC |
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, | |
50db59e0 | 230 | None => bail!("unknown cargo feature `{}`", feature), |
3181ef24 AC |
231 | }; |
232 | ||
233 | if *slot { | |
50db59e0 | 234 | bail!("the cargo feature `{}` has already been activated", feature); |
3181ef24 AC |
235 | } |
236 | ||
237 | match status { | |
238 | Status::Stable => { | |
1e682848 AC |
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 | ); | |
3181ef24 AC |
245 | warnings.push(warning); |
246 | } | |
50db59e0 | 247 | Status::Unstable if !nightly_features_allowed() => bail!( |
1e682848 | 248 | "the cargo feature `{}` requires a nightly version of \ |
35b843ef EH |
249 | Cargo, but this is the `{}` channel\n\ |
250 | {}", | |
1e682848 | 251 | feature, |
35b843ef EH |
252 | channel(), |
253 | SEE_CHANNELS | |
1e682848 | 254 | ), |
3181ef24 AC |
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<()> { | |
f26fc37d | 268 | if feature.is_enabled(self) { |
3181ef24 AC |
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() { | |
1e682848 AC |
275 | let s = format!( |
276 | "\n\nconsider adding `cargo-features = [\"{0}\"]` \ | |
277 | to the manifest", | |
278 | feature | |
279 | ); | |
3181ef24 AC |
280 | msg.push_str(&s); |
281 | } else { | |
1e682848 AC |
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 | ); | |
3181ef24 AC |
289 | msg.push_str(&s); |
290 | } | |
50db59e0 | 291 | bail!("{}", msg); |
3181ef24 AC |
292 | } |
293 | } | |
2d1af7b5 MG |
294 | |
295 | pub fn is_enabled(&self, feature: &Feature) -> bool { | |
296 | feature.is_enabled(self) | |
297 | } | |
3181ef24 AC |
298 | } |
299 | ||
71ef41a9 | 300 | /// A parsed representation of all unstable flags that Cargo accepts. |
f26fc37d AC |
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)] | |
326 | pub struct CliUnstable { | |
327 | pub print_im_a_teapot: bool, | |
0f82507e | 328 | pub unstable_options: bool, |
b83ef97e | 329 | pub no_index_update: bool, |
df5f7d68 | 330 | pub avoid_dev_deps: bool, |
9a098922 | 331 | pub minimal_versions: bool, |
d369f97c | 332 | pub package_features: bool, |
154c787c | 333 | pub advanced_env: bool, |
e7eda2f9 | 334 | pub config_include: bool, |
6814c203 | 335 | pub dual_proc_macros: bool, |
5f6ede29 | 336 | pub mtime_on_use: bool, |
756ab7d5 | 337 | pub named_profiles: bool, |
c6e626b3 | 338 | pub binary_dep_depinfo: bool, |
1f14fa31 | 339 | pub build_std: Option<Vec<String>>, |
06644845 | 340 | pub timings: Option<Vec<String>>, |
a5235b7b | 341 | pub doctest_xcompile: bool, |
f37f3aea | 342 | pub panic_abort_tests: bool, |
494b3c0a | 343 | pub jobserver_per_rustc: bool, |
f26fc37d AC |
344 | } |
345 | ||
346 | impl CliUnstable { | |
347 | pub fn parse(&mut self, flags: &[String]) -> CargoResult<()> { | |
23591fe5 | 348 | if !flags.is_empty() && !nightly_features_allowed() { |
50db59e0 | 349 | bail!( |
35b843ef EH |
350 | "the `-Z` flag is only accepted on the nightly channel of Cargo, \ |
351 | but this is the `{}` channel\n\ | |
352 | {}", | |
353 | channel(), | |
354 | SEE_CHANNELS | |
355 | ); | |
f26fc37d AC |
356 | } |
357 | for flag in flags { | |
358 | self.add(flag)?; | |
359 | } | |
360 | Ok(()) | |
361 | } | |
362 | ||
363 | fn add(&mut self, flag: &str) -> CargoResult<()> { | |
364 | let mut parts = flag.splitn(2, '='); | |
365 | let k = parts.next().unwrap(); | |
366 | let v = parts.next(); | |
367 | ||
50db59e0 | 368 | fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> { |
f26fc37d | 369 | match value { |
1e682848 | 370 | None | Some("yes") => Ok(true), |
f26fc37d | 371 | Some("no") => Ok(false), |
50db59e0 | 372 | Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s), |
f26fc37d AC |
373 | } |
374 | } | |
375 | ||
06644845 EH |
376 | fn parse_timings(value: Option<&str>) -> Vec<String> { |
377 | match value { | |
378 | None => vec!["html".to_string(), "info".to_string()], | |
379 | Some(v) => v.split(',').map(|s| s.to_string()).collect(), | |
380 | } | |
381 | } | |
382 | ||
50db59e0 EH |
383 | // Asserts that there is no argument to the flag. |
384 | fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> { | |
385 | if let Some(v) = value { | |
386 | bail!("flag -Z{} does not take a value, found: `{}`", key, v); | |
387 | } | |
388 | Ok(true) | |
389 | }; | |
390 | ||
f26fc37d | 391 | match k { |
50db59e0 EH |
392 | "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?, |
393 | "unstable-options" => self.unstable_options = parse_empty(k, v)?, | |
394 | "no-index-update" => self.no_index_update = parse_empty(k, v)?, | |
395 | "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?, | |
396 | "minimal-versions" => self.minimal_versions = parse_empty(k, v)?, | |
397 | "package-features" => self.package_features = parse_empty(k, v)?, | |
398 | "advanced-env" => self.advanced_env = parse_empty(k, v)?, | |
e7eda2f9 | 399 | "config-include" => self.config_include = parse_empty(k, v)?, |
50db59e0 | 400 | "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?, |
30df7317 | 401 | // can also be set in .cargo/config or with and ENV |
50db59e0 | 402 | "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?, |
50db59e0 EH |
403 | "named-profiles" => self.named_profiles = parse_empty(k, v)?, |
404 | "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?, | |
1f14fa31 EH |
405 | "build-std" => { |
406 | self.build_std = Some(crate::core::compiler::standard_lib::parse_unstable_flag(v)) | |
407 | } | |
06644845 | 408 | "timings" => self.timings = Some(parse_timings(v)), |
50db59e0 EH |
409 | "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?, |
410 | "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, | |
494b3c0a | 411 | "jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?, |
50db59e0 | 412 | _ => bail!("unknown `-Z` flag specified: {}", k), |
f26fc37d AC |
413 | } |
414 | ||
415 | Ok(()) | |
416 | } | |
35b843ef EH |
417 | |
418 | /// Generates an error if `-Z unstable-options` was not used. | |
419 | /// Intended to be used when a user passes a command-line flag that | |
420 | /// requires `-Z unstable-options`. | |
421 | pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> { | |
422 | if !self.unstable_options { | |
423 | let see = format!( | |
424 | "See https://github.com/rust-lang/cargo/issues/{} for more \ | |
425 | information about the `{}` flag.", | |
426 | issue, flag | |
427 | ); | |
428 | if nightly_features_allowed() { | |
50db59e0 | 429 | bail!( |
35b843ef EH |
430 | "the `{}` flag is unstable, pass `-Z unstable-options` to enable it\n\ |
431 | {}", | |
432 | flag, | |
433 | see | |
434 | ); | |
435 | } else { | |
50db59e0 | 436 | bail!( |
35b843ef EH |
437 | "the `{}` flag is unstable, and only available on the nightly channel \ |
438 | of Cargo, but this is the `{}` channel\n\ | |
439 | {}\n\ | |
440 | {}", | |
441 | flag, | |
442 | channel(), | |
443 | SEE_CHANNELS, | |
444 | see | |
445 | ); | |
446 | } | |
447 | } | |
448 | Ok(()) | |
449 | } | |
f26fc37d AC |
450 | } |
451 | ||
35b843ef EH |
452 | /// Returns the current release channel ("stable", "beta", "nightly", "dev"). |
453 | pub fn channel() -> String { | |
1bdb89d9 OS |
454 | if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") { |
455 | return override_channel; | |
456 | } | |
ac3cac44 OS |
457 | if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") { |
458 | if staging == "1" { | |
459 | return "dev".to_string(); | |
460 | } | |
461 | } | |
04ddd4d0 | 462 | crate::version() |
ac3cac44 OS |
463 | .cfg_info |
464 | .map(|c| c.release_channel) | |
465 | .unwrap_or_else(|| String::from("dev")) | |
3181ef24 AC |
466 | } |
467 | ||
75bb1906 E |
468 | thread_local!( |
469 | static NIGHTLY_FEATURES_ALLOWED: Cell<bool> = Cell::new(false); | |
470 | static ENABLE_NIGHTLY_FEATURES: Cell<bool> = Cell::new(false); | |
471 | ); | |
472 | ||
473 | /// This is a little complicated. | |
474 | /// This should return false if: | |
475 | /// - this is an artifact of the rustc distribution process for "stable" or for "beta" | |
476 | /// - this is an `#[test]` that does not opt in with `enable_nightly_features` | |
477 | /// - this is a integration test that uses `ProcessBuilder` | |
478 | /// that does not opt in with `masquerade_as_nightly_cargo` | |
479 | /// This should return true if: | |
480 | /// - this is an artifact of the rustc distribution process for "nightly" | |
481 | /// - this is being used in the rustc distribution process internally | |
482 | /// - this is a cargo executable that was built from source | |
483 | /// - this is an `#[test]` that called `enable_nightly_features` | |
484 | /// - this is a integration test that uses `ProcessBuilder` | |
485 | /// that called `masquerade_as_nightly_cargo` | |
d1372315 | 486 | pub fn nightly_features_allowed() -> bool { |
75bb1906 | 487 | if ENABLE_NIGHTLY_FEATURES.with(|c| c.get()) { |
fecb7246 | 488 | return true; |
75bb1906 | 489 | } |
fecb7246 | 490 | match &channel()[..] { |
75bb1906 | 491 | "nightly" | "dev" => NIGHTLY_FEATURES_ALLOWED.with(|c| c.get()), |
3181ef24 AC |
492 | _ => false, |
493 | } | |
494 | } | |
75bb1906 E |
495 | |
496 | /// Allows nightly features to be enabled for this thread, but only if the | |
497 | /// development channel is nightly or dev. | |
498 | /// | |
499 | /// Used by cargo main to ensure that a cargo build from source has nightly features | |
500 | pub fn maybe_allow_nightly_features() { | |
501 | NIGHTLY_FEATURES_ALLOWED.with(|c| c.set(true)); | |
502 | } | |
503 | ||
504 | /// Forcibly enables nightly features for this thread. | |
505 | /// | |
506 | /// Used by tests to allow the use of nightly features. | |
507 | pub fn enable_nightly_features() { | |
508 | ENABLE_NIGHTLY_FEATURES.with(|c| c.set(true)); | |
509 | } |