build: delete module_homes — imports resolve through the declared zon

The module-to-domain table in build-support duplicated what each
domain's build.zig already states with its addModule exports. userBinary
now resolves each named import by searching the packages the binary
declared in its own build.zig.zon (b.available_deps), which also makes
the zon the literal include path: an import can only be satisfied by a
domain the binary claims, and naming a module whose domain is missing
fails the build graph with the domain to declare. build-support is down
to the recipe alone. All build variants green; manifest unchanged.
This commit is contained in:
Daniel Samson
2026-07-30 07:49:19 +01:00
parent 62d6a7a150
commit be04ebe954
29 changed files with 53 additions and 93 deletions
+19 -61
View File
@@ -31,60 +31,20 @@ pub fn freestandingTarget(b: *std.Build) std.Build.ResolvedTarget {
});
}
/// Which library domain package exports each importable module — the one
/// name -> home table. When a domain grows a module, it gets a row here; a
/// binary naming a module whose home is missing from its own build.zig.zon
/// fails loudly at dependency resolution.
const ModuleHome = struct { name: []const u8, home: []const u8 };
const module_homes = [_]ModuleHome{
// library/kernel — the userspace private-ABI library, split by concern.
.{ .name = "abi", .home = "kernel" },
.{ .name = "system-call", .home = "kernel" },
.{ .name = "ipc", .home = "kernel" },
.{ .name = "time", .home = "kernel" },
.{ .name = "thread", .home = "kernel" },
.{ .name = "logging", .home = "kernel" },
.{ .name = "process", .home = "kernel" },
.{ .name = "file-system", .home = "kernel" },
.{ .name = "memory", .home = "kernel" },
.{ .name = "service", .home = "kernel" },
.{ .name = "start", .home = "kernel" },
// library/device — driver-side libraries + the flat reference data.
.{ .name = "mmio", .home = "device" },
.{ .name = "acpi-ids", .home = "device" },
.{ .name = "device-abi", .home = "device" },
.{ .name = "aml", .home = "device" },
.{ .name = "usb-abi", .home = "device" },
.{ .name = "usb-ids", .home = "device" },
.{ .name = "usb", .home = "device" },
.{ .name = "driver", .home = "device" },
.{ .name = "block", .home = "device" },
.{ .name = "pci", .home = "device" },
.{ .name = "pci-class", .home = "device" },
.{ .name = "device-registry", .home = "device" },
// library/client — userspace service clients.
.{ .name = "display-client", .home = "client" },
.{ .name = "input-client", .home = "client" },
// library/protocol — the wire protocols.
.{ .name = "vfs-protocol", .home = "protocol" },
.{ .name = "input-protocol", .home = "protocol" },
.{ .name = "block-protocol", .home = "protocol" },
.{ .name = "usb-transfer-protocol", .home = "protocol" },
.{ .name = "device-manager-protocol", .home = "protocol" },
.{ .name = "display-protocol", .home = "protocol" },
.{ .name = "scanout-protocol", .home = "protocol" },
.{ .name = "power-protocol", .home = "protocol" },
// library/csv — the /etc/*.csv helpers.
.{ .name = "csv", .home = "csv" },
// library/xkeyboard-config — keycode -> keysym/character tables.
.{ .name = "xkeyboard-config", .home = "xkeyboard-config" },
};
fn moduleHome(name: []const u8) ?[]const u8 {
for (module_homes) |entry| {
if (std.mem.eql(u8, entry.name, name)) return entry.home;
/// Resolve one imported module by searching the packages this binary DECLARED
/// in its own build.zig.zon — the C include path made literal: an import can
/// only be satisfied by a domain the binary claims, and each domain's own
/// build.zig (its addModule exports) is the single statement of who owns
/// what. There is no name table here to drift.
fn moduleFromDeclaredDependencies(b: *std.Build, name: []const u8) *std.Build.Module {
for (b.available_deps) |declared| {
const dependency = b.dependency(declared[0], .{});
if (dependency.builder.modules.get(name)) |module| return module;
}
return null;
@panic(b.fmt(
"no declared dependency exports a module named '{s}' — declare the domain that owns it in this package's build.zig.zon",
.{name},
));
}
/// What `userBinary` needs to know about one user binary.
@@ -95,8 +55,8 @@ pub const UserBinaryOptions = struct {
root_source_file: std.Build.LazyPath,
/// Exactly the modules the program's source @imports (directly or through
/// its same-directory files) — no more, no less. Order is free; sorted
/// reads best. An undeclared @import fails the compile; a declared name no
/// domain exports fails the build graph with a pointer to module_homes.
/// reads best. An undeclared @import fails the compile; a name no
/// declared domain exports fails the build graph, naming the miss.
imports: []const []const u8,
/// Built multi-threaded (`single_threaded = false`) so real atomics/TLS
/// work — required before a binary may call `Thread.spawn`
@@ -121,12 +81,10 @@ pub fn userBinary(b: *std.Build, options: UserBinaryOptions) *std.Build.Step.Com
const kernel = b.dependency("kernel", .{});
var imports: std.ArrayListUnmanaged(std.Build.Module.Import) = .empty;
for (options.imports) |name| {
const home = moduleHome(name) orelse @panic(b.fmt(
"no library domain exports a module named '{s}' — if a domain grew it, add its row to module_homes in build-support/build.zig",
.{name},
));
const dependency = if (std.mem.eql(u8, home, "kernel")) kernel else b.dependency(home, .{});
imports.append(b.allocator, .{ .name = name, .module = dependency.module(name) }) catch @panic("OOM");
imports.append(b.allocator, .{
.name = name,
.module = moduleFromDeclaredDependencies(b, name),
}) catch @panic("OOM");
}
// Settings (target, optimize, code model, ...) live on the root module
// only; the program module inherits them.