]>
Commit | Line | Data |
---|---|---|
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, and include the version and a URL for the documentation. | |
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. You can | |
90 | //! quickly find what needs to be removed by searching for the name | |
91 | //! of the feature, e.g. `print_im_a_teapot` | |
92 | //! 3. Update the docs in unstable.md to move the section to the bottom | |
93 | //! and summarize it similar to the other entries. Update the rest of the | |
94 | //! documentation to add the new feature. | |
95 | ||
96 | use std::collections::BTreeSet; | |
97 | use std::env; | |
98 | use std::fmt::{self, Write}; | |
99 | use std::str::FromStr; | |
100 | ||
101 | use anyhow::{bail, Error}; | |
102 | use cargo_util::ProcessBuilder; | |
103 | use serde::{Deserialize, Serialize}; | |
104 | ||
105 | use crate::core::resolver::ResolveBehavior; | |
106 | use crate::util::errors::CargoResult; | |
107 | use crate::util::{indented_lines, iter_join}; | |
108 | use crate::Config; | |
109 | ||
110 | pub const HIDDEN: &str = ""; | |
111 | pub const SEE_CHANNELS: &str = | |
112 | "See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information \ | |
113 | about Rust release channels."; | |
114 | ||
115 | /// The edition of the compiler (RFC 2052) | |
116 | #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)] | |
117 | pub enum Edition { | |
118 | /// The 2015 edition | |
119 | Edition2015, | |
120 | /// The 2018 edition | |
121 | Edition2018, | |
122 | /// The 2021 edition | |
123 | Edition2021, | |
124 | } | |
125 | ||
126 | // Adding a new edition: | |
127 | // - Add the next edition to the enum. | |
128 | // - Update every match expression that now fails to compile. | |
129 | // - Update the `FromStr` impl. | |
130 | // - Update CLI_VALUES to include the new edition. | |
131 | // - Set LATEST_UNSTABLE to Some with the new edition. | |
132 | // - Add an unstable feature to the features! macro below for the new edition. | |
133 | // - Gate on that new feature in TomlManifest::to_real_manifest. | |
134 | // - Update the shell completion files. | |
135 | // - Update any failing tests (hopefully there are very few). | |
136 | // - Update unstable.md to add a new section for this new edition (see | |
137 | // https://github.com/rust-lang/cargo/blob/3ebb5f15a940810f250b68821149387af583a79e/src/doc/src/reference/unstable.md?plain=1#L1238-L1264 | |
138 | // as an example). | |
139 | // | |
140 | // Stabilization instructions: | |
141 | // - Set LATEST_UNSTABLE to None. | |
142 | // - Set LATEST_STABLE to the new version. | |
143 | // - Update `is_stable` to `true`. | |
144 | // - Set the editionNNNN feature to stable in the features macro below. | |
145 | // - Update any tests that are affected. | |
146 | // - Update the man page for the --edition flag. | |
147 | // - Update unstable.md to move the edition section to the bottom. | |
148 | // - Update the documentation: | |
149 | // - Update any features impacted by the edition. | |
150 | // - Update manifest.md#the-edition-field. | |
151 | // - Update the --edition flag (options-new.md). | |
152 | // - Rebuild man pages. | |
153 | impl Edition { | |
154 | /// The latest edition that is unstable. | |
155 | /// | |
156 | /// This is `None` if there is no next unstable edition. | |
157 | pub const LATEST_UNSTABLE: Option<Edition> = None; | |
158 | /// The latest stable edition. | |
159 | pub const LATEST_STABLE: Edition = Edition::Edition2021; | |
160 | /// Possible values allowed for the `--edition` CLI flag. | |
161 | /// | |
162 | /// This requires a static value due to the way clap works, otherwise I | |
163 | /// would have built this dynamically. | |
164 | pub const CLI_VALUES: [&'static str; 3] = ["2015", "2018", "2021"]; | |
165 | ||
166 | /// Returns the first version that a particular edition was released on | |
167 | /// stable. | |
168 | pub(crate) fn first_version(&self) -> Option<semver::Version> { | |
169 | use Edition::*; | |
170 | match self { | |
171 | Edition2015 => None, | |
172 | Edition2018 => Some(semver::Version::new(1, 31, 0)), | |
173 | Edition2021 => Some(semver::Version::new(1, 56, 0)), | |
174 | } | |
175 | } | |
176 | ||
177 | /// Returns `true` if this edition is stable in this release. | |
178 | pub fn is_stable(&self) -> bool { | |
179 | use Edition::*; | |
180 | match self { | |
181 | Edition2015 => true, | |
182 | Edition2018 => true, | |
183 | Edition2021 => true, | |
184 | } | |
185 | } | |
186 | ||
187 | /// Returns the previous edition from this edition. | |
188 | /// | |
189 | /// Returns `None` for 2015. | |
190 | pub fn previous(&self) -> Option<Edition> { | |
191 | use Edition::*; | |
192 | match self { | |
193 | Edition2015 => None, | |
194 | Edition2018 => Some(Edition2015), | |
195 | Edition2021 => Some(Edition2018), | |
196 | } | |
197 | } | |
198 | ||
199 | /// Returns the next edition from this edition, returning the last edition | |
200 | /// if this is already the last one. | |
201 | pub fn saturating_next(&self) -> Edition { | |
202 | use Edition::*; | |
203 | match self { | |
204 | Edition2015 => Edition2018, | |
205 | Edition2018 => Edition2021, | |
206 | Edition2021 => Edition2021, | |
207 | } | |
208 | } | |
209 | ||
210 | /// Updates the given [`ProcessBuilder`] to include the appropriate flags | |
211 | /// for setting the edition. | |
212 | pub(crate) fn cmd_edition_arg(&self, cmd: &mut ProcessBuilder) { | |
213 | if *self != Edition::Edition2015 { | |
214 | cmd.arg(format!("--edition={}", self)); | |
215 | } | |
216 | if !self.is_stable() { | |
217 | cmd.arg("-Z").arg("unstable-options"); | |
218 | } | |
219 | } | |
220 | ||
221 | /// Whether or not this edition supports the `rust_*_compatibility` lint. | |
222 | /// | |
223 | /// Ideally this would not be necessary, but editions may not have any | |
224 | /// lints, and thus `rustc` doesn't recognize it. Perhaps `rustc` could | |
225 | /// create an empty group instead? | |
226 | pub(crate) fn supports_compat_lint(&self) -> bool { | |
227 | use Edition::*; | |
228 | match self { | |
229 | Edition2015 => false, | |
230 | Edition2018 => true, | |
231 | Edition2021 => true, | |
232 | } | |
233 | } | |
234 | ||
235 | /// Whether or not this edition supports the `rust_*_idioms` lint. | |
236 | /// | |
237 | /// Ideally this would not be necessary... | |
238 | pub(crate) fn supports_idiom_lint(&self) -> bool { | |
239 | use Edition::*; | |
240 | match self { | |
241 | Edition2015 => false, | |
242 | Edition2018 => true, | |
243 | Edition2021 => false, | |
244 | } | |
245 | } | |
246 | ||
247 | pub(crate) fn default_resolve_behavior(&self) -> ResolveBehavior { | |
248 | if *self >= Edition::Edition2021 { | |
249 | ResolveBehavior::V2 | |
250 | } else { | |
251 | ResolveBehavior::V1 | |
252 | } | |
253 | } | |
254 | } | |
255 | ||
256 | impl fmt::Display for Edition { | |
257 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
258 | match *self { | |
259 | Edition::Edition2015 => f.write_str("2015"), | |
260 | Edition::Edition2018 => f.write_str("2018"), | |
261 | Edition::Edition2021 => f.write_str("2021"), | |
262 | } | |
263 | } | |
264 | } | |
265 | impl FromStr for Edition { | |
266 | type Err = Error; | |
267 | fn from_str(s: &str) -> Result<Self, Error> { | |
268 | match s { | |
269 | "2015" => Ok(Edition::Edition2015), | |
270 | "2018" => Ok(Edition::Edition2018), | |
271 | "2021" => Ok(Edition::Edition2021), | |
272 | s if s.parse().map_or(false, |y: u16| y > 2021 && y < 2050) => bail!( | |
273 | "this version of Cargo is older than the `{}` edition, \ | |
274 | and only supports `2015`, `2018`, and `2021` editions.", | |
275 | s | |
276 | ), | |
277 | s => bail!( | |
278 | "supported edition values are `2015`, `2018`, or `2021`, \ | |
279 | but `{}` is unknown", | |
280 | s | |
281 | ), | |
282 | } | |
283 | } | |
284 | } | |
285 | ||
286 | #[derive(PartialEq)] | |
287 | enum Status { | |
288 | Stable, | |
289 | Unstable, | |
290 | Removed, | |
291 | } | |
292 | ||
293 | macro_rules! features { | |
294 | ( | |
295 | $(($stab:ident, $feature:ident, $version:expr, $docs:expr),)* | |
296 | ) => ( | |
297 | #[derive(Default, Clone, Debug)] | |
298 | pub struct Features { | |
299 | $($feature: bool,)* | |
300 | activated: Vec<String>, | |
301 | nightly_features_allowed: bool, | |
302 | is_local: bool, | |
303 | } | |
304 | ||
305 | impl Feature { | |
306 | $( | |
307 | pub fn $feature() -> &'static Feature { | |
308 | fn get(features: &Features) -> bool { | |
309 | stab!($stab) == Status::Stable || features.$feature | |
310 | } | |
311 | static FEAT: Feature = Feature { | |
312 | name: stringify!($feature), | |
313 | stability: stab!($stab), | |
314 | version: $version, | |
315 | docs: $docs, | |
316 | get, | |
317 | }; | |
318 | &FEAT | |
319 | } | |
320 | )* | |
321 | ||
322 | fn is_enabled(&self, features: &Features) -> bool { | |
323 | (self.get)(features) | |
324 | } | |
325 | } | |
326 | ||
327 | impl Features { | |
328 | fn status(&mut self, feature: &str) -> Option<(&mut bool, &'static Feature)> { | |
329 | if feature.contains("_") { | |
330 | return None | |
331 | } | |
332 | let feature = feature.replace("-", "_"); | |
333 | $( | |
334 | if feature == stringify!($feature) { | |
335 | return Some((&mut self.$feature, Feature::$feature())) | |
336 | } | |
337 | )* | |
338 | None | |
339 | } | |
340 | } | |
341 | ) | |
342 | } | |
343 | ||
344 | macro_rules! stab { | |
345 | (stable) => { | |
346 | Status::Stable | |
347 | }; | |
348 | (unstable) => { | |
349 | Status::Unstable | |
350 | }; | |
351 | (removed) => { | |
352 | Status::Removed | |
353 | }; | |
354 | } | |
355 | ||
356 | // A listing of all features in Cargo. | |
357 | // | |
358 | // "look here" | |
359 | // | |
360 | // This is the macro that lists all stable and unstable features in Cargo. | |
361 | // You'll want to add to this macro whenever you add a feature to Cargo, also | |
362 | // following the directions above. | |
363 | // | |
364 | // Note that all feature names here are valid Rust identifiers, but the `_` | |
365 | // character is translated to `-` when specified in the `cargo-features` | |
366 | // manifest entry in `Cargo.toml`. | |
367 | features! { | |
368 | // A dummy feature that doesn't actually gate anything, but it's used in | |
369 | // testing to ensure that we can enable stable features. | |
370 | (stable, test_dummy_stable, "1.0", ""), | |
371 | ||
372 | // A dummy feature that gates the usage of the `im-a-teapot` manifest | |
373 | // entry. This is basically just intended for tests. | |
374 | (unstable, test_dummy_unstable, "", "reference/unstable.html"), | |
375 | ||
376 | // Downloading packages from alternative registry indexes. | |
377 | (stable, alternative_registries, "1.34", "reference/registries.html"), | |
378 | ||
379 | // Using editions | |
380 | (stable, edition, "1.31", "reference/manifest.html#the-edition-field"), | |
381 | ||
382 | // Renaming a package in the manifest via the `package` key | |
383 | (stable, rename_dependency, "1.31", "reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml"), | |
384 | ||
385 | // Whether a lock file is published with this crate | |
386 | (removed, publish_lockfile, "1.37", "reference/unstable.html#publish-lockfile"), | |
387 | ||
388 | // Overriding profiles for dependencies. | |
389 | (stable, profile_overrides, "1.41", "reference/profiles.html#overrides"), | |
390 | ||
391 | // "default-run" manifest option, | |
392 | (stable, default_run, "1.37", "reference/manifest.html#the-default-run-field"), | |
393 | ||
394 | // Declarative build scripts. | |
395 | (unstable, metabuild, "", "reference/unstable.html#metabuild"), | |
396 | ||
397 | // Specifying the 'public' attribute on dependencies | |
398 | (unstable, public_dependency, "", "reference/unstable.html#public-dependency"), | |
399 | ||
400 | // Allow to specify profiles other than 'dev', 'release', 'test', etc. | |
401 | (stable, named_profiles, "1.57", "reference/profiles.html#custom-profiles"), | |
402 | ||
403 | // Opt-in new-resolver behavior. | |
404 | (stable, resolver, "1.51", "reference/resolver.html#resolver-versions"), | |
405 | ||
406 | // Allow to specify whether binaries should be stripped. | |
407 | (stable, strip, "1.58", "reference/profiles.html#strip-option"), | |
408 | ||
409 | // Specifying a minimal 'rust-version' attribute for crates | |
410 | (stable, rust_version, "1.56", "reference/manifest.html#the-rust-version-field"), | |
411 | ||
412 | // Support for 2021 edition. | |
413 | (stable, edition2021, "1.56", "reference/manifest.html#the-edition-field"), | |
414 | ||
415 | // Allow to specify per-package targets (compile kinds) | |
416 | (unstable, per_package_target, "", "reference/unstable.html#per-package-target"), | |
417 | ||
418 | // Allow to specify which codegen backend should be used. | |
419 | (unstable, codegen_backend, "", "reference/unstable.html#codegen-backend"), | |
420 | ||
421 | // Allow specifying different binary name apart from the crate name | |
422 | (unstable, different_binary_name, "", "reference/unstable.html#different-binary-name"), | |
423 | ||
424 | // Allow specifying rustflags directly in a profile | |
425 | (unstable, profile_rustflags, "", "reference/unstable.html#profile-rustflags-option"), | |
426 | ||
427 | // Allow specifying rustflags directly in a profile | |
428 | (stable, workspace_inheritance, "1.64", "reference/unstable.html#workspace-inheritance"), | |
429 | } | |
430 | ||
431 | pub struct Feature { | |
432 | name: &'static str, | |
433 | stability: Status, | |
434 | version: &'static str, | |
435 | docs: &'static str, | |
436 | get: fn(&Features) -> bool, | |
437 | } | |
438 | ||
439 | impl Features { | |
440 | pub fn new( | |
441 | features: &[String], | |
442 | config: &Config, | |
443 | warnings: &mut Vec<String>, | |
444 | is_local: bool, | |
445 | ) -> CargoResult<Features> { | |
446 | let mut ret = Features::default(); | |
447 | ret.nightly_features_allowed = config.nightly_features_allowed; | |
448 | ret.is_local = is_local; | |
449 | for feature in features { | |
450 | ret.add(feature, config, warnings)?; | |
451 | ret.activated.push(feature.to_string()); | |
452 | } | |
453 | Ok(ret) | |
454 | } | |
455 | ||
456 | fn add( | |
457 | &mut self, | |
458 | feature_name: &str, | |
459 | config: &Config, | |
460 | warnings: &mut Vec<String>, | |
461 | ) -> CargoResult<()> { | |
462 | let nightly_features_allowed = self.nightly_features_allowed; | |
463 | let is_local = self.is_local; | |
464 | let (slot, feature) = match self.status(feature_name) { | |
465 | Some(p) => p, | |
466 | None => bail!("unknown cargo feature `{}`", feature_name), | |
467 | }; | |
468 | ||
469 | if *slot { | |
470 | bail!( | |
471 | "the cargo feature `{}` has already been activated", | |
472 | feature_name | |
473 | ); | |
474 | } | |
475 | ||
476 | let see_docs = || { | |
477 | let url_channel = match channel().as_str() { | |
478 | "dev" | "nightly" => "nightly/", | |
479 | "beta" => "beta/", | |
480 | _ => "", | |
481 | }; | |
482 | format!( | |
483 | "See https://doc.rust-lang.org/{}cargo/{} for more information \ | |
484 | about using this feature.", | |
485 | url_channel, feature.docs | |
486 | ) | |
487 | }; | |
488 | ||
489 | match feature.stability { | |
490 | Status::Stable => { | |
491 | // The user can't do anything about non-local packages. | |
492 | // Warnings are usually suppressed, but just being cautious here. | |
493 | if is_local { | |
494 | let warning = format!( | |
495 | "the cargo feature `{}` has been stabilized in the {} \ | |
496 | release and is no longer necessary to be listed in the \ | |
497 | manifest\n {}", | |
498 | feature_name, | |
499 | feature.version, | |
500 | see_docs() | |
501 | ); | |
502 | warnings.push(warning); | |
503 | } | |
504 | } | |
505 | Status::Unstable if !nightly_features_allowed => bail!( | |
506 | "the cargo feature `{}` requires a nightly version of \ | |
507 | Cargo, but this is the `{}` channel\n\ | |
508 | {}\n{}", | |
509 | feature_name, | |
510 | channel(), | |
511 | SEE_CHANNELS, | |
512 | see_docs() | |
513 | ), | |
514 | Status::Unstable => { | |
515 | if let Some(allow) = &config.cli_unstable().allow_features { | |
516 | if !allow.contains(feature_name) { | |
517 | bail!( | |
518 | "the feature `{}` is not in the list of allowed features: [{}]", | |
519 | feature_name, | |
520 | iter_join(allow, ", "), | |
521 | ); | |
522 | } | |
523 | } | |
524 | } | |
525 | Status::Removed => { | |
526 | let mut msg = format!( | |
527 | "the cargo feature `{}` has been removed in the {} release\n\n", | |
528 | feature_name, feature.version | |
529 | ); | |
530 | if self.is_local { | |
531 | drop(writeln!( | |
532 | msg, | |
533 | "Remove the feature from Cargo.toml to remove this error." | |
534 | )); | |
535 | } else { | |
536 | drop(writeln!( | |
537 | msg, | |
538 | "This package cannot be used with this version of Cargo, \ | |
539 | as the unstable feature `{}` is no longer supported.", | |
540 | feature_name | |
541 | )); | |
542 | } | |
543 | drop(writeln!(msg, "{}", see_docs())); | |
544 | bail!(msg); | |
545 | } | |
546 | } | |
547 | ||
548 | *slot = true; | |
549 | ||
550 | Ok(()) | |
551 | } | |
552 | ||
553 | pub fn activated(&self) -> &[String] { | |
554 | &self.activated | |
555 | } | |
556 | ||
557 | pub fn require(&self, feature: &Feature) -> CargoResult<()> { | |
558 | if feature.is_enabled(self) { | |
559 | return Ok(()); | |
560 | } | |
561 | let feature_name = feature.name.replace("_", "-"); | |
562 | let mut msg = format!( | |
563 | "feature `{}` is required\n\ | |
564 | \n\ | |
565 | The package requires the Cargo feature called `{}`, but \ | |
566 | that feature is not stabilized in this version of Cargo ({}).\n\ | |
567 | ", | |
568 | feature_name, | |
569 | feature_name, | |
570 | crate::version(), | |
571 | ); | |
572 | ||
573 | if self.nightly_features_allowed { | |
574 | if self.is_local { | |
575 | drop(writeln!( | |
576 | msg, | |
577 | "Consider adding `cargo-features = [\"{}\"]` \ | |
578 | to the top of Cargo.toml (above the [package] table) \ | |
579 | to tell Cargo you are opting in to use this unstable feature.", | |
580 | feature_name | |
581 | )); | |
582 | } else { | |
583 | drop(writeln!( | |
584 | msg, | |
585 | "Consider trying a more recent nightly release." | |
586 | )); | |
587 | } | |
588 | } else { | |
589 | drop(writeln!( | |
590 | msg, | |
591 | "Consider trying a newer version of Cargo \ | |
592 | (this may require the nightly release)." | |
593 | )); | |
594 | } | |
595 | drop(writeln!( | |
596 | msg, | |
597 | "See https://doc.rust-lang.org/nightly/cargo/{} for more information \ | |
598 | about the status of this feature.", | |
599 | feature.docs | |
600 | )); | |
601 | ||
602 | bail!("{}", msg); | |
603 | } | |
604 | ||
605 | pub fn is_enabled(&self, feature: &Feature) -> bool { | |
606 | feature.is_enabled(self) | |
607 | } | |
608 | } | |
609 | ||
610 | macro_rules! unstable_cli_options { | |
611 | ( | |
612 | $( | |
613 | $(#[$meta:meta])? | |
614 | $element: ident: $ty: ty = ($help: expr ), | |
615 | )* | |
616 | ) => { | |
617 | /// A parsed representation of all unstable flags that Cargo accepts. | |
618 | /// | |
619 | /// Cargo, like `rustc`, accepts a suite of `-Z` flags which are intended for | |
620 | /// gating unstable functionality to Cargo. These flags are only available on | |
621 | /// the nightly channel of Cargo. | |
622 | #[derive(Default, Debug, Deserialize)] | |
623 | #[serde(default, rename_all = "kebab-case")] | |
624 | pub struct CliUnstable { | |
625 | $( | |
626 | $(#[$meta])? | |
627 | pub $element: $ty | |
628 | ),* | |
629 | } | |
630 | impl CliUnstable { | |
631 | pub fn help() -> Vec<(&'static str, &'static str)> { | |
632 | let fields = vec![$((stringify!($element), $help)),*]; | |
633 | fields | |
634 | } | |
635 | } | |
636 | } | |
637 | } | |
638 | ||
639 | unstable_cli_options!( | |
640 | // Permanently unstable features: | |
641 | allow_features: Option<BTreeSet<String>> = ("Allow *only* the listed unstable features"), | |
642 | print_im_a_teapot: bool = (HIDDEN), | |
643 | ||
644 | // All other unstable features. | |
645 | // Please keep this list lexiographically ordered. | |
646 | advanced_env: bool = (HIDDEN), | |
647 | avoid_dev_deps: bool = ("Avoid installing dev-dependencies if possible"), | |
648 | binary_dep_depinfo: bool = ("Track changes to dependency artifacts"), | |
649 | bindeps: bool = ("Allow Cargo packages to depend on bin, cdylib, and staticlib crates, and use the artifacts built by those crates"), | |
650 | #[serde(deserialize_with = "deserialize_build_std")] | |
651 | build_std: Option<Vec<String>> = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"), | |
652 | build_std_features: Option<Vec<String>> = ("Configure features enabled for the standard library itself when building the standard library"), | |
653 | config_include: bool = ("Enable the `include` key in config files"), | |
654 | credential_process: bool = ("Add a config setting to fetch registry authentication tokens by calling an external process"), | |
655 | #[serde(deserialize_with = "deserialize_check_cfg")] | |
656 | check_cfg: Option<(/*features:*/ bool, /*well_known_names:*/ bool, /*well_known_values:*/ bool, /*output:*/ bool)> = ("Specify scope of compile-time checking of `cfg` names/values"), | |
657 | doctest_in_workspace: bool = ("Compile doctests with paths relative to the workspace root"), | |
658 | doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"), | |
659 | dual_proc_macros: bool = ("Build proc-macros for both the host and the target"), | |
660 | features: Option<Vec<String>> = (HIDDEN), | |
661 | jobserver_per_rustc: bool = (HIDDEN), | |
662 | minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"), | |
663 | mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"), | |
664 | multitarget: bool = ("Allow passing multiple `--target` flags to the cargo subcommand selected"), | |
665 | no_index_update: bool = ("Do not update the registry index even if the cache is outdated"), | |
666 | panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"), | |
667 | host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"), | |
668 | sparse_registry: bool = ("Support plain-HTTP-based crate registries"), | |
669 | target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"), | |
670 | rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"), | |
671 | separate_nightlies: bool = (HIDDEN), | |
672 | terminal_width: Option<Option<usize>> = ("Provide a terminal width to rustc for error truncation"), | |
673 | unstable_options: bool = ("Allow the usage of unstable options"), | |
674 | // TODO(wcrichto): move scrape example configuration into Cargo.toml before stabilization | |
675 | // See: https://github.com/rust-lang/cargo/pull/9525#discussion_r728470927 | |
676 | rustdoc_scrape_examples: Option<String> = ("Allow rustdoc to scrape examples from reverse-dependencies for documentation"), | |
677 | skip_rustdoc_fingerprint: bool = (HIDDEN), | |
678 | ); | |
679 | ||
680 | const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \ | |
681 | enabled when used on an interactive console.\n\ | |
682 | See https://doc.rust-lang.org/cargo/reference/config.html#termprogresswhen \ | |
683 | for information on controlling the progress bar."; | |
684 | ||
685 | const STABILIZED_OFFLINE: &str = "Offline mode is now available via the \ | |
686 | --offline CLI option"; | |
687 | ||
688 | const STABILIZED_CACHE_MESSAGES: &str = "Message caching is now always enabled."; | |
689 | ||
690 | const STABILIZED_INSTALL_UPGRADE: &str = "Packages are now always upgraded if \ | |
691 | they appear out of date.\n\ | |
692 | See https://doc.rust-lang.org/cargo/commands/cargo-install.html for more \ | |
693 | information on how upgrading works."; | |
694 | ||
695 | const STABILIZED_CONFIG_PROFILE: &str = "See \ | |
696 | https://doc.rust-lang.org/cargo/reference/config.html#profile for more \ | |
697 | information about specifying profiles in config."; | |
698 | ||
699 | const STABILIZED_CRATE_VERSIONS: &str = "The crate version is now \ | |
700 | automatically added to the documentation."; | |
701 | ||
702 | const STABILIZED_PACKAGE_FEATURES: &str = "Enhanced feature flag behavior is now \ | |
703 | available in virtual workspaces, and `member/feature-name` syntax is also \ | |
704 | always available. Other extensions require setting `resolver = \"2\"` in \ | |
705 | Cargo.toml.\n\ | |
706 | See https://doc.rust-lang.org/nightly/cargo/reference/features.html#resolver-version-2-command-line-flags \ | |
707 | for more information."; | |
708 | ||
709 | const STABILIZED_FEATURES: &str = "The new feature resolver is now available \ | |
710 | by specifying `resolver = \"2\"` in Cargo.toml.\n\ | |
711 | See https://doc.rust-lang.org/nightly/cargo/reference/features.html#feature-resolver-version-2 \ | |
712 | for more information."; | |
713 | ||
714 | const STABILIZED_EXTRA_LINK_ARG: &str = "Additional linker arguments are now \ | |
715 | supported without passing this flag."; | |
716 | ||
717 | const STABILIZED_CONFIGURABLE_ENV: &str = "The [env] section is now always enabled."; | |
718 | ||
719 | const STABILIZED_PATCH_IN_CONFIG: &str = "The patch-in-config feature is now always enabled."; | |
720 | ||
721 | const STABILIZED_NAMED_PROFILES: &str = "The named-profiles feature is now always enabled.\n\ | |
722 | See https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#custom-profiles \ | |
723 | for more information"; | |
724 | ||
725 | const STABILIZED_FUTURE_INCOMPAT_REPORT: &str = | |
726 | "The future-incompat-report feature is now always enabled."; | |
727 | ||
728 | const STABILIZED_WEAK_DEP_FEATURES: &str = "Weak dependency features are now always available."; | |
729 | ||
730 | const STABILISED_NAMESPACED_FEATURES: &str = "Namespaced features are now always available."; | |
731 | ||
732 | const STABILIZED_TIMINGS: &str = "The -Ztimings option has been stabilized as --timings."; | |
733 | ||
734 | const STABILISED_MULTITARGET: &str = "Multiple `--target` options are now always available."; | |
735 | ||
736 | fn deserialize_build_std<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error> | |
737 | where | |
738 | D: serde::Deserializer<'de>, | |
739 | { | |
740 | let crates = match <Option<Vec<String>>>::deserialize(deserializer)? { | |
741 | Some(list) => list, | |
742 | None => return Ok(None), | |
743 | }; | |
744 | let v = crates.join(","); | |
745 | Ok(Some( | |
746 | crate::core::compiler::standard_lib::parse_unstable_flag(Some(&v)), | |
747 | )) | |
748 | } | |
749 | ||
750 | fn deserialize_check_cfg<'de, D>( | |
751 | deserializer: D, | |
752 | ) -> Result<Option<(bool, bool, bool, bool)>, D::Error> | |
753 | where | |
754 | D: serde::Deserializer<'de>, | |
755 | { | |
756 | use serde::de::Error; | |
757 | let crates = match <Option<Vec<String>>>::deserialize(deserializer)? { | |
758 | Some(list) => list, | |
759 | None => return Ok(None), | |
760 | }; | |
761 | ||
762 | parse_check_cfg(crates.into_iter()).map_err(D::Error::custom) | |
763 | } | |
764 | ||
765 | fn parse_check_cfg( | |
766 | it: impl Iterator<Item = impl AsRef<str>>, | |
767 | ) -> CargoResult<Option<(bool, bool, bool, bool)>> { | |
768 | let mut features = false; | |
769 | let mut well_known_names = false; | |
770 | let mut well_known_values = false; | |
771 | let mut output = false; | |
772 | ||
773 | for e in it { | |
774 | match e.as_ref() { | |
775 | "features" => features = true, | |
776 | "names" => well_known_names = true, | |
777 | "values" => well_known_values = true, | |
778 | "output" => output = true, | |
779 | _ => bail!("unstable check-cfg only takes `features`, `names`, `values` or `output` as valid inputs"), | |
780 | } | |
781 | } | |
782 | ||
783 | Ok(Some(( | |
784 | features, | |
785 | well_known_names, | |
786 | well_known_values, | |
787 | output, | |
788 | ))) | |
789 | } | |
790 | ||
791 | impl CliUnstable { | |
792 | pub fn parse( | |
793 | &mut self, | |
794 | flags: &[String], | |
795 | nightly_features_allowed: bool, | |
796 | ) -> CargoResult<Vec<String>> { | |
797 | if !flags.is_empty() && !nightly_features_allowed { | |
798 | bail!( | |
799 | "the `-Z` flag is only accepted on the nightly channel of Cargo, \ | |
800 | but this is the `{}` channel\n\ | |
801 | {}", | |
802 | channel(), | |
803 | SEE_CHANNELS | |
804 | ); | |
805 | } | |
806 | let mut warnings = Vec::new(); | |
807 | // We read flags twice, first to get allowed-features (if specified), | |
808 | // and then to read the remaining unstable flags. | |
809 | for flag in flags { | |
810 | if flag.starts_with("allow-features=") { | |
811 | self.add(flag, &mut warnings)?; | |
812 | } | |
813 | } | |
814 | for flag in flags { | |
815 | self.add(flag, &mut warnings)?; | |
816 | } | |
817 | Ok(warnings) | |
818 | } | |
819 | ||
820 | fn add(&mut self, flag: &str, warnings: &mut Vec<String>) -> CargoResult<()> { | |
821 | let mut parts = flag.splitn(2, '='); | |
822 | let k = parts.next().unwrap(); | |
823 | let v = parts.next(); | |
824 | ||
825 | fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> { | |
826 | match value { | |
827 | None | Some("yes") => Ok(true), | |
828 | Some("no") => Ok(false), | |
829 | Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s), | |
830 | } | |
831 | } | |
832 | ||
833 | fn parse_features(value: Option<&str>) -> Vec<String> { | |
834 | match value { | |
835 | None => Vec::new(), | |
836 | Some("") => Vec::new(), | |
837 | Some(v) => v.split(',').map(|s| s.to_string()).collect(), | |
838 | } | |
839 | } | |
840 | ||
841 | // Asserts that there is no argument to the flag. | |
842 | fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> { | |
843 | if let Some(v) = value { | |
844 | bail!("flag -Z{} does not take a value, found: `{}`", key, v); | |
845 | } | |
846 | Ok(true) | |
847 | } | |
848 | ||
849 | fn parse_usize_opt(value: Option<&str>) -> CargoResult<Option<usize>> { | |
850 | Ok(match value { | |
851 | Some(value) => match value.parse::<usize>() { | |
852 | Ok(value) => Some(value), | |
853 | Err(e) => bail!("expected a number, found: {}", e), | |
854 | }, | |
855 | None => None, | |
856 | }) | |
857 | } | |
858 | ||
859 | let mut stabilized_warn = |key: &str, version: &str, message: &str| { | |
860 | warnings.push(format!( | |
861 | "flag `-Z {}` has been stabilized in the {} release, \ | |
862 | and is no longer necessary\n{}", | |
863 | key, | |
864 | version, | |
865 | indented_lines(message) | |
866 | )); | |
867 | }; | |
868 | ||
869 | // Use this if the behavior now requires another mechanism to enable. | |
870 | let stabilized_err = |key: &str, version: &str, message: &str| { | |
871 | Err(anyhow::format_err!( | |
872 | "flag `-Z {}` has been stabilized in the {} release\n{}", | |
873 | key, | |
874 | version, | |
875 | indented_lines(message) | |
876 | )) | |
877 | }; | |
878 | ||
879 | if let Some(allowed) = &self.allow_features { | |
880 | if k != "allow-features" && !allowed.contains(k) { | |
881 | bail!( | |
882 | "the feature `{}` is not in the list of allowed features: [{}]", | |
883 | k, | |
884 | iter_join(allowed, ", ") | |
885 | ); | |
886 | } | |
887 | } | |
888 | ||
889 | match k { | |
890 | "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?, | |
891 | "allow-features" => self.allow_features = Some(parse_features(v).into_iter().collect()), | |
892 | "unstable-options" => self.unstable_options = parse_empty(k, v)?, | |
893 | "no-index-update" => self.no_index_update = parse_empty(k, v)?, | |
894 | "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?, | |
895 | "minimal-versions" => self.minimal_versions = parse_empty(k, v)?, | |
896 | "advanced-env" => self.advanced_env = parse_empty(k, v)?, | |
897 | "config-include" => self.config_include = parse_empty(k, v)?, | |
898 | "check-cfg" => { | |
899 | self.check_cfg = v.map_or(Ok(None), |v| parse_check_cfg(v.split(',')))? | |
900 | } | |
901 | "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?, | |
902 | // can also be set in .cargo/config or with and ENV | |
903 | "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?, | |
904 | "named-profiles" => stabilized_warn(k, "1.57", STABILIZED_NAMED_PROFILES), | |
905 | "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?, | |
906 | "bindeps" => self.bindeps = parse_empty(k, v)?, | |
907 | "build-std" => { | |
908 | self.build_std = Some(crate::core::compiler::standard_lib::parse_unstable_flag(v)) | |
909 | } | |
910 | "build-std-features" => self.build_std_features = Some(parse_features(v)), | |
911 | "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?, | |
912 | "doctest-in-workspace" => self.doctest_in_workspace = parse_empty(k, v)?, | |
913 | "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, | |
914 | "jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?, | |
915 | "host-config" => self.host_config = parse_empty(k, v)?, | |
916 | "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?, | |
917 | "features" => { | |
918 | // For now this is still allowed (there are still some | |
919 | // unstable options like "compare"). This should be removed at | |
920 | // some point, and migrate to a new -Z flag for any future | |
921 | // things. | |
922 | let feats = parse_features(v); | |
923 | let stab_is_not_empty = feats.iter().any(|feat| { | |
924 | matches!( | |
925 | feat.as_str(), | |
926 | "build_dep" | "host_dep" | "dev_dep" | "itarget" | "all" | |
927 | ) | |
928 | }); | |
929 | if stab_is_not_empty || feats.is_empty() { | |
930 | // Make this stabilized_err once -Zfeature support is removed. | |
931 | stabilized_warn(k, "1.51", STABILIZED_FEATURES); | |
932 | } | |
933 | self.features = Some(feats); | |
934 | } | |
935 | "separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?, | |
936 | "multitarget" => stabilized_warn(k, "1.64", STABILISED_MULTITARGET), | |
937 | "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?, | |
938 | "terminal-width" => self.terminal_width = Some(parse_usize_opt(v)?), | |
939 | "sparse-registry" => self.sparse_registry = parse_empty(k, v)?, | |
940 | "namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES), | |
941 | "weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES), | |
942 | "credential-process" => self.credential_process = parse_empty(k, v)?, | |
943 | "rustdoc-scrape-examples" => { | |
944 | if let Some(s) = v { | |
945 | self.rustdoc_scrape_examples = Some(s.to_string()) | |
946 | } else { | |
947 | bail!( | |
948 | r#"-Z rustdoc-scrape-examples must take "all" or "examples" as an argument"# | |
949 | ) | |
950 | } | |
951 | } | |
952 | "skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(k, v)?, | |
953 | "compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS), | |
954 | "offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?, | |
955 | "cache-messages" => stabilized_warn(k, "1.40", STABILIZED_CACHE_MESSAGES), | |
956 | "install-upgrade" => stabilized_warn(k, "1.41", STABILIZED_INSTALL_UPGRADE), | |
957 | "config-profile" => stabilized_warn(k, "1.43", STABILIZED_CONFIG_PROFILE), | |
958 | "crate-versions" => stabilized_warn(k, "1.47", STABILIZED_CRATE_VERSIONS), | |
959 | "package-features" => stabilized_warn(k, "1.51", STABILIZED_PACKAGE_FEATURES), | |
960 | "extra-link-arg" => stabilized_warn(k, "1.56", STABILIZED_EXTRA_LINK_ARG), | |
961 | "configurable-env" => stabilized_warn(k, "1.56", STABILIZED_CONFIGURABLE_ENV), | |
962 | "patch-in-config" => stabilized_warn(k, "1.56", STABILIZED_PATCH_IN_CONFIG), | |
963 | "future-incompat-report" => { | |
964 | stabilized_warn(k, "1.59.0", STABILIZED_FUTURE_INCOMPAT_REPORT) | |
965 | } | |
966 | "timings" => stabilized_warn(k, "1.60", STABILIZED_TIMINGS), | |
967 | _ => bail!("unknown `-Z` flag specified: {}", k), | |
968 | } | |
969 | ||
970 | Ok(()) | |
971 | } | |
972 | ||
973 | /// Generates an error if `-Z unstable-options` was not used for a new, | |
974 | /// unstable command-line flag. | |
975 | pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> { | |
976 | if !self.unstable_options { | |
977 | let see = format!( | |
978 | "See https://github.com/rust-lang/cargo/issues/{} for more \ | |
979 | information about the `{}` flag.", | |
980 | issue, flag | |
981 | ); | |
982 | // NOTE: a `config` isn't available here, check the channel directly | |
983 | let channel = channel(); | |
984 | if channel == "nightly" || channel == "dev" { | |
985 | bail!( | |
986 | "the `{}` flag is unstable, pass `-Z unstable-options` to enable it\n\ | |
987 | {}", | |
988 | flag, | |
989 | see | |
990 | ); | |
991 | } else { | |
992 | bail!( | |
993 | "the `{}` flag is unstable, and only available on the nightly channel \ | |
994 | of Cargo, but this is the `{}` channel\n\ | |
995 | {}\n\ | |
996 | {}", | |
997 | flag, | |
998 | channel, | |
999 | SEE_CHANNELS, | |
1000 | see | |
1001 | ); | |
1002 | } | |
1003 | } | |
1004 | Ok(()) | |
1005 | } | |
1006 | ||
1007 | /// Generates an error if `-Z unstable-options` was not used for a new, | |
1008 | /// unstable subcommand. | |
1009 | pub fn fail_if_stable_command( | |
1010 | &self, | |
1011 | config: &Config, | |
1012 | command: &str, | |
1013 | issue: u32, | |
1014 | ) -> CargoResult<()> { | |
1015 | if self.unstable_options { | |
1016 | return Ok(()); | |
1017 | } | |
1018 | let see = format!( | |
1019 | "See https://github.com/rust-lang/cargo/issues/{} for more \ | |
1020 | information about the `cargo {}` command.", | |
1021 | issue, command | |
1022 | ); | |
1023 | if config.nightly_features_allowed { | |
1024 | bail!( | |
1025 | "the `cargo {}` command is unstable, pass `-Z unstable-options` to enable it\n\ | |
1026 | {}", | |
1027 | command, | |
1028 | see | |
1029 | ); | |
1030 | } else { | |
1031 | bail!( | |
1032 | "the `cargo {}` command is unstable, and only available on the \ | |
1033 | nightly channel of Cargo, but this is the `{}` channel\n\ | |
1034 | {}\n\ | |
1035 | {}", | |
1036 | command, | |
1037 | channel(), | |
1038 | SEE_CHANNELS, | |
1039 | see | |
1040 | ); | |
1041 | } | |
1042 | } | |
1043 | } | |
1044 | ||
1045 | /// Returns the current release channel ("stable", "beta", "nightly", "dev"). | |
1046 | pub fn channel() -> String { | |
1047 | if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") { | |
1048 | return override_channel; | |
1049 | } | |
1050 | if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") { | |
1051 | if staging == "1" { | |
1052 | return "dev".to_string(); | |
1053 | } | |
1054 | } | |
1055 | crate::version() | |
1056 | .release_channel | |
1057 | .unwrap_or_else(|| String::from("dev")) | |
1058 | } |