Skip to content

Commit 58a20bb

Browse files
committed
Add require_explicit_unstable_features option
1 parent 3239a0e commit 58a20bb

File tree

6 files changed

+130
-20
lines changed

6 files changed

+130
-20
lines changed

rust/private/rust.bzl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,16 @@ _common_attrs = {
753753
cfg = "exec",
754754
providers = [[CrateInfo], [CrateGroupInfo]],
755755
),
756+
"require_explicit_unstable_features": attr.int(
757+
doc = (
758+
"Whether to require all unstable features to be explicitly opted in to using " +
759+
"`-Zallow-features=...`. Possible values: [-1, 0, 1]. -1 means delegate to the " +
760+
"toolchain.require_explicit_unstable_features boolean build setting; 0 means False; " +
761+
"1 means True."
762+
),
763+
values = [-1, 0, 1],
764+
default = -1,
765+
),
756766
"rustc_env": attr.string_dict(
757767
doc = dedent("""\
758768
Dictionary of additional `"key": "value"` environment variables to set for rustc.

rust/private/rustc.bzl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,7 @@ def construct_arguments(
835835
build_metadata = False,
836836
force_depend_on_objects = False,
837837
skip_expanding_rustc_env = False,
838+
require_explicit_unstable_features = False,
838839
error_format = None):
839840
"""Builds an Args object containing common rustc flags
840841
@@ -867,6 +868,7 @@ def construct_arguments(
867868
build_metadata (bool): Generate CLI arguments for building *only* .rmeta files. This requires use_json_output.
868869
force_depend_on_objects (bool): Force using `.rlib` object files instead of metadata (`.rmeta`) files even if they are available.
869870
skip_expanding_rustc_env (bool): Whether to skip expanding CrateInfo.rustc_env_attr
871+
require_explicit_unstable_features (bool): Whether to require all unstable features to be explicitly opted in to using `-Zallow-features=...`.
870872
error_format (str, optional): Error format to pass to the `--error-format` command line argument. If set to None, uses the "_error_format" entry in `attr`.
871873
872874
Returns:
@@ -895,6 +897,9 @@ def construct_arguments(
895897

896898
process_wrapper_flags.add_all(build_flags_files, before_each = "--arg-file")
897899

900+
if require_explicit_unstable_features:
901+
process_wrapper_flags.add("--require-explicit-unstable-features", "true")
902+
898903
# Certain rust build processes expect to find files from the environment
899904
# variable `$CARGO_MANIFEST_DIR`. Examples of this include pest, tera,
900905
# asakuma.
@@ -1289,6 +1294,16 @@ def rustc_compile_action(
12891294
if experimental_use_cc_common_link:
12901295
emit = ["obj"]
12911296

1297+
# Determine whether to pass `--require-explicit-unstable-features true` to the process wrapper:
1298+
require_explicit_unstable_features = False
1299+
if hasattr(ctx.attr, "require_explicit_unstable_features"):
1300+
if ctx.attr.require_explicit_unstable_features == 0:
1301+
require_explicit_unstable_features = False
1302+
elif ctx.attr.require_explicit_unstable_features == 1:
1303+
require_explicit_unstable_features = True
1304+
elif ctx.attr.require_explicit_unstable_features == -1:
1305+
require_explicit_unstable_features = toolchain.require_explicit_unstable_features
1306+
12921307
args, env_from_args = construct_arguments(
12931308
ctx = ctx,
12941309
attr = attr,
@@ -1311,6 +1326,7 @@ def rustc_compile_action(
13111326
stamp = stamp,
13121327
use_json_output = bool(build_metadata) or bool(rustc_output) or bool(rustc_rmeta_output),
13131328
skip_expanding_rustc_env = skip_expanding_rustc_env,
1329+
require_explicit_unstable_features = require_explicit_unstable_features,
13141330
)
13151331

13161332
args_metadata = None
@@ -1337,6 +1353,7 @@ def rustc_compile_action(
13371353
stamp = stamp,
13381354
use_json_output = True,
13391355
build_metadata = True,
1356+
require_explicit_unstable_features = require_explicit_unstable_features,
13401357
)
13411358

13421359
env = dict(ctx.configuration.default_shell_env)

rust/settings/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ load(
3131
"no_std",
3232
"pipelined_compilation",
3333
"rename_first_party_crates",
34+
"require_explicit_unstable_features",
3435
"rustc_output_diagnostics",
3536
"rustfmt_toml",
3637
"third_party_dir",
@@ -119,6 +120,8 @@ pipelined_compilation()
119120

120121
rename_first_party_crates()
121122

123+
require_explicit_unstable_features()
124+
122125
rustc_output_diagnostics()
123126

124127
rustfmt_toml()

rust/settings/settings.bzl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,19 @@ def rename_first_party_crates():
7878
build_setting_default = False,
7979
)
8080

81+
def require_explicit_unstable_features():
82+
"""A flag controlling whether unstable features should be disallowed by default
83+
84+
If true, an empty `-Zallow-features=` will be added to the rustc command line whenever no other
85+
`-Zallow-features=` is present in the rustc flags. The effect is to disallow all unstable
86+
features by default, with the possibility to explicitly re-enable them selectively using
87+
`-Zallow-features=...`.
88+
"""
89+
bool_flag(
90+
name = "require_explicit_unstable_features",
91+
build_setting_default = False,
92+
)
93+
8194
def third_party_dir():
8295
"""A flag specifying the location of vendored third-party rust crates within this \
8396
repository that must not be renamed when `rename_first_party_crates` is enabled.

rust/toolchain.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,9 @@ def _generate_sysroot(
538538
def _experimental_use_cc_common_link(ctx):
539539
return ctx.attr.experimental_use_cc_common_link[BuildSettingInfo].value
540540

541+
def _require_explicit_unstable_features(ctx):
542+
return ctx.attr.require_explicit_unstable_features[BuildSettingInfo].value
543+
541544
def _expand_flags(ctx, attr_name, targets, make_variables):
542545
targets = deduplicate(targets)
543546
expanded_flags = []
@@ -753,6 +756,7 @@ def _rust_toolchain_impl(ctx):
753756
target_json = target_json,
754757
target_os = target_os,
755758
target_triple = target_triple,
759+
require_explicit_unstable_features = _require_explicit_unstable_features(ctx),
756760

757761
# Experimental and incompatible flags
758762
_rename_first_party_crates = rename_first_party_crates,
@@ -904,6 +908,13 @@ rust_toolchain = rule(
904908
"per_crate_rustc_flags": attr.string_list(
905909
doc = "Extra flags to pass to rustc in non-exec configuration",
906910
),
911+
"require_explicit_unstable_features": attr.label(
912+
default = Label(
913+
"//rust/settings:require_explicit_unstable_features",
914+
),
915+
doc = ("Label to a boolean build setting that controls whether all uses of unstable " +
916+
"Rust features must be explicitly opted in to using `-Zallow-features=...`."),
917+
),
907918
"rust_doc": attr.label(
908919
doc = "The location of the `rustdoc` binary. Can be a direct source or a filegroup containing one item.",
909920
allow_single_file = True,

util/process_wrapper/options.rs

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ pub(crate) fn options() -> Result<Options, OptionError> {
6767
let mut rustc_quit_on_rmeta_raw = None;
6868
let mut rustc_output_format_raw = None;
6969
let mut flags = Flags::new();
70+
let mut require_explicit_unstable_features = None;
7071
flags.define_repeated_flag("--subst", "", &mut subst_mapping_raw);
7172
flags.define_flag("--stable-status-file", "", &mut stable_status_file_raw);
7273
flags.define_flag("--volatile-status-file", "", &mut volatile_status_file_raw);
@@ -114,6 +115,12 @@ pub(crate) fn options() -> Result<Options, OptionError> {
114115
Default: `rendered`",
115116
&mut rustc_output_format_raw,
116117
);
118+
flags.define_flag(
119+
"--require-explicit-unstable-features",
120+
"If set, an empty -Zallow-features= will be added to the rustc command line whenever no \
121+
other -Zallow-features= is present in the rustc flags.",
122+
&mut require_explicit_unstable_features,
123+
);
117124

118125
let mut child_args = match flags
119126
.parse(env::args().collect())
@@ -191,9 +198,13 @@ pub(crate) fn options() -> Result<Options, OptionError> {
191198
&volatile_stamp_mappings,
192199
&subst_mappings,
193200
);
201+
202+
let require_explicit_unstable_features =
203+
require_explicit_unstable_features.is_some_and(|s| s == "true");
204+
194205
// Append all the arguments fetched from files to those provided via command line.
195206
child_args.append(&mut file_arguments);
196-
let child_args = prepare_args(child_args, &subst_mappings)?;
207+
let child_args = prepare_args(child_args, &subst_mappings, require_explicit_unstable_features)?;
197208
// Split the executable path from the rest of the arguments.
198209
let (exec_path, args) = child_args.split_first().ok_or_else(|| {
199210
OptionError::Generic(
@@ -243,6 +254,10 @@ fn env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionE
243254
Ok(env_vars)
244255
}
245256

257+
fn is_allow_features_flag(arg: &str) -> bool {
258+
arg.starts_with("-Zallow-features=") || arg.starts_with("allow-features=")
259+
}
260+
246261
fn prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String {
247262
for (f, replace_with) in subst_mappings {
248263
let from = format!("${{{f}}}");
@@ -251,11 +266,12 @@ fn prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String {
251266
arg
252267
}
253268

254-
/// Apply substitutions to the given param file. Returns the new filename.
269+
/// Apply substitutions to the given param file. Returns the new filename and whether any
270+
/// allow-features flags were found.
255271
fn prepare_param_file(
256272
filename: &str,
257273
subst_mappings: &[(String, String)],
258-
) -> Result<String, OptionError> {
274+
) -> Result<(String, bool), OptionError> {
259275
let expanded_file = format!("{filename}.expanded");
260276
let format_err = |err: io::Error| {
261277
OptionError::Generic(format!(
@@ -271,38 +287,48 @@ fn prepare_param_file(
271287
out: &mut io::BufWriter<File>,
272288
subst_mappings: &[(String, String)],
273289
format_err: &impl Fn(io::Error) -> OptionError,
274-
) -> Result<(), OptionError> {
290+
) -> Result<bool, OptionError> {
291+
let mut has_allow_features_flag = false;
275292
for arg in read_file_to_array(filename).map_err(OptionError::Generic)? {
276293
let arg = prepare_arg(arg, subst_mappings);
294+
has_allow_features_flag |= is_allow_features_flag(&arg);
277295
if let Some(arg_file) = arg.strip_prefix('@') {
278-
process_file(arg_file, out, subst_mappings, format_err)?;
296+
has_allow_features_flag |= process_file(arg_file, out, subst_mappings, format_err)?;
279297
} else {
280298
writeln!(out, "{arg}").map_err(format_err)?;
281299
}
282300
}
283-
Ok(())
301+
Ok(has_allow_features_flag)
284302
}
285-
process_file(filename, &mut out, subst_mappings, &format_err)?;
286-
Ok(expanded_file)
303+
let has_allow_features_flag = process_file(filename, &mut out, subst_mappings, &format_err)?;
304+
Ok((expanded_file, has_allow_features_flag))
287305
}
288306

289307
/// Apply substitutions to the provided arguments, recursing into param files.
290308
fn prepare_args(
291309
args: Vec<String>,
292310
subst_mappings: &[(String, String)],
311+
require_explicit_unstable_features: bool,
293312
) -> Result<Vec<String>, OptionError> {
294-
args.into_iter()
295-
.map(|arg| {
296-
let arg = prepare_arg(arg, subst_mappings);
297-
if let Some(param_file) = arg.strip_prefix('@') {
298-
// Note that substitutions may also apply to the param file path!
299-
prepare_param_file(param_file, subst_mappings)
300-
.map(|filename| format!("@{filename}"))
301-
} else {
302-
Ok(arg)
303-
}
304-
})
305-
.collect()
313+
let mut allowed_features = false;
314+
let mut processed_args = Vec::<String>::new();
315+
for arg in args.into_iter() {
316+
let arg = prepare_arg(arg, subst_mappings);
317+
if let Some(param_file) = arg.strip_prefix('@') {
318+
// Note that substitutions may also apply to the param file path!
319+
let (file, allowed) = prepare_param_file(param_file, subst_mappings)
320+
.map(|(filename, af)| (format!("@{filename}"), af))?;
321+
allowed_features |= allowed;
322+
processed_args.push(file);
323+
} else {
324+
allowed_features |= is_allow_features_flag(&arg);
325+
processed_args.push(arg);
326+
}
327+
}
328+
if !allowed_features && require_explicit_unstable_features {
329+
processed_args.push("-Zallow-features=".to_string());
330+
}
331+
Ok(processed_args)
306332
}
307333

308334
fn environment_block(
@@ -334,3 +360,33 @@ fn environment_block(
334360
}
335361
environment_variables
336362
}
363+
364+
#[cfg(test)]
365+
mod test {
366+
use super::*;
367+
368+
#[test]
369+
fn test_enforce_allow_features_flag_user_didnt_say() {
370+
let args = vec!["rustc".to_string()];
371+
let subst_mappings: Vec<(String, String)> = vec![];
372+
let args = prepare_args(args, &subst_mappings).unwrap();
373+
assert_eq!(args, vec!["rustc".to_string(), "-Zallow-features=".to_string(),]);
374+
}
375+
376+
#[test]
377+
fn test_enforce_allow_features_flag_user_requested_something() {
378+
let args = vec![
379+
"rustc".to_string(),
380+
"-Zallow-features=whitespace_instead_of_curly_braces".to_string(),
381+
];
382+
let subst_mappings: Vec<(String, String)> = vec![];
383+
let args = prepare_args(args, &subst_mappings).unwrap();
384+
assert_eq!(
385+
args,
386+
vec![
387+
"rustc".to_string(),
388+
"-Zallow-features=whitespace_instead_of_curly_braces".to_string(),
389+
]
390+
);
391+
}
392+
}

0 commit comments

Comments
 (0)