diff --git a/build.zig b/build.zig index 6d9fee2..c474769 100644 --- a/build.zig +++ b/build.zig @@ -221,11 +221,12 @@ pub fn build(b: *std.Build) void { // image leaves it out). const init_exe = b.dependency("init", .{ .serial = serial }).artifact("init"); - // --- the rest of the boot tree: /system services and drivers, /test fixtures --- + // --- the rest of the boot tree: /system services and drivers --- // Each is built by the same user-binary recipe and laid out at its FHS path on // the boot volume (see `bundled` below). The EFI loader walks the tree at boot // and hands the kernel an in-RAM initial_ramdisk of it (system/initial-ramdisk.zig). - const vfstest_exe = b.dependency("vfs-test", .{}).artifact("vfs-test"); + // (The /test fixtures are lazy dependencies, resolved further down only + // for a -Dtest-case build.) // The drivers, each directory its own package: the PS/2 bus family (bus + // keyboard + mouse from one package), the xHCI bus driver, the USB HID // class drivers, and USB mass storage. Their unit tests ride along. @@ -248,47 +249,31 @@ pub fn build(b: *std.Build) void { const display_demo_exe = b.dependency("display-demo", .{}).artifact("display-demo"); const virtio_gpu_package = b.dependency("virtio-gpu", .{}); const virtio_gpu_exe = virtio_gpu_package.artifact("virtio-gpu"); - const shared_memory_server_exe = b.dependency("shared-memory-server", .{}).artifact("shared-memory-server"); - const shared_memory_client_exe = b.dependency("shared-memory-client", .{}).artifact("shared-memory-client"); - const fat_test_exe = b.dependency("fat-test", .{}).artifact("fat-test"); // The first binary package (docs/build-packages-plan.md, phase 2): pci-bus // builds itself against the domain packages; the root build just takes the // artifact for the boot image. const pci_bus_exe = b.dependency("pci-bus", .{}).artifact("pci-bus"); - // crash-test is a fixture, not a real driver: it hellos to the device - // manager, then faults — what the driver-restart scenario drives the - // crash-loop cap with. pci-cap-test and iommu-fault-test exercise the - // driver-side PCI library surface and the VT-d rogue-DMA negative proof. - const crash_test_exe = b.dependency("crash-test", .{}).artifact("crash-test"); - const device_list_exe = b.dependency("device-list", .{}).artifact("device-list"); - const pci_cap_test_exe = b.dependency("pci-cap-test", .{}).artifact("pci-cap-test"); - const iommu_fault_test_exe = b.dependency("iommu-fault-test", .{}).artifact("iommu-fault-test"); // The discovery service: one swappable process per firmware // (docs/discovery.md), bundled under the neutral ramdisk name // "discovery" so the device manager never learns which firmware it is on. // x86 boots describe hardware with ACPI; the Raspberry Pis hand over a // flattened device tree — the aarch64 target flips the default when it - // lands (docs/arm.md). Each firmware's service is its own package; both - // export an artifact named "discovery", and this option picks which one - // ships (only the chosen one is compiled). + // lands (docs/arm.md). Each firmware's service is its own LAZY package; + // both export an artifact named "discovery", and this option picks which + // one ships — the unselected package's build file is never even loaded. + // (lazyDependency returns null only for an unfetched remote package; these + // are in-repo path dependencies, so a null means the directory is gone.) const Discovery = enum { acpi, fdt }; const discovery = b.option(Discovery, "discovery", "Which discovery service fills the ramdisk's 'discovery' slot (default: acpi)") orelse Discovery.acpi; const discovery_exe = switch (discovery) { - .acpi => b.dependency("acpi", .{}).artifact("discovery"), - .fdt => b.dependency("fdt", .{}).artifact("discovery"), + .acpi => (b.lazyDependency("acpi", .{}) orelse @panic("system/services/acpi is missing")).artifact("discovery"), + .fdt => (b.lazyDependency("fdt", .{}) orelse @panic("system/services/fdt is missing")).artifact("discovery"), }; const device_manager_exe = b.dependency("device-manager", .{}).artifact("device-manager"); // The input service and its exercisers: the fan-out server, a hardware-free synthetic // source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md. const input_exe = b.dependency("input", .{}).artifact("input"); - const input_source_exe = b.dependency("input-source", .{}).artifact("input-source"); - const input_test_exe = b.dependency("input-test", .{}).artifact("input-test"); - const args_echo_exe = b.dependency("args-echo", .{}).artifact("args-echo"); - const process_test_exe = b.dependency("process-test", .{}).artifact("process-test"); const logger_exe = b.dependency("logger", .{}).artifact("logger"); - // The first multi-threaded binary: exercises runtime.Thread over the thread ABI - // (docs/threading.md). Its package opts into threading (real atomics/TLS). - const thread_test_exe = b.dependency("thread-test", .{}).artifact("thread-test"); // Every user binary and its FHS home on the boot volume. There is no packed // ramdisk artifact any more: make-fat-image.py lays each binary out at this @@ -327,32 +312,38 @@ pub fn build(b: *std.Build) void { .{ .path = "system/drivers/virtio-gpu", .binary = virtio_gpu_exe.getEmittedBin() }, .{ .path = "system/drivers/pci-bus", .binary = pci_bus_exe.getEmittedBin() }, }; - // The userspace test fixtures under /test. A plain `zig build` produces a clean - // image WITHOUT them; they are bundled only for a test build — which the QEMU - // harness signals by passing -Dtest-case= for every scenario, exactly when - // these fixtures must be on the boot volume. Merely building this array never - // forces a compile: the fixture exes build only if `bundled` (below) includes them. - const test_bundled = [_]images.BundledBinary{ - .{ .path = "test/system/services/vfs-test", .binary = vfstest_exe.getEmittedBin() }, - .{ .path = "test/system/services/fat-test", .binary = fat_test_exe.getEmittedBin() }, - .{ .path = "test/system/services/shared-memory-server", .binary = shared_memory_server_exe.getEmittedBin() }, - .{ .path = "test/system/services/shared-memory-client", .binary = shared_memory_client_exe.getEmittedBin() }, - .{ .path = "test/system/services/crash-test", .binary = crash_test_exe.getEmittedBin() }, - .{ .path = "test/system/services/device-list", .binary = device_list_exe.getEmittedBin() }, - .{ .path = "test/system/services/pci-cap-test", .binary = pci_cap_test_exe.getEmittedBin() }, - .{ .path = "test/system/services/iommu-fault-test", .binary = iommu_fault_test_exe.getEmittedBin() }, - .{ .path = "test/system/services/input-source", .binary = input_source_exe.getEmittedBin() }, - .{ .path = "test/system/services/input-test", .binary = input_test_exe.getEmittedBin() }, - .{ .path = "test/system/services/args-echo", .binary = args_echo_exe.getEmittedBin() }, - .{ .path = "test/system/services/process-test", .binary = process_test_exe.getEmittedBin() }, - .{ .path = "test/system/services/thread-test", .binary = thread_test_exe.getEmittedBin() }, - }; // A no-option build assumes neither -Dtest-case nor -Ddiagnose: it ships the - // production set only. Test fixtures join in only under -Dtest-case; the - // diagnose display-omission is already handled by init_csv_source above. + // production set only. The userspace test fixtures under /test join in only + // for a test build — which the QEMU harness signals by passing + // -Dtest-case= for every scenario, exactly when they must be on the + // boot volume. They are LAZY dependencies: a plain build neither compiles + // them nor loads their build files (docs/build-packages-plan.md). Fixture + // packages are uniform — the dependency name, the artifact name, and the + // boot path's leaf all match the directory — so a name is a whole entry. var bundled_list: std.ArrayListUnmanaged(images.BundledBinary) = .empty; bundled_list.appendSlice(b.allocator, &production_bundled) catch @panic("OOM"); - if (test_case != null) bundled_list.appendSlice(b.allocator, &test_bundled) catch @panic("OOM"); + if (test_case != null) for ([_][]const u8{ + "vfs-test", // the user-space VFS round-trip client + "fat-test", + "shared-memory-server", + "shared-memory-client", + "crash-test", // hellos to the device manager, then faults — drives the crash-loop cap + "device-list", + "pci-cap-test", // exercises the driver-side PCI library against the pci-caps NIC + "iommu-fault-test", // fires the rogue DMA that VT-d must fault + "input-source", + "input-test", + "args-echo", + "process-test", + "thread-test", // the multi-threaded fixture (its package sets .threaded) + }) |fixture| { + const package = b.lazyDependency(fixture, .{}) orelse + @panic("a test fixture package is missing under test/system/services"); + bundled_list.append(b.allocator, .{ + .path = b.fmt("test/system/services/{s}", .{fixture}), + .binary = package.artifact(fixture).getEmittedBin(), + }) catch @panic("OOM"); + }; const bundled = bundled_list.items; // Boot methods live in boot/, one per way of getting the kernel running. diff --git a/build.zig.zon b/build.zig.zon index 3f708cd..c1b043a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -51,26 +51,29 @@ .@"device-manager" = .{ .path = "system/services/device-manager" }, .input = .{ .path = "system/services/input" }, .logger = .{ .path = "system/services/logger" }, - .acpi = .{ .path = "system/services/acpi" }, - .fdt = .{ .path = "system/services/fdt" }, + // The discovery pair and the /test fixtures are lazy: only what a + // given build actually ships gets its build file loaded and compiled + // (-Ddiscovery picks one of the pair; -Dtest-case pulls the fixtures). + .acpi = .{ .path = "system/services/acpi", .lazy = true }, + .fdt = .{ .path = "system/services/fdt", .lazy = true }, .@"ps2-bus" = .{ .path = "system/drivers/ps2-bus" }, .@"usb-xhci-bus" = .{ .path = "system/drivers/usb-xhci-bus" }, .@"usb-hid" = .{ .path = "system/drivers/usb-hid" }, .@"usb-storage" = .{ .path = "system/drivers/usb-storage" }, .@"virtio-gpu" = .{ .path = "system/drivers/virtio-gpu" }, - .@"vfs-test" = .{ .path = "test/system/services/vfs-test" }, - .@"fat-test" = .{ .path = "test/system/services/fat-test" }, - .@"shared-memory-server" = .{ .path = "test/system/services/shared-memory-server" }, - .@"shared-memory-client" = .{ .path = "test/system/services/shared-memory-client" }, - .@"crash-test" = .{ .path = "test/system/services/crash-test" }, - .@"device-list" = .{ .path = "test/system/services/device-list" }, - .@"pci-cap-test" = .{ .path = "test/system/services/pci-cap-test" }, - .@"iommu-fault-test" = .{ .path = "test/system/services/iommu-fault-test" }, - .@"input-source" = .{ .path = "test/system/services/input-source" }, - .@"input-test" = .{ .path = "test/system/services/input-test" }, - .@"args-echo" = .{ .path = "test/system/services/args-echo" }, - .@"process-test" = .{ .path = "test/system/services/process-test" }, - .@"thread-test" = .{ .path = "test/system/services/thread-test" }, + .@"vfs-test" = .{ .path = "test/system/services/vfs-test", .lazy = true }, + .@"fat-test" = .{ .path = "test/system/services/fat-test", .lazy = true }, + .@"shared-memory-server" = .{ .path = "test/system/services/shared-memory-server", .lazy = true }, + .@"shared-memory-client" = .{ .path = "test/system/services/shared-memory-client", .lazy = true }, + .@"crash-test" = .{ .path = "test/system/services/crash-test", .lazy = true }, + .@"device-list" = .{ .path = "test/system/services/device-list", .lazy = true }, + .@"pci-cap-test" = .{ .path = "test/system/services/pci-cap-test", .lazy = true }, + .@"iommu-fault-test" = .{ .path = "test/system/services/iommu-fault-test", .lazy = true }, + .@"input-source" = .{ .path = "test/system/services/input-source", .lazy = true }, + .@"input-test" = .{ .path = "test/system/services/input-test", .lazy = true }, + .@"args-echo" = .{ .path = "test/system/services/args-echo", .lazy = true }, + .@"process-test" = .{ .path = "test/system/services/process-test", .lazy = true }, + .@"thread-test" = .{ .path = "test/system/services/thread-test", .lazy = true }, // See `zig fetch --save ` for a command-line interface for adding dependencies. //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding diff --git a/docs/build-packages-plan.md b/docs/build-packages-plan.md index 37d5a1c..22b4955 100644 --- a/docs/build-packages-plan.md +++ b/docs/build-packages-plan.md @@ -9,7 +9,9 @@ named "discovery" that the root's -Ddiscovery picks between), and phase 3 (the root split into `build/images.zig` + `build/qemu.zig`; the root `build.zig` is ~460 lines of orchestration, down from ~1,250). Every phase landed green: unit tests, the QEMU suite at parity with main, boot-image file list unchanged. -Still future: `lazyDependency` for image-specific builds (What-this-buys #4). +The `lazyDependency` payoff (What-this-buys #4) is in too: the /test fixtures +and the unselected discovery package are lazy — a build loads and compiles +only what it ships. ## Why @@ -76,7 +78,9 @@ Rules: stability testing in isolation. 3. Adding a binary = adding a directory (source + two small files), not editing three places in a 1,250-line file. -4. Later: `lazyDependency` lets an image target build only what it ships. +4. `lazyDependency` lets an image target build only what it ships: the /test + fixtures resolve only under -Dtest-case, and only the -Ddiscovery-selected + discovery package ever loads. ## Phases