457 lines
24 KiB
Zig
457 lines
24 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
// The danos build API (docs/build-packages-plan.md): the shared user-binary
|
|
// recipe lives in the build-support package; this root build orchestrates —
|
|
// what ships (the bundled list), the kernel + loader, and the test aggregate.
|
|
// Image assembly and the QEMU run steps live beside it in build/.
|
|
const build_support = @import("build-support");
|
|
const images = @import("build/images.zig");
|
|
const qemu = @import("build/qemu.zig");
|
|
|
|
/// 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);
|
|
}
|
|
}
|
|
|
|
/// The modules the kernel imports, gathered once so both kernel variants (the
|
|
/// installed one and the serial-enabled one `run-x86-64` boots) are built from
|
|
/// the same set. `build_options` is *not* here — it carries `serial`/`test_case`,
|
|
/// which differ per variant, so `addKernel` builds it fresh each time.
|
|
const KernelModules = struct {
|
|
boot_handoff: *std.Build.Module,
|
|
abi: *std.Build.Module,
|
|
device_abi: *std.Build.Module,
|
|
architecture: *std.Build.Module,
|
|
platform: *std.Build.Module,
|
|
parameters: *std.Build.Module,
|
|
initial_ramdisk: *std.Build.Module,
|
|
};
|
|
|
|
/// Build the freestanding x86_64 kernel ELF. Factored so we can build it twice
|
|
/// from one recipe: the installed/flashable image (serial off by default) and the
|
|
/// serial-enabled variant `run-x86-64` boots — they differ only in the `serial`
|
|
/// build option baked into `build_options`.
|
|
fn addKernel(
|
|
b: *std.Build,
|
|
kernel_target: std.Build.ResolvedTarget,
|
|
optimize: std.builtin.OptimizeMode,
|
|
modules: KernelModules,
|
|
test_case: ?[]const u8,
|
|
serial: bool,
|
|
) *std.Build.Step.Compile {
|
|
// Compile-time configuration the kernel reads as `@import("build_options")`:
|
|
// the QEMU harness's -Dtest-case, and whether the serial log sink is compiled
|
|
// in (see the -Dserial option). Built per variant since `serial` differs.
|
|
const build_options = b.addOptions();
|
|
build_options.addOption(?[]const u8, "test_case", test_case);
|
|
build_options.addOption(bool, "serial", serial);
|
|
const build_options_module = build_options.createModule();
|
|
|
|
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,
|
|
.strip = optimize != .Debug, // DWARF info doubles the flashable image; keep it only for debug builds
|
|
.imports = &.{
|
|
.{ .name = "boot-handoff", .module = modules.boot_handoff },
|
|
.{ .name = "abi", .module = modules.abi },
|
|
.{ .name = "device-abi", .module = modules.device_abi },
|
|
.{ .name = "architecture", .module = modules.architecture },
|
|
.{ .name = "platform", .module = modules.platform },
|
|
.{ .name = "parameters", .module = modules.parameters },
|
|
.{ .name = "build_options", .module = build_options_module },
|
|
.{ .name = "initial-ramdisk", .module = modules.initial_ramdisk },
|
|
},
|
|
}),
|
|
});
|
|
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;
|
|
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
|
|
/// /system/configuration 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();
|
|
|
|
const target = b.standardTargetOptions(.{});
|
|
const optimize = b.standardOptimizeOption(.{});
|
|
|
|
// The library domain packages (docs/build-packages-plan.md, phase 1): each
|
|
// domain owns a build.zig/zon that wires and exports its modules, and this
|
|
// root build is a consumer — a library interface change now happens in the
|
|
// domain's own build file, not here. The per-module commentary lives with
|
|
// each domain's build.zig.
|
|
const kernel_library = b.dependency("kernel", .{});
|
|
const device_library = b.dependency("device", .{});
|
|
const client_library = b.dependency("client", .{});
|
|
const protocol_library = b.dependency("protocol", .{});
|
|
const csv_library = b.dependency("csv", .{});
|
|
const xkeyboard_config_library = b.dependency("xkeyboard-config", .{});
|
|
|
|
// 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, ...)
|
|
// boot-handoff stays a root module (a system/ source the loader <-> kernel
|
|
// pair speaks); abi is exported by the kernel library package (its source
|
|
// also lives in system/), device-abi by the device package.
|
|
const boot_handoff_module = b.addModule("boot-handoff", .{
|
|
.root_source_file = b.path("system/boot-handoff.zig"),
|
|
});
|
|
const abi_module = kernel_library.module("abi");
|
|
const device_abi_module = device_library.module("device-abi");
|
|
|
|
// 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/kernel/platform.zig).
|
|
const platform_module = b.addModule("platform", .{
|
|
.root_source_file = b.path("system/kernel/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 = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool)
|
|
},
|
|
});
|
|
|
|
|
|
|
|
// 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
|
|
// 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)");
|
|
// The serial-console log sink. Off by default: a real machine often has no
|
|
// working legacy COM1, and the boot log is kept in RAM (klog) and flushed to
|
|
// disk instead — serial is now only a QEMU convenience. `run-x86-64` and the
|
|
// QEMU test harness (test/qemu_test.py, which asserts on serial markers) turn
|
|
// it on; a flashable `zig build` image leaves it out. See serial.zig.
|
|
const serial = b.option(bool, "serial", "Compile the serial-console log sink into the kernel (default: off; run-x86-64 and the test harness enable it)") orelse false;
|
|
// The diagnose boot: init skips the display service (and demo), so the
|
|
// on-screen boot transcript is never suppressed — the full timestamped
|
|
// timeline stays on the screen for real-hardware debugging by eye.
|
|
const diagnose = b.option(bool, "diagnose", "Boot without the display service so the timestamped boot transcript stays on screen (real-hardware debugging)") orelse false;
|
|
|
|
// --- Kernel: freestanding x86_64 ELF, jumped to by the bootloader ---
|
|
// (See build-support/build.zig for why SSE2 stays enabled.)
|
|
const kernel_target = build_support.freestandingTarget(b);
|
|
|
|
const kernel_modules = KernelModules{
|
|
.boot_handoff = boot_handoff_module,
|
|
.abi = abi_module,
|
|
.device_abi = device_abi_module,
|
|
.architecture = architecture_module,
|
|
.platform = platform_module,
|
|
.parameters = parameters_module,
|
|
.initial_ramdisk = initial_ramdisk_module,
|
|
};
|
|
// The installed/flashable kernel: serial follows -Dserial (off by default).
|
|
// The serial-enabled twin is what `run-x86-64` boots — built lazily (only
|
|
// when its image is requested), never installed.
|
|
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);
|
|
|
|
// --- 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 /system/configuration 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.
|
|
// 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 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.lazyDependency("acpi", .{}) orelse @panic("system/services/acpi is missing")).artifact("discovery"),
|
|
.fdt => (b.lazyDependency("fdt", .{}) orelse @panic("system/services/fdt is missing")).artifact("discovery"),
|
|
};
|
|
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 /system/configuration rides
|
|
// the kernel's read-only initrd mount of /system (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
|
|
// /system/configuration/init.csv path.
|
|
const init_csv_source = if (diagnose) "system/configuration/init-diagnose.csv" else "system/configuration/init.csv";
|
|
bundled_list.append(b.allocator, .{ .path = "system/configuration/devices.csv", .binary = b.path("system/configuration/devices.csv") }) catch @panic("OOM");
|
|
bundled_list.append(b.allocator, .{ .path = "system/configuration/init.csv", .binary = b.path(init_csv_source) }) catch @panic("OOM");
|
|
// The protocol grants: who may claim which name under /protocol
|
|
// (docs/os-development/protocol-namespace.md). init reads it beside init.csv,
|
|
// out of the same read-only initrd, before it spawns anything — the registrar
|
|
// has to know its policy before the first provider asks.
|
|
bundled_list.append(b.allocator, .{ .path = "system/configuration/protocol.csv", .binary = b.path("system/configuration/protocol.csv") }) 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=<name> for every scenario, exactly when they must be on the
|
|
// 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",
|
|
"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)
|
|
"user-memory-test", // aims deliberately bad user pointers at the checked copy layer
|
|
"protocol-registry-test", // drives the registrar: ungranted bind, collision, restart
|
|
"protocol-denied-test", // restriction stage one: an ungranted open answers as absence
|
|
}) |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.
|
|
// 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.
|
|
// The loader reads -Dserial too, so its boot-progress breadcrumbs (con_out,
|
|
// which firmware may mirror to a serial console) are silenced by default — a
|
|
// real-hardware boot stays quiet. Fatal-error messages ignore this and always
|
|
// show, so a failed boot still explains itself on screen. See boot/efi.zig.
|
|
const loader_options = b.addOptions();
|
|
loader_options.addOption(bool, "serial", serial);
|
|
const loader_options_module = loader_options.createModule();
|
|
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 the handoff contract and the ramdisk
|
|
// container it packs the /system tree into — never the user ABI.
|
|
.{ .name = "boot-handoff", .module = boot_handoff_module },
|
|
.{ .name = "initial-ramdisk", .module = initial_ramdisk_module },
|
|
.{ .name = "build_options", .module = loader_options_module },
|
|
},
|
|
}),
|
|
});
|
|
|
|
// Image assembly (the FHS install tree, boot manifest + capsule, both FAT32
|
|
// images, the release ISO, the check steps) and the QEMU run steps live in
|
|
// build/ — the root decides what ships, those files own how it runs.
|
|
const fat_image_serial = images.addImageSteps(b, .{
|
|
.kernel = exe,
|
|
.kernel_serial = exe_serial,
|
|
.efi = efiexe,
|
|
.bundled = bundled,
|
|
});
|
|
qemu.addRunSteps(b, fat_image_serial);
|
|
|
|
// 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/initial-ramdisk.zig", // v2 path-named entries: find/basename/magic
|
|
}) |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 library domains and the binary packages 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 package can never be silently skipped here. Package
|
|
// 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,
|
|
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);
|
|
}
|
|
|
|
// 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
|
|
// fit the plain loop above.
|
|
const log_ring_tests = b.addTest(.{
|
|
.root_module = b.createModule(.{
|
|
.root_source_file = b.path("system/kernel/log-ring.zig"),
|
|
.target = target,
|
|
.optimize = optimize,
|
|
.imports = &.{
|
|
.{ .name = "abi", .module = abi_module },
|
|
},
|
|
}),
|
|
});
|
|
test_step.dependOn(&b.addRunArtifact(log_ring_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);
|
|
}
|