Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions rust/private/rust.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,16 @@ _common_attrs = {
cfg = "exec",
providers = [[CrateInfo], [CrateGroupInfo]],
),
"require_explicit_unstable_features": attr.int(
doc = (
"Whether to require all unstable features to be explicitly opted in to using " +
"`-Zallow-features=...`. Possible values: [-1, 0, 1]. -1 means delegate to the " +
"toolchain.require_explicit_unstable_features boolean build setting; 0 means False; " +
"1 means True."
),
values = [-1, 0, 1],
default = -1,
),
"rustc_env": attr.string_dict(
doc = dedent("""\
Dictionary of additional `"key": "value"` environment variables to set for rustc.
Expand Down
17 changes: 17 additions & 0 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ def construct_arguments(
build_metadata = False,
force_depend_on_objects = False,
skip_expanding_rustc_env = False,
require_explicit_unstable_features = False,
error_format = None):
"""Builds an Args object containing common rustc flags

Expand Down Expand Up @@ -867,6 +868,7 @@ def construct_arguments(
build_metadata (bool): Generate CLI arguments for building *only* .rmeta files. This requires use_json_output.
force_depend_on_objects (bool): Force using `.rlib` object files instead of metadata (`.rmeta`) files even if they are available.
skip_expanding_rustc_env (bool): Whether to skip expanding CrateInfo.rustc_env_attr
require_explicit_unstable_features (bool): Whether to require all unstable features to be explicitly opted in to using `-Zallow-features=...`.
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`.

Returns:
Expand Down Expand Up @@ -895,6 +897,9 @@ def construct_arguments(

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

if require_explicit_unstable_features:
process_wrapper_flags.add("--require-explicit-unstable-features", "true")

# Certain rust build processes expect to find files from the environment
# variable `$CARGO_MANIFEST_DIR`. Examples of this include pest, tera,
# asakuma.
Expand Down Expand Up @@ -1289,6 +1294,16 @@ def rustc_compile_action(
if experimental_use_cc_common_link:
emit = ["obj"]

# Determine whether to pass `--require-explicit-unstable-features true` to the process wrapper:
require_explicit_unstable_features = False
if hasattr(ctx.attr, "require_explicit_unstable_features"):
if ctx.attr.require_explicit_unstable_features == 0:
require_explicit_unstable_features = False
elif ctx.attr.require_explicit_unstable_features == 1:
require_explicit_unstable_features = True
elif ctx.attr.require_explicit_unstable_features == -1:
require_explicit_unstable_features = toolchain.require_explicit_unstable_features

args, env_from_args = construct_arguments(
ctx = ctx,
attr = attr,
Expand All @@ -1311,6 +1326,7 @@ def rustc_compile_action(
stamp = stamp,
use_json_output = bool(build_metadata) or bool(rustc_output) or bool(rustc_rmeta_output),
skip_expanding_rustc_env = skip_expanding_rustc_env,
require_explicit_unstable_features = require_explicit_unstable_features,
)

args_metadata = None
Expand All @@ -1337,6 +1353,7 @@ def rustc_compile_action(
stamp = stamp,
use_json_output = True,
build_metadata = True,
require_explicit_unstable_features = require_explicit_unstable_features,
)

env = dict(ctx.configuration.default_shell_env)
Expand Down
3 changes: 3 additions & 0 deletions rust/settings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ load(
"no_std",
"pipelined_compilation",
"rename_first_party_crates",
"require_explicit_unstable_features",
"rustc_output_diagnostics",
"rustfmt_toml",
"third_party_dir",
Expand Down Expand Up @@ -119,6 +120,8 @@ pipelined_compilation()

rename_first_party_crates()

require_explicit_unstable_features()

rustc_output_diagnostics()

rustfmt_toml()
Expand Down
13 changes: 13 additions & 0 deletions rust/settings/settings.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ def rename_first_party_crates():
build_setting_default = False,
)

def require_explicit_unstable_features():
"""A flag controlling whether unstable features should be disallowed by default

If true, an empty `-Zallow-features=` will be added to the rustc command line whenever no other
`-Zallow-features=` is present in the rustc flags. The effect is to disallow all unstable
features by default, with the possibility to explicitly re-enable them selectively using
`-Zallow-features=...`.
"""
bool_flag(
name = "require_explicit_unstable_features",
build_setting_default = False,
)

def third_party_dir():
"""A flag specifying the location of vendored third-party rust crates within this \
repository that must not be renamed when `rename_first_party_crates` is enabled.
Expand Down
11 changes: 11 additions & 0 deletions rust/toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,9 @@ def _generate_sysroot(
def _experimental_use_cc_common_link(ctx):
return ctx.attr.experimental_use_cc_common_link[BuildSettingInfo].value

def _require_explicit_unstable_features(ctx):
return ctx.attr.require_explicit_unstable_features[BuildSettingInfo].value

def _expand_flags(ctx, attr_name, targets, make_variables):
targets = deduplicate(targets)
expanded_flags = []
Expand Down Expand Up @@ -753,6 +756,7 @@ def _rust_toolchain_impl(ctx):
target_json = target_json,
target_os = target_os,
target_triple = target_triple,
require_explicit_unstable_features = _require_explicit_unstable_features(ctx),

# Experimental and incompatible flags
_rename_first_party_crates = rename_first_party_crates,
Expand Down Expand Up @@ -904,6 +908,13 @@ rust_toolchain = rule(
"per_crate_rustc_flags": attr.string_list(
doc = "Extra flags to pass to rustc in non-exec configuration",
),
"require_explicit_unstable_features": attr.label(
default = Label(
"//rust/settings:require_explicit_unstable_features",
),
doc = ("Label to a boolean build setting that controls whether all uses of unstable " +
"Rust features must be explicitly opted in to using `-Zallow-features=...`."),
),
"rust_doc": attr.label(
doc = "The location of the `rustdoc` binary. Can be a direct source or a filegroup containing one item.",
allow_single_file = True,
Expand Down
96 changes: 76 additions & 20 deletions util/process_wrapper/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub(crate) fn options() -> Result<Options, OptionError> {
let mut rustc_quit_on_rmeta_raw = None;
let mut rustc_output_format_raw = None;
let mut flags = Flags::new();
let mut require_explicit_unstable_features = None;
flags.define_repeated_flag("--subst", "", &mut subst_mapping_raw);
flags.define_flag("--stable-status-file", "", &mut stable_status_file_raw);
flags.define_flag("--volatile-status-file", "", &mut volatile_status_file_raw);
Expand Down Expand Up @@ -114,6 +115,12 @@ pub(crate) fn options() -> Result<Options, OptionError> {
Default: `rendered`",
&mut rustc_output_format_raw,
);
flags.define_flag(
"--require-explicit-unstable-features",
"If set, an empty -Zallow-features= will be added to the rustc command line whenever no \
other -Zallow-features= is present in the rustc flags.",
&mut require_explicit_unstable_features,
);

let mut child_args = match flags
.parse(env::args().collect())
Expand Down Expand Up @@ -191,9 +198,13 @@ pub(crate) fn options() -> Result<Options, OptionError> {
&volatile_stamp_mappings,
&subst_mappings,
);

let require_explicit_unstable_features =
require_explicit_unstable_features.is_some_and(|s| s == "true");

// Append all the arguments fetched from files to those provided via command line.
child_args.append(&mut file_arguments);
let child_args = prepare_args(child_args, &subst_mappings)?;
let child_args = prepare_args(child_args, &subst_mappings, require_explicit_unstable_features)?;
// Split the executable path from the rest of the arguments.
let (exec_path, args) = child_args.split_first().ok_or_else(|| {
OptionError::Generic(
Expand Down Expand Up @@ -243,6 +254,10 @@ fn env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionE
Ok(env_vars)
}

fn is_allow_features_flag(arg: &str) -> bool {
arg.starts_with("-Zallow-features=") || arg.starts_with("allow-features=")
}

fn prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String {
for (f, replace_with) in subst_mappings {
let from = format!("${{{f}}}");
Expand All @@ -251,11 +266,12 @@ fn prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String {
arg
}

/// Apply substitutions to the given param file. Returns the new filename.
/// Apply substitutions to the given param file. Returns the new filename and whether any
/// allow-features flags were found.
fn prepare_param_file(
filename: &str,
subst_mappings: &[(String, String)],
) -> Result<String, OptionError> {
) -> Result<(String, bool), OptionError> {
let expanded_file = format!("{filename}.expanded");
let format_err = |err: io::Error| {
OptionError::Generic(format!(
Expand All @@ -271,38 +287,48 @@ fn prepare_param_file(
out: &mut io::BufWriter<File>,
subst_mappings: &[(String, String)],
format_err: &impl Fn(io::Error) -> OptionError,
) -> Result<(), OptionError> {
) -> Result<bool, OptionError> {
let mut has_allow_features_flag = false;
for arg in read_file_to_array(filename).map_err(OptionError::Generic)? {
let arg = prepare_arg(arg, subst_mappings);
has_allow_features_flag |= is_allow_features_flag(&arg);
if let Some(arg_file) = arg.strip_prefix('@') {
process_file(arg_file, out, subst_mappings, format_err)?;
has_allow_features_flag |= process_file(arg_file, out, subst_mappings, format_err)?;
} else {
writeln!(out, "{arg}").map_err(format_err)?;
}
}
Ok(())
Ok(has_allow_features_flag)
}
process_file(filename, &mut out, subst_mappings, &format_err)?;
Ok(expanded_file)
let has_allow_features_flag = process_file(filename, &mut out, subst_mappings, &format_err)?;
Ok((expanded_file, has_allow_features_flag))
}

/// Apply substitutions to the provided arguments, recursing into param files.
fn prepare_args(
args: Vec<String>,
subst_mappings: &[(String, String)],
require_explicit_unstable_features: bool,
) -> Result<Vec<String>, OptionError> {
args.into_iter()
.map(|arg| {
let arg = prepare_arg(arg, subst_mappings);
if let Some(param_file) = arg.strip_prefix('@') {
// Note that substitutions may also apply to the param file path!
prepare_param_file(param_file, subst_mappings)
.map(|filename| format!("@{filename}"))
} else {
Ok(arg)
}
})
.collect()
let mut allowed_features = false;
let mut processed_args = Vec::<String>::new();
for arg in args.into_iter() {
let arg = prepare_arg(arg, subst_mappings);
if let Some(param_file) = arg.strip_prefix('@') {
// Note that substitutions may also apply to the param file path!
let (file, allowed) = prepare_param_file(param_file, subst_mappings)
.map(|(filename, af)| (format!("@{filename}"), af))?;
allowed_features |= allowed;
processed_args.push(file);
} else {
allowed_features |= is_allow_features_flag(&arg);
processed_args.push(arg);
}
}
if !allowed_features && require_explicit_unstable_features {
processed_args.push("-Zallow-features=".to_string());
}
Ok(processed_args)
}

fn environment_block(
Expand Down Expand Up @@ -334,3 +360,33 @@ fn environment_block(
}
environment_variables
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_enforce_allow_features_flag_user_didnt_say() {
let args = vec!["rustc".to_string()];
let subst_mappings: Vec<(String, String)> = vec![];
let args = prepare_args(args, &subst_mappings).unwrap();
assert_eq!(args, vec!["rustc".to_string(), "-Zallow-features=".to_string(),]);
}

#[test]
fn test_enforce_allow_features_flag_user_requested_something() {
let args = vec![
"rustc".to_string(),
"-Zallow-features=whitespace_instead_of_curly_braces".to_string(),
];
let subst_mappings: Vec<(String, String)> = vec![];
let args = prepare_args(args, &subst_mappings).unwrap();
assert_eq!(
args,
vec![
"rustc".to_string(),
"-Zallow-features=whitespace_instead_of_curly_braces".to_string(),
]
);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you also come up with a test that recurses into @-param files?

}