build: single-source the default import set; delegate library tests

Review findings from the pilot: the 17-module default import set was
maintained twice (root's array and build-support's userBinary) — drift
would silently make packaged binaries differ from root-built ones. It
now lives once, as build-support.defaultImports; root and userBinary
both draw from it, and root's user-binary wrapper names the shim +
link script through the kernel package like build-support does.
The root aggregate test step likewise duplicated the library domains'
14 test definitions; it now depends on each domain's own standalone
test step (host-only, so root's -Dtarget/-Doptimize deliberately do
not reach them), and the client domain gains an empty test step for
uniformity. Boot-image file list unchanged.
This commit is contained in:
Daniel Samson
2026-07-26 23:10:10 +01:00
parent 6f4fdc2789
commit 15575960bd
3 changed files with 63 additions and 127 deletions
+33 -112
View File
@@ -86,11 +86,14 @@ fn addUserBinaryImpl(
root: []const u8,
threaded: bool,
) *std.Build.Step.Compile {
// The shim and link script live in the kernel domain package, so name them
// through it — the same spelling build-support's userBinary uses.
const kernel_library = b.dependency("kernel", .{});
return build_support.userBinaryFromImports(b, .{
.name = name,
.root_source_file = b.path(root),
.shim_source_file = b.path("library/kernel/root.zig"),
.linker_script = b.path("library/kernel/user.ld"),
.shim_source_file = kernel_library.path("root.zig"),
.linker_script = kernel_library.path("user.ld"),
.target = target,
.default_imports = default_imports,
.threaded = threaded,
@@ -251,7 +254,6 @@ pub fn build(b: *std.Build) void {
const csv_module = csv_library.module("csv");
const device_registry_module = device_library.module("device-registry");
const aml_module = device_library.module("aml");
const acpi_ids_module = device_library.module("acpi-ids");
const usb_abi_module = device_library.module("usb-abi");
const usb_ids_module = device_library.module("usb-ids");
const usb_transfer_protocol_module = protocol_library.module("usb-transfer-protocol");
@@ -305,27 +307,11 @@ pub fn build(b: *std.Build) void {
const scanout_protocol_module = protocol_library.module("scanout-protocol");
const power_protocol_module = protocol_library.module("power-protocol");
// The library/kernel concern modules (the userspace private-ABI library),
// the device-domain driver libraries, and the service clients.
const mmio_module = device_library.module("mmio");
const system_call_module = kernel_library.module("system-call");
const ipc_module = kernel_library.module("ipc");
const time_module = kernel_library.module("time");
const thread_module = kernel_library.module("thread");
const logging_module = kernel_library.module("logging");
const process_module = kernel_library.module("process");
const file_system_module = kernel_library.module("file-system");
const memory_module = kernel_library.module("memory");
const service_module = kernel_library.module("service");
const start_module = kernel_library.module("start");
const driver_module = device_library.module("driver");
const block_client_module = device_library.module("block");
const display_client_module = client_library.module("display");
const input_client_module = client_library.module("input");
// The driver-side libraries some binaries name as per-binary extras. (The
// rest of the domain modules reach the root only through build-support's
// defaultImports below.)
const pci_module = device_library.module("pci");
const usb_module = device_library.module("usb");
const xkb_layouts_module = xkeyboard_config_library.module("layouts");
const xkeyboard_config_module = xkeyboard_config_library.module("xkeyboard-config");
// The initial_ramdisk container format, shared by the kernel (unpacks it) and
// the EFI loader (packs it in RAM from the boot volume's /system tree). No
@@ -373,28 +359,16 @@ pub fn build(b: *std.Build) void {
b.getInstallStep().dependOn(&kernel_install.step);
// --- init: the first user-space program (a system service) ---
// The default module set every user binary can import directly: the library/kernel
// concern modules, the device/service clients, mmio, the keyboard layouts, and the ACPI
// id registry. Per-binary extras are added with programModule(exe).addImport.
const default_imports = [_]std.Build.Module.Import{
.{ .name = "mmio", .module = mmio_module },
.{ .name = "xkeyboard-config", .module = xkeyboard_config_module },
.{ .name = "acpi-ids", .module = acpi_ids_module },
.{ .name = "system-call", .module = system_call_module },
.{ .name = "ipc", .module = ipc_module },
.{ .name = "memory", .module = memory_module },
.{ .name = "process", .module = process_module },
.{ .name = "thread", .module = thread_module },
.{ .name = "time", .module = time_module },
.{ .name = "logging", .module = logging_module },
.{ .name = "file-system", .module = file_system_module },
.{ .name = "service", .module = service_module },
.{ .name = "start", .module = start_module },
.{ .name = "driver", .module = driver_module },
.{ .name = "block", .module = block_client_module },
.{ .name = "display", .module = display_client_module },
.{ .name = "input", .module = input_client_module },
};
// The default module set every user binary can import directly, drawn from
// build-support's single authoritative list so the root stanzas and the
// binary packages can never drift apart. Per-binary extras are added with
// programModule(exe).addImport.
const default_imports = build_support.defaultImports(.{
.kernel = kernel_library,
.device = device_library,
.client = client_library,
.xkeyboard_config = xkeyboard_config_library,
});
// Built by the shared user-binary recipe (see addUserBinary): freestanding,
// linked into the kernel's user region against the library/kernel modules, and
@@ -873,24 +847,14 @@ pub fn build(b: *std.Build) void {
"system/boot-handoff.zig",
"system/abi.zig",
"system/initial-ramdisk.zig", // v2 path-named entries: find/basename/magic
"library/device/model/device-abi.zig",
"library/device/pci/pci-class.zig", // class/subclass/prog-IF name decoding
"library/device/acpi/acpi-ids.zig", // _HID name decoding
"library/device/acpi/aml/aml.zig", // AML parse + interpret, incl. Notify dispatch (M21)
"library/device/usb/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings
"library/device/usb/usb-ids.zig", // class/subclass/protocol code assignments
"library/csv/csv.zig", // shared /etc/*.csv comment-strip + field-split helpers
"library/device/mmio/mmio.zig", // barriers assemble + registers round-trip
"system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine
"system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly
"system/drivers/usb-hid/hid-report.zig", // HID boot-report keyboard/mouse decode
"system/drivers/usb-storage/bulk-only-transport.zig", // CBW/CSW wrapper sizes
"system/drivers/usb-storage/scsi.zig", // SCSI CDB encodings (big-endian)
"library/protocol/vfs/vfs-protocol.zig", // NodeKind / DirectoryEntry sizes + op values
"system/services/fat/on-disk.zig", // FAT on-disk struct sizes + type detection
"system/services/fat/engine.zig", // FAT read/write over a RAM-backed image
"system/services/display/compositor.zig", // Rect math + fill/composite/blit-tile
"library/protocol/display/display-protocol.zig", // pack(): native pixel encoding per format
"system/drivers/virtio-gpu/virtio-gpu-protocol.zig", // virtio-gpu command struct sizes
"system/drivers/virtio-gpu/virtio-pci.zig", // virtio 1.0 PCI transport struct sizes
}) |root| {
@@ -904,33 +868,21 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&b.addRunArtifact(mod_tests).step);
}
// The device registry imports the shared `csv` module, so its tests need that
// import wired and don't fit the plain loop above. These prove the /etc/devices.csv
// parse + most-specific driver match (incl. virtio 1AF4:1050 beating a class rule).
const device_registry_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("library/device/registry/device-registry.zig"),
.target = target,
.optimize = optimize,
.imports = &.{.{ .name = "csv", .module = csv_module }},
}),
});
test_step.dependOn(&b.addRunArtifact(device_registry_tests).step);
// The xkeyboard-config keymap tests need its generated `layouts` import wired, so they
// don't fit the plain loop above. Its keycode->character assertions are the end-to-end
// proof that the xkb-data -> generator -> Zig-lookup pipeline is correct.
const xkb_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("library/xkeyboard-config/xkeyboard-config.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "layouts", .module = xkb_layouts_module },
},
}),
});
test_step.dependOn(&b.addRunArtifact(xkb_tests).step);
// The library domains own their unit tests (each package's standalone
// `zig build test` step); the root aggregate delegates to those steps so
// one command still runs everything and a test added inside a domain
// package can never be silently skipped here. Domain tests are host-only,
// so root's -Dtarget/-Doptimize deliberately do not reach them.
for ([_]*std.Build.Dependency{
kernel_library,
device_library,
client_library,
protocol_library,
csv_library,
xkeyboard_config_library,
}) |library| {
test_step.dependOn(&library.builder.top_level_steps.get("test").?.step);
}
// The tagged kernel log ring: append/wrap/reclaim/sequence-gap behavior over
// a RAM buffer. Needs the `abi` module (record header layout), so it doesn't
@@ -947,37 +899,6 @@ pub fn build(b: *std.Build) void {
});
test_step.dependOn(&b.addRunArtifact(log_ring_tests).step);
// runtime.time's Instant/Duration arithmetic. time.zig pulls in system.zig (the
// syscall wrappers), which needs the `abi` module, so it doesn't fit the plain
// loop above.
const time_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("library/kernel/time.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "abi", .module = abi_module },
},
}),
});
test_step.dependOn(&b.addRunArtifact(time_tests).step);
// runtime.Thread's lock/condvar state machines (Mutex/Condition/RwLock/WaitGroup). Its
// Futex seam falls back to std.Thread.Futex off the danos target, so the tests exercise
// them with real host threads (docs/threading-plan.md M11). Like time.zig it pulls in
// system.zig (syscall wrappers), which needs the `abi` module.
const thread_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("library/kernel/thread.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "abi", .module = abi_module },
},
}),
});
test_step.dependOn(&b.addRunArtifact(thread_tests).step);
// Convenience: `zig build gen-xkeyboard-config` regenerates the layout tables from the
// vendored data (offline). `fetch` (the network step) stays a manual script run.
const gen_xkb = b.addSystemCommand(&.{ "python3", "tools/make-xkeyboard-config.py", "generate" });