diff --git a/build.zig b/build.zig index c474769..be36e5e 100644 --- a/build.zig +++ b/build.zig @@ -96,6 +96,47 @@ fn addKernel( return exe; } +/// One row of the production ship table: which package, which of its +/// artifacts, and the FHS boot path. For most binaries all three share one +/// name; the helpers below make a row from just that name. +const ShipRow = struct { path: []const u8, package: []const u8, artifact: []const u8 }; + +fn service(comptime name: []const u8) ShipRow { + return .{ .path = "system/services/" ++ name, .package = name, .artifact = name }; +} +fn driver(comptime name: []const u8) ShipRow { + return .{ .path = "system/drivers/" ++ name, .package = name, .artifact = name }; +} +/// An extra artifact of a multi-binary driver package (ps2-bus, usb-hid), +/// bundled at its own flattened /system/drivers path. +fn driverArtifact(comptime package: []const u8, comptime artifact: []const u8) ShipRow { + return .{ .path = "system/drivers/" ++ artifact, .package = package, .artifact = artifact }; +} + +/// The production ship table — what a plain `zig build` image contains, +/// beyond the specials the build fn adds around it (init, discovery, the +/// /etc data files; the /test fixtures join only under -Dtest-case). +/// Selecting what goes into a build = selecting rows: a package in no row is +/// not just unshipped, its build file is never even loaded +/// (docs/build-packages-plan.md). +const production_ship = [_]ShipRow{ + service("fat"), + service("display"), + service("display-demo"), + service("device-manager"), + service("input"), + service("logger"), + driver("pci-bus"), + driver("ps2-bus"), + driverArtifact("ps2-bus", "ps2-keyboard"), + driverArtifact("ps2-bus", "ps2-mouse"), + driver("usb-xhci-bus"), + driverArtifact("usb-hid", "usb-hid-keyboard"), + driverArtifact("usb-hid", "usb-hid-mouse"), + driver("usb-storage"), + driver("virtio-gpu"), +}; + pub fn build(b: *std.Build) void { ensureZigVersion(); @@ -211,48 +252,27 @@ pub fn build(b: *std.Build) void { const exe = addKernel(b, kernel_target, optimize, kernel_modules, test_case, serial); const exe_serial = addKernel(b, kernel_target, optimize, kernel_modules, test_case, true); - // --- the user-space binaries, every one of them a package --- - // Binary packages (docs/build-packages-plan.md, phase 2): each binary - // builds itself against the domain packages via build-support's shared - // recipe, started in ring 3 by the kernel's user-ELF loader like always; - // the root build just takes artifacts for the boot image. init receives - // the root's -Dserial as a dependency option (its liveness heartbeat is a - // serial/test-build diagnostic the QEMU harness asserts on; a flashable - // image leaves it out). - const init_exe = b.dependency("init", .{ .serial = serial }).artifact("init"); - - // --- 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). - // (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. - const ps2_bus_package = b.dependency("ps2-bus", .{}); - const ps2_bus_exe = ps2_bus_package.artifact("ps2-bus"); - const ps2_keyboard_exe = ps2_bus_package.artifact("ps2-keyboard"); - const ps2_mouse_exe = ps2_bus_package.artifact("ps2-mouse"); - const usb_xhci_bus_exe = b.dependency("usb-xhci-bus", .{}).artifact("usb-xhci-bus"); - const usb_hid_package = b.dependency("usb-hid", .{}); - const usb_hid_keyboard_exe = usb_hid_package.artifact("usb-hid-keyboard"); - const usb_hid_mouse_exe = usb_hid_package.artifact("usb-hid-mouse"); - const usb_storage_package = b.dependency("usb-storage", .{}); - const usb_storage_exe = usb_storage_package.artifact("usb-storage"); - // The FAT filesystem server and the display stack, each its own package - // (fat's and display's unit tests ride along in their packages). - const fat_package = b.dependency("fat", .{}); - const fat_exe = fat_package.artifact("fat"); - const display_package = b.dependency("display", .{}); - const display_exe = display_package.artifact("display"); - 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"); - // 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"); + // --- what ships: the boot tree --- + // 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 its + // path on the image, and the EFI loader walks /system and /test at boot and + // builds the in-RAM initial_ramdisk table from the trees — the volume's file + // structure is the single source of truth. Entry names (and hence argv[0] and + // task names) are these paths with a leading slash. + // + // The uniform rows live in `production_ship` (the table above `build`); + // spelled out here are only the genuinely non-uniform entries: init + // (receives the root's -Dserial as a dependency option — its liveness + // heartbeat is a serial/test-build diagnostic the QEMU harness asserts + // on; a flashable image leaves it out), discovery (the -Ddiscovery pick), + // and the /etc data files. Each binary builds itself against the domain + // packages via build-support's shared recipe; the root just takes + // artifacts (docs/build-packages-plan.md). + var bundled_list: std.ArrayListUnmanaged(images.BundledBinary) = .empty; + bundled_list.append(b.allocator, .{ + .path = "system/services/init", + .binary = b.dependency("init", .{ .serial = serial }).artifact("init").getEmittedBin(), + }) catch @panic("OOM"); // 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. @@ -269,59 +289,33 @@ pub fn build(b: *std.Build) void { .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 logger_exe = b.dependency("logger", .{}).artifact("logger"); - - // 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 - // path on the image, and the EFI loader walks /system and /test at boot and - // builds the in-RAM initial_ramdisk table from the trees — the volume's file - // structure is the single source of truth. Entry names (and hence argv[0] and - // task names) are these paths with a leading slash. Test fixtures mirror their - // repo home: test/system/services/ in the source tree IS the boot path. - // init's boot service list is data (/etc/init.csv). -Ddiagnose selects the - // variant that omits the display stack (so the kernel's boot transcript stays - // on screen); both are bundled at the same /etc/init.csv path. + bundled_list.append(b.allocator, .{ + .path = "system/services/discovery", + .binary = discovery_exe.getEmittedBin(), + }) catch @panic("OOM"); + // The ship table: every uniform row, one line each. + for (production_ship) |row| { + bundled_list.append(b.allocator, .{ + .path = row.path, + .binary = b.dependency(row.package, .{}).artifact(row.artifact).getEmittedBin(), + }) catch @panic("OOM"); + } + // Data files, not binaries: packing them under /etc makes the kernel + // auto-mount /etc as a read-only initrd tree (system/kernel/vfs.zig + // setInitialRamdisk) — the device manager reads its registry and init its + // service list with no filesystem service running. -Ddiagnose selects the + // init.csv variant that omits the display stack (so the kernel's boot + // transcript stays on screen); both bundle at the same /etc/init.csv path. const init_csv_source = if (diagnose) "etc/init-diagnose.csv" else "etc/init.csv"; - const production_bundled = [_]images.BundledBinary{ - .{ .path = "system/services/init", .binary = init_exe.getEmittedBin() }, - .{ .path = "system/services/fat", .binary = fat_exe.getEmittedBin() }, - .{ .path = "system/services/display", .binary = display_exe.getEmittedBin() }, - .{ .path = "system/services/display-demo", .binary = display_demo_exe.getEmittedBin() }, - .{ .path = "system/services/device-manager", .binary = device_manager_exe.getEmittedBin() }, - .{ .path = "system/services/input", .binary = input_exe.getEmittedBin() }, - .{ .path = "system/services/discovery", .binary = discovery_exe.getEmittedBin() }, - .{ .path = "system/services/logger", .binary = logger_exe.getEmittedBin() }, - // A data file, not a binary: the device registry the manager reads at boot. - // Packing it under /etc makes the kernel auto-mount /etc as a read-only - // initrd tree (system/kernel/vfs.zig setInitialRamdisk), so the manager can - // fs.open("/etc/devices.csv") with no filesystem service running. - .{ .path = "etc/devices.csv", .binary = b.path("etc/devices.csv") }, - // init's service list, likewise read from the kernel-served initrd /etc. - .{ .path = "etc/init.csv", .binary = b.path(init_csv_source) }, - .{ .path = "system/drivers/ps2-bus", .binary = ps2_bus_exe.getEmittedBin() }, - .{ .path = "system/drivers/ps2-keyboard", .binary = ps2_keyboard_exe.getEmittedBin() }, - .{ .path = "system/drivers/ps2-mouse", .binary = ps2_mouse_exe.getEmittedBin() }, - .{ .path = "system/drivers/usb-xhci-bus", .binary = usb_xhci_bus_exe.getEmittedBin() }, - .{ .path = "system/drivers/usb-hid-keyboard", .binary = usb_hid_keyboard_exe.getEmittedBin() }, - .{ .path = "system/drivers/usb-hid-mouse", .binary = usb_hid_mouse_exe.getEmittedBin() }, - .{ .path = "system/drivers/usb-storage", .binary = usb_storage_exe.getEmittedBin() }, - .{ .path = "system/drivers/virtio-gpu", .binary = virtio_gpu_exe.getEmittedBin() }, - .{ .path = "system/drivers/pci-bus", .binary = pci_bus_exe.getEmittedBin() }, - }; + bundled_list.append(b.allocator, .{ .path = "etc/devices.csv", .binary = b.path("etc/devices.csv") }) catch @panic("OOM"); + bundled_list.append(b.allocator, .{ .path = "etc/init.csv", .binary = b.path(init_csv_source) }) catch @panic("OOM"); // A no-option build assumes neither -Dtest-case nor -Ddiagnose: it ships the // 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"); + // boot volume. They are LAZY dependencies too. 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. if (test_case != null) for ([_][]const u8{ "vfs-test", // the user-space VFS round-trip client "fat-test", @@ -419,12 +413,12 @@ pub fn build(b: *std.Build) void { protocol_library, csv_library, xkeyboard_config_library, - fat_package, - display_package, - ps2_bus_package, - usb_hid_package, - usb_storage_package, - virtio_gpu_package, + b.dependency("fat", .{}), + b.dependency("display", .{}), + b.dependency("ps2-bus", .{}), + b.dependency("usb-hid", .{}), + b.dependency("usb-storage", .{}), + b.dependency("virtio-gpu", .{}), }) |package| { test_step.dependOn(&package.builder.top_level_steps.get("test").?.step); }