From d4e121fafe6b212a8c12fcbbaf533260825ef8b3 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Wed, 10 Jun 2026 17:56:41 +0200 Subject: [PATCH 01/77] enumerate registered Zig SDKs --- zig/private/bzlmod/zig.bzl | 3 +++ zig/private/repo/toolchains_repo.bzl | 29 ++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/zig/private/bzlmod/zig.bzl b/zig/private/bzlmod/zig.bzl index 3aa4853c..dbaaf792 100644 --- a/zig/private/bzlmod/zig.bzl +++ b/zig/private/bzlmod/zig.bzl @@ -302,6 +302,7 @@ def _toolchain_extension(module_ctx): toolchain_names = [] toolchain_labels = [] toolchain_zig_versions = [] + toolchain_exec_platforms = [] toolchain_exec_lengths = [] toolchain_exec_constraints = [] toolchain_target_compatible_lengths = [] @@ -332,6 +333,7 @@ def _toolchain_extension(module_ctx): toolchain_names.append(name) toolchain_labels.append("@{}//:zig_toolchain".format(repo_name)) toolchain_zig_versions.append(zig_version) + toolchain_exec_platforms.append(platform) toolchain_exec_lengths.append(len(compatible_with)) toolchain_exec_constraints.extend(compatible_with) toolchain_target_compatible_lengths.append(len(variant.extra_target_compatible_with)) @@ -344,6 +346,7 @@ def _toolchain_extension(module_ctx): names = toolchain_names, labels = toolchain_labels, zig_versions = toolchain_zig_versions, + exec_platforms = toolchain_exec_platforms, exec_lengths = toolchain_exec_lengths, exec_constraints = toolchain_exec_constraints, target_compatible_lengths = toolchain_target_compatible_lengths, diff --git a/zig/private/repo/toolchains_repo.bzl b/zig/private/repo/toolchains_repo.bzl index c404950e..5399d1a0 100644 --- a/zig/private/repo/toolchains_repo.bzl +++ b/zig/private/repo/toolchains_repo.bzl @@ -32,6 +32,7 @@ ATTRS = { "names": attr.string_list(doc = "The name suffixes to assign to the generated toolchain targets. Will be pre-fixed with a counter for ordering."), "labels": attr.string_list(doc = "The labels to the toolchain implementation targets."), "zig_versions": attr.string_list(doc = "The Zig SDK versions of the corresponding toolchain targets."), + "exec_platforms": attr.string_list(doc = "The execution platform of each toolchain target, e.g. `x86_64-linux`."), "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."), @@ -67,7 +68,7 @@ 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_compatible_lengths", "target_settings_lengths"] + for attr in ["labels", "zig_versions", "exec_platforms", "exec_lengths", "target_compatible_lengths", "target_settings_lengths"] ]) if not len_equal: fail("Lengths of the attributes `names`, `labels`, `zig_versions`, `exec_lengths`, `target_compatible_lengths`, `target_settings_lengths` must match.") @@ -105,6 +106,14 @@ def _toolchains_repo_impl(repository_ctx): load("@bazel_skylib//lib:selects.bzl", "selects") load("@bazel_skylib//rules:common_settings.bzl", "string_flag") +""" + + toolchains_content = """\ +# Generated by toolchains_repo.bzl +# +# This file is not part of the public API, please do not rely on its contents. + +ZIG_TOOLCHAINS = [ """ unique_zig_versions = { @@ -177,6 +186,7 @@ selects.config_setting_group( repository_ctx.attr.names, repository_ctx.attr.labels, repository_ctx.attr.zig_versions, + repository_ctx.attr.exec_platforms, repository_ctx.attr.exec_lengths, repository_ctx.attr.target_compatible_lengths, repository_ctx.attr.target_settings_lengths, @@ -184,7 +194,7 @@ selects.config_setting_group( exec_offset = 0 target_compatible_offset = 0 target_settings_offset = 0 - for counter, (name, label, zig_version, exec_len, target_compatible_len, target_settings_len) in enumerate(zipped): + for counter, (name, label, zig_version, exec_platform, 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] @@ -211,9 +221,24 @@ toolchain( label = label, ) + zig_exe = "zig.exe" if "windows" in exec_platform else "zig" + toolchains_content += """\ + struct(version = "{version}", exec_platform = "{exec_platform}", zig = Label("{zig}")), +""".format( + version = zig_version, + exec_platform = exec_platform, + zig = label.replace("//:zig_toolchain", "//:" + zig_exe), + ) + # Base BUILD file for this repository repository_ctx.file("BUILD.bazel", build_content) + repository_ctx.file("private/BUILD.bazel", "") + toolchains_content += """\ +] +""" + repository_ctx.file("private/toolchains.bzl", toolchains_content) + toolchains_repo = repository_rule( _toolchains_repo_impl, doc = DOC, From cfd889d28889e6e7bd821c5fd38c093e9144d84a Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Wed, 10 Jun 2026 18:28:15 +0200 Subject: [PATCH 02/77] Expose the resolved host toolchain --- MODULE.bazel | 4 ++ zig/private/repo/BUILD.bazel | 1 + zig/private/repo/zig_host_toolchain.bzl | 59 +++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 zig/private/repo/zig_host_toolchain.bzl diff --git a/MODULE.bazel b/MODULE.bazel index ae9e5680..938de727 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -35,6 +35,10 @@ register_toolchains("@rules_zig//zig/target:all") register_toolchains("@zig_toolchains//:all") +zig_host_toolchain = use_repo_rule("//zig/private/repo:zig_host_toolchain.bzl", "zig_host_toolchain") + +zig_host_toolchain(name = "rules_zig_host_toolchain") + zls = use_extension("//zig/zls:extensions.bzl", "zls") zls.index(file = "//zig/zls/private:versions.json") use_repo(zls, "zls_toolchains") diff --git a/zig/private/repo/BUILD.bazel b/zig/private/repo/BUILD.bazel index 59521193..150d3ed8 100644 --- a/zig/private/repo/BUILD.bazel +++ b/zig/private/repo/BUILD.bazel @@ -26,6 +26,7 @@ filegroup( srcs = [ ":BUILD.bazel", ":toolchains_repo.bzl", + ":zig_host_toolchain.bzl", ":zig_repository.bzl", ], visibility = ["//zig/private:__pkg__"], diff --git a/zig/private/repo/zig_host_toolchain.bzl b/zig/private/repo/zig_host_toolchain.bzl new file mode 100644 index 00000000..ed4b885d --- /dev/null +++ b/zig/private/repo/zig_host_toolchain.bzl @@ -0,0 +1,59 @@ +"""Implementation of the `zig_host_toolchain` repository rule.""" + +load("@zig_toolchains//private:toolchains.bzl", "ZIG_TOOLCHAINS") + +DOC = """\ +Expose the latest registered Zig SDK for the host platform. + +Generates a `toolchain.bzl` file with a `zig_path(ctx)` function that resolves +the host platform's Zig binary from a repository_ctx or module_ctx. +""" + +def _detect_host_platform(repository_ctx): + os_name = repository_ctx.os.name + arch = repository_ctx.os.arch + + if "linux" in os_name: + os = "linux" + elif "mac" in os_name or "darwin" in os_name: + os = "macos" + elif "windows" in os_name: + os = "windows" + else: + fail("Unsupported host operating system: {}".format(os_name)) + + if arch in ["amd64", "x86_64", "x64"]: + cpu = "x86_64" + elif arch in ["aarch64", "arm64"]: + cpu = "aarch64" + else: + fail("Unsupported host CPU architecture: {}".format(arch)) + + return "{}-{}".format(cpu, os) + +def _zig_host_toolchain_impl(repository_ctx): + platform = _detect_host_platform(repository_ctx) + + zig = None + for toolchain in ZIG_TOOLCHAINS: + if toolchain.exec_platform == platform: + zig = toolchain.zig + break + + if zig == None: + fail("No registered Zig SDK supports the host platform '{}'.".format(platform)) + + repository_ctx.file("BUILD.bazel", "") + repository_ctx.file("toolchain.bzl", """\ +# Generated by zig_host_toolchain.bzl + +_ZIG = Label({}) + +def zig_path(ctx): + return ctx.path(_ZIG) +""".format(repr(str(zig)))) + +zig_host_toolchain = repository_rule( + _zig_host_toolchain_impl, + doc = DOC, +) From 746ca89a6fb46d4e6282efa9aa70f1a10290ff4c Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 11:35:57 +0200 Subject: [PATCH 03/77] configurable Zig host version via RULES_ZIG_HOST_SDK --- zig/private/repo/zig_host_toolchain.bzl | 45 +++++++++++++++++++------ 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/zig/private/repo/zig_host_toolchain.bzl b/zig/private/repo/zig_host_toolchain.bzl index ed4b885d..61ccd76b 100644 --- a/zig/private/repo/zig_host_toolchain.bzl +++ b/zig/private/repo/zig_host_toolchain.bzl @@ -2,12 +2,18 @@ load("@zig_toolchains//private:toolchains.bzl", "ZIG_TOOLCHAINS") +_VERSION_ENV = "RULES_ZIG_HOST_SDK" + DOC = """\ -Expose the latest registered Zig SDK for the host platform. +Expose a registered Zig SDK for the host platform. + +By default selects the first registered Zig SDK version, i.e. the default +toolchain version, or the latest if no default is set. Set the +`{env}` environment variable to a registered version to override. Generates a `toolchain.bzl` file with a `zig_path(ctx)` function that resolves the host platform's Zig binary from a repository_ctx or module_ctx. -""" +""".format(env = _VERSION_ENV) def _detect_host_platform(repository_ctx): os_name = repository_ctx.os.name @@ -31,17 +37,33 @@ def _detect_host_platform(repository_ctx): return "{}-{}".format(cpu, os) -def _zig_host_toolchain_impl(repository_ctx): - platform = _detect_host_platform(repository_ctx) +def _select_zig(repository_ctx, platform): + candidates = [ + toolchain + for toolchain in ZIG_TOOLCHAINS + if toolchain.exec_platform == platform + ] + if not candidates: + fail("No registered Zig SDK supports the host platform '{}'.".format(platform)) - zig = None - for toolchain in ZIG_TOOLCHAINS: - if toolchain.exec_platform == platform: - zig = toolchain.zig - break + requested = repository_ctx.os.environ.get(_VERSION_ENV) + if not requested: + return candidates[0].zig - if zig == None: - fail("No registered Zig SDK supports the host platform '{}'.".format(platform)) + for toolchain in candidates: + if toolchain.version == requested: + return toolchain.zig + + fail("{} requested Zig SDK version '{}', which is not registered for host platform '{}'. Registered versions: {}.".format( + _VERSION_ENV, + requested, + platform, + [toolchain.version for toolchain in candidates], + )) + +def _zig_host_toolchain_impl(repository_ctx): + platform = _detect_host_platform(repository_ctx) + zig = _select_zig(repository_ctx, platform) repository_ctx.file("BUILD.bazel", "") repository_ctx.file("toolchain.bzl", """\ @@ -56,4 +78,5 @@ def zig_path(ctx): zig_host_toolchain = repository_rule( _zig_host_toolchain_impl, doc = DOC, + environ = [_VERSION_ENV], ) From 589f15a22de6aa6bb75ef86ea6271dbcccd0e6f8 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 21:05:34 +0200 Subject: [PATCH 04/77] generate bzl_library targets for zig(_host)_toolchains repos required by the Gazelle generated bzl_library targets in rules_zig --- zig/private/repo/toolchains_repo.bzl | 10 +++++++++- zig/private/repo/zig_host_toolchain.bzl | 12 +++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/zig/private/repo/toolchains_repo.bzl b/zig/private/repo/toolchains_repo.bzl index 5399d1a0..fc0d340e 100644 --- a/zig/private/repo/toolchains_repo.bzl +++ b/zig/private/repo/toolchains_repo.bzl @@ -233,7 +233,15 @@ toolchain( # Base BUILD file for this repository repository_ctx.file("BUILD.bazel", build_content) - repository_ctx.file("private/BUILD.bazel", "") + repository_ctx.file("private/BUILD.bazel", """\ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "toolchains", + srcs = ["toolchains.bzl"], + visibility = ["//visibility:public"], +) +""") toolchains_content += """\ ] """ diff --git a/zig/private/repo/zig_host_toolchain.bzl b/zig/private/repo/zig_host_toolchain.bzl index 61ccd76b..1a641797 100644 --- a/zig/private/repo/zig_host_toolchain.bzl +++ b/zig/private/repo/zig_host_toolchain.bzl @@ -65,7 +65,17 @@ def _zig_host_toolchain_impl(repository_ctx): platform = _detect_host_platform(repository_ctx) zig = _select_zig(repository_ctx, platform) - repository_ctx.file("BUILD.bazel", "") + repository_ctx.file("BUILD.bazel", """\ +# Generated by zig_host_toolchain.bzl + +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "toolchain", + srcs = ["toolchain.bzl"], + visibility = ["//visibility:public"], +) +""") repository_ctx.file("toolchain.bzl", """\ # Generated by zig_host_toolchain.bzl From 64391ec470d12791944fe5f0b59f2199dedb6f5b Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 9 Jun 2026 14:01:18 +0200 Subject: [PATCH 05/77] demo zig package dependency graph --- e2e/workspace/zig-package-import/.gitignore | 3 +++ .../zig-package-import/app/build.zig | 27 +++++++++++++++++++ .../zig-package-import/app/build.zig.zon | 20 ++++++++++++++ .../zig-package-import/app/src/main.zig | 22 +++++++++++++++ .../zig-package-import/greet/build.zig | 18 +++++++++++++ .../zig-package-import/greet/build.zig.zon | 17 ++++++++++++ .../zig-package-import/greet/src/greet.zig | 14 ++++++++++ 7 files changed, 121 insertions(+) create mode 100644 e2e/workspace/zig-package-import/.gitignore create mode 100644 e2e/workspace/zig-package-import/app/build.zig create mode 100644 e2e/workspace/zig-package-import/app/build.zig.zon create mode 100644 e2e/workspace/zig-package-import/app/src/main.zig create mode 100644 e2e/workspace/zig-package-import/greet/build.zig create mode 100644 e2e/workspace/zig-package-import/greet/build.zig.zon create mode 100644 e2e/workspace/zig-package-import/greet/src/greet.zig diff --git a/e2e/workspace/zig-package-import/.gitignore b/e2e/workspace/zig-package-import/.gitignore new file mode 100644 index 00000000..9411c9d1 --- /dev/null +++ b/e2e/workspace/zig-package-import/.gitignore @@ -0,0 +1,3 @@ +zig-pkg/ +zig-out/ +.zig-cache/ diff --git a/e2e/workspace/zig-package-import/app/build.zig b/e2e/workspace/zig-package-import/app/build.zig new file mode 100644 index 00000000..7ae012e3 --- /dev/null +++ b/e2e/workspace/zig-package-import/app/build.zig @@ -0,0 +1,27 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const clap = b.dependency("clap", .{ + .target = target, + .optimize = optimize, + }); + const greet = b.dependency("greet", .{ + .target = target, + .optimize = optimize, + }); + + const exe = b.addExecutable(.{ + .name = "app", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + exe.root_module.addImport("clap", clap.module("clap")); + exe.root_module.addImport("greet", greet.module("greet")); + b.installArtifact(exe); +} diff --git a/e2e/workspace/zig-package-import/app/build.zig.zon b/e2e/workspace/zig-package-import/app/build.zig.zon new file mode 100644 index 00000000..04034b9d --- /dev/null +++ b/e2e/workspace/zig-package-import/app/build.zig.zon @@ -0,0 +1,20 @@ +.{ + .name = .app, + .version = "0.1.0", + .fingerprint = 0xc96e70cfecd7faf1, + .minimum_zig_version = "0.16.0", + .dependencies = .{ + .clap = .{ + .url = "https://github.com/Hejsil/zig-clap/archive/8d97efa1ee1e575443c7888d5c38e1c3fc145cf5.tar.gz", + .hash = "clap-0.12.0-oBajB7foAQDqlSwaSG5g0yq7xGbQARUsBk5T64gAOqP5", + }, + .greet = .{ + .path = "../greet", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/e2e/workspace/zig-package-import/app/src/main.zig b/e2e/workspace/zig-package-import/app/src/main.zig new file mode 100644 index 00000000..432dd19e --- /dev/null +++ b/e2e/workspace/zig-package-import/app/src/main.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const clap = @import("clap"); +const greet = @import("greet"); + +pub fn main(init: std.process.Init) !void { + var diag = clap.Diagnostic{}; + var res = clap.parse(clap.Help, &greet.params, clap.parsers.default, init.minimal.args, .{ + .diagnostic = &diag, + .allocator = init.gpa, + }) catch |err| { + try diag.reportToFile(init.io, .stderr(), err); + return err; + }; + defer res.deinit(); + + if (res.args.help != 0) + return clap.helpToFile(init.io, .stderr(), clap.Help, &greet.params, .{}); + + const message = try greet.greeting(init.gpa, res); + defer init.gpa.free(message); + std.debug.print("{s}\n", .{message}); +} diff --git a/e2e/workspace/zig-package-import/greet/build.zig b/e2e/workspace/zig-package-import/greet/build.zig new file mode 100644 index 00000000..9349f46b --- /dev/null +++ b/e2e/workspace/zig-package-import/greet/build.zig @@ -0,0 +1,18 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const clap = b.dependency("clap", .{ + .target = target, + .optimize = optimize, + }); + + const mod = b.addModule("greet", .{ + .root_source_file = b.path("src/greet.zig"), + .target = target, + .optimize = optimize, + }); + mod.addImport("clap", clap.module("clap")); +} diff --git a/e2e/workspace/zig-package-import/greet/build.zig.zon b/e2e/workspace/zig-package-import/greet/build.zig.zon new file mode 100644 index 00000000..403fb19e --- /dev/null +++ b/e2e/workspace/zig-package-import/greet/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .greet, + .version = "0.1.0", + .fingerprint = 0x2df8175bd2f91638, + .minimum_zig_version = "0.16.0", + .dependencies = .{ + .clap = .{ + .url = "https://github.com/Hejsil/zig-clap/archive/8d97efa1ee1e575443c7888d5c38e1c3fc145cf5.tar.gz", + .hash = "clap-0.12.0-oBajB7foAQDqlSwaSG5g0yq7xGbQARUsBk5T64gAOqP5", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/e2e/workspace/zig-package-import/greet/src/greet.zig b/e2e/workspace/zig-package-import/greet/src/greet.zig new file mode 100644 index 00000000..d9a928f7 --- /dev/null +++ b/e2e/workspace/zig-package-import/greet/src/greet.zig @@ -0,0 +1,14 @@ +const std = @import("std"); +const clap = @import("clap"); + +// contrived example to introduce a transitive clap dependency +pub const params = clap.parseParamsComptime( + \\-h, --help Display this help and exit. + \\-n, --name Name to greet (defaults to "world"). + \\ +); + +pub fn greeting(allocator: std.mem.Allocator, res: anytype) ![]u8 { + const name = res.args.name orelse "world"; + return std.fmt.allocPrint(allocator, "Hello {s}!", .{name}); +} From fe5fddd02e8d6970b62835ee4bbd35a15eca37e4 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 10:20:28 +0200 Subject: [PATCH 06/77] start zig_packages module extension --- e2e/workspace/MODULE.bazel | 7 +++++ .../zig-package-import/app/BUILD.bazel | 0 zig/packages.bzl | 5 +++ zig/private/bzlmod/zig_packages.bzl | 31 +++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 e2e/workspace/zig-package-import/app/BUILD.bazel create mode 100644 zig/packages.bzl create mode 100644 zig/private/bzlmod/zig_packages.bzl diff --git a/e2e/workspace/MODULE.bazel b/e2e/workspace/MODULE.bazel index 3573cbc3..6423c3be 100644 --- a/e2e/workspace/MODULE.bazel +++ b/e2e/workspace/MODULE.bazel @@ -19,6 +19,13 @@ zig.toolchain(zig_version = "0.16.0") zig.toolchain(zig_version = "0.15.2") use_repo(zig, "zig_toolchains") +zig_packages = use_extension( + "@rules_zig//zig:packages.bzl", + "zig_packages", + dev_dependency = True, +) +zig_packages.from_file(build_zig_zon = "//zig-package-import/app:build.zig.zon") + http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( diff --git a/e2e/workspace/zig-package-import/app/BUILD.bazel b/e2e/workspace/zig-package-import/app/BUILD.bazel new file mode 100644 index 00000000..e69de29b diff --git a/zig/packages.bzl b/zig/packages.bzl new file mode 100644 index 00000000..b4053f4b --- /dev/null +++ b/zig/packages.bzl @@ -0,0 +1,5 @@ +"""Extension for importing Zig package dependencies.""" + +load("//zig/private/bzlmod:zig_packages.bzl", _zig_packages = "zig_packages") + +zig_packages = _zig_packages diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl new file mode 100644 index 00000000..4089ba01 --- /dev/null +++ b/zig/private/bzlmod/zig_packages.bzl @@ -0,0 +1,31 @@ +"""Implementation of the `zig_packages` module extension.""" + +load("@rules_zig_host_toolchain//:toolchain.bzl", "zig_path") + +from_file = tag_class( + attrs = { + "build_zig_zon": attr.label( + doc = "A `build.zig.zon` manifest to resolve Zig dependencies for.", + mandatory = True, + allow_single_file = True, + ), + }, +) + +def _zig_packages_impl(module_ctx): + zig = zig_path(module_ctx) + + for mod in module_ctx.modules: + for tag in mod.tags.from_file: + manifest = module_ctx.path(tag.build_zig_zon) + result = module_ctx.execute([zig, "version"]) + + # buildifier: disable=print + print("zig {} for manifest {}".format(result.stdout.strip(), manifest)) + +zig_packages = module_extension( + implementation = _zig_packages_impl, + tag_classes = { + "from_file": from_file, + }, +) From 814cb12a728e5e155ad553f191198ea7e1db1b6f Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 10:41:59 +0200 Subject: [PATCH 07/77] resolve Zig dependencies --- zig/private/bzlmod/zig_packages.bzl | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 4089ba01..1c1385ea 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -12,16 +12,39 @@ from_file = tag_class( }, ) +def _zig_build(module_ctx, zig, project_dir, cache_dir, args): + result = module_ctx.execute( + [zig, "build", "--cache-dir", str(cache_dir)] + args, + working_directory = str(project_dir), + ) + if result.return_code != 0: + fail("`zig build {}` failed in {}:\n{}".format(" ".join(args), project_dir, result.stderr)) + +def _read_dependencies(module_ctx, zig, manifest): + project_dir = manifest.dirname + cache_dir = module_ctx.path("cache") + + _zig_build(module_ctx, zig, project_dir, cache_dir, ["--fetch=all"]) + _zig_build(module_ctx, zig, project_dir, cache_dir, ["--list-steps"]) + + output_dir = cache_dir.get_child("o") + for entry in output_dir.readdir(): + dependencies = entry.get_child("dependencies.zig") + if dependencies.exists: + return module_ctx.read(dependencies) + + fail("Could not find the generated `@dependencies` module under {}.".format(output_dir)) + def _zig_packages_impl(module_ctx): zig = zig_path(module_ctx) for mod in module_ctx.modules: for tag in mod.tags.from_file: manifest = module_ctx.path(tag.build_zig_zon) - result = module_ctx.execute([zig, "version"]) + dependencies = _read_dependencies(module_ctx, zig, manifest) # buildifier: disable=print - print("zig {} for manifest {}".format(result.stdout.strip(), manifest)) + print(dependencies) zig_packages = module_extension( implementation = _zig_packages_impl, From 1fad8cf8ba44fbddd59d4402d70e8a2d02b4e502 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 11:00:23 +0200 Subject: [PATCH 08/77] fetch Zig nightly build --- e2e/workspace/MODULE.bazel | 7 +- e2e/workspace/extra-versions.json | 148 ++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 e2e/workspace/extra-versions.json diff --git a/e2e/workspace/MODULE.bazel b/e2e/workspace/MODULE.bazel index 6423c3be..bcbff52d 100644 --- a/e2e/workspace/MODULE.bazel +++ b/e2e/workspace/MODULE.bazel @@ -15,8 +15,13 @@ zig = use_extension( "zig", dev_dependency = True, ) -zig.toolchain(zig_version = "0.16.0") +zig.index(file = "extra-versions.json") +zig.toolchain( + default = True, + zig_version = "0.16.0", +) zig.toolchain(zig_version = "0.15.2") +zig.toolchain(zig_version = "0.17.0-dev.813+2153f8143") use_repo(zig, "zig_toolchains") zig_packages = use_extension( diff --git a/e2e/workspace/extra-versions.json b/e2e/workspace/extra-versions.json new file mode 100644 index 00000000..8929cf19 --- /dev/null +++ b/e2e/workspace/extra-versions.json @@ -0,0 +1,148 @@ +{ + "master": { + "version": "0.17.0-dev.813+2153f8143", + "date": "2026-06-07", + "docs": "https://ziglang.org/documentation/master/", + "stdDocs": "https://ziglang.org/documentation/master/std/", + "src": { + "tarball": "https://ziglang.org/builds/zig-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "bcb2ec46a2353620f2d90fcda1b046895ac7ba16b6b9febc9a0f9bd48f568c31", + "size": "22688996" + }, + "bootstrap": { + "tarball": "https://ziglang.org/builds/zig-bootstrap-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "d93ea88bcd934b24695de5e445f3640d8e04afc544f88dbed6fef1d91c4c5292", + "size": "56681092" + }, + "x86_64-macos": { + "tarball": "https://ziglang.org/builds/zig-x86_64-macos-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "3938c46ae4bca3c13f423b09503e3ef00bb4b7ef12b8bc1e5122ede366057a5b", + "size": "59323140" + }, + "aarch64-macos": { + "tarball": "https://ziglang.org/builds/zig-aarch64-macos-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "36673d2513afa4a96c86780648ba504beedd7f0451389091cf9d53e38d5b4840", + "size": "53999760" + }, + "x86_64-linux": { + "tarball": "https://ziglang.org/builds/zig-x86_64-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "b0d46ffc4587b9e8dd0b524ee5bc4da1e67f28bba55e7c534cec64af2f2d7a74", + "size": "57296196" + }, + "aarch64-linux": { + "tarball": "https://ziglang.org/builds/zig-aarch64-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "aa67b418d50bdde3043cfe765016d5387a2333b514ada2c57f24baae4005c331", + "size": "52949940" + }, + "arm-linux": { + "tarball": "https://ziglang.org/builds/zig-arm-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "7e365014a7520ca405b18d8690d802592199ce178d9a3bbfa526b1417edcfaa5", + "size": "53772136" + }, + "riscv64-linux": { + "tarball": "https://ziglang.org/builds/zig-riscv64-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "e2282b0784722eb3e41a41dcdea257618bc02c6b10fdd462c5f8e514b09628da", + "size": "57219904" + }, + "powerpc64le-linux": { + "tarball": "https://ziglang.org/builds/zig-powerpc64le-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "4b4ffe65aa052e1399319f4d80b5d117e6902d514d52d7bca5dfce66153474ec", + "size": "57144644" + }, + "x86-linux": { + "tarball": "https://ziglang.org/builds/zig-x86-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "cb14dab396b988221e02c4736680a0e166048d5592fafc4ba2fde362d3bc78b3", + "size": "59897932" + }, + "loongarch64-linux": { + "tarball": "https://ziglang.org/builds/zig-loongarch64-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "9a078bfac64a0d51752de87eb31348d33035451043f9a03546bf57d05c070a43", + "size": "54315620" + }, + "s390x-linux": { + "tarball": "https://ziglang.org/builds/zig-s390x-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "c631cf79d8a405edf9d941c621131ac262d4468566cdbf1442087f764df46eb7", + "size": "57127508" + }, + "x86_64-windows": { + "tarball": "https://ziglang.org/builds/zig-x86_64-windows-0.17.0-dev.813+2153f8143.zip", + "shasum": "2a8f1a34402076ab7931e4535bd379b20c83fc263d1387cb3f70cb2e397f9ebe", + "size": "101062660" + }, + "aarch64-windows": { + "tarball": "https://ziglang.org/builds/zig-aarch64-windows-0.17.0-dev.813+2153f8143.zip", + "shasum": "c335026c4b666a995ac2f4d5481f74f7f9a455dd7ab3620b3e04779d3f6055a8", + "size": "96847518" + }, + "x86-windows": { + "tarball": "https://ziglang.org/builds/zig-x86-windows-0.17.0-dev.813+2153f8143.zip", + "shasum": "f341544a7263651616c7ce292e23a257c85f892ac72c0eeea9a7f0c6e93351da", + "size": "102769164" + }, + "aarch64-freebsd": { + "tarball": "https://ziglang.org/builds/zig-aarch64-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "aa0da8ed92903cb7e32b18bccd4d6d5770192105749af6053ca5faafe6afdfbe", + "size": "52893704" + }, + "arm-freebsd": { + "tarball": "https://ziglang.org/builds/zig-arm-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "60b75fa5e895e9ef01b9a745495bd1ed48078a7a8bebd546278bda82be27dc1d", + "size": "54464888" + }, + "powerpc64le-freebsd": { + "tarball": "https://ziglang.org/builds/zig-powerpc64le-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "22c735fb40f8d0ad7342660a6a8a405615f27b66432dd5021efca1267b51191c", + "size": "57177396" + }, + "riscv64-freebsd": { + "tarball": "https://ziglang.org/builds/zig-riscv64-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "0876f2330308279466a6d86630d3510f0498d94e11238c5653bee7532e6f078d", + "size": "57367100" + }, + "x86_64-freebsd": { + "tarball": "https://ziglang.org/builds/zig-x86_64-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "a75220898fe9f403d24e9712ef56fa7cf622ddb96be21f2d2b7f01535a4f26a9", + "size": "57464308" + }, + "aarch64-netbsd": { + "tarball": "https://ziglang.org/builds/zig-aarch64-netbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "c004ad712df83a2eed7a0724fd582a08bb09eaee262996c4e6b82680a22d7b6f", + "size": "52847860" + }, + "arm-netbsd": { + "tarball": "https://ziglang.org/builds/zig-arm-netbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "611fdc70b82e6bf88053a72efba73a788962ef0bd9008336e0202bb43eb67967", + "size": "55526764" + }, + "x86-netbsd": { + "tarball": "https://ziglang.org/builds/zig-x86-netbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "1296c141a8603a0911547e60532c63638cf5dd859ab4acf2ead33a925d64776b", + "size": "60476932" + }, + "x86_64-netbsd": { + "tarball": "https://ziglang.org/builds/zig-x86_64-netbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "bd88fad25ef1251d74a8051c30b886256c8b0f0292b4209b1013e925b54cd314", + "size": "57360376" + }, + "aarch64-openbsd": { + "tarball": "https://ziglang.org/builds/zig-aarch64-openbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "7730b3b2bc98317c154fda750a74790a3e6e79526a868a9bc04d63a9bb71c79e", + "size": "53347332" + }, + "arm-openbsd": { + "tarball": "https://ziglang.org/builds/zig-arm-openbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "d067209109995e5311c2f196f75fec59124d55aa140b5727c9b2eb386f46cf6a", + "size": "53971816" + }, + "riscv64-openbsd": { + "tarball": "https://ziglang.org/builds/zig-riscv64-openbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "32a6df5e4e0a3647ac4354b0a7f103a6bab9d0c74225ec5c0e0d9aea0f4d5d74", + "size": "57655868" + }, + "x86_64-openbsd": { + "tarball": "https://ziglang.org/builds/zig-x86_64-openbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "fb173bd2caff90b7483a513f7759181de0b4f60d87698f89bc7caf8150a00e0a", + "size": "58620540" + } + } +} From 790221709e621c9ed1d18b75656e28767bcff542 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 12:01:03 +0200 Subject: [PATCH 09/77] pass --pkg-dir flag to avoid resolution polluting user tree --- e2e/workspace/.bazelrc | 4 ++++ zig/private/bzlmod/zig_packages.bzl | 9 +++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/e2e/workspace/.bazelrc b/e2e/workspace/.bazelrc index 02f4efd0..247576ea 100644 --- a/e2e/workspace/.bazelrc +++ b/e2e/workspace/.bazelrc @@ -5,3 +5,7 @@ try-import %workspace%/.bazelrc.user # docs: https://bazel.build/reference/command-line-reference#flag--workspace_status_command build --workspace_status_command=$(pwd)/workspace_status.sh + +# Use Zig HEAD, which includes https://codeberg.org/ziglang/zig/pulls/35428 and +# provides `--pkg-dir` so resolution does not write into the source tree. +common --repo_env=RULES_ZIG_HOST_SDK=0.17.0-dev.813+2153f8143 diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 1c1385ea..98100f8f 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -12,9 +12,9 @@ from_file = tag_class( }, ) -def _zig_build(module_ctx, zig, project_dir, cache_dir, args): +def _zig_build(module_ctx, zig, project_dir, cache_dir, pkg_dir, args): result = module_ctx.execute( - [zig, "build", "--cache-dir", str(cache_dir)] + args, + [zig, "build", "--cache-dir", str(cache_dir), "--pkg-dir", str(pkg_dir)] + args, working_directory = str(project_dir), ) if result.return_code != 0: @@ -23,9 +23,10 @@ def _zig_build(module_ctx, zig, project_dir, cache_dir, args): def _read_dependencies(module_ctx, zig, manifest): project_dir = manifest.dirname cache_dir = module_ctx.path("cache") + pkg_dir = module_ctx.path("pkg") - _zig_build(module_ctx, zig, project_dir, cache_dir, ["--fetch=all"]) - _zig_build(module_ctx, zig, project_dir, cache_dir, ["--list-steps"]) + _zig_build(module_ctx, zig, project_dir, cache_dir, pkg_dir, ["--fetch=all"]) + _zig_build(module_ctx, zig, project_dir, cache_dir, pkg_dir, ["--list-steps"]) output_dir = cache_dir.get_child("o") for entry in output_dir.readdir(): From 9293fd1db7c172c74b747f17eaeacb777c63455c Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 15:14:21 +0200 Subject: [PATCH 10/77] parse build.zig.zon to json --- zig/private/zon2json.zig | 65 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 zig/private/zon2json.zig diff --git a/zig/private/zon2json.zig b/zig/private/zon2json.zig new file mode 100644 index 00000000..49e73239 --- /dev/null +++ b/zig/private/zon2json.zig @@ -0,0 +1,65 @@ +//! Parse a ZON file and emit its contents as JSON on stdout. + +const std = @import("std"); +const Zoir = std.zig.Zoir; + +pub fn main(init: std.process.Init) !void { + const allocator = init.arena.allocator(); + const io = init.io; + + const args = try init.minimal.args.toSlice(allocator); + if (args.len != 2) fatal("usage: zon2json ", .{}); + + const source = try std.Io.Dir.cwd().readFileAllocOptions(io, args[1], allocator, .unlimited, .of(u8), 0); + + const ast = try std.zig.Ast.parse(allocator, source, .zon); + const zoir = try std.zig.ZonGen.generate(allocator, ast, .{}); + if (zoir.compile_errors.len != 0) fatal("'{s}' is not valid ZON", .{args[1]}); + + var stdout_buffer: [4096]u8 = undefined; + var stdout = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); + const writer = &stdout.interface; + + var json: std.json.Stringify = .{ .writer = writer }; + try writeNode(&json, zoir, .root); + try writer.writeByte('\n'); + try writer.flush(); +} + +fn writeNode(json: *std.json.Stringify, zoir: Zoir, index: Zoir.Node.Index) !void { + switch (index.get(zoir)) { + .true => try json.write(true), + .false => try json.write(false), + .null, .pos_inf, .neg_inf, .nan => try json.write(null), + .int_literal => |int| switch (int) { + .small => |small| try json.write(small), + .big => |big| try json.print("{f}", .{big}), + }, + .float_literal => |float| try json.write(float), + .char_literal => |char| try json.write(char), + .enum_literal => |name| try json.write(name.get(zoir)), + .string_literal => |string| try json.write(string), + .empty_literal => { + try json.beginObject(); + try json.endObject(); + }, + .array_literal => |elements| { + try json.beginArray(); + for (0..elements.len) |i| try writeNode(json, zoir, elements.at(@intCast(i))); + try json.endArray(); + }, + .struct_literal => |fields| { + try json.beginObject(); + for (fields.names, 0..) |name, i| { + try json.objectField(name.get(zoir)); + try writeNode(json, zoir, fields.vals.at(@intCast(i))); + } + try json.endObject(); + }, + } +} + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.debug.print("zon2json: " ++ format ++ "\n", args); + std.process.exit(1); +} From 4008d30544b8f430dd86cfe059c800195a15cb2b Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 15:29:02 +0200 Subject: [PATCH 11/77] parse build.zig.zon in module extension --- zig/private/bzlmod/zig_packages.bzl | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 98100f8f..28e03736 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -36,16 +36,32 @@ def _read_dependencies(module_ctx, zig, manifest): fail("Could not find the generated `@dependencies` module under {}.".format(output_dir)) +def _parse_manifest(module_ctx, zig, zon2json, manifest): + result = module_ctx.execute([ + zig, + "run", + "--cache-dir", + str(module_ctx.path("cache")), + zon2json, + "--", + str(manifest), + ]) + if result.return_code != 0: + fail("Failed to parse manifest {}:\n{}".format(manifest, result.stderr)) + return json.decode(result.stdout) + def _zig_packages_impl(module_ctx): zig = zig_path(module_ctx) + zon2json = module_ctx.path(Label("//zig/private:zon2json.zig")) for mod in module_ctx.modules: for tag in mod.tags.from_file: manifest = module_ctx.path(tag.build_zig_zon) - dependencies = _read_dependencies(module_ctx, zig, manifest) + _read_dependencies(module_ctx, zig, manifest) + manifest_json = _parse_manifest(module_ctx, zig, zon2json, manifest) # buildifier: disable=print - print(dependencies) + print(manifest_json) zig_packages = module_extension( implementation = _zig_packages_impl, From c2f1ceb569ed0d024f3d0b82e5ac135be4c839c1 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 17:19:09 +0200 Subject: [PATCH 12/77] resolve the Zig package dependency graph --- zig/private/bzlmod/zig_packages.bzl | 54 +++--- zig/private/zon2json.zig | 248 +++++++++++++++++++++++----- 2 files changed, 230 insertions(+), 72 deletions(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 28e03736..dbda8f96 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -12,56 +12,40 @@ from_file = tag_class( }, ) -def _zig_build(module_ctx, zig, project_dir, cache_dir, pkg_dir, args): +def _fetch(module_ctx, zig, manifest, cache_dir, pkg_dir): result = module_ctx.execute( - [zig, "build", "--cache-dir", str(cache_dir), "--pkg-dir", str(pkg_dir)] + args, - working_directory = str(project_dir), + [zig, "build", "--fetch=all", "--cache-dir", str(cache_dir), "--pkg-dir", str(pkg_dir)], + working_directory = str(manifest.dirname), ) if result.return_code != 0: - fail("`zig build {}` failed in {}:\n{}".format(" ".join(args), project_dir, result.stderr)) + fail("`zig build --fetch=all` failed in {}:\n{}".format(manifest.dirname, result.stderr)) -def _read_dependencies(module_ctx, zig, manifest): - project_dir = manifest.dirname - cache_dir = module_ctx.path("cache") - pkg_dir = module_ctx.path("pkg") - - _zig_build(module_ctx, zig, project_dir, cache_dir, pkg_dir, ["--fetch=all"]) - _zig_build(module_ctx, zig, project_dir, cache_dir, pkg_dir, ["--list-steps"]) - - output_dir = cache_dir.get_child("o") - for entry in output_dir.readdir(): - dependencies = entry.get_child("dependencies.zig") - if dependencies.exists: - return module_ctx.read(dependencies) - - fail("Could not find the generated `@dependencies` module under {}.".format(output_dir)) - -def _parse_manifest(module_ctx, zig, zon2json, manifest): - result = module_ctx.execute([ - zig, - "run", - "--cache-dir", - str(module_ctx.path("cache")), - zon2json, - "--", - str(manifest), - ]) +def _resolve_graph(module_ctx, zig, zon2json, cache_dir, pkg_dir, manifests): + result = module_ctx.execute( + [zig, "run", "--cache-dir", str(cache_dir), zon2json, "--", str(pkg_dir)] + + [str(manifest) for manifest in manifests], + ) if result.return_code != 0: - fail("Failed to parse manifest {}:\n{}".format(manifest, result.stderr)) + fail("Failed to resolve the Zig dependency graph:\n{}".format(result.stderr)) return json.decode(result.stdout) def _zig_packages_impl(module_ctx): zig = zig_path(module_ctx) zon2json = module_ctx.path(Label("//zig/private:zon2json.zig")) + cache_dir = module_ctx.path("cache") + pkg_dir = module_ctx.path("pkg") + manifests = [] for mod in module_ctx.modules: for tag in mod.tags.from_file: manifest = module_ctx.path(tag.build_zig_zon) - _read_dependencies(module_ctx, zig, manifest) - manifest_json = _parse_manifest(module_ctx, zig, zon2json, manifest) + _fetch(module_ctx, zig, manifest, cache_dir, pkg_dir) + manifests.append(manifest) + + graph = _resolve_graph(module_ctx, zig, zon2json, cache_dir, pkg_dir, manifests) - # buildifier: disable=print - print(manifest_json) + # buildifier: disable=print + print(graph) zig_packages = module_extension( implementation = _zig_packages_impl, diff --git a/zig/private/zon2json.zig b/zig/private/zon2json.zig index 49e73239..b5a383c7 100644 --- a/zig/private/zon2json.zig +++ b/zig/private/zon2json.zig @@ -1,64 +1,238 @@ -//! Parse a ZON file and emit its contents as JSON on stdout. +//! Resolve a Zig package dependency graph by recursively parsing `build.zig.zon` +//! manifests, and emit the merged graph as JSON on stdout. +//! +//! Usage: zon2json ... +//! +//! `` is the local package directory (`zig build --pkg-dir`) that holds +//! the fetched URL dependencies, each unpacked under `/`. The +//! remaining arguments are the root manifests to resolve. +//! +//! Packages are keyed by their Zig hash (URL dependencies) or by their resolved +//! absolute path (path dependencies). The emitted JSON has the shape: +//! +//! { +//! "roots": [{"deps": {"": ""}}], +//! "packages": {"": {"url": ..., "path": ..., "paths": [...], "deps": {"": ""}}} +//! } const std = @import("std"); const Zoir = std.zig.Zoir; +const Allocator = std.mem.Allocator; +const Io = std.Io; + +const Dep = struct { + name: []const u8, + url: ?[]const u8 = null, + hash: ?[]const u8 = null, + path: ?[]const u8 = null, +}; + +const Manifest = struct { + deps: []const Dep, + paths: []const []const u8, +}; + +const Edge = struct { + name: []const u8, + key: []const u8, +}; + +const Package = struct { + url: ?[]const u8, + path: ?[]const u8, + paths: []const []const u8, + deps: []const Edge, +}; + +const Resolved = struct { + key: []const u8, + url: ?[]const u8, + path: ?[]const u8, + dir: []const u8, +}; + +const Walker = struct { + arena: Allocator, + io: Io, + pkg_dir: []const u8, + packages: std.StringArrayHashMapUnmanaged(Package) = .empty, + + fn resolveDep(walker: *Walker, dep: Dep, parent_dir: []const u8) !Resolved { + if (dep.url) |url| { + const hash = dep.hash orelse fatal("URL dependency '{s}' is missing a hash", .{dep.name}); + return .{ + .key = hash, + .url = url, + .path = null, + .dir = try std.fs.path.join(walker.arena, &.{ walker.pkg_dir, hash }), + }; + } + if (dep.path) |rel| { + const dir = try std.fs.path.resolve(walker.arena, &.{ parent_dir, rel }); + return .{ .key = dir, .url = null, .path = dir, .dir = dir }; + } + fatal("dependency '{s}' has neither a url nor a path", .{dep.name}); + } + + fn resolveEdges(walker: *Walker, manifest: Manifest, dir: []const u8) ![]const Edge { + var edges: std.ArrayList(Edge) = .empty; + for (manifest.deps) |dep| { + const resolved = try walker.resolveDep(dep, dir); + try edges.append(walker.arena, .{ .name = dep.name, .key = resolved.key }); + try walker.walk(resolved); + } + return edges.items; + } + + fn walk(walker: *Walker, resolved: Resolved) anyerror!void { + const gop = try walker.packages.getOrPut(walker.arena, resolved.key); + if (gop.found_existing) return; + gop.value_ptr.* = .{ .url = resolved.url, .path = resolved.path, .paths = &.{}, .deps = &.{} }; + + const manifest_path = try std.fs.path.join(walker.arena, &.{ resolved.dir, "build.zig.zon" }); + const manifest = try parseManifest(walker.arena, walker.io, manifest_path); + const edges = try walker.resolveEdges(manifest, resolved.dir); + + walker.packages.getPtr(resolved.key).?.* = .{ + .url = resolved.url, + .path = resolved.path, + .paths = manifest.paths, + .deps = edges, + }; + } +}; pub fn main(init: std.process.Init) !void { - const allocator = init.arena.allocator(); + const arena = init.arena.allocator(); const io = init.io; - const args = try init.minimal.args.toSlice(allocator); - if (args.len != 2) fatal("usage: zon2json ", .{}); + const args = try init.minimal.args.toSlice(arena); + if (args.len < 2) fatal("usage: zon2json ...", .{}); - const source = try std.Io.Dir.cwd().readFileAllocOptions(io, args[1], allocator, .unlimited, .of(u8), 0); + var walker: Walker = .{ .arena = arena, .io = io, .pkg_dir = args[1] }; - const ast = try std.zig.Ast.parse(allocator, source, .zon); - const zoir = try std.zig.ZonGen.generate(allocator, ast, .{}); - if (zoir.compile_errors.len != 0) fatal("'{s}' is not valid ZON", .{args[1]}); + var roots: std.ArrayList([]const Edge) = .empty; + for (args[2..]) |root_path| { + const dir = std.fs.path.dirname(root_path) orelse "."; + const manifest = try parseManifest(arena, io, root_path); + try roots.append(arena, try walker.resolveEdges(manifest, dir)); + } var stdout_buffer: [4096]u8 = undefined; var stdout = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); const writer = &stdout.interface; var json: std.json.Stringify = .{ .writer = writer }; - try writeNode(&json, zoir, .root); + try json.beginObject(); + + try json.objectField("roots"); + try json.beginArray(); + for (roots.items) |edges| { + try json.beginObject(); + try json.objectField("deps"); + try writeEdges(&json, edges); + try json.endObject(); + } + try json.endArray(); + + try json.objectField("packages"); + try json.beginObject(); + for (walker.packages.keys(), walker.packages.values()) |key, package| { + try json.objectField(key); + try json.beginObject(); + try json.objectField("url"); + try json.write(package.url); + try json.objectField("path"); + try json.write(package.path); + try json.objectField("paths"); + try json.write(package.paths); + try json.objectField("deps"); + try writeEdges(&json, package.deps); + try json.endObject(); + } + try json.endObject(); + + try json.endObject(); try writer.writeByte('\n'); try writer.flush(); } -fn writeNode(json: *std.json.Stringify, zoir: Zoir, index: Zoir.Node.Index) !void { - switch (index.get(zoir)) { - .true => try json.write(true), - .false => try json.write(false), - .null, .pos_inf, .neg_inf, .nan => try json.write(null), - .int_literal => |int| switch (int) { - .small => |small| try json.write(small), - .big => |big| try json.print("{f}", .{big}), - }, - .float_literal => |float| try json.write(float), - .char_literal => |char| try json.write(char), - .enum_literal => |name| try json.write(name.get(zoir)), - .string_literal => |string| try json.write(string), - .empty_literal => { - try json.beginObject(); - try json.endObject(); - }, - .array_literal => |elements| { - try json.beginArray(); - for (0..elements.len) |i| try writeNode(json, zoir, elements.at(@intCast(i))); - try json.endArray(); - }, - .struct_literal => |fields| { - try json.beginObject(); - for (fields.names, 0..) |name, i| { - try json.objectField(name.get(zoir)); - try writeNode(json, zoir, fields.vals.at(@intCast(i))); +fn writeEdges(json: *std.json.Stringify, edges: []const Edge) !void { + try json.beginObject(); + for (edges) |edge| { + try json.objectField(edge.name); + try json.write(edge.key); + } + try json.endObject(); +} + +fn parseManifest(arena: Allocator, io: Io, path: []const u8) !Manifest { + const source = try std.Io.Dir.cwd().readFileAllocOptions(io, path, arena, .unlimited, .of(u8), 0); + + const ast = try std.zig.Ast.parse(arena, source, .zon); + const zoir = try std.zig.ZonGen.generate(arena, ast, .{}); + if (zoir.compile_errors.len != 0) fatal("'{s}' is not valid ZON", .{path}); + + var deps: std.ArrayList(Dep) = .empty; + var paths: std.ArrayList([]const u8) = .empty; + + switch (Zoir.Node.Index.root.get(zoir)) { + .struct_literal => |fields| for (fields.names, 0..) |name, i| { + const field = name.get(zoir); + const value = fields.vals.at(@intCast(i)); + if (std.mem.eql(u8, field, "dependencies")) { + try parseDeps(arena, zoir, value, &deps); + } else if (std.mem.eql(u8, field, "paths")) { + try parsePaths(arena, zoir, value, &paths); } - try json.endObject(); }, + else => fatal("'{s}' does not contain a struct", .{path}), + } + + return .{ .deps = deps.items, .paths = paths.items }; +} + +fn parseDeps(arena: Allocator, zoir: Zoir, index: Zoir.Node.Index, deps: *std.ArrayList(Dep)) !void { + const fields = switch (index.get(zoir)) { + .struct_literal => |fields| fields, + else => return, + }; + for (fields.names, 0..) |name, i| { + var dep: Dep = .{ .name = name.get(zoir) }; + switch (fields.vals.at(@intCast(i)).get(zoir)) { + .struct_literal => |entry| for (entry.names, 0..) |key, j| { + const value = entry.vals.at(@intCast(j)); + const field = key.get(zoir); + if (std.mem.eql(u8, field, "url")) { + dep.url = stringOf(zoir, value); + } else if (std.mem.eql(u8, field, "hash")) { + dep.hash = stringOf(zoir, value); + } else if (std.mem.eql(u8, field, "path")) { + dep.path = stringOf(zoir, value); + } + }, + else => {}, + } + try deps.append(arena, dep); + } +} + +fn parsePaths(arena: Allocator, zoir: Zoir, index: Zoir.Node.Index, paths: *std.ArrayList([]const u8)) !void { + switch (index.get(zoir)) { + .array_literal => |elements| for (0..elements.len) |i| { + try paths.append(arena, stringOf(zoir, elements.at(@intCast(i)))); + }, + else => {}, } } +fn stringOf(zoir: Zoir, index: Zoir.Node.Index) []const u8 { + return switch (index.get(zoir)) { + .string_literal => |string| string, + else => "", + }; +} + fn fatal(comptime format: []const u8, args: anytype) noreturn { std.debug.print("zon2json: " ++ format ++ "\n", args); std.process.exit(1); From 0398a360c2cd24fe890dcd335d5a2c91847ac9c8 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 17:29:34 +0200 Subject: [PATCH 13/77] map absolute paths to labels --- e2e/workspace/MODULE.bazel | 1 + .../zig-package-import/greet/BUILD.bazel | 0 zig/private/bzlmod/zig_packages.bzl | 39 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 e2e/workspace/zig-package-import/greet/BUILD.bazel diff --git a/e2e/workspace/MODULE.bazel b/e2e/workspace/MODULE.bazel index bcbff52d..6a49a56b 100644 --- a/e2e/workspace/MODULE.bazel +++ b/e2e/workspace/MODULE.bazel @@ -30,6 +30,7 @@ zig_packages = use_extension( dev_dependency = True, ) zig_packages.from_file(build_zig_zon = "//zig-package-import/app:build.zig.zon") +zig_packages.from_file(build_zig_zon = "//zig-package-import/greet:build.zig.zon") http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") diff --git a/e2e/workspace/zig-package-import/greet/BUILD.bazel b/e2e/workspace/zig-package-import/greet/BUILD.bazel new file mode 100644 index 00000000..e69de29b diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index dbda8f96..44c98185 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -29,6 +29,42 @@ def _resolve_graph(module_ctx, zig, zon2json, cache_dir, pkg_dir, manifests): fail("Failed to resolve the Zig dependency graph:\n{}".format(result.stderr)) return json.decode(result.stdout) +def _portable_key(key, pkg_dir, manifest_labels): + """Map a path dependency's absolute directory to a portable key. + + Absolute paths are local to a single resolution and must not be persisted. + Path dependencies inside the package directory become package-relative keys; + those in module source become the label of their provided manifest. + """ + if key.startswith(pkg_dir + "/"): + return key[len(pkg_dir) + 1:] + + manifest = key + "/build.zig.zon" + if manifest in manifest_labels: + return manifest_labels[manifest] + + fail("Zig path dependency at '{}' has no provided manifest; add its `build.zig.zon` as a `from_file` tag.".format(key)) + +def _localize_paths(graph, pkg_dir, manifest_labels): + remap = { + key: _portable_key(key, pkg_dir, manifest_labels) + for key, package in graph["packages"].items() + if package["path"] != None + } + + packages = {} + for key, package in graph["packages"].items(): + package["deps"] = {name: remap.get(child, child) for name, child in package["deps"].items()} + if package["path"] != None: + package["path"] = remap[key] + packages[remap.get(key, key)] = package + graph["packages"] = packages + + for root in graph["roots"]: + root["deps"] = {name: remap.get(child, child) for name, child in root["deps"].items()} + + return graph + def _zig_packages_impl(module_ctx): zig = zig_path(module_ctx) zon2json = module_ctx.path(Label("//zig/private:zon2json.zig")) @@ -36,13 +72,16 @@ def _zig_packages_impl(module_ctx): pkg_dir = module_ctx.path("pkg") manifests = [] + manifest_labels = {} for mod in module_ctx.modules: for tag in mod.tags.from_file: manifest = module_ctx.path(tag.build_zig_zon) _fetch(module_ctx, zig, manifest, cache_dir, pkg_dir) manifests.append(manifest) + manifest_labels[str(manifest)] = str(tag.build_zig_zon) graph = _resolve_graph(module_ctx, zig, zon2json, cache_dir, pkg_dir, manifests) + graph = _localize_paths(graph, str(pkg_dir), manifest_labels) # buildifier: disable=print print(graph) From 0d55cc6636ba8a24d6db6fa661e56d0240779410 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 11 Jun 2026 22:46:19 +0200 Subject: [PATCH 14/77] start zig_package rule --- e2e/workspace/MODULE.bazel | 8 ++++ zig/private/repo/zig_package.bzl | 65 ++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 zig/private/repo/zig_package.bzl diff --git a/e2e/workspace/MODULE.bazel b/e2e/workspace/MODULE.bazel index 6a49a56b..df380c25 100644 --- a/e2e/workspace/MODULE.bazel +++ b/e2e/workspace/MODULE.bazel @@ -32,6 +32,14 @@ zig_packages = use_extension( zig_packages.from_file(build_zig_zon = "//zig-package-import/app:build.zig.zon") zig_packages.from_file(build_zig_zon = "//zig-package-import/greet:build.zig.zon") +zig_package = use_repo_rule("@rules_zig//zig/private/repo:zig_package.bzl", "zig_package") + +zig_package( + name = "zig_clap", + url = "https://github.com/Hejsil/zig-clap/archive/8d97efa1ee1e575443c7888d5c38e1c3fc145cf5.tar.gz", + zig_hash = "clap-0.12.0-oBajB7foAQDqlSwaSG5g0yq7xGbQARUsBk5T64gAOqP5", +) + http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl new file mode 100644 index 00000000..bffe240a --- /dev/null +++ b/zig/private/repo/zig_package.bzl @@ -0,0 +1,65 @@ +"""Implementation of the `zig_package` repository rule.""" + +load("@rules_zig_host_toolchain//:toolchain.bzl", "zig_path") + +DOC = """\ +Fetch a Zig package with the Zig SDK. + +The Zig SDK downloads, verifies, and prunes the package according to its +`build.zig.zon`, and supports `git+` URLs. Fetching fails if the resulting +package hash does not match the expected `zig_hash`. +""" + +ATTRS = { + "url": attr.string(mandatory = True, doc = "The package URL, e.g. `https://...` or `git+https://...`."), + "zig_hash": attr.string(mandatory = True, doc = "The expected Zig package hash."), +} + +def _package_prefix(repository_ctx, archive): + # The archive nests the package under `//`. Strip up to + # and including the directory that holds `build.zig.zon`. + listing = repository_ctx.execute(["tar", "-tzf", str(archive)]) + if listing.return_code != 0: + fail("Failed to list the Zig package archive '{}':\n{}".format(archive, listing.stderr)) + + prefix = None + for entry in listing.stdout.split("\n"): + if entry.endswith("/build.zig.zon") and (prefix == None or len(entry) < len(prefix)): + prefix = entry + if prefix == None: + fail("No `build.zig.zon` found in the Zig package archive '{}'.".format(archive)) + return prefix[:-len("/build.zig.zon")] + +def _zig_package_impl(repository_ctx): + zig = zig_path(repository_ctx) + cache = repository_ctx.path("cache") + + fetch = repository_ctx.execute([zig, "fetch", "--global-cache-dir", str(cache), repository_ctx.attr.url]) + if fetch.return_code != 0: + fail("`zig fetch {}` failed:\n{}".format(repository_ctx.attr.url, fetch.stderr)) + + fetched_hash = fetch.stdout.strip() + if fetched_hash != repository_ctx.attr.zig_hash: + fail("Zig package hash mismatch for '{}':\n expected: {}\n fetched: {}".format( + repository_ctx.attr.url, + repository_ctx.attr.zig_hash, + fetched_hash, + )) + + archive = cache.get_child("p").get_child(fetched_hash + ".tar.gz") + repository_ctx.extract(archive, strip_prefix = _package_prefix(repository_ctx, archive)) + repository_ctx.delete(cache) + + repository_ctx.file("BUILD.bazel", """\ +filegroup( + name = "files", + srcs = glob(["**"], exclude = ["BUILD.bazel"]), + visibility = ["//visibility:public"], +) +""") + +zig_package = repository_rule( + _zig_package_impl, + attrs = ATTRS, + doc = DOC, +) From a7ee051c615aea2635009cb6e335b25e5c90fd2e Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 09:37:07 +0200 Subject: [PATCH 15/77] Zig script to determine package prefix - avoid host tar dependency --- zig/private/package_prefix.zig | 57 ++++++++++++++++++++++++++++++++ zig/private/repo/zig_package.bzl | 24 +++++--------- 2 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 zig/private/package_prefix.zig diff --git a/zig/private/package_prefix.zig b/zig/private/package_prefix.zig new file mode 100644 index 00000000..135b6f55 --- /dev/null +++ b/zig/private/package_prefix.zig @@ -0,0 +1,57 @@ +//! Print the directory prefix of the shallowest `build.zig.zon` in a Zig +//! package archive (a gzip-compressed tarball), for use as an extraction +//! `strip_prefix`. +//! +//! Usage: package_prefix + +const std = @import("std"); +const flate = std.compress.flate; +const tar = std.tar; + +pub fn main(init: std.process.Init) !void { + const allocator = init.arena.allocator(); + const io = init.io; + + const args = try init.minimal.args.toSlice(allocator); + if (args.len != 2) fatal("usage: package_prefix ", .{}); + + var file = try std.Io.Dir.cwd().openFile(io, args[1], .{}); + defer file.close(io); + + var read_buffer: [64 * 1024]u8 = undefined; + var file_reader = file.reader(io, &read_buffer); + + var window: [flate.max_window_len]u8 = undefined; + var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &window); + + var name_buffer: [std.fs.max_path_bytes]u8 = undefined; + var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; + var iterator: tar.Iterator = .init(&decompress.reader, .{ + .file_name_buffer = &name_buffer, + .link_name_buffer = &link_name_buffer, + }); + + var prefix: ?[]const u8 = null; + while (try iterator.next()) |entry| { + if (entry.kind == .directory) continue; + if (!std.mem.eql(u8, std.fs.path.basename(entry.name), "build.zig.zon")) continue; + + const dir = std.fs.path.dirname(entry.name) orelse ""; + if (prefix == null or dir.len < prefix.?.len) { + prefix = try allocator.dupe(u8, dir); + } + } + + const result = prefix orelse fatal("no `build.zig.zon` found in '{s}'", .{args[1]}); + + var stdout_buffer: [std.fs.max_path_bytes]u8 = undefined; + var stdout = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); + try stdout.interface.writeAll(result); + try stdout.interface.writeByte('\n'); + try stdout.interface.flush(); +} + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.debug.print("package_prefix: " ++ format ++ "\n", args); + std.process.exit(1); +} diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index bffe240a..a56599bb 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -15,23 +15,17 @@ ATTRS = { "zig_hash": attr.string(mandatory = True, doc = "The expected Zig package hash."), } -def _package_prefix(repository_ctx, archive): - # The archive nests the package under `//`. Strip up to - # and including the directory that holds `build.zig.zon`. - listing = repository_ctx.execute(["tar", "-tzf", str(archive)]) - if listing.return_code != 0: - fail("Failed to list the Zig package archive '{}':\n{}".format(archive, listing.stderr)) - - prefix = None - for entry in listing.stdout.split("\n"): - if entry.endswith("/build.zig.zon") and (prefix == None or len(entry) < len(prefix)): - prefix = entry - if prefix == None: - fail("No `build.zig.zon` found in the Zig package archive '{}'.".format(archive)) - return prefix[:-len("/build.zig.zon")] +def _package_prefix(repository_ctx, zig, helper, cache, archive): + # The archive nests the package under `//`; strip up to + # the directory that holds `build.zig.zon`. + result = repository_ctx.execute([zig, "run", "--cache-dir", str(cache), helper, "--", str(archive)]) + if result.return_code != 0: + fail("Failed to inspect the Zig package archive '{}':\n{}".format(archive, result.stderr)) + return result.stdout.strip() def _zig_package_impl(repository_ctx): zig = zig_path(repository_ctx) + helper = repository_ctx.path(Label("//zig/private:package_prefix.zig")) cache = repository_ctx.path("cache") fetch = repository_ctx.execute([zig, "fetch", "--global-cache-dir", str(cache), repository_ctx.attr.url]) @@ -47,7 +41,7 @@ def _zig_package_impl(repository_ctx): )) archive = cache.get_child("p").get_child(fetched_hash + ".tar.gz") - repository_ctx.extract(archive, strip_prefix = _package_prefix(repository_ctx, archive)) + repository_ctx.extract(archive, strip_prefix = _package_prefix(repository_ctx, zig, helper, cache, archive)) repository_ctx.delete(cache) repository_ctx.file("BUILD.bazel", """\ From cc573027dab919fb962e46685ef7a198f26aa845 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 09:54:59 +0200 Subject: [PATCH 16/77] invoke zig_package in zig_packages extension --- zig/private/bzlmod/zig_packages.bzl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 44c98185..4ffa25e0 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -1,6 +1,7 @@ """Implementation of the `zig_packages` module extension.""" load("@rules_zig_host_toolchain//:toolchain.bzl", "zig_path") +load("//zig/private/repo:zig_package.bzl", "zig_package") from_file = tag_class( attrs = { @@ -83,8 +84,13 @@ def _zig_packages_impl(module_ctx): graph = _resolve_graph(module_ctx, zig, zon2json, cache_dir, pkg_dir, manifests) graph = _localize_paths(graph, str(pkg_dir), manifest_labels) - # buildifier: disable=print - print(graph) + for key, package in graph["packages"].items(): + if package["url"] != None: + zig_package( + name = key, + url = package["url"], + zig_hash = key, + ) zig_packages = module_extension( implementation = _zig_packages_impl, From 580c1ca583ecaddae5a471546b7f4319a010f12d Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 09:55:35 +0200 Subject: [PATCH 17/77] remove explicit demo zig_clap import --- e2e/workspace/MODULE.bazel | 8 -------- 1 file changed, 8 deletions(-) diff --git a/e2e/workspace/MODULE.bazel b/e2e/workspace/MODULE.bazel index df380c25..6a49a56b 100644 --- a/e2e/workspace/MODULE.bazel +++ b/e2e/workspace/MODULE.bazel @@ -32,14 +32,6 @@ zig_packages = use_extension( zig_packages.from_file(build_zig_zon = "//zig-package-import/app:build.zig.zon") zig_packages.from_file(build_zig_zon = "//zig-package-import/greet:build.zig.zon") -zig_package = use_repo_rule("@rules_zig//zig/private/repo:zig_package.bzl", "zig_package") - -zig_package( - name = "zig_clap", - url = "https://github.com/Hejsil/zig-clap/archive/8d97efa1ee1e575443c7888d5c38e1c3fc145cf5.tar.gz", - zig_hash = "clap-0.12.0-oBajB7foAQDqlSwaSG5g0yq7xGbQARUsBk5T64gAOqP5", -) - http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( From 348478ae3674c8bfe2758d2ae215143c1a6bdd59 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 12:12:23 +0200 Subject: [PATCH 18/77] generate Zig packages hub repository --- e2e/workspace/MODULE.bazel | 1 + .../zig-package-import/greet/BUILD.bazel | 8 +++ zig/private/bzlmod/zig_packages.bzl | 39 ++++++++++++++ zig/private/repo/zig_deps_hub.bzl | 53 +++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 zig/private/repo/zig_deps_hub.bzl diff --git a/e2e/workspace/MODULE.bazel b/e2e/workspace/MODULE.bazel index 6a49a56b..f610eb0b 100644 --- a/e2e/workspace/MODULE.bazel +++ b/e2e/workspace/MODULE.bazel @@ -31,6 +31,7 @@ zig_packages = use_extension( ) zig_packages.from_file(build_zig_zon = "//zig-package-import/app:build.zig.zon") zig_packages.from_file(build_zig_zon = "//zig-package-import/greet:build.zig.zon") +use_repo(zig_packages, "zig_deps") http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") diff --git a/e2e/workspace/zig-package-import/greet/BUILD.bazel b/e2e/workspace/zig-package-import/greet/BUILD.bazel index e69de29b..6307adfb 100644 --- a/e2e/workspace/zig-package-import/greet/BUILD.bazel +++ b/e2e/workspace/zig-package-import/greet/BUILD.bazel @@ -0,0 +1,8 @@ +filegroup( + name = "files", + srcs = glob( + ["**"], + exclude = ["BUILD.bazel"], + ), + visibility = ["//visibility:public"], +) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 4ffa25e0..e7d9b6d8 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -1,6 +1,7 @@ """Implementation of the `zig_packages` module extension.""" load("@rules_zig_host_toolchain//:toolchain.bzl", "zig_path") +load("//zig/private/repo:zig_deps_hub.bzl", "zig_deps_hub") load("//zig/private/repo:zig_package.bzl", "zig_package") from_file = tag_class( @@ -74,16 +75,26 @@ def _zig_packages_impl(module_ctx): manifests = [] manifest_labels = {} + root_tags = [] + root_dev = False + root_nondev = False for mod in module_ctx.modules: for tag in mod.tags.from_file: manifest = module_ctx.path(tag.build_zig_zon) _fetch(module_ctx, zig, manifest, cache_dir, pkg_dir) manifests.append(manifest) manifest_labels[str(manifest)] = str(tag.build_zig_zon) + root_tags.append(tag.build_zig_zon) + if mod.is_root: + if module_ctx.is_dev_dependency(tag): + root_dev = True + else: + root_nondev = True graph = _resolve_graph(module_ctx, zig, zon2json, cache_dir, pkg_dir, manifests) graph = _localize_paths(graph, str(pkg_dir), manifest_labels) + package_labels = {} for key, package in graph["packages"].items(): if package["url"] != None: zig_package( @@ -91,6 +102,34 @@ def _zig_packages_impl(module_ctx): url = package["url"], zig_hash = key, ) + package_labels[key] = "@{}//:files".format(key) + + for tag in root_tags: + package_labels[str(tag)] = tag.same_package_label("files") + + zig_deps_hub( + name = "zig_deps", + manifests = json.encode(_hub_manifests(graph, root_tags)), + packages = package_labels, + ) + + direct = ["zig_deps"] if root_nondev else [] + dev = ["zig_deps"] if root_dev and not root_nondev else [] + return module_ctx.extension_metadata( + root_module_direct_deps = direct, + root_module_direct_dev_deps = dev, + ) + +def _hub_manifests(graph, root_tags): + manifests = [] + for root, label in zip(graph["roots"], root_tags): + package = label.repo_name + "/" + label.package if label.repo_name else label.package + manifests.append({ + "package": package, + "scope": str(label.same_package_label("__subpackages__")), + "deps": root["deps"], + }) + return manifests zig_packages = module_extension( implementation = _zig_packages_impl, diff --git a/zig/private/repo/zig_deps_hub.bzl b/zig/private/repo/zig_deps_hub.bzl new file mode 100644 index 00000000..c8f6931b --- /dev/null +++ b/zig/private/repo/zig_deps_hub.bzl @@ -0,0 +1,53 @@ +"""Implementation of the `zig_deps` hub repository rule.""" + +DOC = """\ +The `@zig_deps` hub repository. + +Exposes each manifest's direct Zig dependencies by name, grouped under the +manifest's own Bazel package and scoped to that package's subtree. Generated by +the `zig_packages` module extension. +""" + +ATTRS = { + "manifests": attr.string( + mandatory = True, + doc = "JSON-encoded list of `{package, scope, deps}` manifest entries.", + ), + "packages": attr.string_keyed_label_dict( + mandatory = True, + doc = "Map from each package key to its `files` target.", + ), +} + +_ALIAS = """\ +alias( + name = "{name}", + actual = "{actual}", + visibility = ["{scope}"], +) +""" + +def _zig_deps_hub_impl(repository_ctx): + packages = repository_ctx.attr.packages + manifests = json.decode(repository_ctx.attr.manifests) + + builds = {} + for manifest in manifests: + aliases = [ + _ALIAS.format(name = name, actual = str(packages[key]), scope = manifest["scope"]) + for name, key in manifest["deps"].items() + ] + builds[manifest["package"]] = "\n".join(aliases) + + if "" not in builds: + builds[""] = "" + + for package, content in builds.items(): + path = package + "/BUILD.bazel" if package else "BUILD.bazel" + repository_ctx.file(path, content) + +zig_deps_hub = repository_rule( + _zig_deps_hub_impl, + attrs = ATTRS, + doc = DOC, +) From e29361789d6f223cee76259ff431767739ceaaea Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 13:20:51 +0200 Subject: [PATCH 19/77] Zig dependencies hub repo accessor macro --- .../zig-package-import/app/BUILD.bazel | 9 ++++ zig/private/bzlmod/zig_packages.bzl | 4 +- zig/private/repo/zig_deps_hub.bzl | 51 +++++++++++++++++-- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/e2e/workspace/zig-package-import/app/BUILD.bazel b/e2e/workspace/zig-package-import/app/BUILD.bazel index e69de29b..5a6355fc 100644 --- a/e2e/workspace/zig-package-import/app/BUILD.bazel +++ b/e2e/workspace/zig-package-import/app/BUILD.bazel @@ -0,0 +1,9 @@ +load("@zig_deps//:defs.bzl", "zig_dep") + +filegroup( + name = "files", + srcs = [ + zig_dep("clap"), + zig_dep("greet"), + ], +) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index e7d9b6d8..5083117d 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -123,9 +123,9 @@ def _zig_packages_impl(module_ctx): def _hub_manifests(graph, root_tags): manifests = [] for root, label in zip(graph["roots"], root_tags): - package = label.repo_name + "/" + label.package if label.repo_name else label.package manifests.append({ - "package": package, + "repo": label.repo_name, + "package": label.package, "scope": str(label.same_package_label("__subpackages__")), "deps": root["deps"], }) diff --git a/zig/private/repo/zig_deps_hub.bzl b/zig/private/repo/zig_deps_hub.bzl index c8f6931b..05fc7003 100644 --- a/zig/private/repo/zig_deps_hub.bzl +++ b/zig/private/repo/zig_deps_hub.bzl @@ -11,7 +11,7 @@ the `zig_packages` module extension. ATTRS = { "manifests": attr.string( mandatory = True, - doc = "JSON-encoded list of `{package, scope, deps}` manifest entries.", + doc = "JSON-encoded list of `{repo, package, scope, deps}` manifest entries.", ), "packages": attr.string_keyed_label_dict( mandatory = True, @@ -27,24 +27,65 @@ alias( ) """ +_DEFS = '''\ +"""Accessors for the Zig package dependencies of each `from_file` manifest.""" + +_MANIFESTS = json.decode("""%MANIFESTS%""") + +def zig_dep(name): + """Return the label of dependency `name` for the enclosing Zig manifest.""" + repo = native.repo_name() + package = native.package_name() + manifests = _MANIFESTS.get(repo, {}) + manifest = _enclosing(manifests, package) + if manifest == None: + fail("no Zig `from_file` manifest covers package '%s'" % package) + if name not in manifests[manifest]: + fail("Zig manifest '%s' declares no dependency '%s'; available: %s" % ( + manifest, + name, + manifests[manifest], + )) + path = repo + "/" + manifest if repo else manifest + return Label("//" + path + ":" + name) + +def _enclosing(manifests, package): + candidate = package + for _ in range(len(package) + 1): + if candidate in manifests: + return candidate + if not candidate: + break + candidate = candidate.rpartition("/")[0] + return None +''' + +def _hub_path(manifest): + repo, package = manifest["repo"], manifest["package"] + return repo + "/" + package if repo else package + def _zig_deps_hub_impl(repository_ctx): packages = repository_ctx.attr.packages manifests = json.decode(repository_ctx.attr.manifests) builds = {} + registry = {} for manifest in manifests: aliases = [ _ALIAS.format(name = name, actual = str(packages[key]), scope = manifest["scope"]) for name, key in manifest["deps"].items() ] - builds[manifest["package"]] = "\n".join(aliases) + builds[_hub_path(manifest)] = "\n".join(aliases) + registry.setdefault(manifest["repo"], {})[manifest["package"]] = manifest["deps"].keys() if "" not in builds: builds[""] = "" - for package, content in builds.items(): - path = package + "/BUILD.bazel" if package else "BUILD.bazel" - repository_ctx.file(path, content) + for path, content in builds.items(): + build_file = path + "/BUILD.bazel" if path else "BUILD.bazel" + repository_ctx.file(build_file, content) + + repository_ctx.file("defs.bzl", _DEFS.replace("%MANIFESTS%", json.encode(registry))) zig_deps_hub = repository_rule( _zig_deps_hub_impl, From 834bf0aeb2101baa6f9e49f3acf937f3b016f6cc Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 13:40:06 +0200 Subject: [PATCH 20/77] convenience macro to return all Zig deps of a manifest --- .../zig-package-import/app/BUILD.bazel | 7 ++---- zig/private/repo/zig_deps_hub.bzl | 22 +++++++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/e2e/workspace/zig-package-import/app/BUILD.bazel b/e2e/workspace/zig-package-import/app/BUILD.bazel index 5a6355fc..ecd7ee99 100644 --- a/e2e/workspace/zig-package-import/app/BUILD.bazel +++ b/e2e/workspace/zig-package-import/app/BUILD.bazel @@ -1,9 +1,6 @@ -load("@zig_deps//:defs.bzl", "zig_dep") +load("@zig_deps//:defs.bzl", "zig_deps") filegroup( name = "files", - srcs = [ - zig_dep("clap"), - zig_dep("greet"), - ], + srcs = zig_deps(), ) diff --git a/zig/private/repo/zig_deps_hub.bzl b/zig/private/repo/zig_deps_hub.bzl index 05fc7003..ba6ede77 100644 --- a/zig/private/repo/zig_deps_hub.bzl +++ b/zig/private/repo/zig_deps_hub.bzl @@ -33,20 +33,28 @@ _DEFS = '''\ _MANIFESTS = json.decode("""%MANIFESTS%""") def zig_dep(name): - """Return the label of dependency `name` for the enclosing Zig manifest.""" + """Return the label of dependency `name` of the enclosing Zig manifest.""" + path, names = _manifest() + if name not in names: + fail("Zig manifest '%s' declares no dependency '%s'; available: %s" % (path, name, names)) + return _label(path, name) + +def zig_deps(): + """Return the labels of every dependency of the enclosing Zig manifest.""" + path, names = _manifest() + return [_label(path, name) for name in names] + +def _manifest(): repo = native.repo_name() package = native.package_name() manifests = _MANIFESTS.get(repo, {}) manifest = _enclosing(manifests, package) if manifest == None: fail("no Zig `from_file` manifest covers package '%s'" % package) - if name not in manifests[manifest]: - fail("Zig manifest '%s' declares no dependency '%s'; available: %s" % ( - manifest, - name, - manifests[manifest], - )) path = repo + "/" + manifest if repo else manifest + return path, manifests[manifest] + +def _label(path, name): return Label("//" + path + ":" + name) def _enclosing(manifests, package): From 8d10e88496975e576749c67edeceb85e052854ea Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 15:56:40 +0200 Subject: [PATCH 21/77] extract a Zig package's module set via build configuration --- zig/private/configurer.zig | 141 +++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 zig/private/configurer.zig diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig new file mode 100644 index 00000000..9ef26cf4 --- /dev/null +++ b/zig/private/configurer.zig @@ -0,0 +1,141 @@ +//! Configure a Zig package's `build.zig` and emit its public module graph as +//! JSON, for translation into Bazel `zig_library` targets. +//! +//! Usage: configurer --zig --build-root +//! +//! Modeled on `lib/compiler/configurer.zig`: it sets up a `std.Build`, runs the +//! package's `build` function, then walks `b.modules` (the modules registered +//! via `b.addModule`) instead of serializing the build graph. The package's +//! `build.zig` is provided as the `pkg` module and its dependency table as the +//! `deps` module, both wired in at compile time. +//! +//! The emitted JSON has the shape: +//! +//! {"modules": [{"name": ..., "root_source": ..., "imports": [ +//! {"name": ..., "root_source": ..., "package": }]}]} +//! +//! `package` is the Zig hash of the dependency package that owns an imported +//! module, or the empty string for an import within the same package. + +const std = @import("std"); +const Io = std.Io; +const Build = std.Build; +const LazyPath = Build.LazyPath; +const mem = std.mem; +const process = std.process; + +pub const root = @import("pkg"); +pub const dependencies = @import("deps"); + +pub fn main(init: process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + + const args = try init.minimal.args.toSlice(arena); + + var zig_exe: ?[]const u8 = null; + var build_root_sub_path: ?[]const u8 = null; + var i: usize = 1; + while (i < args.len) : (i += 1) { + if (mem.eql(u8, args[i], "--zig")) { + zig_exe = nextArg(args, &i); + } else if (mem.eql(u8, args[i], "--build-root")) { + build_root_sub_path = nextArg(args, &i); + } else { + fatal("unrecognized argument: {s}", .{args[i]}); + } + } + + const zig = zig_exe orelse fatal("missing --zig", .{}); + const build_root_path = build_root_sub_path orelse fatal("missing --build-root", .{}); + + var graph: Build.Graph = .{ + .io = io, + .arena = arena, + .environ_map = try init.minimal.environ.createMap(arena), + .host = .{ + .query = .{}, + .result = try std.zig.system.resolveTargetQuery(io, .{}), + }, + .generated_files = .empty, + .zig_exe = zig, + .wip_configuration = .init(arena), + }; + // Seed the configuration string table so its reserved sentinels resolve: + // `.empty` must intern at offset 0 and `.root` at offset 1, before any other + // string. + const empty_string = try graph.wip_configuration.addString(""); + const root_string = try graph.wip_configuration.addString("root"); + std.debug.assert(empty_string == .empty); + std.debug.assert(root_string == .root); + + const build_root: Build.Cache.Path = .{ + .root_dir = .{ + .handle = try Io.Dir.cwd().openDir(io, build_root_path, .{}), + .path = build_root_path, + }, + }; + + const builder = try Build.create(&graph, build_root, dependencies.root_deps); + + try builder.runBuild(root); + + try emit(io, builder); +} + +fn emit(io: Io, builder: *Build) !void { + var stdout_buffer: [4096]u8 = undefined; + var stdout = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); + const writer = &stdout.interface; + + var json: std.json.Stringify = .{ .writer = writer }; + try json.beginObject(); + try json.objectField("modules"); + try json.beginArray(); + for (builder.modules.keys(), builder.modules.values()) |name, module| { + try json.beginObject(); + try json.objectField("name"); + try json.write(name); + try json.objectField("root_source"); + try json.write(lazyPathString(module.root_source_file)); + try json.objectField("imports"); + try json.beginArray(); + for (module.import_table.keys(), module.import_table.values()) |import_name, imported| { + try json.beginObject(); + try json.objectField("name"); + try json.write(import_name); + try json.objectField("root_source"); + try json.write(lazyPathString(imported.root_source_file)); + try json.objectField("package"); + try json.write(imported.owner.pkg_hash); + try json.endObject(); + } + try json.endArray(); + try json.endObject(); + } + try json.endArray(); + try json.endObject(); + try writer.writeByte('\n'); + try writer.flush(); +} + +fn lazyPathString(lazy_path: ?LazyPath) ?[]const u8 { + const path = lazy_path orelse return null; + return switch (path) { + .src_path => |src| src.sub_path, + .cwd_relative => |rel| rel, + .relative => |rel| rel.sub_path, + else => null, + }; +} + +fn nextArg(args: []const [:0]const u8, i: *usize) []const u8 { + i.* += 1; + if (i.* >= args.len) fatal("'{s}' requires a value", .{args[i.* - 1]}); + return args[i.*]; +} + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.debug.print("configurer: " ++ format ++ "\n", args); + std.process.exit(1); +} From a639cdc797a1f0471faff9c0cae343bd057f31f2 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 17:28:33 +0200 Subject: [PATCH 22/77] run Zig build configuration --- zig/private/bzlmod/zig_packages.bzl | 24 ++++++++ zig/private/repo/zig_package.bzl | 95 ++++++++++++++++++++++++++++- zig/private/zon2json.zig | 14 +++-- 3 files changed, 126 insertions(+), 7 deletions(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 5083117d..2796ef02 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -67,6 +67,19 @@ def _localize_paths(graph, pkg_dir, manifest_labels): return graph +def _url_edges(graph, key): + return [ + [name, child] + for name, child in graph["packages"][key]["deps"].items() + if graph["packages"][child]["url"] != None + ] + +def _deps_data(graph, key, closure): + return { + "root_deps": _url_edges(graph, key), + "packages": {dep: {"has_build_zig": True, "deps": _url_edges(graph, dep)} for dep in closure}, + } + def _zig_packages_impl(module_ctx): zig = zig_path(module_ctx) zon2json = module_ctx.path(Label("//zig/private:zon2json.zig")) @@ -94,13 +107,24 @@ def _zig_packages_impl(module_ctx): graph = _resolve_graph(module_ctx, zig, zon2json, cache_dir, pkg_dir, manifests) graph = _localize_paths(graph, str(pkg_dir), manifest_labels) + # `graph["packages"]` is topologically ordered, so each dependency's closure + # is already known by the time we reach a package: accumulate in one pass. + closures = {} package_labels = {} for key, package in graph["packages"].items(): + closure = {} + for _name, child in _url_edges(graph, key): + closure[child] = True + for dep in closures[child]: + closure[dep] = True + closures[key] = closure.keys() if package["url"] != None: zig_package( name = key, url = package["url"], zig_hash = key, + deps = json.encode(_deps_data(graph, key, closures[key])), + dep_build_files = {dep: "@{}//:build.zig".format(dep) for dep in closures[key]}, ) package_labels[key] = "@{}//:files".format(key) diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index a56599bb..13599191 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -7,12 +7,20 @@ Fetch a Zig package with the Zig SDK. The Zig SDK downloads, verifies, and prunes the package according to its `build.zig.zon`, and supports `git+` URLs. Fetching fails if the resulting -package hash does not match the expected `zig_hash`. +package hash does not match the expected `zig_hash`. The package's `build.zig` +is then configured to extract its public module graph (`module_manifest.json`). """ ATTRS = { "url": attr.string(mandatory = True, doc = "The package URL, e.g. `https://...` or `git+https://...`."), "zig_hash": attr.string(mandatory = True, doc = "The expected Zig package hash."), + "deps": attr.string( + default = "{\"root_deps\": [], \"packages\": {}}", + doc = "JSON `{root_deps, packages}` describing the `@dependencies` closure used to configure the package.", + ), + "dep_build_files": attr.string_keyed_label_dict( + doc = "Map from each dependency package hash to its `build.zig`, used to wire `@dependencies`.", + ), } def _package_prefix(repository_ctx, zig, helper, cache, archive): @@ -23,6 +31,85 @@ def _package_prefix(repository_ctx, zig, helper, cache, archive): fail("Failed to inspect the Zig package archive '{}':\n{}".format(archive, result.stderr)) return result.stdout.strip() +_EMPTY_DEPS = """\ +pub const packages = struct {}; +pub const root_deps: []const struct { []const u8, []const u8 } = &.{}; +""" + +def _zig_string(value): + return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"" + +def _edge_lines(edges, indent): + return [ + "{}.{{ {}, {} }},".format(indent, _zig_string(name), _zig_string(key)) + for name, key in edges + ] + +def _dependencies_source(repository_ctx, deps): + """Render the `@dependencies` module that `b.dependency` consumes.""" + packages = deps["packages"] + if not packages: + return _EMPTY_DEPS + + build_files = repository_ctx.attr.dep_build_files + lines = ["pub const packages = struct {"] + for key in sorted(packages): + package = packages[key] + build_root = str(repository_ctx.path(build_files[key]).dirname) + lines.append(" pub const @\"{}\" = struct {{".format(key)) + lines.append(" pub const build_root = {};".format(_zig_string(build_root))) + if package["has_build_zig"]: + lines.append(" pub const build_zig = @import(\"{}\");".format(key)) + lines.append(" pub const deps: []const struct { []const u8, []const u8 } = &.{") + lines.extend(_edge_lines(package["deps"], " ")) + lines.append(" };") + lines.append(" };") + lines.append("};") + lines.append("") + lines.append("pub const root_deps: []const struct { []const u8, []const u8 } = &.{") + lines.extend(_edge_lines(deps["root_deps"], " ")) + lines.append("};") + return "\n".join(lines) + "\n" + +def _configure(repository_ctx, zig, build_zig): + """Configure the package's `build.zig` and return its module-graph JSON.""" + configurer = repository_ctx.path(Label("//zig/private:configurer.zig")) + deps = json.decode(repository_ctx.attr.deps) + build_files = repository_ctx.attr.dep_build_files + + repository_ctx.file("_configure/deps.zig", _dependencies_source(repository_ctx, deps)) + + hashes = sorted([key for key in deps["packages"] if deps["packages"][key]["has_build_zig"]]) + + args = [zig, "build-exe", "--dep", "pkg", "--dep", "deps", "-Mroot=" + str(configurer), "-Mpkg=" + str(build_zig)] + for key in hashes: + args.extend(["--dep", key]) + args.append("-Mdeps=" + str(repository_ctx.path("_configure/deps.zig"))) + for key in hashes: + args.append("-M{}={}".format(key, repository_ctx.path(build_files[key]))) + args.extend([ + "--cache-dir", + str(repository_ctx.path("_configure/cache")), + "-femit-bin=" + str(repository_ctx.path("_configure/configurer")), + ]) + + compiled = repository_ctx.execute(args) + if compiled.return_code != 0: + fail("Failed to compile the Zig configurer for '{}':\n{}".format(repository_ctx.attr.url, compiled.stderr)) + + configured = repository_ctx.execute([ + str(repository_ctx.path("_configure/configurer")), + "--zig", + str(zig), + "--build-root", + str(repository_ctx.path(".")), + ]) + if configured.return_code != 0: + fail("Failed to configure the Zig package '{}':\n{}".format(repository_ctx.attr.url, configured.stderr)) + + repository_ctx.delete("_configure") + return configured.stdout + def _zig_package_impl(repository_ctx): zig = zig_path(repository_ctx) helper = repository_ctx.path(Label("//zig/private:package_prefix.zig")) @@ -44,10 +131,14 @@ def _zig_package_impl(repository_ctx): repository_ctx.extract(archive, strip_prefix = _package_prefix(repository_ctx, zig, helper, cache, archive)) repository_ctx.delete(cache) + build_zig = repository_ctx.path("build.zig") + if build_zig.exists: + repository_ctx.file("module_manifest.json", _configure(repository_ctx, zig, build_zig)) + repository_ctx.file("BUILD.bazel", """\ filegroup( name = "files", - srcs = glob(["**"], exclude = ["BUILD.bazel"]), + srcs = glob(["**"], exclude = ["BUILD.bazel", "module_manifest.json"]), visibility = ["//visibility:public"], ) """) diff --git a/zig/private/zon2json.zig b/zig/private/zon2json.zig index b5a383c7..58568568 100644 --- a/zig/private/zon2json.zig +++ b/zig/private/zon2json.zig @@ -8,7 +8,9 @@ //! remaining arguments are the root manifests to resolve. //! //! Packages are keyed by their Zig hash (URL dependencies) or by their resolved -//! absolute path (path dependencies). The emitted JSON has the shape: +//! absolute path (path dependencies), and listed in topological order: a package +//! always precedes any package that lists it as a dependency. The emitted JSON +//! has the shape: //! //! { //! "roots": [{"deps": {"": ""}}], @@ -56,6 +58,7 @@ const Walker = struct { io: Io, pkg_dir: []const u8, packages: std.StringArrayHashMapUnmanaged(Package) = .empty, + visited: std.StringHashMapUnmanaged(void) = .empty, fn resolveDep(walker: *Walker, dep: Dep, parent_dir: []const u8) !Resolved { if (dep.url) |url| { @@ -85,20 +88,21 @@ const Walker = struct { } fn walk(walker: *Walker, resolved: Resolved) anyerror!void { - const gop = try walker.packages.getOrPut(walker.arena, resolved.key); + const gop = try walker.visited.getOrPut(walker.arena, resolved.key); if (gop.found_existing) return; - gop.value_ptr.* = .{ .url = resolved.url, .path = resolved.path, .paths = &.{}, .deps = &.{} }; const manifest_path = try std.fs.path.join(walker.arena, &.{ resolved.dir, "build.zig.zon" }); const manifest = try parseManifest(walker.arena, walker.io, manifest_path); const edges = try walker.resolveEdges(manifest, resolved.dir); - walker.packages.getPtr(resolved.key).?.* = .{ + // Append only after the dependencies have been walked, so `packages` ends + // up in topological order: a package precedes any package depending on it. + try walker.packages.put(walker.arena, resolved.key, .{ .url = resolved.url, .path = resolved.path, .paths = manifest.paths, .deps = edges, - }; + }); } }; From 236f1612bc3b8cc9ac9873c0fde4cb0d6fc63930 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 18:03:35 +0200 Subject: [PATCH 23/77] generate Zig library targets --- zig/private/repo/zig_package.bzl | 53 +++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 13599191..9ace50ef 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -71,6 +71,46 @@ def _dependencies_source(repository_ctx, deps): lines.append("};") return "\n".join(lines) + "\n" +_FILES = """\ +filegroup( + name = "files", + srcs = glob(["**"], exclude = ["BUILD.bazel", "module_manifest.json"]), + visibility = ["//visibility:public"], +) +""" + +_ZIG_LIBRARY = """\ +zig_library( + name = "{name}", + main = "{main}", + import_name = "{name}", + srcs = glob(["**/*.zig"], exclude = ["{main}"]), + deps = {deps}, + visibility = ["//visibility:public"], +) +""" + +def _module_dep(repository_ctx, imported): + # A same-package import resolves to a sibling module target; a cross-package + # import resolves to the module of the same name in the dependency's spoke. + if imported["package"]: + spoke = repository_ctx.attr.dep_build_files[imported["package"]] + return str(spoke.same_package_label(imported["name"])) + return ":" + imported["name"] + +def _build_file(repository_ctx, modules): + chunks = ["load(\"@rules_zig//zig:defs.bzl\", \"zig_library\")", "", _FILES] + for module in modules: + if not module["root_source"]: + continue + deps = [_module_dep(repository_ctx, imported) for imported in module["imports"]] + chunks.append(_ZIG_LIBRARY.format( + name = module["name"], + main = module["root_source"], + deps = json.encode(deps), + )) + return "\n".join(chunks) + def _configure(repository_ctx, zig, build_zig): """Configure the package's `build.zig` and return its module-graph JSON.""" configurer = repository_ctx.path(Label("//zig/private:configurer.zig")) @@ -132,16 +172,13 @@ def _zig_package_impl(repository_ctx): repository_ctx.delete(cache) build_zig = repository_ctx.path("build.zig") + modules = [] if build_zig.exists: - repository_ctx.file("module_manifest.json", _configure(repository_ctx, zig, build_zig)) + manifest = _configure(repository_ctx, zig, build_zig) + repository_ctx.file("module_manifest.json", manifest) + modules = json.decode(manifest)["modules"] - repository_ctx.file("BUILD.bazel", """\ -filegroup( - name = "files", - srcs = glob(["**"], exclude = ["BUILD.bazel", "module_manifest.json"]), - visibility = ["//visibility:public"], -) -""") + repository_ctx.file("BUILD.bazel", _build_file(repository_ctx, modules)) zig_package = repository_rule( _zig_package_impl, From 38cf49d485ca19221bf5a32972faadc73d067d73 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 18:27:03 +0200 Subject: [PATCH 24/77] generate Zig package deps for URL deps --- zig/private/bzlmod/zig_packages.bzl | 32 ++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 2796ef02..599fd44d 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -110,7 +110,6 @@ def _zig_packages_impl(module_ctx): # `graph["packages"]` is topologically ordered, so each dependency's closure # is already known by the time we reach a package: accumulate in one pass. closures = {} - package_labels = {} for key, package in graph["packages"].items(): closure = {} for _name, child in _url_edges(graph, key): @@ -126,15 +125,12 @@ def _zig_packages_impl(module_ctx): deps = json.encode(_deps_data(graph, key, closures[key])), dep_build_files = {dep: "@{}//:build.zig".format(dep) for dep in closures[key]}, ) - package_labels[key] = "@{}//:files".format(key) - - for tag in root_tags: - package_labels[str(tag)] = tag.same_package_label("files") + manifests, targets = _hub_data(graph, root_tags) zig_deps_hub( name = "zig_deps", - manifests = json.encode(_hub_manifests(graph, root_tags)), - packages = package_labels, + manifests = json.encode(manifests), + packages = targets, ) direct = ["zig_deps"] if root_nondev else [] @@ -144,16 +140,32 @@ def _zig_packages_impl(module_ctx): root_module_direct_dev_deps = dev, ) -def _hub_manifests(graph, root_tags): +def _hub_data(graph, root_tags): + """Build the hub manifests and the dependency target map. + + A URL dependency named `name` resolves to the module of the same name in its + spoke; a path dependency resolves to the local package's `files` for now. + """ + path_files = {str(tag): tag.same_package_label("files") for tag in root_tags} manifests = [] + targets = {} for root, label in zip(graph["roots"], root_tags): + deps = {} + for name, key in root["deps"].items(): + if graph["packages"][key]["url"] != None: + target = "@{}//:{}".format(key, name) + targets[target] = target + else: + target = str(path_files[key]) + targets[target] = path_files[key] + deps[name] = target manifests.append({ "repo": label.repo_name, "package": label.package, "scope": str(label.same_package_label("__subpackages__")), - "deps": root["deps"], + "deps": deps, }) - return manifests + return manifests, targets zig_packages = module_extension( implementation = _zig_packages_impl, From eb7c0172b4cd0859151f7cfa77e04312da848945 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 20:31:17 +0200 Subject: [PATCH 25/77] handle local path dependencies --- .../zig-package-import/greet/BUILD.bazel | 16 ++++++++++++---- zig/private/bzlmod/zig_packages.bzl | 10 ++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/e2e/workspace/zig-package-import/greet/BUILD.bazel b/e2e/workspace/zig-package-import/greet/BUILD.bazel index 6307adfb..bd7214ea 100644 --- a/e2e/workspace/zig-package-import/greet/BUILD.bazel +++ b/e2e/workspace/zig-package-import/greet/BUILD.bazel @@ -1,8 +1,16 @@ -filegroup( - name = "files", +load("@rules_zig//zig:defs.bzl", "zig_library") +load("@zig_deps//:defs.bzl", "zig_dep") + +# A local path dependency must expose a module named after the dependency so +# that dependents can resolve it through the hub. +zig_library( + name = "greet", srcs = glob( - ["**"], - exclude = ["BUILD.bazel"], + ["src/**/*.zig"], + exclude = ["src/greet.zig"], ), + import_name = "greet", + main = "src/greet.zig", visibility = ["//visibility:public"], + deps = [zig_dep("clap")], ) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 599fd44d..f5c2f4c8 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -144,9 +144,10 @@ def _hub_data(graph, root_tags): """Build the hub manifests and the dependency target map. A URL dependency named `name` resolves to the module of the same name in its - spoke; a path dependency resolves to the local package's `files` for now. + spoke. A local path dependency resolves, by convention, to a target of the + same name in the dependency manifest's own package, which the user provides. """ - path_files = {str(tag): tag.same_package_label("files") for tag in root_tags} + tag_by_key = {str(tag): tag for tag in root_tags} manifests = [] targets = {} for root, label in zip(graph["roots"], root_tags): @@ -156,8 +157,9 @@ def _hub_data(graph, root_tags): target = "@{}//:{}".format(key, name) targets[target] = target else: - target = str(path_files[key]) - targets[target] = path_files[key] + module = tag_by_key[key].same_package_label(name) + target = str(module) + targets[target] = module deps[name] = target manifests.append({ "repo": label.repo_name, From 024d06e5e9f5153a06eac6305c183057f9c39957 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 12 Jun 2026 20:38:34 +0200 Subject: [PATCH 26/77] build app with zig_deps --- e2e/workspace/zig-package-import/app/BUILD.bazel | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/e2e/workspace/zig-package-import/app/BUILD.bazel b/e2e/workspace/zig-package-import/app/BUILD.bazel index ecd7ee99..06f20237 100644 --- a/e2e/workspace/zig-package-import/app/BUILD.bazel +++ b/e2e/workspace/zig-package-import/app/BUILD.bazel @@ -1,6 +1,12 @@ +load("@rules_zig//zig:defs.bzl", "zig_binary") load("@zig_deps//:defs.bzl", "zig_deps") -filegroup( - name = "files", - srcs = zig_deps(), +zig_binary( + name = "app", + srcs = glob( + ["src/**/*.zig"], + exclude = ["src/main.zig"], + ), + main = "src/main.zig", + deps = zig_deps(), ) From 30ae0d62caece6e0149d16463433b44ed6c17f51 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Sat, 13 Jun 2026 12:43:12 +0200 Subject: [PATCH 27/77] write a Zig package tarball for integration tests --- zig/tests/integration_tests/pack.zig | 204 +++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 zig/tests/integration_tests/pack.zig diff --git a/zig/tests/integration_tests/pack.zig b/zig/tests/integration_tests/pack.zig new file mode 100644 index 00000000..c8fe0af8 --- /dev/null +++ b/zig/tests/integration_tests/pack.zig @@ -0,0 +1,204 @@ +//! Pack a Zig package directory into a tarball and print its package hash. +//! +//! Usage: pack +//! +//! Writes `` (the package rooted under its name) and prints the +//! package hash on stdout, so test fixtures can be referenced by `.url` + +//! `.hash` without invoking `zig fetch`. +//! +//! The hash replicates the Zig package manager (`src/Package/Fetch.zig` +//! `computeHash` + `Package.Hash.init`): per file `sha256(rel_path ++ {0,0} ++ +//! content)`, combined by `sha256` over the files sorted by path, formatted as +//! `name-version-base64url(LE id ++ LE size ++ digest[0..25])`. Pinned to the +//! toolchain Zig version. + +const std = @import("std"); +const Io = std.Io; +const Allocator = std.mem.Allocator; +const Zoir = std.zig.Zoir; +const Sha256 = std.crypto.hash.sha2.Sha256; + +const Manifest = struct { + name: []const u8, + version: []const u8, + id: u32, + paths: []const []const u8, +}; + +const Entry = struct { + path: []const u8, + digest: [Sha256.digest_length]u8 = undefined, + size: u64 = 0, + + fn lessThan(_: void, a: Entry, b: Entry) bool { + return std.mem.lessThan(u8, a.path, b.path); + } +}; + +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + + const args = try init.minimal.args.toSlice(arena); + if (args.len != 3) fatal("usage: pack ", .{}); + + var pkg_dir = try Io.Dir.cwd().openDir(io, args[1], .{ .iterate = true }); + defer pkg_dir.close(io); + + const manifest = try parseManifest(arena, io, pkg_dir); + + var entries: std.ArrayList(Entry) = .empty; + var walker = try pkg_dir.walk(arena); + while (try walker.next(io)) |entry| { + if (entry.kind != .file) { + if (entry.kind == .directory) continue; + fatal("unsupported file kind '{t}' at '{s}'", .{ entry.kind, entry.path }); + } + const path = try arena.dupe(u8, entry.path); + if (!includePath(manifest.paths, path)) continue; + try entries.append(arena, .{ .path = path }); + } + + for (entries.items) |*entry| { + const content = try pkg_dir.readFileAlloc(io, entry.path, arena, .unlimited); + var hasher: Sha256 = .init(.{}); + hasher.update(entry.path); + // Hard-coded non-executable bit, matching Zig's `computeHash`. + hasher.update(&.{ 0, 0 }); + hasher.update(content); + hasher.final(&entry.digest); + entry.size = content.len; + } + + std.mem.sortUnstable(Entry, entries.items, {}, Entry.lessThan); + + var combined: Sha256 = .init(.{}); + var total: u64 = 0; + for (entries.items) |entry| { + combined.update(&entry.digest); + total += entry.size; + } + var digest: [Sha256.digest_length]u8 = undefined; + combined.final(&digest); + + try writeTar(io, pkg_dir, manifest.name, entries.items, args[2]); + + const size: u32 = std.math.cast(u32, total) orelse std.math.maxInt(u32); + const hash = formatHash(arena, manifest, digest, size); + + var stdout_buffer: [256]u8 = undefined; + var stdout = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); + try stdout.interface.writeAll(hash); + try stdout.interface.writeByte('\n'); + try stdout.interface.flush(); +} + +fn formatHash(arena: Allocator, manifest: Manifest, digest: [Sha256.digest_length]u8, size: u32) []const u8 { + var hashplus: [33]u8 = undefined; + std.mem.writeInt(u32, hashplus[0..4], manifest.id, .little); + std.mem.writeInt(u32, hashplus[4..8], size, .little); + hashplus[8..33].* = digest[0..25].*; + + var encoded: [44]u8 = undefined; + _ = std.base64.url_safe_no_pad.Encoder.encode(&encoded, &hashplus); + + return std.fmt.allocPrint(arena, "{s}-{s}-{s}", .{ manifest.name, manifest.version, encoded }) catch fatal("out of memory", .{}); +} + +fn includePath(paths: []const []const u8, sub_path: []const u8) bool { + for (paths) |path| { + if (path.len == 0 or std.mem.eql(u8, path, ".")) return true; + if (std.mem.eql(u8, path, sub_path)) return true; + } + var dirname = sub_path; + while (std.fs.path.dirname(dirname)) |parent| : (dirname = parent) { + for (paths) |path| { + if (std.mem.eql(u8, path, parent)) return true; + } + } + return false; +} + +fn writeTar(io: Io, pkg_dir: Io.Dir, root: []const u8, entries: []const Entry, out_path: []const u8) !void { + var out_file = try Io.Dir.cwd().createFile(io, out_path, .{}); + defer out_file.close(io); + + var out_buffer: [64 * 1024]u8 = undefined; + var out_writer = out_file.writer(io, &out_buffer); + + var tar: std.tar.Writer = .{ .underlying_writer = &out_writer.interface }; + try tar.setRoot(root); + for (entries) |entry| { + const content = try pkg_dir.readFileAlloc(io, entry.path, std.heap.page_allocator, .unlimited); + defer std.heap.page_allocator.free(content); + try tar.writeFileBytes(entry.path, content, .{}); + } + try tar.finishPedantically(); + try out_writer.interface.flush(); +} + +fn parseManifest(arena: Allocator, io: Io, pkg_dir: Io.Dir) !Manifest { + const source = try pkg_dir.readFileAllocOptions(io, "build.zig.zon", arena, .unlimited, .of(u8), 0); + + const ast = try std.zig.Ast.parse(arena, source, .zon); + const zoir = try std.zig.ZonGen.generate(arena, ast, .{}); + if (zoir.compile_errors.len != 0) fatal("invalid 'build.zig.zon'", .{}); + + var name: []const u8 = ""; + var version: []const u8 = ""; + var id: u32 = 0; + var paths: std.ArrayList([]const u8) = .empty; + + switch (Zoir.Node.Index.root.get(zoir)) { + .struct_literal => |fields| for (fields.names, 0..) |field_name, i| { + const field = field_name.get(zoir); + const value = fields.vals.at(@intCast(i)); + if (std.mem.eql(u8, field, "name")) { + name = enumLiteral(zoir, value); + } else if (std.mem.eql(u8, field, "version")) { + version = stringOf(zoir, value); + } else if (std.mem.eql(u8, field, "fingerprint")) { + id = @truncate(intOf(zoir, value)); + } else if (std.mem.eql(u8, field, "paths")) { + switch (value.get(zoir)) { + .array_literal => |elements| for (0..elements.len) |j| { + try paths.append(arena, stringOf(zoir, elements.at(@intCast(j)))); + }, + else => {}, + } + } + }, + else => fatal("'build.zig.zon' does not contain a struct", .{}), + } + + return .{ .name = name, .version = version, .id = id, .paths = paths.items }; +} + +fn enumLiteral(zoir: Zoir, index: Zoir.Node.Index) []const u8 { + return switch (index.get(zoir)) { + .enum_literal => |literal| std.mem.sliceTo(zoir.string_bytes[@intFromEnum(literal)..], 0), + else => "", + }; +} + +fn stringOf(zoir: Zoir, index: Zoir.Node.Index) []const u8 { + return switch (index.get(zoir)) { + .string_literal => |string| string, + else => "", + }; +} + +fn intOf(zoir: Zoir, index: Zoir.Node.Index) u64 { + return switch (index.get(zoir)) { + .int_literal => |int| switch (int) { + .small => |small| @bitCast(@as(i64, small)), + .big => |big| big.toInt(u64) catch 0, + }, + else => 0, + }; +} + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.debug.print("pack: " ++ format ++ "\n", args); + std.process.exit(1); +} From ac684483d2f0fdaa67dde883def1f32dfa87c931 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Sat, 13 Jun 2026 16:39:53 +0200 Subject: [PATCH 28/77] zig package deps integration test --- .bazelrc | 4 +- zig/BUILD.bazel | 2 + zig/private/BUILD.bazel | 3 + zig/private/bzlmod/BUILD.bazel | 7 + zig/private/bzlmod/zig_packages.bzl | 16 +- zig/private/repo/BUILD.bazel | 14 ++ zig/private/repo/zig_host_toolchain.bzl | 10 +- zig/private/repo/zig_package.bzl | 21 +-- zig/tests/integration_tests/BUILD.bazel | 24 +++ .../integration_tests/integration_testing.zig | 41 +++++ zig/tests/integration_tests/packages/.bazelrc | 1 + .../integration_tests/packages/BUILD.bazel | 8 + .../integration_tests/packages/MODULE.bazel | 22 +++ .../integration_tests/packages/WORKSPACE | 0 .../integration_tests/packages/build.zig | 3 + .../integration_tests/packages/build.zig.zon | 15 ++ .../packages/extra-versions.json | 148 ++++++++++++++++++ .../packages/fixtures/leaf/build.zig | 3 + .../packages/fixtures/leaf/build.zig.zon | 10 ++ .../packages/fixtures/leaf/src/leaf.zig | 1 + zig/tests/integration_tests/packages/main.zig | 5 + .../packages/tools/BUILD.bazel | 7 + .../{ => packages/tools}/pack.zig | 7 +- .../packages_tests_runner.zig | 34 ++++ 24 files changed, 381 insertions(+), 25 deletions(-) create mode 100644 zig/tests/integration_tests/packages/.bazelrc create mode 100644 zig/tests/integration_tests/packages/BUILD.bazel create mode 100644 zig/tests/integration_tests/packages/MODULE.bazel create mode 100644 zig/tests/integration_tests/packages/WORKSPACE create mode 100644 zig/tests/integration_tests/packages/build.zig create mode 100644 zig/tests/integration_tests/packages/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/extra-versions.json create mode 100644 zig/tests/integration_tests/packages/fixtures/leaf/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/leaf/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/leaf/src/leaf.zig create mode 100644 zig/tests/integration_tests/packages/main.zig create mode 100644 zig/tests/integration_tests/packages/tools/BUILD.bazel rename zig/tests/integration_tests/{ => packages/tools}/pack.zig (97%) create mode 100644 zig/tests/integration_tests/packages_tests_runner.zig diff --git a/.bazelrc b/.bazelrc index 117762c5..78967a3c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -18,8 +18,8 @@ build --workspace_status_command=$(pwd)/workspace_status.sh # To update these lines, execute # `bazel run @rules_bazel_integration_test//tools:update_deleted_packages` # docs: https://bazel.build/reference/command-line-reference#flag--deleted_packages -build --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles -query --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles +build --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles +query --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles # Load any settings specific to the current user. # Place settings that should affect the integration tests into `.bazelrc.ic.user`. diff --git a/zig/BUILD.bazel b/zig/BUILD.bazel index 6e77cd79..2abc8fe7 100644 --- a/zig/BUILD.bazel +++ b/zig/BUILD.bazel @@ -6,6 +6,7 @@ exports_files( [ "defs.bzl", "extensions.bzl", + "packages.bzl", "toolchain.bzl", ], visibility = ["//docs:__pkg__"], @@ -82,6 +83,7 @@ filegroup( ":BUILD.bazel", ":defs.bzl", ":extensions.bzl", + ":packages.bzl", ":toolchain.bzl", "//zig/config:all_files", "//zig/lib:all_files", diff --git a/zig/private/BUILD.bazel b/zig/private/BUILD.bazel index 36ee7fce..2b0cfd78 100644 --- a/zig/private/BUILD.bazel +++ b/zig/private/BUILD.bazel @@ -175,6 +175,8 @@ filegroup( ":BUILD.bazel", ":cc_helper.bzl", ":cc_linkopts.bzl", + ":configurer.zig", + ":package_prefix.zig", ":platforms.bzl", ":resolved_target_toolchain.bzl", ":resolved_toolchain.bzl", @@ -192,6 +194,7 @@ filegroup( ":zig_test.bzl", ":zig_toolchain.bzl", ":zig_toolchain_header.bzl", + ":zon2json.zig", "//zig/private/bzlmod:all_files", "//zig/private/common:all_files", "//zig/private/providers:all_files", diff --git a/zig/private/bzlmod/BUILD.bazel b/zig/private/bzlmod/BUILD.bazel index ef90ceb9..1f937e96 100644 --- a/zig/private/bzlmod/BUILD.bazel +++ b/zig/private/bzlmod/BUILD.bazel @@ -17,6 +17,12 @@ bzl_library( visibility = ["//zig:__subpackages__"], ) +bzl_library( + name = "zig_packages", + srcs = ["zig_packages.bzl"], + visibility = ["//zig:__subpackages__"], +) + # Execute `bazel run //util:update_filegroups` to update this target. filegroup( name = "all_files", @@ -24,6 +30,7 @@ filegroup( ":BUILD.bazel", ":cc_common_link.bzl", ":zig.bzl", + ":zig_packages.bzl", ], visibility = ["//zig/private:__pkg__"], ) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index f5c2f4c8..fbeee364 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -1,6 +1,6 @@ """Implementation of the `zig_packages` module extension.""" -load("@rules_zig_host_toolchain//:toolchain.bzl", "zig_path") +load("@rules_zig_host_toolchain//:toolchain.bzl", "zig_cache", "zig_path") load("//zig/private/repo:zig_deps_hub.bzl", "zig_deps_hub") load("//zig/private/repo:zig_package.bzl", "zig_package") @@ -14,17 +14,17 @@ from_file = tag_class( }, ) -def _fetch(module_ctx, zig, manifest, cache_dir, pkg_dir): +def _fetch(module_ctx, zig, manifest, cache, pkg_dir): result = module_ctx.execute( - [zig, "build", "--fetch=all", "--cache-dir", str(cache_dir), "--pkg-dir", str(pkg_dir)], + [zig, "build", "--fetch=all", "--cache-dir", cache, "--global-cache-dir", cache, "--pkg-dir", str(pkg_dir)], working_directory = str(manifest.dirname), ) if result.return_code != 0: fail("`zig build --fetch=all` failed in {}:\n{}".format(manifest.dirname, result.stderr)) -def _resolve_graph(module_ctx, zig, zon2json, cache_dir, pkg_dir, manifests): +def _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests): result = module_ctx.execute( - [zig, "run", "--cache-dir", str(cache_dir), zon2json, "--", str(pkg_dir)] + + [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", str(pkg_dir)] + [str(manifest) for manifest in manifests], ) if result.return_code != 0: @@ -83,7 +83,7 @@ def _deps_data(graph, key, closure): def _zig_packages_impl(module_ctx): zig = zig_path(module_ctx) zon2json = module_ctx.path(Label("//zig/private:zon2json.zig")) - cache_dir = module_ctx.path("cache") + cache = zig_cache(module_ctx) pkg_dir = module_ctx.path("pkg") manifests = [] @@ -94,7 +94,7 @@ def _zig_packages_impl(module_ctx): for mod in module_ctx.modules: for tag in mod.tags.from_file: manifest = module_ctx.path(tag.build_zig_zon) - _fetch(module_ctx, zig, manifest, cache_dir, pkg_dir) + _fetch(module_ctx, zig, manifest, cache, pkg_dir) manifests.append(manifest) manifest_labels[str(manifest)] = str(tag.build_zig_zon) root_tags.append(tag.build_zig_zon) @@ -104,7 +104,7 @@ def _zig_packages_impl(module_ctx): else: root_nondev = True - graph = _resolve_graph(module_ctx, zig, zon2json, cache_dir, pkg_dir, manifests) + graph = _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests) graph = _localize_paths(graph, str(pkg_dir), manifest_labels) # `graph["packages"]` is topologically ordered, so each dependency's closure diff --git a/zig/private/repo/BUILD.bazel b/zig/private/repo/BUILD.bazel index 150d3ed8..ee686664 100644 --- a/zig/private/repo/BUILD.bazel +++ b/zig/private/repo/BUILD.bazel @@ -20,13 +20,27 @@ bzl_library( ], ) +bzl_library( + name = "zig_package", + srcs = ["zig_package.bzl"], + visibility = ["//zig:__subpackages__"], +) + +bzl_library( + name = "zig_deps_hub", + srcs = ["zig_deps_hub.bzl"], + visibility = ["//zig:__subpackages__"], +) + # Execute `bazel run //util:update_filegroups` to update this target. filegroup( name = "all_files", srcs = [ ":BUILD.bazel", ":toolchains_repo.bzl", + ":zig_deps_hub.bzl", ":zig_host_toolchain.bzl", + ":zig_package.bzl", ":zig_repository.bzl", ], visibility = ["//zig/private:__pkg__"], diff --git a/zig/private/repo/zig_host_toolchain.bzl b/zig/private/repo/zig_host_toolchain.bzl index 1a641797..82969315 100644 --- a/zig/private/repo/zig_host_toolchain.bzl +++ b/zig/private/repo/zig_host_toolchain.bzl @@ -79,11 +79,17 @@ bzl_library( repository_ctx.file("toolchain.bzl", """\ # Generated by zig_host_toolchain.bzl -_ZIG = Label({}) +load("@rules_zig//zig/private/common:zig_cache.bzl", "env_zig_cache_prefix") + +_ZIG = Label({zig}) +_PLATFORM = {platform} def zig_path(ctx): return ctx.path(_ZIG) -""".format(repr(str(zig)))) + +def zig_cache(ctx): + return env_zig_cache_prefix(ctx.os.environ, _PLATFORM) +""".format(zig = repr(str(zig)), platform = repr(platform))) zig_host_toolchain = repository_rule( _zig_host_toolchain_impl, diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 9ace50ef..200889fe 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -1,6 +1,6 @@ """Implementation of the `zig_package` repository rule.""" -load("@rules_zig_host_toolchain//:toolchain.bzl", "zig_path") +load("@rules_zig_host_toolchain//:toolchain.bzl", "zig_cache", "zig_path") DOC = """\ Fetch a Zig package with the Zig SDK. @@ -26,7 +26,9 @@ ATTRS = { def _package_prefix(repository_ctx, zig, helper, cache, archive): # The archive nests the package under `//`; strip up to # the directory that holds `build.zig.zon`. - result = repository_ctx.execute([zig, "run", "--cache-dir", str(cache), helper, "--", str(archive)]) + result = repository_ctx.execute( + [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, helper, "--", str(archive)], + ) if result.return_code != 0: fail("Failed to inspect the Zig package archive '{}':\n{}".format(archive, result.stderr)) return result.stdout.strip() @@ -111,7 +113,7 @@ def _build_file(repository_ctx, modules): )) return "\n".join(chunks) -def _configure(repository_ctx, zig, build_zig): +def _configure(repository_ctx, zig, build_zig, cache): """Configure the package's `build.zig` and return its module-graph JSON.""" configurer = repository_ctx.path(Label("//zig/private:configurer.zig")) deps = json.decode(repository_ctx.attr.deps) @@ -129,7 +131,9 @@ def _configure(repository_ctx, zig, build_zig): args.append("-M{}={}".format(key, repository_ctx.path(build_files[key]))) args.extend([ "--cache-dir", - str(repository_ctx.path("_configure/cache")), + cache, + "--global-cache-dir", + cache, "-femit-bin=" + str(repository_ctx.path("_configure/configurer")), ]) @@ -153,9 +157,9 @@ def _configure(repository_ctx, zig, build_zig): def _zig_package_impl(repository_ctx): zig = zig_path(repository_ctx) helper = repository_ctx.path(Label("//zig/private:package_prefix.zig")) - cache = repository_ctx.path("cache") + cache = zig_cache(repository_ctx) - fetch = repository_ctx.execute([zig, "fetch", "--global-cache-dir", str(cache), repository_ctx.attr.url]) + fetch = repository_ctx.execute([zig, "fetch", "--global-cache-dir", cache, repository_ctx.attr.url]) if fetch.return_code != 0: fail("`zig fetch {}` failed:\n{}".format(repository_ctx.attr.url, fetch.stderr)) @@ -167,14 +171,13 @@ def _zig_package_impl(repository_ctx): fetched_hash, )) - archive = cache.get_child("p").get_child(fetched_hash + ".tar.gz") + archive = repository_ctx.path(cache).get_child("p").get_child(fetched_hash + ".tar.gz") repository_ctx.extract(archive, strip_prefix = _package_prefix(repository_ctx, zig, helper, cache, archive)) - repository_ctx.delete(cache) build_zig = repository_ctx.path("build.zig") modules = [] if build_zig.exists: - manifest = _configure(repository_ctx, zig, build_zig) + manifest = _configure(repository_ctx, zig, build_zig, cache) repository_ctx.file("module_manifest.json", manifest) modules = json.decode(manifest)["modules"] diff --git a/zig/tests/integration_tests/BUILD.bazel b/zig/tests/integration_tests/BUILD.bazel index 5609d5c4..106c0d8f 100644 --- a/zig/tests/integration_tests/BUILD.bazel +++ b/zig/tests/integration_tests/BUILD.bazel @@ -149,6 +149,29 @@ bazel_integration_tests( workspace_path = "mirrors", ) +packages_files = integration_test_utils.glob_workspace_files("packages") + [ + "//:all_files", + "//:bazelrc", + ".bazelrc.meta", +] + +zig_test( + name = "packages_tests_runner", + main = "packages_tests_runner.zig", + tags = ["manual"], + deps = [":integration_testing"], +) + +bazel_integration_test( + name = "packages_test", + size = "large", + bazel_version = bazel_binaries.versions.current, + tags = ["requires-network"] + integration_test_utils.DEFAULT_INTEGRATION_TEST_TAGS, + test_runner = ":packages_tests_runner", + workspace_files = packages_files, + workspace_path = "packages", +) + test_suite( name = "integration_tests", tags = integration_test_utils.DEFAULT_INTEGRATION_TEST_TAGS, @@ -162,6 +185,7 @@ test_suite( "bzlmod_test", bazel_binaries.versions.all, ) + [ + ":packages_test", ":zig_version_tests", ], ) diff --git a/zig/tests/integration_tests/integration_testing.zig b/zig/tests/integration_tests/integration_testing.zig index 8853247f..04c0afef 100644 --- a/zig/tests/integration_tests/integration_testing.zig +++ b/zig/tests/integration_tests/integration_testing.zig @@ -200,6 +200,47 @@ pub const BitContext = struct { return true; } + pub const writeWorkspaceFile = if (is_zig_0_16_or_later) writeWorkspaceFile_016 else writeWorkspaceFile_pre_016; + + fn writeWorkspaceFile_pre_016(self: BitContext, sub_path: []const u8, content: []const u8) !void { + var workspace = try self.openWorkspace(); + defer closeWorkspaceDir(&workspace); + workspace.deleteFile(sub_path) catch {}; + var file = try workspace.createFile(sub_path, .{}); + defer file.close(); + try file.writeAll(content); + } + + fn writeWorkspaceFile_016(self: BitContext, sub_path: []const u8, content: []const u8) !void { + var workspace = try self.openWorkspace(); + defer closeWorkspaceDir(&workspace); + workspace.deleteFile(std.testing.io, sub_path) catch {}; + var file = try workspace.createFile(std.testing.io, sub_path, .{}); + defer file.close(std.testing.io); + var buffer: [4096]u8 = undefined; + var writer = file.writer(std.testing.io, &buffer); + try writer.interface.writeAll(content); + try writer.interface.flush(); + } + + /// Replace each `needle` with its `replacement` in a workspace file, writing + /// a fresh file so the original source (a symlink in the test sandbox) is + /// never modified. + pub fn patchWorkspaceFile( + self: BitContext, + sub_path: []const u8, + replacements: []const [2][]const u8, + ) !void { + var content = try self.readWorkspaceFileAlloc(sub_path, 4 * 1024 * 1024); + for (replacements) |replacement| { + const patched = try std.mem.replaceOwned(u8, std.testing.allocator, content, replacement[0], replacement[1]); + std.testing.allocator.free(content); + content = patched; + } + defer std.testing.allocator.free(content); + try self.writeWorkspaceFile(sub_path, content); + } + pub const BazelResult = struct { success: bool, term: Term, diff --git a/zig/tests/integration_tests/packages/.bazelrc b/zig/tests/integration_tests/packages/.bazelrc new file mode 100644 index 00000000..15fc888e --- /dev/null +++ b/zig/tests/integration_tests/packages/.bazelrc @@ -0,0 +1 @@ +import %workspace%/../../../../.bazelrc.common diff --git a/zig/tests/integration_tests/packages/BUILD.bazel b/zig/tests/integration_tests/packages/BUILD.bazel new file mode 100644 index 00000000..8e239650 --- /dev/null +++ b/zig/tests/integration_tests/packages/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_zig//zig:defs.bzl", "zig_binary") +load("@zig_deps//:defs.bzl", "zig_dep") + +zig_binary( + name = "binary", + main = "main.zig", + deps = [zig_dep("leaf")], +) diff --git a/zig/tests/integration_tests/packages/MODULE.bazel b/zig/tests/integration_tests/packages/MODULE.bazel new file mode 100644 index 00000000..eae814f1 --- /dev/null +++ b/zig/tests/integration_tests/packages/MODULE.bazel @@ -0,0 +1,22 @@ +module(name = "packages") + +bazel_dep(name = "rules_zig", version = "0.0.0") +bazel_dep(name = "platforms", version = "0.0.6") +bazel_dep(name = "rules_cc", version = "0.1.1") + +local_path_override( + module_name = "rules_zig", + path = "../../../..", +) + +zig = use_extension("@rules_zig//zig:extensions.bzl", "zig") +zig.index(file = "extra-versions.json") +zig.toolchain( + default = True, + zig_version = "0.17.0-dev.813+2153f8143", +) +use_repo(zig, "zig_toolchains") + +zig_packages = use_extension("@rules_zig//zig:packages.bzl", "zig_packages") +zig_packages.from_file(build_zig_zon = "//:build.zig.zon") +use_repo(zig_packages, "zig_deps") diff --git a/zig/tests/integration_tests/packages/WORKSPACE b/zig/tests/integration_tests/packages/WORKSPACE new file mode 100644 index 00000000..e69de29b diff --git a/zig/tests/integration_tests/packages/build.zig b/zig/tests/integration_tests/packages/build.zig new file mode 100644 index 00000000..ab632e05 --- /dev/null +++ b/zig/tests/integration_tests/packages/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b; +} diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon new file mode 100644 index 00000000..8162bd9f --- /dev/null +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = .packages, + .version = "0.0.0", + .fingerprint = 0x9bb5c0a73538e72b, + .dependencies = .{ + .leaf = .{ + .url = "__LEAF_URL__", + .hash = "__LEAF_HASH__", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/zig/tests/integration_tests/packages/extra-versions.json b/zig/tests/integration_tests/packages/extra-versions.json new file mode 100644 index 00000000..8929cf19 --- /dev/null +++ b/zig/tests/integration_tests/packages/extra-versions.json @@ -0,0 +1,148 @@ +{ + "master": { + "version": "0.17.0-dev.813+2153f8143", + "date": "2026-06-07", + "docs": "https://ziglang.org/documentation/master/", + "stdDocs": "https://ziglang.org/documentation/master/std/", + "src": { + "tarball": "https://ziglang.org/builds/zig-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "bcb2ec46a2353620f2d90fcda1b046895ac7ba16b6b9febc9a0f9bd48f568c31", + "size": "22688996" + }, + "bootstrap": { + "tarball": "https://ziglang.org/builds/zig-bootstrap-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "d93ea88bcd934b24695de5e445f3640d8e04afc544f88dbed6fef1d91c4c5292", + "size": "56681092" + }, + "x86_64-macos": { + "tarball": "https://ziglang.org/builds/zig-x86_64-macos-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "3938c46ae4bca3c13f423b09503e3ef00bb4b7ef12b8bc1e5122ede366057a5b", + "size": "59323140" + }, + "aarch64-macos": { + "tarball": "https://ziglang.org/builds/zig-aarch64-macos-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "36673d2513afa4a96c86780648ba504beedd7f0451389091cf9d53e38d5b4840", + "size": "53999760" + }, + "x86_64-linux": { + "tarball": "https://ziglang.org/builds/zig-x86_64-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "b0d46ffc4587b9e8dd0b524ee5bc4da1e67f28bba55e7c534cec64af2f2d7a74", + "size": "57296196" + }, + "aarch64-linux": { + "tarball": "https://ziglang.org/builds/zig-aarch64-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "aa67b418d50bdde3043cfe765016d5387a2333b514ada2c57f24baae4005c331", + "size": "52949940" + }, + "arm-linux": { + "tarball": "https://ziglang.org/builds/zig-arm-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "7e365014a7520ca405b18d8690d802592199ce178d9a3bbfa526b1417edcfaa5", + "size": "53772136" + }, + "riscv64-linux": { + "tarball": "https://ziglang.org/builds/zig-riscv64-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "e2282b0784722eb3e41a41dcdea257618bc02c6b10fdd462c5f8e514b09628da", + "size": "57219904" + }, + "powerpc64le-linux": { + "tarball": "https://ziglang.org/builds/zig-powerpc64le-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "4b4ffe65aa052e1399319f4d80b5d117e6902d514d52d7bca5dfce66153474ec", + "size": "57144644" + }, + "x86-linux": { + "tarball": "https://ziglang.org/builds/zig-x86-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "cb14dab396b988221e02c4736680a0e166048d5592fafc4ba2fde362d3bc78b3", + "size": "59897932" + }, + "loongarch64-linux": { + "tarball": "https://ziglang.org/builds/zig-loongarch64-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "9a078bfac64a0d51752de87eb31348d33035451043f9a03546bf57d05c070a43", + "size": "54315620" + }, + "s390x-linux": { + "tarball": "https://ziglang.org/builds/zig-s390x-linux-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "c631cf79d8a405edf9d941c621131ac262d4468566cdbf1442087f764df46eb7", + "size": "57127508" + }, + "x86_64-windows": { + "tarball": "https://ziglang.org/builds/zig-x86_64-windows-0.17.0-dev.813+2153f8143.zip", + "shasum": "2a8f1a34402076ab7931e4535bd379b20c83fc263d1387cb3f70cb2e397f9ebe", + "size": "101062660" + }, + "aarch64-windows": { + "tarball": "https://ziglang.org/builds/zig-aarch64-windows-0.17.0-dev.813+2153f8143.zip", + "shasum": "c335026c4b666a995ac2f4d5481f74f7f9a455dd7ab3620b3e04779d3f6055a8", + "size": "96847518" + }, + "x86-windows": { + "tarball": "https://ziglang.org/builds/zig-x86-windows-0.17.0-dev.813+2153f8143.zip", + "shasum": "f341544a7263651616c7ce292e23a257c85f892ac72c0eeea9a7f0c6e93351da", + "size": "102769164" + }, + "aarch64-freebsd": { + "tarball": "https://ziglang.org/builds/zig-aarch64-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "aa0da8ed92903cb7e32b18bccd4d6d5770192105749af6053ca5faafe6afdfbe", + "size": "52893704" + }, + "arm-freebsd": { + "tarball": "https://ziglang.org/builds/zig-arm-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "60b75fa5e895e9ef01b9a745495bd1ed48078a7a8bebd546278bda82be27dc1d", + "size": "54464888" + }, + "powerpc64le-freebsd": { + "tarball": "https://ziglang.org/builds/zig-powerpc64le-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "22c735fb40f8d0ad7342660a6a8a405615f27b66432dd5021efca1267b51191c", + "size": "57177396" + }, + "riscv64-freebsd": { + "tarball": "https://ziglang.org/builds/zig-riscv64-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "0876f2330308279466a6d86630d3510f0498d94e11238c5653bee7532e6f078d", + "size": "57367100" + }, + "x86_64-freebsd": { + "tarball": "https://ziglang.org/builds/zig-x86_64-freebsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "a75220898fe9f403d24e9712ef56fa7cf622ddb96be21f2d2b7f01535a4f26a9", + "size": "57464308" + }, + "aarch64-netbsd": { + "tarball": "https://ziglang.org/builds/zig-aarch64-netbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "c004ad712df83a2eed7a0724fd582a08bb09eaee262996c4e6b82680a22d7b6f", + "size": "52847860" + }, + "arm-netbsd": { + "tarball": "https://ziglang.org/builds/zig-arm-netbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "611fdc70b82e6bf88053a72efba73a788962ef0bd9008336e0202bb43eb67967", + "size": "55526764" + }, + "x86-netbsd": { + "tarball": "https://ziglang.org/builds/zig-x86-netbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "1296c141a8603a0911547e60532c63638cf5dd859ab4acf2ead33a925d64776b", + "size": "60476932" + }, + "x86_64-netbsd": { + "tarball": "https://ziglang.org/builds/zig-x86_64-netbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "bd88fad25ef1251d74a8051c30b886256c8b0f0292b4209b1013e925b54cd314", + "size": "57360376" + }, + "aarch64-openbsd": { + "tarball": "https://ziglang.org/builds/zig-aarch64-openbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "7730b3b2bc98317c154fda750a74790a3e6e79526a868a9bc04d63a9bb71c79e", + "size": "53347332" + }, + "arm-openbsd": { + "tarball": "https://ziglang.org/builds/zig-arm-openbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "d067209109995e5311c2f196f75fec59124d55aa140b5727c9b2eb386f46cf6a", + "size": "53971816" + }, + "riscv64-openbsd": { + "tarball": "https://ziglang.org/builds/zig-riscv64-openbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "32a6df5e4e0a3647ac4354b0a7f103a6bab9d0c74225ec5c0e0d9aea0f4d5d74", + "size": "57655868" + }, + "x86_64-openbsd": { + "tarball": "https://ziglang.org/builds/zig-x86_64-openbsd-0.17.0-dev.813+2153f8143.tar.xz", + "shasum": "fb173bd2caff90b7483a513f7759181de0b4f60d87698f89bc7caf8150a00e0a", + "size": "58620540" + } + } +} diff --git a/zig/tests/integration_tests/packages/fixtures/leaf/build.zig b/zig/tests/integration_tests/packages/fixtures/leaf/build.zig new file mode 100644 index 00000000..73d3aa47 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/leaf/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("leaf", .{ .root_source_file = b.path("src/leaf.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/leaf/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/leaf/build.zig.zon new file mode 100644 index 00000000..a3a6049c --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/leaf/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .leaf, + .version = "0.0.0", + .fingerprint = 0xc69f00e73c05ec58, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/leaf/src/leaf.zig b/zig/tests/integration_tests/packages/fixtures/leaf/src/leaf.zig new file mode 100644 index 00000000..ef1c4a67 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/leaf/src/leaf.zig @@ -0,0 +1 @@ +pub const value: u32 = 7; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig new file mode 100644 index 00000000..7a965864 --- /dev/null +++ b/zig/tests/integration_tests/packages/main.zig @@ -0,0 +1,5 @@ +const leaf = @import("leaf"); + +pub fn main() void { + _ = leaf.value; +} diff --git a/zig/tests/integration_tests/packages/tools/BUILD.bazel b/zig/tests/integration_tests/packages/tools/BUILD.bazel new file mode 100644 index 00000000..9679bba1 --- /dev/null +++ b/zig/tests/integration_tests/packages/tools/BUILD.bazel @@ -0,0 +1,7 @@ +load("@rules_zig//zig:defs.bzl", "zig_binary") + +zig_binary( + name = "pack", + main = "pack.zig", + visibility = ["//visibility:public"], +) diff --git a/zig/tests/integration_tests/pack.zig b/zig/tests/integration_tests/packages/tools/pack.zig similarity index 97% rename from zig/tests/integration_tests/pack.zig rename to zig/tests/integration_tests/packages/tools/pack.zig index c8fe0af8..c431ae7d 100644 --- a/zig/tests/integration_tests/pack.zig +++ b/zig/tests/integration_tests/packages/tools/pack.zig @@ -50,10 +50,9 @@ pub fn main(init: std.process.Init) !void { var entries: std.ArrayList(Entry) = .empty; var walker = try pkg_dir.walk(arena); while (try walker.next(io)) |entry| { - if (entry.kind != .file) { - if (entry.kind == .directory) continue; - fatal("unsupported file kind '{t}' at '{s}'", .{ entry.kind, entry.path }); - } + // Directories are not hashed; symlinks (e.g. Bazel-staged fixture files) + // are dereferenced and treated as regular files via their content. + if (entry.kind == .directory) continue; const path = try arena.dupe(u8, entry.path); if (!includePath(manifest.paths, path)) continue; try entries.append(arena, .{ .path = path }); diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig new file mode 100644 index 00000000..be2b6483 --- /dev/null +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +const integration_testing = @import("integration_testing"); +const BitContext = integration_testing.BitContext; + +test "Zig package is imported from a file:// tarball" { + const ctx = try BitContext.init(); + defer ctx.deinit(); + const allocator = std.testing.allocator; + + const fixture = try std.fs.path.join(allocator, &.{ ctx.workspace_path, "fixtures", "leaf" }); + defer allocator.free(fixture); + const tarball = try std.fs.path.join(allocator, &.{ ctx.workspace_path, "leaf.tar" }); + defer allocator.free(tarball); + + const pack = try ctx.exec_bazel(.{ + .argv = &[_][]const u8{ "run", "//tools:pack", "--", fixture, tarball }, + }); + defer pack.deinit(); + try std.testing.expect(pack.success); + const hash = std.mem.trim(u8, pack.stdout, " \t\r\n"); + + const url = try std.fmt.allocPrint(allocator, "file://{s}", .{tarball}); + defer allocator.free(url); + try ctx.patchWorkspaceFile("build.zig.zon", &.{ + .{ "__LEAF_URL__", url }, + .{ "__LEAF_HASH__", hash }, + }); + + const result = try ctx.exec_bazel(.{ + .argv = &[_][]const u8{ "build", "//:binary" }, + }); + defer result.deinit(); + try std.testing.expect(result.success); +} From c94bf769a959cb74d725d2e708215fe4d83c974d Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Sat, 13 Jun 2026 18:15:43 +0200 Subject: [PATCH 29/77] add nested path dependency to deps integration test --- e2e/workspace/.bazelrc | 4 +- zig/private/bzlmod/zig_packages.bzl | 28 ++++---- zig/private/repo/zig_package.bzl | 68 ++++++++++++++----- .../integration_tests/packages/BUILD.bazel | 4 +- .../integration_tests/packages/build.zig.zon | 4 ++ .../packages/fixtures/host/build.zig | 7 ++ .../packages/fixtures/host/build.zig.zon | 14 ++++ .../packages/fixtures/host/libs/foo/build.zig | 3 + .../fixtures/host/libs/foo/build.zig.zon | 10 +++ .../fixtures/host/libs/foo/src/foo.zig | 1 + .../packages/fixtures/host/src/root.zig | 3 + zig/tests/integration_tests/packages/main.zig | 2 + .../packages_tests_runner.zig | 55 +++++++++------ 13 files changed, 150 insertions(+), 53 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/host/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/host/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/host/src/root.zig diff --git a/e2e/workspace/.bazelrc b/e2e/workspace/.bazelrc index 247576ea..541ebaf9 100644 --- a/e2e/workspace/.bazelrc +++ b/e2e/workspace/.bazelrc @@ -6,6 +6,6 @@ try-import %workspace%/.bazelrc.user # docs: https://bazel.build/reference/command-line-reference#flag--workspace_status_command build --workspace_status_command=$(pwd)/workspace_status.sh -# Use Zig HEAD, which includes https://codeberg.org/ziglang/zig/pulls/35428 and -# provides `--pkg-dir` so resolution does not write into the source tree. +# The importer requires the reworked Zig build system (configurer + `std` APIs), +# see https://codeberg.org/ziglang/zig/pulls/35428. common --repo_env=RULES_ZIG_HOST_SDK=0.17.0-dev.813+2153f8143 diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index fbeee364..74b15614 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -14,17 +14,9 @@ from_file = tag_class( }, ) -def _fetch(module_ctx, zig, manifest, cache, pkg_dir): - result = module_ctx.execute( - [zig, "build", "--fetch=all", "--cache-dir", cache, "--global-cache-dir", cache, "--pkg-dir", str(pkg_dir)], - working_directory = str(manifest.dirname), - ) - if result.return_code != 0: - fail("`zig build --fetch=all` failed in {}:\n{}".format(manifest.dirname, result.stderr)) - def _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests): result = module_ctx.execute( - [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", str(pkg_dir)] + + [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", zig, cache, str(pkg_dir)] + [str(manifest) for manifest in manifests], ) if result.return_code != 0: @@ -74,10 +66,23 @@ def _url_edges(graph, key): if graph["packages"][child]["url"] != None ] +def _subtree_edges(graph, key): + """Direct path dependencies that live inside this package's own fetched tree.""" + return [ + [name, child] + for name, child in graph["packages"][key]["deps"].items() + if graph["packages"][child]["url"] == None and child.startswith(key + "/") + ] + def _deps_data(graph, key, closure): + # URL dependencies resolve to sibling spokes; sub-tree path dependencies are + # configured in-tree (`path` is their location relative to this package). + packages = {dep: {"deps": _url_edges(graph, dep), "path": None} for dep in closure} + for _name, child in _subtree_edges(graph, key): + packages[child] = {"deps": [], "path": child[len(key) + 1:]} return { - "root_deps": _url_edges(graph, key), - "packages": {dep: {"has_build_zig": True, "deps": _url_edges(graph, dep)} for dep in closure}, + "root_deps": _url_edges(graph, key) + _subtree_edges(graph, key), + "packages": packages, } def _zig_packages_impl(module_ctx): @@ -94,7 +99,6 @@ def _zig_packages_impl(module_ctx): for mod in module_ctx.modules: for tag in mod.tags.from_file: manifest = module_ctx.path(tag.build_zig_zon) - _fetch(module_ctx, zig, manifest, cache, pkg_dir) manifests.append(manifest) manifest_labels[str(manifest)] = str(tag.build_zig_zon) root_tags.append(tag.build_zig_zon) diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 200889fe..ee28cf8d 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -47,21 +47,30 @@ def _edge_lines(edges, indent): for name, key in edges ] +def _build_root(repository_ctx, key, package): + # A sub-tree path dependency lives inside this package's own tree; a URL + # dependency lives in its own spoke. + if package["path"] != None: + return str(repository_ctx.path(package["path"])) + return str(repository_ctx.path(repository_ctx.attr.dep_build_files[key]).dirname) + +def _build_zig(repository_ctx, key, package): + if package["path"] != None: + return str(repository_ctx.path(package["path"] + "/build.zig")) + return str(repository_ctx.path(repository_ctx.attr.dep_build_files[key])) + def _dependencies_source(repository_ctx, deps): """Render the `@dependencies` module that `b.dependency` consumes.""" packages = deps["packages"] if not packages: return _EMPTY_DEPS - build_files = repository_ctx.attr.dep_build_files lines = ["pub const packages = struct {"] for key in sorted(packages): package = packages[key] - build_root = str(repository_ctx.path(build_files[key]).dirname) lines.append(" pub const @\"{}\" = struct {{".format(key)) - lines.append(" pub const build_root = {};".format(_zig_string(build_root))) - if package["has_build_zig"]: - lines.append(" pub const build_zig = @import(\"{}\");".format(key)) + lines.append(" pub const build_root = {};".format(_zig_string(_build_root(repository_ctx, key, package)))) + lines.append(" pub const build_zig = @import(\"{}\");".format(key)) lines.append(" pub const deps: []const struct { []const u8, []const u8 } = &.{") lines.extend(_edge_lines(package["deps"], " ")) lines.append(" };") @@ -92,43 +101,65 @@ zig_library( ) """ -def _module_dep(repository_ctx, imported): - # A same-package import resolves to a sibling module target; a cross-package - # import resolves to the module of the same name in the dependency's spoke. - if imported["package"]: - spoke = repository_ctx.attr.dep_build_files[imported["package"]] +_ZIG_LIBRARY_SUBTREE = """\ +zig_library( + name = "{name}", + main = "{main}", + import_name = "{name}", + srcs = glob(["{subpath}/**/*.zig"], exclude = ["{main}"]), + visibility = ["//visibility:public"], +) +""" + +def _module_dep(repository_ctx, imported, packages, subtree): + key = imported["package"] + if key and key in packages and packages[key]["path"] != None: + # A sub-tree path dependency is generated as a sibling module in this spoke. + subtree[imported["name"]] = (packages[key]["path"], imported["root_source"]) + return ":" + imported["name"] + if key: + # A cross-package import resolves to the module of the same name in the + # dependency's spoke. + spoke = repository_ctx.attr.dep_build_files[key] return str(spoke.same_package_label(imported["name"])) return ":" + imported["name"] -def _build_file(repository_ctx, modules): +def _build_file(repository_ctx, modules, packages): chunks = ["load(\"@rules_zig//zig:defs.bzl\", \"zig_library\")", "", _FILES] + subtree = {} for module in modules: if not module["root_source"]: continue - deps = [_module_dep(repository_ctx, imported) for imported in module["imports"]] + deps = [_module_dep(repository_ctx, imported, packages, subtree) for imported in module["imports"]] chunks.append(_ZIG_LIBRARY.format( name = module["name"], main = module["root_source"], deps = json.encode(deps), )) + for name in sorted(subtree): + subpath, root_source = subtree[name] + chunks.append(_ZIG_LIBRARY_SUBTREE.format( + name = name, + main = subpath + "/" + root_source, + subpath = subpath, + )) return "\n".join(chunks) def _configure(repository_ctx, zig, build_zig, cache): """Configure the package's `build.zig` and return its module-graph JSON.""" configurer = repository_ctx.path(Label("//zig/private:configurer.zig")) deps = json.decode(repository_ctx.attr.deps) - build_files = repository_ctx.attr.dep_build_files repository_ctx.file("_configure/deps.zig", _dependencies_source(repository_ctx, deps)) - hashes = sorted([key for key in deps["packages"] if deps["packages"][key]["has_build_zig"]]) + keys = sorted(deps["packages"]) args = [zig, "build-exe", "--dep", "pkg", "--dep", "deps", "-Mroot=" + str(configurer), "-Mpkg=" + str(build_zig)] - for key in hashes: + for key in keys: args.extend(["--dep", key]) args.append("-Mdeps=" + str(repository_ctx.path("_configure/deps.zig"))) - for key in hashes: - args.append("-M{}={}".format(key, repository_ctx.path(build_files[key]))) + for key in keys: + args.append("-M{}={}".format(key, _build_zig(repository_ctx, key, deps["packages"][key]))) args.extend([ "--cache-dir", cache, @@ -181,7 +212,8 @@ def _zig_package_impl(repository_ctx): repository_ctx.file("module_manifest.json", manifest) modules = json.decode(manifest)["modules"] - repository_ctx.file("BUILD.bazel", _build_file(repository_ctx, modules)) + packages = json.decode(repository_ctx.attr.deps)["packages"] + repository_ctx.file("BUILD.bazel", _build_file(repository_ctx, modules, packages)) zig_package = repository_rule( _zig_package_impl, diff --git a/zig/tests/integration_tests/packages/BUILD.bazel b/zig/tests/integration_tests/packages/BUILD.bazel index 8e239650..23e702b3 100644 --- a/zig/tests/integration_tests/packages/BUILD.bazel +++ b/zig/tests/integration_tests/packages/BUILD.bazel @@ -1,8 +1,8 @@ load("@rules_zig//zig:defs.bzl", "zig_binary") -load("@zig_deps//:defs.bzl", "zig_dep") +load("@zig_deps//:defs.bzl", "zig_deps") zig_binary( name = "binary", main = "main.zig", - deps = [zig_dep("leaf")], + deps = zig_deps(), ) diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 8162bd9f..c3adf742 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -7,6 +7,10 @@ .url = "__LEAF_URL__", .hash = "__LEAF_HASH__", }, + .host = .{ + .url = "__HOST_URL__", + .hash = "__HOST_HASH__", + }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/fixtures/host/build.zig b/zig/tests/integration_tests/packages/fixtures/host/build.zig new file mode 100644 index 00000000..33425712 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/build.zig @@ -0,0 +1,7 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const foo = b.dependency("foo", .{}); + const mod = b.addModule("host", .{ .root_source_file = b.path("src/root.zig") }); + mod.addImport("foo", foo.module("foo")); +} diff --git a/zig/tests/integration_tests/packages/fixtures/host/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/host/build.zig.zon new file mode 100644 index 00000000..cf615763 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/build.zig.zon @@ -0,0 +1,14 @@ +.{ + .name = .host, + .version = "0.0.0", + .fingerprint = 0xcf2713fdd0ee5aad, + .dependencies = .{ + .foo = .{ .path = "libs/foo" }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "libs", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig new file mode 100644 index 00000000..91914229 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("foo", .{ .root_source_file = b.path("src/foo.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon new file mode 100644 index 00000000..440c4e77 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .foo, + .version = "0.0.0", + .fingerprint = 0x8c736521d1c2c9ec, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig new file mode 100644 index 00000000..0be459b7 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig @@ -0,0 +1 @@ +pub const value: u32 = 42; diff --git a/zig/tests/integration_tests/packages/fixtures/host/src/root.zig b/zig/tests/integration_tests/packages/fixtures/host/src/root.zig new file mode 100644 index 00000000..fee1d4d7 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/src/root.zig @@ -0,0 +1,3 @@ +const foo = @import("foo"); + +pub const value = foo.value; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 7a965864..c100a90f 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -1,5 +1,7 @@ const leaf = @import("leaf"); +const host = @import("host"); pub fn main() void { _ = leaf.value; + _ = host.value; } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index be2b6483..5293f2da 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -2,30 +2,47 @@ const std = @import("std"); const integration_testing = @import("integration_testing"); const BitContext = integration_testing.BitContext; -test "Zig package is imported from a file:// tarball" { +const Fixture = struct { + name: []const u8, + url_placeholder: []const u8, + hash_placeholder: []const u8, +}; + +const fixtures = [_]Fixture{ + .{ .name = "leaf", .url_placeholder = "__LEAF_URL__", .hash_placeholder = "__LEAF_HASH__" }, + .{ .name = "host", .url_placeholder = "__HOST_URL__", .hash_placeholder = "__HOST_HASH__" }, +}; + +test "Zig packages are imported from file:// tarballs" { const ctx = try BitContext.init(); defer ctx.deinit(); - const allocator = std.testing.allocator; - const fixture = try std.fs.path.join(allocator, &.{ ctx.workspace_path, "fixtures", "leaf" }); - defer allocator.free(fixture); - const tarball = try std.fs.path.join(allocator, &.{ ctx.workspace_path, "leaf.tar" }); - defer allocator.free(tarball); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); - const pack = try ctx.exec_bazel(.{ - .argv = &[_][]const u8{ "run", "//tools:pack", "--", fixture, tarball }, - }); - defer pack.deinit(); - try std.testing.expect(pack.success); - const hash = std.mem.trim(u8, pack.stdout, " \t\r\n"); - - const url = try std.fmt.allocPrint(allocator, "file://{s}", .{tarball}); - defer allocator.free(url); - try ctx.patchWorkspaceFile("build.zig.zon", &.{ - .{ "__LEAF_URL__", url }, - .{ "__LEAF_HASH__", hash }, - }); + // Pack each fixture (built by the inner nightly toolchain) into a tarball and + // resolve the consumer manifest's URL/hash placeholders to it. + var replacements: [fixtures.len * 2][2][]const u8 = undefined; + for (fixtures, 0..) |fixture, i| { + const dir = try std.fmt.allocPrint(allocator, "{s}/fixtures/{s}", .{ ctx.workspace_path, fixture.name }); + const tarball = try std.fmt.allocPrint(allocator, "{s}/{s}.tar", .{ ctx.workspace_path, fixture.name }); + + const pack = try ctx.exec_bazel(.{ + .argv = &[_][]const u8{ "run", "//tools:pack", "--", dir, tarball }, + }); + defer pack.deinit(); + try std.testing.expect(pack.success); + + const hash = try allocator.dupe(u8, std.mem.trim(u8, pack.stdout, " \t\r\n")); + const url = try std.fmt.allocPrint(allocator, "file://{s}", .{tarball}); + replacements[i * 2] = .{ fixture.url_placeholder, url }; + replacements[i * 2 + 1] = .{ fixture.hash_placeholder, hash }; + } + try ctx.patchWorkspaceFile("build.zig.zon", &replacements); + // The importer fetches, configures, and exposes each package (including the + // sub-tree path dependency of `host`) as a module that the binary imports. const result = try ctx.exec_bazel(.{ .argv = &[_][]const u8{ "build", "//:binary" }, }); From fb8c63c3c26aac7dade47dfa28cbaf21ea62b071 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Sat, 13 Jun 2026 18:18:11 +0200 Subject: [PATCH 30/77] fetch Zig packages in zon2json to work around sub-package path dependency limitations. `zig build --fetch=all --pkg-dir` fails on absolute --pkg-dir paths. --- zig/private/zon2json.zig | 101 +++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 8 deletions(-) diff --git a/zig/private/zon2json.zig b/zig/private/zon2json.zig index 58568568..92e91b6e 100644 --- a/zig/private/zon2json.zig +++ b/zig/private/zon2json.zig @@ -1,11 +1,14 @@ //! Resolve a Zig package dependency graph by recursively parsing `build.zig.zon` //! manifests, and emit the merged graph as JSON on stdout. //! -//! Usage: zon2json ... +//! Usage: zon2json ... //! -//! `` is the local package directory (`zig build --pkg-dir`) that holds -//! the fetched URL dependencies, each unpacked under `/`. The -//! remaining arguments are the root manifests to resolve. +//! URL dependencies are fetched with ` fetch` into `` as +//! content-addressed tarballs (no source-tree unpacking); their `build.zig.zon` +//! manifests are extracted under `/`. The remaining arguments are +//! the root manifests to resolve. Resolving manifests ourselves (rather than via +//! `zig build --fetch --pkg-dir`) lets path dependencies inside fetched packages +//! resolve relative to the extracted tree. //! //! Packages are keyed by their Zig hash (URL dependencies) or by their resolved //! absolute path (path dependencies), and listed in topological order: a package @@ -21,6 +24,8 @@ const std = @import("std"); const Zoir = std.zig.Zoir; const Allocator = std.mem.Allocator; const Io = std.Io; +const flate = std.compress.flate; +const tar = std.tar; const Dep = struct { name: []const u8, @@ -56,13 +61,19 @@ const Resolved = struct { const Walker = struct { arena: Allocator, io: Io, + zig: []const u8, + global_cache: []const u8, pkg_dir: []const u8, packages: std.StringArrayHashMapUnmanaged(Package) = .empty, visited: std.StringHashMapUnmanaged(void) = .empty, fn resolveDep(walker: *Walker, dep: Dep, parent_dir: []const u8) !Resolved { if (dep.url) |url| { - const hash = dep.hash orelse fatal("URL dependency '{s}' is missing a hash", .{dep.name}); + const declared = dep.hash orelse fatal("URL dependency '{s}' is missing a hash", .{dep.name}); + const hash = try walker.fetch(url); + if (!std.mem.eql(u8, hash, declared)) { + fatal("hash mismatch for '{s}':\n declared: {s}\n fetched: {s}", .{ dep.name, declared, hash }); + } return .{ .key = hash, .url = url, @@ -77,6 +88,74 @@ const Walker = struct { fatal("dependency '{s}' has neither a url nor a path", .{dep.name}); } + /// Fetch a URL package as a content-addressed tarball (no source-tree + /// unpacking), then extract its `build.zig.zon` manifests into `pkg_dir` so + /// the walk can resolve the dependency graph from the filesystem. + fn fetch(walker: *Walker, url: []const u8) ![]const u8 { + const result = try std.process.run(walker.arena, walker.io, .{ + .argv = &.{ walker.zig, "fetch", "--global-cache-dir", walker.global_cache, url }, + }); + switch (result.term) { + .exited => |code| if (code != 0) fatal("`zig fetch {s}` failed:\n{s}", .{ url, result.stderr }), + else => fatal("`zig fetch {s}` terminated abnormally", .{url}), + } + const hash = try walker.arena.dupe(u8, std.mem.trim(u8, result.stdout, " \t\r\n")); + + const dest = try std.fs.path.join(walker.arena, &.{ walker.pkg_dir, hash }); + if (Io.Dir.cwd().access(walker.io, dest, .{})) |_| return hash else |_| {} + try walker.extractManifests(hash, dest); + return hash; + } + + fn extractManifests(walker: *Walker, hash: []const u8, dest: []const u8) !void { + const arena = walker.arena; + const io = walker.io; + const tarball = try std.fmt.allocPrint(arena, "{s}/p/{s}.tar.gz", .{ walker.global_cache, hash }); + + var file = try Io.Dir.cwd().openFile(io, tarball, .{}); + defer file.close(io); + var read_buffer: [64 * 1024]u8 = undefined; + var file_reader = file.reader(io, &read_buffer); + var window: [flate.max_window_len]u8 = undefined; + var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &window); + + var name_buffer: [std.fs.max_path_bytes]u8 = undefined; + var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; + var iterator: tar.Iterator = .init(&decompress.reader, .{ + .file_name_buffer = &name_buffer, + .link_name_buffer = &link_name_buffer, + }); + + // Collect every manifest, tracking the shallowest one's directory as the + // archive prefix to strip (the tarball nests the package under it). + const Found = struct { name: []const u8, content: []const u8 }; + var manifests: std.ArrayList(Found) = .empty; + var prefix: []const u8 = ""; + var have_prefix = false; + while (try iterator.next()) |entry| { + if (entry.kind == .directory) continue; + if (!std.mem.eql(u8, std.fs.path.basename(entry.name), "build.zig.zon")) continue; + var content: std.Io.Writer.Allocating = .init(arena); + try iterator.streamRemaining(entry, &content.writer); + const name = try arena.dupe(u8, entry.name); + const dir = std.fs.path.dirname(name) orelse ""; + if (!have_prefix or dir.len < prefix.len) { + prefix = dir; + have_prefix = true; + } + try manifests.append(arena, .{ .name = name, .content = content.written() }); + } + + try Io.Dir.cwd().createDirPath(io, dest); + var dest_dir = try Io.Dir.cwd().openDir(io, dest, .{}); + defer dest_dir.close(io); + for (manifests.items) |found| { + const rel = if (prefix.len == 0) found.name else found.name[prefix.len + 1 ..]; + if (std.fs.path.dirname(rel)) |parent| try dest_dir.createDirPath(io, parent); + try dest_dir.writeFile(io, .{ .sub_path = rel, .data = found.content }); + } + } + fn resolveEdges(walker: *Walker, manifest: Manifest, dir: []const u8) ![]const Edge { var edges: std.ArrayList(Edge) = .empty; for (manifest.deps) |dep| { @@ -111,12 +190,18 @@ pub fn main(init: std.process.Init) !void { const io = init.io; const args = try init.minimal.args.toSlice(arena); - if (args.len < 2) fatal("usage: zon2json ...", .{}); + if (args.len < 4) fatal("usage: zon2json ...", .{}); - var walker: Walker = .{ .arena = arena, .io = io, .pkg_dir = args[1] }; + var walker: Walker = .{ + .arena = arena, + .io = io, + .zig = args[1], + .global_cache = args[2], + .pkg_dir = args[3], + }; var roots: std.ArrayList([]const Edge) = .empty; - for (args[2..]) |root_path| { + for (args[4..]) |root_path| { const dir = std.fs.path.dirname(root_path) orelse "."; const manifest = try parseManifest(arena, io, root_path); try roots.append(arena, try walker.resolveEdges(manifest, dir)); From 36eb8c59f38cca0fe6fd0e347216fc804a6e2d1e Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 09:28:03 +0200 Subject: [PATCH 31/77] Test nested and diamond dependencies --- .../integration_tests/packages/build.zig.zon | 4 ++ .../packages/fixtures/base/build.zig | 3 + .../packages/fixtures/base/build.zig.zon | 1 + .../packages/fixtures/base/src/base.zig | 1 + .../packages/fixtures/bottom/build.zig | 7 ++ .../packages/fixtures/bottom/build.zig.zon | 9 +++ .../packages/fixtures/bottom/src/bottom.zig | 1 + .../packages/fixtures/left/build.zig | 7 ++ .../packages/fixtures/left/build.zig.zon | 9 +++ .../packages/fixtures/left/src/left.zig | 1 + .../packages/fixtures/right/build.zig | 7 ++ .../packages/fixtures/right/build.zig.zon | 9 +++ .../packages/fixtures/right/src/right.zig | 1 + .../packages/fixtures/top/build.zig | 9 +++ .../packages/fixtures/top/build.zig.zon | 10 +++ .../packages/fixtures/top/src/top.zig | 1 + zig/tests/integration_tests/packages/main.zig | 2 + .../packages_tests_runner.zig | 68 ++++++++++++++----- 18 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/base/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/base/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/base/src/base.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/bottom/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/bottom/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/bottom/src/bottom.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/left/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/left/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/left/src/left.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/right/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/right/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/right/src/right.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/top/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/top/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/top/src/top.zig diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index c3adf742..8138bc2d 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -11,6 +11,10 @@ .url = "__HOST_URL__", .hash = "__HOST_HASH__", }, + .top = .{ + .url = "__TOP_URL__", + .hash = "__TOP_HASH__", + }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/fixtures/base/build.zig b/zig/tests/integration_tests/packages/fixtures/base/build.zig new file mode 100644 index 00000000..f21ce72e --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/base/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("base", .{ .root_source_file = b.path("src/base.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/base/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/base/build.zig.zon new file mode 100644 index 00000000..378252c8 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/base/build.zig.zon @@ -0,0 +1 @@ +.{ .name = .base, .version = "0.0.0", .fingerprint = 0xc0b4fe61db8e1e53, .paths = .{ "build.zig", "build.zig.zon", "src" } } diff --git a/zig/tests/integration_tests/packages/fixtures/base/src/base.zig b/zig/tests/integration_tests/packages/fixtures/base/src/base.zig new file mode 100644 index 00000000..75c2e74c --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/base/src/base.zig @@ -0,0 +1 @@ +pub const value: u32 = 1; diff --git a/zig/tests/integration_tests/packages/fixtures/bottom/build.zig b/zig/tests/integration_tests/packages/fixtures/bottom/build.zig new file mode 100644 index 00000000..af72b345 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/bottom/build.zig @@ -0,0 +1,7 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const base = b.dependency("base", .{}); + const mod = b.addModule("bottom", .{ .root_source_file = b.path("src/bottom.zig") }); + mod.addImport("base", base.module("base")); +} diff --git a/zig/tests/integration_tests/packages/fixtures/bottom/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/bottom/build.zig.zon new file mode 100644 index 00000000..c8b16a19 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/bottom/build.zig.zon @@ -0,0 +1,9 @@ +.{ + .name = .bottom, + .version = "0.0.0", + .fingerprint = 0x895f72a49d5f57fe, + .dependencies = .{ + .base = .{ .url = "__BASE_URL__", .hash = "__BASE_HASH__" }, + }, + .paths = .{ "build.zig", "build.zig.zon", "src" }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/bottom/src/bottom.zig b/zig/tests/integration_tests/packages/fixtures/bottom/src/bottom.zig new file mode 100644 index 00000000..c0e85c0f --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/bottom/src/bottom.zig @@ -0,0 +1 @@ +const base = @import("base"); pub const value: u32 = base.value + 1; diff --git a/zig/tests/integration_tests/packages/fixtures/left/build.zig b/zig/tests/integration_tests/packages/fixtures/left/build.zig new file mode 100644 index 00000000..38390b44 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/left/build.zig @@ -0,0 +1,7 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const bottom = b.dependency("bottom", .{}); + const mod = b.addModule("left", .{ .root_source_file = b.path("src/left.zig") }); + mod.addImport("bottom", bottom.module("bottom")); +} diff --git a/zig/tests/integration_tests/packages/fixtures/left/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/left/build.zig.zon new file mode 100644 index 00000000..a054d7c6 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/left/build.zig.zon @@ -0,0 +1,9 @@ +.{ + .name = .left, + .version = "0.0.0", + .fingerprint = 0x7a67e768d7122cc8, + .dependencies = .{ + .bottom = .{ .url = "__BOTTOM_URL__", .hash = "__BOTTOM_HASH__" }, + }, + .paths = .{ "build.zig", "build.zig.zon", "src" }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/left/src/left.zig b/zig/tests/integration_tests/packages/fixtures/left/src/left.zig new file mode 100644 index 00000000..975fde7d --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/left/src/left.zig @@ -0,0 +1 @@ +const bottom = @import("bottom"); pub const value: u32 = bottom.value + 1; diff --git a/zig/tests/integration_tests/packages/fixtures/right/build.zig b/zig/tests/integration_tests/packages/fixtures/right/build.zig new file mode 100644 index 00000000..c39c486d --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/right/build.zig @@ -0,0 +1,7 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const bottom = b.dependency("bottom", .{}); + const mod = b.addModule("right", .{ .root_source_file = b.path("src/right.zig") }); + mod.addImport("bottom", bottom.module("bottom")); +} diff --git a/zig/tests/integration_tests/packages/fixtures/right/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/right/build.zig.zon new file mode 100644 index 00000000..24f25480 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/right/build.zig.zon @@ -0,0 +1,9 @@ +.{ + .name = .right, + .version = "0.0.0", + .fingerprint = 0xb4ca7514bed08aba, + .dependencies = .{ + .bottom = .{ .url = "__BOTTOM_URL__", .hash = "__BOTTOM_HASH__" }, + }, + .paths = .{ "build.zig", "build.zig.zon", "src" }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/right/src/right.zig b/zig/tests/integration_tests/packages/fixtures/right/src/right.zig new file mode 100644 index 00000000..975fde7d --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/right/src/right.zig @@ -0,0 +1 @@ +const bottom = @import("bottom"); pub const value: u32 = bottom.value + 1; diff --git a/zig/tests/integration_tests/packages/fixtures/top/build.zig b/zig/tests/integration_tests/packages/fixtures/top/build.zig new file mode 100644 index 00000000..d5f74f76 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/top/build.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const left = b.dependency("left", .{}); + const right = b.dependency("right", .{}); + const mod = b.addModule("top", .{ .root_source_file = b.path("src/top.zig") }); + mod.addImport("left", left.module("left")); + mod.addImport("right", right.module("right")); +} diff --git a/zig/tests/integration_tests/packages/fixtures/top/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/top/build.zig.zon new file mode 100644 index 00000000..041fbe77 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/top/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .top, + .version = "0.0.0", + .fingerprint = 0x1ed91fcabefd4e03, + .dependencies = .{ + .left = .{ .url = "__LEFT_URL__", .hash = "__LEFT_HASH__" }, + .right = .{ .url = "__RIGHT_URL__", .hash = "__RIGHT_HASH__" }, + }, + .paths = .{ "build.zig", "build.zig.zon", "src" }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/top/src/top.zig b/zig/tests/integration_tests/packages/fixtures/top/src/top.zig new file mode 100644 index 00000000..5635996a --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/top/src/top.zig @@ -0,0 +1 @@ +const left = @import("left"); const right = @import("right"); pub const value: u32 = left.value + right.value; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index c100a90f..8fd40020 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -1,7 +1,9 @@ const leaf = @import("leaf"); const host = @import("host"); +const top = @import("top"); pub fn main() void { _ = leaf.value; _ = host.value; + _ = top.value; } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index 5293f2da..c873a182 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -2,17 +2,25 @@ const std = @import("std"); const integration_testing = @import("integration_testing"); const BitContext = integration_testing.BitContext; -const Fixture = struct { +const Package = struct { name: []const u8, - url_placeholder: []const u8, - hash_placeholder: []const u8, + deps: []const []const u8 = &.{}, }; -const fixtures = [_]Fixture{ - .{ .name = "leaf", .url_placeholder = "__LEAF_URL__", .hash_placeholder = "__LEAF_HASH__" }, - .{ .name = "host", .url_placeholder = "__HOST_URL__", .hash_placeholder = "__HOST_HASH__" }, +// Packed in topological order (dependencies first). +const packages = [_]Package{ + .{ .name = "leaf" }, + .{ .name = "host" }, + .{ .name = "base" }, + .{ .name = "bottom", .deps = &.{"base"} }, + .{ .name = "left", .deps = &.{"bottom"} }, + .{ .name = "right", .deps = &.{"bottom"} }, + .{ .name = "top", .deps = &.{ "left", "right" } }, }; +// Direct dependencies declared by the consumer manifest. +const root_deps = [_][]const u8{ "leaf", "host", "top" }; + test "Zig packages are imported from file:// tarballs" { const ctx = try BitContext.init(); defer ctx.deinit(); @@ -21,31 +29,55 @@ test "Zig packages are imported from file:// tarballs" { defer arena.deinit(); const allocator = arena.allocator(); - // Pack each fixture (built by the inner nightly toolchain) into a tarball and - // resolve the consumer manifest's URL/hash placeholders to it. - var replacements: [fixtures.len * 2][2][]const u8 = undefined; - for (fixtures, 0..) |fixture, i| { - const dir = try std.fmt.allocPrint(allocator, "{s}/fixtures/{s}", .{ ctx.workspace_path, fixture.name }); - const tarball = try std.fmt.allocPrint(allocator, "{s}/{s}.tar", .{ ctx.workspace_path, fixture.name }); + var urls = std.StringHashMap([]const u8).init(allocator); + var hashes = std.StringHashMap([]const u8).init(allocator); + + for (packages) |pkg| { + if (pkg.deps.len > 0) { + const manifest = try std.fmt.allocPrint(allocator, "fixtures/{s}/build.zig.zon", .{pkg.name}); + try ctx.patchWorkspaceFile(manifest, try depReplacements(allocator, pkg.deps, &urls, &hashes)); + } + const dir = try std.fmt.allocPrint(allocator, "{s}/fixtures/{s}", .{ ctx.workspace_path, pkg.name }); + const tarball = try std.fmt.allocPrint(allocator, "{s}/{s}.tar", .{ ctx.workspace_path, pkg.name }); const pack = try ctx.exec_bazel(.{ .argv = &[_][]const u8{ "run", "//tools:pack", "--", dir, tarball }, }); defer pack.deinit(); try std.testing.expect(pack.success); - const hash = try allocator.dupe(u8, std.mem.trim(u8, pack.stdout, " \t\r\n")); - const url = try std.fmt.allocPrint(allocator, "file://{s}", .{tarball}); - replacements[i * 2] = .{ fixture.url_placeholder, url }; - replacements[i * 2 + 1] = .{ fixture.hash_placeholder, hash }; + try hashes.put(pkg.name, try allocator.dupe(u8, std.mem.trim(u8, pack.stdout, " \t\r\n"))); + try urls.put(pkg.name, try std.fmt.allocPrint(allocator, "file://{s}", .{tarball})); } - try ctx.patchWorkspaceFile("build.zig.zon", &replacements); + + try ctx.patchWorkspaceFile("build.zig.zon", try depReplacements(allocator, &root_deps, &urls, &hashes)); // The importer fetches, configures, and exposes each package (including the - // sub-tree path dependency of `host`) as a module that the binary imports. + // sub-tree path dependency of `host` and the transitive chain under `top`) + // as a module that the binary imports. const result = try ctx.exec_bazel(.{ .argv = &[_][]const u8{ "build", "//:binary" }, }); defer result.deinit(); try std.testing.expect(result.success); } + +fn depReplacements( + allocator: std.mem.Allocator, + deps: []const []const u8, + urls: *std.StringHashMap([]const u8), + hashes: *std.StringHashMap([]const u8), +) ![]const [2][]const u8 { + const replacements = try allocator.alloc([2][]const u8, deps.len * 2); + for (deps, 0..) |dep, i| { + replacements[i * 2] = .{ try placeholder(allocator, dep, "URL"), urls.get(dep).? }; + replacements[i * 2 + 1] = .{ try placeholder(allocator, dep, "HASH"), hashes.get(dep).? }; + } + return replacements; +} + +fn placeholder(allocator: std.mem.Allocator, name: []const u8, kind: []const u8) ![]const u8 { + const upper = try allocator.alloc(u8, name.len); + _ = std.ascii.upperString(upper, name); + return std.fmt.allocPrint(allocator, "__{s}_{s}__", .{ upper, kind }); +} From 90a532879300100726e31fc60aee08b603dea4c5 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 10:14:45 +0200 Subject: [PATCH 32/77] test nested bazel module with zig deps --- zig/private/repo/zig_deps_hub.bzl | 5 ++--- zig/tests/integration_tests/packages/BUILD.bazel | 2 +- .../integration_tests/packages/MODULE.bazel | 6 ++++++ .../integration_tests/packages/child/BUILD.bazel | 10 ++++++++++ .../packages/child/MODULE.bazel | 10 ++++++++++ .../integration_tests/packages/child/WORKSPACE | 0 .../packages/child/build.zig.zon | 15 +++++++++++++++ .../packages/child/src/child.zig | 3 +++ zig/tests/integration_tests/packages/main.zig | 2 ++ .../integration_tests/packages_tests_runner.zig | 16 +++++++++++++--- 10 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 zig/tests/integration_tests/packages/child/BUILD.bazel create mode 100644 zig/tests/integration_tests/packages/child/MODULE.bazel create mode 100644 zig/tests/integration_tests/packages/child/WORKSPACE create mode 100644 zig/tests/integration_tests/packages/child/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/child/src/child.zig diff --git a/zig/private/repo/zig_deps_hub.bzl b/zig/private/repo/zig_deps_hub.bzl index ba6ede77..0abd9881 100644 --- a/zig/private/repo/zig_deps_hub.bzl +++ b/zig/private/repo/zig_deps_hub.bzl @@ -51,7 +51,7 @@ def _manifest(): manifest = _enclosing(manifests, package) if manifest == None: fail("no Zig `from_file` manifest covers package '%s'" % package) - path = repo + "/" + manifest if repo else manifest + path = "/".join([part for part in [repo, manifest] if part]) return path, manifests[manifest] def _label(path, name): @@ -69,8 +69,7 @@ def _enclosing(manifests, package): ''' def _hub_path(manifest): - repo, package = manifest["repo"], manifest["package"] - return repo + "/" + package if repo else package + return "/".join([part for part in [manifest["repo"], manifest["package"]] if part]) def _zig_deps_hub_impl(repository_ctx): packages = repository_ctx.attr.packages diff --git a/zig/tests/integration_tests/packages/BUILD.bazel b/zig/tests/integration_tests/packages/BUILD.bazel index 23e702b3..a8bf7e15 100644 --- a/zig/tests/integration_tests/packages/BUILD.bazel +++ b/zig/tests/integration_tests/packages/BUILD.bazel @@ -4,5 +4,5 @@ load("@zig_deps//:defs.bzl", "zig_deps") zig_binary( name = "binary", main = "main.zig", - deps = zig_deps(), + deps = zig_deps() + ["@child_module//:child_lib"], ) diff --git a/zig/tests/integration_tests/packages/MODULE.bazel b/zig/tests/integration_tests/packages/MODULE.bazel index eae814f1..e361d877 100644 --- a/zig/tests/integration_tests/packages/MODULE.bazel +++ b/zig/tests/integration_tests/packages/MODULE.bazel @@ -3,12 +3,18 @@ module(name = "packages") bazel_dep(name = "rules_zig", version = "0.0.0") bazel_dep(name = "platforms", version = "0.0.6") bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "child_module", version = "0.0.0") local_path_override( module_name = "rules_zig", path = "../../../..", ) +local_path_override( + module_name = "child_module", + path = "child", +) + zig = use_extension("@rules_zig//zig:extensions.bzl", "zig") zig.index(file = "extra-versions.json") zig.toolchain( diff --git a/zig/tests/integration_tests/packages/child/BUILD.bazel b/zig/tests/integration_tests/packages/child/BUILD.bazel new file mode 100644 index 00000000..7968bb1d --- /dev/null +++ b/zig/tests/integration_tests/packages/child/BUILD.bazel @@ -0,0 +1,10 @@ +load("@rules_zig//zig:defs.bzl", "zig_library") +load("@zig_deps//:defs.bzl", "zig_deps") + +zig_library( + name = "child_lib", + import_name = "child", + main = "src/child.zig", + visibility = ["//visibility:public"], + deps = zig_deps(), +) diff --git a/zig/tests/integration_tests/packages/child/MODULE.bazel b/zig/tests/integration_tests/packages/child/MODULE.bazel new file mode 100644 index 00000000..6097bb6c --- /dev/null +++ b/zig/tests/integration_tests/packages/child/MODULE.bazel @@ -0,0 +1,10 @@ +module( + name = "child_module", + version = "0.0.0", +) + +bazel_dep(name = "rules_zig", version = "0.0.0") + +zig_packages = use_extension("@rules_zig//zig:packages.bzl", "zig_packages") +zig_packages.from_file(build_zig_zon = "//:build.zig.zon") +use_repo(zig_packages, "zig_deps") diff --git a/zig/tests/integration_tests/packages/child/WORKSPACE b/zig/tests/integration_tests/packages/child/WORKSPACE new file mode 100644 index 00000000..e69de29b diff --git a/zig/tests/integration_tests/packages/child/build.zig.zon b/zig/tests/integration_tests/packages/child/build.zig.zon new file mode 100644 index 00000000..0fe5898a --- /dev/null +++ b/zig/tests/integration_tests/packages/child/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = .child, + .version = "0.0.0", + .fingerprint = 0x2b9d6e1f7a4c8053, + .dependencies = .{ + .leaf = .{ + .url = "__LEAF_URL__", + .hash = "__LEAF_HASH__", + }, + }, + .paths = .{ + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/child/src/child.zig b/zig/tests/integration_tests/packages/child/src/child.zig new file mode 100644 index 00000000..c882efad --- /dev/null +++ b/zig/tests/integration_tests/packages/child/src/child.zig @@ -0,0 +1,3 @@ +const leaf = @import("leaf"); + +pub const value: u32 = leaf.value + 100; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 8fd40020..110ce0b6 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -1,9 +1,11 @@ const leaf = @import("leaf"); const host = @import("host"); const top = @import("top"); +const child = @import("child"); pub fn main() void { _ = leaf.value; _ = host.value; _ = top.value; + _ = child.value; } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index c873a182..fb609f87 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -18,8 +18,16 @@ const packages = [_]Package{ .{ .name = "top", .deps = &.{ "left", "right" } }, }; -// Direct dependencies declared by the consumer manifest. -const root_deps = [_][]const u8{ "leaf", "host", "top" }; +const Consumer = struct { + manifest: []const u8, + deps: []const []const u8, +}; + +// Manifests that resolve dependencies via `zig_packages.from_file`. +const consumers = [_]Consumer{ + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top" } }, + .{ .manifest = "child/build.zig.zon", .deps = &.{"leaf"} }, +}; test "Zig packages are imported from file:// tarballs" { const ctx = try BitContext.init(); @@ -50,7 +58,9 @@ test "Zig packages are imported from file:// tarballs" { try urls.put(pkg.name, try std.fmt.allocPrint(allocator, "file://{s}", .{tarball})); } - try ctx.patchWorkspaceFile("build.zig.zon", try depReplacements(allocator, &root_deps, &urls, &hashes)); + for (consumers) |consumer| { + try ctx.patchWorkspaceFile(consumer.manifest, try depReplacements(allocator, consumer.deps, &urls, &hashes)); + } // The importer fetches, configures, and exposes each package (including the // sub-tree path dependency of `host` and the transitive chain under `top`) From f9ffa3c3dbef9add142a4f2cc13854c60227edb9 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 10:14:57 +0200 Subject: [PATCH 33/77] update generated files --- .bazelrc | 4 ++-- zig/BUILD.bazel | 7 +++++++ zig/private/bzlmod/BUILD.bazel | 5 +++++ zig/private/repo/BUILD.bazel | 16 ++++++++++++---- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.bazelrc b/.bazelrc index 78967a3c..29ac1488 100644 --- a/.bazelrc +++ b/.bazelrc @@ -18,8 +18,8 @@ build --workspace_status_command=$(pwd)/workspace_status.sh # To update these lines, execute # `bazel run @rules_bazel_integration_test//tools:update_deleted_packages` # docs: https://bazel.build/reference/command-line-reference#flag--deleted_packages -build --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles -query --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles +build --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/child,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles +query --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/child,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles # Load any settings specific to the current user. # Place settings that should affect the integration tests into `.bazelrc.ic.user`. diff --git a/zig/BUILD.bazel b/zig/BUILD.bazel index 2abc8fe7..fa2695db 100644 --- a/zig/BUILD.bazel +++ b/zig/BUILD.bazel @@ -76,6 +76,13 @@ bzl_library( ], ) +bzl_library( + name = "packages", + srcs = ["packages.bzl"], + visibility = ["//visibility:public"], + deps = ["//zig/private/bzlmod:zig_packages"], +) + # Execute `bazel run //util:update_filegroups` to update this target. filegroup( name = "all_files", diff --git a/zig/private/bzlmod/BUILD.bazel b/zig/private/bzlmod/BUILD.bazel index 1f937e96..61e64eec 100644 --- a/zig/private/bzlmod/BUILD.bazel +++ b/zig/private/bzlmod/BUILD.bazel @@ -21,6 +21,11 @@ bzl_library( name = "zig_packages", srcs = ["zig_packages.bzl"], visibility = ["//zig:__subpackages__"], + deps = [ + "//zig/private/repo:zig_deps_hub", + "//zig/private/repo:zig_package", + "@rules_zig_host_toolchain//:toolchain", + ], ) # Execute `bazel run //util:update_filegroups` to update this target. diff --git a/zig/private/repo/BUILD.bazel b/zig/private/repo/BUILD.bazel index ee686664..e386bb17 100644 --- a/zig/private/repo/BUILD.bazel +++ b/zig/private/repo/BUILD.bazel @@ -21,15 +21,23 @@ bzl_library( ) bzl_library( - name = "zig_package", - srcs = ["zig_package.bzl"], + name = "zig_deps_hub", + srcs = ["zig_deps_hub.bzl"], visibility = ["//zig:__subpackages__"], ) bzl_library( - name = "zig_deps_hub", - srcs = ["zig_deps_hub.bzl"], + name = "zig_host_toolchain", + srcs = ["zig_host_toolchain.bzl"], + visibility = ["//zig:__subpackages__"], + deps = ["@zig_toolchains//private:toolchains"], +) + +bzl_library( + name = "zig_package", + srcs = ["zig_package.bzl"], visibility = ["//zig:__subpackages__"], + deps = ["@rules_zig_host_toolchain//:toolchain"], ) # Execute `bazel run //util:update_filegroups` to update this target. From c9ce46269ad16af0d02c62bf3d2ba6209f3da0dd Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 10:25:47 +0200 Subject: [PATCH 34/77] use skylib paths.join --- zig/private/repo/zig_deps_hub.bzl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/zig/private/repo/zig_deps_hub.bzl b/zig/private/repo/zig_deps_hub.bzl index 0abd9881..321f5984 100644 --- a/zig/private/repo/zig_deps_hub.bzl +++ b/zig/private/repo/zig_deps_hub.bzl @@ -1,5 +1,7 @@ """Implementation of the `zig_deps` hub repository rule.""" +load("@bazel_skylib//lib:paths.bzl", "paths") + DOC = """\ The `@zig_deps` hub repository. @@ -30,6 +32,8 @@ alias( _DEFS = '''\ """Accessors for the Zig package dependencies of each `from_file` manifest.""" +load("@bazel_skylib//lib:paths.bzl", "paths") + _MANIFESTS = json.decode("""%MANIFESTS%""") def zig_dep(name): @@ -51,7 +55,8 @@ def _manifest(): manifest = _enclosing(manifests, package) if manifest == None: fail("no Zig `from_file` manifest covers package '%s'" % package) - path = "/".join([part for part in [repo, manifest] if part]) + parts = [part for part in [repo, manifest] if part] + path = paths.join(*parts) if parts else "" return path, manifests[manifest] def _label(path, name): @@ -69,7 +74,10 @@ def _enclosing(manifests, package): ''' def _hub_path(manifest): - return "/".join([part for part in [manifest["repo"], manifest["package"]] if part]) + # `paths.join` keeps empty trailing segments (e.g. `repo/`), so drop empties + # first: the main module has no repo prefix and root manifests no package. + parts = [part for part in [manifest["repo"], manifest["package"]] if part] + return paths.join(*parts) if parts else "" def _zig_deps_hub_impl(repository_ctx): packages = repository_ctx.attr.packages From 280d03323e02774ee176bb7c1c31cc6d03fad39c Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 10:26:21 +0200 Subject: [PATCH 35/77] update generated files --- zig/private/bzlmod/BUILD.bazel | 12 ++++++------ zig/private/repo/BUILD.bazel | 13 +++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/zig/private/bzlmod/BUILD.bazel b/zig/private/bzlmod/BUILD.bazel index 61e64eec..251c209d 100644 --- a/zig/private/bzlmod/BUILD.bazel +++ b/zig/private/bzlmod/BUILD.bazel @@ -11,12 +11,6 @@ bzl_library( ], ) -bzl_library( - name = "cc_common_link", - srcs = ["cc_common_link.bzl"], - visibility = ["//zig:__subpackages__"], -) - bzl_library( name = "zig_packages", srcs = ["zig_packages.bzl"], @@ -28,6 +22,12 @@ bzl_library( ], ) +bzl_library( + name = "cc_common_link", + srcs = ["cc_common_link.bzl"], + visibility = ["//zig:__subpackages__"], +) + # Execute `bazel run //util:update_filegroups` to update this target. filegroup( name = "all_files", diff --git a/zig/private/repo/BUILD.bazel b/zig/private/repo/BUILD.bazel index e386bb17..bfe75ecf 100644 --- a/zig/private/repo/BUILD.bazel +++ b/zig/private/repo/BUILD.bazel @@ -20,12 +20,6 @@ bzl_library( ], ) -bzl_library( - name = "zig_deps_hub", - srcs = ["zig_deps_hub.bzl"], - visibility = ["//zig:__subpackages__"], -) - bzl_library( name = "zig_host_toolchain", srcs = ["zig_host_toolchain.bzl"], @@ -40,6 +34,13 @@ bzl_library( deps = ["@rules_zig_host_toolchain//:toolchain"], ) +bzl_library( + name = "zig_deps_hub", + srcs = ["zig_deps_hub.bzl"], + visibility = ["//zig:__subpackages__"], + deps = ["@bazel_skylib//lib:paths"], +) + # Execute `bazel run //util:update_filegroups` to update this target. filegroup( name = "all_files", From a22c21dc9c79a1d02e5762791371713830d80f4d Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 11:20:03 +0200 Subject: [PATCH 36/77] test multiple modules per Zig package dep --- zig/private/bzlmod/zig_packages.bzl | 7 +++- zig/private/repo/zig_deps_hub.bzl | 40 +++++++++++++------ .../integration_tests/packages/BUILD.bazel | 13 +++++- .../integration_tests/packages/build.zig.zon | 4 ++ .../packages/fixtures/multi/build.zig | 8 ++++ .../packages/fixtures/multi/build.zig.zon | 10 +++++ .../packages/fixtures/multi/src/multi.zig | 3 ++ .../packages/fixtures/multi/src/widget.zig | 1 + zig/tests/integration_tests/packages/main.zig | 4 ++ .../packages_tests_runner.zig | 3 +- 10 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/multi/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/multi/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/multi/src/multi.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/multi/src/widget.zig diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 74b15614..26edad91 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -150,6 +150,8 @@ def _hub_data(graph, root_tags): A URL dependency named `name` resolves to the module of the same name in its spoke. A local path dependency resolves, by convention, to a target of the same name in the dependency manifest's own package, which the user provides. + Each dependency records whether it is a URL spoke, so the hub can expose its + other modules by name (`zig_dep(name, module = ...)`). """ tag_by_key = {str(tag): tag for tag in root_tags} manifests = [] @@ -157,14 +159,15 @@ def _hub_data(graph, root_tags): for root, label in zip(graph["roots"], root_tags): deps = {} for name, key in root["deps"].items(): - if graph["packages"][key]["url"] != None: + url = graph["packages"][key]["url"] != None + if url: target = "@{}//:{}".format(key, name) targets[target] = target else: module = tag_by_key[key].same_package_label(name) target = str(module) targets[target] = module - deps[name] = target + deps[name] = {"target": target, "url": url} manifests.append({ "repo": label.repo_name, "package": label.package, diff --git a/zig/private/repo/zig_deps_hub.bzl b/zig/private/repo/zig_deps_hub.bzl index 321f5984..0d0ce195 100644 --- a/zig/private/repo/zig_deps_hub.bzl +++ b/zig/private/repo/zig_deps_hub.bzl @@ -36,17 +36,26 @@ load("@bazel_skylib//lib:paths.bzl", "paths") _MANIFESTS = json.decode("""%MANIFESTS%""") -def zig_dep(name): - """Return the label of dependency `name` of the enclosing Zig manifest.""" - path, names = _manifest() - if name not in names: - fail("Zig manifest '%s' declares no dependency '%s'; available: %s" % (path, name, names)) - return _label(path, name) +def zig_dep(name, module = None): + """Return the label of dependency `name` of the enclosing Zig manifest. + + Resolves to the dependency's module of the same name. Pass `module` to select + a differently named module exposed by the same (URL) dependency. + """ + path, deps = _manifest() + if name not in deps: + fail("Zig manifest '%s' declares no dependency '%s'; available: %s" % (path, name, sorted(deps))) + if module == None or module == name: + return _label(path, name) + dep = deps[name] + if not dep["select"]: + fail("Zig dependency '%s' is a path dependency; cannot select module '%s'" % (name, module)) + return Label("@@" + dep["spoke"] + "//:" + module) def zig_deps(): """Return the labels of every dependency of the enclosing Zig manifest.""" - path, names = _manifest() - return [_label(path, name) for name in names] + path, deps = _manifest() + return [_label(path, name) for name in deps] def _manifest(): repo = native.repo_name() @@ -86,12 +95,17 @@ def _zig_deps_hub_impl(repository_ctx): builds = {} registry = {} for manifest in manifests: - aliases = [ - _ALIAS.format(name = name, actual = str(packages[key]), scope = manifest["scope"]) - for name, key in manifest["deps"].items() - ] + aliases = [] + deps = {} + for name, dep in manifest["deps"].items(): + label = packages[dep["target"]] + aliases.append(_ALIAS.format(name = name, actual = str(label), scope = manifest["scope"])) + + # URL dependencies expose further modules by name, addressed through + # their spoke's canonical repository; path dependencies do not. + deps[name] = {"spoke": label.repo_name if dep["url"] else "", "select": dep["url"]} builds[_hub_path(manifest)] = "\n".join(aliases) - registry.setdefault(manifest["repo"], {})[manifest["package"]] = manifest["deps"].keys() + registry.setdefault(manifest["repo"], {})[manifest["package"]] = deps if "" not in builds: builds[""] = "" diff --git a/zig/tests/integration_tests/packages/BUILD.bazel b/zig/tests/integration_tests/packages/BUILD.bazel index a8bf7e15..c520f68f 100644 --- a/zig/tests/integration_tests/packages/BUILD.bazel +++ b/zig/tests/integration_tests/packages/BUILD.bazel @@ -1,8 +1,17 @@ load("@rules_zig//zig:defs.bzl", "zig_binary") -load("@zig_deps//:defs.bzl", "zig_deps") +load("@zig_deps//:defs.bzl", "zig_dep", "zig_deps") +# `zig_deps()` imports each dependency's same-named module (including `multi`'s +# primary `multi` module). `zig_dep("multi", module = "widget")` selects a second +# module from the same dependency whose name differs from the dependency name. zig_binary( name = "binary", main = "main.zig", - deps = zig_deps() + ["@child_module//:child_lib"], + deps = zig_deps() + [ + "@child_module//:child_lib", + zig_dep( + "multi", + module = "widget", + ), + ], ) diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 8138bc2d..8bf84c45 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -15,6 +15,10 @@ .url = "__TOP_URL__", .hash = "__TOP_HASH__", }, + .multi = .{ + .url = "__MULTI_URL__", + .hash = "__MULTI_HASH__", + }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/fixtures/multi/build.zig b/zig/tests/integration_tests/packages/fixtures/multi/build.zig new file mode 100644 index 00000000..1bddcc69 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/multi/build.zig @@ -0,0 +1,8 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const widget = b.addModule("widget", .{ .root_source_file = b.path("src/widget.zig") }); + + const multi = b.addModule("multi", .{ .root_source_file = b.path("src/multi.zig") }); + multi.addImport("widget", widget); +} diff --git a/zig/tests/integration_tests/packages/fixtures/multi/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/multi/build.zig.zon new file mode 100644 index 00000000..c451adb5 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/multi/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .multi, + .version = "0.0.0", + .fingerprint = 0xc5914305857f07ef, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/multi/src/multi.zig b/zig/tests/integration_tests/packages/fixtures/multi/src/multi.zig new file mode 100644 index 00000000..3eb42a61 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/multi/src/multi.zig @@ -0,0 +1,3 @@ +const widget = @import("widget"); + +pub const value: u32 = widget.value + 10; diff --git a/zig/tests/integration_tests/packages/fixtures/multi/src/widget.zig b/zig/tests/integration_tests/packages/fixtures/multi/src/widget.zig new file mode 100644 index 00000000..ca458229 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/multi/src/widget.zig @@ -0,0 +1 @@ +pub const value: u32 = 3; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 110ce0b6..4befdebd 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -2,10 +2,14 @@ const leaf = @import("leaf"); const host = @import("host"); const top = @import("top"); const child = @import("child"); +const multi = @import("multi"); +const widget = @import("widget"); pub fn main() void { _ = leaf.value; _ = host.value; _ = top.value; _ = child.value; + _ = multi.value; + _ = widget.value; } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index fb609f87..91d6c1bf 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -16,6 +16,7 @@ const packages = [_]Package{ .{ .name = "left", .deps = &.{"bottom"} }, .{ .name = "right", .deps = &.{"bottom"} }, .{ .name = "top", .deps = &.{ "left", "right" } }, + .{ .name = "multi" }, }; const Consumer = struct { @@ -25,7 +26,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{"leaf"} }, }; From e564548a88073a0227dff1897784aad6108b697a Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 17:03:40 +0200 Subject: [PATCH 37/77] implement and test transitive deps through path dep --- zig/private/bzlmod/zig_packages.bzl | 67 ++++++++++--------- zig/private/repo/zig_package.bzl | 21 +++++- .../packages/fixtures/host/libs/foo/build.zig | 4 +- .../fixtures/host/libs/foo/build.zig.zon | 6 ++ .../fixtures/host/libs/foo/src/foo.zig | 4 +- .../packages_tests_runner.zig | 7 +- 6 files changed, 70 insertions(+), 39 deletions(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 26edad91..670ffa6f 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -59,29 +59,27 @@ def _localize_paths(graph, pkg_dir, manifest_labels): return graph -def _url_edges(graph, key): - return [ - [name, child] - for name, child in graph["packages"][key]["deps"].items() - if graph["packages"][child]["url"] != None - ] - -def _subtree_edges(graph, key): - """Direct path dependencies that live inside this package's own fetched tree.""" - return [ - [name, child] - for name, child in graph["packages"][key]["deps"].items() - if graph["packages"][child]["url"] == None and child.startswith(key + "/") - ] - -def _deps_data(graph, key, closure): - # URL dependencies resolve to sibling spokes; sub-tree path dependencies are - # configured in-tree (`path` is their location relative to this package). - packages = {dep: {"deps": _url_edges(graph, dep), "path": None} for dep in closure} - for _name, child in _subtree_edges(graph, key): - packages[child] = {"deps": [], "path": child[len(key) + 1:]} +def _edges(graph, key): + return [[name, child] for name, child in graph["packages"][key]["deps"].items()] + +def _deps_data(graph, key, reachable): + # Every transitively reachable package must be configurable: a URL dependency + # resolves to its sibling spoke (`path` is None); a sub-tree path dependency + # is configured in-tree (`path` is its location relative to this package). + # Sub-tree dependencies keep their own `deps`, including any reached through + # them, so their `build.zig` (e.g. `b.dependency`) and modules resolve. + packages = {} + for dep in reachable: + package = graph["packages"][dep] + if package["url"] != None: + path = None + elif dep.startswith(key + "/"): + path = dep[len(key) + 1:] + else: + fail("Zig package '{}' depends on out-of-tree path dependency '{}', which is unsupported.".format(key, dep)) + packages[dep] = {"deps": _edges(graph, dep), "path": path} return { - "root_deps": _url_edges(graph, key) + _subtree_edges(graph, key), + "root_deps": _edges(graph, key), "packages": packages, } @@ -111,23 +109,26 @@ def _zig_packages_impl(module_ctx): graph = _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests) graph = _localize_paths(graph, str(pkg_dir), manifest_labels) - # `graph["packages"]` is topologically ordered, so each dependency's closure - # is already known by the time we reach a package: accumulate in one pass. - closures = {} + # `graph["packages"]` is topologically ordered, so each dependency's reachable + # set is already known by the time we reach a package: accumulate in one pass. + # Reachability follows every edge (URL and sub-tree path), so URL spokes reached + # through a sub-tree dependency are configured too. + reachable = {} for key, package in graph["packages"].items(): - closure = {} - for _name, child in _url_edges(graph, key): - closure[child] = True - for dep in closures[child]: - closure[dep] = True - closures[key] = closure.keys() + reached = {} + for _name, child in graph["packages"][key]["deps"].items(): + reached[child] = True + for dep in reachable[child]: + reached[dep] = True + reachable[key] = reached if package["url"] != None: + spokes = [dep for dep in reached if graph["packages"][dep]["url"] != None] zig_package( name = key, url = package["url"], zig_hash = key, - deps = json.encode(_deps_data(graph, key, closures[key])), - dep_build_files = {dep: "@{}//:build.zig".format(dep) for dep in closures[key]}, + deps = json.encode(_deps_data(graph, key, reached)), + dep_build_files = {dep: "@{}//:build.zig".format(dep) for dep in spokes}, ) manifests, targets = _hub_data(graph, root_tags) diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index ee28cf8d..74ca878b 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -107,6 +107,7 @@ zig_library( main = "{main}", import_name = "{name}", srcs = glob(["{subpath}/**/*.zig"], exclude = ["{main}"]), + deps = {deps}, visibility = ["//visibility:public"], ) """ @@ -115,7 +116,7 @@ def _module_dep(repository_ctx, imported, packages, subtree): key = imported["package"] if key and key in packages and packages[key]["path"] != None: # A sub-tree path dependency is generated as a sibling module in this spoke. - subtree[imported["name"]] = (packages[key]["path"], imported["root_source"]) + subtree[imported["name"]] = (key, imported["root_source"]) return ":" + imported["name"] if key: # A cross-package import resolves to the module of the same name in the @@ -124,6 +125,19 @@ def _module_dep(repository_ctx, imported, packages, subtree): return str(spoke.same_package_label(imported["name"])) return ":" + imported["name"] +def _subtree_dep(repository_ctx, edge, packages): + """Resolve a sub-tree package's own dependency edge to a Bazel label. + + The configurer only reports the host package's modules, so a sub-tree's own + imports are wired from the resolved graph instead, by convention assuming the + imported module shares the dependency's name. + """ + name, key = edge[0], edge[1] + if packages[key]["path"] != None: + return ":" + name + spoke = repository_ctx.attr.dep_build_files[key] + return str(spoke.same_package_label(name)) + def _build_file(repository_ctx, modules, packages): chunks = ["load(\"@rules_zig//zig:defs.bzl\", \"zig_library\")", "", _FILES] subtree = {} @@ -137,11 +151,14 @@ def _build_file(repository_ctx, modules, packages): deps = json.encode(deps), )) for name in sorted(subtree): - subpath, root_source = subtree[name] + key, root_source = subtree[name] + subpath = packages[key]["path"] + deps = [_subtree_dep(repository_ctx, edge, packages) for edge in packages[key]["deps"]] chunks.append(_ZIG_LIBRARY_SUBTREE.format( name = name, main = subpath + "/" + root_source, subpath = subpath, + deps = json.encode(deps), )) return "\n".join(chunks) diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig index 91914229..03f26c31 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig @@ -1,3 +1,5 @@ pub fn build(b: *@import("std").Build) void { - _ = b.addModule("foo", .{ .root_source_file = b.path("src/foo.zig") }); + const leaf = b.dependency("leaf", .{}); + const foo = b.addModule("foo", .{ .root_source_file = b.path("src/foo.zig") }); + foo.addImport("leaf", leaf.module("leaf")); } diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon index 440c4e77..2867a8e5 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon @@ -2,6 +2,12 @@ .name = .foo, .version = "0.0.0", .fingerprint = 0x8c736521d1c2c9ec, + .dependencies = .{ + .leaf = .{ + .url = "__LEAF_URL__", + .hash = "__LEAF_HASH__", + }, + }, .paths = .{ "build.zig", "build.zig.zon", diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig index 0be459b7..68c1b6c2 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig @@ -1 +1,3 @@ -pub const value: u32 = 42; +const leaf = @import("leaf"); + +pub const value: u32 = leaf.value + 42; diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index 91d6c1bf..b8bcb63f 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -5,12 +5,15 @@ const BitContext = integration_testing.BitContext; const Package = struct { name: []const u8, deps: []const []const u8 = &.{}, + // Manifest holding this package's dependency placeholders, relative to the + // fixture root. `host`'s dependency belongs to its `libs/foo` sub-tree. + manifest: []const u8 = "build.zig.zon", }; // Packed in topological order (dependencies first). const packages = [_]Package{ .{ .name = "leaf" }, - .{ .name = "host" }, + .{ .name = "host", .deps = &.{"leaf"}, .manifest = "libs/foo/build.zig.zon" }, .{ .name = "base" }, .{ .name = "bottom", .deps = &.{"base"} }, .{ .name = "left", .deps = &.{"bottom"} }, @@ -43,7 +46,7 @@ test "Zig packages are imported from file:// tarballs" { for (packages) |pkg| { if (pkg.deps.len > 0) { - const manifest = try std.fmt.allocPrint(allocator, "fixtures/{s}/build.zig.zon", .{pkg.name}); + const manifest = try std.fmt.allocPrint(allocator, "fixtures/{s}/{s}", .{ pkg.name, pkg.manifest }); try ctx.patchWorkspaceFile(manifest, try depReplacements(allocator, pkg.deps, &urls, &hashes)); } From b925be33c4d6b47a7a4333095c9f55ee818414d7 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 17:35:25 +0200 Subject: [PATCH 38/77] clearer pkg test package patching configuration --- .../packages_tests_runner.zig | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index b8bcb63f..dfc099dc 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -2,23 +2,27 @@ const std = @import("std"); const integration_testing = @import("integration_testing"); const BitContext = integration_testing.BitContext; +// A manifest inside a fixture whose URL-dependency placeholders to fill with +// already-packed fixtures' url+hash. +const Patch = struct { + manifest: []const u8 = "build.zig.zon", + deps: []const []const u8, +}; + const Package = struct { name: []const u8, - deps: []const []const u8 = &.{}, - // Manifest holding this package's dependency placeholders, relative to the - // fixture root. `host`'s dependency belongs to its `libs/foo` sub-tree. - manifest: []const u8 = "build.zig.zon", + patches: []const Patch = &.{}, }; // Packed in topological order (dependencies first). const packages = [_]Package{ .{ .name = "leaf" }, - .{ .name = "host", .deps = &.{"leaf"}, .manifest = "libs/foo/build.zig.zon" }, + .{ .name = "host", .patches = &.{.{ .manifest = "libs/foo/build.zig.zon", .deps = &.{"leaf"} }} }, .{ .name = "base" }, - .{ .name = "bottom", .deps = &.{"base"} }, - .{ .name = "left", .deps = &.{"bottom"} }, - .{ .name = "right", .deps = &.{"bottom"} }, - .{ .name = "top", .deps = &.{ "left", "right" } }, + .{ .name = "bottom", .patches = &.{.{ .deps = &.{"base"} }} }, + .{ .name = "left", .patches = &.{.{ .deps = &.{"bottom"} }} }, + .{ .name = "right", .patches = &.{.{ .deps = &.{"bottom"} }} }, + .{ .name = "top", .patches = &.{.{ .deps = &.{ "left", "right" } }} }, .{ .name = "multi" }, }; @@ -45,9 +49,9 @@ test "Zig packages are imported from file:// tarballs" { var hashes = std.StringHashMap([]const u8).init(allocator); for (packages) |pkg| { - if (pkg.deps.len > 0) { - const manifest = try std.fmt.allocPrint(allocator, "fixtures/{s}/{s}", .{ pkg.name, pkg.manifest }); - try ctx.patchWorkspaceFile(manifest, try depReplacements(allocator, pkg.deps, &urls, &hashes)); + for (pkg.patches) |patch| { + const manifest = try std.fmt.allocPrint(allocator, "fixtures/{s}/{s}", .{ pkg.name, patch.manifest }); + try ctx.patchWorkspaceFile(manifest, try depReplacements(allocator, patch.deps, &urls, &hashes)); } const dir = try std.fmt.allocPrint(allocator, "{s}/fixtures/{s}", .{ ctx.workspace_path, pkg.name }); From 8fcf3a9f8f87d862f21d244fddf11915ae26ddb7 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 18:02:42 +0200 Subject: [PATCH 39/77] handle sub-sub modules --- zig/private/configurer.zig | 52 +++++++++++++---- zig/private/repo/zig_package.bzl | 56 ++++++++----------- .../packages/fixtures/host/libs/foo/build.zig | 2 + .../fixtures/host/libs/foo/build.zig.zon | 2 + .../fixtures/host/libs/foo/libs/bar/build.zig | 3 + .../host/libs/foo/libs/bar/build.zig.zon | 10 ++++ .../host/libs/foo/libs/bar/src/bar.zig | 1 + .../fixtures/host/libs/foo/src/foo.zig | 3 +- 8 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/src/bar.zig diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig index 9ef26cf4..363b6159 100644 --- a/zig/private/configurer.zig +++ b/zig/private/configurer.zig @@ -4,23 +4,27 @@ //! Usage: configurer --zig --build-root //! //! Modeled on `lib/compiler/configurer.zig`: it sets up a `std.Build`, runs the -//! package's `build` function, then walks `b.modules` (the modules registered -//! via `b.addModule`) instead of serializing the build graph. The package's +//! package's `build` function, then walks the module import graph (seeded by the +//! modules registered via `b.addModule`) instead of serializing the build graph. +//! Following imports transitively reaches the modules of in-tree sub-tree path +//! dependencies, whose `build.zig` runs in a sub-builder. The package's //! `build.zig` is provided as the `pkg` module and its dependency table as the //! `deps` module, both wired in at compile time. //! //! The emitted JSON has the shape: //! -//! {"modules": [{"name": ..., "root_source": ..., "imports": [ -//! {"name": ..., "root_source": ..., "package": }]}]} +//! {"modules": [{"name": ..., "package": , "root_source": ..., +//! "imports": [{"name": ..., "package": }]}]} //! -//! `package` is the Zig hash of the dependency package that owns an imported -//! module, or the empty string for an import within the same package. +//! A module's `package` is the Zig hash (or sub-tree key) of the package that +//! owns it, or the empty string for the root package being configured. An +//! import's `package` identifies the owner of the imported module likewise. const std = @import("std"); const Io = std.Io; const Build = std.Build; const LazyPath = Build.LazyPath; +const Allocator = std.mem.Allocator; const mem = std.mem; const process = std.process; @@ -80,10 +84,34 @@ pub fn main(init: process.Init) !void { try builder.runBuild(root); - try emit(io, builder); + try emit(arena, io, builder); } -fn emit(io: Io, builder: *Build) !void { +const ModuleSet = std.AutoArrayHashMapUnmanaged(*Build.Module, void); + +/// Collect `module` and every module it transitively imports, deduplicated by +/// identity. Imports cross into sub-builders, so this reaches the modules of +/// in-tree sub-tree path dependencies in addition to the root package's own. +fn collect(arena: Allocator, modules: *ModuleSet, module: *Build.Module) !void { + const gop = try modules.getOrPut(arena, module); + if (gop.found_existing) return; + for (module.import_table.values()) |imported| try collect(arena, modules, imported); +} + +/// The name a module is registered under in its owning package, or the empty +/// string for an anonymous module (which our generated targets do not expose). +fn moduleName(module: *Build.Module) []const u8 { + var it = module.owner.modules.iterator(); + while (it.next()) |entry| { + if (entry.value_ptr.* == module) return entry.key_ptr.*; + } + return ""; +} + +fn emit(arena: Allocator, io: Io, builder: *Build) !void { + var modules: ModuleSet = .empty; + for (builder.modules.values()) |module| try collect(arena, &modules, module); + var stdout_buffer: [4096]u8 = undefined; var stdout = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); const writer = &stdout.interface; @@ -92,10 +120,12 @@ fn emit(io: Io, builder: *Build) !void { try json.beginObject(); try json.objectField("modules"); try json.beginArray(); - for (builder.modules.keys(), builder.modules.values()) |name, module| { + for (modules.keys()) |module| { try json.beginObject(); try json.objectField("name"); - try json.write(name); + try json.write(moduleName(module)); + try json.objectField("package"); + try json.write(module.owner.pkg_hash); try json.objectField("root_source"); try json.write(lazyPathString(module.root_source_file)); try json.objectField("imports"); @@ -104,8 +134,6 @@ fn emit(io: Io, builder: *Build) !void { try json.beginObject(); try json.objectField("name"); try json.write(import_name); - try json.objectField("root_source"); - try json.write(lazyPathString(imported.root_source_file)); try json.objectField("package"); try json.write(imported.owner.pkg_hash); try json.endObject(); diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 74ca878b..8a5fcdd4 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -112,11 +112,10 @@ zig_library( ) """ -def _module_dep(repository_ctx, imported, packages, subtree): +def _module_dep(repository_ctx, imported, packages): key = imported["package"] if key and key in packages and packages[key]["path"] != None: - # A sub-tree path dependency is generated as a sibling module in this spoke. - subtree[imported["name"]] = (key, imported["root_source"]) + # An in-tree (sub-tree) module is generated as a sibling in this spoke. return ":" + imported["name"] if key: # A cross-package import resolves to the module of the same name in the @@ -125,41 +124,32 @@ def _module_dep(repository_ctx, imported, packages, subtree): return str(spoke.same_package_label(imported["name"])) return ":" + imported["name"] -def _subtree_dep(repository_ctx, edge, packages): - """Resolve a sub-tree package's own dependency edge to a Bazel label. - - The configurer only reports the host package's modules, so a sub-tree's own - imports are wired from the resolved graph instead, by convention assuming the - imported module shares the dependency's name. - """ - name, key = edge[0], edge[1] - if packages[key]["path"] != None: - return ":" + name - spoke = repository_ctx.attr.dep_build_files[key] - return str(spoke.same_package_label(name)) - def _build_file(repository_ctx, modules, packages): chunks = ["load(\"@rules_zig//zig:defs.bzl\", \"zig_library\")", "", _FILES] - subtree = {} for module in modules: if not module["root_source"]: continue - deps = [_module_dep(repository_ctx, imported, packages, subtree) for imported in module["imports"]] - chunks.append(_ZIG_LIBRARY.format( - name = module["name"], - main = module["root_source"], - deps = json.encode(deps), - )) - for name in sorted(subtree): - key, root_source = subtree[name] - subpath = packages[key]["path"] - deps = [_subtree_dep(repository_ctx, edge, packages) for edge in packages[key]["deps"]] - chunks.append(_ZIG_LIBRARY_SUBTREE.format( - name = name, - main = subpath + "/" + root_source, - subpath = subpath, - deps = json.encode(deps), - )) + + # The configurer reports every reachable module tagged with its owner: the + # root package being configured (empty key) becomes a top-level library; an + # in-tree sub-tree path dependency becomes a library scoped to its sub-path; + # a module owned by a URL dependency lives in its own spoke and is skipped. + owner = module["package"] + deps = [_module_dep(repository_ctx, imported, packages) for imported in module["imports"]] + if not owner: + chunks.append(_ZIG_LIBRARY.format( + name = module["name"], + main = module["root_source"], + deps = json.encode(deps), + )) + elif owner in packages and packages[owner]["path"] != None: + subpath = packages[owner]["path"] + chunks.append(_ZIG_LIBRARY_SUBTREE.format( + name = module["name"], + main = subpath + "/" + module["root_source"], + subpath = subpath, + deps = json.encode(deps), + )) return "\n".join(chunks) def _configure(repository_ctx, zig, build_zig, cache): diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig index 03f26c31..a0152605 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig @@ -1,5 +1,7 @@ pub fn build(b: *@import("std").Build) void { + const bar = b.dependency("bar", .{}); const leaf = b.dependency("leaf", .{}); const foo = b.addModule("foo", .{ .root_source_file = b.path("src/foo.zig") }); + foo.addImport("bar", bar.module("bar")); foo.addImport("leaf", leaf.module("leaf")); } diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon index 2867a8e5..262317dc 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon @@ -3,6 +3,7 @@ .version = "0.0.0", .fingerprint = 0x8c736521d1c2c9ec, .dependencies = .{ + .bar = .{ .path = "libs/bar" }, .leaf = .{ .url = "__LEAF_URL__", .hash = "__LEAF_HASH__", @@ -12,5 +13,6 @@ "build.zig", "build.zig.zon", "src", + "libs", }, } diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/build.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/build.zig new file mode 100644 index 00000000..6e79e82b --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("bar", .{ .root_source_file = b.path("src/bar.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/build.zig.zon new file mode 100644 index 00000000..50109672 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .bar, + .version = "0.0.0", + .fingerprint = 0x76ff8caa9fb92ea7, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/src/bar.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/src/bar.zig new file mode 100644 index 00000000..cb3a6e63 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/libs/bar/src/bar.zig @@ -0,0 +1 @@ +pub const value: u32 = 5; diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig index 68c1b6c2..eee8a230 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig @@ -1,3 +1,4 @@ +const bar = @import("bar"); const leaf = @import("leaf"); -pub const value: u32 = leaf.value + 42; +pub const value: u32 = bar.value + leaf.value + 42; From ec4a4e6bf17451d7db671bedba5af9616e814e9d Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 18:24:05 +0200 Subject: [PATCH 40/77] handle module name collisions --- zig/private/repo/zig_package.bzl | 16 +++++++++++++--- .../packages/fixtures/host/build.zig | 2 ++ .../packages/fixtures/host/build.zig.zon | 1 + .../packages/fixtures/host/libs/bar/build.zig | 3 +++ .../fixtures/host/libs/bar/build.zig.zon | 10 ++++++++++ .../packages/fixtures/host/libs/bar/src/bar.zig | 1 + .../packages/fixtures/host/src/root.zig | 3 ++- 7 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/host/libs/bar/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/host/libs/bar/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/host/libs/bar/src/bar.zig diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 8a5fcdd4..2a8b73de 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -105,18 +105,27 @@ _ZIG_LIBRARY_SUBTREE = """\ zig_library( name = "{name}", main = "{main}", - import_name = "{name}", + import_name = "{import_name}", srcs = glob(["{subpath}/**/*.zig"], exclude = ["{main}"]), deps = {deps}, visibility = ["//visibility:public"], ) """ +def _target_name(packages, owner, name): + # Root-package modules keep their bare name (the spoke's public API). In-tree + # sub-tree modules are namespaced by their sub-path so that identically named + # modules in different sub-trees do not collide; their `import_name` stays the + # bare module name, which is what importing code uses. + if not owner: + return name + return packages[owner]["path"] + "/" + name + def _module_dep(repository_ctx, imported, packages): key = imported["package"] if key and key in packages and packages[key]["path"] != None: # An in-tree (sub-tree) module is generated as a sibling in this spoke. - return ":" + imported["name"] + return ":" + _target_name(packages, key, imported["name"]) if key: # A cross-package import resolves to the module of the same name in the # dependency's spoke. @@ -145,7 +154,8 @@ def _build_file(repository_ctx, modules, packages): elif owner in packages and packages[owner]["path"] != None: subpath = packages[owner]["path"] chunks.append(_ZIG_LIBRARY_SUBTREE.format( - name = module["name"], + name = _target_name(packages, owner, module["name"]), + import_name = module["name"], main = subpath + "/" + module["root_source"], subpath = subpath, deps = json.encode(deps), diff --git a/zig/tests/integration_tests/packages/fixtures/host/build.zig b/zig/tests/integration_tests/packages/fixtures/host/build.zig index 33425712..c8c30992 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/build.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/build.zig @@ -2,6 +2,8 @@ const std = @import("std"); pub fn build(b: *std.Build) void { const foo = b.dependency("foo", .{}); + const bar = b.dependency("bar", .{}); const mod = b.addModule("host", .{ .root_source_file = b.path("src/root.zig") }); mod.addImport("foo", foo.module("foo")); + mod.addImport("bar", bar.module("bar")); } diff --git a/zig/tests/integration_tests/packages/fixtures/host/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/host/build.zig.zon index cf615763..be846b05 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/build.zig.zon +++ b/zig/tests/integration_tests/packages/fixtures/host/build.zig.zon @@ -4,6 +4,7 @@ .fingerprint = 0xcf2713fdd0ee5aad, .dependencies = .{ .foo = .{ .path = "libs/foo" }, + .bar = .{ .path = "libs/bar" }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/bar/build.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/bar/build.zig new file mode 100644 index 00000000..6e79e82b --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/bar/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("bar", .{ .root_source_file = b.path("src/bar.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/bar/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/host/libs/bar/build.zig.zon new file mode 100644 index 00000000..83101d6d --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/bar/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .bar, + .version = "0.0.0", + .fingerprint = 0x76ff8caad565ac33, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/bar/src/bar.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/bar/src/bar.zig new file mode 100644 index 00000000..da3d5ffb --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/bar/src/bar.zig @@ -0,0 +1 @@ +pub const value: u32 = 9; diff --git a/zig/tests/integration_tests/packages/fixtures/host/src/root.zig b/zig/tests/integration_tests/packages/fixtures/host/src/root.zig index fee1d4d7..43dae3a0 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/src/root.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/src/root.zig @@ -1,3 +1,4 @@ const foo = @import("foo"); +const bar = @import("bar"); -pub const value = foo.value; +pub const value = foo.value + bar.value; From fb1d21dc22beb3f37e56100737dfdc4a3fb25dcf Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 20:06:26 +0200 Subject: [PATCH 41/77] handle per module dependency import name remapping --- zig/private/configurer.zig | 9 +++- zig/private/repo/zig_package.bzl | 48 ++++++++++++++----- .../packages/fixtures/host/build.zig | 2 +- .../packages/fixtures/host/libs/foo/build.zig | 2 +- .../fixtures/host/libs/foo/src/foo.zig | 2 +- .../packages/fixtures/host/src/root.zig | 4 +- 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig index 363b6159..f26ff621 100644 --- a/zig/private/configurer.zig +++ b/zig/private/configurer.zig @@ -14,11 +14,14 @@ //! The emitted JSON has the shape: //! //! {"modules": [{"name": ..., "package": , "root_source": ..., -//! "imports": [{"name": ..., "package": }]}]} +//! "imports": [{"name": ..., "module": ..., "package": }]}]} //! //! A module's `package` is the Zig hash (or sub-tree key) of the package that //! owns it, or the empty string for the root package being configured. An -//! import's `package` identifies the owner of the imported module likewise. +//! import's `package` identifies the owner of the imported module likewise. An +//! import's `name` is the name the importer uses (`@import(name)`), while its +//! `module` is the imported module's own registered name; the two differ when a +//! module is imported under an alias. const std = @import("std"); const Io = std.Io; @@ -134,6 +137,8 @@ fn emit(arena: Allocator, io: Io, builder: *Build) !void { try json.beginObject(); try json.objectField("name"); try json.write(import_name); + try json.objectField("module"); + try json.write(moduleName(imported)); try json.objectField("package"); try json.write(imported.owner.pkg_hash); try json.endObject(); diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 2a8b73de..0839ac7c 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -97,6 +97,7 @@ zig_library( import_name = "{name}", srcs = glob(["**/*.zig"], exclude = ["{main}"]), deps = {deps}, + import_names = {import_names}, visibility = ["//visibility:public"], ) """ @@ -108,30 +109,34 @@ zig_library( import_name = "{import_name}", srcs = glob(["{subpath}/**/*.zig"], exclude = ["{main}"]), deps = {deps}, + import_names = {import_names}, visibility = ["//visibility:public"], ) """ -def _target_name(packages, owner, name): +def _is_subtree(packages, owner): + return bool(owner) and owner in packages and packages[owner]["path"] != None + +def _target_name(packages, owner, module): # Root-package modules keep their bare name (the spoke's public API). In-tree # sub-tree modules are namespaced by their sub-path so that identically named - # modules in different sub-trees do not collide; their `import_name` stays the - # bare module name, which is what importing code uses. + # modules in different sub-trees do not collide. Targets are keyed by the + # module's own name, independent of the (possibly aliased) name it is imported + # under. if not owner: - return name - return packages[owner]["path"] + "/" + name + return module + return packages[owner]["path"] + "/" + module def _module_dep(repository_ctx, imported, packages): key = imported["package"] - if key and key in packages and packages[key]["path"] != None: + if _is_subtree(packages, key): # An in-tree (sub-tree) module is generated as a sibling in this spoke. - return ":" + _target_name(packages, key, imported["name"]) + return ":" + _target_name(packages, key, imported["module"]) if key: - # A cross-package import resolves to the module of the same name in the - # dependency's spoke. + # A cross-package import resolves to the module in the dependency's spoke. spoke = repository_ctx.attr.dep_build_files[key] - return str(spoke.same_package_label(imported["name"])) - return ":" + imported["name"] + return str(spoke.same_package_label(imported["module"])) + return ":" + imported["module"] def _build_file(repository_ctx, modules, packages): chunks = ["load(\"@rules_zig//zig:defs.bzl\", \"zig_library\")", "", _FILES] @@ -144,14 +149,30 @@ def _build_file(repository_ctx, modules, packages): # in-tree sub-tree path dependency becomes a library scoped to its sub-path; # a module owned by a URL dependency lives in its own spoke and is skipped. owner = module["package"] - deps = [_module_dep(repository_ctx, imported, packages) for imported in module["imports"]] + if owner and not _is_subtree(packages, owner): + continue + if not module["name"]: + fail("Cannot expose an anonymous Zig module (one declared without `b.addModule`) of '{}'.".format(repository_ctx.attr.url)) + + # A dependency is imported under its own name by default; an import that + # uses a different name is remapped per-edge via `import_names`, so the + # same module can be imported under different names by different modules. + deps = [] + import_names = {} + for imported in module["imports"]: + target = _module_dep(repository_ctx, imported, packages) + deps.append(target) + if imported["name"] != imported["module"]: + import_names[target] = imported["name"] + if not owner: chunks.append(_ZIG_LIBRARY.format( name = module["name"], main = module["root_source"], deps = json.encode(deps), + import_names = json.encode(import_names), )) - elif owner in packages and packages[owner]["path"] != None: + else: subpath = packages[owner]["path"] chunks.append(_ZIG_LIBRARY_SUBTREE.format( name = _target_name(packages, owner, module["name"]), @@ -159,6 +180,7 @@ def _build_file(repository_ctx, modules, packages): main = subpath + "/" + module["root_source"], subpath = subpath, deps = json.encode(deps), + import_names = json.encode(import_names), )) return "\n".join(chunks) diff --git a/zig/tests/integration_tests/packages/fixtures/host/build.zig b/zig/tests/integration_tests/packages/fixtures/host/build.zig index c8c30992..9cd47023 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/build.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/build.zig @@ -5,5 +5,5 @@ pub fn build(b: *std.Build) void { const bar = b.dependency("bar", .{}); const mod = b.addModule("host", .{ .root_source_file = b.path("src/root.zig") }); mod.addImport("foo", foo.module("foo")); - mod.addImport("bar", bar.module("bar")); + mod.addImport("barlib", bar.module("bar")); } diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig index a0152605..94503b0d 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig @@ -3,5 +3,5 @@ pub fn build(b: *@import("std").Build) void { const leaf = b.dependency("leaf", .{}); const foo = b.addModule("foo", .{ .root_source_file = b.path("src/foo.zig") }); foo.addImport("bar", bar.module("bar")); - foo.addImport("leaf", leaf.module("leaf")); + foo.addImport("leaflib", leaf.module("leaf")); } diff --git a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig index eee8a230..c897c965 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig @@ -1,4 +1,4 @@ const bar = @import("bar"); -const leaf = @import("leaf"); +const leaf = @import("leaflib"); pub const value: u32 = bar.value + leaf.value + 42; diff --git a/zig/tests/integration_tests/packages/fixtures/host/src/root.zig b/zig/tests/integration_tests/packages/fixtures/host/src/root.zig index 43dae3a0..77d6862b 100644 --- a/zig/tests/integration_tests/packages/fixtures/host/src/root.zig +++ b/zig/tests/integration_tests/packages/fixtures/host/src/root.zig @@ -1,4 +1,4 @@ const foo = @import("foo"); -const bar = @import("bar"); +const barlib = @import("barlib"); -pub const value = foo.value + bar.value; +pub const value = foo.value + barlib.value; From 915ade0997ab57018ad11fb2a876ab3a640c16ab Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 20:54:09 +0200 Subject: [PATCH 42/77] handle anonymous modules --- zig/private/configurer.zig | 15 +++++++++------ zig/private/repo/zig_package.bzl | 2 -- .../packages/fixtures/multi/build.zig | 3 +++ .../packages/fixtures/multi/src/internal.zig | 1 + .../packages/fixtures/multi/src/multi.zig | 3 ++- 5 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/multi/src/internal.zig diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig index f26ff621..8936601e 100644 --- a/zig/private/configurer.zig +++ b/zig/private/configurer.zig @@ -101,14 +101,17 @@ fn collect(arena: Allocator, modules: *ModuleSet, module: *Build.Module) !void { for (module.import_table.values()) |imported| try collect(arena, modules, imported); } -/// The name a module is registered under in its owning package, or the empty -/// string for an anonymous module (which our generated targets do not expose). -fn moduleName(module: *Build.Module) []const u8 { +/// The name a module is registered under in its owning package. Anonymous +/// modules (created via `b.createModule` rather than `b.addModule`) have no +/// registered name, so synthesize a stable one from the module's position in the +/// deduplicated set; its generated target and every import edge referencing it go +/// through this function and so agree. +fn moduleName(arena: Allocator, modules: *const ModuleSet, module: *Build.Module) ![]const u8 { var it = module.owner.modules.iterator(); while (it.next()) |entry| { if (entry.value_ptr.* == module) return entry.key_ptr.*; } - return ""; + return std.fmt.allocPrint(arena, "__anon_{d}", .{modules.getIndex(module).?}); } fn emit(arena: Allocator, io: Io, builder: *Build) !void { @@ -126,7 +129,7 @@ fn emit(arena: Allocator, io: Io, builder: *Build) !void { for (modules.keys()) |module| { try json.beginObject(); try json.objectField("name"); - try json.write(moduleName(module)); + try json.write(try moduleName(arena, &modules, module)); try json.objectField("package"); try json.write(module.owner.pkg_hash); try json.objectField("root_source"); @@ -138,7 +141,7 @@ fn emit(arena: Allocator, io: Io, builder: *Build) !void { try json.objectField("name"); try json.write(import_name); try json.objectField("module"); - try json.write(moduleName(imported)); + try json.write(try moduleName(arena, &modules, imported)); try json.objectField("package"); try json.write(imported.owner.pkg_hash); try json.endObject(); diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 0839ac7c..2bafb4bb 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -151,8 +151,6 @@ def _build_file(repository_ctx, modules, packages): owner = module["package"] if owner and not _is_subtree(packages, owner): continue - if not module["name"]: - fail("Cannot expose an anonymous Zig module (one declared without `b.addModule`) of '{}'.".format(repository_ctx.attr.url)) # A dependency is imported under its own name by default; an import that # uses a different name is remapped per-edge via `import_names`, so the diff --git a/zig/tests/integration_tests/packages/fixtures/multi/build.zig b/zig/tests/integration_tests/packages/fixtures/multi/build.zig index 1bddcc69..40cc4c8e 100644 --- a/zig/tests/integration_tests/packages/fixtures/multi/build.zig +++ b/zig/tests/integration_tests/packages/fixtures/multi/build.zig @@ -3,6 +3,9 @@ const std = @import("std"); pub fn build(b: *std.Build) void { const widget = b.addModule("widget", .{ .root_source_file = b.path("src/widget.zig") }); + const internal = b.createModule(.{ .root_source_file = b.path("src/internal.zig") }); + const multi = b.addModule("multi", .{ .root_source_file = b.path("src/multi.zig") }); multi.addImport("widget", widget); + multi.addImport("internal", internal); } diff --git a/zig/tests/integration_tests/packages/fixtures/multi/src/internal.zig b/zig/tests/integration_tests/packages/fixtures/multi/src/internal.zig new file mode 100644 index 00000000..a2730532 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/multi/src/internal.zig @@ -0,0 +1 @@ +pub const value: u32 = 99; diff --git a/zig/tests/integration_tests/packages/fixtures/multi/src/multi.zig b/zig/tests/integration_tests/packages/fixtures/multi/src/multi.zig index 3eb42a61..24e109b2 100644 --- a/zig/tests/integration_tests/packages/fixtures/multi/src/multi.zig +++ b/zig/tests/integration_tests/packages/fixtures/multi/src/multi.zig @@ -1,3 +1,4 @@ const widget = @import("widget"); +const internal = @import("internal"); -pub const value: u32 = widget.value + 10; +pub const value: u32 = widget.value + internal.value + 10; From da50a6e3acb55ba1bf0b71746f3d61c7c318ce41 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 22:30:05 +0200 Subject: [PATCH 43/77] test nested path deps in integration tests --- .bazelrc | 4 ++-- zig/tests/integration_tests/packages/MODULE.bazel | 2 ++ zig/tests/integration_tests/packages/build.zig.zon | 3 +++ zig/tests/integration_tests/packages/main.zig | 2 ++ .../packages/path_deps/greeter/BUILD.bazel | 10 ++++++++++ .../packages/path_deps/greeter/build.zig.zon | 11 +++++++++++ .../packages/path_deps/greeter/greeter.zig | 3 +++ .../packages/path_deps/message/BUILD.bazel | 8 ++++++++ .../packages/path_deps/message/build.zig.zon | 8 ++++++++ .../packages/path_deps/message/message.zig | 1 + 10 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 zig/tests/integration_tests/packages/path_deps/greeter/BUILD.bazel create mode 100644 zig/tests/integration_tests/packages/path_deps/greeter/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/path_deps/greeter/greeter.zig create mode 100644 zig/tests/integration_tests/packages/path_deps/message/BUILD.bazel create mode 100644 zig/tests/integration_tests/packages/path_deps/message/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/path_deps/message/message.zig diff --git a/.bazelrc b/.bazelrc index 29ac1488..5a5ec282 100644 --- a/.bazelrc +++ b/.bazelrc @@ -18,8 +18,8 @@ build --workspace_status_command=$(pwd)/workspace_status.sh # To update these lines, execute # `bazel run @rules_bazel_integration_test//tools:update_deleted_packages` # docs: https://bazel.build/reference/command-line-reference#flag--deleted_packages -build --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/child,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles -query --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/child,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles +build --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/child,zig/tests/integration_tests/packages/path_deps/greeter,zig/tests/integration_tests/packages/path_deps/message,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles +query --deleted_packages=e2e/workspace,e2e/workspace/bazel_builtin,e2e/workspace/c-sources,e2e/workspace/canonical-name-module,e2e/workspace/canonical-name-module/other,e2e/workspace/cc-dependencies,e2e/workspace/cc-dependencies/shared-library,e2e/workspace/cc-dependencies/static-library,e2e/workspace/cc-dependencies/static-library-cdeps,e2e/workspace/configure-mode,e2e/workspace/configure-target,e2e/workspace/configure-threaded,e2e/workspace/configure-use_cc_common_link,e2e/workspace/configure-use_cc_common_link/shared-library,e2e/workspace/configure-use_cc_common_link/static-library,e2e/workspace/configure-version,e2e/workspace/data-dependencies,e2e/workspace/embed-file,e2e/workspace/env-attr,e2e/workspace/import-name-attr,e2e/workspace/import-names-attr,e2e/workspace/include-dependencies,e2e/workspace/include-dependencies/zig-include,e2e/workspace/include-dependencies/zig-include-define,e2e/workspace/include-dependencies/zig-include-isystem,e2e/workspace/include-dependencies/zig-std-include,e2e/workspace/link-dependencies,e2e/workspace/link-dependencies/shared-library,e2e/workspace/link-dependencies/static-library,e2e/workspace/linker-script,e2e/workspace/linkopts-attr,e2e/workspace/location-expansion,e2e/workspace/multiple-sources-and-packages-test,e2e/workspace/multiple-sources-binary,e2e/workspace/root-module-from-single-dependency,e2e/workspace/runfiles-library,e2e/workspace/runfiles-library/dependency,e2e/workspace/runfiles-library/dependency/transitive-dependency,e2e/workspace/simple-binary,e2e/workspace/simple-cmake-library,e2e/workspace/simple-library,e2e/workspace/simple-shared-library,e2e/workspace/simple-test,e2e/workspace/test-runner-attr,e2e/workspace/third_party/arocc,e2e/workspace/third_party/translate-c,e2e/workspace/toolchain-glibc-version,e2e/workspace/transitive-zig-modules-binary,e2e/workspace/transitive-zig-modules-binary/hello-world,e2e/workspace/transitive-zig-modules-binary/hello-world/data,e2e/workspace/transitive-zig-modules-binary/hello-world/data/hello,e2e/workspace/transitive-zig-modules-binary/hello-world/data/world,e2e/workspace/transitive-zig-modules-binary/hello-world/io,e2e/workspace/translate-c/transitive-cc-library-zig-binary,e2e/workspace/zig-docs,e2e/workspace/zig-header,e2e/workspace/zig-module-binary,e2e/workspace/zig-module-binary/data,e2e/workspace/zig-module-binary/io,e2e/workspace/zig-package-import/app,e2e/workspace/zig-package-import/greet,zig/tests/integration_tests/minimal,zig/tests/integration_tests/mirrors,zig/tests/integration_tests/packages,zig/tests/integration_tests/packages/child,zig/tests/integration_tests/packages/path_deps/greeter,zig/tests/integration_tests/packages/path_deps/message,zig/tests/integration_tests/packages/tools,zig/tests/integration_tests/workspace,zig/tests/integration_tests/workspace/custom_interpreter,zig/tests/integration_tests/workspace/env-attr,zig/tests/integration_tests/workspace/runfiles # Load any settings specific to the current user. # Place settings that should affect the integration tests into `.bazelrc.ic.user`. diff --git a/zig/tests/integration_tests/packages/MODULE.bazel b/zig/tests/integration_tests/packages/MODULE.bazel index e361d877..fc133bd6 100644 --- a/zig/tests/integration_tests/packages/MODULE.bazel +++ b/zig/tests/integration_tests/packages/MODULE.bazel @@ -25,4 +25,6 @@ use_repo(zig, "zig_toolchains") zig_packages = use_extension("@rules_zig//zig:packages.bzl", "zig_packages") zig_packages.from_file(build_zig_zon = "//:build.zig.zon") +zig_packages.from_file(build_zig_zon = "//path_deps/greeter:build.zig.zon") +zig_packages.from_file(build_zig_zon = "//path_deps/message:build.zig.zon") use_repo(zig_packages, "zig_deps") diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 8bf84c45..c634624d 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -19,6 +19,9 @@ .url = "__MULTI_URL__", .hash = "__MULTI_HASH__", }, + .greeter = .{ + .path = "path_deps/greeter", + }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 4befdebd..5b6284c7 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -4,6 +4,7 @@ const top = @import("top"); const child = @import("child"); const multi = @import("multi"); const widget = @import("widget"); +const greeter = @import("greeter"); pub fn main() void { _ = leaf.value; @@ -12,4 +13,5 @@ pub fn main() void { _ = child.value; _ = multi.value; _ = widget.value; + _ = greeter.value; } diff --git a/zig/tests/integration_tests/packages/path_deps/greeter/BUILD.bazel b/zig/tests/integration_tests/packages/path_deps/greeter/BUILD.bazel new file mode 100644 index 00000000..d7e0419f --- /dev/null +++ b/zig/tests/integration_tests/packages/path_deps/greeter/BUILD.bazel @@ -0,0 +1,10 @@ +load("@rules_zig//zig:defs.bzl", "zig_library") +load("@zig_deps//:defs.bzl", "zig_deps") + +zig_library( + name = "greeter", + import_name = "greeter", + main = "greeter.zig", + visibility = ["//visibility:public"], + deps = zig_deps(), +) diff --git a/zig/tests/integration_tests/packages/path_deps/greeter/build.zig.zon b/zig/tests/integration_tests/packages/path_deps/greeter/build.zig.zon new file mode 100644 index 00000000..90a62795 --- /dev/null +++ b/zig/tests/integration_tests/packages/path_deps/greeter/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .greeter, + .version = "0.0.0", + .dependencies = .{ + .message = .{ .path = "../message" }, + }, + .paths = .{ + "build.zig.zon", + "greeter.zig", + }, +} diff --git a/zig/tests/integration_tests/packages/path_deps/greeter/greeter.zig b/zig/tests/integration_tests/packages/path_deps/greeter/greeter.zig new file mode 100644 index 00000000..26950a52 --- /dev/null +++ b/zig/tests/integration_tests/packages/path_deps/greeter/greeter.zig @@ -0,0 +1,3 @@ +const message = @import("message"); + +pub const value = message.value; diff --git a/zig/tests/integration_tests/packages/path_deps/message/BUILD.bazel b/zig/tests/integration_tests/packages/path_deps/message/BUILD.bazel new file mode 100644 index 00000000..1d3e50e7 --- /dev/null +++ b/zig/tests/integration_tests/packages/path_deps/message/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_zig//zig:defs.bzl", "zig_library") + +zig_library( + name = "message", + import_name = "message", + main = "message.zig", + visibility = ["//visibility:public"], +) diff --git a/zig/tests/integration_tests/packages/path_deps/message/build.zig.zon b/zig/tests/integration_tests/packages/path_deps/message/build.zig.zon new file mode 100644 index 00000000..e0996a87 --- /dev/null +++ b/zig/tests/integration_tests/packages/path_deps/message/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .message, + .version = "0.0.0", + .paths = .{ + "build.zig.zon", + "message.zig", + }, +} diff --git a/zig/tests/integration_tests/packages/path_deps/message/message.zig b/zig/tests/integration_tests/packages/path_deps/message/message.zig new file mode 100644 index 00000000..75c2e74c --- /dev/null +++ b/zig/tests/integration_tests/packages/path_deps/message/message.zig @@ -0,0 +1 @@ +pub const value: u32 = 1; From 18931ffbea29fd87ae305f9a19ae5395f92c25f2 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 10:15:30 +0200 Subject: [PATCH 44/77] test Zig package file pruning --- zig/tests/integration_tests/packages/build.zig.zon | 4 ++++ .../packages/fixtures/pruned/build.zig | 3 +++ .../packages/fixtures/pruned/build.zig.zon | 12 ++++++++++++ .../packages/fixtures/pruned/extra.zig | 1 + .../packages/fixtures/pruned/src/pruned.zig | 1 + .../packages/fixtures/pruned/tests/test.zig | 1 + zig/tests/integration_tests/packages/main.zig | 2 ++ .../integration_tests/packages_tests_runner.zig | 3 ++- 8 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/pruned/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/pruned/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/pruned/extra.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/pruned/src/pruned.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/pruned/tests/test.zig diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index c634624d..d0344e2d 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -22,6 +22,10 @@ .greeter = .{ .path = "path_deps/greeter", }, + .pruned = .{ + .url = "__PRUNED_URL__", + .hash = "__PRUNED_HASH__", + }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/fixtures/pruned/build.zig b/zig/tests/integration_tests/packages/fixtures/pruned/build.zig new file mode 100644 index 00000000..dd58807c --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/pruned/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("pruned", .{ .root_source_file = b.path("src/pruned.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/pruned/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/pruned/build.zig.zon new file mode 100644 index 00000000..7b8f0d5c --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/pruned/build.zig.zon @@ -0,0 +1,12 @@ +.{ + .name = .pruned, + .version = "0.0.0", + .fingerprint = 0x750f93ea2c71346e, + // `extra.zig` and `tests/` are deliberately omitted so the packer must prune + // them from the tarball and the hash, matching `zig fetch`. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/pruned/extra.zig b/zig/tests/integration_tests/packages/fixtures/pruned/extra.zig new file mode 100644 index 00000000..5d7797fa --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/pruned/extra.zig @@ -0,0 +1 @@ +pub const excluded = "not packaged"; diff --git a/zig/tests/integration_tests/packages/fixtures/pruned/src/pruned.zig b/zig/tests/integration_tests/packages/fixtures/pruned/src/pruned.zig new file mode 100644 index 00000000..7d8c47b3 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/pruned/src/pruned.zig @@ -0,0 +1 @@ +pub const value: u32 = 13; diff --git a/zig/tests/integration_tests/packages/fixtures/pruned/tests/test.zig b/zig/tests/integration_tests/packages/fixtures/pruned/tests/test.zig new file mode 100644 index 00000000..b55512b5 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/pruned/tests/test.zig @@ -0,0 +1 @@ +pub const unused = 0; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 5b6284c7..7b614872 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -5,6 +5,7 @@ const child = @import("child"); const multi = @import("multi"); const widget = @import("widget"); const greeter = @import("greeter"); +const pruned = @import("pruned"); pub fn main() void { _ = leaf.value; @@ -14,4 +15,5 @@ pub fn main() void { _ = multi.value; _ = widget.value; _ = greeter.value; + _ = pruned.value; } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index dfc099dc..cbd4a0ee 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -24,6 +24,7 @@ const packages = [_]Package{ .{ .name = "right", .patches = &.{.{ .deps = &.{"bottom"} }} }, .{ .name = "top", .patches = &.{.{ .deps = &.{ "left", "right" } }} }, .{ .name = "multi" }, + .{ .name = "pruned" }, }; const Consumer = struct { @@ -33,7 +34,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{"leaf"} }, }; From e678567cbc9f37586a5c451aa068b1dbfc79fb26 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 10:43:16 +0200 Subject: [PATCH 45/77] test separate versions of same package in closure --- zig/tests/integration_tests/packages/build.zig.zon | 4 ++++ .../integration_tests/packages/child/build.zig.zon | 6 ++++++ .../integration_tests/packages/child/src/child.zig | 3 ++- .../packages/fixtures/libv1/build.zig | 3 +++ .../packages/fixtures/libv1/build.zig.zon | 10 ++++++++++ .../packages/fixtures/libv1/src/lib.zig | 3 +++ .../packages/fixtures/libv2/build.zig | 3 +++ .../packages/fixtures/libv2/build.zig.zon | 10 ++++++++++ .../packages/fixtures/libv2/src/lib.zig | 3 +++ zig/tests/integration_tests/packages/main.zig | 2 ++ zig/tests/integration_tests/packages_tests_runner.zig | 6 ++++-- 11 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/libv1/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/libv1/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/libv1/src/lib.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/libv2/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/libv2/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/libv2/src/lib.zig diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index d0344e2d..52bf0e52 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -26,6 +26,10 @@ .url = "__PRUNED_URL__", .hash = "__PRUNED_HASH__", }, + .lib = .{ + .url = "__LIBV1_URL__", + .hash = "__LIBV1_HASH__", + }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/child/build.zig.zon b/zig/tests/integration_tests/packages/child/build.zig.zon index 0fe5898a..51f51de0 100644 --- a/zig/tests/integration_tests/packages/child/build.zig.zon +++ b/zig/tests/integration_tests/packages/child/build.zig.zon @@ -7,6 +7,12 @@ .url = "__LEAF_URL__", .hash = "__LEAF_HASH__", }, + // The same package `lib` as the root module depends on, but a different + // version: the two must resolve to separate spokes (no MVS). + .lib = .{ + .url = "__LIBV2_URL__", + .hash = "__LIBV2_HASH__", + }, }, .paths = .{ "build.zig.zon", diff --git a/zig/tests/integration_tests/packages/child/src/child.zig b/zig/tests/integration_tests/packages/child/src/child.zig index c882efad..2494dbd2 100644 --- a/zig/tests/integration_tests/packages/child/src/child.zig +++ b/zig/tests/integration_tests/packages/child/src/child.zig @@ -1,3 +1,4 @@ const leaf = @import("leaf"); +const lib = @import("lib"); -pub const value: u32 = leaf.value + 100; +pub const value: u32 = leaf.value + lib.v2 + 100; diff --git a/zig/tests/integration_tests/packages/fixtures/libv1/build.zig b/zig/tests/integration_tests/packages/fixtures/libv1/build.zig new file mode 100644 index 00000000..cda0cc35 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/libv1/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("lib", .{ .root_source_file = b.path("src/lib.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/libv1/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/libv1/build.zig.zon new file mode 100644 index 00000000..8b837914 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/libv1/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .lib, + .version = "1.0.0", + .fingerprint = 0xa90f3bccad0f02e5, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/libv1/src/lib.zig b/zig/tests/integration_tests/packages/fixtures/libv1/src/lib.zig new file mode 100644 index 00000000..c4d68974 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/libv1/src/lib.zig @@ -0,0 +1,3 @@ +// Distinct symbol from v2, so a consumer wired to the wrong version fails to +// compile rather than silently picking the other. +pub const v1: u32 = 1; diff --git a/zig/tests/integration_tests/packages/fixtures/libv2/build.zig b/zig/tests/integration_tests/packages/fixtures/libv2/build.zig new file mode 100644 index 00000000..cda0cc35 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/libv2/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("lib", .{ .root_source_file = b.path("src/lib.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/libv2/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/libv2/build.zig.zon new file mode 100644 index 00000000..6e43cddd --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/libv2/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .lib, + .version = "2.0.0", + .fingerprint = 0xa90f3bccad0f02e5, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/libv2/src/lib.zig b/zig/tests/integration_tests/packages/fixtures/libv2/src/lib.zig new file mode 100644 index 00000000..06c7ec96 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/libv2/src/lib.zig @@ -0,0 +1,3 @@ +// Distinct symbol from v1, so a consumer wired to the wrong version fails to +// compile rather than silently picking the other. +pub const v2: u32 = 2; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 7b614872..ebefb447 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -6,6 +6,7 @@ const multi = @import("multi"); const widget = @import("widget"); const greeter = @import("greeter"); const pruned = @import("pruned"); +const lib = @import("lib"); pub fn main() void { _ = leaf.value; @@ -16,4 +17,5 @@ pub fn main() void { _ = widget.value; _ = greeter.value; _ = pruned.value; + _ = lib.v1; } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index cbd4a0ee..67df213a 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -25,6 +25,8 @@ const packages = [_]Package{ .{ .name = "top", .patches = &.{.{ .deps = &.{ "left", "right" } }} }, .{ .name = "multi" }, .{ .name = "pruned" }, + .{ .name = "libv1" }, + .{ .name = "libv2" }, }; const Consumer = struct { @@ -34,8 +36,8 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned" } }, - .{ .manifest = "child/build.zig.zon", .deps = &.{"leaf"} }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1" } }, + .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; test "Zig packages are imported from file:// tarballs" { From 9918273c5828c1f37f55075251bbd156321483a9 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 11:40:35 +0200 Subject: [PATCH 46/77] watch build.zig.zon manifests in the module extension They are read by zon2json, which is opaque to Bazel. --- zig/private/bzlmod/zig_packages.bzl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 670ffa6f..2326dc1b 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -97,6 +97,8 @@ def _zig_packages_impl(module_ctx): for mod in module_ctx.modules: for tag in mod.tags.from_file: manifest = module_ctx.path(tag.build_zig_zon) + + module_ctx.watch(manifest) manifests.append(manifest) manifest_labels[str(manifest)] = str(tag.build_zig_zon) root_tags.append(tag.build_zig_zon) From b92f3297bc082ee73b478f1945818e897f0511b6 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 13:13:51 +0200 Subject: [PATCH 47/77] package integration failure tests --- .../packages_tests_runner.zig | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index 67df213a..b5baa630 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -83,6 +83,53 @@ test "Zig packages are imported from file:// tarballs" { try std.testing.expect(result.success); } +// Runs after the positive test above, tarballs are packed and consumer +// manifests are patched. Breaks one thing at a time, asserts the build fails as +// expected, and restores the original. +test "the importer rejects invalid package configurations" { + const ctx = try BitContext.init(); + defer ctx.deinit(); + + // A declared hash that does not match the fetched package. + try ctx.patchWorkspaceFile("build.zig.zon", &.{.{ "leaf-0.0.0-", "leaf-0.0.0-x" }}); + try expectBuildFailure(ctx, "hash mismatch"); + try ctx.patchWorkspaceFile("build.zig.zon", &.{.{ "leaf-0.0.0-x", "leaf-0.0.0-" }}); + + // A path dependency whose `build.zig.zon` is not provided via `from_file`. + const greeter_manifest = "path_deps/greeter/build.zig.zon"; + try ctx.patchWorkspaceFile(greeter_manifest, &.{.{ "../message", "../../fixtures/leaf" }}); + try expectBuildFailure(ctx, "has no provided manifest"); + try ctx.patchWorkspaceFile(greeter_manifest, &.{.{ "../../fixtures/leaf", "../message" }}); + + // `zig_dep` referencing a dependency the manifest does not declare. + const greeter_build = "path_deps/greeter/BUILD.bazel"; + try ctx.patchWorkspaceFile(greeter_build, &.{ + .{ "\"zig_deps\")", "\"zig_dep\", \"zig_deps\")" }, + .{ "deps = zig_deps()", "deps = [zig_dep(\"nonexistent\")]" }, + }); + try expectBuildFailure(ctx, "declares no dependency"); + try ctx.patchWorkspaceFile(greeter_build, &.{ + .{ "\"zig_dep\", \"zig_deps\")", "\"zig_deps\")" }, + .{ "deps = [zig_dep(\"nonexistent\")]", "deps = zig_deps()" }, + }); +} + +fn expectBuildFailure(ctx: BitContext, expected: []const u8) !void { + const result = try ctx.exec_bazel(.{ + .argv = &[_][]const u8{ "build", "//:binary" }, + .print_on_error = false, + }); + defer result.deinit(); + if (result.success) { + std.debug.print("expected build to FAIL (mentioning '{s}') but it succeeded\n", .{expected}); + return error.BuildUnexpectedlySucceeded; + } + if (std.mem.indexOf(u8, result.stderr, expected) == null) { + std.debug.print("expected build failure mentioning '{s}', stderr:\n{s}\n", .{ expected, result.stderr }); + return error.UnexpectedFailureMessage; + } +} + fn depReplacements( allocator: std.mem.Allocator, deps: []const []const u8, From b7d0af3d0a11334e3c615361991db9829ad43483 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 14:20:55 +0200 Subject: [PATCH 48/77] add a git+https test case --- .../zig-package-import/app/BUILD.bazel | 22 +++++++++++++++++++ .../zig-package-import/app/build.zig.zon | 5 +++++ .../zig-package-import/app/output.expected | 2 ++ .../zig-package-import/app/src/main.zig | 13 ++++++++++- 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 e2e/workspace/zig-package-import/app/output.expected diff --git a/e2e/workspace/zig-package-import/app/BUILD.bazel b/e2e/workspace/zig-package-import/app/BUILD.bazel index 06f20237..0b241f20 100644 --- a/e2e/workspace/zig-package-import/app/BUILD.bazel +++ b/e2e/workspace/zig-package-import/app/BUILD.bazel @@ -1,3 +1,5 @@ +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("@bazel_skylib//rules:diff_test.bzl", "diff_test") load("@rules_zig//zig:defs.bzl", "zig_binary") load("@zig_deps//:defs.bzl", "zig_deps") @@ -10,3 +12,23 @@ zig_binary( main = "src/main.zig", deps = zig_deps(), ) + +genrule( + name = "output", + outs = ["output.actual"], + cmd = "$(execpath :app) > $(OUTS)", + tools = [":app"], +) + +diff_test( + name = "output_test", + size = "small", + file1 = ":output.expected", + file2 = ":output.actual", +) + +build_test( + name = "build_test", + size = "small", + targets = [":app"], +) diff --git a/e2e/workspace/zig-package-import/app/build.zig.zon b/e2e/workspace/zig-package-import/app/build.zig.zon index 04034b9d..5188b2de 100644 --- a/e2e/workspace/zig-package-import/app/build.zig.zon +++ b/e2e/workspace/zig-package-import/app/build.zig.zon @@ -11,6 +11,11 @@ .greet = .{ .path = "../greet", }, + .cbor = .{ + // Intentionally a git+https URL to test that fetch mechanism. + .url = "git+https://github.com/neurocyte/cbor.git#0d9cab516a1508c832c685183aeac0365b9a224a", + .hash = "cbor-1.3.0-RcQE_Mx9AQBfuyJs1eAURJfqdG_5vTJ-VqYCNgpdXrHJ", + }, }, .paths = .{ "build.zig", diff --git a/e2e/workspace/zig-package-import/app/output.expected b/e2e/workspace/zig-package-import/app/output.expected new file mode 100644 index 00000000..f5c0ccc7 --- /dev/null +++ b/e2e/workspace/zig-package-import/app/output.expected @@ -0,0 +1,2 @@ +Hello world! +cbor: 13 bytes diff --git a/e2e/workspace/zig-package-import/app/src/main.zig b/e2e/workspace/zig-package-import/app/src/main.zig index 432dd19e..253f210a 100644 --- a/e2e/workspace/zig-package-import/app/src/main.zig +++ b/e2e/workspace/zig-package-import/app/src/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const clap = @import("clap"); const greet = @import("greet"); +const cbor = @import("cbor"); pub fn main(init: std.process.Init) !void { var diag = clap.Diagnostic{}; @@ -18,5 +19,15 @@ pub fn main(init: std.process.Init) !void { const message = try greet.greeting(init.gpa, res); defer init.gpa.free(message); - std.debug.print("{s}\n", .{message}); + + // CBOR-encode the greeting using the git+https `cbor` dependency. + var cbor_buf: [256]u8 = undefined; + const encoded = cbor.fmt(&cbor_buf, message); + + var stdout_buf: [512]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(init.io, &stdout_buf); + const stdout = &stdout_writer.interface; + try stdout.print("{s}\n", .{message}); + try stdout.print("cbor: {d} bytes\n", .{encoded.len}); + try stdout.flush(); } From 088e36b72de6938ee59e87c0aac68bd4dde100f3 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 15:10:16 +0200 Subject: [PATCH 49/77] test eager fetching of lazy dependencies Zig packages support lazy dependencies that are only fetched when demanded during `build.zig`. Bazel repo rules do not support this type of laziness, repo rules themselves are fetched lazily, but only at repo granularity. So, we fetch eagerly here. --- .../integration_tests/packages/build.zig.zon | 6 ++++++ .../packages/fixtures/lazyhost/build.zig | 8 ++++++++ .../packages/fixtures/lazyhost/build.zig.zon | 17 +++++++++++++++++ .../packages/fixtures/lazyhost/src/lazyhost.zig | 3 +++ .../packages/fixtures/lazyleaf/build.zig | 3 +++ .../packages/fixtures/lazyleaf/build.zig.zon | 10 ++++++++++ .../packages/fixtures/lazyleaf/src/lazyleaf.zig | 1 + zig/tests/integration_tests/packages/main.zig | 2 ++ .../integration_tests/packages_tests_runner.zig | 4 +++- 9 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/lazyhost/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/lazyhost/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/lazyhost/src/lazyhost.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/lazyleaf/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/lazyleaf/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/lazyleaf/src/lazyleaf.zig diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 52bf0e52..8ea6547d 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -30,6 +30,12 @@ .url = "__LIBV1_URL__", .hash = "__LIBV1_HASH__", }, + // Eager dependency on a package that has a *lazy* dependency; verifies + // that the importer fetches lazy deps eagerly. + .lazyhost = .{ + .url = "__LAZYHOST_URL__", + .hash = "__LAZYHOST_HASH__", + }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/fixtures/lazyhost/build.zig b/zig/tests/integration_tests/packages/fixtures/lazyhost/build.zig new file mode 100644 index 00000000..a0650d9e --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/lazyhost/build.zig @@ -0,0 +1,8 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const mod = b.addModule("lazyhost", .{ .root_source_file = b.path("src/lazyhost.zig") }); + if (b.lazyDependency("lazyleaf", .{})) |lazyleaf| { + mod.addImport("lazyleaf", lazyleaf.module("lazyleaf")); + } +} diff --git a/zig/tests/integration_tests/packages/fixtures/lazyhost/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/lazyhost/build.zig.zon new file mode 100644 index 00000000..c2e1d2d8 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/lazyhost/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .lazyhost, + .version = "0.0.0", + .fingerprint = 0x7fbf4d34c537a82c, + .dependencies = .{ + .lazyleaf = .{ + .url = "__LAZYLEAF_URL__", + .hash = "__LAZYLEAF_HASH__", + .lazy = true, + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/lazyhost/src/lazyhost.zig b/zig/tests/integration_tests/packages/fixtures/lazyhost/src/lazyhost.zig new file mode 100644 index 00000000..6bfe0f57 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/lazyhost/src/lazyhost.zig @@ -0,0 +1,3 @@ +const lazyleaf = @import("lazyleaf"); + +pub const value: u32 = lazyleaf.value + 1; diff --git a/zig/tests/integration_tests/packages/fixtures/lazyleaf/build.zig b/zig/tests/integration_tests/packages/fixtures/lazyleaf/build.zig new file mode 100644 index 00000000..bb211d01 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/lazyleaf/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("lazyleaf", .{ .root_source_file = b.path("src/lazyleaf.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/lazyleaf/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/lazyleaf/build.zig.zon new file mode 100644 index 00000000..a1089191 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/lazyleaf/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .lazyleaf, + .version = "0.0.0", + .fingerprint = 0x76075e2e0af3a2a5, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/lazyleaf/src/lazyleaf.zig b/zig/tests/integration_tests/packages/fixtures/lazyleaf/src/lazyleaf.zig new file mode 100644 index 00000000..41abe6da --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/lazyleaf/src/lazyleaf.zig @@ -0,0 +1 @@ +pub const value: u32 = 2000; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index ebefb447..0d844f9d 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -7,6 +7,7 @@ const widget = @import("widget"); const greeter = @import("greeter"); const pruned = @import("pruned"); const lib = @import("lib"); +const lazyhost = @import("lazyhost"); pub fn main() void { _ = leaf.value; @@ -18,4 +19,5 @@ pub fn main() void { _ = greeter.value; _ = pruned.value; _ = lib.v1; + _ = lazyhost.value; } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index b5baa630..2efb4f7b 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -27,6 +27,8 @@ const packages = [_]Package{ .{ .name = "pruned" }, .{ .name = "libv1" }, .{ .name = "libv2" }, + .{ .name = "lazyleaf" }, + .{ .name = "lazyhost", .patches = &.{.{ .deps = &.{"lazyleaf"} }} }, }; const Consumer = struct { @@ -36,7 +38,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; From 6ef4377553f191de23dbfdaea1502c3a8ccb565b Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 15:27:11 +0200 Subject: [PATCH 50/77] test zig package symlink handling --- .../integration_tests/integration_testing.zig | 19 +++++++++++++++++++ .../integration_tests/packages/build.zig.zon | 4 ++++ .../packages/fixtures/symlinked/build.zig | 3 +++ .../packages/fixtures/symlinked/build.zig.zon | 10 ++++++++++ .../packages/fixtures/symlinked/src/real.zig | 1 + .../fixtures/symlinked/src/symlinked.zig | 3 +++ zig/tests/integration_tests/packages/main.zig | 2 ++ .../packages_tests_runner.zig | 9 ++++++++- 8 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/symlinked/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/symlinked/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/symlinked/src/real.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/symlinked/src/symlinked.zig diff --git a/zig/tests/integration_tests/integration_testing.zig b/zig/tests/integration_tests/integration_testing.zig index 04c0afef..0b0245d6 100644 --- a/zig/tests/integration_tests/integration_testing.zig +++ b/zig/tests/integration_tests/integration_testing.zig @@ -223,6 +223,25 @@ pub const BitContext = struct { try writer.interface.flush(); } + /// Create a symlink at `sym_link_sub_path` pointing to `target`. Used to + /// introduce an in-package symlink at test time, since the harness stages the + /// workspace by dereferencing symlinks. + pub const symLinkWorkspaceFile = if (is_zig_0_16_or_later) symLinkWorkspaceFile_016 else symLinkWorkspaceFile_pre_016; + + fn symLinkWorkspaceFile_pre_016(self: BitContext, target: []const u8, sym_link_sub_path: []const u8) !void { + var workspace = try self.openWorkspace(); + defer closeWorkspaceDir(&workspace); + workspace.deleteFile(sym_link_sub_path) catch {}; + try workspace.symLink(target, sym_link_sub_path, .{}); + } + + fn symLinkWorkspaceFile_016(self: BitContext, target: []const u8, sym_link_sub_path: []const u8) !void { + var workspace = try self.openWorkspace(); + defer closeWorkspaceDir(&workspace); + workspace.deleteFile(std.testing.io, sym_link_sub_path) catch {}; + try workspace.symLink(std.testing.io, target, sym_link_sub_path, .{}); + } + /// Replace each `needle` with its `replacement` in a workspace file, writing /// a fresh file so the original source (a symlink in the test sandbox) is /// never modified. diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 8ea6547d..57553d6a 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -36,6 +36,10 @@ .url = "__LAZYHOST_URL__", .hash = "__LAZYHOST_HASH__", }, + .symlinked = .{ + .url = "__SYMLINKED_URL__", + .hash = "__SYMLINKED_HASH__", + }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/fixtures/symlinked/build.zig b/zig/tests/integration_tests/packages/fixtures/symlinked/build.zig new file mode 100644 index 00000000..f9c44c90 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/symlinked/build.zig @@ -0,0 +1,3 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("symlinked", .{ .root_source_file = b.path("src/symlinked.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/symlinked/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/symlinked/build.zig.zon new file mode 100644 index 00000000..bfdab3bf --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/symlinked/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .symlinked, + .version = "0.0.0", + .fingerprint = 0x5063086f099ee769, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/symlinked/src/real.zig b/zig/tests/integration_tests/packages/fixtures/symlinked/src/real.zig new file mode 100644 index 00000000..ab446f23 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/symlinked/src/real.zig @@ -0,0 +1 @@ +pub const value: u32 = 3000; diff --git a/zig/tests/integration_tests/packages/fixtures/symlinked/src/symlinked.zig b/zig/tests/integration_tests/packages/fixtures/symlinked/src/symlinked.zig new file mode 100644 index 00000000..62f73c84 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/symlinked/src/symlinked.zig @@ -0,0 +1,3 @@ +const aliased = @import("aliased.zig"); + +pub const value: u32 = aliased.value; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 0d844f9d..a70303ed 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -8,6 +8,7 @@ const greeter = @import("greeter"); const pruned = @import("pruned"); const lib = @import("lib"); const lazyhost = @import("lazyhost"); +const symlinked = @import("symlinked"); pub fn main() void { _ = leaf.value; @@ -20,4 +21,5 @@ pub fn main() void { _ = pruned.value; _ = lib.v1; _ = lazyhost.value; + _ = symlinked.value; } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index 2efb4f7b..7a7008f8 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -12,6 +12,7 @@ const Patch = struct { const Package = struct { name: []const u8, patches: []const Patch = &.{}, + symlink: ?[2][]const u8 = null, }; // Packed in topological order (dependencies first). @@ -29,6 +30,7 @@ const packages = [_]Package{ .{ .name = "libv2" }, .{ .name = "lazyleaf" }, .{ .name = "lazyhost", .patches = &.{.{ .deps = &.{"lazyleaf"} }} }, + .{ .name = "symlinked", .symlink = .{ "real.zig", "src/aliased.zig" } }, }; const Consumer = struct { @@ -38,7 +40,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; @@ -59,6 +61,11 @@ test "Zig packages are imported from file:// tarballs" { try ctx.patchWorkspaceFile(manifest, try depReplacements(allocator, patch.deps, &urls, &hashes)); } + if (pkg.symlink) |link| { + const link_path = try std.fmt.allocPrint(allocator, "fixtures/{s}/{s}", .{ pkg.name, link[1] }); + try ctx.symLinkWorkspaceFile(link[0], link_path); + } + const dir = try std.fmt.allocPrint(allocator, "{s}/fixtures/{s}", .{ ctx.workspace_path, pkg.name }); const tarball = try std.fmt.allocPrint(allocator, "{s}/{s}.tar", .{ ctx.workspace_path, pkg.name }); const pack = try ctx.exec_bazel(.{ From 3f7e24aabe195375257f849c676561b8033ecaf3 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 16:16:56 +0200 Subject: [PATCH 51/77] mark source only deps as explicitly unsupported add a test case to confirm the error message --- zig/private/repo/zig_package.bzl | 7 +++++++ zig/tests/integration_tests/packages/build.zig.zon | 2 ++ .../packages/fixtures/srconly/build.zig | 5 +++++ .../packages/fixtures/srconly/build.zig.zon | 14 ++++++++++++++ .../fixtures/srconly/libs/data/build.zig.zon | 9 +++++++++ .../packages/fixtures/srconly/libs/data/data.zig | 1 + .../packages/fixtures/srconly/src/root.zig | 1 + .../integration_tests/packages_tests_runner.zig | 7 ++++++- 8 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/srconly/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/srconly/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/srconly/libs/data/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/srconly/libs/data/data.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/srconly/src/root.zig diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 2bafb4bb..ae85e84a 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -187,6 +187,13 @@ def _configure(repository_ctx, zig, build_zig, cache): configurer = repository_ctx.path(Label("//zig/private:configurer.zig")) deps = json.decode(repository_ctx.attr.deps) + for key in sorted(deps["packages"]): + package = deps["packages"][key] + if package["path"] != None and not repository_ctx.path(package["path"] + "/build.zig").exists: + fail(("The Zig package '{}' has a source-only dependency at '{}' (a " + + "`build.zig.zon` with no `build.zig`); source-only dependencies " + + "are not supported.").format(repository_ctx.attr.url, package["path"])) + repository_ctx.file("_configure/deps.zig", _dependencies_source(repository_ctx, deps)) keys = sorted(deps["packages"]) diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 57553d6a..c8fa8899 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -40,6 +40,8 @@ .url = "__SYMLINKED_URL__", .hash = "__SYMLINKED_HASH__", }, + // A source-only sub-tree dependency; only enabled by the test. + // .srconly = .{ .url = "__SRCONLY_URL__", .hash = "__SRCONLY_HASH__" }, }, .paths = .{ "build.zig", diff --git a/zig/tests/integration_tests/packages/fixtures/srconly/build.zig b/zig/tests/integration_tests/packages/fixtures/srconly/build.zig new file mode 100644 index 00000000..09c03d5c --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/srconly/build.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + _ = b.addModule("srconly", .{ .root_source_file = b.path("src/root.zig") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/srconly/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/srconly/build.zig.zon new file mode 100644 index 00000000..def03386 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/srconly/build.zig.zon @@ -0,0 +1,14 @@ +.{ + .name = .srconly, + .version = "0.0.0", + .fingerprint = 0x190958d3eade8856, + .dependencies = .{ + .data = .{ .path = "libs/data" }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "libs", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/srconly/libs/data/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/srconly/libs/data/build.zig.zon new file mode 100644 index 00000000..17503507 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/srconly/libs/data/build.zig.zon @@ -0,0 +1,9 @@ +.{ + .name = .data, + .version = "0.0.0", + .fingerprint = 0x190958d3d1799fc3, + .paths = .{ + "build.zig.zon", + "data.zig", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/srconly/libs/data/data.zig b/zig/tests/integration_tests/packages/fixtures/srconly/libs/data/data.zig new file mode 100644 index 00000000..ef1c4a67 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/srconly/libs/data/data.zig @@ -0,0 +1 @@ +pub const value: u32 = 7; diff --git a/zig/tests/integration_tests/packages/fixtures/srconly/src/root.zig b/zig/tests/integration_tests/packages/fixtures/srconly/src/root.zig new file mode 100644 index 00000000..a2730532 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/srconly/src/root.zig @@ -0,0 +1 @@ +pub const value: u32 = 99; diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index 7a7008f8..92b19696 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -31,6 +31,7 @@ const packages = [_]Package{ .{ .name = "lazyleaf" }, .{ .name = "lazyhost", .patches = &.{.{ .deps = &.{"lazyleaf"} }} }, .{ .name = "symlinked", .symlink = .{ "real.zig", "src/aliased.zig" } }, + .{ .name = "srconly" }, }; const Consumer = struct { @@ -40,7 +41,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "srconly" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; @@ -121,6 +122,10 @@ test "the importer rejects invalid package configurations" { .{ "\"zig_dep\", \"zig_deps\")", "\"zig_deps\")" }, .{ "deps = [zig_dep(\"nonexistent\")]", "deps = zig_deps()" }, }); + + try ctx.patchWorkspaceFile("build.zig.zon", &.{.{ "// .srconly", ".srconly" }}); + try expectBuildFailure(ctx, "source-only"); + try ctx.patchWorkspaceFile("build.zig.zon", &.{.{ ".srconly", "// .srconly" }}); } fn expectBuildFailure(ctx: BitContext, expected: []const u8) !void { From b03bb594cbea5a5623a1d689ba1c978117691827 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 18:03:20 +0200 Subject: [PATCH 52/77] support generated options modules --- zig/private/configurer.zig | 26 +++++++++++++++++++ zig/private/repo/zig_package.bzl | 13 ++++++++++ .../integration_tests/packages/build.zig.zon | 4 +++ .../packages/fixtures/genopts/build.zig | 11 ++++++++ .../packages/fixtures/genopts/build.zig.zon | 10 +++++++ .../packages/fixtures/genopts/src/root.zig | 3 +++ zig/tests/integration_tests/packages/main.zig | 2 ++ .../packages_tests_runner.zig | 3 ++- 8 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/genopts/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/genopts/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/genopts/src/root.zig diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig index 8936601e..35495354 100644 --- a/zig/private/configurer.zig +++ b/zig/private/configurer.zig @@ -22,6 +22,10 @@ //! import's `name` is the name the importer uses (`@import(name)`), while its //! `module` is the imported module's own registered name; the two differ when a //! module is imported under an alias. +//! +//! A module whose root source is produced by `b.addOptions()` has no file in the +//! package tree; for these the `generated_source` field carries the step's +//! accumulated source so the importer can materialize it as a static file. const std = @import("std"); const Io = std.Io; @@ -134,6 +138,10 @@ fn emit(arena: Allocator, io: Io, builder: *Build) !void { try json.write(module.owner.pkg_hash); try json.objectField("root_source"); try json.write(lazyPathString(module.root_source_file)); + if (generatedOptionsSource(builder, module.root_source_file)) |source| { + try json.objectField("generated_source"); + try json.write(source); + } try json.objectField("imports"); try json.beginArray(); for (module.import_table.keys(), module.import_table.values()) |import_name, imported| { @@ -165,6 +173,24 @@ fn lazyPathString(lazy_path: ?LazyPath) ?[]const u8 { }; } +/// If `lazy_path` is the generated output of a `b.addOptions()` step, return its +/// accumulated source. The options source is a self-contained set of `pub const` +/// declarations fully determined once `build` has run, so it can be materialized +/// as a static file instead of being produced by a build step — which the +/// importer cannot run. Other generated paths return null (and the module is +/// dropped, as it has no static source). +fn generatedOptionsSource(builder: *Build, lazy_path: ?LazyPath) ?[]const u8 { + const generated = switch (lazy_path orelse return null) { + .generated => |generated| generated, + else => return null, + }; + if (generated.up != 0 or generated.sub_path.len != 0) return null; + const step = builder.graph.generated_files.items[@intFromEnum(generated.index)]; + if (step.tag != .options) return null; + const options: *Build.Step.Options = @fieldParentPtr("step", step); + return options.contents.items; +} + fn nextArg(args: []const [:0]const u8, i: *usize) []const u8 { i.* += 1; if (i.* >= args.len) fatal("'{s}' requires a value", .{args[i.* - 1]}); diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index ae85e84a..894d4eba 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -229,6 +229,18 @@ def _configure(repository_ctx, zig, build_zig, cache): repository_ctx.delete("_configure") return configured.stdout +def _materialize_generated(repository_ctx, modules, packages): + for module in modules: + generated = module.get("generated_source") + if generated == None: + continue + rel = "_zig_generated/" + module["name"] + ".zig" + if _is_subtree(packages, module["package"]): + repository_ctx.file(packages[module["package"]]["path"] + "/" + rel, generated) + else: + repository_ctx.file(rel, generated) + module["root_source"] = rel + def _zig_package_impl(repository_ctx): zig = zig_path(repository_ctx) helper = repository_ctx.path(Label("//zig/private:package_prefix.zig")) @@ -257,6 +269,7 @@ def _zig_package_impl(repository_ctx): modules = json.decode(manifest)["modules"] packages = json.decode(repository_ctx.attr.deps)["packages"] + _materialize_generated(repository_ctx, modules, packages) repository_ctx.file("BUILD.bazel", _build_file(repository_ctx, modules, packages)) zig_package = repository_rule( diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index c8fa8899..96dc46c6 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -40,6 +40,10 @@ .url = "__SYMLINKED_URL__", .hash = "__SYMLINKED_HASH__", }, + .genopts = .{ + .url = "__GENOPTS_URL__", + .hash = "__GENOPTS_HASH__", + }, // A source-only sub-tree dependency; only enabled by the test. // .srconly = .{ .url = "__SRCONLY_URL__", .hash = "__SRCONLY_HASH__" }, }, diff --git a/zig/tests/integration_tests/packages/fixtures/genopts/build.zig b/zig/tests/integration_tests/packages/fixtures/genopts/build.zig new file mode 100644 index 00000000..86dc06d2 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/genopts/build.zig @@ -0,0 +1,11 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const options = b.addOptions(); + options.addOption(bool, "feature", true); + + const genopts = b.addModule("genopts", .{ + .root_source_file = b.path("src/root.zig"), + }); + genopts.addImport("build_options", options.createModule()); +} diff --git a/zig/tests/integration_tests/packages/fixtures/genopts/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/genopts/build.zig.zon new file mode 100644 index 00000000..1eab8e51 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/genopts/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .genopts, + .version = "0.0.0", + .fingerprint = 0x18ade9a14ecf9519, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/genopts/src/root.zig b/zig/tests/integration_tests/packages/fixtures/genopts/src/root.zig new file mode 100644 index 00000000..289b1702 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/genopts/src/root.zig @@ -0,0 +1,3 @@ +const build_options = @import("build_options"); + +pub const value: u32 = if (build_options.feature) 7 else 0; diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index a70303ed..54dfed5c 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -9,6 +9,7 @@ const pruned = @import("pruned"); const lib = @import("lib"); const lazyhost = @import("lazyhost"); const symlinked = @import("symlinked"); +const genopts = @import("genopts"); pub fn main() void { _ = leaf.value; @@ -22,4 +23,5 @@ pub fn main() void { _ = lib.v1; _ = lazyhost.value; _ = symlinked.value; + _ = genopts.value; } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index 92b19696..d1706022 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -32,6 +32,7 @@ const packages = [_]Package{ .{ .name = "lazyhost", .patches = &.{.{ .deps = &.{"lazyleaf"} }} }, .{ .name = "symlinked", .symlink = .{ "real.zig", "src/aliased.zig" } }, .{ .name = "srconly" }, + .{ .name = "genopts" }, }; const Consumer = struct { @@ -41,7 +42,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "srconly" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; From 948f2420bc7b79fa6947c6cbcef683810c8a227f Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 18:07:22 +0200 Subject: [PATCH 53/77] add ezi_gex dependency to e2e package import test --- e2e/workspace/zig-package-import/app/build.zig.zon | 4 ++++ e2e/workspace/zig-package-import/app/output.expected | 1 + e2e/workspace/zig-package-import/app/src/main.zig | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/e2e/workspace/zig-package-import/app/build.zig.zon b/e2e/workspace/zig-package-import/app/build.zig.zon index 5188b2de..ad5c5db5 100644 --- a/e2e/workspace/zig-package-import/app/build.zig.zon +++ b/e2e/workspace/zig-package-import/app/build.zig.zon @@ -16,6 +16,10 @@ .url = "git+https://github.com/neurocyte/cbor.git#0d9cab516a1508c832c685183aeac0365b9a224a", .hash = "cbor-1.3.0-RcQE_Mx9AQBfuyJs1eAURJfqdG_5vTJ-VqYCNgpdXrHJ", }, + .ezi_gex = .{ + .url = "git+https://github.com/shaik-abdul-thouhid/ezi-gex.git#ee198a599ecd55baa108395788979c0eb40675f5", + .hash = "ezi_gex-0.5.0-dev-fTQAPBMbFwDJ7MdWAT2OIXD1RdXWbO6gWcc1hzqbGD80", + }, }, .paths = .{ "build.zig", diff --git a/e2e/workspace/zig-package-import/app/output.expected b/e2e/workspace/zig-package-import/app/output.expected index f5c0ccc7..c8fc619c 100644 --- a/e2e/workspace/zig-package-import/app/output.expected +++ b/e2e/workspace/zig-package-import/app/output.expected @@ -1,2 +1,3 @@ Hello world! cbor: 13 bytes +regex match: bob@example diff --git a/e2e/workspace/zig-package-import/app/src/main.zig b/e2e/workspace/zig-package-import/app/src/main.zig index 253f210a..b200a70e 100644 --- a/e2e/workspace/zig-package-import/app/src/main.zig +++ b/e2e/workspace/zig-package-import/app/src/main.zig @@ -2,6 +2,7 @@ const std = @import("std"); const clap = @import("clap"); const greet = @import("greet"); const cbor = @import("cbor"); +const gex = @import("ezi_gex"); pub fn main(init: std.process.Init) !void { var diag = clap.Diagnostic{}; @@ -24,10 +25,20 @@ pub fn main(init: std.process.Init) !void { var cbor_buf: [256]u8 = undefined; const encoded = cbor.fmt(&cbor_buf, message); + // Match a pattern with the multi-module `ezi_gex` regex engine. + var gex_diag: gex.Diagnostic = .{}; + var re = try gex.compileRuntime(init.gpa, "(?\\w+)@(?\\w+)", &gex_diag, .{}); + defer re.deinit(); + var scratch = try @TypeOf(re).Scratch.init(init.gpa, &re.program); + defer scratch.deinit(init.gpa); + const haystack = "contact: bob@example"; + const match = if (re.find(&scratch, haystack)) |m| m.slice(haystack) else "(none)"; + var stdout_buf: [512]u8 = undefined; var stdout_writer = std.Io.File.stdout().writer(init.io, &stdout_buf); const stdout = &stdout_writer.interface; try stdout.print("{s}\n", .{message}); try stdout.print("cbor: {d} bytes\n", .{encoded.len}); + try stdout.print("regex match: {s}\n", .{match}); try stdout.flush(); } From 966bb979a7f0488906b818ae387e6eff8a9ae5b0 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 21:06:59 +0200 Subject: [PATCH 54/77] skip buildifier canonical repo warning on generated hub repo --- zig/private/repo/zig_deps_hub.bzl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zig/private/repo/zig_deps_hub.bzl b/zig/private/repo/zig_deps_hub.bzl index 0d0ce195..94fd7fe3 100644 --- a/zig/private/repo/zig_deps_hub.bzl +++ b/zig/private/repo/zig_deps_hub.bzl @@ -29,6 +29,9 @@ alias( ) """ +# The generated accessors reference each spoke by its canonical (`@@`) repo name, +# which is resolved at module-extension time and has no apparent-name alias here. +# buildifier: disable=canonical-repository _DEFS = '''\ """Accessors for the Zig package dependencies of each `from_file` manifest.""" From 8b6cd1d7976970951d9c5c4f009b6ead06323c30 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 22:35:22 +0200 Subject: [PATCH 55/77] mark relevant package import tests as 0.16+ --- e2e/workspace/zig-package-import/app/BUILD.bazel | 5 +++++ e2e/workspace/zig-package-import/greet/BUILD.bazel | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/e2e/workspace/zig-package-import/app/BUILD.bazel b/e2e/workspace/zig-package-import/app/BUILD.bazel index 0b241f20..2c154212 100644 --- a/e2e/workspace/zig-package-import/app/BUILD.bazel +++ b/e2e/workspace/zig-package-import/app/BUILD.bazel @@ -10,6 +10,11 @@ zig_binary( exclude = ["src/main.zig"], ), main = "src/main.zig", + # `main.zig` and its dependencies (`clap`, `cbor`, `ezi_gex`) require Zig 0.16+. + target_compatible_with = select({ + "@zig_toolchains//:0.15.2": ["@platforms//:incompatible"], + "//conditions:default": [], + }), deps = zig_deps(), ) diff --git a/e2e/workspace/zig-package-import/greet/BUILD.bazel b/e2e/workspace/zig-package-import/greet/BUILD.bazel index bd7214ea..80ffc29f 100644 --- a/e2e/workspace/zig-package-import/greet/BUILD.bazel +++ b/e2e/workspace/zig-package-import/greet/BUILD.bazel @@ -11,6 +11,11 @@ zig_library( ), import_name = "greet", main = "src/greet.zig", + # Depends on `clap`, which uses the Zig 0.16+. + target_compatible_with = select({ + "@zig_toolchains//:0.15.2": ["@platforms//:incompatible"], + "//conditions:default": [], + }), visibility = ["//visibility:public"], deps = [zig_dep("clap")], ) From 0dfbd0dcac125f519c0b3156d10f13ca2a3b33ae Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 23:18:04 +0200 Subject: [PATCH 56/77] print zig fetch failures in zon2json to stderr --- zig/private/zon2json.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig/private/zon2json.zig b/zig/private/zon2json.zig index 92e91b6e..0830c05f 100644 --- a/zig/private/zon2json.zig +++ b/zig/private/zon2json.zig @@ -97,7 +97,7 @@ const Walker = struct { }); switch (result.term) { .exited => |code| if (code != 0) fatal("`zig fetch {s}` failed:\n{s}", .{ url, result.stderr }), - else => fatal("`zig fetch {s}` terminated abnormally", .{url}), + else => |term| fatal("`zig fetch {s}` terminated abnormally ({s}):\n{s}", .{ url, @tagName(term), result.stderr }), } const hash = try walker.arena.dupe(u8, std.mem.trim(u8, result.stdout, " \t\r\n")); From 272fc8669a9a4d8630c62386c750ccb8e9e8c918 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 23:38:00 +0200 Subject: [PATCH 57/77] report if zig fetch is killed by a signal in zon2json --- zig/private/zon2json.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/zig/private/zon2json.zig b/zig/private/zon2json.zig index 0830c05f..28ac79f1 100644 --- a/zig/private/zon2json.zig +++ b/zig/private/zon2json.zig @@ -97,6 +97,7 @@ const Walker = struct { }); switch (result.term) { .exited => |code| if (code != 0) fatal("`zig fetch {s}` failed:\n{s}", .{ url, result.stderr }), + .signal => |sig| fatal("`zig fetch {s}` killed by signal {d}:\nstdout:\n{s}\nstderr:\n{s}", .{ url, sig, result.stdout, result.stderr }), else => |term| fatal("`zig fetch {s}` terminated abnormally ({s}):\n{s}", .{ url, @tagName(term), result.stderr }), } const hash = try walker.arena.dupe(u8, std.mem.trim(u8, result.stdout, " \t\r\n")); From 03e001d8ee71f18f8650c22b6e5c7604c302db11 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 16 Jun 2026 23:55:38 +0200 Subject: [PATCH 58/77] unsandboxed package integration tests to avoid macos segfault --- zig/tests/integration_tests/BUILD.bazel | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zig/tests/integration_tests/BUILD.bazel b/zig/tests/integration_tests/BUILD.bazel index 106c0d8f..f65ec2b9 100644 --- a/zig/tests/integration_tests/BUILD.bazel +++ b/zig/tests/integration_tests/BUILD.bazel @@ -166,7 +166,11 @@ bazel_integration_test( name = "packages_test", size = "large", bazel_version = bazel_binaries.versions.current, - tags = ["requires-network"] + integration_test_utils.DEFAULT_INTEGRATION_TEST_TAGS, + # `no-sandbox`: `zig fetch` (`0.17.0-dev.813`) fails on macOS with sandbox. + tags = [ + "no-sandbox", + "requires-network", + ] + integration_test_utils.DEFAULT_INTEGRATION_TEST_TAGS, test_runner = ":packages_tests_runner", workspace_files = packages_files, workspace_path = "packages", From 658762250b44d49d5811b239e6899a0b1603410f Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Wed, 17 Jun 2026 00:13:12 +0200 Subject: [PATCH 59/77] skip macos package integration tests --- zig/tests/integration_tests/BUILD.bazel | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zig/tests/integration_tests/BUILD.bazel b/zig/tests/integration_tests/BUILD.bazel index f65ec2b9..df3f5489 100644 --- a/zig/tests/integration_tests/BUILD.bazel +++ b/zig/tests/integration_tests/BUILD.bazel @@ -166,11 +166,10 @@ bazel_integration_test( name = "packages_test", size = "large", bazel_version = bazel_binaries.versions.current, - # `no-sandbox`: `zig fetch` (`0.17.0-dev.813`) fails on macOS with sandbox. - tags = [ - "no-sandbox", - "requires-network", - ] + integration_test_utils.DEFAULT_INTEGRATION_TEST_TAGS, + tags = ["requires-network"] + integration_test_utils.DEFAULT_INTEGRATION_TEST_TAGS, + # `zig fetch` (`0.17.0-dev.813`) segfaults (SIGSEGV) on aarch64-macOS when + # fetching `file://` fixture tarballs. Retry on `0.17.0` release. + target_compatible_with = ["@platforms//os:linux"], test_runner = ":packages_tests_runner", workspace_files = packages_files, workspace_path = "packages", From b9ca0761e08f175d4136e9e5148c3ac0490d670e Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 18 Jun 2026 08:57:27 +0200 Subject: [PATCH 60/77] Zig package import tracks module libc/libc++ dependencies --- zig/private/configurer.zig | 14 ++++++++++++++ zig/private/repo/zig_package.bzl | 5 +++++ zig/tests/integration_tests/packages/build.zig.zon | 4 ++++ .../packages/fixtures/usec/build.zig | 6 ++++++ .../packages/fixtures/usec/build.zig.zon | 10 ++++++++++ .../packages/fixtures/usec/src/usec.zig | 6 ++++++ zig/tests/integration_tests/packages/main.zig | 2 ++ .../integration_tests/packages_tests_runner.zig | 3 ++- 8 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/usec/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/usec/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/usec/src/usec.zig diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig index 35495354..0cf5b587 100644 --- a/zig/private/configurer.zig +++ b/zig/private/configurer.zig @@ -14,6 +14,7 @@ //! The emitted JSON has the shape: //! //! {"modules": [{"name": ..., "package": , "root_source": ..., +//! "link_libc": true, "link_libcpp": true, // each present only when set //! "imports": [{"name": ..., "module": ..., "package": }]}]} //! //! A module's `package` is the Zig hash (or sub-tree key) of the package that @@ -26,6 +27,11 @@ //! A module whose root source is produced by `b.addOptions()` has no file in the //! package tree; for these the `generated_source` field carries the step's //! accumulated source so the importer can materialize it as a static file. +//! +//! The `link_libc`/`link_libcpp` fields are emitted (as `true`) only when the +//! module links the C / C++ standard library, e.g. via +//! `b.addModule(..., .{ .link_libc = true })` or `module.linkSystemLibrary("c", +//! .{})`; they are omitted otherwise. const std = @import("std"); const Io = std.Io; @@ -142,6 +148,14 @@ fn emit(arena: Allocator, io: Io, builder: *Build) !void { try json.objectField("generated_source"); try json.write(source); } + if (module.link_libc == true) { + try json.objectField("link_libc"); + try json.write(true); + } + if (module.link_libcpp == true) { + try json.objectField("link_libcpp"); + try json.write(true); + } try json.objectField("imports"); try json.beginArray(); for (module.import_table.keys(), module.import_table.values()) |import_name, imported| { diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 894d4eba..8568b830 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -163,6 +163,11 @@ def _build_file(repository_ctx, modules, packages): if imported["name"] != imported["module"]: import_names[target] = imported["name"] + if module.get("link_libc"): + deps.append("@rules_zig//zig/lib:libc") + if module.get("link_libcpp"): + deps.append("@rules_zig//zig/lib:libc++") + if not owner: chunks.append(_ZIG_LIBRARY.format( name = module["name"], diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 96dc46c6..12ed6595 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -44,6 +44,10 @@ .url = "__GENOPTS_URL__", .hash = "__GENOPTS_HASH__", }, + .usec = .{ + .url = "__USEC_URL__", + .hash = "__USEC_HASH__", + }, // A source-only sub-tree dependency; only enabled by the test. // .srconly = .{ .url = "__SRCONLY_URL__", .hash = "__SRCONLY_HASH__" }, }, diff --git a/zig/tests/integration_tests/packages/fixtures/usec/build.zig b/zig/tests/integration_tests/packages/fixtures/usec/build.zig new file mode 100644 index 00000000..222760c7 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/usec/build.zig @@ -0,0 +1,6 @@ +pub fn build(b: *@import("std").Build) void { + _ = b.addModule("usec", .{ + .root_source_file = b.path("src/usec.zig"), + .link_libc = true, + }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/usec/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/usec/build.zig.zon new file mode 100644 index 00000000..57b5d158 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/usec/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .usec, + .version = "0.0.0", + .fingerprint = 0xe723f6bb3527e304, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/usec/src/usec.zig b/zig/tests/integration_tests/packages/fixtures/usec/src/usec.zig new file mode 100644 index 00000000..c376dc04 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/usec/src/usec.zig @@ -0,0 +1,6 @@ +// `getpid` is provided by libc. +extern fn getpid() c_int; + +pub fn pid() c_int { + return getpid(); +} diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 54dfed5c..317a8504 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -10,6 +10,7 @@ const lib = @import("lib"); const lazyhost = @import("lazyhost"); const symlinked = @import("symlinked"); const genopts = @import("genopts"); +const usec = @import("usec"); pub fn main() void { _ = leaf.value; @@ -24,4 +25,5 @@ pub fn main() void { _ = lazyhost.value; _ = symlinked.value; _ = genopts.value; + _ = usec.pid(); } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index d1706022..0689d943 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -33,6 +33,7 @@ const packages = [_]Package{ .{ .name = "symlinked", .symlink = .{ "real.zig", "src/aliased.zig" } }, .{ .name = "srconly" }, .{ .name = "genopts" }, + .{ .name = "usec" }, }; const Consumer = struct { @@ -42,7 +43,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; From 242add087c0f687dfe6697874ed11f1447c5dee0 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 18 Jun 2026 14:33:32 +0200 Subject: [PATCH 61/77] add support for vendored C sources in package import --- zig/private/configurer.zig | 125 +++++++++++++++++- zig/private/repo/zig_package.bzl | 99 +++++++++++++- .../integration_tests/packages/build.zig.zon | 4 + .../packages/fixtures/cdep/build.zig | 16 +++ .../packages/fixtures/cdep/build.zig.zon | 11 ++ .../packages/fixtures/cdep/include/value.h | 7 + .../packages/fixtures/cdep/src/cdep.zig | 6 + .../packages/fixtures/cdep/src/other.c | 9 ++ .../packages/fixtures/cdep/src/value.c | 9 ++ zig/tests/integration_tests/packages/main.zig | 2 + .../packages_tests_runner.zig | 3 +- 11 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/cdep/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/cdep/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/cdep/include/value.h create mode 100644 zig/tests/integration_tests/packages/fixtures/cdep/src/cdep.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/cdep/src/other.c create mode 100644 zig/tests/integration_tests/packages/fixtures/cdep/src/value.c diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig index 0cf5b587..a01b8579 100644 --- a/zig/private/configurer.zig +++ b/zig/private/configurer.zig @@ -14,7 +14,8 @@ //! The emitted JSON has the shape: //! //! {"modules": [{"name": ..., "package": , "root_source": ..., -//! "link_libc": true, "link_libcpp": true, // each present only when set +//! "generated_source": ..., "link_libc": true, "link_libcpp": true, +//! "csrcs": [...], "include_dirs": [...], "unsupported": [...], // each present only when set/non-empty //! "imports": [{"name": ..., "module": ..., "package": }]}]} //! //! A module's `package` is the Zig hash (or sub-tree key) of the package that @@ -32,6 +33,14 @@ //! module links the C / C++ standard library, e.g. via //! `b.addModule(..., .{ .link_libc = true })` or `module.linkSystemLibrary("c", //! .{})`; they are omitted otherwise. +//! +//! The `csrcs` field lists the module's vendored C sources, each with its +//! per-file `flags` and an optional `language`. The `include_dirs` field lists +//! the module's own include directories, each tagged by `kind` (`path`, +//! `path_system`, or `path_after`). The `unsupported` field lists +//! human-readable descriptions of C or link constructs the importer cannot +//! represent (assembly, prebuilt objects, generated config headers, linked +//! compile steps, ...), causes failure if present. const std = @import("std"); const Io = std.Io; @@ -156,6 +165,7 @@ fn emit(arena: Allocator, io: Io, builder: *Build) !void { try json.objectField("link_libcpp"); try json.write(true); } + try emitC(arena, &json, module); try json.objectField("imports"); try json.beginArray(); for (module.import_table.keys(), module.import_table.values()) |import_name, imported| { @@ -205,6 +215,119 @@ fn generatedOptionsSource(builder: *Build, lazy_path: ?LazyPath) ?[]const u8 { return options.contents.items; } +const CSource = struct { + path: []const u8, + flags: []const []const u8, + language: ?[]const u8, +}; + +const IncludeDir = struct { + kind: []const u8, + path: []const u8, +}; + +fn joinPath(arena: Allocator, base: []const u8, sub: []const u8) ![]const u8 { + if (base.len == 0) return sub; + return std.fmt.allocPrint(arena, "{s}/{s}", .{ base, sub }); +} + +fn languageName(language: ?Build.Module.CSourceLanguage) ?[]const u8 { + return if (language) |l| @tagName(l) else null; +} + +fn appendIncludeDir( + arena: Allocator, + include_dirs: *std.ArrayList(IncludeDir), + unsupported: *std.ArrayList([]const u8), + kind: []const u8, + lazy_path: LazyPath, +) !void { + if (lazyPathString(lazy_path)) |path| { + try include_dirs.append(arena, .{ .kind = kind, .path = path }); + } else { + try unsupported.append(arena, "an include path with a generated or out-of-package location"); + } +} + +fn emitC(arena: Allocator, json: *std.json.Stringify, module: *Build.Module) !void { + var csrcs: std.ArrayList(CSource) = .empty; + var include_dirs: std.ArrayList(IncludeDir) = .empty; + var unsupported: std.ArrayList([]const u8) = .empty; + + for (module.link_objects.items) |link_object| switch (link_object) { + .c_source_file => |c| { + if (lazyPathString(c.file)) |path| { + try csrcs.append(arena, .{ .path = path, .flags = c.flags, .language = languageName(c.language) }); + } else { + try unsupported.append(arena, "a C source file with a generated or out-of-package path"); + } + }, + .c_source_files => |c| { + if (lazyPathString(c.root)) |root_path| { + for (c.files) |file| { + try csrcs.append(arena, .{ .path = try joinPath(arena, root_path, file), .flags = c.flags, .language = languageName(c.language) }); + } + } else { + try unsupported.append(arena, "C source files with a generated or out-of-package root"); + } + }, + .system_lib => {}, + .static_path => try unsupported.append(arena, "a precompiled object or static library (`addObjectFile`)"), + .assembly_file => try unsupported.append(arena, "an assembly source file"), + .win32_resource_file => try unsupported.append(arena, "a Win32 resource file"), + .other_step => try unsupported.append(arena, "a linked compile step (`linkLibrary`/`addObject`)"), + }; + + for (module.include_dirs.items) |include_dir| switch (include_dir) { + .path => |lp| try appendIncludeDir(arena, &include_dirs, &unsupported, "path", lp), + .path_system => |lp| try appendIncludeDir(arena, &include_dirs, &unsupported, "path_system", lp), + .path_after => |lp| try appendIncludeDir(arena, &include_dirs, &unsupported, "path_after", lp), + .embed_path => try unsupported.append(arena, "an embed include path (`addEmbedPath`)"), + .framework_path, .framework_path_system => try unsupported.append(arena, "a framework include path"), + .config_header_step => try unsupported.append(arena, "a generated config header (`addConfigHeader`)"), + .other_step => try unsupported.append(arena, "an include path from a linked compile step"), + }; + + if (csrcs.items.len > 0) { + try json.objectField("csrcs"); + try json.beginArray(); + for (csrcs.items) |c| { + try json.beginObject(); + try json.objectField("path"); + try json.write(c.path); + try json.objectField("flags"); + try json.beginArray(); + for (c.flags) |flag| try json.write(flag); + try json.endArray(); + try json.objectField("language"); + try json.write(c.language); + try json.endObject(); + } + try json.endArray(); + } + + if (include_dirs.items.len > 0) { + try json.objectField("include_dirs"); + try json.beginArray(); + for (include_dirs.items) |inc| { + try json.beginObject(); + try json.objectField("kind"); + try json.write(inc.kind); + try json.objectField("path"); + try json.write(inc.path); + try json.endObject(); + } + try json.endArray(); + } + + if (unsupported.items.len > 0) { + try json.objectField("unsupported"); + try json.beginArray(); + for (unsupported.items) |u| try json.write(u); + try json.endArray(); + } +} + fn nextArg(args: []const [:0]const u8, i: *usize) []const u8 { i.* += 1; if (i.* >= args.len) fatal("'{s}' requires a value", .{args[i.* - 1]}); diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 8568b830..bfd524f7 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -114,6 +114,28 @@ zig_library( ) """ +_HEADER_EXTENSIONS = ["h", "hh", "hpp", "hxx"] + +# vendored C is compiled via sibling `cc_library` targets. +_CC_LIBRARY = """\ +cc_library( + name = "{name}", + srcs = {srcs}, + hdrs = glob({hdrs}, allow_empty = True), + copts = {copts}, + includes = {includes}, + visibility = ["//visibility:public"], +) +""" + +_CC_LIBRARY_GROUP = """\ +cc_library( + name = "{name}", + deps = {deps}, + visibility = ["//visibility:public"], +) +""" + def _is_subtree(packages, owner): return bool(owner) and owner in packages and packages[owner]["path"] != None @@ -127,6 +149,11 @@ def _target_name(packages, owner, module): return module return packages[owner]["path"] + "/" + module +def _csrc_path(packages, owner, path): + if not owner: + return path + return packages[owner]["path"] + "/" + path + def _module_dep(repository_ctx, imported, packages): key = imported["package"] if _is_subtree(packages, key): @@ -139,7 +166,8 @@ def _module_dep(repository_ctx, imported, packages): return ":" + imported["module"] def _build_file(repository_ctx, modules, packages): - chunks = ["load(\"@rules_zig//zig:defs.bzl\", \"zig_library\")", "", _FILES] + library_chunks = [] + cc_chunks = [] for module in modules: if not module["root_source"]: continue @@ -152,6 +180,14 @@ def _build_file(repository_ctx, modules, packages): if owner and not _is_subtree(packages, owner): continue + unsupported = module.get("unsupported") + if unsupported: + fail(("The Zig package '{}' module '{}' uses unsupported constructs: {}.").format( + repository_ctx.attr.url, + module["name"], + "; ".join(unsupported), + )) + # A dependency is imported under its own name by default; an import that # uses a different name is remapped per-edge via `import_names`, so the # same module can be imported under different names by different modules. @@ -168,8 +204,59 @@ def _build_file(repository_ctx, modules, packages): if module.get("link_libcpp"): deps.append("@rules_zig//zig/lib:libc++") + c_sources = [] + for csrc in module.get("csrcs", []): + if csrc["language"] not in (None, "c", "cpp"): + fail(("The Zig package '{}' module '{}' declares the unsupported language '{}' for a C source.").format( + repository_ctx.attr.url, + module["name"], + csrc["language"], + )) + c_sources.append((_csrc_path(packages, owner, csrc["path"]), csrc["flags"])) + + if c_sources: + cinc = _target_name(packages, owner, module["name"]) + ".cinc" + prefix = (packages[owner]["path"] + "/") if owner else "" + + header_dirs = {prefix + inc["path"]: None for inc in module.get("include_dirs", [])} + for path, _flags in c_sources: + header_dirs[path.rpartition("/")[0]] = None + header_globs = [ + (dir + "/" if dir else "") + "**/*." + ext + for dir in sorted(header_dirs) + for ext in _HEADER_EXTENSIONS + ] + includes = [prefix + inc["path"] for inc in module.get("include_dirs", [])] + + # Partition C sources by common `copts`. + groups = [] + for path, flags in c_sources: + if groups and groups[-1][0] == flags: + groups[-1][1].append(path) + else: + groups.append((flags, [path])) + + group_labels = [] + for index in range(len(groups)): + flags = groups[index][0] + paths = groups[index][1] + group_name = "{}.{}".format(cinc, index) + group_labels.append(":" + group_name) + cc_chunks.append(_CC_LIBRARY.format( + name = group_name, + srcs = json.encode(paths), + hdrs = json.encode(header_globs), + copts = json.encode(flags), + includes = json.encode(includes), + )) + cc_chunks.append(_CC_LIBRARY_GROUP.format( + name = cinc, + deps = json.encode(group_labels), + )) + deps.append(":" + cinc) + if not owner: - chunks.append(_ZIG_LIBRARY.format( + library_chunks.append(_ZIG_LIBRARY.format( name = module["name"], main = module["root_source"], deps = json.encode(deps), @@ -177,7 +264,7 @@ def _build_file(repository_ctx, modules, packages): )) else: subpath = packages[owner]["path"] - chunks.append(_ZIG_LIBRARY_SUBTREE.format( + library_chunks.append(_ZIG_LIBRARY_SUBTREE.format( name = _target_name(packages, owner, module["name"]), import_name = module["name"], main = subpath + "/" + module["root_source"], @@ -185,7 +272,11 @@ def _build_file(repository_ctx, modules, packages): deps = json.encode(deps), import_names = json.encode(import_names), )) - return "\n".join(chunks) + + loads = ["load(\"@rules_zig//zig:defs.bzl\", \"zig_library\")"] + if cc_chunks: + loads.append("load(\"@rules_cc//cc:defs.bzl\", \"cc_library\")") + return "\n".join(loads + ["", _FILES] + cc_chunks + library_chunks) def _configure(repository_ctx, zig, build_zig, cache): """Configure the package's `build.zig` and return its module-graph JSON.""" diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 12ed6595..46e87253 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -48,6 +48,10 @@ .url = "__USEC_URL__", .hash = "__USEC_HASH__", }, + .cdep = .{ + .url = "__CDEP_URL__", + .hash = "__CDEP_HASH__", + }, // A source-only sub-tree dependency; only enabled by the test. // .srconly = .{ .url = "__SRCONLY_URL__", .hash = "__SRCONLY_HASH__" }, }, diff --git a/zig/tests/integration_tests/packages/fixtures/cdep/build.zig b/zig/tests/integration_tests/packages/fixtures/cdep/build.zig new file mode 100644 index 00000000..c573a638 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cdep/build.zig @@ -0,0 +1,16 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const cdep = b.addModule("cdep", .{ + .root_source_file = b.path("src/cdep.zig"), + }); + cdep.addCSourceFile(.{ + .file = b.path("src/value.c"), + .flags = &.{"-DSCALE=3"}, + }); + cdep.addCSourceFile(.{ + .file = b.path("src/other.c"), + .flags = &.{"-DSCALE=7"}, + }); + cdep.addIncludePath(b.path("include")); +} diff --git a/zig/tests/integration_tests/packages/fixtures/cdep/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/cdep/build.zig.zon new file mode 100644 index 00000000..94b6eeee --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cdep/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .cdep, + .version = "0.0.0", + .fingerprint = 0x0f860ad394dc4b83, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "include", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/cdep/include/value.h b/zig/tests/integration_tests/packages/fixtures/cdep/include/value.h new file mode 100644 index 00000000..cbecbc4c --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cdep/include/value.h @@ -0,0 +1,7 @@ +#ifndef VALUE_H +#define VALUE_H + +int scaled_value(void); +int offset_value(void); + +#endif diff --git a/zig/tests/integration_tests/packages/fixtures/cdep/src/cdep.zig b/zig/tests/integration_tests/packages/fixtures/cdep/src/cdep.zig new file mode 100644 index 00000000..a756365b --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cdep/src/cdep.zig @@ -0,0 +1,6 @@ +extern fn scaled_value() c_int; +extern fn offset_value() c_int; + +pub fn value() c_int { + return scaled_value() + offset_value(); +} diff --git a/zig/tests/integration_tests/packages/fixtures/cdep/src/other.c b/zig/tests/integration_tests/packages/fixtures/cdep/src/other.c new file mode 100644 index 00000000..34d97d10 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cdep/src/other.c @@ -0,0 +1,9 @@ +#include "value.h" + +#if SCALE != 7 +#error "other.c expected -DSCALE=7" +#endif + +int offset_value(void) { + return SCALE * 2; +} diff --git a/zig/tests/integration_tests/packages/fixtures/cdep/src/value.c b/zig/tests/integration_tests/packages/fixtures/cdep/src/value.c new file mode 100644 index 00000000..2dfd1d2d --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cdep/src/value.c @@ -0,0 +1,9 @@ +#include "value.h" + +#if SCALE != 3 +#error "value.c expected -DSCALE=3" +#endif + +int scaled_value(void) { + return SCALE * 14; +} diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 317a8504..5c739bb1 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -11,6 +11,7 @@ const lazyhost = @import("lazyhost"); const symlinked = @import("symlinked"); const genopts = @import("genopts"); const usec = @import("usec"); +const cdep = @import("cdep"); pub fn main() void { _ = leaf.value; @@ -26,4 +27,5 @@ pub fn main() void { _ = symlinked.value; _ = genopts.value; _ = usec.pid(); + _ = cdep.value(); } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index 0689d943..67913b28 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -34,6 +34,7 @@ const packages = [_]Package{ .{ .name = "srconly" }, .{ .name = "genopts" }, .{ .name = "usec" }, + .{ .name = "cdep" }, }; const Consumer = struct { @@ -43,7 +44,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; From 0ae04ee0454fecd0d82d9642294c640337146bf5 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 18 Jun 2026 15:35:22 +0200 Subject: [PATCH 62/77] support Bazel provided system library dependencies to Zig packages --- zig/private/bzlmod/zig_packages.bzl | 23 +++++++++++++++++++ zig/private/configurer.zig | 17 +++++++++++--- zig/private/repo/zig_package.bzl | 16 +++++++++++++ .../integration_tests/packages/BUILD.bazel | 7 ++++++ .../integration_tests/packages/MODULE.bazel | 4 ++++ .../integration_tests/packages/build.zig.zon | 4 ++++ .../packages/fixtures/syslibdep/build.zig | 9 ++++++++ .../packages/fixtures/syslibdep/build.zig.zon | 10 ++++++++ .../fixtures/syslibdep/src/syslibdep.zig | 5 ++++ zig/tests/integration_tests/packages/main.zig | 2 ++ zig/tests/integration_tests/packages/mymath.c | 3 +++ .../packages_tests_runner.zig | 8 ++++++- 12 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/syslibdep/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/syslibdep/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/syslibdep/src/syslibdep.zig create mode 100644 zig/tests/integration_tests/packages/mymath.c diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 2326dc1b..e000784a 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -14,6 +14,19 @@ from_file = tag_class( }, ) +system_library = tag_class( + attrs = { + "name": attr.string( + doc = "The name of the system library as passed to `linkSystemLibrary` in a package's `build.zig`.", + mandatory = True, + ), + "lib": attr.label( + doc = "A `cc_library` that provides the named system library.", + mandatory = True, + ), + }, +) + def _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests): result = module_ctx.execute( [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", zig, cache, str(pkg_dir)] + @@ -108,6 +121,14 @@ def _zig_packages_impl(module_ctx): else: root_nondev = True + system_libraries = {} + for mod in module_ctx.modules: + for tag in mod.tags.system_library: + existing = system_libraries.get(tag.name) + if existing != None and existing != tag.lib: + fail("Conflicting `system_library` annotations for '{}': {} and {}.".format(tag.name, existing, tag.lib)) + system_libraries[tag.name] = tag.lib + graph = _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests) graph = _localize_paths(graph, str(pkg_dir), manifest_labels) @@ -131,6 +152,7 @@ def _zig_packages_impl(module_ctx): zig_hash = key, deps = json.encode(_deps_data(graph, key, reached)), dep_build_files = {dep: "@{}//:build.zig".format(dep) for dep in spokes}, + system_libraries = system_libraries, ) manifests, targets = _hub_data(graph, root_tags) @@ -183,5 +205,6 @@ zig_packages = module_extension( implementation = _zig_packages_impl, tag_classes = { "from_file": from_file, + "system_library": system_library, }, ) diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig index a01b8579..543c851d 100644 --- a/zig/private/configurer.zig +++ b/zig/private/configurer.zig @@ -15,7 +15,7 @@ //! //! {"modules": [{"name": ..., "package": , "root_source": ..., //! "generated_source": ..., "link_libc": true, "link_libcpp": true, -//! "csrcs": [...], "include_dirs": [...], "unsupported": [...], // each present only when set/non-empty +//! "csrcs": [...], "include_dirs": [...], "system_libs": [...], "unsupported": [...], // each present only when set/non-empty //! "imports": [{"name": ..., "module": ..., "package": }]}]} //! //! A module's `package` is the Zig hash (or sub-tree key) of the package that @@ -37,7 +37,10 @@ //! The `csrcs` field lists the module's vendored C sources, each with its //! per-file `flags` and an optional `language`. The `include_dirs` field lists //! the module's own include directories, each tagged by `kind` (`path`, -//! `path_system`, or `path_after`). The `unsupported` field lists +//! `path_system`, or `path_after`). The `system_libs` field lists the names of +//! non-libc system libraries the module links (`linkSystemLibrary`); the +//! importer requires each to be mapped to a `cc_library` via a +//! `zig_packages.system_library` annotation. The `unsupported` field lists //! human-readable descriptions of C or link constructs the importer cannot //! represent (assembly, prebuilt objects, generated config headers, linked //! compile steps, ...), causes failure if present. @@ -252,6 +255,7 @@ fn appendIncludeDir( fn emitC(arena: Allocator, json: *std.json.Stringify, module: *Build.Module) !void { var csrcs: std.ArrayList(CSource) = .empty; var include_dirs: std.ArrayList(IncludeDir) = .empty; + var system_libs: std.ArrayList([]const u8) = .empty; var unsupported: std.ArrayList([]const u8) = .empty; for (module.link_objects.items) |link_object| switch (link_object) { @@ -271,7 +275,7 @@ fn emitC(arena: Allocator, json: *std.json.Stringify, module: *Build.Module) !vo try unsupported.append(arena, "C source files with a generated or out-of-package root"); } }, - .system_lib => {}, + .system_lib => |lib| try system_libs.append(arena, lib.name), .static_path => try unsupported.append(arena, "a precompiled object or static library (`addObjectFile`)"), .assembly_file => try unsupported.append(arena, "an assembly source file"), .win32_resource_file => try unsupported.append(arena, "a Win32 resource file"), @@ -320,6 +324,13 @@ fn emitC(arena: Allocator, json: *std.json.Stringify, module: *Build.Module) !vo try json.endArray(); } + if (system_libs.items.len > 0) { + try json.objectField("system_libs"); + try json.beginArray(); + for (system_libs.items) |name| try json.write(name); + try json.endArray(); + } + if (unsupported.items.len > 0) { try json.objectField("unsupported"); try json.beginArray(); diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index bfd524f7..2e484379 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -21,6 +21,9 @@ ATTRS = { "dep_build_files": attr.string_keyed_label_dict( doc = "Map from each dependency package hash to its `build.zig`, used to wire `@dependencies`.", ), + "system_libraries": attr.string_keyed_label_dict( + doc = "Map from a system-library name (as passed to `linkSystemLibrary`) to a `cc_library` providing it.", + ), } def _package_prefix(repository_ctx, zig, helper, cache, archive): @@ -204,6 +207,19 @@ def _build_file(repository_ctx, modules, packages): if module.get("link_libcpp"): deps.append("@rules_zig//zig/lib:libc++") + for name in module.get("system_libs", []): + lib = repository_ctx.attr.system_libraries.get(name) + if lib == None: + fail(("The Zig package '{}' module '{}' requires the system library '{}', " + + "which is not provided. Map it to a cc_library with a " + + "`zig_packages.system_library(name = \"{}\", lib = ...)` annotation.").format( + repository_ctx.attr.url, + module["name"], + name, + name, + )) + deps.append(str(lib)) + c_sources = [] for csrc in module.get("csrcs", []): if csrc["language"] not in (None, "c", "cpp"): diff --git a/zig/tests/integration_tests/packages/BUILD.bazel b/zig/tests/integration_tests/packages/BUILD.bazel index c520f68f..0fcb2ef9 100644 --- a/zig/tests/integration_tests/packages/BUILD.bazel +++ b/zig/tests/integration_tests/packages/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") load("@rules_zig//zig:defs.bzl", "zig_binary") load("@zig_deps//:defs.bzl", "zig_dep", "zig_deps") @@ -15,3 +16,9 @@ zig_binary( ), ], ) + +cc_library( + name = "mymath_impl", + srcs = ["mymath.c"], + visibility = ["//visibility:public"], +) diff --git a/zig/tests/integration_tests/packages/MODULE.bazel b/zig/tests/integration_tests/packages/MODULE.bazel index fc133bd6..0dfc07f3 100644 --- a/zig/tests/integration_tests/packages/MODULE.bazel +++ b/zig/tests/integration_tests/packages/MODULE.bazel @@ -27,4 +27,8 @@ zig_packages = use_extension("@rules_zig//zig:packages.bzl", "zig_packages") zig_packages.from_file(build_zig_zon = "//:build.zig.zon") zig_packages.from_file(build_zig_zon = "//path_deps/greeter:build.zig.zon") zig_packages.from_file(build_zig_zon = "//path_deps/message:build.zig.zon") +zig_packages.system_library( + name = "mymath", + lib = "//:mymath_impl", +) use_repo(zig_packages, "zig_deps") diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 46e87253..709eed2c 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -52,6 +52,10 @@ .url = "__CDEP_URL__", .hash = "__CDEP_HASH__", }, + .syslibdep = .{ + .url = "__SYSLIBDEP_URL__", + .hash = "__SYSLIBDEP_HASH__", + }, // A source-only sub-tree dependency; only enabled by the test. // .srconly = .{ .url = "__SRCONLY_URL__", .hash = "__SRCONLY_HASH__" }, }, diff --git a/zig/tests/integration_tests/packages/fixtures/syslibdep/build.zig b/zig/tests/integration_tests/packages/fixtures/syslibdep/build.zig new file mode 100644 index 00000000..adcc35df --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/syslibdep/build.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const m = b.addModule("syslibdep", .{ + .root_source_file = b.path("src/syslibdep.zig"), + .target = b.standardTargetOptions(.{}), + }); + m.linkSystemLibrary("mymath", .{}); +} diff --git a/zig/tests/integration_tests/packages/fixtures/syslibdep/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/syslibdep/build.zig.zon new file mode 100644 index 00000000..ddf19298 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/syslibdep/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .syslibdep, + .version = "0.0.0", + .fingerprint = 0x3cd2c304bd1152dd, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/syslibdep/src/syslibdep.zig b/zig/tests/integration_tests/packages/fixtures/syslibdep/src/syslibdep.zig new file mode 100644 index 00000000..ac6fbfd9 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/syslibdep/src/syslibdep.zig @@ -0,0 +1,5 @@ +extern fn my_compute(c_int) c_int; + +pub fn compute(x: c_int) c_int { + return my_compute(x); +} diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 5c739bb1..3d2d92e2 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -12,6 +12,7 @@ const symlinked = @import("symlinked"); const genopts = @import("genopts"); const usec = @import("usec"); const cdep = @import("cdep"); +const syslibdep = @import("syslibdep"); pub fn main() void { _ = leaf.value; @@ -28,4 +29,5 @@ pub fn main() void { _ = genopts.value; _ = usec.pid(); _ = cdep.value(); + _ = syslibdep.compute(21); } diff --git a/zig/tests/integration_tests/packages/mymath.c b/zig/tests/integration_tests/packages/mymath.c new file mode 100644 index 00000000..24022bca --- /dev/null +++ b/zig/tests/integration_tests/packages/mymath.c @@ -0,0 +1,3 @@ +int my_compute(int x) { + return x * 2; +} diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index 67913b28..baa9802b 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -35,6 +35,7 @@ const packages = [_]Package{ .{ .name = "genopts" }, .{ .name = "usec" }, .{ .name = "cdep" }, + .{ .name = "syslibdep" }, }; const Consumer = struct { @@ -44,7 +45,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep", "syslibdep" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; @@ -129,6 +130,11 @@ test "the importer rejects invalid package configurations" { try ctx.patchWorkspaceFile("build.zig.zon", &.{.{ "// .srconly", ".srconly" }}); try expectBuildFailure(ctx, "source-only"); try ctx.patchWorkspaceFile("build.zig.zon", &.{.{ ".srconly", "// .srconly" }}); + + // A required system library with no matching annotation fails. + try ctx.patchWorkspaceFile("MODULE.bazel", &.{.{ "name = \"mymath\"", "name = \"mymath-unprovided\"" }}); + try expectBuildFailure(ctx, "system library"); + try ctx.patchWorkspaceFile("MODULE.bazel", &.{.{ "name = \"mymath-unprovided\"", "name = \"mymath\"" }}); } fn expectBuildFailure(ctx: BitContext, expected: []const u8) !void { From 7040358576be8435e89765da5c465b779547978a Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 18 Jun 2026 16:09:27 +0200 Subject: [PATCH 63/77] support system integrations in Zig packages --- zig/private/bzlmod/zig_packages.bzl | 18 ++++++++++++++++ zig/private/configurer.zig | 21 ++++++++++++++----- zig/private/repo/zig_package.bzl | 10 +++++++-- .../integration_tests/packages/BUILD.bazel | 6 ++++++ .../integration_tests/packages/MODULE.bazel | 5 +++++ .../integration_tests/packages/build.zig.zon | 4 ++++ .../packages/fixtures/optdep/build.zig | 11 ++++++++++ .../packages/fixtures/optdep/build.zig.zon | 10 +++++++++ .../packages/fixtures/optdep/src/optdep.zig | 5 +++++ zig/tests/integration_tests/packages/main.zig | 2 ++ .../integration_tests/packages/optmath.c | 3 +++ .../packages_tests_runner.zig | 7 ++++++- 12 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/optdep/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/optdep/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/optdep/src/optdep.zig create mode 100644 zig/tests/integration_tests/packages/optmath.c diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index e000784a..452c0f10 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -27,6 +27,15 @@ system_library = tag_class( }, ) +system_integration = tag_class( + attrs = { + "name": attr.string( + doc = "The name of an optional system integration (`systemIntegrationOption`) to enable.", + mandatory = True, + ), + }, +) + def _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests): result = module_ctx.execute( [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", zig, cache, str(pkg_dir)] + @@ -129,6 +138,13 @@ def _zig_packages_impl(module_ctx): fail("Conflicting `system_library` annotations for '{}': {} and {}.".format(tag.name, existing, tag.lib)) system_libraries[tag.name] = tag.lib + system_integrations = {} + for mod in module_ctx.modules: + for tag in mod.tags.system_integration: + system_integrations[tag.name] = True + + system_integrations = system_integrations.keys() + graph = _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests) graph = _localize_paths(graph, str(pkg_dir), manifest_labels) @@ -153,6 +169,7 @@ def _zig_packages_impl(module_ctx): deps = json.encode(_deps_data(graph, key, reached)), dep_build_files = {dep: "@{}//:build.zig".format(dep) for dep in spokes}, system_libraries = system_libraries, + system_integrations = system_integrations, ) manifests, targets = _hub_data(graph, root_tags) @@ -206,5 +223,6 @@ zig_packages = module_extension( tag_classes = { "from_file": from_file, "system_library": system_library, + "system_integration": system_integration, }, ) diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig index 543c851d..4791d5fa 100644 --- a/zig/private/configurer.zig +++ b/zig/private/configurer.zig @@ -1,7 +1,7 @@ //! Configure a Zig package's `build.zig` and emit its public module graph as //! JSON, for translation into Bazel `zig_library` targets. //! -//! Usage: configurer --zig --build-root +//! Usage: configurer --zig --build-root [--system-integration NAME ...] //! //! Modeled on `lib/compiler/configurer.zig`: it sets up a `std.Build`, runs the //! package's `build` function, then walks the module import graph (seeded by the @@ -40,10 +40,14 @@ //! `path_system`, or `path_after`). The `system_libs` field lists the names of //! non-libc system libraries the module links (`linkSystemLibrary`); the //! importer requires each to be mapped to a `cc_library` via a -//! `zig_packages.system_library` annotation. The `unsupported` field lists -//! human-readable descriptions of C or link constructs the importer cannot -//! represent (assembly, prebuilt objects, generated config headers, linked -//! compile steps, ...), causes failure if present. +//! `zig_packages.system_library` annotation. Each `--system-integration NAME` +//! argument pre-enables the named optional system integration before the build +//! runs, so the package's `systemIntegrationOption(NAME)` returns true and its +//! guarded `linkSystemLibrary` calls run (surfacing as `system_libs` entries). +//! The `unsupported` field lists human-readable descriptions of C or link +//! constructs the importer cannot represent (assembly, prebuilt objects, +//! generated config headers, linked compile steps, ...) and causes failure if +//! present. const std = @import("std"); const Io = std.Io; @@ -64,12 +68,15 @@ pub fn main(init: process.Init) !void { var zig_exe: ?[]const u8 = null; var build_root_sub_path: ?[]const u8 = null; + var system_integrations: std.ArrayList([]const u8) = .empty; var i: usize = 1; while (i < args.len) : (i += 1) { if (mem.eql(u8, args[i], "--zig")) { zig_exe = nextArg(args, &i); } else if (mem.eql(u8, args[i], "--build-root")) { build_root_sub_path = nextArg(args, &i); + } else if (mem.eql(u8, args[i], "--system-integration")) { + try system_integrations.append(arena, nextArg(args, &i)); } else { fatal("unrecognized argument: {s}", .{args[i]}); } @@ -98,6 +105,10 @@ pub fn main(init: process.Init) !void { std.debug.assert(empty_string == .empty); std.debug.assert(root_string == .root); + for (system_integrations.items) |name| { + try graph.system_integration_options.put(arena, name, .user_enabled); + } + const build_root: Build.Cache.Path = .{ .root_dir = .{ .handle = try Io.Dir.cwd().openDir(io, build_root_path, .{}), diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 2e484379..2b06bda8 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -24,6 +24,9 @@ ATTRS = { "system_libraries": attr.string_keyed_label_dict( doc = "Map from a system-library name (as passed to `linkSystemLibrary`) to a `cc_library` providing it.", ), + "system_integrations": attr.string_list( + doc = "Names of optional system integrations (`systemIntegrationOption`) to enable when configuring the package.", + ), } def _package_prefix(repository_ctx, zig, helper, cache, archive): @@ -328,13 +331,16 @@ def _configure(repository_ctx, zig, build_zig, cache): if compiled.return_code != 0: fail("Failed to compile the Zig configurer for '{}':\n{}".format(repository_ctx.attr.url, compiled.stderr)) - configured = repository_ctx.execute([ + configure_args = [ str(repository_ctx.path("_configure/configurer")), "--zig", str(zig), "--build-root", str(repository_ctx.path(".")), - ]) + ] + for name in repository_ctx.attr.system_integrations: + configure_args.extend(["--system-integration", name]) + configured = repository_ctx.execute(configure_args) if configured.return_code != 0: fail("Failed to configure the Zig package '{}':\n{}".format(repository_ctx.attr.url, configured.stderr)) diff --git a/zig/tests/integration_tests/packages/BUILD.bazel b/zig/tests/integration_tests/packages/BUILD.bazel index 0fcb2ef9..cc1c9d2d 100644 --- a/zig/tests/integration_tests/packages/BUILD.bazel +++ b/zig/tests/integration_tests/packages/BUILD.bazel @@ -22,3 +22,9 @@ cc_library( srcs = ["mymath.c"], visibility = ["//visibility:public"], ) + +cc_library( + name = "optmath_impl", + srcs = ["optmath.c"], + visibility = ["//visibility:public"], +) diff --git a/zig/tests/integration_tests/packages/MODULE.bazel b/zig/tests/integration_tests/packages/MODULE.bazel index 0dfc07f3..7dbb0c68 100644 --- a/zig/tests/integration_tests/packages/MODULE.bazel +++ b/zig/tests/integration_tests/packages/MODULE.bazel @@ -31,4 +31,9 @@ zig_packages.system_library( name = "mymath", lib = "//:mymath_impl", ) +zig_packages.system_integration(name = "optmath") +zig_packages.system_library( + name = "optmath", + lib = "//:optmath_impl", +) use_repo(zig_packages, "zig_deps") diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index 709eed2c..fd42c3e9 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -56,6 +56,10 @@ .url = "__SYSLIBDEP_URL__", .hash = "__SYSLIBDEP_HASH__", }, + .optdep = .{ + .url = "__OPTDEP_URL__", + .hash = "__OPTDEP_HASH__", + }, // A source-only sub-tree dependency; only enabled by the test. // .srconly = .{ .url = "__SRCONLY_URL__", .hash = "__SRCONLY_HASH__" }, }, diff --git a/zig/tests/integration_tests/packages/fixtures/optdep/build.zig b/zig/tests/integration_tests/packages/fixtures/optdep/build.zig new file mode 100644 index 00000000..71f90fe8 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/optdep/build.zig @@ -0,0 +1,11 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const m = b.addModule("optdep", .{ + .root_source_file = b.path("src/optdep.zig"), + .target = b.standardTargetOptions(.{}), + }); + if (b.systemIntegrationOption("optmath", .{})) { + m.linkSystemLibrary("optmath", .{}); + } +} diff --git a/zig/tests/integration_tests/packages/fixtures/optdep/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/optdep/build.zig.zon new file mode 100644 index 00000000..07285d28 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/optdep/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .optdep, + .version = "0.0.0", + .fingerprint = 0x52be460a7daafc05, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/optdep/src/optdep.zig b/zig/tests/integration_tests/packages/fixtures/optdep/src/optdep.zig new file mode 100644 index 00000000..f30e70f5 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/optdep/src/optdep.zig @@ -0,0 +1,5 @@ +extern fn opt_compute(c_int) c_int; + +pub fn compute(x: c_int) c_int { + return opt_compute(x); +} diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 3d2d92e2..73ce8a1b 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -13,6 +13,7 @@ const genopts = @import("genopts"); const usec = @import("usec"); const cdep = @import("cdep"); const syslibdep = @import("syslibdep"); +const optdep = @import("optdep"); pub fn main() void { _ = leaf.value; @@ -30,4 +31,5 @@ pub fn main() void { _ = usec.pid(); _ = cdep.value(); _ = syslibdep.compute(21); + _ = optdep.compute(1); } diff --git a/zig/tests/integration_tests/packages/optmath.c b/zig/tests/integration_tests/packages/optmath.c new file mode 100644 index 00000000..f42c677c --- /dev/null +++ b/zig/tests/integration_tests/packages/optmath.c @@ -0,0 +1,3 @@ +int opt_compute(int x) { + return x + 100; +} diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index baa9802b..522a3a54 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -36,6 +36,7 @@ const packages = [_]Package{ .{ .name = "usec" }, .{ .name = "cdep" }, .{ .name = "syslibdep" }, + .{ .name = "optdep" }, }; const Consumer = struct { @@ -45,7 +46,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep", "syslibdep" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep", "syslibdep", "optdep" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; @@ -135,6 +136,10 @@ test "the importer rejects invalid package configurations" { try ctx.patchWorkspaceFile("MODULE.bazel", &.{.{ "name = \"mymath\"", "name = \"mymath-unprovided\"" }}); try expectBuildFailure(ctx, "system library"); try ctx.patchWorkspaceFile("MODULE.bazel", &.{.{ "name = \"mymath-unprovided\"", "name = \"mymath\"" }}); + + try ctx.patchWorkspaceFile("MODULE.bazel", &.{.{ "system_integration(name = \"optmath\")", "system_integration(name = \"optmath-off\")" }}); + try expectBuildFailure(ctx, "opt_compute"); + try ctx.patchWorkspaceFile("MODULE.bazel", &.{.{ "system_integration(name = \"optmath-off\")", "system_integration(name = \"optmath\")" }}); } fn expectBuildFailure(ctx: BitContext, expected: []const u8) !void { From 3ce8f7c94f50585a59aad0e001198405dcb27e10 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Thu, 18 Jun 2026 17:04:15 +0200 Subject: [PATCH 64/77] add test coverage for libc++ dependency in package import tests --- zig/tests/integration_tests/packages/build.zig.zon | 4 ++++ .../packages/fixtures/cppdep/build.zig | 9 +++++++++ .../packages/fixtures/cppdep/build.zig.zon | 10 ++++++++++ .../packages/fixtures/cppdep/src/cppdep.zig | 5 +++++ .../packages/fixtures/cppdep/src/impl.cpp | 8 ++++++++ zig/tests/integration_tests/packages/main.zig | 2 ++ zig/tests/integration_tests/packages_tests_runner.zig | 3 ++- 7 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 zig/tests/integration_tests/packages/fixtures/cppdep/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/cppdep/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/cppdep/src/cppdep.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/cppdep/src/impl.cpp diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index fd42c3e9..e7c900c7 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -60,6 +60,10 @@ .url = "__OPTDEP_URL__", .hash = "__OPTDEP_HASH__", }, + .cppdep = .{ + .url = "__CPPDEP_URL__", + .hash = "__CPPDEP_HASH__", + }, // A source-only sub-tree dependency; only enabled by the test. // .srconly = .{ .url = "__SRCONLY_URL__", .hash = "__SRCONLY_HASH__" }, }, diff --git a/zig/tests/integration_tests/packages/fixtures/cppdep/build.zig b/zig/tests/integration_tests/packages/fixtures/cppdep/build.zig new file mode 100644 index 00000000..d89c6d5c --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cppdep/build.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const cppdep = b.addModule("cppdep", .{ + .root_source_file = b.path("src/cppdep.zig"), + .link_libcpp = true, + }); + cppdep.addCSourceFile(.{ .file = b.path("src/impl.cpp") }); +} diff --git a/zig/tests/integration_tests/packages/fixtures/cppdep/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/cppdep/build.zig.zon new file mode 100644 index 00000000..c449fdd2 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cppdep/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .cppdep, + .version = "0.0.0", + .fingerprint = 0xaa1e1126b9a5e407, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/cppdep/src/cppdep.zig b/zig/tests/integration_tests/packages/fixtures/cppdep/src/cppdep.zig new file mode 100644 index 00000000..7de853a6 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cppdep/src/cppdep.zig @@ -0,0 +1,5 @@ +extern fn cpp_compute(c_int) c_int; + +pub fn value() c_int { + return cpp_compute(2); +} diff --git a/zig/tests/integration_tests/packages/fixtures/cppdep/src/impl.cpp b/zig/tests/integration_tests/packages/fixtures/cppdep/src/impl.cpp new file mode 100644 index 00000000..fcd1d076 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cppdep/src/impl.cpp @@ -0,0 +1,8 @@ +// Uses operator new/delete (C++ runtime symbols whose mangling is shared by +// libc++ and libstdc++). +extern "C" int cpp_compute(int x) { + int *p = new int(x); + int r = *p + 1; + delete p; + return r; +} diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 73ce8a1b..49203466 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -14,6 +14,7 @@ const usec = @import("usec"); const cdep = @import("cdep"); const syslibdep = @import("syslibdep"); const optdep = @import("optdep"); +const cppdep = @import("cppdep"); pub fn main() void { _ = leaf.value; @@ -32,4 +33,5 @@ pub fn main() void { _ = cdep.value(); _ = syslibdep.compute(21); _ = optdep.compute(1); + _ = cppdep.value(); } diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index 522a3a54..c25511eb 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -37,6 +37,7 @@ const packages = [_]Package{ .{ .name = "cdep" }, .{ .name = "syslibdep" }, .{ .name = "optdep" }, + .{ .name = "cppdep" }, }; const Consumer = struct { @@ -46,7 +47,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep", "syslibdep", "optdep" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep", "syslibdep", "optdep", "cppdep" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; From 16cecfdc6cf44dcaf73360c4a79648a912908aaa Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:02:47 +0200 Subject: [PATCH 65/77] feat(zig_packages): pass --zig-option in the configurer --- zig/private/configurer.zig | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/zig/private/configurer.zig b/zig/private/configurer.zig index 4791d5fa..e281001f 100644 --- a/zig/private/configurer.zig +++ b/zig/private/configurer.zig @@ -1,7 +1,7 @@ //! Configure a Zig package's `build.zig` and emit its public module graph as //! JSON, for translation into Bazel `zig_library` targets. //! -//! Usage: configurer --zig --build-root [--system-integration NAME ...] +//! Usage: configurer --zig --build-root [--system-integration NAME ...] [--zig-option -DNAME=VALUE ...] //! //! Modeled on `lib/compiler/configurer.zig`: it sets up a `std.Build`, runs the //! package's `build` function, then walks the module import graph (seeded by the @@ -69,6 +69,7 @@ pub fn main(init: process.Init) !void { var zig_exe: ?[]const u8 = null; var build_root_sub_path: ?[]const u8 = null; var system_integrations: std.ArrayList([]const u8) = .empty; + var zig_options: std.ArrayList([]const u8) = .empty; var i: usize = 1; while (i < args.len) : (i += 1) { if (mem.eql(u8, args[i], "--zig")) { @@ -77,6 +78,8 @@ pub fn main(init: process.Init) !void { build_root_sub_path = nextArg(args, &i); } else if (mem.eql(u8, args[i], "--system-integration")) { try system_integrations.append(arena, nextArg(args, &i)); + } else if (mem.eql(u8, args[i], "--zig-option")) { + try zig_options.append(arena, nextArg(args, &i)); } else { fatal("unrecognized argument: {s}", .{args[i]}); } @@ -118,8 +121,19 @@ pub fn main(init: process.Init) !void { const builder = try Build.create(&graph, build_root, dependencies.root_deps); + for (zig_options.items) |option| { + const setting = if (mem.startsWith(u8, option, "-D")) option[2..] else fatal("--zig-option requires -DNAME=VALUE, got '{s}'", .{option}); + const eq = mem.indexOfScalar(u8, setting, '=') orelse + fatal("--zig-option requires -DNAME=VALUE, got '{s}'", .{option}); + if (try builder.addUserInputOption(setting[0..eq], setting[eq + 1 ..])) + fatal("invalid --zig-option '{s}'", .{option}); + } + try builder.runBuild(root); + if (builder.validateUserInputDidItFail()) + fatal("a --zig-option was not recognized by the package's build.zig", .{}); + try emit(arena, io, builder); } From 479969aa0da07d936a25c7c7f076eef60b0f71b7 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:26:00 +0200 Subject: [PATCH 66/77] feat(zig_packages): resolve a config tag into a matrix cell --- zig/private/bzlmod/zig_packages.bzl | 39 ++++++++++++++++ zig/tests/BUILD.bazel | 3 ++ zig/tests/zig_packages_test.bzl | 69 +++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 zig/tests/zig_packages_test.bzl diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 452c0f10..a34cf47c 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -36,6 +36,45 @@ system_integration = tag_class( }, ) +_OPTIMIZE_MODES = { + "debug": "Debug", + "release_safe": "ReleaseSafe", + "release_small": "ReleaseSmall", + "release_fast": "ReleaseFast", +} + +def resolve_cell(tag): + """Resolve a `config` tag into a build-configuration matrix cell. + + `optimize` expands to `//zig/config/mode:mode` and `-Doptimize=Mode`; + `select_on` is appended verbatim and `zig_flags` become `-DNAME=VALUE`. + + Args: + tag: a `config` tag. + + Returns: + `(error, cell)`, `cell`: `struct(name, select_on, zig_options)`, where + `zig_options` is a list of `-DNAME=VALUE` Zig build option flags. + """ + select_on = [] + zig_options = [] + + if tag.optimize: + mode = _OPTIMIZE_MODES.get(tag.optimize) + if mode == None: + return ("config '{}' has unknown optimize mode '{}'".format(tag.name, tag.optimize), None) + select_on.append("@rules_zig//zig/config/mode:" + tag.optimize) + zig_options.append("-Doptimize=" + mode) + + select_on.extend(tag.select_on) + + for flag in tag.zig_flags: + if "=" not in flag: + return ("config '{}' flag '{}' is not NAME=VALUE".format(tag.name, flag), None) + zig_options.append("-D" + flag) + + return (None, struct(name = tag.name, select_on = select_on, zig_options = zig_options)) + def _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests): result = module_ctx.execute( [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", zig, cache, str(pkg_dir)] + diff --git a/zig/tests/BUILD.bazel b/zig/tests/BUILD.bazel index 48078ccc..a759c0e3 100644 --- a/zig/tests/BUILD.bazel +++ b/zig/tests/BUILD.bazel @@ -16,6 +16,7 @@ load(":translate_c_action_test.bzl", "translate_c_action_test_suite") load(":use_cc_common_link_test.bzl", "use_cc_common_link_test_suite") load(":util.bzl", "forward_exec_settings") load(":versions_test.bzl", "versions_test_suite") +load(":zig_packages_test.bzl", "zig_packages_test_suite") load(":zigopt_test.bzl", "zigopt_test_suite") forward_exec_settings(name = "exec_settings") @@ -52,6 +53,8 @@ translate_c_action_test_suite(name = "translate_c_action_test") versions_test_suite(name = "versions_test") +zig_packages_test_suite(name = "zig_packages_test") + zigopt_test_suite(name = "zigopt_test") bzl_library( diff --git a/zig/tests/zig_packages_test.bzl b/zig/tests/zig_packages_test.bzl new file mode 100644 index 00000000..613fc9e4 --- /dev/null +++ b/zig/tests/zig_packages_test.bzl @@ -0,0 +1,69 @@ +"""Unit tests for the `zig_packages` module extension helpers.""" + +load("@bazel_skylib//lib:partial.bzl", "partial") +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load("//zig/private/bzlmod:zig_packages.bzl", "resolve_cell") + +def _config(*, name, optimize = "", select_on = [], zig_flags = []): + return struct(name = name, optimize = optimize, select_on = select_on, zig_flags = zig_flags) + +def _resolve_cell_test_impl(ctx): + env = unittest.begin(ctx) + + asserts.equals( + env, + (None, struct( + name = "dbg", + select_on = ["@rules_zig//zig/config/mode:debug"], + zig_options = ["-Doptimize=Debug"], + )), + resolve_cell(_config(name = "dbg", optimize = "debug")), + "optimize expands to a mode condition and -Doptimize", + ) + + asserts.equals( + env, + (None, struct( + name = "x", + select_on = ["@rules_zig//zig/config/mode:release_fast", "//my:flag"], + zig_options = ["-Doptimize=ReleaseFast", "-DDEFINE=a=b"], + )), + resolve_cell(_config( + name = "x", + optimize = "release_fast", + select_on = ["//my:flag"], + zig_flags = ["DEFINE=a=b"], + )), + "select_on and zig_flags append; a flag value may contain '='", + ) + + asserts.equals( + env, + (None, struct(name = "bare", select_on = [], zig_options = [])), + resolve_cell(_config(name = "bare")), + "a cell may carry no conditions", + ) + + asserts.equals( + env, + ("config 'x' has unknown optimize mode 'fast'", None), + resolve_cell(_config(name = "x", optimize = "fast")), + "an unknown optimize mode is rejected", + ) + + asserts.equals( + env, + ("config 'x' flag 'BAD' is not NAME=VALUE", None), + resolve_cell(_config(name = "x", zig_flags = ["BAD"])), + "a zig_flags entry must be NAME=VALUE", + ) + + return unittest.end(env) + +_resolve_cell_test = unittest.make(_resolve_cell_test_impl) + +def zig_packages_test_suite(name): + unittest.suite( + name, + partial.make(_resolve_cell_test, size = "small"), + ) From 0f2670205a6698f355ba5c8646ce296f258e1e8a Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:27:42 +0200 Subject: [PATCH 67/77] feat(zig_packages): validate and deduplicate matrix cells --- zig/private/bzlmod/zig_packages.bzl | 47 ++++++++++++++++++++++++ zig/tests/zig_packages_test.bzl | 55 ++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index a34cf47c..d5db88c5 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -75,6 +75,53 @@ def resolve_cell(tag): return (None, struct(name = tag.name, select_on = select_on, zig_options = zig_options)) +def check_cells(package_name, cells): + """Validate and deduplicate a package's matrix cells. + + Args: + package_name: the package the cells belong to, for error messages. + cells: the package's cells, each a + `struct(name, select_on, zig_options, config_setting)`. + + Returns: + (error, cells), the fallback cells followed by the deduplicated + non-fallback cells. + """ + fallbacks = [cell for cell in cells if cell.config_setting == ""] + nonfallback = [cell for cell in cells if cell.config_setting != ""] + + deduped = [] + by_conditions = {} + for cell in nonfallback: + if not cell.select_on: + return ("package '{}' config '{}' has no select conditions".format(package_name, cell.name), None) + + key = tuple(sorted(cell.select_on)) + existing = by_conditions.get(key) + if existing != None: + if existing.zig_options != cell.zig_options: + return ("package '{}' configs '{}' and '{}' share conditions but differ in build options".format( + package_name, + existing.name, + cell.name, + ), None) + continue + by_conditions[key] = cell + deduped.append(cell) + + for outer in deduped: + for inner in deduped: + if outer.name == inner.name: + continue + if len(outer.select_on) < len(inner.select_on) and all([c in inner.select_on for c in outer.select_on]): + return ("package '{}' config '{}' conditions are a subset of '{}'; they would match ambiguously".format( + package_name, + outer.name, + inner.name, + ), None) + + return (None, fallbacks + deduped) + def _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests): result = module_ctx.execute( [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", zig, cache, str(pkg_dir)] + diff --git a/zig/tests/zig_packages_test.bzl b/zig/tests/zig_packages_test.bzl index 613fc9e4..31d5d3f3 100644 --- a/zig/tests/zig_packages_test.bzl +++ b/zig/tests/zig_packages_test.bzl @@ -2,11 +2,14 @@ load("@bazel_skylib//lib:partial.bzl", "partial") load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load("//zig/private/bzlmod:zig_packages.bzl", "resolve_cell") +load("//zig/private/bzlmod:zig_packages.bzl", "check_cells", "resolve_cell") def _config(*, name, optimize = "", select_on = [], zig_flags = []): return struct(name = name, optimize = optimize, select_on = select_on, zig_flags = zig_flags) +def _cell(*, name, select_on = [], zig_options = [], config_setting = ""): + return struct(name = name, select_on = select_on, zig_options = zig_options, config_setting = config_setting) + def _resolve_cell_test_impl(ctx): env = unittest.begin(ctx) @@ -62,8 +65,58 @@ def _resolve_cell_test_impl(ctx): _resolve_cell_test = unittest.make(_resolve_cell_test_impl) +def _check_cells_test_impl(ctx): + env = unittest.begin(ctx) + + fallback = _cell(name = "dbg", select_on = ["//mode:debug"], config_setting = "") + rel = _cell(name = "rel", select_on = ["//mode:release"], zig_options = ["-Doptimize=ReleaseFast"], config_setting = "rel") + + asserts.equals( + env, + (None, [fallback, rel]), + check_cells("pkg", [fallback, rel]), + "a fallback and a distinct non-fallback cell pass through", + ) + + duplicate = _cell(name = "rel2", select_on = ["//mode:release"], zig_options = ["-Doptimize=ReleaseFast"], config_setting = "rel2") + asserts.equals( + env, + (None, [fallback, rel]), + check_cells("pkg", [fallback, rel, duplicate]), + "duplicate cells are merged", + ) + + conflicting = _cell(name = "rel3", select_on = ["//mode:release"], zig_options = ["-Doptimize=ReleaseSafe"], config_setting = "rel3") + asserts.equals( + env, + ("package 'pkg' configs 'rel' and 'rel3' share conditions but differ in build options", None), + check_cells("pkg", [fallback, rel, conflicting]), + "shared conditions with differing options conflict", + ) + + general = _cell(name = "g", select_on = ["//os:linux"], config_setting = "g") + specific = _cell(name = "s", select_on = ["//os:linux", "//mode:release"], config_setting = "s") + asserts.equals( + env, + ("package 'pkg' config 'g' conditions are a subset of 's'; they would match ambiguously", None), + check_cells("pkg", [fallback, general, specific]), + "a cell whose conditions are a subset of another's is rejected", + ) + + asserts.equals( + env, + ("package 'pkg' config 'e' has no select conditions", None), + check_cells("pkg", [fallback, _cell(name = "e", config_setting = "e")]), + "a non-fallback cell must have conditions", + ) + + return unittest.end(env) + +_check_cells_test = unittest.make(_check_cells_test_impl) + def zig_packages_test_suite(name): unittest.suite( name, partial.make(_resolve_cell_test, size = "small"), + partial.make(_check_cells_test, size = "small"), ) From 773b5d24a5047cce3d5323e7427b8cab6a56e54e Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:28:31 +0200 Subject: [PATCH 68/77] feat(zig_packages): split a package key into name and version --- zig/private/bzlmod/zig_packages.bzl | 13 +++++++++++++ zig/tests/zig_packages_test.bzl | 24 +++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index d5db88c5..13fcf731 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -122,6 +122,19 @@ def check_cells(package_name, cells): return (None, fallbacks + deduped) +def package_name_version(key): + """Split a URL package's hash key into its name and version. + + Args: + key: a URL package's Zig hash key, `--`. + `version` may contain `-` (e.g. `0.5.0-dev`). + + Returns: + (name, version). + """ + name, _, rest = key.partition("-") + return name, rest.rpartition("-")[0] + def _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests): result = module_ctx.execute( [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", zig, cache, str(pkg_dir)] + diff --git a/zig/tests/zig_packages_test.bzl b/zig/tests/zig_packages_test.bzl index 31d5d3f3..cf24dd81 100644 --- a/zig/tests/zig_packages_test.bzl +++ b/zig/tests/zig_packages_test.bzl @@ -2,7 +2,7 @@ load("@bazel_skylib//lib:partial.bzl", "partial") load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load("//zig/private/bzlmod:zig_packages.bzl", "check_cells", "resolve_cell") +load("//zig/private/bzlmod:zig_packages.bzl", "check_cells", "package_name_version", "resolve_cell") def _config(*, name, optimize = "", select_on = [], zig_flags = []): return struct(name = name, optimize = optimize, select_on = select_on, zig_flags = zig_flags) @@ -114,9 +114,31 @@ def _check_cells_test_impl(ctx): _check_cells_test = unittest.make(_check_cells_test_impl) +def _package_name_version_test_impl(ctx): + env = unittest.begin(ctx) + + asserts.equals( + env, + ("cfgdep", "0.0.0"), + package_name_version("cfgdep-0.0.0-AAAABBBBCCCC"), + "name and version split off the digest", + ) + + asserts.equals( + env, + ("ezi_gex", "0.5.0-dev"), + package_name_version("ezi_gex-0.5.0-dev-fTQAPBMbFwDJ"), + "a version may itself contain '-'", + ) + + return unittest.end(env) + +_package_name_version_test = unittest.make(_package_name_version_test_impl) + def zig_packages_test_suite(name): unittest.suite( name, partial.make(_resolve_cell_test, size = "small"), partial.make(_check_cells_test, size = "small"), + partial.make(_package_name_version_test, size = "small"), ) From 100cd5b850bef8f46063eb0212560075183ca383 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:30:04 +0200 Subject: [PATCH 69/77] feat(zig_packages): resolve a package's matrix cells --- zig/private/bzlmod/zig_packages.bzl | 45 ++++++++++++++++ zig/tests/zig_packages_test.bzl | 83 ++++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 13fcf731..a168bb11 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -135,6 +135,51 @@ def package_name_version(key): name, _, rest = key.partition("-") return name, rest.rpartition("-")[0] +def package_cells(key, cells_by_name, global_configure, per_package_configure): + """Resolve the matrix cells a URL package is configured under. + + Args: + key: the package's Zig hash key. + cells_by_name: map from config name to its `struct(name, select_on, zig_options)`. + global_configure: the global `configure` (with `configs`/`fallback`), or None. + per_package_configure: map from `(name, version)` to a `configure`. + + Returns: + (error, cells): cells is the ordered list of `struct(name, select_on, + zig_options, config_setting)`, or None if the package is configured + once at the host default. + """ + name, version = package_name_version(key) + + selected = per_package_configure.get((name, version)) + if selected == None: + selected = per_package_configure.get((name, "")) + if selected == None: + selected = global_configure + if selected == None: + return (None, None) + + if selected.fallback not in selected.configs: + return ("package '{}' configure fallback '{}' is not among its configs".format(name, selected.fallback), None) + + cells = [] + seen = {} + for config_name in selected.configs: + if config_name in seen: + return ("package '{}' configure lists config '{}' more than once".format(name, config_name), None) + seen[config_name] = True + cell = cells_by_name.get(config_name) + if cell == None: + return ("package '{}' configure references unknown config '{}'".format(name, config_name), None) + cells.append(struct( + name = cell.name, + select_on = cell.select_on, + zig_options = cell.zig_options, + config_setting = "" if config_name == selected.fallback else cell.name, + )) + + return check_cells(name, cells) + def _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests): result = module_ctx.execute( [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", zig, cache, str(pkg_dir)] + diff --git a/zig/tests/zig_packages_test.bzl b/zig/tests/zig_packages_test.bzl index cf24dd81..a481dca9 100644 --- a/zig/tests/zig_packages_test.bzl +++ b/zig/tests/zig_packages_test.bzl @@ -2,7 +2,7 @@ load("@bazel_skylib//lib:partial.bzl", "partial") load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load("//zig/private/bzlmod:zig_packages.bzl", "check_cells", "package_name_version", "resolve_cell") +load("//zig/private/bzlmod:zig_packages.bzl", "check_cells", "package_cells", "package_name_version", "resolve_cell") def _config(*, name, optimize = "", select_on = [], zig_flags = []): return struct(name = name, optimize = optimize, select_on = select_on, zig_flags = zig_flags) @@ -135,10 +135,91 @@ def _package_name_version_test_impl(ctx): _package_name_version_test = unittest.make(_package_name_version_test_impl) +_CELLS_BY_NAME = { + "dbg": struct(name = "dbg", select_on = ["//mode:debug"], zig_options = ["-Doptimize=Debug"]), + "rel": struct(name = "rel", select_on = ["//mode:release"], zig_options = ["-Doptimize=ReleaseFast"]), +} + +def _configure(*, configs, fallback): + return struct(configs = configs, fallback = fallback) + +def _configured(base, config_setting): + return struct(name = base.name, select_on = base.select_on, zig_options = base.zig_options, config_setting = config_setting) + +def _package_cells_test_impl(ctx): + env = unittest.begin(ctx) + + dbg = _configured(_CELLS_BY_NAME["dbg"], "") + rel = _configured(_CELLS_BY_NAME["rel"], "rel") + + asserts.equals( + env, + (None, None), + package_cells("pkg-1.0.0-xyz", _CELLS_BY_NAME, None, {}), + "no configure means no matrix", + ) + + asserts.equals( + env, + (None, [dbg, rel]), + package_cells("pkg-1.0.0-xyz", _CELLS_BY_NAME, _configure(configs = ["dbg", "rel"], fallback = "dbg"), {}), + "global configure applies", + ) + + asserts.equals( + env, + (None, [_configured(_CELLS_BY_NAME["rel"], "")]), + package_cells( + "pkg-1.0.0-xyz", + _CELLS_BY_NAME, + _configure(configs = ["dbg", "rel"], fallback = "dbg"), + {("pkg", "1.0.0"): _configure(configs = ["rel"], fallback = "rel")}, + ), + "a versioned per-package configure overrides the global one", + ) + + asserts.equals( + env, + (None, [dbg, rel]), + package_cells( + "pkg-1.0.0-xyz", + _CELLS_BY_NAME, + None, + {("pkg", ""): _configure(configs = ["dbg", "rel"], fallback = "dbg")}, + ), + "a version-less per-package configure matches any version", + ) + + asserts.equals( + env, + ("package 'pkg' configure fallback 'rel' is not among its configs", None), + package_cells("pkg-1.0.0-xyz", _CELLS_BY_NAME, _configure(configs = ["dbg"], fallback = "rel"), {}), + "the fallback must be one of the configs", + ) + + asserts.equals( + env, + ("package 'pkg' configure references unknown config 'nope'", None), + package_cells("pkg-1.0.0-xyz", _CELLS_BY_NAME, _configure(configs = ["nope"], fallback = "nope"), {}), + "an unknown config name is rejected", + ) + + asserts.equals( + env, + ("package 'pkg' configure lists config 'dbg' more than once", None), + package_cells("pkg-1.0.0-xyz", _CELLS_BY_NAME, _configure(configs = ["dbg", "rel", "dbg"], fallback = "dbg"), {}), + "a config listed more than once is rejected", + ) + + return unittest.end(env) + +_package_cells_test = unittest.make(_package_cells_test_impl) + def zig_packages_test_suite(name): unittest.suite( name, partial.make(_resolve_cell_test, size = "small"), partial.make(_check_cells_test, size = "small"), partial.make(_package_name_version_test, size = "small"), + partial.make(_package_cells_test, size = "small"), ) From b9befff71a50e94998f65e5cbe76cfd78ccf302e Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:31:47 +0200 Subject: [PATCH 70/77] feat(zig_packages): collect the matrix from the root module --- zig/private/bzlmod/zig_packages.bzl | 52 ++++++++++++++++ zig/tests/zig_packages_test.bzl | 92 ++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 3 deletions(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index a168bb11..68d7522a 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -180,6 +180,58 @@ def package_cells(key, cells_by_name, global_configure, per_package_configure): return check_cells(name, cells) +def collect_configs(modules): + """Collect the build-configuration matrix from the root module's tags. + + Configurations from non-root modules are ignored (their names are returned + so the caller can warn). + + Args: + modules: sequence of bazel_module, the extension's modules with + `tags.config` and `tags.configure`. + + Returns: + (error, result), where result is `struct(cells_by_name, global_configure, + per_package_configure, ignored)`. + """ + ignored = [] + cells_by_name = {} + global_configure = None + per_package_configure = {} + + for mod in modules: + if not mod.is_root: + if mod.tags.config or mod.tags.configure: + ignored.append(mod.name) + continue + + for tag in mod.tags.config: + error, cell = resolve_cell(tag) + if error != None: + return (error, None) + existing = cells_by_name.get(tag.name) + if existing != None and existing != cell: + return ("conflicting config tags named '{}'".format(tag.name), None) + cells_by_name[tag.name] = cell + + for tag in mod.tags.configure: + if tag.package: + key = (tag.package, tag.version) + if key in per_package_configure: + return ("multiple configure tags for package '{}'".format(tag.package), None) + per_package_configure[key] = tag + elif global_configure != None: + return ("at most one global configure tag is allowed", None) + else: + global_configure = tag + + return (None, struct( + cells_by_name = cells_by_name, + global_configure = global_configure, + per_package_configure = per_package_configure, + ignored = ignored, + )) + def _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests): result = module_ctx.execute( [zig, "run", "--cache-dir", cache, "--global-cache-dir", cache, zon2json, "--", zig, cache, str(pkg_dir)] + diff --git a/zig/tests/zig_packages_test.bzl b/zig/tests/zig_packages_test.bzl index a481dca9..026eaad8 100644 --- a/zig/tests/zig_packages_test.bzl +++ b/zig/tests/zig_packages_test.bzl @@ -2,11 +2,21 @@ load("@bazel_skylib//lib:partial.bzl", "partial") load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load("//zig/private/bzlmod:zig_packages.bzl", "check_cells", "package_cells", "package_name_version", "resolve_cell") +load( + "//zig/private/bzlmod:zig_packages.bzl", + "check_cells", + "collect_configs", + "package_cells", + "package_name_version", + "resolve_cell", +) def _config(*, name, optimize = "", select_on = [], zig_flags = []): return struct(name = name, optimize = optimize, select_on = select_on, zig_flags = zig_flags) +def _module(*, is_root, name = "m", config = [], configure = []): + return struct(is_root = is_root, name = name, tags = struct(config = config, configure = configure)) + def _cell(*, name, select_on = [], zig_options = [], config_setting = ""): return struct(name = name, select_on = select_on, zig_options = zig_options, config_setting = config_setting) @@ -140,8 +150,8 @@ _CELLS_BY_NAME = { "rel": struct(name = "rel", select_on = ["//mode:release"], zig_options = ["-Doptimize=ReleaseFast"]), } -def _configure(*, configs, fallback): - return struct(configs = configs, fallback = fallback) +def _configure(*, configs, fallback, package = "", version = ""): + return struct(configs = configs, fallback = fallback, package = package, version = version) def _configured(base, config_setting): return struct(name = base.name, select_on = base.select_on, zig_options = base.zig_options, config_setting = config_setting) @@ -215,6 +225,81 @@ def _package_cells_test_impl(ctx): _package_cells_test = unittest.make(_package_cells_test_impl) +def _collect_configs_test_impl(ctx): + env = unittest.begin(ctx) + + asserts.equals( + env, + (None, struct(cells_by_name = {}, global_configure = None, per_package_configure = {}, ignored = [])), + collect_configs([]), + "no modules yields an empty matrix", + ) + + global_cfg = _configure(configs = ["dbg", "rel"], fallback = "dbg") + per_pkg = _configure(configs = ["rel"], fallback = "rel", package = "curl") + asserts.equals( + env, + (None, struct( + cells_by_name = { + "dbg": struct(name = "dbg", select_on = ["@rules_zig//zig/config/mode:debug"], zig_options = ["-Doptimize=Debug"]), + "rel": struct(name = "rel", select_on = ["@rules_zig//zig/config/mode:release_fast"], zig_options = ["-Doptimize=ReleaseFast"]), + }, + global_configure = global_cfg, + per_package_configure = {("curl", ""): per_pkg}, + ignored = [], + )), + collect_configs([_module( + is_root = True, + config = [_config(name = "dbg", optimize = "debug"), _config(name = "rel", optimize = "release_fast")], + configure = [global_cfg, per_pkg], + )]), + "root config and configure tags are collected", + ) + + asserts.equals( + env, + (None, struct(cells_by_name = {}, global_configure = None, per_package_configure = {}, ignored = ["dep"])), + collect_configs([_module(is_root = False, name = "dep", config = [_config(name = "x", optimize = "debug")])]), + "non-root tags are ignored and reported", + ) + + asserts.equals( + env, + ("conflicting config tags named 'x'", None), + collect_configs([_module( + is_root = True, + config = [_config(name = "x", optimize = "debug"), _config(name = "x", optimize = "release_fast")], + )]), + "config tags sharing a name with differing content conflict", + ) + + asserts.equals( + env, + ("at most one global configure tag is allowed", None), + collect_configs([_module( + is_root = True, + configure = [_configure(configs = ["dbg"], fallback = "dbg"), _configure(configs = ["rel"], fallback = "rel")], + )]), + "only one global configure is allowed", + ) + + asserts.equals( + env, + ("multiple configure tags for package 'curl'", None), + collect_configs([_module( + is_root = True, + configure = [ + _configure(configs = ["dbg"], fallback = "dbg", package = "curl"), + _configure(configs = ["rel"], fallback = "rel", package = "curl"), + ], + )]), + "a package may not have two configure tags", + ) + + return unittest.end(env) + +_collect_configs_test = unittest.make(_collect_configs_test_impl) + def zig_packages_test_suite(name): unittest.suite( name, @@ -222,4 +307,5 @@ def zig_packages_test_suite(name): partial.make(_check_cells_test, size = "small"), partial.make(_package_name_version_test, size = "small"), partial.make(_package_cells_test, size = "small"), + partial.make(_collect_configs_test, size = "small"), ) From 086a4b1f5a7d7fd16ba04a52cdf1523c85fa0dd1 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:34:13 +0200 Subject: [PATCH 71/77] feat(zig_package): parse the configuration matrix cells --- zig/private/repo/zig_package.bzl | 23 ++++++++++++++ zig/tests/BUILD.bazel | 3 ++ zig/tests/zig_package_test.bzl | 51 ++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 zig/tests/zig_package_test.bzl diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 2b06bda8..bbcccc5e 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -142,6 +142,29 @@ cc_library( ) """ +def parse_cells(configs): + """Parse the `configs` attr into the package's configuration matrix cells. + + Args: + configs: the JSON `configs` attr, or "" for the host default. + + Returns: + (error, cells), each cell `struct(name, zig_options, config_setting)`. + """ + if not configs: + return (None, [struct(name = "", zig_options = [], config_setting = "")]) + + cells = [ + struct(name = cell["name"], zig_options = cell["zig_options"], config_setting = cell["config_setting"]) + for cell in json.decode(configs) + ] + fallbacks = [cell for cell in cells if cell.config_setting == ""] + if len(fallbacks) != 1: + return ("expected exactly one fallback cell, found {}".format(len(fallbacks)), None) + + rest = [cell for cell in cells if cell.config_setting != ""] + return (None, fallbacks + rest) + def _is_subtree(packages, owner): return bool(owner) and owner in packages and packages[owner]["path"] != None diff --git a/zig/tests/BUILD.bazel b/zig/tests/BUILD.bazel index a759c0e3..fe0fad11 100644 --- a/zig/tests/BUILD.bazel +++ b/zig/tests/BUILD.bazel @@ -16,6 +16,7 @@ load(":translate_c_action_test.bzl", "translate_c_action_test_suite") load(":use_cc_common_link_test.bzl", "use_cc_common_link_test_suite") load(":util.bzl", "forward_exec_settings") load(":versions_test.bzl", "versions_test_suite") +load(":zig_package_test.bzl", "zig_package_test_suite") load(":zig_packages_test.bzl", "zig_packages_test_suite") load(":zigopt_test.bzl", "zigopt_test_suite") @@ -53,6 +54,8 @@ translate_c_action_test_suite(name = "translate_c_action_test") versions_test_suite(name = "versions_test") +zig_package_test_suite(name = "zig_package_test") + zig_packages_test_suite(name = "zig_packages_test") zigopt_test_suite(name = "zigopt_test") diff --git a/zig/tests/zig_package_test.bzl b/zig/tests/zig_package_test.bzl new file mode 100644 index 00000000..424485fb --- /dev/null +++ b/zig/tests/zig_package_test.bzl @@ -0,0 +1,51 @@ +"""Unit tests for the `zig_package` repository rule helpers.""" + +load("@bazel_skylib//lib:partial.bzl", "partial") +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load("//zig/private/repo:zig_package.bzl", "parse_cells") + +def _parse_cells_test_impl(ctx): + env = unittest.begin(ctx) + + asserts.equals( + env, + (None, [struct(name = "", zig_options = [], config_setting = "")]), + parse_cells(""), + "no matrix yields a single host fallback cell", + ) + + dbg = {"name": "dbg", "zig_options": ["-Doptimize=Debug"], "config_setting": ""} + rel = {"name": "rel", "zig_options": ["-Doptimize=ReleaseFast"], "config_setting": "rel"} + asserts.equals( + env, + (None, [ + struct(name = "dbg", zig_options = ["-Doptimize=Debug"], config_setting = ""), + struct(name = "rel", zig_options = ["-Doptimize=ReleaseFast"], config_setting = "rel"), + ]), + parse_cells(json.encode([rel, dbg])), + "the fallback cell is ordered first", + ) + + asserts.equals( + env, + ("expected exactly one fallback cell, found 0", None), + parse_cells(json.encode([rel])), + "a matrix must have a fallback", + ) + + asserts.equals( + env, + ("expected exactly one fallback cell, found 2", None), + parse_cells(json.encode([dbg, dict(rel, config_setting = "")])), + "a matrix must have only one fallback", + ) + + return unittest.end(env) + +_parse_cells_test = unittest.make(_parse_cells_test_impl) + +def zig_package_test_suite(name): + unittest.suite( + name, + partial.make(_parse_cells_test, size = "small"), + ) From 0075ffca887319bd2a01e69cd483845f3429996b Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:35:39 +0200 Subject: [PATCH 72/77] feat(zig_package): merge per-configuration target records --- zig/private/repo/zig_package.bzl | 68 +++++++++++++++++++++++++++ zig/tests/zig_package_test.bzl | 80 +++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index bbcccc5e..28b61b75 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -165,6 +165,74 @@ def parse_cells(configs): rest = [cell for cell in cells if cell.config_setting != ""] return (None, fallbacks + rest) +def merge(package, per_cell_data, cells): + """Merge each cell's target records into one set, pushing `select()` to attrs. + + The set of target names must agree across cells, as must each target's `kind` + and its `fixed` attrs. Each `varying` attr resolves to `("const", value)` when + equal across cells, else `("select", {cell: value})`. + + Args: + package: the package URL, for error messages. + per_cell_data: map from cell name to that cell's list of target records. + cells: the ordered cells, the fallback first. + + Returns: + (error, merged), each merged record a `struct(kind, name, fixed, varying)`, + fallback first. + """ + fallback = cells[0].name + + indexed = { + cell.name: {record.name: record for record in per_cell_data[cell.name]} + for cell in cells + } + + base_names = sorted(indexed[fallback]) + for cell in cells[1:]: + if sorted(indexed[cell.name]) != base_names: + return ("package '{}' produces a different set of targets under config '{}' than under '{}'".format( + package, + cell.name, + fallback, + ), None) + + merged = [] + for name in indexed[fallback]: + base = indexed[fallback][name] + + for cell in cells[1:]: + other = indexed[cell.name][name] + if other.kind != base.kind: + return ("package '{}' target '{}' has kind '{}' under config '{}' but '{}' under '{}'".format( + package, + name, + other.kind, + cell.name, + base.kind, + fallback, + ), None) + for attr in base.fixed: + if base.fixed[attr] != other.fixed[attr]: + return ("package '{}' target '{}' fixed attribute '{}' varies by configuration".format( + package, + base.name, + attr, + ), None) + + resolved = {} + for attr in base.varying: + value = base.varying[attr] + uniform = all([indexed[cell.name][name].varying[attr] == value for cell in cells[1:]]) + if uniform: + resolved[attr] = ("const", value) + else: + resolved[attr] = ("select", {cell.name: indexed[cell.name][name].varying[attr] for cell in cells}) + + merged.append(struct(kind = base.kind, name = base.name, fixed = base.fixed, varying = resolved)) + + return (None, merged) + def _is_subtree(packages, owner): return bool(owner) and owner in packages and packages[owner]["path"] != None diff --git a/zig/tests/zig_package_test.bzl b/zig/tests/zig_package_test.bzl index 424485fb..4286426f 100644 --- a/zig/tests/zig_package_test.bzl +++ b/zig/tests/zig_package_test.bzl @@ -2,7 +2,13 @@ load("@bazel_skylib//lib:partial.bzl", "partial") load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load("//zig/private/repo:zig_package.bzl", "parse_cells") +load("//zig/private/repo:zig_package.bzl", "merge", "parse_cells") + +def _record(*, kind = "zig_library", name, fixed = {}, varying = {}): + return struct(kind = kind, name = name, fixed = fixed, varying = varying) + +_FALLBACK = struct(name = "dbg", config_setting = "") +_REL = struct(name = "rel", config_setting = "rel") def _parse_cells_test_impl(ctx): env = unittest.begin(ctx) @@ -44,8 +50,80 @@ def _parse_cells_test_impl(ctx): _parse_cells_test = unittest.make(_parse_cells_test_impl) +def _merge_test_impl(ctx): + env = unittest.begin(ctx) + + asserts.equals( + env, + (None, [_record(name = "foo", fixed = {"main": "foo.zig"}, varying = {"deps": ("const", ["a"])})]), + merge("pkg", {"dbg": [_record(name = "foo", fixed = {"main": "foo.zig"}, varying = {"deps": ["a"]})]}, [_FALLBACK]), + "a single cell makes every varying attr constant", + ) + + asserts.equals( + env, + (None, [_record( + name = "foo", + fixed = {"main": "foo.zig"}, + varying = {"deps": ("select", {"dbg": ["a"], "rel": ["b"]}), "import_names": ("const", {})}, + )]), + merge( + "pkg", + { + "dbg": [_record(name = "foo", fixed = {"main": "foo.zig"}, varying = {"deps": ["a"], "import_names": {}})], + "rel": [_record(name = "foo", fixed = {"main": "foo.zig"}, varying = {"deps": ["b"], "import_names": {}})], + }, + [_FALLBACK, _REL], + ), + "a differing attr becomes a select; an agreeing attr stays constant", + ) + + asserts.equals( + env, + ("package 'pkg' produces a different set of targets under config 'rel' than under 'dbg'", None), + merge( + "pkg", + {"dbg": [_record(name = "foo")], "rel": [_record(name = "foo"), _record(name = "bar")]}, + [_FALLBACK, _REL], + ), + "the set of targets must be constant across configs", + ) + + asserts.equals( + env, + ("package 'pkg' target 'foo' has kind 'cc_library' under config 'rel' but 'zig_library' under 'dbg'", None), + merge( + "pkg", + { + "dbg": [_record(kind = "zig_library", name = "foo")], + "rel": [_record(kind = "cc_library", name = "foo")], + }, + [_FALLBACK, _REL], + ), + "a target's kind must be constant across configs", + ) + + asserts.equals( + env, + ("package 'pkg' target 'foo' fixed attribute 'main' varies by configuration", None), + merge( + "pkg", + { + "dbg": [_record(name = "foo", fixed = {"main": "foo.zig"})], + "rel": [_record(name = "foo", fixed = {"main": "bar.zig"})], + }, + [_FALLBACK, _REL], + ), + "a fixed attribute must be constant across configs", + ) + + return unittest.end(env) + +_merge_test = unittest.make(_merge_test_impl) + def zig_package_test_suite(name): unittest.suite( name, partial.make(_parse_cells_test, size = "small"), + partial.make(_merge_test, size = "small"), ) From 376d33524cfe67fd1450536b301dd11e85b3747b Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:37:34 +0200 Subject: [PATCH 73/77] feat(zig_package): render merged records to BUILD text --- zig/private/repo/zig_package.bzl | 79 ++++++++++++++++++++++++++++++++ zig/tests/zig_package_test.bzl | 77 ++++++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 1 deletion(-) diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index 28b61b75..f14a54ed 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -233,6 +233,85 @@ def merge(package, per_cell_data, cells): return (None, merged) +def render_attr(config_settings, resolved, cells): + """Render a merged attribute as a literal or a `select()`. + + Args: + config_settings: map from a non-fallback cell name to its config_setting label. + resolved: `("const", value)` or `("select", {cell: value})`. + cells: configuration matrix cells. + + Returns: + the attribute's Starlark code. + """ + if resolved[0] == "const": + return json.encode(resolved[1]) + + by_cell = resolved[1] + lines = ["select({"] + for cell in cells: + if cell.config_setting != "": + lines.append(" {}: {},".format(json.encode(config_settings[cell.name]), json.encode(by_cell[cell.name]))) + for cell in cells: + if cell.config_setting == "": + lines.append(" {}: {},".format(json.encode("//conditions:default"), json.encode(by_cell[cell.name]))) + lines.append("})") + return "\n".join(lines) + +def render(config_settings, merged, cells): + """Render merged target records into the spoke's `BUILD.bazel` text. + + Args: + config_settings: map from a non-fallback cell name to its config_setting label. + merged: the merged target records from `merge`. + cells: configuration matrix cells. + + Returns: + the `BUILD.bazel` contents. + """ + cc_chunks = [] + library_chunks = [] + has_cc = False + for record in merged: + fixed = record.fixed + varying = record.varying + if record.kind == "zig_library": + library_chunks.append(_ZIG_LIBRARY.format( + name = record.name, + main = fixed["main"], + deps = render_attr(config_settings, varying["deps"], cells), + import_names = render_attr(config_settings, varying["import_names"], cells), + )) + elif record.kind == "zig_library_subtree": + library_chunks.append(_ZIG_LIBRARY_SUBTREE.format( + name = record.name, + import_name = fixed["import_name"], + main = fixed["main"], + subpath = fixed["subpath"], + deps = render_attr(config_settings, varying["deps"], cells), + import_names = render_attr(config_settings, varying["import_names"], cells), + )) + elif record.kind == "cc_library": + has_cc = True + cc_chunks.append(_CC_LIBRARY.format( + name = record.name, + srcs = render_attr(config_settings, varying["srcs"], cells), + hdrs = json.encode(fixed["header_globs"]), + copts = render_attr(config_settings, varying["copts"], cells), + includes = render_attr(config_settings, varying["includes"], cells), + )) + elif record.kind == "cc_library_group": + has_cc = True + cc_chunks.append(_CC_LIBRARY_GROUP.format( + name = record.name, + deps = render_attr(config_settings, varying["deps"], cells), + )) + + loads = ["load(\"@rules_zig//zig:defs.bzl\", \"zig_library\")"] + if has_cc: + loads.append("load(\"@rules_cc//cc:defs.bzl\", \"cc_library\")") + return "\n".join(loads + ["", _FILES] + cc_chunks + library_chunks) + def _is_subtree(packages, owner): return bool(owner) and owner in packages and packages[owner]["path"] != None diff --git a/zig/tests/zig_package_test.bzl b/zig/tests/zig_package_test.bzl index 4286426f..06678c56 100644 --- a/zig/tests/zig_package_test.bzl +++ b/zig/tests/zig_package_test.bzl @@ -2,7 +2,7 @@ load("@bazel_skylib//lib:partial.bzl", "partial") load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load("//zig/private/repo:zig_package.bzl", "merge", "parse_cells") +load("//zig/private/repo:zig_package.bzl", "merge", "parse_cells", "render", "render_attr") def _record(*, kind = "zig_library", name, fixed = {}, varying = {}): return struct(kind = kind, name = name, fixed = fixed, varying = varying) @@ -121,9 +121,84 @@ def _merge_test_impl(ctx): _merge_test = unittest.make(_merge_test_impl) +def _render_attr_test_impl(ctx): + env = unittest.begin(ctx) + + asserts.equals( + env, + '["a"]', + render_attr({}, ("const", ["a"]), [_FALLBACK]), + "a constant attribute renders without select", + ) + + asserts.equals( + env, + """\ +select({ + "@x//config:cfg_rel": ["b"], + "//conditions:default": ["a"], +})""", + render_attr({"rel": "@x//config:cfg_rel"}, ("select", {"dbg": ["a"], "rel": ["b"]}), [_FALLBACK, _REL]), + "a select renders non-fallback cells, then the fallback as //conditions:default", + ) + + return unittest.end(env) + +_render_attr_test = unittest.make(_render_attr_test_impl) + +def _render_test_impl(ctx): + env = unittest.begin(ctx) + + out = render( + {}, + [_record(name = "foo", fixed = {"main": "foo.zig"}, varying = {"deps": ("const", ["a"]), "import_names": ("const", {})})], + [_FALLBACK], + ) + asserts.true(env, 'load("@rules_zig//zig:defs.bzl", "zig_library")' in out, "loads zig_library") + asserts.true(env, 'name = "foo"' in out, "emits the target") + asserts.true(env, 'deps = ["a"]' in out, "inlines a constant attribute") + asserts.true(env, "@rules_cc" not in out, "omits the cc_library load with no C targets") + + out = render( + {}, + [ + _record( + kind = "zig_library_subtree", + name = "vendor/foo", + fixed = {"import_name": "foo", "main": "vendor/foo/foo.zig", "subpath": "vendor/foo"}, + varying = {"deps": ("const", [":bar"]), "import_names": ("const", {})}, + ), + _record( + kind = "cc_library", + name = "z.cinc.0", + fixed = {"header_globs": ["c/**/*.h"]}, + varying = {"srcs": ("const", ["c/z.c"]), "copts": ("const", ["-DZ"]), "includes": ("const", ["c"])}, + ), + _record( + kind = "cc_library_group", + name = "z.cinc", + varying = {"deps": ("const", [":z.cinc.0"])}, + ), + ], + [_FALLBACK], + ) + asserts.true(env, 'load("@rules_cc//cc:defs.bzl", "cc_library")' in out, "loads cc_library when C targets are present") + asserts.true(env, 'name = "vendor/foo"' in out, "emits the subtree library scoped by sub-path") + asserts.true(env, 'import_name = "foo"' in out, "the subtree library keeps the module's import name") + asserts.true(env, 'srcs = glob(["vendor/foo/**/*.zig"], exclude = ["vendor/foo/foo.zig"])' in out, "globs the subtree's sources") + asserts.true(env, 'srcs = ["c/z.c"]' in out, "emits the cc_library sources") + asserts.true(env, 'hdrs = glob(["c/**/*.h"], allow_empty = True)' in out, "globs the cc_library headers") + asserts.true(env, 'deps = [":z.cinc.0"]' in out, "the cc_library group depends on its source groups") + + return unittest.end(env) + +_render_test = unittest.make(_render_test_impl) + def zig_package_test_suite(name): unittest.suite( name, partial.make(_parse_cells_test, size = "small"), partial.make(_merge_test, size = "small"), + partial.make(_render_attr_test, size = "small"), + partial.make(_render_test, size = "small"), ) From 8ca78f43599f134c794c5f641e3a40f2fe3c68eb Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:46:37 +0200 Subject: [PATCH 74/77] feat(zig_package): configure across the matrix --- zig/private/repo/zig_package.bzl | 137 ++++++++++++++++++++----------- 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl index f14a54ed..2f442797 100644 --- a/zig/private/repo/zig_package.bzl +++ b/zig/private/repo/zig_package.bzl @@ -27,6 +27,13 @@ ATTRS = { "system_integrations": attr.string_list( doc = "Names of optional system integrations (`systemIntegrationOption`) to enable when configuring the package.", ), + "configs": attr.string( + default = "", + doc = "JSON configuration-matrix cells; empty configures once at the host default.", + ), + "config_settings": attr.string_keyed_label_dict( + doc = "Map from a non-fallback cell name to its config_setting_group label.", + ), } def _package_prefix(repository_ctx, zig, helper, cache, archive): @@ -341,9 +348,10 @@ def _module_dep(repository_ctx, imported, packages): return str(spoke.same_package_label(imported["module"])) return ":" + imported["module"] -def _build_file(repository_ctx, modules, packages): - library_chunks = [] - cc_chunks = [] +def _target_data(repository_ctx, modules, packages): + """Compute one configuration cell's generated-target records.""" + cc_records = [] + library_records = [] for module in modules: if not module["root_source"]: continue @@ -431,44 +439,40 @@ def _build_file(repository_ctx, modules, packages): paths = groups[index][1] group_name = "{}.{}".format(cinc, index) group_labels.append(":" + group_name) - cc_chunks.append(_CC_LIBRARY.format( + cc_records.append(struct( + kind = "cc_library", name = group_name, - srcs = json.encode(paths), - hdrs = json.encode(header_globs), - copts = json.encode(flags), - includes = json.encode(includes), + fixed = {"header_globs": header_globs}, + varying = {"srcs": paths, "copts": flags, "includes": includes}, )) - cc_chunks.append(_CC_LIBRARY_GROUP.format( + cc_records.append(struct( + kind = "cc_library_group", name = cinc, - deps = json.encode(group_labels), + fixed = {}, + varying = {"deps": group_labels}, )) deps.append(":" + cinc) if not owner: - library_chunks.append(_ZIG_LIBRARY.format( + library_records.append(struct( + kind = "zig_library", name = module["name"], - main = module["root_source"], - deps = json.encode(deps), - import_names = json.encode(import_names), + fixed = {"main": module["root_source"], "import_name": module["name"]}, + varying = {"deps": deps, "import_names": import_names}, )) else: subpath = packages[owner]["path"] - library_chunks.append(_ZIG_LIBRARY_SUBTREE.format( + library_records.append(struct( + kind = "zig_library_subtree", name = _target_name(packages, owner, module["name"]), - import_name = module["name"], - main = subpath + "/" + module["root_source"], - subpath = subpath, - deps = json.encode(deps), - import_names = json.encode(import_names), + fixed = {"main": subpath + "/" + module["root_source"], "import_name": module["name"], "subpath": subpath}, + varying = {"deps": deps, "import_names": import_names}, )) - loads = ["load(\"@rules_zig//zig:defs.bzl\", \"zig_library\")"] - if cc_chunks: - loads.append("load(\"@rules_cc//cc:defs.bzl\", \"cc_library\")") - return "\n".join(loads + ["", _FILES] + cc_chunks + library_chunks) + return cc_records + library_records -def _configure(repository_ctx, zig, build_zig, cache): - """Configure the package's `build.zig` and return its module-graph JSON.""" +def _compile_configurer(repository_ctx, zig, build_zig, cache): + """Compile the configurer once, returning its executable path.""" configurer = repository_ctx.path(Label("//zig/private:configurer.zig")) deps = json.decode(repository_ctx.attr.deps) @@ -501,20 +505,20 @@ def _configure(repository_ctx, zig, build_zig, cache): if compiled.return_code != 0: fail("Failed to compile the Zig configurer for '{}':\n{}".format(repository_ctx.attr.url, compiled.stderr)) - configure_args = [ - str(repository_ctx.path("_configure/configurer")), - "--zig", - str(zig), - "--build-root", - str(repository_ctx.path(".")), - ] + return repository_ctx.path("_configure/configurer") + +def _configure(repository_ctx, zig, configurer_bin, cell): + """Run the compiled configurer for one cell, returning its module-graph JSON.""" + args = [str(configurer_bin), "--zig", str(zig), "--build-root", str(repository_ctx.path("."))] for name in repository_ctx.attr.system_integrations: - configure_args.extend(["--system-integration", name]) - configured = repository_ctx.execute(configure_args) - if configured.return_code != 0: - fail("Failed to configure the Zig package '{}':\n{}".format(repository_ctx.attr.url, configured.stderr)) + args.extend(["--system-integration", name]) + for option in cell.zig_options: + args.extend(["--zig-option", option]) - repository_ctx.delete("_configure") + configured = repository_ctx.execute(args) + if configured.return_code != 0: + scope = " under configuration '{}'".format(cell.name) if cell.name else "" + fail("Failed to configure the Zig package '{}'{}:\n{}".format(repository_ctx.attr.url, scope, configured.stderr)) return configured.stdout def _materialize_generated(repository_ctx, modules, packages): @@ -549,16 +553,57 @@ def _zig_package_impl(repository_ctx): archive = repository_ctx.path(cache).get_child("p").get_child(fetched_hash + ".tar.gz") repository_ctx.extract(archive, strip_prefix = _package_prefix(repository_ctx, zig, helper, cache, archive)) - build_zig = repository_ctx.path("build.zig") - modules = [] - if build_zig.exists: - manifest = _configure(repository_ctx, zig, build_zig, cache) - repository_ctx.file("module_manifest.json", manifest) - modules = json.decode(manifest)["modules"] + error, cells = parse_cells(repository_ctx.attr.configs) + if error != None: + fail("The Zig package '{}' has an invalid configuration matrix: {}.".format(repository_ctx.attr.url, error)) + fallback = cells[0].name packages = json.decode(repository_ctx.attr.deps)["packages"] - _materialize_generated(repository_ctx, modules, packages) - repository_ctx.file("BUILD.bazel", _build_file(repository_ctx, modules, packages)) + config_settings = {name: str(label) for name, label in repository_ctx.attr.config_settings.items()} + + build_zig = repository_ctx.path("build.zig") + if not build_zig.exists: + repository_ctx.file("BUILD.bazel", render(config_settings, [], cells)) + return + + # Configure once per cell with that cell's `-D` build options. + configurer_bin = _compile_configurer(repository_ctx, zig, build_zig, cache) + per_cell_modules = {} + for cell in cells: + manifest = _configure(repository_ctx, zig, configurer_bin, cell) + if cell.name == fallback: + repository_ctx.file("module_manifest.json", manifest) + per_cell_modules[cell.name] = json.decode(manifest)["modules"] + repository_ctx.delete("_configure") + + # Generated source is materialized to one file shared by every cell, so it + # must not vary across them; reflect the fallback's into the others. + fallback_generated = { + module["name"]: module["generated_source"] + for module in per_cell_modules[fallback] + if module.get("generated_source") != None + } + for cell in cells[1:]: + for module in per_cell_modules[cell.name]: + if module.get("generated_source") != fallback_generated.get(module["name"]): + fail(("The Zig package '{}' module '{}' produces different generated source under " + + "configuration '{}' than under '{}'; config-varying generated source is not " + + "supported.").format(repository_ctx.attr.url, module["name"], cell.name, fallback)) + _materialize_generated(repository_ctx, per_cell_modules[fallback], packages) + fallback_root_source = {module["name"]: module["root_source"] for module in per_cell_modules[fallback]} + for cell in cells[1:]: + for module in per_cell_modules[cell.name]: + if module["name"] in fallback_root_source: + module["root_source"] = fallback_root_source[module["name"]] + + per_cell_data = { + cell.name: _target_data(repository_ctx, per_cell_modules[cell.name], packages) + for cell in cells + } + error, merged = merge(repository_ctx.attr.url, per_cell_data, cells) + if error != None: + fail("The Zig package '{}' cannot be configured across the matrix: {}.".format(repository_ctx.attr.url, error)) + repository_ctx.file("BUILD.bazel", render(config_settings, merged, cells)) zig_package = repository_rule( _zig_package_impl, From fdd6996b7522971648d01adc0b5d6756633df5b1 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:49:47 +0200 Subject: [PATCH 75/77] feat(zig_deps_hub): generate matrix config_setting_groups --- zig/private/repo/zig_deps_hub.bzl | 36 +++++++++++++++++++++++++++++++ zig/tests/BUILD.bazel | 3 +++ zig/tests/zig_deps_hub_test.bzl | 32 +++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 zig/tests/zig_deps_hub_test.bzl diff --git a/zig/private/repo/zig_deps_hub.bzl b/zig/private/repo/zig_deps_hub.bzl index 94fd7fe3..df1995d0 100644 --- a/zig/private/repo/zig_deps_hub.bzl +++ b/zig/private/repo/zig_deps_hub.bzl @@ -19,6 +19,10 @@ ATTRS = { mandatory = True, doc = "Map from each package key to its `files` target.", ), + "config_groups": attr.string( + default = "[]", + doc = "JSON list of `{name, select_on}`, the non-fallback configuration cells across all packages.", + ), } _ALIAS = """\ @@ -29,6 +33,34 @@ alias( ) """ +_CONFIG_GROUP = """\ +selects.config_setting_group( + name = "cfg_{name}", + match_all = {match_all}, + visibility = ["//visibility:public"], +) +""" + +def render_config_groups(config_groups): + """Render the matrix's config_setting_groups as the hub's `config/BUILD.bazel`. + + Each cell becomes a `cfg_` group matching all of its conditions, which + the spokes `select()` on. + + Args: + config_groups: list of `{name, select_on}` (the non-fallback cells). + + Returns: + the `config/BUILD.bazel` text, or "" when there are no groups. + """ + if not config_groups: + return "" + + chunks = ["load(\"@bazel_skylib//lib:selects.bzl\", \"selects\")"] + for group in config_groups: + chunks.append(_CONFIG_GROUP.format(name = group["name"], match_all = json.encode(group["select_on"]))) + return "\n".join(chunks) + # The generated accessors reference each spoke by its canonical (`@@`) repo name, # which is resolved at module-extension time and has no apparent-name alias here. # buildifier: disable=canonical-repository @@ -119,6 +151,10 @@ def _zig_deps_hub_impl(repository_ctx): repository_ctx.file("defs.bzl", _DEFS.replace("%MANIFESTS%", json.encode(registry))) + config_build = render_config_groups(json.decode(repository_ctx.attr.config_groups)) + if config_build: + repository_ctx.file("config/BUILD.bazel", config_build) + zig_deps_hub = repository_rule( _zig_deps_hub_impl, attrs = ATTRS, diff --git a/zig/tests/BUILD.bazel b/zig/tests/BUILD.bazel index fe0fad11..139f1931 100644 --- a/zig/tests/BUILD.bazel +++ b/zig/tests/BUILD.bazel @@ -16,6 +16,7 @@ load(":translate_c_action_test.bzl", "translate_c_action_test_suite") load(":use_cc_common_link_test.bzl", "use_cc_common_link_test_suite") load(":util.bzl", "forward_exec_settings") load(":versions_test.bzl", "versions_test_suite") +load(":zig_deps_hub_test.bzl", "zig_deps_hub_test_suite") load(":zig_package_test.bzl", "zig_package_test_suite") load(":zig_packages_test.bzl", "zig_packages_test_suite") load(":zigopt_test.bzl", "zigopt_test_suite") @@ -54,6 +55,8 @@ translate_c_action_test_suite(name = "translate_c_action_test") versions_test_suite(name = "versions_test") +zig_deps_hub_test_suite(name = "zig_deps_hub_test") + zig_package_test_suite(name = "zig_package_test") zig_packages_test_suite(name = "zig_packages_test") diff --git a/zig/tests/zig_deps_hub_test.bzl b/zig/tests/zig_deps_hub_test.bzl new file mode 100644 index 00000000..a2a27177 --- /dev/null +++ b/zig/tests/zig_deps_hub_test.bzl @@ -0,0 +1,32 @@ +"""Unit tests for the `zig_deps` hub repository rule helpers.""" + +load("@bazel_skylib//lib:partial.bzl", "partial") +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load("//zig/private/repo:zig_deps_hub.bzl", "render_config_groups") + +def _render_config_groups_test_impl(ctx): + env = unittest.begin(ctx) + + asserts.equals( + env, + "", + render_config_groups([]), + "no groups renders nothing", + ) + + out = render_config_groups([{"name": "rel", "select_on": ["@platforms//os:linux", "//zig/config/mode:release_fast"]}]) + asserts.true(env, "load(\"@bazel_skylib//lib:selects.bzl\", \"selects\")" in out, "loads selects") + asserts.true(env, "name = \"cfg_rel\"" in out, "names the group cfg_") + asserts.true(env, "match_all = [" in out, "matches all conditions") + asserts.true(env, "\"@platforms//os:linux\"" in out, "includes the os condition") + asserts.true(env, "\"//zig/config/mode:release_fast\"" in out, "includes the mode condition") + + return unittest.end(env) + +_render_config_groups_test = unittest.make(_render_config_groups_test_impl) + +def zig_deps_hub_test_suite(name): + unittest.suite( + name, + partial.make(_render_config_groups_test, size = "small"), + ) From eb0d58de6b4e3539edb9de57f8b5df7e2440998f Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:56:02 +0200 Subject: [PATCH 76/77] feat(zig_packages): wire the configuration matrix into the extension --- zig/private/bzlmod/zig_packages.bzl | 73 ++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/zig/private/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl index 68d7522a..5ecc88d7 100644 --- a/zig/private/bzlmod/zig_packages.bzl +++ b/zig/private/bzlmod/zig_packages.bzl @@ -36,6 +36,24 @@ system_integration = tag_class( }, ) +config = tag_class( + attrs = { + "name": attr.string(mandatory = True, doc = "Module-local name of this configuration cell."), + "optimize": attr.string(doc = "Zig optimize mode: debug | release_safe | release_small | release_fast."), + "select_on": attr.string_list(doc = "Extra Bazel condition labels ANDed into this cell's `select()` branch."), + "zig_flags": attr.string_list(doc = "Extra `-D` build options (NAME=VALUE) passed to the configurer for this cell."), + }, +) + +configure = tag_class( + attrs = { + "configs": attr.string_list(mandatory = True, doc = "Ordered config names that apply."), + "fallback": attr.string(mandatory = True, doc = "The config name used for the `//conditions:default` branch."), + "package": attr.string(doc = "If set, override the matrix for the named package only; otherwise global."), + "version": attr.string(doc = "Disambiguate `package` by version."), + }, +) + _OPTIMIZE_MODES = { "debug": "Debug", "release_safe": "ReleaseSafe", @@ -341,6 +359,13 @@ def _zig_packages_impl(module_ctx): system_integrations = system_integrations.keys() + error, matrix = collect_configs(module_ctx.modules) + if error != None: + fail("Invalid Zig package configuration matrix: {}.".format(error)) + for name in matrix.ignored: + # buildifier: disable=print + print("ignoring config/configure tags from non-root module '{}'; set dev_dependency to True to avoid this warning".format(name)) + graph = _resolve_graph(module_ctx, zig, zon2json, cache, pkg_dir, manifests) graph = _localize_paths(graph, str(pkg_dir), manifest_labels) @@ -349,6 +374,7 @@ def _zig_packages_impl(module_ctx): # Reachability follows every edge (URL and sub-tree path), so URL spokes reached # through a sub-tree dependency are configured too. reachable = {} + config_groups = {} for key, package in graph["packages"].items(): reached = {} for _name, child in graph["packages"][key]["deps"].items(): @@ -356,23 +382,46 @@ def _zig_packages_impl(module_ctx): for dep in reachable[child]: reached[dep] = True reachable[key] = reached - if package["url"] != None: - spokes = [dep for dep in reached if graph["packages"][dep]["url"] != None] - zig_package( - name = key, - url = package["url"], - zig_hash = key, - deps = json.encode(_deps_data(graph, key, reached)), - dep_build_files = {dep: "@{}//:build.zig".format(dep) for dep in spokes}, - system_libraries = system_libraries, - system_integrations = system_integrations, - ) + if package["url"] == None: + continue + + error, cells = package_cells(key, matrix.cells_by_name, matrix.global_configure, matrix.per_package_configure) + if error != None: + fail("Invalid Zig package configuration matrix: {}.".format(error)) + + configs = {} + if cells != None: + config_settings = {} + for cell in cells: + if cell.config_setting != "": + config_settings[cell.name] = "@zig_deps//config:cfg_" + cell.name + config_groups[cell.name] = {"name": cell.name, "select_on": cell.select_on} + configs = { + "configs": json.encode([ + {"name": cell.name, "zig_options": cell.zig_options, "config_setting": cell.config_setting} + for cell in cells + ]), + "config_settings": config_settings, + } + + spokes = [dep for dep in reached if graph["packages"][dep]["url"] != None] + zig_package( + name = key, + url = package["url"], + zig_hash = key, + deps = json.encode(_deps_data(graph, key, reached)), + dep_build_files = {dep: "@{}//:build.zig".format(dep) for dep in spokes}, + system_libraries = system_libraries, + system_integrations = system_integrations, + **configs + ) manifests, targets = _hub_data(graph, root_tags) zig_deps_hub( name = "zig_deps", manifests = json.encode(manifests), packages = targets, + config_groups = json.encode([config_groups[name] for name in sorted(config_groups)]), ) direct = ["zig_deps"] if root_nondev else [] @@ -420,5 +469,7 @@ zig_packages = module_extension( "from_file": from_file, "system_library": system_library, "system_integration": system_integration, + "config": config, + "configure": configure, }, ) From 31ea44121039a33f162dafe49536029e9252b15d Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Fri, 19 Jun 2026 11:56:03 +0200 Subject: [PATCH 77/77] test: cover the build-configuration matrix end to end --- .../integration_tests/packages/BUILD.bazel | 12 ++++++++++ .../integration_tests/packages/MODULE.bazel | 24 +++++++++++++++++++ .../integration_tests/packages/build.zig.zon | 4 ++++ .../integration_tests/packages/dbgonly.c | 3 +++ .../packages/fixtures/cfgdep/build.zig | 15 ++++++++++++ .../packages/fixtures/cfgdep/build.zig.zon | 10 ++++++++ .../packages/fixtures/cfgdep/src/cfgdep.zig | 12 ++++++++++ zig/tests/integration_tests/packages/main.zig | 2 ++ .../integration_tests/packages/relonly.c | 3 +++ .../packages_tests_runner.zig | 10 +++++++- 10 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 zig/tests/integration_tests/packages/dbgonly.c create mode 100644 zig/tests/integration_tests/packages/fixtures/cfgdep/build.zig create mode 100644 zig/tests/integration_tests/packages/fixtures/cfgdep/build.zig.zon create mode 100644 zig/tests/integration_tests/packages/fixtures/cfgdep/src/cfgdep.zig create mode 100644 zig/tests/integration_tests/packages/relonly.c diff --git a/zig/tests/integration_tests/packages/BUILD.bazel b/zig/tests/integration_tests/packages/BUILD.bazel index cc1c9d2d..1f1225a0 100644 --- a/zig/tests/integration_tests/packages/BUILD.bazel +++ b/zig/tests/integration_tests/packages/BUILD.bazel @@ -28,3 +28,15 @@ cc_library( srcs = ["optmath.c"], visibility = ["//visibility:public"], ) + +cc_library( + name = "dbgonly_impl", + srcs = ["dbgonly.c"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "relonly_impl", + srcs = ["relonly.c"], + visibility = ["//visibility:public"], +) diff --git a/zig/tests/integration_tests/packages/MODULE.bazel b/zig/tests/integration_tests/packages/MODULE.bazel index 7dbb0c68..f8926db5 100644 --- a/zig/tests/integration_tests/packages/MODULE.bazel +++ b/zig/tests/integration_tests/packages/MODULE.bazel @@ -36,4 +36,28 @@ zig_packages.system_library( name = "optmath", lib = "//:optmath_impl", ) +zig_packages.system_library( + name = "dbgonly", + lib = "//:dbgonly_impl", +) +zig_packages.system_library( + name = "relonly", + lib = "//:relonly_impl", +) +zig_packages.config( + name = "dbg", + optimize = "debug", +) +zig_packages.config( + name = "rel", + optimize = "release_fast", +) +zig_packages.configure( + configs = [ + "dbg", + "rel", + ], + fallback = "dbg", + package = "cfgdep", +) use_repo(zig_packages, "zig_deps") diff --git a/zig/tests/integration_tests/packages/build.zig.zon b/zig/tests/integration_tests/packages/build.zig.zon index e7c900c7..434ded81 100644 --- a/zig/tests/integration_tests/packages/build.zig.zon +++ b/zig/tests/integration_tests/packages/build.zig.zon @@ -64,6 +64,10 @@ .url = "__CPPDEP_URL__", .hash = "__CPPDEP_HASH__", }, + .cfgdep = .{ + .url = "__CFGDEP_URL__", + .hash = "__CFGDEP_HASH__", + }, // A source-only sub-tree dependency; only enabled by the test. // .srconly = .{ .url = "__SRCONLY_URL__", .hash = "__SRCONLY_HASH__" }, }, diff --git a/zig/tests/integration_tests/packages/dbgonly.c b/zig/tests/integration_tests/packages/dbgonly.c new file mode 100644 index 00000000..4ae6362b --- /dev/null +++ b/zig/tests/integration_tests/packages/dbgonly.c @@ -0,0 +1,3 @@ +int dbg_compute(void) { + return 1; +} diff --git a/zig/tests/integration_tests/packages/fixtures/cfgdep/build.zig b/zig/tests/integration_tests/packages/fixtures/cfgdep/build.zig new file mode 100644 index 00000000..73059968 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cfgdep/build.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const optimize = b.standardOptimizeOption(.{}); + const cfgdep = b.addModule("cfgdep", .{ + .root_source_file = b.path("src/cfgdep.zig"), + .target = b.standardTargetOptions(.{}), + .optimize = optimize, + }); + if (optimize == .Debug) { + cfgdep.linkSystemLibrary("dbgonly", .{}); + } else { + cfgdep.linkSystemLibrary("relonly", .{}); + } +} diff --git a/zig/tests/integration_tests/packages/fixtures/cfgdep/build.zig.zon b/zig/tests/integration_tests/packages/fixtures/cfgdep/build.zig.zon new file mode 100644 index 00000000..94609520 --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cfgdep/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .cfgdep, + .version = "0.0.0", + .fingerprint = 0x88701c22be06c646, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/zig/tests/integration_tests/packages/fixtures/cfgdep/src/cfgdep.zig b/zig/tests/integration_tests/packages/fixtures/cfgdep/src/cfgdep.zig new file mode 100644 index 00000000..74459c4b --- /dev/null +++ b/zig/tests/integration_tests/packages/fixtures/cfgdep/src/cfgdep.zig @@ -0,0 +1,12 @@ +const builtin = @import("builtin"); + +extern fn dbg_compute() c_int; +extern fn rel_compute() c_int; + +pub fn value() c_int { + if (builtin.mode == .Debug) { + return dbg_compute(); + } else { + return rel_compute(); + } +} diff --git a/zig/tests/integration_tests/packages/main.zig b/zig/tests/integration_tests/packages/main.zig index 49203466..decb612b 100644 --- a/zig/tests/integration_tests/packages/main.zig +++ b/zig/tests/integration_tests/packages/main.zig @@ -15,6 +15,7 @@ const cdep = @import("cdep"); const syslibdep = @import("syslibdep"); const optdep = @import("optdep"); const cppdep = @import("cppdep"); +const cfgdep = @import("cfgdep"); pub fn main() void { _ = leaf.value; @@ -34,4 +35,5 @@ pub fn main() void { _ = syslibdep.compute(21); _ = optdep.compute(1); _ = cppdep.value(); + _ = cfgdep.value(); } diff --git a/zig/tests/integration_tests/packages/relonly.c b/zig/tests/integration_tests/packages/relonly.c new file mode 100644 index 00000000..0a3733bf --- /dev/null +++ b/zig/tests/integration_tests/packages/relonly.c @@ -0,0 +1,3 @@ +int rel_compute(void) { + return 2; +} diff --git a/zig/tests/integration_tests/packages_tests_runner.zig b/zig/tests/integration_tests/packages_tests_runner.zig index c25511eb..2a586251 100644 --- a/zig/tests/integration_tests/packages_tests_runner.zig +++ b/zig/tests/integration_tests/packages_tests_runner.zig @@ -38,6 +38,7 @@ const packages = [_]Package{ .{ .name = "syslibdep" }, .{ .name = "optdep" }, .{ .name = "cppdep" }, + .{ .name = "cfgdep" }, }; const Consumer = struct { @@ -47,7 +48,7 @@ const Consumer = struct { // Manifests that resolve dependencies via `zig_packages.from_file`. const consumers = [_]Consumer{ - .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep", "syslibdep", "optdep", "cppdep" } }, + .{ .manifest = "build.zig.zon", .deps = &.{ "leaf", "host", "top", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep", "syslibdep", "optdep", "cppdep", "cfgdep" } }, .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } }, }; @@ -97,6 +98,13 @@ test "Zig packages are imported from file:// tarballs" { }); defer result.deinit(); try std.testing.expect(result.success); + + // Test that the importer correctly selects system libraries per-configuration. + const opt_result = try ctx.exec_bazel(.{ + .argv = &[_][]const u8{ "build", "//:binary", "-c", "opt" }, + }); + defer opt_result.deinit(); + try std.testing.expect(opt_result.success); } // Runs after the positive test above, tarballs are packed and consumer