From aee25ea2ebb285b424f3602f02a06f7e1e0f5a6a Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 15 Jun 2026 19:41:30 +0200 Subject: [PATCH 01/65] feat: add import_names attribute for per target dependency name override --- .bazelrc | 4 +-- docs/rules.md | 19 ++++++++----- e2e/workspace/import-names-attr/BUILD.bazel | 31 +++++++++++++++++++++ e2e/workspace/import-names-attr/greet.zig | 1 + e2e/workspace/import-names-attr/lib.zig | 3 ++ e2e/workspace/import-names-attr/main.zig | 17 +++++++++++ zig/private/common/zig_build.bzl | 23 +++++++++++++++ zig/private/providers/zig_module_info.bzl | 14 +++++++--- zig/private/zig_library.bzl | 21 ++++++++++++++ 9 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 e2e/workspace/import-names-attr/BUILD.bazel create mode 100644 e2e/workspace/import-names-attr/greet.zig create mode 100644 e2e/workspace/import-names-attr/lib.zig create mode 100644 e2e/workspace/import-names-attr/main.zig diff --git a/.bazelrc b/.bazelrc index d244032c..117762c5 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/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/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,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 # Load any settings specific to the current user. # Place settings that should affect the integration tests into `.bazelrc.ic.user`. diff --git a/docs/rules.md b/docs/rules.md index e71e00bb..9d3bc7ee 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -14,7 +14,7 @@ the current target name or current repository name. load("@rules_zig//zig:defs.bzl", "zig_binary") zig_binary(name, deps, srcs, data, compiler_runtime, copts, csrcs, emit_asm, emit_llvm_bc, - emit_llvm_ir, env, extra_docs, extra_srcs, linker_script, linkopts, main, + emit_llvm_ir, env, extra_docs, extra_srcs, import_names, linker_script, linkopts, main, strip_debug_symbols, zigopts) @@ -61,6 +61,7 @@ zig_binary( | env | Additional environment variables to set when executed by `bazel run`. Subject to location expansion. NOTE: The environment variables are not set when you run the target outside of Bazel (for example, by manually executing the binary in bazel-bin/). | Dictionary: String -> String | optional | `{}` | | extra_docs | Other files required to generate documentation, e.g. guides referenced using `//!zig-autodoc-guide:`. | List of labels | optional | `[]` | | extra_srcs | Other files required to build the target, e.g. files embedded using `@embedFile`. | List of labels | optional | `[]` | +| import_names | Override the import name under which a dependency is imported.

Maps a dependency in `deps` to the name used to `@import` it from this target's sources. By default a Zig module dependency is imported under its own `import_name` or its target name. | Dictionary: Label -> String | optional | `{}` | | linker_script | Custom linker script for the target.

Note, as of Zig version 0.15.1 linker-scripts require the LLVM/LLD backend to be enabled, see https://github.com/ziglang/zig/issues/25069. Set `zigopts=["-fllvm", "-flld"]` to that end. | Label | optional | `None` | | linkopts | Additional list of flags passed to the linker. Subject to location expansion. | List of strings | optional | `[]` | | main | The main source file.

