Skip to content

Commit 5f04c8c

Browse files
Add cargo_build_script.use_cc_toolchain (#4161)
This change introduces `use_cc_toolchain` to `cargo_build_script` which can be used to control whether or not a `cc_toolchain` is explicitly withheld from `CargoBuildScript` actions. Additionally a global flag `--@rules_rust//cargo/settings:use_cc_toolchain` can be used to control the default of any build script that has not either explicitly set `use_cc_toolchain = 1` (always include cc toolchains) or `use_cc_toolchain = 0` (always exclude cc toolchains). closes #3680 closes #3679 Co-authored-by: Krasimir Georgiev <krasimir@google.com>
1 parent 0c5c01d commit 5f04c8c

26 files changed

Lines changed: 293 additions & 30 deletions

cargo/private/cargo_build_script.bzl

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,24 @@ def _feature_enabled(ctx, feature_name, default = False):
286286

287287
return default
288288

289+
def _resolve_tristate(attr_value, default_flag_target):
290+
"""Resolve a tri-state `int` attribute (`-1`/`0`/`1`) to a `bool`.
291+
292+
`-1` defers to the `BuildSettingInfo` on `default_flag_target`; any other
293+
value is treated as truthy/falsy directly.
294+
295+
Args:
296+
attr_value (int): The tri-state attribute value (`-1`, `0`, or `1`).
297+
default_flag_target (Target): The `bool_flag` target providing the
298+
default when `attr_value` is `-1`.
299+
300+
Returns:
301+
bool: The resolved value.
302+
"""
303+
if attr_value == -1:
304+
return default_flag_target[BuildSettingInfo].value
305+
return bool(attr_value)
306+
289307
def _rlocationpath(file, workspace_name):
290308
if file.short_path.startswith("../"):
291309
return file.short_path[len("../"):]
@@ -390,12 +408,10 @@ def _cargo_build_script_impl(ctx):
390408

391409
env = {}
392410

393-
if ctx.attr.use_default_shell_env == -1:
394-
use_default_shell_env = ctx.attr._default_use_default_shell_env[BuildSettingInfo].value
395-
elif ctx.attr.use_default_shell_env == 0:
396-
use_default_shell_env = False
397-
else:
398-
use_default_shell_env = True
411+
use_default_shell_env = _resolve_tristate(
412+
ctx.attr.use_default_shell_env,
413+
ctx.attr._default_use_default_shell_env,
414+
)
399415

400416
# If enabled, start with the default shell env, which contains any --action_env
401417
# settings passed in on the command line and defaults like $PATH.
@@ -433,9 +449,17 @@ def _cargo_build_script_impl(ctx):
433449
env["CARGO_PKG_VERSION_PRE"] = patch[1] if len(patch) > 1 else ""
434450
env["CARGO_PKG_VERSION"] = ctx.attr.version
435451

452+
use_cc_toolchain = _resolve_tristate(
453+
ctx.attr.use_cc_toolchain,
454+
ctx.attr._default_use_cc_toolchain,
455+
)
456+
436457
# Pull in env vars which may be required for the cc_toolchain to work (e.g. on OSX, the SDK version).
437458
# We hope that the linker env is sufficient for the whole cc_toolchain.
438-
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
459+
if use_cc_toolchain:
460+
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
461+
else:
462+
cc_toolchain, feature_configuration = None, None
439463
linker, _, link_args, linker_env = get_linker_and_args(ctx, "bin", toolchain, cc_toolchain, feature_configuration, None)
440464
env.update(**linker_env)
441465
env["LD"] = linker
@@ -784,6 +808,31 @@ cargo_build_script = rule(
784808
allow_files = True,
785809
cfg = "exec",
786810
),
811+
"use_cc_toolchain": attr.int(
812+
doc = dedent("""\
813+
Whether or not to pull in the resolved `cc_toolchain` when
814+
running the build script.
815+
816+
When enabled, the resolved `cc_toolchain`'s `all_files` are
817+
added to the action inputs and the `CC`, `CXX`, `AR`,
818+
`CFLAGS`, `CXXFLAGS`, `LDFLAGS`, and `INCLUDE` environment
819+
variables are populated from that toolchain (matching Cargo's
820+
normal behavior).
821+
822+
When disabled, the `cc_toolchain` is not requested for the
823+
build script action. This can significantly shrink the input
824+
trees of `cargo_build_script` actions (particularly with
825+
hermetic sysroots) but breaks any build script that needs to
826+
compile C/C++ code.
827+
828+
Unset (`-1`, the default) defers to the
829+
`@rules_rust//cargo/settings:use_cc_toolchain` build setting
830+
which itself defaults to enabled. Set to `1` to force enable
831+
or `0` to force disable for a specific target.
832+
"""),
833+
default = -1,
834+
values = [-1, 0, 1],
835+
),
787836
"use_default_shell_env": attr.int(
788837
doc = dedent("""\
789838
Whether or not to include the default shell environment for the build
@@ -808,6 +857,9 @@ cargo_build_script = rule(
808857
"_debug_std_streams_output_group": attr.label(
809858
default = Label("//cargo/settings:debug_std_streams_output_group"),
810859
),
860+
"_default_use_cc_toolchain": attr.label(
861+
default = Label("//cargo/settings:use_cc_toolchain"),
862+
),
811863
"_default_use_default_shell_env": attr.label(
812864
default = Label("//cargo/settings:use_default_shell_env"),
813865
),

cargo/private/cargo_build_script_wrapper.bzl

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@ load(
99
)
1010
load("//rust:defs.bzl", "rust_binary")
1111

12+
def _sanitize_tristate(value):
13+
"""Coerce a user-provided value to the rule's `-1`/`0`/`1` tri-state `int` shape.
14+
15+
Args:
16+
value (bool | int | None): The user-provided value. `None` is treated as
17+
"unset" (`-1`), booleans are mapped to `0`/`1`, and integers are
18+
passed through.
19+
20+
Returns:
21+
int: The sanitized tri-state value (`-1`, `0`, or `1`).
22+
"""
23+
if value == None:
24+
return -1
25+
if type(value) == "bool":
26+
return 1 if value else 0
27+
return value
28+
1229
def cargo_build_script(
1330
*,
1431
name,
@@ -24,6 +41,7 @@ def cargo_build_script(
2441
build_script_env = {},
2542
build_script_env_files = [],
2643
emit_warnings = True,
44+
use_cc_toolchain = None,
2745
use_default_shell_env = None,
2846
data = [],
2947
compile_data = [],
@@ -117,6 +135,14 @@ def cargo_build_script(
117135
to stderr. Honored only when
118136
`@rules_rust//cargo/settings:emit_build_script_warnings` is `auto` (the
119137
default); set the flag to `on` or `off` to override every target.
138+
use_cc_toolchain (bool, optional): Whether or not to pull in the resolved `cc_toolchain` when running the build script.
139+
140+
When enabled, the resolved `cc_toolchain`'s `all_files` are added to the action inputs and the `CC`,
141+
`CXX`, `AR`, `CFLAGS`, `CXXFLAGS`, `LDFLAGS`, and `INCLUDE` environment variables are populated from
142+
that toolchain (matching Cargo's normal behavior). Disabling this can significantly shrink build
143+
script action input trees (particularly with hermetic sysroots) but breaks any build script that
144+
needs to compile C/C++ code. If unset the global setting
145+
`@rules_rust//cargo/settings:use_cc_toolchain` will be used to determine this value.
120146
use_default_shell_env (bool, optional): Whether or not to include the default shell environment for the build script action. If unset the global
121147
setting `@rules_rust//cargo/settings:use_default_shell_env` will be used to determine this value.
122148
data (list, optional): Files needed by the build script.
@@ -206,12 +232,8 @@ def cargo_build_script(
206232
**wrapper_kwargs
207233
)
208234

209-
if use_default_shell_env == None:
210-
sanitized_use_default_shell_env = -1
211-
elif type(use_default_shell_env) == "bool":
212-
sanitized_use_default_shell_env = 1 if use_default_shell_env else 0
213-
else:
214-
sanitized_use_default_shell_env = use_default_shell_env
235+
sanitized_use_default_shell_env = _sanitize_tristate(use_default_shell_env)
236+
sanitized_use_cc_toolchain = _sanitize_tristate(use_cc_toolchain)
215237

216238
# This target executes the build script.
217239
_build_script_run(
@@ -225,6 +247,7 @@ def cargo_build_script(
225247
build_script_env = build_script_env,
226248
build_script_env_files = build_script_env_files,
227249
emit_warnings = emit_warnings,
250+
use_cc_toolchain = sanitized_use_cc_toolchain,
228251
use_default_shell_env = sanitized_use_default_shell_env,
229252
links = links,
230253
deps = deps,

cargo/settings/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ load(
66
"emit_build_script_warnings",
77
"experimental_symlink_execroot",
88
"out_dir_volatile_file_basenames",
9+
"use_cc_toolchain",
910
"use_default_shell_env",
1011
)
1112

@@ -33,3 +34,5 @@ use_default_shell_env()
3334
out_dir_volatile_file_basenames()
3435

3536
emit_build_script_warnings()
37+
38+
use_cc_toolchain()

cargo/settings/settings.bzl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,29 @@ def use_default_shell_env():
4343
build_setting_default = True,
4444
)
4545

46+
def use_cc_toolchain():
47+
"""A flag which controls the global default of whether `cargo_build_script` \
48+
targets should pull in the resolved `cc_toolchain`.
49+
50+
When enabled (the default), each `cargo_build_script` action gets the
51+
resolved `cc_toolchain`'s `all_files` added to its inputs and picks up the
52+
`CC`, `CXX`, `AR`, `CFLAGS`, `CXXFLAGS`, `LDFLAGS`, and `INCLUDE`
53+
environment variables derived from that toolchain. This matches Cargo's
54+
behavior and is required for build scripts that shell out to a C/C++
55+
compiler (e.g. those using `cc-rs` or `cmake-rs`).
56+
57+
When disabled, the `cc_toolchain` is omitted from `cargo_build_script`
58+
actions. This can significantly reduce action input sizes when using a
59+
hermetic sysroot but breaks any build script that needs to compile C/C++
60+
code. Individual targets may override this default via the
61+
`cargo_build_script.use_cc_toolchain` attribute.
62+
"""
63+
bool_flag(
64+
name = "use_cc_toolchain",
65+
scope = "universal",
66+
build_setting_default = True,
67+
)
68+
4669
def emit_build_script_warnings():
4770
"""A flag which controls whether `cargo_build_script` warnings \
4871
(`cargo::warning=`) are printed to stderr.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
load(":use_cc_toolchain_test.bzl", "use_cc_toolchain_test_suite")
2+
3+
use_cc_toolchain_test_suite(name = "use_cc_toolchain_test_suite")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Analysis tests for the `use_cc_toolchain` attribute of `cargo_build_script`.
2+
3+
Only the disabled paths are exercised; the enabled paths are already covered by
4+
the pre-existing `cc_args_and_env` suite.
5+
"""
6+
7+
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
8+
load("//cargo:defs.bzl", "cargo_build_script")
9+
10+
_USE_CC_TOOLCHAIN_FLAG = str(Label("//cargo/settings:use_cc_toolchain"))
11+
12+
def _find_cargo_action(actions):
13+
"""Return the `CargoBuildScriptRun` action from a target's action list.
14+
15+
Args:
16+
actions (list[Action]): The actions registered by the target under test.
17+
18+
Returns:
19+
Action: The `CargoBuildScriptRun` action. Fails if none is present.
20+
"""
21+
for action in actions:
22+
if action.mnemonic == "CargoBuildScriptRun":
23+
return action
24+
fail("Could not find CargoBuildScriptRun action")
25+
26+
def _assert_cc_toolchain_absent_impl(ctx):
27+
env = analysistest.begin(ctx)
28+
cargo_action = _find_cargo_action(analysistest.target_under_test(env).actions)
29+
for var in ("CC", "CXX", "AR"):
30+
value = cargo_action.env.get(var)
31+
asserts.true(
32+
env,
33+
value != None,
34+
"expected env var {} to be set, but it was missing".format(var),
35+
)
36+
asserts.true(
37+
env,
38+
"no_" + var.lower() in value,
39+
"expected env var {} to point at the fallback tool, got: {}".format(var, value),
40+
)
41+
return analysistest.end(env)
42+
43+
_cc_toolchain_absent_with_flag_disabled_test = analysistest.make(
44+
impl = _assert_cc_toolchain_absent_impl,
45+
config_settings = {_USE_CC_TOOLCHAIN_FLAG: False},
46+
)
47+
48+
_cc_toolchain_absent_with_flag_enabled_test = analysistest.make(
49+
impl = _assert_cc_toolchain_absent_impl,
50+
config_settings = {_USE_CC_TOOLCHAIN_FLAG: True},
51+
)
52+
53+
def use_cc_toolchain_test_suite(name):
54+
"""Instantiates analysis tests covering `cargo_build_script.use_cc_toolchain`.
55+
56+
Args:
57+
name (str): The name of the test suite.
58+
"""
59+
cargo_build_script(
60+
name = "build_script_default",
61+
edition = "2018",
62+
srcs = ["build.rs"],
63+
tags = ["manual"],
64+
)
65+
66+
cargo_build_script(
67+
name = "build_script_disabled",
68+
edition = "2018",
69+
srcs = ["build.rs"],
70+
use_cc_toolchain = False,
71+
tags = ["manual"],
72+
)
73+
74+
# Attribute unset (`-1`) + flag flipped off => no toolchain.
75+
_cc_toolchain_absent_with_flag_disabled_test(
76+
name = "default_attr_follows_disabled_flag_test",
77+
target_under_test = ":build_script_default",
78+
)
79+
80+
# Attribute explicitly disabled (`0`) wins over either flag value.
81+
_cc_toolchain_absent_with_flag_disabled_test(
82+
name = "disabled_attr_matches_disabled_flag_test",
83+
target_under_test = ":build_script_disabled",
84+
)
85+
_cc_toolchain_absent_with_flag_enabled_test(
86+
name = "disabled_attr_overrides_enabled_flag_test",
87+
target_under_test = ":build_script_disabled",
88+
)
89+
90+
native.test_suite(
91+
name = name,
92+
tests = [
93+
":default_attr_follows_disabled_flag_test",
94+
":disabled_attr_matches_disabled_flag_test",
95+
":disabled_attr_overrides_enabled_flag_test",
96+
],
97+
)

crate_universe/extensions.bzl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,19 @@ def _crate_impl(module_ctx):
10411041
annotation_dict["gen_binaries"] = True
10421042
annotation_dict["gen_build_script"] = _OPT_BOOL_VALUES[annotation_dict["gen_build_script"]]
10431043

1044+
# Convert the tri-state string values ("auto"/"on"/"off") into the
1045+
# `int` representation understood by `crate.annotation` (`None`, `1`,
1046+
# or `0` respectively).
1047+
for opt_bool_key in (
1048+
"build_script_use_cc_toolchain",
1049+
"build_script_use_default_shell_env",
1050+
):
1051+
bool_value = _OPT_BOOL_VALUES[annotation_dict[opt_bool_key]]
1052+
if bool_value == None:
1053+
annotation_dict.pop(opt_bool_key)
1054+
else:
1055+
annotation_dict[opt_bool_key] = int(bool_value)
1056+
10441057
# Process the override targets for the annotation.
10451058
# In the non-bzlmod approach, this is given as a dict
10461059
# with the possible keys "`proc_macro`, `build_script`, `lib`, `bin`".
@@ -1306,6 +1319,24 @@ _ANNOTATION_NORMAL_ATTRS = {
13061319
"build_script_toolchains": attr.label_list(
13071320
doc = "A list of labels to set on a crates's `cargo_build_script::toolchains` attribute.",
13081321
),
1322+
"build_script_use_cc_toolchain": attr.string(
1323+
doc = (
1324+
"Whether or not to pull in the resolved `cc_toolchain` when running the build script. " +
1325+
"Supported values are `on`, `off`, and `auto`. Setting `auto` (the default) defers to the " +
1326+
"`@rules_rust//cargo/settings:use_cc_toolchain` build setting (defaults to enabled)."
1327+
),
1328+
values = _OPT_BOOL_VALUES.keys(),
1329+
default = "auto",
1330+
),
1331+
"build_script_use_default_shell_env": attr.string(
1332+
doc = (
1333+
"Whether or not to include the default shell environment for the build script action. " +
1334+
"Supported values are `on`, `off`, and `auto`. Setting `auto` (the default) defers to the " +
1335+
"`@rules_rust//cargo/settings:use_default_shell_env` build setting."
1336+
),
1337+
values = _OPT_BOOL_VALUES.keys(),
1338+
default = "auto",
1339+
),
13091340
"compile_data_glob": attr.string_list(
13101341
doc = "A list of glob patterns to add to a crate's `rust_library::compile_data` attribute.",
13111342
),

crate_universe/private/crate.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def _annotation(
103103
build_script_rustc_env = None,
104104
build_script_toolchains = None,
105105
build_script_use_default_shell_env = None,
106+
build_script_use_cc_toolchain = None,
106107
compile_data = None,
107108
compile_data_glob = None,
108109
compile_data_glob_excludes = None,
@@ -151,6 +152,9 @@ def _annotation(
151152
build_script_toolchains (list, optional): A list of labels to set on a crates's `cargo_build_script::toolchains` attribute.
152153
build_script_use_default_shell_env (int, optional): Whether or not to include the default shell environment for the build
153154
script action.
155+
build_script_use_cc_toolchain (int, optional): Whether or not to pull in the resolved `cc_toolchain` when
156+
running the build script. Set to `1` to force enable, `0` to force disable, or leave unset to
157+
defer to the `@rules_rust//cargo/settings:use_cc_toolchain` build setting (defaults to enabled).
154158
compile_data (list, optional): A list of labels to add to a crate's `rust_library::compile_data` attribute.
155159
compile_data_glob (list, optional): A list of glob patterns to add to a crate's `rust_library::compile_data`
156160
attribute.
@@ -217,6 +221,7 @@ def _annotation(
217221
build_script_rustc_env = build_script_rustc_env,
218222
build_script_toolchains = _stringify_list(build_script_toolchains),
219223
build_script_use_default_shell_env = build_script_use_default_shell_env,
224+
build_script_use_cc_toolchain = build_script_use_cc_toolchain,
220225
compile_data = _stringify_list(compile_data),
221226
compile_data_glob = compile_data_glob,
222227
compile_data_glob_excludes = compile_data_glob_excludes,

0 commit comments

Comments
 (0)