@@ -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+
12771351def 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+
21972325def _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+
22502387def portable_link_flags (
22512388 lib ,
22522389 use_pic ,
0 commit comments