]>
Commit | Line | Data |
---|---|---|
3181ef24 AC |
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 | //! | |
71ef41a9 | 21 | //! 3. To actually perform the feature gate, you'll want to have code that looks |
3181ef24 AC |
22 | //! like: |
23 | //! | |
24 | //! ```rust,ignore | |
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 | |
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 | //! | |
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 | |
84130089 AC |
53 | use failure::Error; |
54 | ||
3181ef24 AC |
55 | use util::errors::CargoResult; |
56 | ||
3bbe93ce | 57 | /// The edition of the compiler (RFC 2052) |
1e682848 | 58 | #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)] |
3bbe93ce KN |
59 | pub enum Edition { |
60 | /// The 2015 edition | |
61 | Edition2015, | |
62 | /// The 2018 edition | |
63 | Edition2018, | |
1d82d2b3 MG |
64 | } |
65 | ||
3bbe93ce | 66 | impl fmt::Display for Edition { |
2d1af7b5 MG |
67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
68 | match *self { | |
3bbe93ce KN |
69 | Edition::Edition2015 => f.write_str("2015"), |
70 | Edition::Edition2018 => f.write_str("2018"), | |
2d1af7b5 MG |
71 | } |
72 | } | |
73 | } | |
3bbe93ce | 74 | impl FromStr for Edition { |
84130089 AC |
75 | type Err = Error; |
76 | fn from_str(s: &str) -> Result<Self, Error> { | |
1d82d2b3 | 77 | match s { |
3bbe93ce KN |
78 | "2015" => Ok(Edition::Edition2015), |
79 | "2018" => Ok(Edition::Edition2018), | |
84130089 AC |
80 | s => { |
81 | bail!("supported edition values are `2015` or `2018`, but `{}` \ | |
82 | is unknown", s) | |
83 | } | |
1d82d2b3 MG |
84 | } |
85 | } | |
86 | } | |
87 | ||
3d029039 | 88 | #[derive(PartialEq)] |
3181ef24 AC |
89 | enum Status { |
90 | Stable, | |
91 | Unstable, | |
92 | } | |
93 | ||
94 | macro_rules! features { | |
95 | ( | |
96 | pub struct Features { | |
97 | $([$stab:ident] $feature:ident: bool,)* | |
98 | } | |
99 | ) => ( | |
100 | #[derive(Default, Clone, Debug)] | |
101 | pub struct Features { | |
102 | $($feature: bool,)* | |
103 | activated: Vec<String>, | |
104 | } | |
105 | ||
106 | impl Feature { | |
107 | $( | |
108 | pub fn $feature() -> &'static Feature { | |
f26fc37d | 109 | fn get(features: &Features) -> bool { |
3d029039 | 110 | stab!($stab) == Status::Stable || features.$feature |
3181ef24 AC |
111 | } |
112 | static FEAT: Feature = Feature { | |
113 | name: stringify!($feature), | |
676edacf | 114 | get, |
3181ef24 AC |
115 | }; |
116 | &FEAT | |
117 | } | |
118 | )* | |
f26fc37d AC |
119 | |
120 | fn is_enabled(&self, features: &Features) -> bool { | |
121 | (self.get)(features) | |
122 | } | |
3181ef24 AC |
123 | } |
124 | ||
125 | impl Features { | |
126 | fn status(&mut self, feature: &str) -> Option<(&mut bool, Status)> { | |
127 | if feature.contains("_") { | |
128 | return None | |
129 | } | |
130 | let feature = feature.replace("-", "_"); | |
131 | $( | |
132 | if feature == stringify!($feature) { | |
133 | return Some((&mut self.$feature, stab!($stab))) | |
134 | } | |
135 | )* | |
136 | None | |
137 | } | |
138 | } | |
139 | ) | |
140 | } | |
141 | ||
142 | macro_rules! stab { | |
a4947c2b EH |
143 | (stable) => { |
144 | Status::Stable | |
145 | }; | |
146 | (unstable) => { | |
147 | Status::Unstable | |
148 | }; | |
3181ef24 AC |
149 | } |
150 | ||
151 | /// A listing of all features in Cargo | |
152 | /// | |
153 | /// "look here" | |
154 | /// | |
155 | /// This is the macro that lists all stable and unstable features in Cargo. | |
156 | /// You'll want to add to this macro whenever you add a feature to Cargo, also | |
157 | /// following the directions above. | |
158 | /// | |
159 | /// Note that all feature names here are valid Rust identifiers, but the `_` | |
160 | /// character is translated to `-` when specified in the `cargo-features` | |
161 | /// manifest entry in `Cargo.toml`. | |
162 | features! { | |
163 | pub struct Features { | |
164 | ||
165 | // A dummy feature that doesn't actually gate anything, but it's used in | |
166 | // testing to ensure that we can enable stable features. | |
167 | [stable] test_dummy_stable: bool, | |
168 | ||
169 | // A dummy feature that gates the usage of the `im-a-teapot` manifest | |
170 | // entry. This is basically just intended for tests. | |
171 | [unstable] test_dummy_unstable: bool, | |
d89cd903 WB |
172 | |
173 | // Downloading packages from alternative registry indexes. | |
174 | [unstable] alternative_registries: bool, | |
fa5be237 | 175 | |
3bbe93ce | 176 | // Using editions |
3d029039 | 177 | [stable] edition: bool, |
79942fea AC |
178 | |
179 | // Renaming a package in the manifest via the `package` key | |
180 | [unstable] rename_dependency: bool, | |
a4a3302d AC |
181 | |
182 | // Whether a lock file is published with this crate | |
183 | [unstable] publish_lockfile: bool, | |
575d6e81 EH |
184 | |
185 | // Overriding profiles for dependencies. | |
186 | [unstable] profile_overrides: bool, | |
0b6f4206 DO |
187 | |
188 | // Separating the namespaces for features and dependencies | |
189 | [unstable] namespaced_features: bool, | |
c955c60e RJ |
190 | |
191 | // "default-run" manifest option, | |
192 | [unstable] default_run: bool, | |
2be857af EH |
193 | |
194 | // Declarative build scripts. | |
195 | [unstable] metabuild: bool, | |
3181ef24 AC |
196 | } |
197 | } | |
198 | ||
199 | pub struct Feature { | |
200 | name: &'static str, | |
f26fc37d | 201 | get: fn(&Features) -> bool, |
3181ef24 AC |
202 | } |
203 | ||
204 | impl Features { | |
1e682848 | 205 | pub fn new(features: &[String], warnings: &mut Vec<String>) -> CargoResult<Features> { |
3181ef24 AC |
206 | let mut ret = Features::default(); |
207 | for feature in features { | |
208 | ret.add(feature, warnings)?; | |
209 | ret.activated.push(feature.to_string()); | |
210 | } | |
211 | Ok(ret) | |
212 | } | |
213 | ||
214 | fn add(&mut self, feature: &str, warnings: &mut Vec<String>) -> CargoResult<()> { | |
215 | let (slot, status) = match self.status(feature) { | |
216 | Some(p) => p, | |
217 | None => bail!("unknown cargo feature `{}`", feature), | |
218 | }; | |
219 | ||
220 | if *slot { | |
71ef41a9 | 221 | bail!("the cargo feature `{}` has already been activated", feature); |
3181ef24 AC |
222 | } |
223 | ||
224 | match status { | |
225 | Status::Stable => { | |
1e682848 AC |
226 | let warning = format!( |
227 | "the cargo feature `{}` is now stable \ | |
228 | and is no longer necessary to be listed \ | |
229 | in the manifest", | |
230 | feature | |
231 | ); | |
3181ef24 AC |
232 | warnings.push(warning); |
233 | } | |
1e682848 AC |
234 | Status::Unstable if !nightly_features_allowed() => bail!( |
235 | "the cargo feature `{}` requires a nightly version of \ | |
236 | Cargo, but this is the `{}` channel", | |
237 | feature, | |
238 | channel() | |
239 | ), | |
3181ef24 AC |
240 | Status::Unstable => {} |
241 | } | |
242 | ||
243 | *slot = true; | |
244 | ||
245 | Ok(()) | |
246 | } | |
247 | ||
248 | pub fn activated(&self) -> &[String] { | |
249 | &self.activated | |
250 | } | |
251 | ||
252 | pub fn require(&self, feature: &Feature) -> CargoResult<()> { | |
f26fc37d | 253 | if feature.is_enabled(self) { |
3181ef24 AC |
254 | Ok(()) |
255 | } else { | |
256 | let feature = feature.name.replace("_", "-"); | |
257 | let mut msg = format!("feature `{}` is required", feature); | |
258 | ||
259 | if nightly_features_allowed() { | |
1e682848 AC |
260 | let s = format!( |
261 | "\n\nconsider adding `cargo-features = [\"{0}\"]` \ | |
262 | to the manifest", | |
263 | feature | |
264 | ); | |
3181ef24 AC |
265 | msg.push_str(&s); |
266 | } else { | |
1e682848 AC |
267 | let s = format!( |
268 | "\n\n\ | |
269 | this Cargo does not support nightly features, but if you\n\ | |
270 | switch to nightly channel you can add\n\ | |
271 | `cargo-features = [\"{}\"]` to enable this feature", | |
272 | feature | |
273 | ); | |
3181ef24 AC |
274 | msg.push_str(&s); |
275 | } | |
276 | bail!("{}", msg); | |
277 | } | |
278 | } | |
2d1af7b5 MG |
279 | |
280 | pub fn is_enabled(&self, feature: &Feature) -> bool { | |
281 | feature.is_enabled(self) | |
282 | } | |
3181ef24 AC |
283 | } |
284 | ||
71ef41a9 | 285 | /// A parsed representation of all unstable flags that Cargo accepts. |
f26fc37d AC |
286 | /// |
287 | /// Cargo, like `rustc`, accepts a suite of `-Z` flags which are intended for | |
288 | /// gating unstable functionality to Cargo. These flags are only available on | |
289 | /// the nightly channel of Cargo. | |
290 | /// | |
291 | /// This struct doesn't have quite the same convenience macro that the features | |
292 | /// have above, but the procedure should still be relatively stable for adding a | |
293 | /// new unstable flag: | |
294 | /// | |
295 | /// 1. First, add a field to this `CliUnstable` structure. All flags are allowed | |
296 | /// to have a value as the `-Z` flags are either of the form `-Z foo` or | |
297 | /// `-Z foo=bar`, and it's up to you how to parse `bar`. | |
298 | /// | |
299 | /// 2. Add an arm to the match statement in `CliUnstable::add` below to match on | |
300 | /// your new flag. The key (`k`) is what you're matching on and the value is | |
301 | /// in `v`. | |
302 | /// | |
303 | /// 3. (optional) Add a new parsing function to parse your datatype. As of now | |
304 | /// there's an example for `bool`, but more can be added! | |
305 | /// | |
306 | /// 4. In Cargo use `config.cli_unstable()` to get a reference to this structure | |
307 | /// and then test for your flag or your value and act accordingly. | |
308 | /// | |
309 | /// If you have any trouble with this, please let us know! | |
310 | #[derive(Default, Debug)] | |
311 | pub struct CliUnstable { | |
312 | pub print_im_a_teapot: bool, | |
0f82507e | 313 | pub unstable_options: bool, |
ec5f78f9 | 314 | pub offline: bool, |
b83ef97e | 315 | pub no_index_update: bool, |
df5f7d68 | 316 | pub avoid_dev_deps: bool, |
9a098922 | 317 | pub minimal_versions: bool, |
d369f97c | 318 | pub package_features: bool, |
154c787c | 319 | pub advanced_env: bool, |
2f7b5225 | 320 | pub config_profile: bool, |
6ce90874 | 321 | pub compile_progress: bool, |
f26fc37d AC |
322 | } |
323 | ||
324 | impl CliUnstable { | |
325 | pub fn parse(&mut self, flags: &[String]) -> CargoResult<()> { | |
23591fe5 | 326 | if !flags.is_empty() && !nightly_features_allowed() { |
f26fc37d AC |
327 | bail!("the `-Z` flag is only accepted on the nightly channel of Cargo") |
328 | } | |
329 | for flag in flags { | |
330 | self.add(flag)?; | |
331 | } | |
332 | Ok(()) | |
333 | } | |
334 | ||
335 | fn add(&mut self, flag: &str) -> CargoResult<()> { | |
336 | let mut parts = flag.splitn(2, '='); | |
337 | let k = parts.next().unwrap(); | |
338 | let v = parts.next(); | |
339 | ||
340 | fn parse_bool(value: Option<&str>) -> CargoResult<bool> { | |
341 | match value { | |
1e682848 | 342 | None | Some("yes") => Ok(true), |
f26fc37d AC |
343 | Some("no") => Ok(false), |
344 | Some(s) => bail!("expected `no` or `yes`, found: {}", s), | |
345 | } | |
346 | } | |
347 | ||
348 | match k { | |
349 | "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(v)?, | |
0f82507e | 350 | "unstable-options" => self.unstable_options = true, |
ec5f78f9 | 351 | "offline" => self.offline = true, |
b83ef97e | 352 | "no-index-update" => self.no_index_update = true, |
df5f7d68 | 353 | "avoid-dev-deps" => self.avoid_dev_deps = true, |
9a098922 | 354 | "minimal-versions" => self.minimal_versions = true, |
d369f97c | 355 | "package-features" => self.package_features = true, |
154c787c | 356 | "advanced-env" => self.advanced_env = true, |
2f7b5225 | 357 | "config-profile" => self.config_profile = true, |
6ce90874 | 358 | "compile-progress" => self.compile_progress = true, |
f26fc37d AC |
359 | _ => bail!("unknown `-Z` flag specified: {}", k), |
360 | } | |
361 | ||
362 | Ok(()) | |
363 | } | |
364 | } | |
365 | ||
3181ef24 | 366 | fn channel() -> String { |
1bdb89d9 OS |
367 | if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") { |
368 | return override_channel; | |
369 | } | |
ac3cac44 OS |
370 | if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") { |
371 | if staging == "1" { | |
372 | return "dev".to_string(); | |
373 | } | |
374 | } | |
ac3cac44 OS |
375 | ::version() |
376 | .cfg_info | |
377 | .map(|c| c.release_channel) | |
378 | .unwrap_or_else(|| String::from("dev")) | |
3181ef24 AC |
379 | } |
380 | ||
75bb1906 E |
381 | thread_local!( |
382 | static NIGHTLY_FEATURES_ALLOWED: Cell<bool> = Cell::new(false); | |
383 | static ENABLE_NIGHTLY_FEATURES: Cell<bool> = Cell::new(false); | |
384 | ); | |
385 | ||
386 | /// This is a little complicated. | |
387 | /// This should return false if: | |
388 | /// - this is an artifact of the rustc distribution process for "stable" or for "beta" | |
389 | /// - this is an `#[test]` that does not opt in with `enable_nightly_features` | |
390 | /// - this is a integration test that uses `ProcessBuilder` | |
391 | /// that does not opt in with `masquerade_as_nightly_cargo` | |
392 | /// This should return true if: | |
393 | /// - this is an artifact of the rustc distribution process for "nightly" | |
394 | /// - this is being used in the rustc distribution process internally | |
395 | /// - this is a cargo executable that was built from source | |
396 | /// - this is an `#[test]` that called `enable_nightly_features` | |
397 | /// - this is a integration test that uses `ProcessBuilder` | |
398 | /// that called `masquerade_as_nightly_cargo` | |
d1372315 | 399 | pub fn nightly_features_allowed() -> bool { |
75bb1906 E |
400 | if ENABLE_NIGHTLY_FEATURES.with(|c| c.get()) { |
401 | return true | |
402 | } | |
403 | match &channel()[..] { | |
404 | "nightly" | "dev" => NIGHTLY_FEATURES_ALLOWED.with(|c| c.get()), | |
3181ef24 AC |
405 | _ => false, |
406 | } | |
407 | } | |
75bb1906 E |
408 | |
409 | /// Allows nightly features to be enabled for this thread, but only if the | |
410 | /// development channel is nightly or dev. | |
411 | /// | |
412 | /// Used by cargo main to ensure that a cargo build from source has nightly features | |
413 | pub fn maybe_allow_nightly_features() { | |
414 | NIGHTLY_FEATURES_ALLOWED.with(|c| c.set(true)); | |
415 | } | |
416 | ||
417 | /// Forcibly enables nightly features for this thread. | |
418 | /// | |
419 | /// Used by tests to allow the use of nightly features. | |
420 | pub fn enable_nightly_features() { | |
421 | ENABLE_NIGHTLY_FEATURES.with(|c| c.set(true)); | |
422 | } |