diff --git a/rust/extensions.bzl b/rust/extensions.bzl index 44601533f8..73c67d205e 100644 --- a/rust/extensions.bzl +++ b/rust/extensions.bzl @@ -16,6 +16,7 @@ load( "DEFAULT_NIGHTLY_VERSION", "DEFAULT_STATIC_RUST_URL_TEMPLATES", ) +load("//rust/private:strip_level.bzl", "build_strip_levels") _RUST_TOOLCHAIN_VERSIONS = [ rust_common.default_version, @@ -113,7 +114,14 @@ def _rust_impl(module_ctx): if toolchain_triples.get(repository_set["exec_triple"]) == repository_set["name"]: toolchain_triples.pop(repository_set["exec_triple"], None) - toolchains = root.tags.toolchain or rules_rust.tags.toolchain + # Toolchains and their `strip_level_select`s are read from the same module + # so that the selects always match the toolchains they refine. + if root.tags.toolchain: + toolchains = root.tags.toolchain + strip_level_selects = root.tags.strip_level_select + else: + toolchains = rules_rust.tags.toolchain + strip_level_selects = rules_rust.tags.strip_level_select for toolchain in toolchains: if toolchain.extra_rustc_flags and toolchain.extra_rustc_flags_triples: @@ -130,6 +138,8 @@ def _rust_impl(module_ctx): extra_rustc_flags = toolchain.extra_rustc_flags if toolchain.extra_rustc_flags else toolchain.extra_rustc_flags_triples extra_exec_rustc_flags = toolchain.extra_exec_rustc_flags if toolchain.extra_rustc_flags else toolchain.extra_exec_rustc_flags_triples + triples = list(toolchain_triples.keys()) + list(toolchain.extra_target_triples) + rust_register_toolchains( hub_name = "rust_toolchains", dev_components = toolchain.dev_components, @@ -143,7 +153,11 @@ def _rust_impl(module_ctx): sha256s = toolchain.sha256s, extra_target_triples = toolchain.extra_target_triples, opt_level = toolchain.opt_level if toolchain.opt_level else None, - strip_level = toolchain.strip_level if toolchain.strip_level else None, + strip_level = build_strip_levels( + strip_level_selects = strip_level_selects, + default_strip_level = toolchain.strip_level, + triples = triples, + ) if triples else None, urls = toolchain.urls, versions = toolchain.versions, compact_windows_names = True, @@ -300,11 +314,44 @@ _RUST_TOOLCHAIN_TAG = tag_class( } | _COMMON_TAG_KWARGS, ) +_RUST_STRIP_LEVEL_SELECT = tag_class( + doc = """\ +Override the `strip_level` for specific target triples. + +```python +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.strip_level_select( + triples = ["x86_64-unknown-linux-gnu"], + opt = "symbols", +) +``` +""", + attrs = { + "dbg": attr.string( + doc = "Strip level for the `dbg` compilation mode.", + default = "none", + ), + "fastbuild": attr.string( + doc = "Strip level for the `fastbuild` compilation mode.", + default = "none", + ), + "opt": attr.string( + doc = "Strip level for the `opt` compilation mode.", + default = "debuginfo", + ), + "triples": attr.string_list( + doc = "The target triples these strip levels apply to.", + mandatory = True, + ), + }, +) + rust = module_extension( doc = "Rust toolchain extension.", implementation = _rust_impl, tag_classes = { "repository_set": _RUST_REPOSITORY_SET_TAG, + "strip_level_select": _RUST_STRIP_LEVEL_SELECT, "toolchain": _RUST_TOOLCHAIN_TAG, }, ) diff --git a/rust/private/strip_level.bzl b/rust/private/strip_level.bzl new file mode 100644 index 0000000000..471212a8b9 --- /dev/null +++ b/rust/private/strip_level.bzl @@ -0,0 +1,47 @@ +"""Helpers for looking up strip levels.""" + +def build_strip_levels(*, strip_level_selects, default_strip_level, triples): + """Look up the per-triple `strip_level` with defaults. + + Args: + strip_level_selects (list): The `strip_level_select` tags, each with a + `triples` list and specified strip levels. + default_strip_level (dict): The fallback strip levels. + triples (list): The target triples toolchains are being registered for. + Must not be empty. + + Returns: + dict: Mapping of target triple to strip levels (compilation mode to + level). + """ + if not triples: + fail("`triples` must not be empty.") + + levels_by_triple = {} # { "x86_64-darwin" : { "dbg" = ...} } + for select in strip_level_selects: + # a select fully defines the strip level for each mode + levels = { + "dbg": select.dbg, + "fastbuild": select.fastbuild, + "opt": select.opt, + } + + # insert all of this select's triples + for triple in select.triples: + # error out if triple is selected multiple times + if triple in levels_by_triple: + fail("Triple `{}` is configured by more than one `strip_level_select` tag.".format(triple)) + levels_by_triple[triple] = levels + + strip_level = {} + for triple in triples: + if triple in levels_by_triple: + strip_level[triple] = levels_by_triple[triple] + elif default_strip_level: + strip_level[triple] = default_strip_level + + # Honor selects for triples that aren't part of the default triple set. + for triple, levels in levels_by_triple.items(): + strip_level.setdefault(triple, levels) + + return strip_level diff --git a/test/unit/strip_level_select/BUILD.bazel b/test/unit/strip_level_select/BUILD.bazel new file mode 100644 index 0000000000..1ff3eff4e3 --- /dev/null +++ b/test/unit/strip_level_select/BUILD.bazel @@ -0,0 +1,3 @@ +load(":strip_level_select_test.bzl", "strip_level_select_test_suite") + +strip_level_select_test_suite(name = "strip_level_select_test_suite") diff --git a/test/unit/strip_level_select/strip_level_select_test.bzl b/test/unit/strip_level_select/strip_level_select_test.bzl new file mode 100644 index 0000000000..6c774475ec --- /dev/null +++ b/test/unit/strip_level_select/strip_level_select_test.bzl @@ -0,0 +1,107 @@ +"""Unit tests for `build_strip_levels`, backing the `rust.strip_level_select` tag.""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") + +# buildifier: disable=bzl-visibility +load("//rust/private:strip_level.bzl", "build_strip_levels") + +# Mirrors the `strip_level_select` tag's attribute defaults, which match the +# `rust_toolchain` strip level defaults. +def _select(triples, dbg = "none", fastbuild = "none", opt = "debuginfo"): + return struct(triples = triples, dbg = dbg, fastbuild = fastbuild, opt = opt) + +_DEFAULT = {"dbg": "none", "fastbuild": "none", "opt": "none"} + +def _build_strip_levels_test_impl(ctx): + env = unittest.begin(ctx) + + # No selects and no default: nothing is configured. + asserts.equals( + env, + {}, + build_strip_levels( + strip_level_selects = [], + default_strip_level = {}, + triples = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin"], + ), + ) + + # The default is applied to every triple when there are no selects. + asserts.equals( + env, + { + "aarch64-apple-darwin": _DEFAULT, + "x86_64-unknown-linux-gnu": _DEFAULT, + }, + build_strip_levels( + strip_level_selects = [], + default_strip_level = _DEFAULT, + triples = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin"], + ), + ) + + # A matching select overrides the default; unmatched triples keep the default. + asserts.equals( + env, + { + "aarch64-apple-darwin": _DEFAULT, + "x86_64-unknown-linux-gnu": {"dbg": "none", "fastbuild": "none", "opt": "symbols"}, + }, + build_strip_levels( + strip_level_selects = [ + _select(["x86_64-unknown-linux-gnu"], opt = "symbols"), + ], + default_strip_level = _DEFAULT, + triples = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin"], + ), + ) + + # A select applies even when there is no default for the other triples. + asserts.equals( + env, + {"x86_64-unknown-linux-gnu": {"dbg": "none", "fastbuild": "none", "opt": "symbols"}}, + build_strip_levels( + strip_level_selects = [ + _select(["x86_64-unknown-linux-gnu"], opt = "symbols"), + ], + default_strip_level = {}, + triples = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin"], + ), + ) + + # One select can target multiple triples, and selects for triples outside the + # default triple set are still honored. + selected = {"dbg": "none", "fastbuild": "none", "opt": "symbols"} + asserts.equals( + env, + { + "aarch64-apple-darwin": selected, + "wasm32-unknown-unknown": selected, + "x86_64-unknown-linux-gnu": selected, + }, + build_strip_levels( + strip_level_selects = [ + _select( + ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "wasm32-unknown-unknown"], + opt = "symbols", + ), + ], + default_strip_level = {}, + triples = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin"], + ), + ) + + return unittest.end(env) + +build_strip_levels_test = unittest.make(_build_strip_levels_test_impl) + +def strip_level_select_test_suite(name): + """Unit tests for `build_strip_levels`. + + Args: + name: the test suite name + """ + unittest.suite( + name, + build_strip_levels_test, + )