rumpk/build.zig

430 lines
14 KiB
Zig

// SPDX-License-Identifier: LCL-1.0
// Copyright (c) 2026 Markus Maiwald
// Stewardship: Self Sovereign Society Foundation
//
// This file is part of the Nexus Commonwealth.
// See legal/LICENSE_COMMONWEALTH.md for license terms.
//! Rumpk Build System — Unified Pipeline
//!
//! Single command builds everything: Nim kernel + Zig HAL + LwIP + link.
//!
//! Usage:
//! zig build # Build kernel (default: riscv64)
//! zig build -Darch=aarch64 # ARM64 target
//! zig build -Darch=x86_64 # AMD64 target
//! zig build nim # Recompile Nim kernel sources only
//! zig build test-lwf # Run LWF tests (native host)
const std = @import("std");
const Arch = enum { riscv64, aarch64, x86_64 };
/// Per-architecture build configuration.
const ArchConfig = struct {
cpu_arch: std.Target.Cpu.Arch,
code_model: std.builtin.CodeModel,
switch_asm: []const u8,
linker_script: []const u8,
/// Arch name for build_nim.sh
nim_arch: []const u8,
};
fn archConfig(arch: Arch) ArchConfig {
return switch (arch) {
.riscv64 => .{
.cpu_arch = .riscv64,
.code_model = .medany,
.switch_asm = "hal/arch/riscv64/switch.S",
.linker_script = "boot/linker.ld",
.nim_arch = "riscv64",
},
.aarch64 => .{
.cpu_arch = .aarch64,
.code_model = .default,
.switch_asm = "hal/arch/aarch64/switch.S",
.linker_script = "boot/linker_aarch64.ld",
.nim_arch = "aarch64",
},
.x86_64 => .{
.cpu_arch = .x86_64,
.code_model = .kernel,
.switch_asm = "hal/arch/x86_64/switch.S",
.linker_script = "boot/linker_x86_64.ld",
.nim_arch = "x86_64",
},
};
}
/// Apply freestanding kernel settings to a module.
fn applyKernelSettings(mod: *std.Build.Module, code_model: std.builtin.CodeModel) void {
mod.red_zone = false;
mod.stack_check = false;
mod.code_model = code_model;
mod.sanitize_thread = false;
mod.single_threaded = true;
mod.strip = true;
}
pub fn build(b: *std.Build) void {
// =========================================================
// Target Selection: -Darch=riscv64|aarch64|x86_64
// =========================================================
const arch = b.option(Arch, "arch", "Target architecture (default: riscv64)") orelse .riscv64;
const config = archConfig(arch);
var query: std.Target.Query = .{
.cpu_arch = config.cpu_arch,
.os_tag = .freestanding,
.abi = .none,
};
// Disable NEON/FP in kernel code generation to avoid unaligned SIMD stores
if (config.cpu_arch == .aarch64) {
query.cpu_features_sub = std.Target.aarch64.featureSet(&.{.neon});
}
const target = b.resolveTargetQuery(query);
// HEPHAESTUS DECREE: Force ReleaseSmall. A kernel runs naked.
const optimize: std.builtin.OptimizeMode = .ReleaseSmall;
// =========================================================
// Step: Nim Kernel Compilation (nim -> C -> .o)
// =========================================================
// Calls build_nim.sh which handles:
// 1. nim c --compileOnly -> generates C
// 2. zig cc -> cross-compiles C to .o
// Incremental: only recompiles changed files.
const nim_step = b.addSystemCommand(&.{ "./build_nim.sh", config.nim_arch });
const nim_build_step = b.step("nim", "Recompile Nim kernel sources (nim -> C -> .o)");
nim_build_step.dependOn(&nim_step.step);
// =========================================================
// L0: Hardware Abstraction Layer (Zig)
// =========================================================
const hal_mod = b.createModule(.{
.root_source_file = b.path("hal/abi.zig"),
.target = target,
.optimize = optimize,
});
applyKernelSettings(hal_mod, config.code_model);
const hal = b.addLibrary(.{
.name = "rumpk_hal",
.root_module = hal_mod,
.linkage = .static,
});
b.installArtifact(hal);
// =========================================================
// Boot: Entry Point (Assembly + Zig)
// =========================================================
const boot_mod = b.createModule(.{
.root_source_file = b.path("boot/header.zig"),
.target = target,
.optimize = optimize,
});
applyKernelSettings(boot_mod, config.code_model);
const boot = b.addObject(.{
.name = "boot",
.root_module = boot_mod,
});
// =========================================================
// LwIP: Lightweight TCP/IP Stack
// =========================================================
// Compiled as C objects with freestanding flags.
// ubsan disabled per-file to avoid runtime dependency.
const lwip_base = "libs/membrane/external/lwip/src/";
const lwip_srcs = [_][]const u8{
// Core
lwip_base ++ "core/init.c",
lwip_base ++ "core/def.c",
lwip_base ++ "core/dns.c",
lwip_base ++ "core/inet_chksum.c",
lwip_base ++ "core/ip.c",
lwip_base ++ "core/mem.c",
lwip_base ++ "core/memp.c",
lwip_base ++ "core/netif.c",
lwip_base ++ "core/pbuf.c",
lwip_base ++ "core/raw.c",
lwip_base ++ "core/sys.c",
lwip_base ++ "core/tcp.c",
lwip_base ++ "core/tcp_in.c",
lwip_base ++ "core/tcp_out.c",
lwip_base ++ "core/timeouts.c",
lwip_base ++ "core/udp.c",
// IPv4
lwip_base ++ "core/ipv4/autoip.c",
lwip_base ++ "core/ipv4/dhcp.c",
lwip_base ++ "core/ipv4/etharp.c",
lwip_base ++ "core/ipv4/icmp.c",
lwip_base ++ "core/ipv4/ip4.c",
lwip_base ++ "core/ipv4/ip4_addr.c",
lwip_base ++ "core/ipv4/ip4_frag.c",
// Netif
lwip_base ++ "netif/ethernet.c",
// SysArch (Rumpk integration)
"libs/membrane/sys_arch.c",
};
const lwip_flags: []const []const u8 = switch (arch) {
.riscv64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DNO_SYS=1", "-mcmodel=medany", "-Icore",
"-Ilibs/membrane", "-Ilibs/membrane/include", "-Ilibs/membrane/external/lwip/src/include",
},
.aarch64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DNO_SYS=1", "-Icore", "-Ilibs/membrane",
"-Ilibs/membrane/include", "-Ilibs/membrane/external/lwip/src/include",
},
.x86_64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DNO_SYS=1", "-mcmodel=kernel", "-Icore",
"-Ilibs/membrane", "-Ilibs/membrane/include", "-Ilibs/membrane/external/lwip/src/include",
},
};
const liblwip = b.addLibrary(.{
.name = "lwip",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
.linkage = .static,
});
liblwip.root_module.sanitize_thread = false;
liblwip.root_module.red_zone = false;
liblwip.root_module.single_threaded = true;
for (lwip_srcs) |src| {
liblwip.addCSourceFile(.{
.file = b.path(src),
.flags = lwip_flags,
});
}
b.installArtifact(liblwip);
// =========================================================
// LittleFS: Sovereign Filesystem (Persistent Storage)
// =========================================================
// Compiled as C object with freestanding flags.
// Uses LFS_CONFIG=lfs_rumpk.h to replace lfs_util.h entirely,
// avoiding stdio/assert deps. malloc/free provided by clib.c.
const lfs_flags: []const []const u8 = switch (arch) {
.riscv64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DLFS_CONFIG=lfs_rumpk.h", "-Ivendor/littlefs", "-mcmodel=medany",
},
.aarch64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DLFS_CONFIG=lfs_rumpk.h", "-Ivendor/littlefs",
},
.x86_64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DLFS_CONFIG=lfs_rumpk.h", "-Ivendor/littlefs", "-mcmodel=kernel",
},
};
const lfs_obj = b.addObject(.{
.name = "littlefs",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
});
lfs_obj.root_module.sanitize_thread = false;
lfs_obj.root_module.red_zone = false;
lfs_obj.root_module.single_threaded = true;
lfs_obj.addCSourceFile(.{
.file = b.path("vendor/littlefs/lfs.c"),
.flags = lfs_flags,
});
// =========================================================
// Dependencies (CLib, LibC Shim, LWF Adapter, Switch)
// =========================================================
// 1. CLib (Minimal C stdlib)
const clib = b.addObject(.{
.name = "clib",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
});
clib.addCSourceFile(.{
.file = b.path("libs/membrane/clib.c"),
.flags = switch (arch) {
.riscv64 => &.{ "-Os", "-DNO_SYS=1", "-DRUMPK_KERNEL=1", "-mcmodel=medany" },
.aarch64 => &.{ "-Os", "-DNO_SYS=1", "-DRUMPK_KERNEL=1" },
.x86_64 => &.{ "-Os", "-DNO_SYS=1", "-DRUMPK_KERNEL=1", "-mcmodel=kernel" },
},
});
// 2. LibC Shim (Zig)
const libc_shim_mod = b.createModule(.{
.root_source_file = b.path("libs/membrane/libc_shim.zig"),
.target = target,
.optimize = optimize,
});
applyKernelSettings(libc_shim_mod, config.code_model);
const libc_shim = b.addObject(.{
.name = "libc_shim",
.root_module = libc_shim_mod,
});
// 3. LittleFS HAL (VirtIO-Block ↔ LittleFS glue)
const lfs_hal_mod = b.createModule(.{
.root_source_file = b.path("hal/littlefs_hal.zig"),
.target = target,
.optimize = optimize,
});
applyKernelSettings(lfs_hal_mod, config.code_model);
const lfs_hal = b.addObject(.{
.name = "lfs_hal",
.root_module = lfs_hal_mod,
});
// 4. LWF Adapter (Project LibWeb — Libertaria Wire Frame)
const lwf_adapter_mod = b.createModule(.{
.root_source_file = b.path("libs/libertaria/lwf_adapter.zig"),
.target = target,
.optimize = optimize,
});
applyKernelSettings(lwf_adapter_mod, config.code_model);
const lwf_adapter = b.addObject(.{
.name = "lwf_adapter",
.root_module = lwf_adapter_mod,
});
// 4. Context Switch (Architecture-specific Assembly)
const switch_obj = b.addObject(.{
.name = "switch",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
});
switch_obj.addAssemblyFile(b.path(config.switch_asm));
// =========================================================
// Final Link: rumpk.elf
// =========================================================
const kernel_mod = b.createModule(.{
.root_source_file = b.path("hal/abi.zig"),
.target = target,
.optimize = optimize,
});
applyKernelSettings(kernel_mod, config.code_model);
const kernel = b.addExecutable(.{
.name = "rumpk.elf",
.root_module = kernel_mod,
});
kernel.setLinkerScript(b.path(config.linker_script));
kernel.addObject(boot);
kernel.addObject(clib);
kernel.addObject(libc_shim);
kernel.addObject(lfs_hal);
kernel.addObject(lwf_adapter);
kernel.addObject(switch_obj);
// LwIP linked into kernel — Membrane/NetSwitch drives DHCP/TCP/ICMP.
kernel.linkLibrary(liblwip);
// LittleFS IS linked into kernel — provides /nexus persistent storage.
kernel.addObject(lfs_obj);
// Nim-generated objects from build/nimcache/
{
var nimcache_dir = std.fs.cwd().openDir("build/nimcache", .{ .iterate = true }) catch |err| {
std.debug.print("Warning: Could not open nimcache dir: {}\n", .{err});
return;
};
defer nimcache_dir.close();
var it = nimcache_dir.iterate();
while (it.next() catch null) |entry| {
if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".o")) {
const path = b.fmt("build/nimcache/{s}", .{entry.name});
kernel.addObjectFile(b.path(path));
}
}
}
// Embedded InitRD — assemble for the target architecture
const initrd_obj = b.addObject(.{
.name = "initrd",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
});
initrd_obj.addAssemblyFile(b.path("build/embed_initrd.S"));
kernel.addObject(initrd_obj);
b.installArtifact(kernel);
// Make default install depend on Nim compilation
kernel.step.dependOn(&nim_step.step);
// =========================================================
// Tests (always run on native host)
// =========================================================
// HAL unit tests
const hal_test_mod = b.createModule(.{
.root_source_file = b.path("hal/abi.zig"),
.target = b.graph.host,
});
const hal_tests = b.addTest(.{
.root_module = hal_test_mod,
});
const run_hal_tests = b.addRunArtifact(hal_tests);
const test_step = b.step("test", "Run Rumpk HAL tests (native host)");
test_step.dependOn(&run_hal_tests.step);
// LWF Adapter + Membrane tests (Project LibWeb)
const lwf_test_mod = b.createModule(.{
.root_source_file = b.path("libs/libertaria/lwf_adapter.zig"),
.target = b.graph.host,
});
const lwf_tests = b.addTest(.{
.root_module = lwf_test_mod,
});
const run_lwf_tests = b.addRunArtifact(lwf_tests);
const lwf_test_step = b.step("test-lwf", "Run LWF Adapter tests (Project LibWeb)");
lwf_test_step.dependOn(&run_lwf_tests.step);
const lwf_membrane_mod = b.createModule(.{
.root_source_file = b.path("libs/libertaria/lwf_membrane.zig"),
.target = b.graph.host,
});
const lwf_membrane_tests = b.addTest(.{
.root_module = lwf_membrane_mod,
});
const run_lwf_membrane_tests = b.addRunArtifact(lwf_membrane_tests);
lwf_test_step.dependOn(&run_lwf_membrane_tests.step);
}