diff --git a/.bazelrc b/.bazelrc
index d244032c..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/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,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/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/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/.bazelrc b/e2e/workspace/.bazelrc
index 02f4efd0..541ebaf9 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
+
+# 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/e2e/workspace/MODULE.bazel b/e2e/workspace/MODULE.bazel
index 3573cbc3..f610eb0b 100644
--- a/e2e/workspace/MODULE.bazel
+++ b/e2e/workspace/MODULE.bazel
@@ -15,10 +15,24 @@ 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(
+ "@rules_zig//zig:packages.bzl",
+ "zig_packages",
+ 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")
+use_repo(zig_packages, "zig_deps")
+
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
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"
+ }
+ }
+}
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/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.bazel b/e2e/workspace/zig-package-import/app/BUILD.bazel
new file mode 100644
index 00000000..2c154212
--- /dev/null
+++ b/e2e/workspace/zig-package-import/app/BUILD.bazel
@@ -0,0 +1,39 @@
+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")
+
+zig_binary(
+ name = "app",
+ srcs = glob(
+ ["src/**/*.zig"],
+ 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(),
+)
+
+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 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..ad5c5db5
--- /dev/null
+++ b/e2e/workspace/zig-package-import/app/build.zig.zon
@@ -0,0 +1,29 @@
+.{
+ .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",
+ },
+ .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",
+ },
+ .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",
+ "build.zig.zon",
+ "src",
+ },
+}
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..c8fc619c
--- /dev/null
+++ b/e2e/workspace/zig-package-import/app/output.expected
@@ -0,0 +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
new file mode 100644
index 00000000..b200a70e
--- /dev/null
+++ b/e2e/workspace/zig-package-import/app/src/main.zig
@@ -0,0 +1,44 @@
+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{};
+ 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);
+
+ // CBOR-encode the greeting using the git+https `cbor` dependency.
+ 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();
+}
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..80ffc29f
--- /dev/null
+++ b/e2e/workspace/zig-package-import/greet/BUILD.bazel
@@ -0,0 +1,21 @@
+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(
+ ["src/**/*.zig"],
+ exclude = ["src/greet.zig"],
+ ),
+ 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")],
+)
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});
+}
diff --git a/zig/BUILD.bazel b/zig/BUILD.bazel
index 6e77cd79..fa2695db 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__"],
@@ -75,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",
@@ -82,6 +90,7 @@ filegroup(
":BUILD.bazel",
":defs.bzl",
":extensions.bzl",
+ ":packages.bzl",
":toolchain.bzl",
"//zig/config:all_files",
"//zig/lib:all_files",
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/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..251c209d 100644
--- a/zig/private/bzlmod/BUILD.bazel
+++ b/zig/private/bzlmod/BUILD.bazel
@@ -11,6 +11,17 @@ bzl_library(
],
)
+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",
+ ],
+)
+
bzl_library(
name = "cc_common_link",
srcs = ["cc_common_link.bzl"],
@@ -24,6 +35,7 @@ filegroup(
":BUILD.bazel",
":cc_common_link.bzl",
":zig.bzl",
+ ":zig_packages.bzl",
],
visibility = ["//zig/private:__pkg__"],
)
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/bzlmod/zig_packages.bzl b/zig/private/bzlmod/zig_packages.bzl
new file mode 100644
index 00000000..452c0f10
--- /dev/null
+++ b/zig/private/bzlmod/zig_packages.bzl
@@ -0,0 +1,228 @@
+"""Implementation of the `zig_packages` module extension."""
+
+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")
+
+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,
+ ),
+ },
+)
+
+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,
+ ),
+ },
+)
+
+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)] +
+ [str(manifest) for manifest in manifests],
+ )
+ if result.return_code != 0:
+ 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 _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": _edges(graph, key),
+ "packages": packages,
+ }
+
+def _zig_packages_impl(module_ctx):
+ zig = zig_path(module_ctx)
+ zon2json = module_ctx.path(Label("//zig/private:zon2json.zig"))
+ cache = zig_cache(module_ctx)
+ pkg_dir = module_ctx.path("pkg")
+
+ 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)
+
+ module_ctx.watch(manifest)
+ 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
+
+ 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
+
+ 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)
+
+ # `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():
+ 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, 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)
+ zig_deps_hub(
+ name = "zig_deps",
+ manifests = json.encode(manifests),
+ packages = targets,
+ )
+
+ 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_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 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 = []
+ targets = {}
+ for root, label in zip(graph["roots"], root_tags):
+ deps = {}
+ for name, key in root["deps"].items():
+ 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": target, "url": url}
+ manifests.append({
+ "repo": label.repo_name,
+ "package": label.package,
+ "scope": str(label.same_package_label("__subpackages__")),
+ "deps": deps,
+ })
+ return manifests, targets
+
+zig_packages = module_extension(
+ implementation = _zig_packages_impl,
+ tag_classes = {
+ "from_file": from_file,
+ "system_library": system_library,
+ "system_integration": system_integration,
+ },
+)
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/configurer.zig b/zig/private/configurer.zig
new file mode 100644
index 00000000..4791d5fa
--- /dev/null
+++ b/zig/private/configurer.zig
@@ -0,0 +1,362 @@
+//! Configure a Zig package's `build.zig` and emit its public module graph as
+//! JSON, for translation into Bazel `zig_library` targets.
+//!
+//! Usage: configurer --zig --build-root [--system-integration NAME ...]
+//!
+//! 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
+//! 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": ..., "package": , "root_source": ...,
+//! "generated_source": ..., "link_libc": true, "link_libcpp": true,
+//! "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
+//! owns it, or the empty string for the root package being configured. An
+//! 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.
+//!
+//! 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.
+//!
+//! 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 `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. 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;
+const Build = std.Build;
+const LazyPath = Build.LazyPath;
+const Allocator = std.mem.Allocator;
+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 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]});
+ }
+ }
+
+ 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);
+
+ 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, .{}),
+ .path = build_root_path,
+ },
+ };
+
+ const builder = try Build.create(&graph, build_root, dependencies.root_deps);
+
+ try builder.runBuild(root);
+
+ try emit(arena, io, builder);
+}
+
+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. 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 std.fmt.allocPrint(arena, "__anon_{d}", .{modules.getIndex(module).?});
+}
+
+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;
+
+ var json: std.json.Stringify = .{ .writer = writer };
+ try json.beginObject();
+ try json.objectField("modules");
+ try json.beginArray();
+ for (modules.keys()) |module| {
+ try json.beginObject();
+ try json.objectField("name");
+ try json.write(try moduleName(arena, &modules, 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));
+ if (generatedOptionsSource(builder, module.root_source_file)) |source| {
+ 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 emitC(arena, &json, module);
+ 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("module");
+ try json.write(try moduleName(arena, &modules, imported));
+ 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,
+ };
+}
+
+/// 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;
+}
+
+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 system_libs: std.ArrayList([]const u8) = .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 => |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"),
+ .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 (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();
+ 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]});
+ return args[i.*];
+}
+
+fn fatal(comptime format: []const u8, args: anytype) noreturn {
+ std.debug.print("configurer: " ++ format ++ "\n", args);
+ std.process.exit(1);
+}
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/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/repo/BUILD.bazel b/zig/private/repo/BUILD.bazel
index 59521193..bfe75ecf 100644
--- a/zig/private/repo/BUILD.bazel
+++ b/zig/private/repo/BUILD.bazel
@@ -20,12 +20,36 @@ bzl_library(
],
)
+bzl_library(
+ 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"],
+)
+
+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",
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/toolchains_repo.bzl b/zig/private/repo/toolchains_repo.bzl
index c404950e..fc0d340e 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,32 @@ 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", """\
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "toolchains",
+ srcs = ["toolchains.bzl"],
+ visibility = ["//visibility:public"],
+)
+""")
+ toolchains_content += """\
+]
+"""
+ repository_ctx.file("private/toolchains.bzl", toolchains_content)
+
toolchains_repo = repository_rule(
_toolchains_repo_impl,
doc = DOC,
diff --git a/zig/private/repo/zig_deps_hub.bzl b/zig/private/repo/zig_deps_hub.bzl
new file mode 100644
index 00000000..94fd7fe3
--- /dev/null
+++ b/zig/private/repo/zig_deps_hub.bzl
@@ -0,0 +1,126 @@
+"""Implementation of the `zig_deps` hub repository rule."""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+
+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 `{repo, 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}"],
+)
+"""
+
+# 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."""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+
+_MANIFESTS = json.decode("""%MANIFESTS%""")
+
+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, deps = _manifest()
+ return [_label(path, name) for name in deps]
+
+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)
+ parts = [part for part in [repo, manifest] if part]
+ path = paths.join(*parts) if parts else ""
+ return path, manifests[manifest]
+
+def _label(path, name):
+ 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):
+ # `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
+ manifests = json.decode(repository_ctx.attr.manifests)
+
+ builds = {}
+ registry = {}
+ for manifest in manifests:
+ 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"]] = deps
+
+ if "" not in builds:
+ builds[""] = ""
+
+ 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,
+ attrs = ATTRS,
+ doc = DOC,
+)
diff --git a/zig/private/repo/zig_host_toolchain.bzl b/zig/private/repo/zig_host_toolchain.bzl
new file mode 100644
index 00000000..82969315
--- /dev/null
+++ b/zig/private/repo/zig_host_toolchain.bzl
@@ -0,0 +1,98 @@
+"""Implementation of the `zig_host_toolchain` repository rule."""
+
+load("@zig_toolchains//private:toolchains.bzl", "ZIG_TOOLCHAINS")
+
+_VERSION_ENV = "RULES_ZIG_HOST_SDK"
+
+DOC = """\
+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
+ 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 _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))
+
+ requested = repository_ctx.os.environ.get(_VERSION_ENV)
+ if not requested:
+ return candidates[0].zig
+
+ 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", """\
+# 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
+
+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)
+
+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,
+ doc = DOC,
+ environ = [_VERSION_ENV],
+)
diff --git a/zig/private/repo/zig_package.bzl b/zig/private/repo/zig_package.bzl
new file mode 100644
index 00000000..2b06bda8
--- /dev/null
+++ b/zig/private/repo/zig_package.bzl
@@ -0,0 +1,397 @@
+"""Implementation of the `zig_package` repository rule."""
+
+load("@rules_zig_host_toolchain//:toolchain.bzl", "zig_cache", "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`. 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`.",
+ ),
+ "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):
+ # The archive nests the package under `//`; strip up to
+ # the directory that holds `build.zig.zon`.
+ 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()
+
+_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 _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
+
+ lines = ["pub const packages = struct {"]
+ for key in sorted(packages):
+ package = packages[key]
+ lines.append(" pub const @\"{}\" = struct {{".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(" };")
+ 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"
+
+_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},
+ import_names = {import_names},
+ visibility = ["//visibility:public"],
+)
+"""
+
+_ZIG_LIBRARY_SUBTREE = """\
+zig_library(
+ name = "{name}",
+ main = "{main}",
+ import_name = "{import_name}",
+ srcs = glob(["{subpath}/**/*.zig"], exclude = ["{main}"]),
+ deps = {deps},
+ import_names = {import_names},
+ visibility = ["//visibility:public"],
+)
+"""
+
+_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
+
+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. Targets are keyed by the
+ # module's own name, independent of the (possibly aliased) name it is imported
+ # under.
+ if not owner:
+ 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):
+ # An in-tree (sub-tree) module is generated as a sibling in this spoke.
+ return ":" + _target_name(packages, key, imported["module"])
+ if key:
+ # 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["module"]))
+ return ":" + imported["module"]
+
+def _build_file(repository_ctx, modules, packages):
+ library_chunks = []
+ cc_chunks = []
+ for module in modules:
+ if not module["root_source"]:
+ continue
+
+ # 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"]
+ 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.
+ 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 module.get("link_libc"):
+ deps.append("@rules_zig//zig/lib:libc")
+ 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"):
+ 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:
+ library_chunks.append(_ZIG_LIBRARY.format(
+ name = module["name"],
+ main = module["root_source"],
+ deps = json.encode(deps),
+ import_names = json.encode(import_names),
+ ))
+ else:
+ subpath = packages[owner]["path"]
+ library_chunks.append(_ZIG_LIBRARY_SUBTREE.format(
+ name = _target_name(packages, owner, module["name"]),
+ import_name = module["name"],
+ main = subpath + "/" + module["root_source"],
+ subpath = subpath,
+ deps = json.encode(deps),
+ import_names = json.encode(import_names),
+ ))
+
+ 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."""
+ 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"])
+
+ args = [zig, "build-exe", "--dep", "pkg", "--dep", "deps", "-Mroot=" + str(configurer), "-Mpkg=" + str(build_zig)]
+ for key in keys:
+ args.extend(["--dep", key])
+ args.append("-Mdeps=" + str(repository_ctx.path("_configure/deps.zig")))
+ for key in keys:
+ args.append("-M{}={}".format(key, _build_zig(repository_ctx, key, deps["packages"][key])))
+ args.extend([
+ "--cache-dir",
+ cache,
+ "--global-cache-dir",
+ 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))
+
+ 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))
+
+ 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"))
+ cache = zig_cache(repository_ctx)
+
+ 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))
+
+ 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 = repository_ctx.path(cache).get_child("p").get_child(fetched_hash + ".tar.gz")
+ repository_ctx.extract(archive, strip_prefix = _package_prefix(repository_ctx, zig, helper, cache, archive))
+
+ build_zig = repository_ctx.path("build.zig")
+ modules = []
+ if build_zig.exists:
+ manifest = _configure(repository_ctx, zig, build_zig, cache)
+ repository_ctx.file("module_manifest.json", manifest)
+ modules = json.decode(manifest)["modules"]
+
+ 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(
+ _zig_package_impl,
+ attrs = ATTRS,
+ doc = DOC,
+)
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]
diff --git a/zig/private/zon2json.zig b/zig/private/zon2json.zig
new file mode 100644
index 00000000..28ac79f1
--- /dev/null
+++ b/zig/private/zon2json.zig
@@ -0,0 +1,329 @@
+//! Resolve a Zig package dependency graph by recursively parsing `build.zig.zon`
+//! manifests, and emit the merged graph as JSON on stdout.
+//!
+//! Usage: zon2json ...
+//!
+//! 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
+//! always precedes any package that lists it as a dependency. 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 flate = std.compress.flate;
+const tar = std.tar;
+
+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,
+ 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 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,
+ .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});
+ }
+
+ /// 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 }),
+ .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"));
+
+ 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| {
+ 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.visited.getOrPut(walker.arena, resolved.key);
+ if (gop.found_existing) return;
+
+ 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);
+
+ // 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,
+ });
+ }
+};
+
+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 < 4) fatal("usage: zon2json ...", .{});
+
+ 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[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));
+ }
+
+ 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("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 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);
+ }
+ },
+ 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);
+}
diff --git a/zig/tests/integration_tests/BUILD.bazel b/zig/tests/integration_tests/BUILD.bazel
index 5609d5c4..df3f5489 100644
--- a/zig/tests/integration_tests/BUILD.bazel
+++ b/zig/tests/integration_tests/BUILD.bazel
@@ -149,6 +149,32 @@ 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,
+ # `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",
+)
+
test_suite(
name = "integration_tests",
tags = integration_test_utils.DEFAULT_INTEGRATION_TEST_TAGS,
@@ -162,6 +188,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..0b0245d6 100644
--- a/zig/tests/integration_tests/integration_testing.zig
+++ b/zig/tests/integration_tests/integration_testing.zig
@@ -200,6 +200,66 @@ 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();
+ }
+
+ /// 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.
+ 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..cc1c9d2d
--- /dev/null
+++ b/zig/tests/integration_tests/packages/BUILD.bazel
@@ -0,0 +1,30 @@
+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")
+
+# `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",
+ zig_dep(
+ "multi",
+ module = "widget",
+ ),
+ ],
+)
+
+cc_library(
+ name = "mymath_impl",
+ 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
new file mode 100644
index 00000000..7dbb0c68
--- /dev/null
+++ b/zig/tests/integration_tests/packages/MODULE.bazel
@@ -0,0 +1,39 @@
+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(
+ 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")
+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",
+)
+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/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..e7c900c7
--- /dev/null
+++ b/zig/tests/integration_tests/packages/build.zig.zon
@@ -0,0 +1,74 @@
+.{
+ .name = .packages,
+ .version = "0.0.0",
+ .fingerprint = 0x9bb5c0a73538e72b,
+ .dependencies = .{
+ .leaf = .{
+ .url = "__LEAF_URL__",
+ .hash = "__LEAF_HASH__",
+ },
+ .host = .{
+ .url = "__HOST_URL__",
+ .hash = "__HOST_HASH__",
+ },
+ .top = .{
+ .url = "__TOP_URL__",
+ .hash = "__TOP_HASH__",
+ },
+ .multi = .{
+ .url = "__MULTI_URL__",
+ .hash = "__MULTI_HASH__",
+ },
+ .greeter = .{
+ .path = "path_deps/greeter",
+ },
+ .pruned = .{
+ .url = "__PRUNED_URL__",
+ .hash = "__PRUNED_HASH__",
+ },
+ .lib = .{
+ .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__",
+ },
+ .symlinked = .{
+ .url = "__SYMLINKED_URL__",
+ .hash = "__SYMLINKED_HASH__",
+ },
+ .genopts = .{
+ .url = "__GENOPTS_URL__",
+ .hash = "__GENOPTS_HASH__",
+ },
+ .usec = .{
+ .url = "__USEC_URL__",
+ .hash = "__USEC_HASH__",
+ },
+ .cdep = .{
+ .url = "__CDEP_URL__",
+ .hash = "__CDEP_HASH__",
+ },
+ .syslibdep = .{
+ .url = "__SYSLIBDEP_URL__",
+ .hash = "__SYSLIBDEP_HASH__",
+ },
+ .optdep = .{
+ .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__" },
+ },
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ },
+}
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..51f51de0
--- /dev/null
+++ b/zig/tests/integration_tests/packages/child/build.zig.zon
@@ -0,0 +1,21 @@
+.{
+ .name = .child,
+ .version = "0.0.0",
+ .fingerprint = 0x2b9d6e1f7a4c8053,
+ .dependencies = .{
+ .leaf = .{
+ .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",
+ "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..2494dbd2
--- /dev/null
+++ b/zig/tests/integration_tests/packages/child/src/child.zig
@@ -0,0 +1,4 @@
+const leaf = @import("leaf");
+const lib = @import("lib");
+
+pub const value: u32 = leaf.value + lib.v2 + 100;
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/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/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/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/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/fixtures/host/build.zig b/zig/tests/integration_tests/packages/fixtures/host/build.zig
new file mode 100644
index 00000000..9cd47023
--- /dev/null
+++ b/zig/tests/integration_tests/packages/fixtures/host/build.zig
@@ -0,0 +1,9 @@
+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("barlib", 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
new file mode 100644
index 00000000..be846b05
--- /dev/null
+++ b/zig/tests/integration_tests/packages/fixtures/host/build.zig.zon
@@ -0,0 +1,15 @@
+.{
+ .name = .host,
+ .version = "0.0.0",
+ .fingerprint = 0xcf2713fdd0ee5aad,
+ .dependencies = .{
+ .foo = .{ .path = "libs/foo" },
+ .bar = .{ .path = "libs/bar" },
+ },
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ "libs",
+ },
+}
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/libs/foo/build.zig b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig
new file mode 100644
index 00000000..94503b0d
--- /dev/null
+++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig
@@ -0,0 +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("leaflib", 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
new file mode 100644
index 00000000..262317dc
--- /dev/null
+++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .foo,
+ .version = "0.0.0",
+ .fingerprint = 0x8c736521d1c2c9ec,
+ .dependencies = .{
+ .bar = .{ .path = "libs/bar" },
+ .leaf = .{
+ .url = "__LEAF_URL__",
+ .hash = "__LEAF_HASH__",
+ },
+ },
+ .paths = .{
+ "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
new file mode 100644
index 00000000..c897c965
--- /dev/null
+++ b/zig/tests/integration_tests/packages/fixtures/host/libs/foo/src/foo.zig
@@ -0,0 +1,4 @@
+const bar = @import("bar");
+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
new file mode 100644
index 00000000..77d6862b
--- /dev/null
+++ b/zig/tests/integration_tests/packages/fixtures/host/src/root.zig
@@ -0,0 +1,4 @@
+const foo = @import("foo");
+const barlib = @import("barlib");
+
+pub const value = foo.value + barlib.value;
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/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/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/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/fixtures/multi/build.zig b/zig/tests/integration_tests/packages/fixtures/multi/build.zig
new file mode 100644
index 00000000..40cc4c8e
--- /dev/null
+++ b/zig/tests/integration_tests/packages/fixtures/multi/build.zig
@@ -0,0 +1,11 @@
+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/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/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
new file mode 100644
index 00000000..24e109b2
--- /dev/null
+++ b/zig/tests/integration_tests/packages/fixtures/multi/src/multi.zig
@@ -0,0 +1,4 @@
+const widget = @import("widget");
+const internal = @import("internal");
+
+pub const value: u32 = widget.value + internal.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/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/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/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/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/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/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/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/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
new file mode 100644
index 00000000..49203466
--- /dev/null
+++ b/zig/tests/integration_tests/packages/main.zig
@@ -0,0 +1,37 @@
+const leaf = @import("leaf");
+const host = @import("host");
+const top = @import("top");
+const child = @import("child");
+const multi = @import("multi");
+const widget = @import("widget");
+const greeter = @import("greeter");
+const pruned = @import("pruned");
+const lib = @import("lib");
+const lazyhost = @import("lazyhost");
+const symlinked = @import("symlinked");
+const genopts = @import("genopts");
+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;
+ _ = host.value;
+ _ = top.value;
+ _ = child.value;
+ _ = multi.value;
+ _ = widget.value;
+ _ = greeter.value;
+ _ = pruned.value;
+ _ = lib.v1;
+ _ = lazyhost.value;
+ _ = symlinked.value;
+ _ = genopts.value;
+ _ = usec.pid();
+ _ = cdep.value();
+ _ = syslibdep.compute(21);
+ _ = optdep.compute(1);
+ _ = cppdep.value();
+}
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/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/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;
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/packages/tools/pack.zig b/zig/tests/integration_tests/packages/tools/pack.zig
new file mode 100644
index 00000000..c431ae7d
--- /dev/null
+++ b/zig/tests/integration_tests/packages/tools/pack.zig
@@ -0,0 +1,203 @@
+//! 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| {
+ // 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 });
+ }
+
+ 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);
+}
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..c25511eb
--- /dev/null
+++ b/zig/tests/integration_tests/packages_tests_runner.zig
@@ -0,0 +1,180 @@
+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,
+ patches: []const Patch = &.{},
+ symlink: ?[2][]const u8 = null,
+};
+
+// Packed in topological order (dependencies first).
+const packages = [_]Package{
+ .{ .name = "leaf" },
+ .{ .name = "host", .patches = &.{.{ .manifest = "libs/foo/build.zig.zon", .deps = &.{"leaf"} }} },
+ .{ .name = "base" },
+ .{ .name = "bottom", .patches = &.{.{ .deps = &.{"base"} }} },
+ .{ .name = "left", .patches = &.{.{ .deps = &.{"bottom"} }} },
+ .{ .name = "right", .patches = &.{.{ .deps = &.{"bottom"} }} },
+ .{ .name = "top", .patches = &.{.{ .deps = &.{ "left", "right" } }} },
+ .{ .name = "multi" },
+ .{ .name = "pruned" },
+ .{ .name = "libv1" },
+ .{ .name = "libv2" },
+ .{ .name = "lazyleaf" },
+ .{ .name = "lazyhost", .patches = &.{.{ .deps = &.{"lazyleaf"} }} },
+ .{ .name = "symlinked", .symlink = .{ "real.zig", "src/aliased.zig" } },
+ .{ .name = "srconly" },
+ .{ .name = "genopts" },
+ .{ .name = "usec" },
+ .{ .name = "cdep" },
+ .{ .name = "syslibdep" },
+ .{ .name = "optdep" },
+ .{ .name = "cppdep" },
+};
+
+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", "multi", "pruned", "libv1", "lazyhost", "symlinked", "genopts", "srconly", "usec", "cdep", "syslibdep", "optdep", "cppdep" } },
+ .{ .manifest = "child/build.zig.zon", .deps = &.{ "leaf", "libv2" } },
+};
+
+test "Zig packages are imported from file:// tarballs" {
+ const ctx = try BitContext.init();
+ defer ctx.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ var urls = std.StringHashMap([]const u8).init(allocator);
+ var hashes = std.StringHashMap([]const u8).init(allocator);
+
+ for (packages) |pkg| {
+ 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));
+ }
+
+ 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(.{
+ .argv = &[_][]const u8{ "run", "//tools:pack", "--", dir, tarball },
+ });
+ defer pack.deinit();
+ try std.testing.expect(pack.success);
+
+ 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}));
+ }
+
+ 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`)
+ // 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);
+}
+
+// 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()" },
+ });
+
+ 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\"" }});
+
+ 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 {
+ 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,
+ 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 });
+}