From 1a67ff1020645db58e6e2fb276035c26a560db28 Mon Sep 17 00:00:00 2001 From: Abhishek Rai Date: Wed, 6 May 2026 23:36:21 -0700 Subject: [PATCH] test: add rules_zig integration coverage and examples --- MODULE.bazel.lock | 2 +- README.md | 14 ++++- examples/bzlmod_consumer/BUILD.bazel.example | 6 ++ examples/bzlmod_consumer/MODULE.bazel.example | 14 +++++ examples/bzlmod_consumer/README.md | 5 ++ examples/bzlmod_consumer/main.zig.example | 5 ++ examples/multi_package/README.md | 9 +++ examples/multi_package/app/BUILD.bazel | 7 +++ examples/multi_package/app/main.zig | 6 ++ examples/multi_package/lib/BUILD.bazel | 8 +++ examples/multi_package/lib/greeting.zig | 3 + tests/BUILD.bazel | 1 + tests/README.md | 12 ++++ tests/integration/basic/BUILD.bazel | 30 ++++++++++ tests/integration/basic/main.zig | 6 ++ tests/integration/basic/math.zig | 7 +++ tests/integration/basic/math_test.zig | 7 +++ tests/private/BUILD.bazel | 1 + tests/private/smoke_test.bzl | 59 +++++++++++++++++++ zig/private/zig_repository.bzl | 2 + zig/rules.bzl | 11 ++-- zig/toolchain.bzl | 6 ++ 22 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 examples/bzlmod_consumer/BUILD.bazel.example create mode 100644 examples/bzlmod_consumer/MODULE.bazel.example create mode 100644 examples/bzlmod_consumer/README.md create mode 100644 examples/bzlmod_consumer/main.zig.example create mode 100644 examples/multi_package/README.md create mode 100644 examples/multi_package/app/BUILD.bazel create mode 100644 examples/multi_package/app/main.zig create mode 100644 examples/multi_package/lib/BUILD.bazel create mode 100644 examples/multi_package/lib/greeting.zig create mode 100644 tests/BUILD.bazel create mode 100644 tests/README.md create mode 100644 tests/integration/basic/BUILD.bazel create mode 100644 tests/integration/basic/main.zig create mode 100644 tests/integration/basic/math.zig create mode 100644 tests/integration/basic/math_test.zig create mode 100644 tests/private/BUILD.bazel create mode 100644 tests/private/smoke_test.bzl diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index fb5b2b8..c2e6ea3 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -189,7 +189,7 @@ "moduleExtensions": { "//zig:extensions.bzl%zig": { "general": { - "bzlTransitiveDigest": "FUodQNJVgCSqxwkc4v/mbiiCeZFZOAk1f3buP+yS+tE=", + "bzlTransitiveDigest": "0tFSWO5uuDcN62dBMHXuSip7u1QbghomNZN7neHw11c=", "usagesDigest": "1zsLy7Z6DquC93RpnXpFVDS+CNStZmTrLh3/REgEtcQ=", "recordedInputs": [], "generatedRepoSpecs": { diff --git a/README.md b/README.md index 96855dc..2f39676 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ CI currently runs: - `bazel test //...` +That includes the dedicated regression tests under `tests/` plus the runnable examples that are test targets. + As the ruleset grows, this matrix can expand to include more Bazel versions, additional examples, and stricter validation. ## Setup @@ -81,10 +83,20 @@ In practice: - local Linux builds use a Linux Zig SDK - remote execution would pick the Zig SDK that matches the remote exec platform -`zig_binary`, `zig_library`, and `zig_test` do not yet model Bazel target-platform driven cross-compilation. They invoke Zig without a `-target` flag, so outputs are currently host-native for the chosen exec-platform SDK. +`zig_binary`, `zig_library`, and `zig_test` currently pass an explicit Zig `-target` matching the selected exec-platform SDK. They do not yet model Bazel target-platform driven cross-compilation. Outputs are currently host-native for the chosen exec-platform SDK. So for now, treat these rules as host-native builds with exec-platform-aware toolchain selection, not full Bazel cross-compilation rules yet. +## Examples + +The repository includes runnable examples for common usage patterns: + +- `examples/hello_world` — minimal `zig_binary` +- `examples/deps` — dependency composition with `zig_library` and `zig_binary` +- `examples/math_lib` — `zig_library` plus `zig_test` +- `examples/multi_package` — a binary depending on a library from another Bazel package +- `examples/bzlmod_consumer` — template files for consuming `rules_zig` from another module before BCR publication + ## Rules ### `zig_binary` example diff --git a/examples/bzlmod_consumer/BUILD.bazel.example b/examples/bzlmod_consumer/BUILD.bazel.example new file mode 100644 index 0000000..a6eebe5 --- /dev/null +++ b/examples/bzlmod_consumer/BUILD.bazel.example @@ -0,0 +1,6 @@ +load("@rules_zig//zig:defs.bzl", "zig_binary") + +zig_binary( + name = "hello", + srcs = ["main.zig"], +) diff --git a/examples/bzlmod_consumer/MODULE.bazel.example b/examples/bzlmod_consumer/MODULE.bazel.example new file mode 100644 index 0000000..aceba53 --- /dev/null +++ b/examples/bzlmod_consumer/MODULE.bazel.example @@ -0,0 +1,14 @@ +module(name = "rules_zig_consumer", version = "0.1.0") + +bazel_dep(name = "rules_zig", version = "0.1.0") +git_override( + module_name = "rules_zig", + remote = "https://github.com/darthfork/rules_zig.git", + commit = "", +) + +zig = use_extension("@rules_zig//zig:extensions.bzl", "zig") +zig.toolchain(zig_version = "0.13.0") +use_repo(zig, "zig_toolchains") + +register_toolchains("@zig_toolchains//:all") diff --git a/examples/bzlmod_consumer/README.md b/examples/bzlmod_consumer/README.md new file mode 100644 index 0000000..59b78c2 --- /dev/null +++ b/examples/bzlmod_consumer/README.md @@ -0,0 +1,5 @@ +# Bzlmod consumer example + +This is a copy-paste skeleton for consuming `rules_zig` from another Bazel module before it is published to the Bazel Central Registry. + +Use the `.example` files as templates in a separate repository. diff --git a/examples/bzlmod_consumer/main.zig.example b/examples/bzlmod_consumer/main.zig.example new file mode 100644 index 0000000..a72ba99 --- /dev/null +++ b/examples/bzlmod_consumer/main.zig.example @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn main() !void { + try std.io.getStdOut().writer().print("Hello from a rules_zig consumer\n", .{}); +} diff --git a/examples/multi_package/README.md b/examples/multi_package/README.md new file mode 100644 index 0000000..908256b --- /dev/null +++ b/examples/multi_package/README.md @@ -0,0 +1,9 @@ +# Multi-package example + +This example shows a `zig_binary` in one Bazel package depending on a `zig_library` in another package. + +Build it with: + +```bash +bazel build //examples/multi_package/app:hello_multi_package +``` diff --git a/examples/multi_package/app/BUILD.bazel b/examples/multi_package/app/BUILD.bazel new file mode 100644 index 0000000..7e7044d --- /dev/null +++ b/examples/multi_package/app/BUILD.bazel @@ -0,0 +1,7 @@ +load("//zig:defs.bzl", "zig_binary") + +zig_binary( + name = "hello_multi_package", + srcs = ["main.zig"], + deps = ["//examples/multi_package/lib:greeting"], +) diff --git a/examples/multi_package/app/main.zig b/examples/multi_package/app/main.zig new file mode 100644 index 0000000..abfd1da --- /dev/null +++ b/examples/multi_package/app/main.zig @@ -0,0 +1,6 @@ +const std = @import("std"); +const greeting = @import("greeting"); + +pub fn main() !void { + try std.io.getStdOut().writer().print("{s}\n", .{greeting.text()}); +} diff --git a/examples/multi_package/lib/BUILD.bazel b/examples/multi_package/lib/BUILD.bazel new file mode 100644 index 0000000..735097d --- /dev/null +++ b/examples/multi_package/lib/BUILD.bazel @@ -0,0 +1,8 @@ +load("//zig:defs.bzl", "zig_library") + +zig_library( + name = "greeting", + srcs = ["greeting.zig"], + module_name = "greeting", + visibility = ["//examples/multi_package/app:__pkg__"], +) diff --git a/examples/multi_package/lib/greeting.zig b/examples/multi_package/lib/greeting.zig new file mode 100644 index 0000000..cff1224 --- /dev/null +++ b/examples/multi_package/lib/greeting.zig @@ -0,0 +1,3 @@ +pub fn text() []const u8 { + return "Hello from a Zig library in another Bazel package"; +} diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel new file mode 100644 index 0000000..d19e17f --- /dev/null +++ b/tests/BUILD.bazel @@ -0,0 +1 @@ +# Integration and regression tests for rules_zig. diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..c9a986d --- /dev/null +++ b/tests/README.md @@ -0,0 +1,12 @@ +# rules_zig tests + +This tree contains regression coverage for the ruleset itself. + +- `integration/basic` verifies that `zig_binary`, `zig_library`, and `zig_test` all work under `bazel test`. +- `private` contains small Starlark helpers used only by the test suite. + +Run everything with: + +```bash +bazel test //... +``` diff --git a/tests/integration/basic/BUILD.bazel b/tests/integration/basic/BUILD.bazel new file mode 100644 index 0000000..b147cc9 --- /dev/null +++ b/tests/integration/basic/BUILD.bazel @@ -0,0 +1,30 @@ +load("//tests/private:smoke_test.bzl", "binary_smoke_test", "build_smoke_test") +load("//zig:defs.bzl", "zig_binary", "zig_library", "zig_test") + +zig_library( + name = "math", + srcs = ["math.zig"], +) + +zig_binary( + name = "calculator", + srcs = ["main.zig"], +) + +zig_test( + name = "math_unit_test", + srcs = [ + "math_test.zig", + "math.zig", + ], +) + +build_smoke_test( + name = "math_build_test", + target = ":math", +) + +binary_smoke_test( + name = "calculator_smoke_test", + binary = ":calculator", +) diff --git a/tests/integration/basic/main.zig b/tests/integration/basic/main.zig new file mode 100644 index 0000000..d2d8be0 --- /dev/null +++ b/tests/integration/basic/main.zig @@ -0,0 +1,6 @@ +const std = @import("std"); + +pub fn main() void { + const stdout = std.io.getStdOut().writer(); + stdout.print("calculator smoke test\n", .{}) catch unreachable; +} diff --git a/tests/integration/basic/math.zig b/tests/integration/basic/math.zig new file mode 100644 index 0000000..83df800 --- /dev/null +++ b/tests/integration/basic/math.zig @@ -0,0 +1,7 @@ +pub fn add(lhs: i32, rhs: i32) i32 { + return lhs + rhs; +} + +pub fn multiply(lhs: i32, rhs: i32) i32 { + return lhs * rhs; +} diff --git a/tests/integration/basic/math_test.zig b/tests/integration/basic/math_test.zig new file mode 100644 index 0000000..cfc8725 --- /dev/null +++ b/tests/integration/basic/math_test.zig @@ -0,0 +1,7 @@ +const std = @import("std"); +const math = @import("math.zig"); + +test "math helpers return expected values" { + try std.testing.expectEqual(@as(i32, 5), math.add(2, 3)); + try std.testing.expectEqual(@as(i32, 42), math.multiply(6, 7)); +} diff --git a/tests/private/BUILD.bazel b/tests/private/BUILD.bazel new file mode 100644 index 0000000..b1a135b --- /dev/null +++ b/tests/private/BUILD.bazel @@ -0,0 +1 @@ +# Test helpers for rules_zig's own integration tests. diff --git a/tests/private/smoke_test.bzl b/tests/private/smoke_test.bzl new file mode 100644 index 0000000..57ec954 --- /dev/null +++ b/tests/private/smoke_test.bzl @@ -0,0 +1,59 @@ +"""Small test-only smoke test rule for executable Zig targets.""" + +def _binary_smoke_test_impl(ctx): + script = ctx.actions.declare_file(ctx.label.name + ".sh") + binary = ctx.executable.binary + ctx.actions.write( + output = script, + is_executable = True, + content = """#!/usr/bin/env bash +set -euo pipefail +runfiles_dir="${RUNFILES_DIR:-$0.runfiles}" +"${runfiles_dir}/${TEST_WORKSPACE}/%s" +""" % binary.short_path, + ) + + return [ + DefaultInfo( + executable = script, + runfiles = ctx.runfiles(files = [binary]), + ), + ] + +def _build_smoke_test_impl(ctx): + script = ctx.actions.declare_file(ctx.label.name + ".sh") + ctx.actions.write( + output = script, + is_executable = True, + content = "#!/usr/bin/env bash\nset -euo pipefail\n", + ) + return [ + DefaultInfo( + executable = script, + runfiles = ctx.runfiles(files = ctx.files.target), + ), + ] + +binary_smoke_test = rule( + implementation = _binary_smoke_test_impl, + attrs = { + "binary": attr.label( + doc = "Executable target to run as a smoke test.", + cfg = "target", + executable = True, + mandatory = True, + ), + }, + test = True, +) + +build_smoke_test = rule( + implementation = _build_smoke_test_impl, + attrs = { + "target": attr.label( + doc = "Target whose default outputs must build.", + mandatory = True, + ), + }, + test = True, +) diff --git a/zig/private/zig_repository.bzl b/zig/private/zig_repository.bzl index d90de2b..2850dbb 100644 --- a/zig/private/zig_repository.bzl +++ b/zig/private/zig_repository.bzl @@ -31,10 +31,12 @@ exports_files(["{zig_exe}"]) zig_toolchain( name = "zig_toolchain_impl", + target = "{target}", zig_exe = "{zig_exe}", zig_version = "{zig_version}", ) """.format( + target = ctx.attr.index_key, zig_exe = zig_exe, zig_version = ctx.attr.zig_version, ), diff --git a/zig/rules.bzl b/zig/rules.bzl index 4519902..4a2fd20 100644 --- a/zig/rules.bzl +++ b/zig/rules.bzl @@ -82,7 +82,9 @@ def _add_module_args(args, dep_modules, module_dep_graph): if not progressed: fail("cyclic Zig module dependencies detected in deps") -def _add_common_args(args, main_src, out, cache_dir, global_cache_dir, root_deps, dep_modules, module_dep_graph, dep_archives): +def _add_common_args(args, zig_target, main_src, out, cache_dir, global_cache_dir, root_deps, dep_modules, module_dep_graph, dep_archives): + args.add("-target") + args.add(zig_target) for module_name in root_deps: args.add("--dep") args.add(module_name) @@ -112,7 +114,7 @@ def _zig_binary_impl(ctx): args = ctx.actions.args() args.add("build-exe") - _add_common_args(args, main_src, out, cache_dir, global_cache_dir, root_deps, dep_modules, module_dep_graph, dep_archives) + _add_common_args(args, zig_info.target, main_src, out, cache_dir, global_cache_dir, root_deps, dep_modules, module_dep_graph, dep_archives) ctx.actions.run( outputs = [out, cache_dir, global_cache_dir], @@ -170,7 +172,7 @@ def _zig_library_impl(ctx): args = ctx.actions.args() args.add("build-lib") - _add_common_args(args, main_src, out, cache_dir, global_cache_dir, root_deps, dep_modules, module_dep_graph, depset(transitive = transitive_archives).to_list()) + _add_common_args(args, zig_info.target, main_src, out, cache_dir, global_cache_dir, root_deps, dep_modules, module_dep_graph, depset(transitive = transitive_archives).to_list()) ctx.actions.run( outputs = [out, cache_dir, global_cache_dir], @@ -239,12 +241,13 @@ def _zig_test_impl(ctx): for src in srcs: copy_commands.append("cp '{}' '{}/{}'".format(src.path, src_tree.path, src.basename)) - command = "set -euo pipefail\nROOT=\"$PWD\"\nmkdir -p '{src_tree}' '{cache_dir}' '{global_cache_dir}'\n{copies}\ncd '{src_tree}'\n\"$ROOT/{zig}\" test '{main}' -femit-bin=\"$ROOT/{out}\" --cache-dir \"$ROOT/{cache_dir}\" --global-cache-dir \"$ROOT/{global_cache_dir}\"".format( + command = "set -euo pipefail\nROOT=\"$PWD\"\nmkdir -p '{src_tree}' '{cache_dir}' '{global_cache_dir}'\n{copies}\ncd '{src_tree}'\n\"$ROOT/{zig}\" test -target '{target}' '{main}' -femit-bin=\"$ROOT/{out}\" --cache-dir \"$ROOT/{cache_dir}\" --global-cache-dir \"$ROOT/{global_cache_dir}\"".format( src_tree = src_tree.path, cache_dir = cache_dir.path, global_cache_dir = global_cache_dir.path, copies = "\n".join(copy_commands), zig = zig_link.path, + target = zig_info.target, main = main_src.basename, out = out.path, ) diff --git a/zig/toolchain.bzl b/zig/toolchain.bzl index 50e95e4..b6d6782 100644 --- a/zig/toolchain.bzl +++ b/zig/toolchain.bzl @@ -3,6 +3,7 @@ ZigInfo = provider( doc = "Information about the Zig compiler.", fields = { + "target": "string: The Zig target triple matching the selected SDK.", "zig_exe": "File: The Zig compiler executable.", "zig_version": "string: The Zig version.", }, @@ -10,6 +11,7 @@ ZigInfo = provider( def _zig_toolchain_impl(ctx): zig_info = ZigInfo( + target = ctx.attr.target, zig_exe = ctx.file.zig_exe, zig_version = ctx.attr.zig_version, ) @@ -21,6 +23,10 @@ def _zig_toolchain_impl(ctx): zig_toolchain = rule( implementation = _zig_toolchain_impl, attrs = { + "target": attr.string( + doc = "The Zig target triple matching this SDK.", + mandatory = True, + ), "zig_exe": attr.label( doc = "The Zig compiler executable.", mandatory = True,