Skip to content

Commit 10ef854

Browse files
committed
Auto merge of #9283 - jonhoo:z-allowed-features, r=ehuss
Add -Zallow-features to match rustc's -Z This PR implements the `-Zallow-features` permanently-unstable feature flag that explicitly enumerates which unstable features are allowed (assuming unstable features are permitted in the first place). This mirrors the `-Zallow-features` flag of `rustc` which serves the same purpose for `rustc` features: https://github.com/rust-lang/rust/blob/5fe790e3c40710ecb95ddaadb98b59a3bb4f8326/compiler/rustc_session/src/options.rs#L856-L857 This flag makes it easier to beta-test unstable features "safely" by ensuring that only a single unstable feature is used if you only have control over build system, and not the source code that developers may end up using, as discussed in [this internals thread](https://internals.rust-lang.org/t/mechanism-for-beta-testing-unstable-features/14280).
2 parents 3c44c3c + 8d79a75 commit 10ef854

File tree

7 files changed

+330
-33
lines changed

7 files changed

+330
-33
lines changed

src/bin/cargo/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub fn main(config: &mut Config) -> CliResult {
3535
"
3636
Available unstable (nightly-only) flags:
3737
38+
-Z allow-features -- Allow *only* the listed unstable features
3839
-Z avoid-dev-deps -- Avoid installing dev-dependencies if possible
3940
-Z extra-link-arg -- Allow `cargo:rustc-link-arg` in build scripts
4041
-Z minimal-versions -- Install minimal dependency versions instead of maximum

src/cargo/core/compiler/fingerprint.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,14 +1327,18 @@ fn calculate_normal(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Finger
13271327
// Include metadata since it is exposed as environment variables.
13281328
let m = unit.pkg.manifest().metadata();
13291329
let metadata = util::hash_u64((&m.authors, &m.description, &m.homepage, &m.repository));
1330-
let config = if unit.mode.is_doc() && cx.bcx.config.cli_unstable().rustdoc_map {
1331-
cx.bcx
1332-
.config
1333-
.doc_extern_map()
1334-
.map_or(0, |map| util::hash_u64(map))
1335-
} else {
1336-
0
1337-
};
1330+
let mut config = 0u64;
1331+
if unit.mode.is_doc() && cx.bcx.config.cli_unstable().rustdoc_map {
1332+
config = config.wrapping_add(
1333+
cx.bcx
1334+
.config
1335+
.doc_extern_map()
1336+
.map_or(0, |map| util::hash_u64(map)),
1337+
);
1338+
}
1339+
if let Some(allow_features) = &cx.bcx.config.cli_unstable().allow_features {
1340+
config = config.wrapping_add(util::hash_u64(allow_features));
1341+
}
13381342
let compile_kind = unit.kind.fingerprint_hash();
13391343
Ok(Fingerprint {
13401344
rustc: util::hash_u64(&cx.bcx.rustc().verbose_version),

src/cargo/core/compiler/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ use crate::core::{Feature, PackageId, Target};
5757
use crate::util::errors::{CargoResult, CargoResultExt, VerboseError};
5858
use crate::util::interning::InternedString;
5959
use crate::util::machine_message::{self, Message};
60-
use crate::util::{add_path_args, internal, profile};
60+
use crate::util::{add_path_args, internal, iter_join_onto, profile};
6161
use cargo_util::{paths, ProcessBuilder, ProcessError};
6262

6363
const RUSTDOC_CRATE_VERSION_FLAG: &str = "--crate-version";
@@ -615,6 +615,7 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
615615
}
616616

617617
add_error_format_and_color(cx, &mut rustdoc, false);
618+
add_allow_features(cx, &mut rustdoc);
618619

619620
if let Some(args) = cx.bcx.extra_args_for(unit) {
620621
rustdoc.args(args);
@@ -697,6 +698,15 @@ fn add_cap_lints(bcx: &BuildContext<'_, '_>, unit: &Unit, cmd: &mut ProcessBuild
697698
}
698699
}
699700

701+
/// Forward -Zallow-features if it is set for cargo.
702+
fn add_allow_features(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder) {
703+
if let Some(allow) = &cx.bcx.config.cli_unstable().allow_features {
704+
let mut arg = String::from("-Zallow-features=");
705+
let _ = iter_join_onto(&mut arg, allow, ",");
706+
cmd.arg(&arg);
707+
}
708+
}
709+
700710
/// Add error-format flags to the command.
701711
///
702712
/// Cargo always uses JSON output. This has several benefits, such as being
@@ -773,6 +783,7 @@ fn build_base_args(
773783

774784
add_path_args(bcx.ws, unit, cmd);
775785
add_error_format_and_color(cx, cmd, cx.rmeta_required(unit));
786+
add_allow_features(cx, cmd);
776787

777788
if !test {
778789
for crate_type in crate_types.iter() {

src/cargo/core/features.rs

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
//! of that page. Update the rest of the documentation to add the new
9393
//! feature.
9494
95+
use std::collections::BTreeSet;
9596
use std::env;
9697
use std::fmt;
9798
use std::str::FromStr;
@@ -101,7 +102,7 @@ use cargo_util::ProcessBuilder;
101102
use serde::{Deserialize, Serialize};
102103

103104
use crate::util::errors::CargoResult;
104-
use crate::util::indented_lines;
105+
use crate::util::{indented_lines, iter_join};
105106
use crate::Config;
106107

107108
pub const SEE_CHANNELS: &str =
@@ -416,13 +417,18 @@ impl Features {
416417
let mut ret = Features::default();
417418
ret.nightly_features_allowed = config.nightly_features_allowed;
418419
for feature in features {
419-
ret.add(feature, warnings)?;
420+
ret.add(feature, config, warnings)?;
420421
ret.activated.push(feature.to_string());
421422
}
422423
Ok(ret)
423424
}
424425

425-
fn add(&mut self, feature_name: &str, warnings: &mut Vec<String>) -> CargoResult<()> {
426+
fn add(
427+
&mut self,
428+
feature_name: &str,
429+
config: &Config,
430+
warnings: &mut Vec<String>,
431+
) -> CargoResult<()> {
426432
let nightly_features_allowed = self.nightly_features_allowed;
427433
let (slot, feature) = match self.status(feature_name) {
428434
Some(p) => p,
@@ -470,7 +476,17 @@ impl Features {
470476
SEE_CHANNELS,
471477
see_docs()
472478
),
473-
Status::Unstable => {}
479+
Status::Unstable => {
480+
if let Some(allow) = &config.cli_unstable().allow_features {
481+
if !allow.contains(feature_name) {
482+
bail!(
483+
"the feature `{}` is not in the list of allowed features: [{}]",
484+
feature_name,
485+
iter_join(allow, ", "),
486+
);
487+
}
488+
}
489+
}
474490
Status::Removed => bail!(
475491
"the cargo feature `{}` has been removed\n\
476492
Remove the feature from Cargo.toml to remove this error.\n\
@@ -530,37 +546,42 @@ impl Features {
530546
#[derive(Default, Debug, Deserialize)]
531547
#[serde(default, rename_all = "kebab-case")]
532548
pub struct CliUnstable {
549+
// Permanently unstable features:
550+
pub allow_features: Option<BTreeSet<String>>,
533551
pub print_im_a_teapot: bool,
534-
pub unstable_options: bool,
535-
pub no_index_update: bool,
536-
pub avoid_dev_deps: bool,
537-
pub minimal_versions: bool,
552+
553+
// All other unstable features.
554+
// Please keep this list lexiographically ordered.
538555
pub advanced_env: bool,
539-
pub config_include: bool,
540-
pub dual_proc_macros: bool,
541-
pub mtime_on_use: bool,
542-
pub named_profiles: bool,
556+
pub avoid_dev_deps: bool,
543557
pub binary_dep_depinfo: bool,
544558
#[serde(deserialize_with = "deserialize_build_std")]
545559
pub build_std: Option<Vec<String>>,
546560
pub build_std_features: Option<Vec<String>>,
547-
pub timings: Option<Vec<String>>,
548-
pub doctest_xcompile: bool,
561+
pub config_include: bool,
562+
pub configurable_env: bool,
563+
pub credential_process: bool,
549564
pub doctest_in_workspace: bool,
550-
pub panic_abort_tests: bool,
551-
pub jobserver_per_rustc: bool,
565+
pub doctest_xcompile: bool,
566+
pub dual_proc_macros: bool,
567+
pub enable_future_incompat_feature: bool,
568+
pub extra_link_arg: bool,
552569
pub features: Option<Vec<String>>,
553-
pub separate_nightlies: bool,
570+
pub jobserver_per_rustc: bool,
571+
pub minimal_versions: bool,
572+
pub mtime_on_use: bool,
554573
pub multitarget: bool,
574+
pub named_profiles: bool,
575+
pub namespaced_features: bool,
576+
pub no_index_update: bool,
577+
pub panic_abort_tests: bool,
578+
pub patch_in_config: bool,
555579
pub rustdoc_map: bool,
580+
pub separate_nightlies: bool,
556581
pub terminal_width: Option<Option<usize>>,
557-
pub namespaced_features: bool,
582+
pub timings: Option<Vec<String>>,
583+
pub unstable_options: bool,
558584
pub weak_dep_features: bool,
559-
pub extra_link_arg: bool,
560-
pub patch_in_config: bool,
561-
pub credential_process: bool,
562-
pub configurable_env: bool,
563-
pub enable_future_incompat_feature: bool,
564585
}
565586

566587
const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
@@ -627,6 +648,13 @@ impl CliUnstable {
627648
);
628649
}
629650
let mut warnings = Vec::new();
651+
// We read flags twice, first to get allowed-features (if specified),
652+
// and then to read the remaining unstable flags.
653+
for flag in flags {
654+
if flag.starts_with("allow-features=") {
655+
self.add(flag, &mut warnings)?;
656+
}
657+
}
630658
for flag in flags {
631659
self.add(flag, &mut warnings)?;
632660
}
@@ -656,6 +684,7 @@ impl CliUnstable {
656684
fn parse_features(value: Option<&str>) -> Vec<String> {
657685
match value {
658686
None => Vec::new(),
687+
Some("") => Vec::new(),
659688
Some(v) => v.split(',').map(|s| s.to_string()).collect(),
660689
}
661690
}
@@ -698,8 +727,19 @@ impl CliUnstable {
698727
))
699728
};
700729

730+
if let Some(allowed) = &self.allow_features {
731+
if k != "allow-features" && !allowed.contains(k) {
732+
bail!(
733+
"the feature `{}` is not in the list of allowed features: [{}]",
734+
k,
735+
iter_join(allowed, ", ")
736+
);
737+
}
738+
}
739+
701740
match k {
702741
"print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?,
742+
"allow-features" => self.allow_features = Some(parse_features(v).into_iter().collect()),
703743
"unstable-options" => self.unstable_options = parse_empty(k, v)?,
704744
"no-index-update" => self.no_index_update = parse_empty(k, v)?,
705745
"avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?,

src/cargo/util/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::fmt;
12
use std::time::Duration;
23

34
pub use self::canonical_url::CanonicalUrl;
@@ -65,6 +66,32 @@ pub fn elapsed(duration: Duration) -> String {
6566
}
6667
}
6768

69+
pub fn iter_join_onto<W, I, T>(mut w: W, iter: I, delim: &str) -> fmt::Result
70+
where
71+
W: fmt::Write,
72+
I: IntoIterator<Item = T>,
73+
T: std::fmt::Display,
74+
{
75+
let mut it = iter.into_iter().peekable();
76+
while let Some(n) = it.next() {
77+
write!(w, "{}", n)?;
78+
if it.peek().is_some() {
79+
write!(w, "{}", delim)?;
80+
}
81+
}
82+
Ok(())
83+
}
84+
85+
pub fn iter_join<I, T>(iter: I, delim: &str) -> String
86+
where
87+
I: IntoIterator<Item = T>,
88+
T: std::fmt::Display,
89+
{
90+
let mut s = String::new();
91+
let _ = iter_join_onto(&mut s, iter, delim);
92+
s
93+
}
94+
6895
pub fn indented_lines(text: &str) -> String {
6996
text.lines()
7097
.map(|line| {

src/doc/src/reference/unstable.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,32 @@ Each new feature described below should explain how to use it.
5858
[nightly channel]: ../../book/appendix-07-nightly-rust.html
5959
[stabilized]: https://doc.crates.io/contrib/process/unstable.html#stabilization
6060

61+
### allow-features
62+
63+
This permanently-unstable flag makes it so that only a listed set of
64+
unstable features can be used. Specifically, if you pass
65+
`-Zallow-features=foo,bar`, you'll continue to be able to pass `-Zfoo`
66+
and `-Zbar` to `cargo`, but you will be unable to pass `-Zbaz`. You can
67+
pass an empty string (`-Zallow-features=`) to disallow all unstable
68+
features.
69+
70+
`-Zallow-features` also restricts which unstable features can be passed
71+
to the `cargo-features` entry in `Cargo.toml`. If, for example, you want
72+
to allow
73+
74+
```toml
75+
cargo-features = ["test-dummy-unstable"]
76+
```
77+
78+
where `test-dummy-unstable` is unstable, that features would also be
79+
disallowed by `-Zallow-features=`, and allowed with
80+
`-Zallow-features=test-dummy-unstable`.
81+
82+
The list of features passed to cargo's `-Zallow-features` is also passed
83+
to any Rust tools that cargo ends up calling (like `rustc` or
84+
`rustdoc`). Thus, if you run `cargo -Zallow-features=`, no unstable
85+
Cargo _or_ Rust features can be used.
86+
6187
### extra-link-arg
6288
* Original Pull Request: [#7811](https://github.com/rust-lang/cargo/pull/7811)
6389

0 commit comments

Comments
 (0)