Skip to content

Commit eb91a9b

Browse files
committed
incremental compilation without a worker
This is considerably less invasive than #667, #517 and #421 - there is no extra code to bootstrap, and no toolchains have to be plumbed into the rules. We had been using a worker to get around sandboxing restrictions, but as @hlopko pointed out on #667, that turns out not to be necessary - even normal rules have access to /tmp. Unfortunately it seems that rustc does not expect the source files to change location, and it will consistently crash when used in a sandboxed context. So the approach this PR takes is to disable sandboxing on targets that are being compiled incrementally. This fixes the compiler crashes, and as a bonus, means we're not limited to saving the cache in /tmp. This PR adds a --@rules_rust//:experimental_incremental_base flag to specify the path where incremental build products are stored - if not provided, the rules will function as they normally do. It also requires targets to be tagged "incremental" - any without the tag will be compiled normally, even if a base folder is provided. Cargo's behaviour appears to be to only store incremental products for the product being built (eg, all the dependencies you pull in from crates.io do not have incremental build products stored), so manual tagging seems preferable to an "include everything" approach.
1 parent 1b27714 commit eb91a9b

File tree

5 files changed

+76
-4
lines changed

5 files changed

+76
-4
lines changed

BUILD.bazel

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
2-
load("//rust:rust.bzl", "error_format")
2+
load("//rust:defs.bzl", "error_format", "string_flag")
33

44
exports_files(["LICENSE"])
55

@@ -17,3 +17,10 @@ error_format(
1717
build_setting_default = "human",
1818
visibility = ["//visibility:public"],
1919
)
20+
21+
# Optional incremental compilation - see docs/index.md
22+
string_flag(
23+
name = "experimental_incremental_base",
24+
build_setting_default = "",
25+
visibility = ["//visibility:public"],
26+
)

docs/index.md

+17
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,20 @@ bazel build @examples//hello_world_wasm --platforms=@rules_rust//rust/platform:w
9090

9191
`rust_wasm_bindgen` will automatically transition to the `wasm` platform and can be used when
9292
building WebAssembly code for the host target.
93+
94+
## Incremental Compilation
95+
96+
There is an experimental flag that enables incremental compilation, which can considerably
97+
speed up rebuilds during development.
98+
99+
Enabling it is a two step process:
100+
101+
- add a tag called "incremental" to any rust library, test, binary or build script targets
102+
that you want to compile incrementally.
103+
- invoke bazel build with the following argument, changing the path to one of your liking:
104+
105+
--@rules_rust//:experimental_incremental_base=/home/user/cache/bazel_incremental
106+
107+
When these two conditions have been met, sandboxing is disabled for the target, and it
108+
will be compiled using the local strategy, with incremental build products being placed
109+
into a subfolder of the one you have provided.

rust/defs.bzl

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ load(
3939
load(
4040
"//rust/private:rustc.bzl",
4141
_error_format = "error_format",
42+
_string_flag = "string_flag",
4243
)
4344
load(
4445
"//rust/private:rustdoc.bzl",
@@ -88,6 +89,9 @@ rust_clippy = _rust_clippy
8889
error_format = _error_format
8990
# See @rules_rust//rust/private:rustc.bzl for a complete description.
9091

92+
string_flag = _string_flag
93+
# See @rules_rust//rust/private:rustc.bzl for a complete description.
94+
9195
rust_common = _rust_common
9296
# See @rules_rust//rust/private:common.bzl for a complete description.
9397

rust/private/rust.bzl

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
# buildifier: disable=module-docstring
16+
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
1617
load("//rust/private:common.bzl", "rust_common")
1718
load("//rust/private:rustc.bzl", "rustc_compile_action")
1819
load("//rust/private:utils.bzl", "crate_name_from_attr", "determine_output_hash", "expand_locations", "find_toolchain")
@@ -263,6 +264,7 @@ def _rust_library_common(ctx, crate_type):
263264
is_test = False,
264265
),
265266
output_hash = output_hash,
267+
incremental_base = ctx.attr._incremental_base[BuildSettingInfo].value,
266268
)
267269

