Skip to content

Commit e78e7de

Browse files
Add -Zno-codegen hollow rlib pipelining behind incompatible flag
Introduce `incompatible_use_unstable_no_codegen_for_pipelining` to switch pipelined compilation from the process-wrapper `--rustc-quit-on-rmeta` approach to Buck2-style `-Zno-codegen` hollow rlibs. Defaults to off; the existing rmeta-interception path remains the default behavior. When enabled: - Metadata action runs `rustc -Zno-codegen --emit=link=<path>` to produce a hollow rlib (`_meta.rlib` in a `_meta/` subdir of the full rlib). - Full action emits only `--emit=link` (no metadata, no dep-info). - `RUSTC_BOOTSTRAP=1` is set on both actions so SVH matches. - Metadata-consuming actions get an extra `-Ldependency` pointing at the `_meta/` subdir for transitive resolution. Tracked in #3977.
1 parent 85007f4 commit e78e7de

23 files changed

Lines changed: 824 additions & 332 deletions

File tree

extensions/prost/private/prost.bzl

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ load(
2323
"can_build_metadata",
2424
"can_use_metadata_for_pipelining",
2525
"generate_output_diagnostics",
26+
"metadata_output_path",
2627
)
2728
load("//:providers.bzl", "ProstProtoInfo")
2829
load(":prost_transform.bzl", "ProstTransformInfo")
@@ -176,26 +177,14 @@ def _compile_rust(
176177
extension = ".rlib",
177178
)
178179

179-
rmeta_name = "{prefix}{name}-{lib_hash}{extension}".format(
180-
prefix = "lib",
181-
name = crate_name,
182-
lib_hash = output_hash,
183-
extension = ".rmeta",
184-
)
185-
186180
lib = ctx.actions.declare_file(lib_name)
187181
rmeta = None
188182
rustc_rmeta_output = None
189183
metadata_supports_pipelining = False
190184

191185
if can_build_metadata(toolchain, ctx, "rlib"):
192-
rmeta_name = "{prefix}{name}-{lib_hash}{extension}".format(
193-
prefix = "lib",
194-
name = crate_name,
195-
lib_hash = output_hash,
196-
extension = ".rmeta",
197-
)
198-
rmeta = ctx.actions.declare_file(rmeta_name)
186+
rmeta_rel_path = metadata_output_path(toolchain, lib_name)
187+
rmeta = ctx.actions.declare_file(rmeta_rel_path, sibling = lib)
199188
rustc_rmeta_output = generate_output_diagnostics(ctx, rmeta)
200189
metadata_supports_pipelining = can_use_metadata_for_pipelining(toolchain, "rlib")
201190

rust/private/clippy.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf
177177
out_dir = out_dir,
178178
build_env_files = build_env_files,
179179
build_flags_files = build_flags_files,
180-
emit = ["dep-info", "metadata"],
180+
emit = ["metadata"],
181181
skip_expanding_rustc_env = True,
182182
use_json_output = bool(clippy_diagnostics_file),
183183
error_format = error_format,

rust/private/rust.bzl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ load(
5050
"generate_output_diagnostics",
5151
"get_edition",
5252
"get_import_macro_deps",
53+
"metadata_output_path",
5354
"transform_deps",
5455
"transform_sources",
5556
)
@@ -206,7 +207,7 @@ def _rust_library_common(ctx, crate_type):
206207
disable_pipelining = getattr(ctx.attr, "disable_pipelining", False),
207208
):
208209
rust_metadata = ctx.actions.declare_file(
209-
paths.replace_extension(rust_lib_name, ".rmeta"),
210+
metadata_output_path(toolchain, rust_lib_name),
210211
sibling = rust_lib,
211212
)
212213
rustc_rmeta_output = generate_output_diagnostics(ctx, rust_metadata)
@@ -279,7 +280,7 @@ def _rust_binary_impl(ctx):
279280
rustc_rmeta_output = None
280281
if can_build_metadata(toolchain, ctx, ctx.attr.crate_type):
281282
rust_metadata = ctx.actions.declare_file(
282-
paths.replace_extension("lib" + crate_name, ".rmeta"),
283+
metadata_output_path(toolchain, "lib" + crate_name),
283284
sibling = output,
284285
)
285286
rustc_rmeta_output = generate_output_diagnostics(ctx, rust_metadata)
@@ -381,7 +382,7 @@ def _rust_test_impl(ctx):
381382
rustc_rmeta_output = None
382383
if can_build_metadata(toolchain, ctx, crate_type):
383384
rust_metadata = ctx.actions.declare_file(
384-
paths.replace_extension("lib" + crate_name, ".rmeta"),
385+
metadata_output_path(toolchain, "lib" + crate_name),
385386
sibling = output,
386387
)
387388
rustc_rmeta_output = generate_output_diagnostics(ctx, rust_metadata)
@@ -451,7 +452,7 @@ def _rust_test_impl(ctx):
451452
rustc_rmeta_output = None
452453
if can_build_metadata(toolchain, ctx, crate_type):
453454
rust_metadata = ctx.actions.declare_file(
454-
paths.replace_extension("lib" + crate_name, ".rmeta"),
455+
metadata_output_path(toolchain, "lib" + crate_name),
455456
sibling = output,
456457
)
457458
rustc_rmeta_output = generate_output_diagnostics(ctx, rust_metadata)