If not set, deps must contain exactly one Zig module dependency which will be used as the root module. Note that in that case, 'srcs', 'extra_srcs' and 'csrcs' must also be empty as they are taken from the root module instead. | Label | optional | `None` | @@ -348,7 +349,7 @@ zig_configure_test(
 load("@rules_zig//zig:defs.bzl", "zig_library")
 
-zig_library(name, deps, srcs, data, extra_srcs, import_name, main, zigopts)
+zig_library(name, deps, srcs, data, extra_srcs, import_name, import_names, main, zigopts)
 
Defines a Zig library. @@ -388,6 +389,7 @@ zig_library( | data | Files required by the module during runtime. | List of labels | optional | `[]` | | extra_srcs | Other files required when building the module, e.g. files embedded using `@embedFile`. | List of labels | optional | `[]` | | import_name | The import name of the module. | String | optional | `""` | +| import_names | Override the import name under which a dependency is imported.

Maps a dependency in `deps` to the name used to `@import` it from this target's sources. By default a Zig module dependency is imported under its own `import_name` or its target name. | Dictionary: Label -> String | optional | `{}` | | main | The main source file. | Label | required | | | zigopts | Additional list of flags passed to the zig compiler for this module.

This is an advanced feature that can conflict with attributes, build settings, and other flags defined by the toolchain itself. Use this at your own risk of hitting undefined behaviors. | List of strings | optional | `[]` | @@ -400,7 +402,7 @@ zig_library( load("@rules_zig//zig:defs.bzl", "zig_shared_library") zig_shared_library(name, deps, srcs, data, compiler_runtime, copts, csrcs, emit_asm, emit_llvm_bc, - emit_llvm_ir, extra_docs, extra_srcs, linker_script, linkopts, main, + emit_llvm_ir, extra_docs, extra_srcs, import_names, linker_script, linkopts, main, shared_lib_name, strip_debug_symbols, zigopts) @@ -447,6 +449,7 @@ zig_shared_library( | emit_llvm_ir | Emit the LLVM IR in the `llvm_ir` output group. | Boolean | optional | `False` | | extra_docs | Other files required to generate documentation, e.g. guides referenced using `//!zig-autodoc-guide:`. | List of labels | optional | `[]` | | extra_srcs | Other files required to build the target, e.g. files embedded using `@embedFile`. | List of labels | optional | `[]` | +| import_names | Override the import name under which a dependency is imported.

Maps a dependency in `deps` to the name used to `@import` it from this target's sources. By default a Zig module dependency is imported under its own `import_name` or its target name. | Dictionary: Label -> String | optional | `{}` | | linker_script | Custom linker script for the target.

Note, as of Zig version 0.15.1 linker-scripts require the LLVM/LLD backend to be enabled, see https://github.com/ziglang/zig/issues/25069. Set `zigopts=["-fllvm", "-flld"]` to that end. | Label | optional | `None` | | linkopts | Additional list of flags passed to the linker. Subject to location expansion. | List of strings | optional | `[]` | | main | The main source file.

If not set, deps must contain exactly one Zig module dependency which will be used as the root module. Note that in that case, 'srcs', 'extra_srcs' and 'csrcs' must also be empty as they are taken from the root module instead. | Label | optional | `None` | @@ -463,8 +466,8 @@ zig_shared_library( load("@rules_zig//zig:defs.bzl", "zig_static_library") zig_static_library(name, deps, srcs, data, compiler_runtime, copts, csrcs, emit_asm, emit_bin, - emit_llvm_bc, emit_llvm_ir, extra_docs, extra_srcs, linker_script, linkopts, main, - strip_debug_symbols, zigopts) + emit_llvm_bc, emit_llvm_ir, extra_docs, extra_srcs, import_names, linker_script, + linkopts, main, strip_debug_symbols, zigopts) Builds a Zig library, produces a static archive. @@ -511,6 +514,7 @@ zig_static_library( | emit_llvm_ir | Emit the LLVM IR in the `llvm_ir` output group. | Boolean | optional | `False` | | extra_docs | Other files required to generate documentation, e.g. guides referenced using `//!zig-autodoc-guide:`. | List of labels | optional | `[]` | | extra_srcs | Other files required to build the target, e.g. files embedded using `@embedFile`. | List of labels | optional | `[]` | +| import_names | Override the import name under which a dependency is imported.

Maps a dependency in `deps` to the name used to `@import` it from this target's sources. By default a Zig module dependency is imported under its own `import_name` or its target name. | Dictionary: Label -> String | optional | `{}` | | linker_script | Custom linker script for the target.

Note, as of Zig version 0.15.1 linker-scripts require the LLVM/LLD backend to be enabled, see https://github.com/ziglang/zig/issues/25069. Set `zigopts=["-fllvm", "-flld"]` to that end. | Label | optional | `None` | | linkopts | Additional list of flags passed to the linker. Subject to location expansion. | List of strings | optional | `[]` | | main | The main source file.

If not set, deps must contain exactly one Zig module dependency which will be used as the root module. Note that in that case, 'srcs', 'extra_srcs' and 'csrcs' must also be empty as they are taken from the root module instead. | Label | optional | `None` | @@ -526,8 +530,8 @@ zig_static_library( load("@rules_zig//zig:defs.bzl", "zig_test") zig_test(name, deps, srcs, data, compiler_runtime, copts, csrcs, emit_asm, emit_llvm_bc, - emit_llvm_ir, env, env_inherit, extra_docs, extra_srcs, linker_script, linkopts, main, - strip_debug_symbols, test_runner, zigopts) + emit_llvm_ir, env, env_inherit, extra_docs, extra_srcs, import_names, linker_script, + linkopts, main, strip_debug_symbols, test_runner, zigopts) Builds a Zig test. @@ -573,6 +577,7 @@ zig_test( | env_inherit | Environment variables to inherit from external environment when executed by `bazel test`. | List of strings | optional | `[]` | | extra_docs | Other files required to generate documentation, e.g. guides referenced using `//!zig-autodoc-guide:`. | List of labels | optional | `[]` | | extra_srcs | Other files required to build the target, e.g. files embedded using `@embedFile`. | List of labels | optional | `[]` | +| import_names | Override the import name under which a dependency is imported.

Maps a dependency in `deps` to the name used to `@import` it from this target's sources. By default a Zig module dependency is imported under its own `import_name` or its target name. | Dictionary: Label -> String | optional | `{}` | | linker_script | Custom linker script for the target.

Note, as of Zig version 0.15.1 linker-scripts require the LLVM/LLD backend to be enabled, see https://github.com/ziglang/zig/issues/25069. Set `zigopts=["-fllvm", "-flld"]` to that end. | Label | optional | `None` | | linkopts | Additional list of flags passed to the linker. Subject to location expansion. | List of strings | optional | `[]` | | main | The main source file.

If not set, deps must contain exactly one Zig module dependency which will be used as the root module. Note that in that case, 'srcs', 'extra_srcs' and 'csrcs' must also be empty as they are taken from the root module instead. | Label | optional | `None` | diff --git a/e2e/workspace/import-names-attr/BUILD.bazel b/e2e/workspace/import-names-attr/BUILD.bazel new file mode 100644 index 00000000..346b2617 --- /dev/null +++ b/e2e/workspace/import-names-attr/BUILD.bazel @@ -0,0 +1,31 @@ +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("@rules_zig//zig:defs.bzl", "zig_binary", "zig_library") + +zig_library( + name = "greet", + import_name = "greeting", + main = "greet.zig", +) + +zig_library( + name = "lib", + import_names = {":greet": "salutation"}, + main = "lib.zig", + deps = [":greet"], +) + +zig_binary( + name = "binary", + import_names = {":greet": "hello"}, + main = "main.zig", + deps = [ + ":greet", + ":lib", + ], +) + +build_test( + name = "output_test", + size = "small", + targets = [":binary"], +) diff --git a/e2e/workspace/import-names-attr/greet.zig b/e2e/workspace/import-names-attr/greet.zig new file mode 100644 index 00000000..19b96ada --- /dev/null +++ b/e2e/workspace/import-names-attr/greet.zig @@ -0,0 +1 @@ +pub const msg = "Hello World!\n"; diff --git a/e2e/workspace/import-names-attr/lib.zig b/e2e/workspace/import-names-attr/lib.zig new file mode 100644 index 00000000..46808c42 --- /dev/null +++ b/e2e/workspace/import-names-attr/lib.zig @@ -0,0 +1,3 @@ +const salutation = @import("salutation"); + +pub const msg = salutation.msg; diff --git a/e2e/workspace/import-names-attr/main.zig b/e2e/workspace/import-names-attr/main.zig new file mode 100644 index 00000000..aba80802 --- /dev/null +++ b/e2e/workspace/import-names-attr/main.zig @@ -0,0 +1,17 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const lib = @import("lib"); + +const greet = @import("hello"); + +const is_zig_0_16_or_later = builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; + +pub const main = if (is_zig_0_16_or_later) main_016 else main_pre_016; + +fn main_pre_016() void { + std.fs.File.stdout().writeAll(lib.msg ++ greet.msg) catch unreachable; +} + +fn main_016(init: std.process.Init) void { + std.Io.File.writeStreamingAll(.stdout(), init.io, lib.msg ++ greet.msg) catch unreachable; +} diff --git a/zig/private/common/zig_build.bzl b/zig/private/common/zig_build.bzl index bdf285bb..b322d5bc 100644 --- a/zig/private/common/zig_build.bzl +++ b/zig/private/common/zig_build.bzl @@ -94,6 +94,17 @@ Note that in that case, 'srcs', 'extra_srcs' and 'csrcs' must also be empty as t mandatory = False, providers = [[ZigModuleInfo], [CcInfo]], ), + "import_names": attr.label_keyed_string_dict( + doc = """\ +Override the import name under which a dependency is imported. + +Maps a dependency in `deps` to the name used to `@import` it from this target's +sources. By default a Zig module dependency is imported under its own +`import_name` or its target name. +""", + mandatory = False, + providers = [[ZigModuleInfo]], + ), "compiler_runtime": attr.string( doc = """\ Whether to include Zig compiler runtime symbols in the generated output. @@ -391,13 +402,24 @@ def zig_build_impl(ctx, *, kind): zdeps = [] cdeps = [] + dep_canonical_names = {} for dep in ctx.attr.deps: if ZigModuleInfo in dep: zdeps.append(dep[ZigModuleInfo]) + dep_canonical_names[dep[ZigModuleInfo].canonical_name] = None elif CcInfo in dep: cdeps.append(dep[CcInfo]) + import_names = {} + for dep, import_name in ctx.attr.import_names.items(): + canonical_name = dep[ZigModuleInfo].canonical_name + if canonical_name not in dep_canonical_names: + fail("import_names: '{}' must also be listed in deps.".format(dep.label)) + import_names[canonical_name] = import_name + if root_module_is_only_dep: + if ctx.attr.import_names: + fail("import_names has no effect when a single dependency is used as the root module; set `main` to define a module.") root_module = ctx.attr.deps[0][ZigModuleInfo] else: root_module = zig_module_info( @@ -409,6 +431,7 @@ def zig_build_impl(ctx, *, kind): deps = zdeps + [bazel_builtin_module(ctx)], cdeps = cdeps, zigopts = zigopts, + import_names = import_names, ) zig_settings( diff --git a/zig/private/providers/zig_module_info.bzl b/zig/private/providers/zig_module_info.bzl index b025c8ad..416d064c 100644 --- a/zig/private/providers/zig_module_info.bzl +++ b/zig/private/providers/zig_module_info.bzl @@ -27,8 +27,11 @@ ZigModuleInfo = provider( doc = DOC, ) -def _zig_module_context(name, canonical_name, main, deps, cdeps, zigopts): - mappings = [struct(name = dep.name, canonical_name = dep.canonical_name) for dep in deps] +def _zig_module_context(name, canonical_name, main, deps, cdeps, zigopts, import_names): + mappings = [ + struct(name = import_names.get(dep.canonical_name, dep.name), canonical_name = dep.canonical_name) + for dep in deps + ] if any([need_translate_c(dep) for dep in cdeps]): # Global C module has a predefined name and canonical name since it is not defined yet here. mappings.append(struct(name = "c", canonical_name = "c")) @@ -40,7 +43,7 @@ def _zig_module_context(name, canonical_name, main, deps, cdeps, zigopts): dependency_mappings = tuple(mappings), ) -def zig_module_info(*, name, canonical_name, main, srcs = [], extra_srcs = [], deps = [], cdeps = [], zigopts = []): +def zig_module_info(*, name, canonical_name, main, srcs = [], extra_srcs = [], deps = [], cdeps = [], zigopts = [], import_names = {}): """Create `ZigModuleInfo` for a new Zig module. Args: @@ -52,6 +55,9 @@ def zig_module_info(*, name, canonical_name, main, srcs = [], extra_srcs = [], d deps: list of ZigModuleInfo, Import dependencies of this module. cdeps: list of CcInfo, C dependencies of this module. zigopts: list of string, Additional list of flags passed to the zig compiler. + import_names: dict of string to string, Override the import name of a + dependency, keyed by the dependency's canonical name. A dependency not + listed is imported under its own `name`. Returns: `ZigModuleInfo` @@ -59,7 +65,7 @@ def zig_module_info(*, name, canonical_name, main, srcs = [], extra_srcs = [], d cc_infos = cdeps + [dep.cc_info for dep in deps if dep.cc_info] cc_info = cc_common.merge_cc_infos(direct_cc_infos = cc_infos) - module_context = _zig_module_context(name, canonical_name, main, deps, cdeps, zigopts) + module_context = _zig_module_context(name, canonical_name, main, deps, cdeps, zigopts, import_names) module = ZigModuleInfo( name = name, diff --git a/zig/private/zig_library.bzl b/zig/private/zig_library.bzl index 669be48b..f5a98725 100644 --- a/zig/private/zig_library.bzl +++ b/zig/private/zig_library.bzl @@ -68,6 +68,17 @@ ATTRS = { mandatory = False, providers = [[ZigModuleInfo], [CcInfo]], ), + "import_names": attr.label_keyed_string_dict( + doc = """\ +Override the import name under which a dependency is imported. + +Maps a dependency in `deps` to the name used to `@import` it from this target's +sources. By default a Zig module dependency is imported under its own +`import_name` or its target name. +""", + mandatory = False, + providers = [[ZigModuleInfo]], + ), "data": attr.label_list( allow_files = True, doc = "Files required by the module during runtime.", @@ -105,12 +116,21 @@ def _zig_library_impl(ctx): zdeps = [] cdeps = [] + dep_canonical_names = {} for dep in ctx.attr.deps: if ZigModuleInfo in dep: zdeps.append(dep[ZigModuleInfo]) + dep_canonical_names[dep[ZigModuleInfo].canonical_name] = None elif CcInfo in dep: cdeps.append(dep[CcInfo]) + import_names = {} + for dep, override in ctx.attr.import_names.items(): + canonical_name = dep[ZigModuleInfo].canonical_name + if canonical_name not in dep_canonical_names: + fail("import_names: '{}' must also be listed in deps.".format(dep.label)) + import_names[canonical_name] = override + import_name = ctx.attr.import_name or ctx.label.name module = zig_module_info( name = import_name, @@ -124,6 +144,7 @@ def _zig_library_impl(ctx): deps = zdeps + [bazel_builtin_module(ctx)], cdeps = cdeps, zigopts = ctx.attr.zigopts, + import_names = import_names, ) return [default, module] From d4e121fafe6b212a8c12fcbbaf533260825ef8b3 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Wed, 10 Jun 2026 17:56:41 +0200 Subject: [PATCH 02/65] 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 03/65] 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 04/65] 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 05/65] 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 06/65] 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 07/65] 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 08/65] 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 09/65] 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 10/65] 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 11/65] 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 12/65] 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 13/65] 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 14/65] 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 15/65] 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 16/65] 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 17/65] 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 18/65] 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 19/65] 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 20/65] 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 21/65] 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 22/65] 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 23/65] 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 24/65] 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 25/65] 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 26/65] 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 27/65] 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 28/65] 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 29/65] 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 30/65] 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 31/65] 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 32/65] 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 33/65] 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 34/65] 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 35/65] 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 36/65] 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 37/65] 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 38/65] 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 39/65] 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 40/65] 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 41/65] 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 42/65] 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 43/65] 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 44/65] 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 45/65] 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 46/65] 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 47/65] 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 48/65] 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 49/65] 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 50/65] 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 51/65] 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 52/65] 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 53/65] 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 54/65] 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 55/65] 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 56/65] 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 57/65] 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 58/65] 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 59/65] 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 60/65] 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 61/65] 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 62/65] 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 63/65] 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 64/65] 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 65/65] 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" } }, };