Skip to content
Draft
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
3 changes: 3 additions & 0 deletions rust/private/rust.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,9 @@ RUSTC_ATTRS = {
"_extra_rustc_flags": attr.label(
default = Label("//rust/settings:extra_rustc_flags"),
),
"_experimental_persistent_worker": attr.label(
default = Label("//rust/settings:experimental_persistent_worker"),
),
"_per_crate_rustc_flag": attr.label(
default = Label("//rust/settings:per_crate_rustc_flag"),
),
Expand Down
67 changes: 56 additions & 11 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,12 @@ def has_location_expansion(values):
return True
return False

_WORKER_RESPONSE_FILE_ARG = "--rules-rust-response-file="

def _worker_request_arg(arg):
"""Prevent Bazel from recursively expanding rustc response files in a WorkRequest."""
return _WORKER_RESPONSE_FILE_ARG + arg[1:] if arg.startswith("@") else arg

def _args_map_bin_dir(file):
"""Extract `bazel-out/<config>/bin` from a File whose path lives in the configuration's bin directory.

Expand Down Expand Up @@ -956,7 +962,8 @@ def construct_arguments(
skip_expanding_rustc_env = False,
require_explicit_unstable_features = False,
error_format = None,
allowed_unstable_rust_features = None):
allowed_unstable_rust_features = None,
use_persistent_worker = False):
"""Builds an Args object containing common rustc flags

Args:
Expand Down Expand Up @@ -1028,6 +1035,7 @@ def construct_arguments(
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`.
allowed_unstable_rust_features (list, optional): List of unstable Rust language features allowed for this target.
use_persistent_worker (bool): Whether Bazel will construct worker requests from the rustc param file.

Returns:
tuple: A tuple of the following items
Expand Down Expand Up @@ -1457,6 +1465,8 @@ def construct_arguments(
# require_explicit_unstable_features makes no sense when all features are allowed anyway
if require_explicit_unstable_features:
process_wrapper_flags.add("--require-explicit-unstable-features", "true")
if use_persistent_worker:
extra_rustc_flags = [_worker_request_arg(flag) for flag in extra_rustc_flags]
rustc_flags.add_all(extra_rustc_flags, map_each = map_flag)

if is_no_std(ctx, toolchain, crate_info.is_test):
Expand Down Expand Up @@ -1486,26 +1496,32 @@ def construct_arguments(
elif rust_flags:
for flag in _extract_allowed_unstable_features_from_flags(rust_flags, all_allowed_unstable_features):
if type(flag) in ["tuple", "list"] and len(flag) == 2:
flag_format = _worker_request_arg(flag[0]) if use_persistent_worker else flag[0]
rustc_flags.add_all(
[flag[1]],
format_each = flag[0],
format_each = flag_format,
expand_directories = False,
)
else:
if map_flag:
flag = map_flag(flag)
if flag != None:
if use_persistent_worker:
flag = _worker_request_arg(flag)
rustc_flags.add(flag)

# Add target specific flags last, so they can override previous flags
authored_rustc_flags = getattr(attr, "rustc_flags", [])
expanded_authored_rustc_flags = expand_list_element_locations(
ctx,
authored_rustc_flags,
data_paths,
{},
)
if use_persistent_worker:
expanded_authored_rustc_flags = [_worker_request_arg(flag) for flag in expanded_authored_rustc_flags]
rustc_flags.add_all(
expand_list_element_locations(
ctx,
authored_rustc_flags,
data_paths,
{},
),
expanded_authored_rustc_flags,
map_each = map_flag,
)

Expand Down Expand Up @@ -1713,6 +1729,10 @@ def rustc_compile_action(
#
# Exclude lints if we're building in the exec configuration to prevent crates
# used in build scripts from generating warnings.
opaque_rust_flags = rust_flags if type(rust_flags) == "Args" else None
if opaque_rust_flags != None:
rust_flags = []

lint_files = []
if hasattr(ctx.attr, "lint_config") and ctx.attr.lint_config and not is_exec_configuration(ctx):
rust_flags = rust_flags + ctx.attr.lint_config[LintsInfo].rustc_lint_flags
Expand Down Expand Up @@ -1794,6 +1814,14 @@ def rustc_compile_action(
dwo_outputs = ctx.actions.declare_directory(fission_directory, sibling = crate_info.output)
rust_flags.append(("-Zsplit-dwarf-out-dir=%s", dwo_outputs))

use_persistent_worker = (
bool(ctx.executable._process_wrapper) and
hasattr(ctx.attr, "_experimental_persistent_worker") and
ctx.attr._experimental_persistent_worker[BuildSettingInfo].value and
opaque_rust_flags == None
)
use_incremental = use_persistent_worker and ctx.var["COMPILATION_MODE"] != "opt" and not _will_emit_object_file(emit)

args, env_from_args = construct_arguments(
ctx = ctx,
attr = attr,
Expand All @@ -1818,7 +1846,12 @@ def rustc_compile_action(
skip_expanding_rustc_env = skip_expanding_rustc_env,
require_explicit_unstable_features = require_explicit_unstable_features,
allowed_unstable_rust_features = allowed_unstable_rust_features,
use_persistent_worker = use_persistent_worker,
)
if opaque_rust_flags != None:
args.all.append(opaque_rust_flags)
if use_incremental:
args.process_wrapper_flags.add("--rustc-incremental", "true")

args_metadata = None
if build_metadata:
Expand Down Expand Up @@ -1846,7 +1879,10 @@ def rustc_compile_action(
build_metadata = True,
require_explicit_unstable_features = require_explicit_unstable_features,
allowed_unstable_rust_features = allowed_unstable_rust_features,
use_persistent_worker = use_persistent_worker,
)
if opaque_rust_flags != None:
args_metadata.all.append(opaque_rust_flags)

env = dict(ctx.configuration.default_shell_env)

Expand Down Expand Up @@ -1893,6 +1929,15 @@ def rustc_compile_action(
action_outputs.append(dwo_outputs) # buildifier: disable=uninitialized

if ctx.executable._process_wrapper:
execution_requirements = {}
if args.supports_path_mapping:
execution_requirements["supports-path-mapping"] = ""
if use_persistent_worker:
execution_requirements.update({
"requires-worker-protocol": "proto",
"supports-workers": "1",
})

# Run as normal
ctx.actions.run(
executable = ctx.executable._process_wrapper,
Expand All @@ -1910,7 +1955,7 @@ def rustc_compile_action(
),
toolchain = "@rules_rust//rust:toolchain_type",
resource_set = get_rustc_resource_set(toolchain),
execution_requirements = {"supports-path-mapping": ""} if args.supports_path_mapping else None,
execution_requirements = execution_requirements,
)
if args_metadata:
ctx.actions.run(
Expand All @@ -1928,7 +1973,7 @@ def rustc_compile_action(
"" if len(srcs) == 1 else "s",
),
toolchain = "@rules_rust//rust:toolchain_type",
execution_requirements = {"supports-path-mapping": ""} if args_metadata.supports_path_mapping else None,
execution_requirements = execution_requirements,
)
elif hasattr(ctx.executable, "_bootstrap_process_wrapper"):
# Run without process_wrapper
Expand All @@ -1939,7 +1984,7 @@ def rustc_compile_action(
inputs = compile_inputs,
outputs = action_outputs,
env = env,
arguments = [args.rustc_path, args.rustc_flags],
arguments = [args.rustc_path, args.rustc_flags] + ([opaque_rust_flags] if opaque_rust_flags != None else []),
mnemonic = "Rustc",
progress_message = "Compiling Rust (without process_wrapper) {} {}{} ({} file{})".format(
crate_info.type,
Expand Down
3 changes: 3 additions & 0 deletions rust/settings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ load(
"error_format",
"experimental_compile_rustdoc_tests",
"experimental_link_std_dylib",
"experimental_persistent_worker",
"experimental_use_allocator_libraries_with_mangled_symbols",
"experimental_use_cc_common_link",
"experimental_use_coverage_metadata_files",
Expand Down Expand Up @@ -87,6 +88,8 @@ experimental_compile_rustdoc_tests()

experimental_link_std_dylib()

experimental_persistent_worker()

experimental_use_cc_common_link()

experimental_use_coverage_metadata_files()
Expand Down
11 changes: 11 additions & 0 deletions rust/settings/settings.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ def pipelined_compilation():
build_setting_default = False,
)

def experimental_persistent_worker():
"""Enable persistent rustc workers and incremental compilation for non-opt builds.

Actions remain compatible with one-shot local and remote execution. Persistent-worker-capable
executors can reuse rustc's incremental cache between requests.
"""
bool_flag(
name = "experimental_persistent_worker",
build_setting_default = False,
)

# buildifier: disable=unnamed-macro
def experimental_use_cc_common_link():
"""A flag to control whether to link rust_binary and rust_test targets using \
Expand Down
19 changes: 19 additions & 0 deletions util/process_wrapper/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod options;
mod output;
mod rustc;
mod util;
mod worker;

use std::collections::HashMap;
use std::fmt;
Expand Down Expand Up @@ -118,11 +119,29 @@ fn process_line(
}

fn main() -> Result<(), ProcessWrapperError> {
let argv: Vec<String> = std::env::args().collect();
if let Some(worker_arg_index) = argv.iter().position(|arg| arg == "--persistent_worker") {
let mut startup_argv = argv;
startup_argv.truncate(worker_arg_index);
return worker::run(startup_argv).map_err(|e| ProcessWrapperError(e.to_string()));
}

let opts = options().map_err(|e| ProcessWrapperError(e.to_string()))?;

let incremental_cache = if opts.rustc_incremental {
opts.rustc_incremental_dir
} else {
None
};

let mut command = Command::new(opts.executable);
command
.args(opts.child_arguments)
.args(
incremental_cache
.as_ref()
.map(|cache| format!("-Cincremental={cache}")),
)
.env_clear()
.envs(opts.child_environment)
.stdout(if let Some(stdout_file) = opts.stdout_file {
Expand Down
71 changes: 69 additions & 2 deletions util/process_wrapper/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::flags::{FlagParseError, Flags, ParseOutcome};
use crate::rustc;
use crate::util::*;

const WORKER_RESPONSE_FILE_ARG: &str = "--rules-rust-response-file=";

#[derive(Debug)]
pub(crate) enum OptionError {
FlagError(FlagParseError),
Expand Down Expand Up @@ -49,6 +51,10 @@ pub(crate) struct Options {
pub(crate) rustc_quit_on_rmeta: bool,
// This controls the output format of rustc messages.
pub(crate) rustc_output_format: Option<rustc::ErrorFormat>,
// Enable rustc incremental compilation using a cache owned by this wrapper.
pub(crate) rustc_incremental: bool,
// An optional persistent cache directory supplied by the worker parent.
pub(crate) rustc_incremental_dir: Option<String>,
}

pub(crate) fn options() -> Result<Options, OptionError> {
Expand All @@ -67,6 +73,8 @@ pub(crate) fn options() -> Result<Options, OptionError> {
let mut output_file = None;
let mut rustc_quit_on_rmeta_raw = None;
let mut rustc_output_format_raw = None;
let mut rustc_incremental_raw = None;
let mut rustc_incremental_dir = None;
let mut flags = Flags::new();
let mut require_explicit_unstable_features = None;
flags.define_repeated_flag("--subst", "", &mut subst_mapping_raw);
Expand Down Expand Up @@ -126,6 +134,16 @@ pub(crate) fn options() -> Result<Options, OptionError> {
Default: `rendered`",
&mut rustc_output_format_raw,
);
flags.define_flag(
"--rustc-incremental",
"Enable rustc incremental compilation.",
&mut rustc_incremental_raw,
);
flags.define_flag(
"--rustc-incremental-dir",
"Persistent incremental cache directory supplied by the worker.",
&mut rustc_incremental_dir,
);
flags.define_flag(
"--require-explicit-unstable-features",
"If set, an empty -Zallow-features= will be added to the rustc command line whenever no \
Expand Down Expand Up @@ -252,6 +270,7 @@ pub(crate) fn options() -> Result<Options, OptionError> {
.transpose()?;

let rustc_quit_on_rmeta = rustc_quit_on_rmeta_raw.is_some_and(|s| s == "true");
let rustc_incremental = rustc_incremental_raw.is_some_and(|s| s == "true");
let rustc_output_format = rustc_output_format_raw
.map(|v| match v.as_str() {
"json" => Ok(rustc::ErrorFormat::Json),
Expand Down Expand Up @@ -301,6 +320,8 @@ pub(crate) fn options() -> Result<Options, OptionError> {
output_file,
rustc_quit_on_rmeta,
rustc_output_format,
rustc_incremental,
rustc_incremental_dir,
})
}

Expand Down Expand Up @@ -344,6 +365,11 @@ fn prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String {
arg
}

fn response_file_path(arg: &str) -> Option<&str> {
arg.strip_prefix(WORKER_RESPONSE_FILE_ARG)
.or_else(|| arg.strip_prefix('@'))
}

/// Apply substitutions to the given param file. Returns true iff any allow-features flags were found.
fn prepare_param_file(
filename: &str,
Expand All @@ -361,7 +387,7 @@ fn prepare_param_file(
for arg in read_file(filename)? {
let arg = prepare_arg(arg, subst_mappings);
has_allow_features_flag |= is_allow_features_flag(&arg);
if let Some(arg_file) = arg.strip_prefix('@') {
if let Some(arg_file) = response_file_path(&arg) {
has_allow_features_flag |=
process_file(arg_file, subst_mappings, read_file, write_to_file)?;
} else {
Expand Down Expand Up @@ -391,7 +417,7 @@ fn prepare_args(

for arg in args.into_iter() {
let arg = prepare_arg(arg, subst_mappings);
if let Some(param_file) = arg.strip_prefix('@') {
if let Some(param_file) = response_file_path(&arg) {
let expanded_file = format!("{param_file}.expanded");
let format_err = |err: io::Error| {
OptionError::Generic(format!(
Expand Down Expand Up @@ -474,6 +500,47 @@ fn environment_block(
mod test {
use super::*;

#[test]
fn test_worker_response_file_applies_substitutions() {
let mut written_files = HashMap::<String, String>::new();
let mut read_file = |filename: &str| -> Result<Vec<String>, OptionError> {
match filename {
"/exec/flags.params" => Ok(vec!["@${pwd}/nested.params".to_owned()]),
"/exec/nested.params" => Ok(vec!["--cfg=from_response_file".to_owned()]),
_ => Err(OptionError::Generic(format!(
"unexpected response file: {filename}"
))),
}
};
let mut write_file = |filename: &str, content: &str| -> Result<(), OptionError> {
written_files.insert(filename.to_owned(), content.to_owned());
Ok(())
};

let args = vec![
"rustc".to_owned(),
format!("{WORKER_RESPONSE_FILE_ARG}${{pwd}}/flags.params"),
];
let subst_mappings = vec![("pwd".to_owned(), "/exec".to_owned())];
let args = prepare_args(
args,
&subst_mappings,
false,
Some(&mut read_file),
Some(&mut write_file),
)
.unwrap();

assert_eq!(args, ["rustc", "@/exec/flags.params.expanded"]);
assert_eq!(
written_files,
HashMap::from([(
"/exec/flags.params.expanded".to_owned(),
"--cfg=from_response_file".to_owned()
)])
);
}

#[test]
fn test_enforce_allow_features_flag_user_didnt_say() {
let args = vec!["rustc".to_string()];
Expand Down
Loading