rust/private/rustc.bzl

Lines changed: 154 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ def construct_arguments(
922922
out_dir,
923923
build_env_files,
924924
build_flags_files,
925-
emit = ["dep-info", "link"],
925+
emit = ["link"],
926926
force_all_deps_direct = False,
927927
add_flags_for_binary = False,
928928
include_link_flags = True,
@@ -933,6 +933,7 @@ def construct_arguments(
933933
force_depend_on_objects = False,
934934
skip_expanding_rustc_env = False,
935935
require_explicit_unstable_features = False,
936+
inject_allow_features_guardrail = False,
936937
error_format = None):
937938
"""Builds an Args object containing common rustc flags
938939
@@ -968,6 +969,7 @@ def construct_arguments(
968969
force_depend_on_objects (bool): Force using `.rlib` object files instead of metadata (`.rmeta`) files even if they are available.
969970
skip_expanding_rustc_env (bool): Whether to skip expanding CrateInfo.rustc_env_attr
970971
require_explicit_unstable_features (bool): Whether to require all unstable features to be explicitly opted in to using `-Zallow-features=...`.
972+
inject_allow_features_guardrail (bool): When True, inject `-Zallow-features=` alongside `-Zno-codegen` to prevent silent unstable-feature enablement via RUSTC_BOOTSTRAP=1. Disabled on nightly toolchains (where unstable features are already allowed by default) and when the user manages bootstrap/allow-features themselves; gated in rustc_compile_action.
971973
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`.
972974
973975
Returns:
@@ -1070,13 +1072,32 @@ def construct_arguments(
10701072
error_format = "json"
10711073

10721074
if build_metadata:
1073-
# Configure process_wrapper to terminate rustc when metadata are emitted
1074-
process_wrapper_flags.add("--rustc-quit-on-rmeta", "true")
1075+
if toolchain._incompatible_use_unstable_no_codegen_for_pipelining:
1076+
# RUSTC_BOOTSTRAP=1 is set on the action env in rustc_compile_action,
1077+
# required for -Zno-codegen on stable/beta rustc. -Zallow-features=
1078+
# (empty list) blocks all #![feature(...)] attributes to prevent the
1079+
# bootstrap env from silently enabling unstable language features in
1080+
# user code. Both are gated on inject_allow_features_guardrail, which
1081+
# is False on nightly (unstable features already allowed) and when
1082+
# the user manages bootstrap/allow-features themselves.
1083+
rustc_flags.add("-Zno-codegen")
1084+
if inject_allow_features_guardrail:
1085+
rustc_flags.add("-Zallow-features=")
1086+
else:
1087+
process_wrapper_flags.add("--rustc-quit-on-rmeta", "true")
10751088
if crate_info.rustc_rmeta_output:
10761089
process_wrapper_flags.add("--output-file", crate_info.rustc_rmeta_output.path)
10771090
elif crate_info.rustc_output:
10781091
process_wrapper_flags.add("--output-file", crate_info.rustc_output.path)
10791092

1093+
if not build_metadata and toolchain._incompatible_use_unstable_no_codegen_for_pipelining and bool(crate_info.metadata):
1094+
# Mirror the metadata action's -Zallow-features= so the bootstrap env
1095+
# does not silently enable unstable language features on the full
1096+
# compile. Gated on inject_allow_features_guardrail (computed in
1097+
# rustc_compile_action via _user_manages_bootstrap).
1098+
if inject_allow_features_guardrail:
1099+
rustc_flags.add("-Zallow-features=")
1100+
10801101
rustc_flags.add(error_format, format = "--error-format=%s")
10811102

10821103
# Mangle symbols to disambiguate crates with the same name. Used for
@@ -1105,8 +1126,17 @@ def construct_arguments(
11051126
rustc_flags.add("--remap-path-prefix=${{output_base}}={}".format(remap_path_prefix))
11061127

11071128
emit_without_paths = []
1129+
redirect_link_to_metadata = (
1130+
build_metadata and
1131+
crate_info.metadata != None and
1132+
toolchain._incompatible_use_unstable_no_codegen_for_pipelining
1133+
)
11081134
for kind in emit:
1109-
if kind == "link" and crate_info.type == "bin" and crate_info.output != None:
1135+
if kind == "link" and redirect_link_to_metadata:
1136+
# -Zno-codegen --emit=link writes lib<name>.rlib by default, which would
1137+
# collide with the full action's output; redirect to the hollow path.
1138+
rustc_flags.add(crate_info.metadata, format = "--emit=link=%s")
1139+
elif kind == "link" and crate_info.type == "bin" and crate_info.output != None:
11101140
rustc_flags.add(crate_info.output, format = "--emit=link=%s")
11111141
else:
11121142
emit_without_paths.append(kind)
@@ -1193,7 +1223,13 @@ def construct_arguments(
11931223
use_metadata = _depend_on_metadata(crate_info, force_depend_on_objects)
11941224

11951225
# These always need to be added, even if not linking this crate.
1196-
add_crate_link_flags(rustc_flags, dep_info, force_all_deps_direct, use_metadata)
1226+
add_crate_link_flags(
1227+
rustc_flags,
1228+
dep_info,
1229+
force_all_deps_direct,
1230+
use_metadata,
1231+
metadata_in_separate_dir = toolchain._incompatible_use_unstable_no_codegen_for_pipelining,
1232+
)
11971233

11981234
needs_extern_proc_macro_flag = _is_proc_macro(crate_info) and crate_info.edition != "2015"
11991235
if needs_extern_proc_macro_flag:
@@ -1274,6 +1310,44 @@ def construct_arguments(
12741310

12751311
return args, env
12761312

1313+
def _flag_is_allow_features(argv, index):
1314+
"""Returns True if the flag at argv[index] is a -Zallow-features flag.
1315+
1316+
Handles three forms rustc accepts:
1317+
- "-Zallow-features=..."
1318+
- "-Zallow-features" followed by the value in the next token
1319+
- "-Z" followed by "allow-features[=...]" in the next token
1320+
"""
1321+
flag = argv[index]
1322+
if flag == "-Zallow-features" or flag.startswith("-Zallow-features="):
1323+
return True
1324+
if flag == "-Z" and index + 1 < len(argv):
1325+
next_tok = argv[index + 1]
1326+
if next_tok == "allow-features" or next_tok.startswith("allow-features="):
1327+
return True
1328+
return False
1329+
1330+
def _user_manages_bootstrap(ctx, attr, env_before_inject, collected_extra_flags):
1331+
"""Return True if the user has set RUSTC_BOOTSTRAP or -Zallow-features themselves.
1332+
1333+
When True, rules_rust skips auto-injecting both RUSTC_BOOTSTRAP=1 and
1334+
-Zallow-features= for this target, treating the user's configuration as
1335+
authoritative.
1336+
"""
1337+
if "RUSTC_BOOTSTRAP" in env_before_inject:
1338+
return True
1339+
1340+
attr_flags = getattr(attr, "rustc_flags", []) or []
1341+
for i in range(len(attr_flags)):
1342+
if _flag_is_allow_features(attr_flags, i):
1343+
return True
1344+
1345+
for i in range(len(collected_extra_flags)):
1346+
if _flag_is_allow_features(collected_extra_flags, i):
1347+
return True
1348+
1349+
return False
1350+
12771351
def collect_extra_rustc_flags(ctx, toolchain, crate_root, crate_type):
12781352
"""Gather all 'extra' rustc flags from the target's attributes and toolchain.
12791353
@@ -1413,18 +1487,21 @@ def rustc_compile_action(
14131487
experimental_use_cc_common_link = experimental_use_cc_common_link,
14141488
)
14151489

1490+
use_no_codegen_pipelining = toolchain._incompatible_use_unstable_no_codegen_for_pipelining
1491+
14161492
# The types of rustc outputs to emit.
1417-
# If we build metadata, we need to keep the command line of the two invocations
1418-
# (rlib and rmeta) as similar as possible, otherwise rustc rejects the rmeta as
1419-
# a candidate.
1420-
# Because of that we need to add emit=metadata to both the rlib and rmeta invocation.
1421-
#
1422-
# When cc_common linking is enabled, emit a `.o` file, which is later
1423-
# passed to the cc_common.link action.
1424-
emit = ["dep-info", "link"]
1425-
if build_metadata:
1426-
emit.append("metadata")
1493+
if use_no_codegen_pipelining:
1494+
# Metadata is emitted by a separate -Zno-codegen action; the full action
1495+
# only produces the linked rlib.
1496+
emit = ["link"]
1497+
else:
1498+
# Legacy pipelining: rustc matches metadata against full-compile command
1499+
# lines, so both actions must request `--emit=...,metadata`.
1500+
emit = ["dep-info", "link"]
1501+
if build_metadata:
1502+
emit.append("metadata")
14271503
if experimental_use_cc_common_link:
1504+
# Pass a `.o` to the cc_common.link action.
14281505
emit = ["obj"]
14291506

14301507
# Determine whether to pass `--require-explicit-unstable-features true` to the process wrapper:
@@ -1437,6 +1514,35 @@ def rustc_compile_action(
14371514
elif ctx.attr.require_explicit_unstable_features == -1:
14381515
require_explicit_unstable_features = toolchain.require_explicit_unstable_features
14391516

1517+
# Build the env snapshot used to detect a user-managed RUSTC_BOOTSTRAP before
1518+
# any rules_rust-injected values are added. Matches the merge order in the
1519+
# main env assembly below.
1520+
env_before_inject = dict(ctx.configuration.default_shell_env)
1521+
env_before_inject.update(crate_info.rustc_env)
1522+
if hasattr(ctx.attr, "_extra_rustc_env") and not is_exec_configuration(ctx):
1523+
env_before_inject.update(
1524+
ctx.attr._extra_rustc_env[ExtraRustcEnvInfo].extra_rustc_env,
1525+
)
1526+
1527+
collected_extra_flags = collect_extra_rustc_flags(
1528+
ctx,
1529+
toolchain,
1530+
crate_info.root,
1531+
crate_info.type,
1532+
)
1533+
1534+
user_manages_bootstrap = _user_manages_bootstrap(
1535+
ctx,
1536+
attr,
1537+
env_before_inject,
1538+
collected_extra_flags,
1539+
)
1540+
inject_allow_features_guardrail = (
1541+
use_no_codegen_pipelining and
1542+
not user_manages_bootstrap and
1543+
toolchain.channel != "nightly"
1544+
)
1545+
14401546
args, env_from_args = construct_arguments(
14411547
ctx = ctx,
14421548
attr = attr,
@@ -1460,18 +1566,20 @@ def rustc_compile_action(
14601566
use_json_output = bool(build_metadata) or bool(rustc_output) or bool(rustc_rmeta_output),
14611567
skip_expanding_rustc_env = skip_expanding_rustc_env,
14621568
require_explicit_unstable_features = require_explicit_unstable_features,
1569+
inject_allow_features_guardrail = inject_allow_features_guardrail,
14631570
)
14641571

14651572
args_metadata = None
14661573
if build_metadata:
1574+
metadata_emit = ["link"] if use_no_codegen_pipelining else emit
14671575
args_metadata, _ = construct_arguments(
14681576
ctx = ctx,
14691577
attr = attr,
14701578
file = ctx.file,
14711579
toolchain = toolchain,
14721580
tool_path = toolchain.rustc.path,
14731581
cc_toolchain = cc_toolchain,
1474-
emit = emit,
1582+
emit = metadata_emit,
14751583
feature_configuration = feature_configuration,
14761584
crate_info = crate_info,
14771585
dep_info = dep_info,
@@ -1487,13 +1595,22 @@ def rustc_compile_action(
14871595
use_json_output = True,
14881596
build_metadata = True,
14891597
require_explicit_unstable_features = require_explicit_unstable_features,
1598+
inject_allow_features_guardrail = inject_allow_features_guardrail,
14901599
)
14911600

14921601
env = dict(ctx.configuration.default_shell_env)
14931602

14941603
# this is the final list of env vars
14951604
env.update(env_from_args)
14961605

1606+
if build_metadata and use_no_codegen_pipelining and inject_allow_features_guardrail:
1607+
# RUSTC_BOOTSTRAP=1 is required for -Zno-codegen on stable/beta rustc, and
1608+
# must be set on both the metadata and full actions for SVH compatibility
1609+
# (since RUSTC_BOOTSTRAP affects the crate hash). Skipped on nightly
1610+
# toolchains (where -Zno-codegen works without bootstrap) and when the user
1611+
# manages RUSTC_BOOTSTRAP or -Zallow-features themselves (escape hatch).
1612+
env["RUSTC_BOOTSTRAP"] = "1"
1613+
14971614
if hasattr(attr, "version") and attr.version != "0.0.0":
14981615
formatted_version = " v{}".format(attr.version)
14991616
else:
@@ -2166,7 +2283,7 @@ def _get_dir_names(files):
21662283
dirs[f.dirname] = None
21672284
return dirs.keys()
21682285

2169-
def add_crate_link_flags(args, dep_info, force_all_deps_direct = False, use_metadata = False):
2286+
def add_crate_link_flags(args, dep_info, force_all_deps_direct = False, use_metadata = False, metadata_in_separate_dir = False):
21702287
"""Adds link flags to an Args object reference
21712288
21722289
Args:
@@ -2175,6 +2292,9 @@ def add_crate_link_flags(args, dep_info, force_all_deps_direct = False, use_meta
21752292
force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
21762293
to the commandline as opposed to -L.
21772294
use_metadata (bool, optional): Build command line arguments using metadata for crates that provide it.
2295+
metadata_in_separate_dir (bool, optional): If True, add an extra `-Ldependency` pointing at
2296+
the `_meta/` subdir where hollow rlibs live under `-Zno-codegen` pipelining. Under legacy
2297+
pipelining the `.rmeta` is a sibling of the full rlib, so no extra flag is needed.
21782298
"""
21792299

21802300
direct_crates = depset(
@@ -2194,6 +2314,14 @@ def add_crate_link_flags(args, dep_info, force_all_deps_direct = False, use_meta
21942314
format_each = "-Ldependency=%s",
21952315
)
21962316

2317+
if use_metadata and metadata_in_separate_dir:
2318+
args.add_all(
2319+
dep_info.transitive_crates,
2320+
map_each = _get_crate_metadata_dirname,
2321+
uniquify = True,
2322+
format_each = "-Ldependency=%s",
2323+
)
2324+
21972325
def _crate_to_link_flag_metadata(crate):
21982326
"""A helper macro used by `add_crate_link_flags` for adding crate link flags to a Arg object
21992327
@@ -2247,6 +2375,15 @@ def _get_crate_dirname(crate):
22472375
"""
22482376
return crate.output.dirname
22492377

2378+
def _get_crate_metadata_dirname(crate):
2379+
"""Returns the `_meta/` subdir containing `crate`'s hollow `_meta.rlib`, or None.
2380+
2381+
Only meaningful under `-Zno-codegen` pipelining; callers gate on that flag.
2382+
"""
2383+
if crate.metadata and crate.metadata_supports_pipelining and crate.metadata.dirname != crate.output.dirname:
2384+
return crate.metadata.dirname
2385+
return None
2386+
22502387
def portable_link_flags(
22512388
lib,
22522389
use_pic,

0 commit comments

Comments
 (0)