The system now boots off a real FAT32 filesystem on a USB mass-storage device instead of QEMU's synthesized VVFAT drive. A new in-repo image builder formats that filesystem from the FHS boot tree, and both QEMU call sites (the run step and the test harness) attach it as a usb-storage device on the xHCI bus, so every boot exercises the full USB path OVMF -> BOOTX64.efi -> kernel. - tools/make-fat-image.py: a Python 3 stdlib-only FAT32 formatter (mirrors tools/make-initial-ramdisk.py — no external host dependencies). It lays down the boot sector + BPB/EBPB32, FSInfo, backup boot sector, two FATs, and the root/subdir/file cluster chains, emitting long-name entries where a name is not 8.3. Packs the four boot inputs (EFI/BOOT/BOOTX64.efi, system/kernel, system/services/init, boot/initial-ramdisk.img) into their boot paths. A --verify subcommand re-checks the 0xAA55 signature, recomputes the cluster count -> FAT32, and resolves EFI/BOOT/BOOTX64.efi, all with no dependencies. - build.zig: a mk_fat step builds zig-out/danos-usb.img from the four boot artifacts (so changing -Dtest-case rebuilds the image with that kernel), a check-fat-image step runs --verify, and run-x86-64 boots the image on a usb-storage device (if=none,id=bootusb + usb-storage,bus=xhci.0,bootindex=0), keeping usb-kbd/usb-mouse on the same controller. - test/qemu_test.py: the default boot config now boots off danos-usb.img on a usb-storage device (xHCI + usb-kbd + usb-mouse + the boot stick). The seven per-case qemu_extra blocks that added their own qemu-xhci/usb-kbd/usb-mouse (or a VVFAT stick) collided on id=xhci and are removed — the default provides the bus and the boot device. usb-storage and fat-mount now exercise the real FAT32 boot image (usb-storage reads its 0x55AA boot sector; fat mounts it at /mnt/usb). A build_case override lets a case reuse another's kernel, used by a new usb-boot case: an explicit, named boot-from-USB regression guard. Verified: zig build, zig build test, and zig build check-fat-image are green (FAT32, 128992 clusters, BOOTX64.efi present); a broad sequential QEMU sweep passes — smoke, init, vfs, input, device-manager, usb-report, usb-hid, usb-storage, fat-mount, device-list, driver-restart, acpi-report, iommu, orderly-shutdown, usb-boot, dma, msi, initial-ramdisk, args, process — proving the boot switch holds across kernel tests, the full init tree, the USB stack, the FAT mount, and orderly shutdown.
736 lines
42 KiB
Zig
736 lines
42 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
|
|
/// danos is developed against Zig 0.16.x. Pre-1.0 Zig makes breaking API changes
|
|
/// between minor releases, and the .zon's `minimum_zig_version` only enforces a
|
|
/// floor — so reject anything off the 0.16 line to keep the build reproducible.
|
|
fn ensureZigVersion() void {
|
|
const v = builtin.zig_version;
|
|
if (v.major != 0 or v.minor != 16) {
|
|
std.debug.print(
|
|
"danos requires Zig 0.16.x, but this is {d}.{d}.{d}. " ++
|
|
"Zig makes breaking changes between minor releases pre-1.0.\n",
|
|
.{ v.major, v.minor, v.patch },
|
|
);
|
|
std.process.exit(1);
|
|
}
|
|
}
|
|
|
|
/// Return the first path in `candidates` that exists on the build host, else the
|
|
/// first candidate as a fallback so a missing-firmware error still names a
|
|
/// concrete (and, by convention, the primary) path. Used to locate OVMF firmware
|
|
/// across distro/OS layouts without configuration.
|
|
fn firstExisting(io: std.Io, candidates: []const []const u8) []const u8 {
|
|
for (candidates) |path| {
|
|
std.Io.Dir.accessAbsolute(io, path, .{}) catch continue;
|
|
return path;
|
|
}
|
|
return candidates[0];
|
|
}
|
|
|
|
/// A UTC timestamp like "20260708-153045", for naming a per-run artifact so
|
|
/// repeated runs don't clobber each other's logs. Resolved when `build.zig` runs
|
|
/// (i.e. at `zig build` invocation), which is moments before QEMU launches.
|
|
fn timestamp(b: *std.Build) []const u8 {
|
|
const ns = std.Io.Clock.now(.real, b.graph.io).nanoseconds;
|
|
const secs: u64 = @intCast(@divFloor(ns, std.time.ns_per_s));
|
|
const es = std.time.epoch.EpochSeconds{ .secs = secs };
|
|
const yd = es.getEpochDay().calculateYearDay();
|
|
const md = yd.calculateMonthDay();
|
|
const ds = es.getDaySeconds();
|
|
return b.fmt("{d:0>4}{d:0>2}{d:0>2}-{d:0>2}{d:0>2}{d:0>2}", .{
|
|
yd.year,
|
|
md.month.numeric(),
|
|
@as(u32, md.day_index) + 1,
|
|
ds.getHoursIntoDay(),
|
|
ds.getMinutesIntoHour(),
|
|
ds.getSecondsIntoMinute(),
|
|
});
|
|
}
|
|
|
|
/// Build one user-space binary the same way for every program (init, and later
|
|
/// the VFS server + drivers): freestanding, ReleaseSmall, `.large` code model
|
|
/// (the image base is above 4 GiB — smaller models emit 32-bit relocations that
|
|
/// can't reach), linked against the `runtime` runtime library with the shared user
|
|
/// link script. Pinned to LLVM + LLD so the script's PHDRS (segment permissions)
|
|
/// are authoritative — the kernel's W^X user-ELF loader requires exact perms.
|
|
fn addUserBinary(
|
|
b: *std.Build,
|
|
target: std.Build.ResolvedTarget,
|
|
runtime_module: *std.Build.Module,
|
|
posix_module: *std.Build.Module,
|
|
mmio_module: *std.Build.Module,
|
|
xkeyboard_config_module: *std.Build.Module,
|
|
acpi_ids_module: *std.Build.Module,
|
|
name: []const u8,
|
|
root: []const u8,
|
|
) *std.Build.Step.Compile {
|
|
const exe = b.addExecutable(.{
|
|
.name = name,
|
|
.root_module = b.createModule(.{
|
|
.root_source_file = b.path(root),
|
|
.target = target,
|
|
.optimize = .ReleaseSmall,
|
|
.code_model = .large,
|
|
.single_threaded = true,
|
|
.sanitize_c = .off,
|
|
.stack_check = false,
|
|
.stack_protector = false,
|
|
.imports = &.{
|
|
.{ .name = "runtime", .module = runtime_module },
|
|
// POSIX/C compatibility layer, available to any program that wants it
|
|
// (danos-native code uses `runtime` directly). See library/posix/.
|
|
.{ .name = "posix", .module = posix_module },
|
|
// Typed volatile MMIO + memory barriers, for drivers. See library/mmio/.
|
|
.{ .name = "mmio", .module = mmio_module },
|
|
// Keyboard layouts (keycode + modifiers -> keysym/character), available
|
|
// to any program that wants it. See library/xkeyboard-config/.
|
|
.{ .name = "xkeyboard-config", .module = xkeyboard_config_module },
|
|
// ACPI/PnP hardware-ID registry, so drivers name devices
|
|
// (HardwareId.ps2_keyboard) instead of magic "_HID" strings.
|
|
.{ .name = "acpi-ids", .module = acpi_ids_module },
|
|
},
|
|
}),
|
|
});
|
|
exe.setLinkerScript(b.path("library/runtime/user.ld"));
|
|
exe.entry = .{ .symbol_name = "_start" };
|
|
exe.image_base = 0x7000_0000_0000;
|
|
exe.use_llvm = true;
|
|
exe.use_lld = true;
|
|
return exe;
|
|
}
|
|
|
|
pub fn build(b: *std.Build) void {
|
|
ensureZigVersion();
|
|
|
|
const target = b.standardTargetOptions(.{});
|
|
const optimize = b.standardOptimizeOption(.{});
|
|
|
|
// The three shared contracts, each with its own audience so every import
|
|
// declares which one it speaks (no target is set, so each inherits the target of
|
|
// whichever binary imports it). See docs/coding-standards.md.
|
|
// boot-handoff : loader <-> kernel (BootInformation, framebuffer, VM layout)
|
|
// abi : kernel <-> runtime, core (SystemCall, mmap prot flags, page_size)
|
|
// device-abi : kernel <-> user, devices (DeviceDescriptor, DeviceClass, ...)
|
|
const boot_handoff_module = b.addModule("boot-handoff", .{
|
|
.root_source_file = b.path("system/boot-handoff.zig"),
|
|
});
|
|
const abi_module = b.addModule("abi", .{
|
|
.root_source_file = b.path("system/abi.zig"),
|
|
});
|
|
// The devices sub-project's public interface (the flat wire types), exposed as
|
|
// its own module like vfs-protocol — importable by user space, unlike the
|
|
// kernel-internal device model it also feeds (system/devices/device-model.zig).
|
|
const device_abi_module = b.addModule("device-abi", .{
|
|
.root_source_file = b.path("system/devices/device-abi.zig"),
|
|
});
|
|
// PCI class-code decoding (class/subclass/prog-IF -> names). Pure reference data,
|
|
// shared by kernel discovery (the device-tree dump) and any user-space PCI tool.
|
|
const pci_class_module = b.addModule("pci-class", .{
|
|
.root_source_file = b.path("system/devices/pci-class.zig"),
|
|
});
|
|
// ACPI/PnP hardware-ID (_HID) names — the flat analog of pci-class for acpi_device
|
|
// nodes. Also shared reference data.
|
|
// The AML interpreter, a build module so the ring-3 acpi service can run the
|
|
// same parser the kernel does (docs/discovery.md — the shared AML module).
|
|
// Pure Zig, no kernel imports — one source, two builds.
|
|
const aml_module = b.addModule("aml", .{
|
|
.root_source_file = b.path("system/devices/aml/aml.zig"),
|
|
});
|
|
|
|
const acpi_ids_module = b.addModule("acpi-ids", .{
|
|
.root_source_file = b.path("system/devices/acpi-ids.zig"),
|
|
});
|
|
|
|
// The USB device-framework wire ABI (chapter-9 set-up packets, standard +
|
|
// class requests, descriptors) and the USB class-code taxonomy — the flat
|
|
// reference the xHCI bus driver, the USB class drivers, and the device
|
|
// manager's identity matcher all share. Pure data, like pci-class/acpi-ids.
|
|
const usb_abi_module = b.addModule("usb-abi", .{
|
|
.root_source_file = b.path("system/devices/usb-abi.zig"),
|
|
});
|
|
const usb_ids_module = b.addModule("usb-ids", .{
|
|
.root_source_file = b.path("system/devices/usb-ids.zig"),
|
|
});
|
|
// The USB transfer protocol: what a USB class driver says to the xHCI bus
|
|
// driver to drive its device (open / control / interrupt / bulk). A protocol
|
|
// module like vfs-protocol, shared by the bus driver and every class driver.
|
|
const usb_transfer_protocol_module = b.addModule("usb-transfer-protocol", .{
|
|
.root_source_file = b.path("system/drivers/usb-xhci-bus/usb-transfer-protocol.zig"),
|
|
});
|
|
// The block-device protocol: read/write of fixed-size blocks, spoken between a
|
|
// filesystem and a block driver (usb-storage). A protocol module like the rest.
|
|
const block_protocol_module = b.addModule("block-protocol", .{
|
|
.root_source_file = b.path("system/services/block/protocol.zig"),
|
|
});
|
|
|
|
// Kernel tunables (maximum_cpus, stack sizes, tick rate). A dependency-free module of
|
|
// compile-time constants, imported wherever a knob is read; keeps the trade-offs
|
|
// in one place instead of scattered across the tree. See system/parameters.zig.
|
|
const parameters_module = b.addModule("parameters", .{
|
|
.root_source_file = b.path("system/parameters.zig"),
|
|
});
|
|
|
|
// Architecture-specific kernel code (CPU ops, entry, later GDT/IDT/paging).
|
|
// The generic kernel imports this as "architecture" and never names x86_64, so a new
|
|
// architecture is a matter of pointing this module at a different directory.
|
|
const architecture_module = b.addModule("architecture", .{
|
|
.root_source_file = b.path("system/kernel/architecture/x86_64/cpu.zig"),
|
|
.imports = &.{
|
|
.{ .name = "boot-handoff", .module = boot_handoff_module }, // paging uses BootInformation/memory-map + physicalToVirtual
|
|
.{ .name = "abi", .module = abi_module }, // paging works in page_size units
|
|
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus, ist_stack_size, timer_hz
|
|
},
|
|
});
|
|
// CPU-exception stubs — real assembly, since they need cross-symbol
|
|
// jumps/calls that Zig inline asm can't express (see the file's header).
|
|
architecture_module.addAssemblyFile(b.path("system/kernel/architecture/x86_64/isr.s"));
|
|
// The AP bring-up trampoline: 16-/32-/64-bit mode-switch code that can't be
|
|
// inline asm (it runs relocated to a low page, not at its link address).
|
|
architecture_module.addAssemblyFile(b.path("system/kernel/architecture/x86_64/trampoline.s"));
|
|
|
|
// Firmware-agnostic device discovery. The generic kernel imports this as
|
|
// "platform" and asks it to enumerate hardware into a backend-neutral device
|
|
// tree, never naming ACPI (or, later, device-tree) — the same discipline the
|
|
// architecture module applies to CPU code. The backend is selected at runtime from
|
|
// the boot handoff (see system/devices/platform.zig).
|
|
const platform_module = b.addModule("platform", .{
|
|
.root_source_file = b.path("system/devices/platform.zig"),
|
|
.imports = &.{
|
|
.{ .name = "boot-handoff", .module = boot_handoff_module }, // BootInformation (carries the ACPI RSDP), physicalToVirtual
|
|
.{ .name = "abi", .module = abi_module }, // acpi.zig works in page_size units
|
|
.{ .name = "device-abi", .module = device_abi_module }, // device-model's DeviceClass/ResourceKind live here
|
|
.{ .name = "pci-class", .module = pci_class_module }, // decode PCI class codes in the device dump
|
|
.{ .name = "acpi-ids", .module = acpi_ids_module }, // decode ACPI _HID names in the device dump
|
|
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool)
|
|
},
|
|
});
|
|
|
|
// The VFS wire protocol: the vfs sub-project's public interface, exposed as its
|
|
// own module. Both the vfs server and the runtime's file layer (unistd/stdio)
|
|
// depend on this contract by name — neither reaches into the other's files. This
|
|
// is the first "protocol module" (see docs/driver-model.md); usb/block will
|
|
// expose theirs the same way.
|
|
const vfs_protocol_module = b.addModule("vfs-protocol", .{
|
|
.root_source_file = b.path("system/services/vfs/protocol.zig"),
|
|
});
|
|
|
|
// The input wire protocol: the input service's public interface, exposed as its own
|
|
// module the same way vfs-protocol is. Shared by the input service, the runtime's
|
|
// `input` helper (subscribe/publish), and every source and subscriber.
|
|
const input_protocol_module = b.addModule("input-protocol", .{
|
|
.root_source_file = b.path("system/services/input/protocol.zig"),
|
|
});
|
|
|
|
// The danos-native user-space runtime: system_call wrappers, the C-convention
|
|
// heap, IPC helpers, the process start shim, device access. This is the stable
|
|
// application ABI; POSIX compatibility is a separate library on top (see below).
|
|
// Compiled into every user binary (see addUserBinary), so it inherits each exe's
|
|
// `.large` code model — do NOT set a target/code_model here. It imports `abi`
|
|
// for the shared SystemCall numbers / mmap flags, `device-abi` for the device
|
|
// types its `device` helper wraps, and re-exports `vfs-protocol` for the VFS
|
|
// server. It never touches `boot-handoff` — user space has no business with the
|
|
// loader↔kernel handoff.
|
|
const runtime_module = b.addModule("runtime", .{
|
|
.root_source_file = b.path("library/runtime/runtime.zig"),
|
|
.imports = &.{
|
|
.{ .name = "abi", .module = abi_module },
|
|
.{ .name = "device-abi", .module = device_abi_module },
|
|
.{ .name = "vfs-protocol", .module = vfs_protocol_module },
|
|
.{ .name = "input-protocol", .module = input_protocol_module },
|
|
},
|
|
});
|
|
|
|
// The device-manager protocol: hello + (M18.2) tree reports, exposed as its
|
|
// own module like the other protocol modules. Imported through the runtime.
|
|
const device_manager_protocol_module = b.addModule("device-manager-protocol", .{
|
|
.root_source_file = b.path("system/services/device-manager/device-manager-protocol.zig"),
|
|
});
|
|
runtime_module.addImport("device-manager-protocol", device_manager_protocol_module);
|
|
// The USB transfer protocol, so runtime.usb (the class-driver client) can speak
|
|
// it, the way runtime.input speaks the input protocol.
|
|
runtime_module.addImport("usb-transfer-protocol", usb_transfer_protocol_module);
|
|
// The block protocol, so runtime.block (the block-device client) can speak it.
|
|
runtime_module.addImport("block-protocol", block_protocol_module);
|
|
|
|
// The power protocol: system power's domain-named surface (docs/power.md).
|
|
const power_protocol_module = b.addModule("power-protocol", .{
|
|
.root_source_file = b.path("system/services/power/protocol.zig"),
|
|
});
|
|
runtime_module.addImport("power-protocol", power_protocol_module);
|
|
|
|
// Typed volatile MMIO register access + memory-ordering barriers, for drivers on
|
|
// top of an mmio_map grant. Depends only on `builtin` (arch-conditional barriers);
|
|
// no target set, so it inherits each driver's. See library/mmio/mmio.zig.
|
|
const mmio_module = b.addModule("mmio", .{
|
|
.root_source_file = b.path("library/mmio/mmio.zig"),
|
|
});
|
|
|
|
// Keyboard layouts compiled from the X11 xkeyboard-config database into native Zig
|
|
// (keycode + modifiers -> keysym/character). The `layouts` tables are generated by
|
|
// tools/make-xkeyboard-config.py; `xkeyboard-config` is the hand-written API over them.
|
|
// No target set, so each inherits its importer's. See library/xkeyboard-config/.
|
|
const xkb_layouts_module = b.addModule("layouts", .{
|
|
.root_source_file = b.path("library/xkeyboard-config/generated/layouts.zig"),
|
|
});
|
|
const xkeyboard_config_module = b.addModule("xkeyboard-config", .{
|
|
.root_source_file = b.path("library/xkeyboard-config/xkeyboard-config.zig"),
|
|
.imports = &.{
|
|
.{ .name = "layouts", .module = xkb_layouts_module },
|
|
},
|
|
});
|
|
|
|
// The POSIX / C compatibility layer, a separate library layered strictly over the
|
|
// runtime (it calls the runtime's IPC/heap, never system calls directly). This is
|
|
// the one place POSIX/C spellings are allowed verbatim — see docs/coding-standards.md
|
|
// and library/posix/posix.zig.
|
|
const posix_module = b.addModule("posix", .{
|
|
.root_source_file = b.path("library/posix/posix.zig"),
|
|
.imports = &.{
|
|
.{ .name = "runtime", .module = runtime_module },
|
|
.{ .name = "vfs-protocol", .module = vfs_protocol_module },
|
|
},
|
|
});
|
|
|
|
// The initial_ramdisk container format, shared by the kernel (unpacks it) and the
|
|
// build-time packer tools/make-initial-ramdisk.py (produces it). No dependencies.
|
|
const initial_ramdisk_module = b.addModule("initial-ramdisk", .{
|
|
.root_source_file = b.path("system/initial-ramdisk.zig"),
|
|
});
|
|
|
|
// Compile-time configuration the kernel reads as `@import("build_options")`. The
|
|
// QEMU test harness sets -Dtest-case=<name> to run one self-test at boot.
|
|
const test_case = b.option([]const u8, "test-case", "Kernel self-test case to run at boot (see system/kernel/tests.zig)");
|
|
const build_options = b.addOptions();
|
|
build_options.addOption(?[]const u8, "test_case", test_case);
|
|
const build_options_module = build_options.createModule();
|
|
|
|
// --- Kernel: freestanding x86_64 ELF, jumped to by the bootloader ---
|
|
// SSE2 is part of the x86_64 baseline and UEFI leaves it enabled at handoff,
|
|
// so we keep it: disabling it forces soft-float and makes the compiler unable
|
|
// to encode the vector ops that std's formatting/runtime still emit.
|
|
const kernel_target = b.resolveTargetQuery(.{
|
|
.cpu_arch = .x86_64,
|
|
.os_tag = .freestanding,
|
|
.abi = .none,
|
|
});
|
|
|
|
const exe = b.addExecutable(.{
|
|
.name = "kernel",
|
|
.root_module = b.createModule(.{
|
|
.root_source_file = b.path("system/kernel/kernel.zig"),
|
|
.target = kernel_target,
|
|
.optimize = optimize,
|
|
.code_model = .kernel, // kernel runs in the top 2 GiB (higher half)
|
|
.red_zone = false, // interrupts would corrupt the SystemV red zone
|
|
.single_threaded = false, // SMP: the big kernel lock's atomics must be real across cores
|
|
.sanitize_c = .off, // the UBSan runtime needs f128/SSE support we don't provide
|
|
.stack_check = false, // stack-probe calls have no runtime to land in
|
|
.stack_protector = false,
|
|
.imports = &.{
|
|
.{ .name = "boot-handoff", .module = boot_handoff_module },
|
|
.{ .name = "abi", .module = abi_module },
|
|
.{ .name = "device-abi", .module = device_abi_module },
|
|
.{ .name = "architecture", .module = architecture_module },
|
|
.{ .name = "platform", .module = platform_module },
|
|
.{ .name = "parameters", .module = parameters_module },
|
|
.{ .name = "build_options", .module = build_options_module },
|
|
.{ .name = "initial-ramdisk", .module = initial_ramdisk_module },
|
|
},
|
|
}),
|
|
});
|
|
exe.setLinkerScript(b.path("system/kernel/architecture/x86_64/linker.ld"));
|
|
exe.entry = .{ .symbol_name = "_start" };
|
|
// The self-hosted linker ignores parts of the linker script (PHDRS,
|
|
// /DISCARD/, AT(), section order); the higher-half layout depends on the
|
|
// script being authoritative, so pin the kernel to LLVM + LLD.
|
|
exe.use_llvm = true;
|
|
exe.use_lld = true;
|
|
// Higher-half virtual base (matches KERNEL_VIRT_BASE in linker.ld); the
|
|
// linker's AT() clauses give each segment a low physical load address
|
|
// (.text at 1 MiB), which the loader allocates and copies into.
|
|
exe.image_base = 0xFFFFFFFF80100000;
|
|
|
|
// Everything installs into a FHS-shaped zig-out: it IS the danos filesystem *and*
|
|
// the boot volume. Each binary lands at its addressed, leaf-collapsed path — the
|
|
// kernel at zig-out/system/kernel (from system/kernel/kernel.zig), init at
|
|
// zig-out/system/services/init, and so on (see docs/README.md). The bootloader
|
|
// then loads these FHS paths off the volume.
|
|
const kernel_install = b.addInstallArtifact(exe, .{ .dest_dir = .{ .override = .{ .custom = "system" } } });
|
|
b.getInstallStep().dependOn(&kernel_install.step);
|
|
|
|
// --- init: the first user-space program (a system service) ---
|
|
// Built by the shared user-binary recipe (see addUserBinary): freestanding,
|
|
// linked into the kernel's user region against the `runtime` runtime library, and
|
|
// started in ring 3 by the kernel's user-ELF loader.
|
|
const init_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "init", "system/services/init/init.zig");
|
|
const init_install = b.addInstallArtifact(init_exe, .{ .dest_dir = .{ .override = .{ .custom = "system/services" } } });
|
|
b.getInstallStep().dependOn(&init_install.step);
|
|
|
|
// --- initial_ramdisk: a bundle of extra user binaries (VFS server + drivers) ---
|
|
// Each is built by the same user-binary recipe, then packed into one image by
|
|
// the host-side make-initial-ramdisk tool. The bootloader ferries the image to the kernel,
|
|
// which unpacks it and spawns each program (system/initial-ramdisk.zig).
|
|
const vfs_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "vfs", "system/services/vfs/vfs.zig");
|
|
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "vfs-test", "system/services/vfs/vfs-test.zig");
|
|
const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-bus", "system/drivers/ps2-bus/ps2-bus.zig");
|
|
const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig");
|
|
const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig");
|
|
const usb_xhci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-xhci-bus", "system/drivers/usb-xhci-bus/usb-xhci-bus.zig");
|
|
// The xHCI bus driver builds chapter-9 requests and decodes descriptors from
|
|
// usb-abi, and reports each interface's (class,subclass,protocol) identity via
|
|
// usb-ids.packTriple.
|
|
usb_xhci_bus_exe.root_module.addImport("usb-abi", usb_abi_module);
|
|
usb_xhci_bus_exe.root_module.addImport("usb-ids", usb_ids_module);
|
|
usb_xhci_bus_exe.root_module.addImport("usb-transfer-protocol", usb_transfer_protocol_module);
|
|
// The USB HID class drivers: keyboard and mouse. They own no hardware — each
|
|
// opens its device through runtime.usb (the transfer protocol) and publishes to
|
|
// the input service. They build chapter-9 class requests from usb-abi.
|
|
const usb_hid_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-hid-keyboard", "system/drivers/usb-hid/keyboard.zig");
|
|
usb_hid_keyboard_exe.root_module.addImport("usb-abi", usb_abi_module);
|
|
const usb_hid_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-hid-mouse", "system/drivers/usb-hid/mouse.zig");
|
|
usb_hid_mouse_exe.root_module.addImport("usb-abi", usb_abi_module);
|
|
// The USB mass-storage class driver: opens its device via runtime.usb, drives it
|
|
// with Bulk-Only Transport + SCSI, and serves the block protocol under `.block`.
|
|
const usb_storage_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-storage", "system/drivers/usb-storage/usb-storage.zig");
|
|
usb_storage_exe.root_module.addImport("block-protocol", block_protocol_module);
|
|
// The FAT filesystem server: mounts the block device and serves it into the VFS
|
|
// at /mnt/usb. Its engine (engine.zig / on-disk.zig) is imported relatively.
|
|
const fat_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "fat", "system/services/fat/fat.zig");
|
|
const fat_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "fat-test", "system/services/fat/fat-test.zig");
|
|
const pci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "pci-bus", "system/drivers/pci-bus/pci-bus.zig");
|
|
// The PCI bus driver decodes each function's class triple to human names in its
|
|
// boot log (class/subclass/prog-IF), so pull in the shared pci-class reference.
|
|
pci_bus_exe.root_module.addImport("pci-class", pci_class_module);
|
|
// A test fixture, not a real driver: hellos to the device manager, then faults —
|
|
// what the driver-restart scenario drives the crash-loop cap with.
|
|
const crash_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "crash-test", "system/services/crash-test/crash-test.zig");
|
|
const device_list_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-list", "system/services/device-list/device-list.zig");
|
|
// 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). Both are placeholders until M20.1 (acpi) and the
|
|
// ARM bring-up (fdt).
|
|
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_source: []const u8 = switch (discovery) {
|
|
.acpi => "system/services/acpi/acpi.zig",
|
|
.fdt => "system/services/fdt/fdt.zig",
|
|
};
|
|
const discovery_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "discovery", discovery_source);
|
|
if (discovery == .acpi) discovery_exe.root_module.addImport("aml", aml_module);
|
|
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
|
// Names the xHCI PCI class triple from the shared taxonomy instead of a bare 0x0C0330.
|
|
device_manager_exe.root_module.addImport("pci-class", pci_class_module);
|
|
// The manager matches reported USB interfaces by their (class,subclass,protocol)
|
|
// triple (usbDriverForIdentity), built from the named usb-ids codes.
|
|
device_manager_exe.root_module.addImport("usb-ids", usb_ids_module);
|
|
// 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 = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "input", "system/services/input/input.zig");
|
|
const input_source_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "input-source", "system/services/input-source/input-source.zig");
|
|
const input_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "input-test", "system/services/input-test/input-test.zig");
|
|
const args_echo_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "args-echo", "system/services/args-echo/args-echo.zig");
|
|
const process_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "process-test", "system/services/process-test/process-test.zig");
|
|
|
|
// Pack the user binaries into the initial_ramdisk image with the host-side Python tool
|
|
// (the container format is trivial, and Python sidesteps std API churn). Args:
|
|
// make-initial-ramdisk.py <out> [<name> <file>]... — one name/file pair per binary.
|
|
const mk_run = b.addSystemCommand(&.{"python3"});
|
|
mk_run.addFileArg(b.path("tools/make-initial-ramdisk.py"));
|
|
const initial_ramdisk_img = mk_run.addOutputFileArg("initial-ramdisk.img");
|
|
mk_run.addArg("vfs");
|
|
mk_run.addFileArg(vfs_exe.getEmittedBin());
|
|
mk_run.addArg("vfs-test");
|
|
mk_run.addFileArg(vfstest_exe.getEmittedBin());
|
|
mk_run.addArg("ps2-bus");
|
|
mk_run.addFileArg(ps2_bus_exe.getEmittedBin());
|
|
mk_run.addArg("ps2-keyboard");
|
|
mk_run.addFileArg(ps2_keyboard_exe.getEmittedBin());
|
|
mk_run.addArg("ps2-mouse");
|
|
mk_run.addFileArg(ps2_mouse_exe.getEmittedBin());
|
|
mk_run.addArg("usb-xhci-bus");
|
|
mk_run.addFileArg(usb_xhci_bus_exe.getEmittedBin());
|
|
mk_run.addArg("usb-hid-keyboard");
|
|
mk_run.addFileArg(usb_hid_keyboard_exe.getEmittedBin());
|
|
mk_run.addArg("usb-hid-mouse");
|
|
mk_run.addFileArg(usb_hid_mouse_exe.getEmittedBin());
|
|
mk_run.addArg("usb-storage");
|
|
mk_run.addFileArg(usb_storage_exe.getEmittedBin());
|
|
mk_run.addArg("fat");
|
|
mk_run.addFileArg(fat_exe.getEmittedBin());
|
|
mk_run.addArg("fat-test");
|
|
mk_run.addFileArg(fat_test_exe.getEmittedBin());
|
|
mk_run.addArg("pci-bus");
|
|
mk_run.addFileArg(pci_bus_exe.getEmittedBin());
|
|
mk_run.addArg("crash-test");
|
|
mk_run.addFileArg(crash_test_exe.getEmittedBin());
|
|
mk_run.addArg("device-list");
|
|
mk_run.addFileArg(device_list_exe.getEmittedBin());
|
|
mk_run.addArg("discovery");
|
|
mk_run.addFileArg(discovery_exe.getEmittedBin());
|
|
mk_run.addArg("device-manager");
|
|
mk_run.addFileArg(device_manager_exe.getEmittedBin());
|
|
mk_run.addArg("input");
|
|
mk_run.addFileArg(input_exe.getEmittedBin());
|
|
mk_run.addArg("input-source");
|
|
mk_run.addFileArg(input_source_exe.getEmittedBin());
|
|
mk_run.addArg("input-test");
|
|
mk_run.addFileArg(input_test_exe.getEmittedBin());
|
|
mk_run.addArg("args-echo");
|
|
mk_run.addFileArg(args_echo_exe.getEmittedBin());
|
|
mk_run.addArg("process-test");
|
|
mk_run.addFileArg(process_test_exe.getEmittedBin());
|
|
|
|
// Also install the packed binaries to their FHS homes, so zig-out is a true image
|
|
// of the filesystem — even though at boot they arrive inside the initial-ramdisk.
|
|
for ([_]struct { *std.Build.Step.Compile, []const u8 }{
|
|
.{ vfs_exe, "system/services" },
|
|
.{ device_manager_exe, "system/services" },
|
|
.{ input_exe, "system/services" },
|
|
.{ ps2_bus_exe, "system/drivers" },
|
|
.{ ps2_keyboard_exe, "system/drivers" },
|
|
.{ ps2_mouse_exe, "system/drivers" },
|
|
.{ usb_xhci_bus_exe, "system/drivers" },
|
|
.{ usb_hid_keyboard_exe, "system/drivers" },
|
|
.{ usb_hid_mouse_exe, "system/drivers" },
|
|
.{ usb_storage_exe, "system/drivers" },
|
|
.{ fat_exe, "system/services" },
|
|
}) |entry| {
|
|
const step = b.addInstallArtifact(entry[0], .{ .dest_dir = .{ .override = .{ .custom = entry[1] } } });
|
|
b.getInstallStep().dependOn(&step.step);
|
|
}
|
|
|
|
// The initial-ramdisk itself installs to /boot (with the loaders).
|
|
const initial_ramdisk_install = b.addInstallFile(initial_ramdisk_img, "boot/initial-ramdisk.img");
|
|
b.getInstallStep().dependOn(&initial_ramdisk_install.step);
|
|
|
|
// Boot methods live in boot/, one per way of getting the kernel running.
|
|
// Each is its own binary/entry (a loader is built for its own target); today
|
|
// that's UEFI for x86-64, with room for e.g. a device-tree path for the Pis.
|
|
const efiexe = b.addExecutable(.{
|
|
.name = "BOOTX64",
|
|
.root_module = b.createModule(.{
|
|
.root_source_file = b.path("boot/efi.zig"),
|
|
.target = b.resolveTargetQuery(.{
|
|
.cpu_arch = .x86_64,
|
|
.os_tag = .uefi,
|
|
}),
|
|
.optimize = optimize,
|
|
.imports = &.{
|
|
// The bootloader speaks only the handoff contract — never the user ABI.
|
|
.{ .name = "boot-handoff", .module = boot_handoff_module },
|
|
},
|
|
}),
|
|
});
|
|
|
|
// UEFI firmware requires the removable-media loader at exactly \EFI\BOOT\BOOTX64.efi,
|
|
// so that path is fixed by the firmware (it is /boot's EFI stub, conceptually).
|
|
const efi_install = b.addInstallArtifact(efiexe, .{ .dest_dir = .{ .override = .{ .custom = "EFI/BOOT" } } });
|
|
b.getInstallStep().dependOn(&efi_install.step);
|
|
|
|
// --- danos-usb.img: the bootable FAT32 USB image ---
|
|
// Format a real FAT32 image (the in-repo Python builder, no external tools)
|
|
// holding exactly what the firmware and bootloader need off the ESP: the EFI
|
|
// stub, the kernel, init, and the initial-ramdisk. QEMU presents this image as
|
|
// a USB mass-storage device the guest boots from (see run-x86-64 and the test
|
|
// harness), and the danos fat driver mounts the same image at /mnt/usb.
|
|
const mk_fat = b.addSystemCommand(&.{"python3"});
|
|
mk_fat.addFileArg(b.path("tools/make-fat-image.py"));
|
|
const fat_image = mk_fat.addOutputFileArg("danos-usb.img");
|
|
mk_fat.addArg("64"); // MiB
|
|
mk_fat.addArg("EFI/BOOT/BOOTX64.efi");
|
|
mk_fat.addFileArg(efiexe.getEmittedBin());
|
|
mk_fat.addArg("system/kernel");
|
|
mk_fat.addFileArg(exe.getEmittedBin());
|
|
mk_fat.addArg("system/services/init");
|
|
mk_fat.addFileArg(init_exe.getEmittedBin());
|
|
mk_fat.addArg("boot/initial-ramdisk.img");
|
|
mk_fat.addFileArg(initial_ramdisk_img);
|
|
const fat_image_install = b.addInstallFile(fat_image, "danos-usb.img");
|
|
b.getInstallStep().dependOn(&fat_image_install.step);
|
|
|
|
// `zig build check-fat-image` — validate the produced image is a real FAT32
|
|
// with the EFI stub present (the builder's own --verify, no external tools).
|
|
const check_fat = b.addSystemCommand(&.{"python3"});
|
|
check_fat.addFileArg(b.path("tools/make-fat-image.py"));
|
|
check_fat.addArg("--verify");
|
|
check_fat.addFileArg(fat_image);
|
|
const check_fat_step = b.step("check-fat-image", "Verify the FAT32 USB image is valid and bootable");
|
|
check_fat_step.dependOn(&check_fat.step);
|
|
|
|
// --- run-x86-64: boot the x86-64 kernel in QEMU via UEFI/OVMF ---
|
|
// Firmware lives in different places per OS/distro, so probe the known
|
|
// layouts (Architecture, Debian/Ubuntu, Fedora, macOS Homebrew) and use the first
|
|
// that exists. Override with -Dovmf-code / -Dovmf-vars if yours is elsewhere.
|
|
const ovmf_code = b.option(
|
|
[]const u8,
|
|
"ovmf-code",
|
|
"Path to the OVMF_CODE firmware image",
|
|
) orelse firstExisting(b.graph.io, &.{
|
|
"/usr/share/edk2/x64/OVMF_CODE.4m.fd", // Architecture
|
|
"/usr/share/OVMF/OVMF_CODE_4M.fd", // Debian/Ubuntu
|
|
"/usr/share/OVMF/OVMF_CODE.fd", // older Debian/Ubuntu
|
|
"/usr/share/edk2-ovmf/x64/OVMF_CODE.fd", // Fedora
|
|
"/opt/homebrew/share/qemu/edk2-x86_64-code.fd", // macOS Homebrew (Apple Silicon)
|
|
"/usr/local/share/qemu/edk2-x86_64-code.fd", // macOS Homebrew (Intel)
|
|
});
|
|
const ovmf_vars = b.option(
|
|
[]const u8,
|
|
"ovmf-vars",
|
|
"Path to the OVMF_VARS firmware image (a writable copy is made)",
|
|
) orelse firstExisting(b.graph.io, &.{
|
|
"/usr/share/edk2/x64/OVMF_VARS.4m.fd", // Architecture
|
|
"/usr/share/OVMF/OVMF_VARS_4M.fd", // Debian/Ubuntu
|
|
"/usr/share/OVMF/OVMF_VARS.fd", // older Debian/Ubuntu
|
|
"/usr/share/edk2-ovmf/x64/OVMF_VARS.fd", // Fedora
|
|
"/opt/homebrew/share/qemu/edk2-i386-vars.fd", // macOS Homebrew (Apple Silicon)
|
|
"/usr/local/share/qemu/edk2-i386-vars.fd", // macOS Homebrew (Intel)
|
|
});
|
|
|
|
// The FHS zig-out (installed above) *is* the boot volume — no separate ESP to
|
|
// assemble. QEMU presents it to the guest as a FAT drive below.
|
|
|
|
// The firmware needs to write NVRAM, so give it a writable copy of the vars.
|
|
const vars_copy = b.addSystemCommand(&.{ "cp", "-f", ovmf_vars });
|
|
const vars_out = vars_copy.addOutputFileArg("OVMF_VARS.4m.fd");
|
|
|
|
const run_efi = b.addSystemCommand(&.{
|
|
"qemu-system-x86_64",
|
|
"-device",
|
|
"qemu-xhci,id=xhci",
|
|
"-device",
|
|
"usb-mouse,bus=xhci.0",
|
|
"-device",
|
|
"usb-kbd,bus=xhci.0",
|
|
// "-usb",
|
|
// "-device",
|
|
// "usb-ehci,id=ehci",
|
|
// "-device",
|
|
// "usb-tablet,bus=usb-bus.0",
|
|
// "-device",
|
|
// "usb-mouse,bus=ehci.0",
|
|
"-machine",
|
|
"q35",
|
|
"-m",
|
|
"128M",
|
|
"-drive",
|
|
b.fmt("if=pflash,format=raw,readonly=on,file={s}", .{ovmf_code}),
|
|
});
|
|
run_efi.addArg("-drive");
|
|
run_efi.addPrefixedFileArg("if=pflash,format=raw,file=", vars_out);
|
|
// Boot off the FAT32 USB image: a mass-storage device on the same xHCI bus as
|
|
// the keyboard and mouse. OVMF finds \EFI\BOOT\BOOTX64.efi on it and boots.
|
|
run_efi.addArg("-drive");
|
|
run_efi.addPrefixedFileArg("if=none,id=bootusb,format=raw,file=", fat_image);
|
|
run_efi.addArgs(&.{
|
|
"-device",
|
|
"usb-storage,bus=xhci.0,drive=bootusb,removable=on,bootindex=0",
|
|
"-net",
|
|
"none",
|
|
// Emulated display advertising 1280x720 as its native (EDID preferred)
|
|
// resolution, so the kernel's native-resolution switch has something to
|
|
// find. `-vga none` avoids a second, default adapter.
|
|
"-vga",
|
|
"none",
|
|
"-device",
|
|
"VGA,edid=on,xres=1280,yres=720",
|
|
});
|
|
// Capture the guest's serial0 (danos's machine-readable log) to the qemu-test
|
|
// scratch area — a dev/host artifact, kept out of the FHS boot volume we mount.
|
|
// (/var/log/system is reserved for the kernel's own logging system later.) One
|
|
// timestamped file per run.
|
|
const log_dir = b.fmt("{s}/qemu-test", .{b.install_path});
|
|
const make_log_dir = b.addSystemCommand(&.{ "mkdir", "-p", log_dir });
|
|
const serial_log = b.fmt("{s}/run-x86-64-serial0-{s}.log", .{ log_dir, timestamp(b) });
|
|
run_efi.addArgs(&.{ "-serial", b.fmt("file:{s}", .{serial_log}) });
|
|
// The whole FHS zig-out must be installed (and the scratch dir created) before we mount it.
|
|
run_efi.step.dependOn(b.getInstallStep());
|
|
run_efi.step.dependOn(&make_log_dir.step);
|
|
|
|
const run_efi_step = b.step("run-x86-64", "Boot the x86-64 kernel in QEMU (UEFI/OVMF); serial0 is logged to zig-out/qemu-test/run-x86-64-serial0-<timestamp>.log");
|
|
run_efi_step.dependOn(&run_efi.step);
|
|
|
|
// const run_cmd = b.addRunArtifact(exe);
|
|
// const run_step = b.step("run", "Run the app");
|
|
// run_step.dependOn(&run_cmd.step);
|
|
// run_cmd.step.dependOn(b.getInstallStep());
|
|
//
|
|
// if (b.args) |args| {
|
|
// run_cmd.addArgs(args);
|
|
// }
|
|
|
|
// Tests run on the host. The kernel and bootloader target freestanding/UEFI
|
|
// and can't be executed natively, so only the shared contracts are unit-tested
|
|
// here (compiled for the host rather than inheriting a freestanding target) —
|
|
// which also compile-checks that the three-way split stays self-consistent.
|
|
const test_step = b.step("test", "Run tests");
|
|
for ([_][]const u8{
|
|
"system/boot-handoff.zig",
|
|
"system/abi.zig",
|
|
"system/devices/device-abi.zig",
|
|
"system/devices/pci-class.zig", // class/subclass/prog-IF name decoding
|
|
"system/devices/acpi-ids.zig", // _HID name decoding
|
|
"system/devices/aml/aml.zig", // AML parse + interpret, incl. Notify dispatch (M21)
|
|
"system/devices/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings
|
|
"system/devices/usb-ids.zig", // class/subclass/protocol code assignments
|
|
"library/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)
|
|
"system/services/vfs/path.zig", // mount-prefix path matching
|
|
"system/services/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
|
|
}) |root| {
|
|
const mod_tests = b.addTest(.{
|
|
.root_module = b.createModule(.{
|
|
.root_source_file = b.path(root),
|
|
.target = target,
|
|
.optimize = optimize,
|
|
}),
|
|
});
|
|
test_step.dependOn(&b.addRunArtifact(mod_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);
|
|
|
|
// 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/runtime/time.zig"),
|
|
.target = target,
|
|
.optimize = optimize,
|
|
.imports = &.{
|
|
.{ .name = "abi", .module = abi_module },
|
|
},
|
|
}),
|
|
});
|
|
test_step.dependOn(&b.addRunArtifact(time_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" });
|
|
const gen_xkb_step = b.step("gen-xkeyboard-config", "Regenerate library/xkeyboard-config/generated from the vendored data");
|
|
gen_xkb_step.dependOn(&gen_xkb.step);
|
|
}
|