Lazy until the terminal
Selectors and predicates perform no project I/O. check, expectPasses, report rendering, or export is the execution boundary.
Zig 0.16 · v0.0.2 preview
ArchUnitZig turns a Zig project's imports into an owned dependency graph, then lets ordinary zig test tests enforce boundaries over files, layers, slices, reports, and Zig-native metrics.
Add the package with a stable dependency name. During pre-release development, replace main with a reviewed commit hash in durable projects.
zig fetch --save=archunit git+https://github.com/LukasNiessen/ArchUnitZig.git#main
Expose ArchUnitZig only to the test module that owns your architecture policy. This is the repository's compiled standalone-consumer fixture—not an abbreviated approximation.
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const archunit = b.dependency("archunit", .{ .target = target, .optimize = optimize });
const architecture = b.createModule(.{
.root_source_file = b.path("test/architecture.zig"),
.target = target,
.optimize = optimize,
});
architecture.addImport("archunit", archunit.module("archunit"));
const architecture_tests = b.addTest(.{ .root_module = architecture });
const run_architecture_tests = b.addRunArtifact(architecture_tests);
run_architecture_tests.setCwd(b.path("."));
const format = b.addFmt(.{ .paths = &.{ "build.zig", "build.zig.zon", "src", "test" }, .check = true });
const test_step = b.step("test", "Run formatting and architecture tests");
test_step.dependOn(&format.step);
test_step.dependOn(&run_architecture_tests.step);
}
Compiled source: test/fixtures/readme-consumer/build.zig
This test selects production Zig files, asks for a positive policy, creates a cycle rule, and delegates failures to the native test adapter. Every fluent stage owns allocated state, so each stage has a matching deinit.
const std = @import("std");
const archunit = @import("archunit");
test "production files have no cycles" {
var files = try archunit.files(std.testing.allocator, .{});
defer files.deinit();
var production = try files.inPath(&.{ .{ .glob = "src/*.zig" }, .{ .glob = "src/**/*.zig" } });
defer production.deinit();
var should = try production.should();
defer should.deinit();
var rule = try should.haveNoCycles();
defer rule.deinit(std.testing.allocator);
try archunit.expectPasses(&rule, .init(.init(std.testing.allocator, std.testing.io)));
}
Compiled source: test/fixtures/readme-consumer/test/architecture.zig
zig build test
A passing rule is quiet. A violation returns error.ArchitectureViolation and prints stable, structured evidence through ArchUnitZig's result formatter.
Selectors and predicates perform no project I/O. check, expectPasses, report rendering, or export is the execution boundary.
Builders are independent owned values. Clone explicitly when branching and release every owned stage.
Rules return tagged, inspectable violations before presentation. Tests can assert exact paths, kinds, and dependency evidence.
Imports, resources, modules, declarations, containers, and coupling replace class-oriented vocabulary that has no honest Zig equivalent.
Start with file rules for most boundaries, move to named layers for an architectural map, and use testing and diagnostics when integrating the result into a larger suite. The compiler-generated API reference lists the complete public facade.
Next: Grammar & patterns