//! The "kernel" library domain (library/kernel): the userspace private-ABI //! library (kernel32-style), split by concern into directly-importable //! modules. The graph is a DAG: memory depends on thread (heap needs //! Thread.Mutex), and thread does its own raw mmap so there is no cycle. //! //! This package also exports `abi` — the kernel <-> user contract (SystemCall //! numbers, mmap prot flags, page_size). Its source lives with the kernel in //! system/abi.zig, outside this directory, but userspace's one view of it is //! exported here so every consumer names the same module instance. Reaching //! outside the package root means this package is valid only as an in-repo //! path dependency (never fetchable by hash) — fine, since path dependencies //! are the only way danos packages are consumed. //! //! The root shim (root.zig) and the user link script (user.ld) are plain //! files, not modules; build-support reaches them through this package's //! directory (Dependency.path). const std = @import("std"); pub fn build(b: *std.Build) void { const protocol = b.dependency("protocol", .{}); const abi = b.addModule("abi", .{ .root_source_file = b.path("../../system/abi.zig"), }); const system_call = b.addModule("system-call", .{ .root_source_file = b.path("system-call.zig"), .imports = &.{.{ .name = "abi", .module = abi }}, }); const ipc = b.addModule("ipc", .{ .root_source_file = b.path("ipc.zig"), .imports = &.{ .{ .name = "abi", .module = abi }, .{ .name = "system-call", .module = system_call } }, }); const time = b.addModule("time", .{ .root_source_file = b.path("time.zig"), .imports = &.{.{ .name = "system-call", .module = system_call }}, }); const thread = b.addModule("thread", .{ .root_source_file = b.path("thread.zig"), .imports = &.{ .{ .name = "abi", .module = abi }, .{ .name = "system-call", .module = system_call } }, }); const logging = b.addModule("logging", .{ .root_source_file = b.path("logging.zig"), .imports = &.{ .{ .name = "abi", .module = abi }, .{ .name = "system-call", .module = system_call } }, }); const process = b.addModule("process", .{ .root_source_file = b.path("process.zig"), .imports = &.{ .{ .name = "abi", .module = abi }, .{ .name = "system-call", .module = system_call }, .{ .name = "ipc", .module = ipc }, .{ .name = "time", .module = time }, }, }); const file_system = b.addModule("file-system", .{ .root_source_file = b.path("file-system.zig"), .imports = &.{ .{ .name = "abi", .module = abi }, .{ .name = "system-call", .module = system_call }, .{ .name = "ipc", .module = ipc }, .{ .name = "vfs-protocol", .module = protocol.module("vfs-protocol") }, .{ .name = "envelope", .module = protocol.module("envelope") }, }, }); // The channel is the L1 concept made concrete (docs/os-development/communication.md): // it needs the namespace (file-system, to resolve a /protocol name) and the // transport (ipc) both, which is why it lives here rather than in a protocol // module — those import nothing. const channel = b.addModule("channel", .{ .root_source_file = b.path("channel.zig"), .imports = &.{ .{ .name = "ipc", .module = ipc }, .{ .name = "time", .module = time }, .{ .name = "file-system", .module = file_system }, .{ .name = "vfs-protocol", .module = protocol.module("vfs-protocol") }, .{ .name = "envelope", .module = protocol.module("envelope") }, }, }); _ = b.addModule("memory", .{ .root_source_file = b.path("memory/memory.zig"), .imports = &.{ .{ .name = "abi", .module = abi }, .{ .name = "system-call", .module = system_call }, .{ .name = "ipc", .module = ipc }, .{ .name = "thread", .module = thread }, }, }); // The harness binds the service's contract name at startup, which is a // conversation with the registry — hence channel (and time, for the patience // a provider that beat init to the mount needs). It also owns the subscriber // table and the fan-out, which are expressed in the envelope's vocabulary // (the reserved subscribe verb, the push floor) — hence envelope. _ = b.addModule("service", .{ .root_source_file = b.path("service.zig"), .imports = &.{ .{ .name = "channel", .module = channel }, .{ .name = "envelope", .module = protocol.module("envelope") }, .{ .name = "ipc", .module = ipc }, .{ .name = "process", .module = process }, }, }); _ = b.addModule("start", .{ .root_source_file = b.path("start.zig"), .imports = &.{ .{ .name = "process", .module = process }, .{ .name = "logging", .module = logging } }, }); // Standalone `zig build test` for this domain alone; the root build keeps // its aggregate test step. time and thread pull in the syscall wrappers, // which need the `abi` module; their danos seams fall back to host // primitives off the danos target, so they run with real host threads. const test_step = b.step("test", "Run the kernel library unit tests"); for ([_][]const u8{ "time.zig", // Instant/Duration arithmetic "thread.zig", // Mutex/Condition/RwLock/WaitGroup state machines }) |root| { const kernel_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path(root), .target = b.resolveTargetQuery(.{}), .imports = &.{.{ .name = "abi", .module = abi }}, }), }); test_step.dependOn(&b.addRunArtifact(kernel_tests).step); } // channel needs its whole import set to compile at all; only its framing is // host-runnable (the syscall seams are x86_64-only, and unreferenced from // the tests), so that is what it tests. const channel_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("channel.zig"), .target = b.resolveTargetQuery(.{}), .imports = &.{ .{ .name = "ipc", .module = ipc }, .{ .name = "time", .module = time }, .{ .name = "file-system", .module = file_system }, .{ .name = "vfs-protocol", .module = protocol.module("vfs-protocol") }, .{ .name = "envelope", .module = protocol.module("envelope") }, }, }), }); test_step.dependOn(&b.addRunArtifact(channel_tests).step); }