From ee89c43f9d523fdc8f9cf0eb6a932df1ed511e3b Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 19 May 2026 15:48:45 +0200 Subject: [PATCH 1/4] feat: support custom Zig toolchain constraints --- zig/private/bzlmod/zig.bzl | 76 +++++- zig/private/repo/toolchains_repo.bzl | 23 +- zig/tests/bzlmod_zig_test.bzl | 351 ++++++++++++++++----------- 3 files changed, 287 insertions(+), 163 deletions(-) diff --git a/zig/private/bzlmod/zig.bzl b/zig/private/bzlmod/zig.bzl index a9d635d2..575e1f53 100644 --- a/zig/private/bzlmod/zig.bzl +++ b/zig/private/bzlmod/zig.bzl @@ -21,12 +21,27 @@ _DEFAULT_NAME = "zig" zig_toolchain = tag_class( attrs = { + "name": attr.string( + doc = "A descriptive suffix for generated toolchain targets. Leave empty for the default wrapper names.", + mandatory = False, + default = "", + ), "zig_version": attr.string(doc = "The Zig SDK version.", mandatory = True), "default": attr.bool( doc = "Make this the default Zig SDK version. Can only be used once, and only in the root module.", mandatory = False, default = False, ), + "extra_exec_compatible_with": attr.label_list( + doc = "Additional execution platform constraints for generated Zig SDK toolchain targets.", + mandatory = False, + default = [], + ), + "extra_target_settings": attr.label_list( + doc = "Additional target settings for generated Zig SDK toolchain targets.", + mandatory = False, + default = [], + ), }, doc = """\ Fetch and define toolchain targets for the given Zig SDK version. @@ -70,23 +85,43 @@ def handle_toolchain_tags(modules, *, known_versions): known_versions: sequence of string, The set of known Zig versions. Returns: - (err, versions), maybe an error or the list of versions. + (err, versions, variants), maybe an error, the ordered list of versions, + and the list of requested toolchain wrappers. """ default = None versions = sets.make() + variants = [] + variant_keys = {} for mod in modules: for toolchain in mod.tags.toolchain: if toolchain.default: if not mod.is_root: - return (["Only the root module may specify a default Zig SDK version.", toolchain], None) + return (["Only the root module may specify a default Zig SDK version.", toolchain], None, None) if default != None: - return (["You may only specify one default Zig SDK version.", toolchain], None) + return (["You may only specify one default Zig SDK version.", toolchain], None, None) default = toolchain.zig_version sets.insert(versions, toolchain.zig_version) + key = "{}\n{}".format(toolchain.zig_version, toolchain.name) + extra_exec_compatible_with = [str(label) for label in toolchain.extra_exec_compatible_with] + extra_target_settings = [str(label) for label in toolchain.extra_target_settings] + variant_fingerprint = repr((extra_exec_compatible_with, extra_target_settings)) + if key in variant_keys: + if variant_keys[key] == variant_fingerprint: + continue + + return (["Conflicting Zig SDK toolchain variant name '{}' for version '{}'.".format(toolchain.name, toolchain.zig_version), toolchain], None, None) + + variant_keys[key] = variant_fingerprint + variants.append(struct( + name = toolchain.name, + zig_version = toolchain.zig_version, + extra_exec_compatible_with = extra_exec_compatible_with, + extra_target_settings = extra_target_settings, + )) if default != None: sets.remove(versions, default) @@ -98,8 +133,14 @@ def handle_toolchain_tags(modules, *, known_versions): if len(versions) == 0: versions.append(known_versions[0]) + variants.append(struct( + name = "", + zig_version = known_versions[0], + extra_exec_compatible_with = [], + extra_target_settings = [], + )) - return None, versions + return None, versions, variants def parse_zig_versions_json(json_string): """Parse a Zig SDK versions index in JSON format. @@ -182,7 +223,7 @@ def _toolchain_extension(module_ctx): known_versions = merge_version_specs(version_specs) - (err, versions) = handle_toolchain_tags(module_ctx.modules, known_versions = known_versions.keys()) + (err, versions, toolchain_variants) = handle_toolchain_tags(module_ctx.modules, known_versions = known_versions.keys()) if err != None: fail(*err) @@ -191,15 +232,12 @@ def _toolchain_extension(module_ctx): toolchain_zig_versions = [] toolchain_exec_lengths = [] toolchain_exec_constraints = [] + toolchain_target_settings_lengths = [] + toolchain_target_settings = [] for zig_version in versions: sanitized_zig_version = sanitize_version(zig_version) for platform, meta in PLATFORMS.items(): repo_name = _DEFAULT_NAME + "_" + sanitized_zig_version + "_" + platform - toolchain_names.append(repo_name) - toolchain_labels.append("@{}//:zig_toolchain".format(repo_name)) - toolchain_zig_versions.append(zig_version) - toolchain_exec_lengths.append(len(meta.compatible_with)) - toolchain_exec_constraints.extend(meta.compatible_with) zig_repository( name = repo_name, url = known_versions[zig_version][platform].url, @@ -208,6 +246,22 @@ def _toolchain_extension(module_ctx): zig_version = zig_version, platform = platform, ) + for variant in toolchain_variants: + if variant.zig_version != zig_version: + continue + + name = repo_name + if variant.name: + name = "{}_{}".format(name, variant.name) + + compatible_with = meta.compatible_with + variant.extra_exec_compatible_with + toolchain_names.append(name) + toolchain_labels.append("@{}//:zig_toolchain".format(repo_name)) + toolchain_zig_versions.append(zig_version) + toolchain_exec_lengths.append(len(compatible_with)) + toolchain_exec_constraints.extend(compatible_with) + toolchain_target_settings_lengths.append(len(variant.extra_target_settings)) + toolchain_target_settings.extend(variant.extra_target_settings) toolchains_repo( name = _DEFAULT_NAME + "_toolchains", @@ -216,6 +270,8 @@ def _toolchain_extension(module_ctx): zig_versions = toolchain_zig_versions, exec_lengths = toolchain_exec_lengths, exec_constraints = toolchain_exec_constraints, + target_settings_lengths = toolchain_target_settings_lengths, + target_settings = toolchain_target_settings, ) zig = module_extension( diff --git a/zig/private/repo/toolchains_repo.bzl b/zig/private/repo/toolchains_repo.bzl index 9696a7e6..673b67fb 100644 --- a/zig/private/repo/toolchains_repo.bzl +++ b/zig/private/repo/toolchains_repo.bzl @@ -34,6 +34,8 @@ ATTRS = { "zig_versions": attr.string_list(doc = "The Zig SDK versions of the corresponding toolchain targets."), "exec_lengths": attr.int_list(doc = "The length of the slice of the `exec_constraints` attribute that corresponds to each toolchain target."), "exec_constraints": attr.string_list(doc = "All toolchain execution platform constraints concatenated to a single list."), + "target_settings_lengths": attr.int_list(doc = "The length of the slice of the `target_settings` attribute that corresponds to each toolchain target."), + "target_settings": attr.string_list(doc = "All extra toolchain target settings concatenated to a single list."), } def sanitize_version(zig_version): @@ -63,10 +65,10 @@ def _toolchains_repo_impl(repository_ctx): len_expected = len(repository_ctx.attr.names) len_equal = all([ len_expected == len(getattr(repository_ctx.attr, attr)) - for attr in ["labels", "zig_versions", "exec_lengths"] + for attr in ["labels", "zig_versions", "exec_lengths", "target_settings_lengths"] ]) if not len_equal: - fail("Lengths of the attributes `names`, `labels`, `zig_versions`, `exec_lengths` must match.") + fail("Lengths of the attributes `names`, `labels`, `zig_versions`, `exec_lengths`, `target_settings_lengths` must match.") len_exec_constraints = 0 for exec_len in repository_ctx.attr.exec_lengths: @@ -75,6 +77,13 @@ def _toolchains_repo_impl(repository_ctx): if not len_exec_constraints == len(repository_ctx.attr.exec_constraints): fail("Length of the `exec_constraints` attribute must match the sum of `exec_lengths`.") + len_target_settings = 0 + for target_settings_len in repository_ctx.attr.target_settings_lengths: + len_target_settings += target_settings_len + + if not len_target_settings == len(repository_ctx.attr.target_settings): + fail("Length of the `target_settings` attribute must match the sum of `target_settings_lengths`.") + if len(repository_ctx.attr.zig_versions) < 1: fail("Must specify at least one Zig SDK version in `zig_versions`.") @@ -160,18 +169,22 @@ selects.config_setting_group( repository_ctx.attr.labels, repository_ctx.attr.zig_versions, repository_ctx.attr.exec_lengths, + repository_ctx.attr.target_settings_lengths, ) exec_offset = 0 - for counter, (name, label, zig_version, exec_len) in enumerate(zipped): + target_settings_offset = 0 + for counter, (name, label, zig_version, exec_len, target_settings_len) in enumerate(zipped): compatible_with = repository_ctx.attr.exec_constraints[exec_offset:exec_offset + exec_len] exec_offset += exec_len + target_settings = [":{}".format(zig_version)] + repository_ctx.attr.target_settings[target_settings_offset:target_settings_offset + target_settings_len] + target_settings_offset += target_settings_len build_content += """ # Declare a toolchain Bazel will select for running the tool in an action # on the execution platform. toolchain( name = "{prefix}_{name}_toolchain", exec_compatible_with = {compatible_with}, - target_settings = [":{version}"], + target_settings = {target_settings}, toolchain = "{label}", toolchain_type = "@rules_zig//zig:toolchain_type", ) @@ -179,7 +192,7 @@ toolchain( prefix = _counter_prefix(counter, width = counter_digits), name = name, compatible_with = compatible_with, - version = zig_version, + target_settings = target_settings, label = label, ) diff --git a/zig/tests/bzlmod_zig_test.bzl b/zig/tests/bzlmod_zig_test.bzl index c89dafd2..11a50d7f 100644 --- a/zig/tests/bzlmod_zig_test.bzl +++ b/zig/tests/bzlmod_zig_test.bzl @@ -277,96 +277,131 @@ _merge_version_specs_test = unittest.make( _merge_version_specs_test_impl, ) +def _toolchain_tag(*, zig_version, default = False, name = "", extra_exec_compatible_with = [], extra_target_settings = []): + return struct( + name = name, + zig_version = zig_version, + default = default, + extra_exec_compatible_with = extra_exec_compatible_with, + extra_target_settings = extra_target_settings, + ) + +def _toolchain_variant(*, zig_version, name = "", extra_exec_compatible_with = [], extra_target_settings = []): + return struct( + name = name, + zig_version = zig_version, + extra_exec_compatible_with = extra_exec_compatible_with, + extra_target_settings = extra_target_settings, + ) + +def _assert_toolchain_versions(env, expected, modules, *, known_versions, msg): + result = handle_toolchain_tags(modules, known_versions = known_versions) + asserts.equals(env, None, result[0], msg) + asserts.equals(env, expected, result[1], msg) + def _zig_versions_test_impl(ctx): env = unittest.begin(ctx) - asserts.equals( + _assert_toolchain_versions( env, - (None, ["0.1.0"]), - handle_toolchain_tags([], known_versions = ["0.1.0"]), - "should fall back to the default Zig SDK version", + ["0.1.0"], + [], + known_versions = ["0.1.0"], + msg = "should fall back to the default Zig SDK version", ) asserts.equals( env, - (None, ["0.1.0"]), - handle_toolchain_tags( - [ - struct( - is_root = False, - tags = struct( - toolchain = [ - struct( - default = False, - zig_version = "0.1.0", - ), - ], - ), + [_toolchain_variant(zig_version = "0.1.0")], + handle_toolchain_tags([], known_versions = ["0.1.0"])[2], + "fallback should create one default wrapper", + ) + + _assert_toolchain_versions( + env, + ["0.1.0"], + [ + struct( + is_root = False, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.1.0"), + ], ), - ], - known_versions = ["0.1.0"], - ), - "should choose a single configured version", + ), + ], + known_versions = ["0.1.0"], + msg = "should choose a single configured version", ) - asserts.equals( + _assert_toolchain_versions( env, - (None, ["0.4.0", "0.2.0", "0.1.0", "0.0.1"]), - handle_toolchain_tags( - [ - struct( - is_root = False, - tags = struct( - toolchain = [ - struct( - default = False, - zig_version = "0.0.1", - ), - struct( - default = False, - zig_version = "0.4.0", - ), - ], - ), + ["0.4.0", "0.2.0", "0.1.0", "0.0.1"], + [ + struct( + is_root = False, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.0.1"), + _toolchain_tag(zig_version = "0.4.0"), + ], ), - struct( - is_root = False, - tags = struct( - toolchain = [ - struct( - default = False, - zig_version = "0.2.0", - ), - struct( - default = False, - zig_version = "0.1.0", - ), - ], - ), + ), + struct( + is_root = False, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.2.0"), + _toolchain_tag(zig_version = "0.1.0"), + ], ), - ], - known_versions = ["0.4.0", "0.2.0", "0.1.0", "0.0.1"], - ), - "should order versions by semver", + ), + ], + known_versions = ["0.4.0", "0.2.0", "0.1.0", "0.0.1"], + msg = "should order versions by semver", + ) + + _assert_toolchain_versions( + env, + ["0.1.0", "0.0.1"], + [ + struct( + is_root = False, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.0.1"), + _toolchain_tag(zig_version = "0.1.0"), + ], + ), + ), + struct( + is_root = False, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.0.1"), + _toolchain_tag(zig_version = "0.1.0"), + ], + ), + ), + ], + known_versions = ["0.1.0", "0.0.1"], + msg = "should deduplicate versions", ) asserts.equals( env, - (None, ["0.1.0", "0.0.1"]), + [ + _toolchain_variant(zig_version = "0.0.1"), + _toolchain_variant(zig_version = "0.1.0"), + ], handle_toolchain_tags( [ struct( is_root = False, tags = struct( toolchain = [ - struct( - default = False, - zig_version = "0.0.1", - ), - struct( - default = False, - zig_version = "0.1.0", - ), + _toolchain_tag(zig_version = "0.0.1"), + _toolchain_tag(zig_version = "0.1.0"), ], ), ), @@ -374,157 +409,177 @@ def _zig_versions_test_impl(ctx): is_root = False, tags = struct( toolchain = [ - struct( - default = False, - zig_version = "0.0.1", - ), - struct( - default = False, - zig_version = "0.1.0", - ), + _toolchain_tag(zig_version = "0.0.1"), + _toolchain_tag(zig_version = "0.1.0"), ], ), ), ], known_versions = ["0.1.0", "0.0.1"], - ), - "should deduplicate versions", + )[2], + "should deduplicate identical wrappers", ) asserts.equals( env, - (None, ["0.1.0", "0.4.0", "0.2.0", "0.0.1"]), + [ + _toolchain_variant( + name = "local", + zig_version = "0.1.0", + extra_exec_compatible_with = ["//constraints:local"], + extra_target_settings = ["//settings:enabled"], + ), + ], handle_toolchain_tags( [ - struct( - is_root = False, - tags = struct( - toolchain = [ - struct( - default = False, - zig_version = "0.0.1", - ), - struct( - default = False, - zig_version = "0.4.0", - ), - ], - ), - ), struct( is_root = True, tags = struct( toolchain = [ - struct( - default = False, - zig_version = "0.2.0", - ), - struct( - default = True, + _toolchain_tag( + name = "local", zig_version = "0.1.0", + extra_exec_compatible_with = ["//constraints:local"], + extra_target_settings = ["//settings:enabled"], ), ], ), ), ], - known_versions = ["0.4.0", "0.2.0", "0.1.0", "0.0.1"], - ), - "the default should take precedence", + known_versions = ["0.1.0"], + )[2], + "should preserve wrapper metadata", + ) + + _assert_toolchain_versions( + env, + ["0.1.0", "0.4.0", "0.2.0", "0.0.1"], + [ + struct( + is_root = False, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.0.1"), + _toolchain_tag(zig_version = "0.4.0"), + ], + ), + ), + struct( + is_root = True, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.2.0"), + _toolchain_tag(zig_version = "0.1.0", default = True), + ], + ), + ), + ], + known_versions = ["0.4.0", "0.2.0", "0.1.0", "0.0.1"], + msg = "the default should take precedence", + ) + + _assert_toolchain_versions( + env, + ["0.1.0", "0.2.0", "0.0.1"], + [ + struct( + is_root = False, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.0.1"), + _toolchain_tag(zig_version = "0.1.0"), + ], + ), + ), + struct( + is_root = True, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.2.0"), + _toolchain_tag(zig_version = "0.1.0", default = True), + ], + ), + ), + ], + known_versions = ["0.2.0", "0.1.0", "0.0.1"], + msg = "should not duplicate default", ) asserts.equals( env, - (None, ["0.1.0", "0.2.0", "0.0.1"]), + (["Only the root module may specify a default Zig SDK version.", _toolchain_tag( + default = True, + zig_version = "0.1.0", + )], None, None), handle_toolchain_tags( [ struct( is_root = False, tags = struct( toolchain = [ - struct( - default = False, - zig_version = "0.0.1", - ), - struct( - default = False, - zig_version = "0.1.0", - ), - ], - ), - ), - struct( - is_root = True, - tags = struct( - toolchain = [ - struct( - default = False, - zig_version = "0.2.0", - ), - struct( - default = True, - zig_version = "0.1.0", - ), + _toolchain_tag(zig_version = "0.1.0", default = True), ], ), ), ], - known_versions = ["0.2.0", "0.1.0", "0.0.1"], + known_versions = ["0.1.0"], ), - "should not duplicate default", + "only root may set default", ) asserts.equals( env, - (["Only the root module may specify a default Zig SDK version.", struct( + (["You may only specify one default Zig SDK version.", _toolchain_tag( default = True, - zig_version = "0.1.0", - )], None), + zig_version = "0.2.0", + )], None, None), handle_toolchain_tags( [ struct( - is_root = False, + is_root = True, tags = struct( toolchain = [ - struct( - default = True, - zig_version = "0.1.0", - ), + _toolchain_tag(zig_version = "0.1.0", default = True), + _toolchain_tag(zig_version = "0.2.0", default = True), ], ), ), ], - known_versions = ["0.1.0"], + known_versions = ["0.2.0", "0.1.0"], ), - "only root may set default", + "only one default allowed", ) asserts.equals( env, - (["You may only specify one default Zig SDK version.", struct( - default = True, - zig_version = "0.2.0", - )], None), + (["Conflicting Zig SDK toolchain variant name 'local' for version '0.1.0'.", _toolchain_tag( + name = "local", + zig_version = "0.1.0", + extra_target_settings = ["//settings:disabled"], + )], None, None), handle_toolchain_tags( [ struct( is_root = True, tags = struct( toolchain = [ - struct( - default = True, + _toolchain_tag( + name = "local", zig_version = "0.1.0", + extra_target_settings = ["//settings:enabled"], ), - struct( - default = True, - zig_version = "0.2.0", + _toolchain_tag( + name = "local", + zig_version = "0.1.0", + extra_target_settings = ["//settings:disabled"], ), ], ), ), ], - known_versions = ["0.2.0", "0.1.0"], + known_versions = ["0.1.0"], ), - "only one default allowed", + "conflicting duplicate wrapper metadata should fail", ) return unittest.end(env) From 25318c15833e6b77f65bd3638a89d651317d88d5 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 19 May 2026 18:16:49 +0200 Subject: [PATCH 2/4] feat: add global Zig toolchain metadata tags --- docs/extensions.md | 46 +++++- zig/private/bzlmod/zig.bzl | 88 ++++++++++- zig/private/repo/toolchains_repo.bzl | 21 ++- zig/tests/bzlmod_zig_test.bzl | 213 ++++++++++++++++++++++++++- 4 files changed, 357 insertions(+), 11 deletions(-) diff --git a/docs/extensions.md b/docs/extensions.md index 78d6b450..780471aa 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -8,7 +8,11 @@ Extensions for bzlmod.
 zig = use_extension("@rules_zig//zig:extensions.bzl", "zig")
-zig.toolchain(default, zig_version)
+zig.toolchain(name, default, extra_exec_compatible_with, extra_target_compatible_with,
+              extra_target_settings, zig_version)
+zig.extra_exec_compatible_with(constraints)
+zig.extra_target_compatible_with(constraints)
+zig.extra_target_settings(settings)
 zig.index(file)
 zig.mirrors(urls)
 
@@ -37,9 +41,49 @@ Defaults to the latest known version. | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A descriptive suffix for generated toolchain targets. Leave empty for the default wrapper names. | Name | optional | `""` | | default | Make this the default Zig SDK version. Can only be used once, and only in the root module. | Boolean | optional | `False` | +| extra_exec_compatible_with | Additional execution platform constraints for generated Zig SDK toolchain targets. | List of labels | optional | `[]` | +| extra_target_compatible_with | Additional target platform constraints for generated Zig SDK toolchain targets. | List of labels | optional | `[]` | +| extra_target_settings | Additional target settings for generated Zig SDK toolchain targets. | List of labels | optional | `[]` | | zig_version | The Zig SDK version. | String | required | | + + +### extra_exec_compatible_with + +Add execution platform constraints to all generated Zig SDK toolchain targets. + +**Attributes** + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| constraints | Additional execution platform constraints for generated Zig SDK toolchain targets. | List of labels | optional | `[]` | + + + +### extra_target_compatible_with + +Add target platform constraints to all generated Zig SDK toolchain targets. + +**Attributes** + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| constraints | Additional target platform constraints for generated Zig SDK toolchain targets. | List of labels | optional | `[]` | + + + +### extra_target_settings + +Add target settings to all generated Zig SDK toolchain targets. + +**Attributes** + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| settings | Additional target settings for generated Zig SDK toolchain targets. | List of labels | optional | `[]` | + ### index diff --git a/zig/private/bzlmod/zig.bzl b/zig/private/bzlmod/zig.bzl index 575e1f53..2918da09 100644 --- a/zig/private/bzlmod/zig.bzl +++ b/zig/private/bzlmod/zig.bzl @@ -37,6 +37,11 @@ zig_toolchain = tag_class( mandatory = False, default = [], ), + "extra_target_compatible_with": attr.label_list( + doc = "Additional target platform constraints for generated Zig SDK toolchain targets.", + mandatory = False, + default = [], + ), "extra_target_settings": attr.label_list( doc = "Additional target settings for generated Zig SDK toolchain targets.", mandatory = False, @@ -50,6 +55,45 @@ Defaults to the latest known version. """, ) +zig_extra_exec_compatible_with = tag_class( + attrs = { + "constraints": attr.label_list( + doc = "Additional execution platform constraints for generated Zig SDK toolchain targets.", + mandatory = False, + default = [], + ), + }, + doc = """\ +Add execution platform constraints to all generated Zig SDK toolchain targets. +""", +) + +zig_extra_target_compatible_with = tag_class( + attrs = { + "constraints": attr.label_list( + doc = "Additional target platform constraints for generated Zig SDK toolchain targets.", + mandatory = False, + default = [], + ), + }, + doc = """\ +Add target platform constraints to all generated Zig SDK toolchain targets. +""", +) + +zig_extra_target_settings = tag_class( + attrs = { + "settings": attr.label_list( + doc = "Additional target settings for generated Zig SDK toolchain targets.", + mandatory = False, + default = [], + ), + }, + doc = """\ +Add target settings to all generated Zig SDK toolchain targets. +""", +) + zig_index = tag_class( attrs = { "file": attr.label(doc = "The Zig version index JSON file.", mandatory = True), @@ -71,6 +115,9 @@ zig_mirrors = tag_class( TAG_CLASSES = { "toolchain": zig_toolchain, + "extra_exec_compatible_with": zig_extra_exec_compatible_with, + "extra_target_compatible_with": zig_extra_target_compatible_with, + "extra_target_settings": zig_extra_target_settings, "index": zig_index, "mirrors": zig_mirrors, } @@ -92,6 +139,28 @@ def handle_toolchain_tags(modules, *, known_versions): versions = sets.make() variants = [] variant_keys = {} + global_extra_exec_compatible_with = [] + global_extra_target_compatible_with = [] + global_extra_target_settings = [] + + for mod in modules: + for extra in getattr(mod.tags, "extra_exec_compatible_with", []): + if not mod.is_root: + return (["Only the root module may specify extra Zig SDK execution constraints.", extra], None, None) + + global_extra_exec_compatible_with.extend([str(label) for label in extra.constraints]) + + for extra in getattr(mod.tags, "extra_target_compatible_with", []): + if not mod.is_root: + return (["Only the root module may specify extra Zig SDK target constraints.", extra], None, None) + + global_extra_target_compatible_with.extend([str(label) for label in extra.constraints]) + + for extra in getattr(mod.tags, "extra_target_settings", []): + if not mod.is_root: + return (["Only the root module may specify extra Zig SDK target settings.", extra], None, None) + + global_extra_target_settings.extend([str(label) for label in extra.settings]) for mod in modules: for toolchain in mod.tags.toolchain: @@ -106,9 +175,10 @@ def handle_toolchain_tags(modules, *, known_versions): sets.insert(versions, toolchain.zig_version) key = "{}\n{}".format(toolchain.zig_version, toolchain.name) - extra_exec_compatible_with = [str(label) for label in toolchain.extra_exec_compatible_with] - extra_target_settings = [str(label) for label in toolchain.extra_target_settings] - variant_fingerprint = repr((extra_exec_compatible_with, extra_target_settings)) + extra_exec_compatible_with = global_extra_exec_compatible_with + [str(label) for label in toolchain.extra_exec_compatible_with] + extra_target_compatible_with = global_extra_target_compatible_with + [str(label) for label in toolchain.extra_target_compatible_with] + extra_target_settings = global_extra_target_settings + [str(label) for label in toolchain.extra_target_settings] + variant_fingerprint = repr((extra_exec_compatible_with, extra_target_compatible_with, extra_target_settings)) if key in variant_keys: if variant_keys[key] == variant_fingerprint: continue @@ -120,6 +190,7 @@ def handle_toolchain_tags(modules, *, known_versions): name = toolchain.name, zig_version = toolchain.zig_version, extra_exec_compatible_with = extra_exec_compatible_with, + extra_target_compatible_with = extra_target_compatible_with, extra_target_settings = extra_target_settings, )) @@ -136,8 +207,9 @@ def handle_toolchain_tags(modules, *, known_versions): variants.append(struct( name = "", zig_version = known_versions[0], - extra_exec_compatible_with = [], - extra_target_settings = [], + extra_exec_compatible_with = global_extra_exec_compatible_with, + extra_target_compatible_with = global_extra_target_compatible_with, + extra_target_settings = global_extra_target_settings, )) return None, versions, variants @@ -232,6 +304,8 @@ def _toolchain_extension(module_ctx): toolchain_zig_versions = [] toolchain_exec_lengths = [] toolchain_exec_constraints = [] + toolchain_target_compatible_lengths = [] + toolchain_target_compatible_constraints = [] toolchain_target_settings_lengths = [] toolchain_target_settings = [] for zig_version in versions: @@ -260,6 +334,8 @@ def _toolchain_extension(module_ctx): toolchain_zig_versions.append(zig_version) toolchain_exec_lengths.append(len(compatible_with)) toolchain_exec_constraints.extend(compatible_with) + toolchain_target_compatible_lengths.append(len(variant.extra_target_compatible_with)) + toolchain_target_compatible_constraints.extend(variant.extra_target_compatible_with) toolchain_target_settings_lengths.append(len(variant.extra_target_settings)) toolchain_target_settings.extend(variant.extra_target_settings) @@ -270,6 +346,8 @@ def _toolchain_extension(module_ctx): zig_versions = toolchain_zig_versions, exec_lengths = toolchain_exec_lengths, exec_constraints = toolchain_exec_constraints, + target_compatible_lengths = toolchain_target_compatible_lengths, + target_compatible_constraints = toolchain_target_compatible_constraints, target_settings_lengths = toolchain_target_settings_lengths, target_settings = toolchain_target_settings, ) diff --git a/zig/private/repo/toolchains_repo.bzl b/zig/private/repo/toolchains_repo.bzl index 673b67fb..c404950e 100644 --- a/zig/private/repo/toolchains_repo.bzl +++ b/zig/private/repo/toolchains_repo.bzl @@ -34,6 +34,8 @@ ATTRS = { "zig_versions": attr.string_list(doc = "The Zig SDK versions of the corresponding toolchain targets."), "exec_lengths": attr.int_list(doc = "The length of the slice of the `exec_constraints` attribute that corresponds to each toolchain target."), "exec_constraints": attr.string_list(doc = "All toolchain execution platform constraints concatenated to a single list."), + "target_compatible_lengths": attr.int_list(doc = "The length of the slice of the `target_compatible_constraints` attribute that corresponds to each toolchain target."), + "target_compatible_constraints": attr.string_list(doc = "All toolchain target platform constraints concatenated to a single list."), "target_settings_lengths": attr.int_list(doc = "The length of the slice of the `target_settings` attribute that corresponds to each toolchain target."), "target_settings": attr.string_list(doc = "All extra toolchain target settings concatenated to a single list."), } @@ -65,10 +67,10 @@ def _toolchains_repo_impl(repository_ctx): len_expected = len(repository_ctx.attr.names) len_equal = all([ len_expected == len(getattr(repository_ctx.attr, attr)) - for attr in ["labels", "zig_versions", "exec_lengths", "target_settings_lengths"] + for attr in ["labels", "zig_versions", "exec_lengths", "target_compatible_lengths", "target_settings_lengths"] ]) if not len_equal: - fail("Lengths of the attributes `names`, `labels`, `zig_versions`, `exec_lengths`, `target_settings_lengths` must match.") + fail("Lengths of the attributes `names`, `labels`, `zig_versions`, `exec_lengths`, `target_compatible_lengths`, `target_settings_lengths` must match.") len_exec_constraints = 0 for exec_len in repository_ctx.attr.exec_lengths: @@ -77,6 +79,13 @@ def _toolchains_repo_impl(repository_ctx): if not len_exec_constraints == len(repository_ctx.attr.exec_constraints): fail("Length of the `exec_constraints` attribute must match the sum of `exec_lengths`.") + len_target_compatible_constraints = 0 + for target_compatible_len in repository_ctx.attr.target_compatible_lengths: + len_target_compatible_constraints += target_compatible_len + + if not len_target_compatible_constraints == len(repository_ctx.attr.target_compatible_constraints): + fail("Length of the `target_compatible_constraints` attribute must match the sum of `target_compatible_lengths`.") + len_target_settings = 0 for target_settings_len in repository_ctx.attr.target_settings_lengths: len_target_settings += target_settings_len @@ -169,13 +178,17 @@ selects.config_setting_group( repository_ctx.attr.labels, repository_ctx.attr.zig_versions, repository_ctx.attr.exec_lengths, + repository_ctx.attr.target_compatible_lengths, repository_ctx.attr.target_settings_lengths, ) exec_offset = 0 + target_compatible_offset = 0 target_settings_offset = 0 - for counter, (name, label, zig_version, exec_len, target_settings_len) in enumerate(zipped): + for counter, (name, label, zig_version, exec_len, target_compatible_len, target_settings_len) in enumerate(zipped): compatible_with = repository_ctx.attr.exec_constraints[exec_offset:exec_offset + exec_len] exec_offset += exec_len + target_compatible_with = repository_ctx.attr.target_compatible_constraints[target_compatible_offset:target_compatible_offset + target_compatible_len] + target_compatible_offset += target_compatible_len target_settings = [":{}".format(zig_version)] + repository_ctx.attr.target_settings[target_settings_offset:target_settings_offset + target_settings_len] target_settings_offset += target_settings_len build_content += """ @@ -184,6 +197,7 @@ selects.config_setting_group( toolchain( name = "{prefix}_{name}_toolchain", exec_compatible_with = {compatible_with}, + target_compatible_with = {target_compatible_with}, target_settings = {target_settings}, toolchain = "{label}", toolchain_type = "@rules_zig//zig:toolchain_type", @@ -192,6 +206,7 @@ toolchain( prefix = _counter_prefix(counter, width = counter_digits), name = name, compatible_with = compatible_with, + target_compatible_with = target_compatible_with, target_settings = target_settings, label = label, ) diff --git a/zig/tests/bzlmod_zig_test.bzl b/zig/tests/bzlmod_zig_test.bzl index 11a50d7f..e1f0778a 100644 --- a/zig/tests/bzlmod_zig_test.bzl +++ b/zig/tests/bzlmod_zig_test.bzl @@ -277,20 +277,32 @@ _merge_version_specs_test = unittest.make( _merge_version_specs_test_impl, ) -def _toolchain_tag(*, zig_version, default = False, name = "", extra_exec_compatible_with = [], extra_target_settings = []): +def _toolchain_tag(*, zig_version, default = False, name = "", extra_exec_compatible_with = [], extra_target_compatible_with = [], extra_target_settings = []): return struct( name = name, zig_version = zig_version, default = default, extra_exec_compatible_with = extra_exec_compatible_with, + extra_target_compatible_with = extra_target_compatible_with, extra_target_settings = extra_target_settings, ) -def _toolchain_variant(*, zig_version, name = "", extra_exec_compatible_with = [], extra_target_settings = []): +def _extra_compatible_with(*, constraints = []): + return struct( + constraints = constraints, + ) + +def _extra_target_settings(*, settings = []): + return struct( + settings = settings, + ) + +def _toolchain_variant(*, zig_version, name = "", extra_exec_compatible_with = [], extra_target_compatible_with = [], extra_target_settings = []): return struct( name = name, zig_version = zig_version, extra_exec_compatible_with = extra_exec_compatible_with, + extra_target_compatible_with = extra_target_compatible_with, extra_target_settings = extra_target_settings, ) @@ -427,6 +439,7 @@ def _zig_versions_test_impl(ctx): name = "local", zig_version = "0.1.0", extra_exec_compatible_with = ["//constraints:local"], + extra_target_compatible_with = ["//constraints:target"], extra_target_settings = ["//settings:enabled"], ), ], @@ -440,6 +453,7 @@ def _zig_versions_test_impl(ctx): name = "local", zig_version = "0.1.0", extra_exec_compatible_with = ["//constraints:local"], + extra_target_compatible_with = ["//constraints:target"], extra_target_settings = ["//settings:enabled"], ), ], @@ -451,6 +465,135 @@ def _zig_versions_test_impl(ctx): "should preserve wrapper metadata", ) + asserts.equals( + env, + [ + _toolchain_variant( + zig_version = "0.1.0", + extra_exec_compatible_with = ["//constraints:global"], + extra_target_compatible_with = ["//constraints:target"], + extra_target_settings = ["//settings:global"], + ), + ], + handle_toolchain_tags( + [ + struct( + is_root = False, + tags = struct( + toolchain = [ + _toolchain_tag(zig_version = "0.1.0"), + ], + ), + ), + struct( + is_root = True, + tags = struct( + toolchain = [], + extra_exec_compatible_with = [ + _extra_compatible_with(constraints = ["//constraints:global"]), + ], + extra_target_compatible_with = [ + _extra_compatible_with(constraints = ["//constraints:target"]), + ], + extra_target_settings = [ + _extra_target_settings(settings = ["//settings:global"]), + ], + ), + ), + ], + known_versions = ["0.1.0"], + )[2], + "root extras should apply to transitive toolchain tags", + ) + + asserts.equals( + env, + [ + _toolchain_variant( + name = "local", + zig_version = "0.1.0", + extra_exec_compatible_with = [ + "//constraints:global", + "//constraints:local", + ], + extra_target_compatible_with = [ + "//constraints:target-global", + "//constraints:target-local", + ], + extra_target_settings = [ + "//settings:global", + "//settings:local", + ], + ), + ], + handle_toolchain_tags( + [ + struct( + is_root = True, + tags = struct( + toolchain = [ + _toolchain_tag( + name = "local", + zig_version = "0.1.0", + extra_exec_compatible_with = ["//constraints:local"], + extra_target_compatible_with = ["//constraints:target-local"], + extra_target_settings = ["//settings:local"], + ), + ], + extra_exec_compatible_with = [ + _extra_compatible_with(constraints = ["//constraints:global"]), + ], + extra_target_compatible_with = [ + _extra_compatible_with(constraints = ["//constraints:target-global"]), + ], + extra_target_settings = [ + _extra_target_settings(settings = ["//settings:global"]), + ], + ), + ), + ], + known_versions = ["0.1.0"], + )[2], + "per-toolchain metadata should be appended after root extras", + ) + + asserts.equals( + env, + [ + _toolchain_variant( + zig_version = "0.1.0", + extra_exec_compatible_with = [ + "//constraints:first", + "//constraints:second", + ], + extra_target_settings = [ + "//settings:first", + "//settings:second", + ], + ), + ], + handle_toolchain_tags( + [ + struct( + is_root = True, + tags = struct( + toolchain = [], + extra_exec_compatible_with = [ + _extra_compatible_with(constraints = ["//constraints:first"]), + _extra_compatible_with(constraints = ["//constraints:second"]), + ], + extra_target_settings = [ + _extra_target_settings(settings = ["//settings:first"]), + _extra_target_settings(settings = ["//settings:second"]), + ], + ), + ), + ], + known_versions = ["0.1.0"], + )[2], + "root extras should be additive", + ) + _assert_toolchain_versions( env, ["0.1.0", "0.4.0", "0.2.0", "0.0.1"], @@ -582,6 +725,72 @@ def _zig_versions_test_impl(ctx): "conflicting duplicate wrapper metadata should fail", ) + asserts.equals( + env, + (["Only the root module may specify extra Zig SDK execution constraints.", _extra_compatible_with( + constraints = ["//constraints:global"], + )], None, None), + handle_toolchain_tags( + [ + struct( + is_root = False, + tags = struct( + toolchain = [], + extra_exec_compatible_with = [ + _extra_compatible_with(constraints = ["//constraints:global"]), + ], + ), + ), + ], + known_versions = ["0.1.0"], + ), + "only root may set extra execution constraints", + ) + + asserts.equals( + env, + (["Only the root module may specify extra Zig SDK target constraints.", _extra_compatible_with( + constraints = ["//constraints:target"], + )], None, None), + handle_toolchain_tags( + [ + struct( + is_root = False, + tags = struct( + toolchain = [], + extra_target_compatible_with = [ + _extra_compatible_with(constraints = ["//constraints:target"]), + ], + ), + ), + ], + known_versions = ["0.1.0"], + ), + "only root may set extra target constraints", + ) + + asserts.equals( + env, + (["Only the root module may specify extra Zig SDK target settings.", _extra_target_settings( + settings = ["//settings:global"], + )], None, None), + handle_toolchain_tags( + [ + struct( + is_root = False, + tags = struct( + toolchain = [], + extra_target_settings = [ + _extra_target_settings(settings = ["//settings:global"]), + ], + ), + ), + ], + known_versions = ["0.1.0"], + ), + "only root may set extra target settings", + ) + return unittest.end(env) _zig_versions_test = unittest.make( From 4c02679b56003ec47a7ef9f5ffb732a900236b54 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 19 May 2026 18:17:24 +0200 Subject: [PATCH 3/4] test: cover global Zig metadata fallback --- zig/tests/bzlmod_zig_test.bzl | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/zig/tests/bzlmod_zig_test.bzl b/zig/tests/bzlmod_zig_test.bzl index e1f0778a..0cc7c123 100644 --- a/zig/tests/bzlmod_zig_test.bzl +++ b/zig/tests/bzlmod_zig_test.bzl @@ -594,6 +594,39 @@ def _zig_versions_test_impl(ctx): "root extras should be additive", ) + asserts.equals( + env, + [ + _toolchain_variant( + zig_version = "0.1.0", + extra_exec_compatible_with = ["//constraints:global"], + extra_target_compatible_with = ["//constraints:target-global"], + extra_target_settings = ["//settings:global"], + ), + ], + handle_toolchain_tags( + [ + struct( + is_root = True, + tags = struct( + toolchain = [], + extra_exec_compatible_with = [ + _extra_compatible_with(constraints = ["//constraints:global"]), + ], + extra_target_compatible_with = [ + _extra_compatible_with(constraints = ["//constraints:target-global"]), + ], + extra_target_settings = [ + _extra_target_settings(settings = ["//settings:global"]), + ], + ), + ), + ], + known_versions = ["0.1.0"], + )[2], + "fallback wrapper should inherit root extras", + ) + _assert_toolchain_versions( env, ["0.1.0", "0.4.0", "0.2.0", "0.0.1"], From 8bc8e254128029912769f5a941441459229dd4e2 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 19 May 2026 18:49:35 +0200 Subject: [PATCH 4/4] refactor: rely on declared Zig extension tags --- zig/private/bzlmod/zig.bzl | 6 ++--- zig/tests/bzlmod_zig_test.bzl | 43 +++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/zig/private/bzlmod/zig.bzl b/zig/private/bzlmod/zig.bzl index 2918da09..3aa4853c 100644 --- a/zig/private/bzlmod/zig.bzl +++ b/zig/private/bzlmod/zig.bzl @@ -144,19 +144,19 @@ def handle_toolchain_tags(modules, *, known_versions): global_extra_target_settings = [] for mod in modules: - for extra in getattr(mod.tags, "extra_exec_compatible_with", []): + for extra in mod.tags.extra_exec_compatible_with: if not mod.is_root: return (["Only the root module may specify extra Zig SDK execution constraints.", extra], None, None) global_extra_exec_compatible_with.extend([str(label) for label in extra.constraints]) - for extra in getattr(mod.tags, "extra_target_compatible_with", []): + for extra in mod.tags.extra_target_compatible_with: if not mod.is_root: return (["Only the root module may specify extra Zig SDK target constraints.", extra], None, None) global_extra_target_compatible_with.extend([str(label) for label in extra.constraints]) - for extra in getattr(mod.tags, "extra_target_settings", []): + for extra in mod.tags.extra_target_settings: if not mod.is_root: return (["Only the root module may specify extra Zig SDK target settings.", extra], None, None) diff --git a/zig/tests/bzlmod_zig_test.bzl b/zig/tests/bzlmod_zig_test.bzl index 0cc7c123..780ab3be 100644 --- a/zig/tests/bzlmod_zig_test.bzl +++ b/zig/tests/bzlmod_zig_test.bzl @@ -306,8 +306,23 @@ def _toolchain_variant(*, zig_version, name = "", extra_exec_compatible_with = [ extra_target_settings = extra_target_settings, ) +def _handle_toolchain_tags(modules, *, known_versions): + modules = [ + struct( + is_root = mod.is_root, + tags = struct( + toolchain = mod.tags.toolchain, + extra_exec_compatible_with = getattr(mod.tags, "extra_exec_compatible_with", []), + extra_target_compatible_with = getattr(mod.tags, "extra_target_compatible_with", []), + extra_target_settings = getattr(mod.tags, "extra_target_settings", []), + ), + ) + for mod in modules + ] + return handle_toolchain_tags(modules, known_versions = known_versions) + def _assert_toolchain_versions(env, expected, modules, *, known_versions, msg): - result = handle_toolchain_tags(modules, known_versions = known_versions) + result = _handle_toolchain_tags(modules, known_versions = known_versions) asserts.equals(env, None, result[0], msg) asserts.equals(env, expected, result[1], msg) @@ -325,7 +340,7 @@ def _zig_versions_test_impl(ctx): asserts.equals( env, [_toolchain_variant(zig_version = "0.1.0")], - handle_toolchain_tags([], known_versions = ["0.1.0"])[2], + _handle_toolchain_tags([], known_versions = ["0.1.0"])[2], "fallback should create one default wrapper", ) @@ -406,7 +421,7 @@ def _zig_versions_test_impl(ctx): _toolchain_variant(zig_version = "0.0.1"), _toolchain_variant(zig_version = "0.1.0"), ], - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = False, @@ -443,7 +458,7 @@ def _zig_versions_test_impl(ctx): extra_target_settings = ["//settings:enabled"], ), ], - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = True, @@ -475,7 +490,7 @@ def _zig_versions_test_impl(ctx): extra_target_settings = ["//settings:global"], ), ], - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = False, @@ -526,7 +541,7 @@ def _zig_versions_test_impl(ctx): ], ), ], - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = True, @@ -572,7 +587,7 @@ def _zig_versions_test_impl(ctx): ], ), ], - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = True, @@ -604,7 +619,7 @@ def _zig_versions_test_impl(ctx): extra_target_settings = ["//settings:global"], ), ], - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = True, @@ -687,7 +702,7 @@ def _zig_versions_test_impl(ctx): default = True, zig_version = "0.1.0", )], None, None), - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = False, @@ -709,7 +724,7 @@ def _zig_versions_test_impl(ctx): default = True, zig_version = "0.2.0", )], None, None), - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = True, @@ -733,7 +748,7 @@ def _zig_versions_test_impl(ctx): zig_version = "0.1.0", extra_target_settings = ["//settings:disabled"], )], None, None), - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = True, @@ -763,7 +778,7 @@ def _zig_versions_test_impl(ctx): (["Only the root module may specify extra Zig SDK execution constraints.", _extra_compatible_with( constraints = ["//constraints:global"], )], None, None), - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = False, @@ -785,7 +800,7 @@ def _zig_versions_test_impl(ctx): (["Only the root module may specify extra Zig SDK target constraints.", _extra_compatible_with( constraints = ["//constraints:target"], )], None, None), - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = False, @@ -807,7 +822,7 @@ def _zig_versions_test_impl(ctx): (["Only the root module may specify extra Zig SDK target settings.", _extra_target_settings( settings = ["//settings:global"], )], None, None), - handle_toolchain_tags( + _handle_toolchain_tags( [ struct( is_root = False,