268270
def _rust_binary_impl(ctx):
@@ -297,6 +299,7 @@ def _rust_binary_impl(ctx):
297299
rustc_env = ctx.attr.rustc_env,
298300
is_test = False,
299301
),
302+
incremental_base = ctx.attr._incremental_base[BuildSettingInfo].value,
300303
)
301304

302305
def _create_test_launcher(ctx, toolchain, output, providers):
@@ -434,6 +437,7 @@ def _rust_test_common(ctx, toolchain, output):
434437
crate_type = crate_type,
435438
crate_info = crate_info,
436439
rust_flags = ["--test"],
440+
incremental_base = ctx.attr._incremental_base[BuildSettingInfo].value,
437441
)
438442

439443
return _create_test_launcher(ctx, toolchain, output, providers)
@@ -673,6 +677,7 @@ _common_attrs = {
673677
default = "@bazel_tools//tools/cpp:current_cc_toolchain",
674678
),
675679
"_error_format": attr.label(default = "//:error_format"),
680+
"_incremental_base": attr.label(default = "//:experimental_incremental_base"),
676681
"_process_wrapper": attr.label(
677682
default = Label("//util/process_wrapper"),
678683
executable = True,

rust/private/rustc.bzl

+42-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
# buildifier: disable=module-docstring
16+
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
1617
load(
1718
"@bazel_tools//tools/build_defs/cc:action_names.bzl",
1819
"CPP_LINK_EXECUTABLE_ACTION_NAME",
@@ -492,7 +493,8 @@ def rustc_compile_action(
492493
crate_info,
493494
output_hash = None,
494495
rust_flags = [],
495-
environ = {}):
496+
environ = {},
497+
incremental_base = None):
496498
"""Create and run a rustc compile action based on the current rule's attributes
497499
498500
Args:
@@ -503,6 +505,7 @@ def rustc_compile_action(
503505
output_hash (str, optional): The hashed path of the crate root. Defaults to None.
504506
rust_flags (list, optional): Additional flags to pass to rustc. Defaults to [].
505507
environ (dict, optional): A set of makefile expandable environment variables for the action
508+
incremental_base (str, optional): path to store incremental build products in.
506509
507510
Returns:
508511
list: A list of the following providers:
@@ -554,14 +557,32 @@ def rustc_compile_action(
554557
else:
555558
formatted_version = ""
556559

560+
if incremental_base and "incremental" in ctx.attr.tags:
561+
incremental_dir = "{}/{}_{}".format(
562+
incremental_base,
563+
ctx.var["COMPILATION_MODE"],
564+
toolchain.target_triple,
565+
)
566+
args.add("--codegen", "incremental=" + incremental_dir)
567+
568+
# with sandboxing enabled, subsequent rustc invocations will crash,
569+
# as it doesn't expect the source files to have moved
570+
execution_requirements = {"no-sandbox": "1"}
571+
mnemonic = "RustcIncr"
572+
else:
573+
execution_requirements = {}
574+
mnemonic = "Rustc"
575+
557576
ctx.actions.run(
558577
executable = ctx.executable._process_wrapper,
559578
inputs = compile_inputs,
560579
outputs = [crate_info.output],
561580
env = env,
562581
arguments = [args],
563-
mnemonic = "Rustc",
564-
progress_message = "Compiling Rust {} {}{} ({} files)".format(
582+
mnemonic = mnemonic,
583+
execution_requirements = execution_requirements,
584+
progress_message = "Compiling {} {} {}{} ({} files)".format(
585+
mnemonic,
565586
crate_info.type,
566587
ctx.label.name,
567588
formatted_version,
@@ -934,3 +955,21 @@ error_format = rule(
934955
implementation = _error_format_impl,
935956
build_setting = config.string(flag = True),
936957
)
958+
959+
def _string_flag_impl(ctx):
960+
"""Implementation for the string_flag() rule
961+
962+
Args:
963+
ctx (ctx): The rule's context object
964+
965+
Returns:
966+
BuildSettingInfo: an object with a value attribute containing the string.
967+
"""
968+
value = ctx.build_setting_value
969+
return BuildSettingInfo(value = value)
970+
971+
string_flag = rule(
972+
build_setting = config.string(flag = True),
973+
implementation = _string_flag_impl,
974+
doc = "Helper to declare a command line argument that accepts an arbitrary string.",
975+
)

0 commit comments

Comments
 (0)