From 4a90ab6910a92f41ca9975981bbdcae027a6257c Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Wed, 17 Mar 2021 15:19:54 -0700 Subject: [PATCH] Add -Zallow-features to match rustc's -Z --- src/bin/cargo/cli.rs | 1 + src/cargo/core/features.rs | 59 +++++++++++++++++++++++++++++-- src/doc/src/reference/unstable.md | 21 +++++++++++ tests/testsuite/cargo_features.rs | 58 ++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 3 deletions(-) diff --git a/src/bin/cargo/cli.rs b/src/bin/cargo/cli.rs index 9c609ca5e..55d7bd9bf 100644 --- a/src/bin/cargo/cli.rs +++ b/src/bin/cargo/cli.rs @@ -35,6 +35,7 @@ pub fn main(config: &mut Config) -> CliResult { " Available unstable (nightly-only) flags: + -Z allow-features -- Allow *only* the listed unstable features -Z avoid-dev-deps -- Avoid installing dev-dependencies if possible -Z extra-link-arg -- Allow `cargo:rustc-link-arg` in build scripts -Z minimal-versions -- Install minimal dependency versions instead of maximum diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index cdebf99a6..f78527723 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -92,6 +92,7 @@ //! of that page. Update the rest of the documentation to add the new //! feature. +use std::collections::HashSet; use std::env; use std::fmt; use std::str::FromStr; @@ -415,13 +416,18 @@ impl Features { let mut ret = Features::default(); ret.nightly_features_allowed = config.nightly_features_allowed; for feature in features { - ret.add(feature, warnings)?; + ret.add(feature, config, warnings)?; ret.activated.push(feature.to_string()); } Ok(ret) } - fn add(&mut self, feature_name: &str, warnings: &mut Vec) -> CargoResult<()> { + fn add( + &mut self, + feature_name: &str, + config: &Config, + warnings: &mut Vec, + ) -> CargoResult<()> { let nightly_features_allowed = self.nightly_features_allowed; let (slot, feature) = match self.status(feature_name) { Some(p) => p, @@ -469,6 +475,24 @@ impl Features { SEE_CHANNELS, see_docs() ), + Status::Unstable + if !config + .cli_unstable() + .allow_features + .as_ref() + .map(|f| f.contains(feature_name)) + .unwrap_or(true) => + { + bail!( + "the feature `{}` is not in the list of allowed features: {:?}", + feature_name, + config + .cli_unstable() + .allow_features + .as_ref() + .expect("!unwrap_or(true) == false") + ) + } Status::Unstable => {} Status::Removed => bail!( "the cargo feature `{}` has been removed\n\ @@ -530,6 +554,8 @@ impl Features { #[serde(default, rename_all = "kebab-case")] pub struct CliUnstable { pub print_im_a_teapot: bool, + pub allow_features: Option>, + pub unstable_options: bool, pub no_index_update: bool, pub avoid_dev_deps: bool, @@ -627,7 +653,32 @@ impl CliUnstable { } let mut warnings = Vec::new(); for flag in flags { - self.add(flag, &mut warnings)?; + if flag.starts_with("allow-features=") { + self.add(flag, &mut warnings)?; + } + } + if let Some(allowed) = self.allow_features.take() { + for flag in flags { + let k = flag + .splitn(2, '=') + .next() + .expect("split always yields >=1 item"); + if k == "allow-features" { + } else if allowed.contains(k) { + self.add(flag, &mut warnings)?; + } else { + bail!( + "the feature `{}` is not in the list of allowed features: {:?}", + k, + allowed + ); + } + } + self.allow_features = Some(allowed); + } else { + for flag in flags { + self.add(flag, &mut warnings)?; + } } Ok(warnings) } @@ -655,6 +706,7 @@ impl CliUnstable { fn parse_features(value: Option<&str>) -> Vec { match value { None => Vec::new(), + Some("") => Vec::new(), Some(v) => v.split(',').map(|s| s.to_string()).collect(), } } @@ -699,6 +751,7 @@ impl CliUnstable { match k { "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?, + "allow-features" => self.allow_features = Some(parse_features(v).into_iter().collect()), "unstable-options" => self.unstable_options = parse_empty(k, v)?, "no-index-update" => self.no_index_update = parse_empty(k, v)?, "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?, diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index b1ef06907..e6b09f51c 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -58,6 +58,27 @@ Each new feature described below should explain how to use it. [nightly channel]: ../../book/appendix-07-nightly-rust.html [stabilized]: https://doc.crates.io/contrib/process/unstable.html#stabilization +### allow-features + +This permanently-unstable flag makes it so that only a listed set of +unstable features can be used. Specifically, if you pass +`-Zallow-features=foo,bar`, you'll continue to be able to pass `-Zfoo` +and `-Zbar` to `cargo`, but you will be unable to pass `-Zbaz`. You can +pass an empty string (`-Zallow-features=`) to disallow all unstable +features. + +`-Zallow-features` also restricts which unstable features can be passed +to the `cargo-features` entry in `Cargo.toml`. If, for example, you want +to allow + +```toml +cargo-features = ["test-dummy-unstable"] +``` + +where `test-dummy-unstable` is unstable, that features would also be +disallowed by `-Zallow-features=`, and allowed with +`-Zallow-features=test-dummy-unstable`. + ### extra-link-arg * Original Pull Request: [#7811](https://github.com/rust-lang/cargo/pull/7811) diff --git a/tests/testsuite/cargo_features.rs b/tests/testsuite/cargo_features.rs index 806fc4195..f95eab0dd 100644 --- a/tests/testsuite/cargo_features.rs +++ b/tests/testsuite/cargo_features.rs @@ -113,6 +113,64 @@ release and is no longer necessary to be listed in the manifest .run(); } +#[cargo_test] +fn allow_features() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["test-dummy-unstable"] + + [package] + name = "a" + version = "0.0.1" + authors = [] + im-a-teapot = true + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("-Zallow-features=test-dummy-unstable build") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[COMPILING] a [..] +[FINISHED] [..] +", + ) + .run(); + + p.cargo("-Zallow-features=test-dummy-unstable,print-im-a-teapot -Zprint-im-a-teapot build") + .masquerade_as_nightly_cargo() + .with_stdout("im-a-teapot = true") + .with_stderr("[FINISHED] [..]") + .run(); + + p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot build") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr( + "\ +error: the feature `print-im-a-teapot` is not in the list of allowed features: {\"test-dummy-unstable\"} +", + ) + .run(); + + p.cargo("-Zallow-features= build") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + the feature `test-dummy-unstable` is not in the list of allowed features: {} +", + ) + .run(); +} + #[cargo_test] fn nightly_feature_requires_nightly() { let p = project()