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
74 changes: 64 additions & 10 deletions cargo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions rust/private/rust.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,9 @@ RUSTC_ATTRS = {
"_rustc_output_diagnostics": attr.label(
default = Label("//rust/settings:rustc_output_diagnostics"),
),
"_experimental_rustc_incremental": attr.label(
default = Label("//rust/settings:experimental_rustc_incremental"),
),
}

_COMMON_ATTRS = {
Expand Down
28 changes: 24 additions & 4 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,8 @@ def construct_arguments(
skip_expanding_rustc_env = False,
require_explicit_unstable_features = False,
always_use_param_file = False,
error_format = None):
error_format = None,
incremental_cache_base = None):
"""Builds an Args object containing common rustc flags

Args:
Expand Down Expand Up @@ -1000,6 +1001,9 @@ def construct_arguments(
if require_explicit_unstable_features:
process_wrapper_flags.add("--require-explicit-unstable-features", "true")

if incremental_cache_base:
process_wrapper_flags.add("--inject-incremental-cache", incremental_cache_base)

# 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 @@ -1437,6 +1441,18 @@ def rustc_compile_action(
elif ctx.attr.require_explicit_unstable_features == -1:
require_explicit_unstable_features = toolchain.require_explicit_unstable_features

# Check if incremental compilation is enabled. process_wrapper injects
# -Cincremental=<output_base>/.rustc_incremental_cache/... when this is on.
# Excluded for cc_common.link (emits .o, incompatible) and when there is
# no process_wrapper (bootstrap rules).
use_incremental = (
hasattr(ctx.attr, "_experimental_rustc_incremental") and
ctx.attr._experimental_rustc_incremental[BuildSettingInfo].value and
not experimental_use_cc_common_link and
ctx.executable._process_wrapper != None
)
incremental_cache_base = ".rustc_incremental_cache" if use_incremental else None

args, env_from_args = construct_arguments(
ctx = ctx,
attr = attr,
Expand All @@ -1461,6 +1477,7 @@ def rustc_compile_action(
skip_expanding_rustc_env = skip_expanding_rustc_env,
require_explicit_unstable_features = require_explicit_unstable_features,
always_use_param_file = not ctx.executable._process_wrapper,
incremental_cache_base = incremental_cache_base,
)

args_metadata = None
Expand Down Expand Up @@ -1488,6 +1505,7 @@ def rustc_compile_action(
use_json_output = True,
build_metadata = True,
require_explicit_unstable_features = require_explicit_unstable_features,
incremental_cache_base = incremental_cache_base,
)

env = dict(ctx.configuration.default_shell_env)
Expand Down Expand Up @@ -1551,19 +1569,20 @@ def rustc_compile_action(
action_outputs.append(dsym_folder)

if ctx.executable._process_wrapper:
# Run as normal
incr_tag = " [incr]" if use_incremental else ""
ctx.actions.run(
executable = ctx.executable._process_wrapper,
inputs = compile_inputs,
outputs = action_outputs,
env = env,
arguments = args.all,
mnemonic = "Rustc",
progress_message = "Compiling Rust {} %{{label}}{} ({} file{})".format(
progress_message = "Compiling Rust {} %{{label}}{} ({} file{}){}".format(
crate_info.type,
formatted_version,
len(srcs),
"" if len(srcs) == 1 else "s",
incr_tag,
),
toolchain = "@rules_rust//rust:toolchain_type",
resource_set = get_rustc_resource_set(toolchain),
Expand All @@ -1576,11 +1595,12 @@ def rustc_compile_action(
env = env,
arguments = args_metadata.all,
mnemonic = "RustcMetadata",
progress_message = "Compiling Rust metadata {} %{{label}}{} ({} file{})".format(
progress_message = "Compiling Rust metadata {} %{{label}}{} ({} file{}){}".format(
crate_info.type,
formatted_version,
len(srcs),
"" if len(srcs) == 1 else "s",
incr_tag,
),
toolchain = "@rules_rust//rust:toolchain_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_link_std_dylib",
"experimental_per_crate_rustc_flag",
"experimental_rustc_incremental",
"experimental_use_allocator_libraries_with_mangled_symbols",
"experimental_use_cc_common_link",
"experimental_use_coverage_metadata_files",
Expand Down Expand Up @@ -85,6 +86,8 @@ experimental_link_std_dylib()

experimental_per_crate_rustc_flag()

experimental_rustc_incremental()

experimental_use_cc_common_link()

experimental_use_coverage_metadata_files()
Expand Down
71 changes: 71 additions & 0 deletions rust/settings/settings.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,77 @@ def incompatible_do_not_include_transitive_data_in_compile_inputs():
issue = "https://github.com/bazelbuild/rules_rust/issues/3915",
)

def experimental_rustc_incremental():
"""Enable rustc incremental compilation via process_wrapper injection.

When enabled, `process_wrapper` injects `-Cincremental=<dir>` into every
rustc invocation. The cache directory lives under the Bazel output_base at
`<output_base>/.rustc_incremental_cache/`, so state persists across builds
and is shared across all concurrent compilations.

This is **non-hermetic** and **local-only**. Rustc's emitted artifacts may
vary with cache state, the cache directory is an undeclared input/output
that Bazel cannot track, and incremental output is not bit-identical to a
non-incremental compile of the same sources even for deterministic crates
(rustc raises the default `-Ccodegen-units` to 256 in incremental mode,
which changes CGU partitioning and therefore codegen). Release builds that
are sensitive to runtime performance should not enable this.

Incompatible with `experimental_use_cc_common_link` (the object-emitting
link path is incompatible with `-Cincremental`).

## Recommended .bazelrc snippet

```
# --config=dev-inc: local-only incremental rebuilds via rustc -Cincremental
# NOT safe for CI or remote — produces non-hermetic, perf-degraded outputs
build:dev-inc --@rules_rust//rust/settings:experimental_rustc_incremental=true
build:dev-inc --strategy=Rustc=local
build:dev-inc --strategy=RustcMetadata=local
build:dev-inc --remote_upload_local_results=false
```

Then invoke: `bazel build --config=dev-inc //some:target`.

Why each flag matters:

- `experimental_rustc_incremental=true` turns on `-Cincremental` injection.
Because this is a `bool_flag`, toggling it produces a distinct
configuration hash, so the Bazel disk cache does not mix incremental and
non-incremental outputs and can stay enabled.
- `--strategy=Rustc=local` / `--strategy=RustcMetadata=local` are required
because the cache lives outside any sandbox; sandboxed strategies would
block writes. Both mnemonics needed when pipelined compilation is on.
- `--remote_upload_local_results=false` stops non-hermetic rlibs from
poisoning a shared remote cache. Remote reads are still fine.

Works transparently alongside `pipelined_compilation=hollow_rlib`.

## Runtime-perf caveat

`-Cincremental` raises the default `-Ccodegen-units` from 16 to 256. LLVM
loses cross-CGU inlining opportunities, and ThinLTO can only partially
recover them. For opt builds this can materially slow the resulting binary
— stick to `--config=dev-inc` on dev machines and keep CI/release builds
on the non-incremental path.

## Cache eviction

The cache grows unbounded. There is no automatic eviction today. To clear
it manually:

```
rm -rf "$(bazel info output_base)/.rustc_incremental_cache"
```

`bazel clean --expunge` also removes it (by removing the whole output_base)
but is heavier than necessary if that is all you want to drop.
"""
bool_flag(
name = "experimental_rustc_incremental",
build_setting_default = False,
)

def codegen_units():
"""The default value for `--codegen-units` which also affects resource allocation for rustc actions.

Expand Down
Loading
Loading