build: phase 0 — extract the build-support package
The shared user-binary recipe (freestanding target, root-shim wiring, link-script and image-base settings) moves out of the root build into build-support/, the package that is the single home for cross-cutting build changes (docs/build-packages-plan.md). The root build's addUserBinary/addThreadedUserBinary keep their signatures and delegate; nothing else moves. Boot-image file list unchanged.
This commit is contained in:
@@ -0,0 +1,115 @@
|
|||||||
|
//! The danos build API (docs/build-packages-plan.md, phase 0): the one shared
|
||||||
|
//! recipe for building a user-space binary, extracted from the root build so
|
||||||
|
//! cross-cutting build changes have a single home. Consumers declare this
|
||||||
|
//! package in their build.zig.zon (as "build-support") and @import its
|
||||||
|
//! build.zig from their own build.zig; nothing is compiled from this package
|
||||||
|
//! itself — it exports build-time functions only.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
_ = b; // nothing to build: this package exports build-time functions only
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The freestanding x86-64 target every danos binary (kernel and user) is
|
||||||
|
/// built for. 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.
|
||||||
|
pub fn freestandingTarget(b: *std.Build) std.Build.ResolvedTarget {
|
||||||
|
return b.resolveTargetQuery(.{
|
||||||
|
.cpu_arch = .x86_64,
|
||||||
|
.os_tag = .freestanding,
|
||||||
|
.abi = .none,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What `userBinaryFromImports` needs to know about one user binary.
|
||||||
|
pub const UserBinaryOptions = struct {
|
||||||
|
name: []const u8,
|
||||||
|
/// The program's own source file — it becomes the `program` module the
|
||||||
|
/// root shim imports; a program only defines `pub fn main`.
|
||||||
|
root_source_file: std.Build.LazyPath,
|
||||||
|
/// The shared root shim supplying the compilation-root declarations
|
||||||
|
/// (`main` re-export, panic handler, `_start` pull): library/kernel/root.zig.
|
||||||
|
shim_source_file: std.Build.LazyPath,
|
||||||
|
/// The shared user link script. Its PHDRS (segment permissions) are
|
||||||
|
/// authoritative — the kernel's W^X user-ELF loader requires exact perms.
|
||||||
|
linker_script: std.Build.LazyPath,
|
||||||
|
target: std.Build.ResolvedTarget,
|
||||||
|
/// The default set of importable modules every user binary sees — the
|
||||||
|
/// library/kernel concern modules (ipc, memory, process, time, logging,
|
||||||
|
/// file-system, ...), the device/service clients (driver, block, display,
|
||||||
|
/// input), mmio, acpi-ids, and xkeyboard-config. Must include `start` and
|
||||||
|
/// `logging` (the root shim reaches those two directly).
|
||||||
|
default_imports: []const std.Build.Module.Import,
|
||||||
|
/// Built multi-threaded (`single_threaded = false`) so real atomics/TLS
|
||||||
|
/// work — required before a binary may call `Thread.spawn`
|
||||||
|
/// (docs/threading.md). Threads are a deliberate per-binary opt-in.
|
||||||
|
threaded: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The module registered under `name` in `imports` — the root shim reaches the
|
||||||
|
/// couple of concern modules it needs (start, logging) out of the default set.
|
||||||
|
fn findImport(imports: []const std.Build.Module.Import, name: []const u8) *std.Build.Module {
|
||||||
|
for (imports) |import| {
|
||||||
|
if (std.mem.eql(u8, import.name, name)) return import.module;
|
||||||
|
}
|
||||||
|
@panic("default_imports is missing a module the root shim needs");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build one user-space binary the same way for every program (init, the
|
||||||
|
/// services, the drivers): freestanding, ReleaseSmall, `.large` code model
|
||||||
|
/// (the image base is above 4 GiB — smaller models emit 32-bit relocations
|
||||||
|
/// that can't reach), linked 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.
|
||||||
|
///
|
||||||
|
/// The compilation root is not the program's own file but the shared shim
|
||||||
|
/// (options.shim_source_file), which supplies the root declarations (`main`
|
||||||
|
/// re-export, panic handler, `_start` pull) so a program only defines
|
||||||
|
/// `pub fn main`. The program's file becomes the `program` module the shim
|
||||||
|
/// imports; reach it through `programModule` to add per-binary imports.
|
||||||
|
pub fn userBinaryFromImports(b: *std.Build, options: UserBinaryOptions) *std.Build.Step.Compile {
|
||||||
|
// Settings (target, optimize, code model, ...) live on the root module
|
||||||
|
// only; the program module inherits them.
|
||||||
|
const program_module = b.createModule(.{
|
||||||
|
.root_source_file = options.root_source_file,
|
||||||
|
.imports = options.default_imports,
|
||||||
|
});
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = options.name,
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = options.shim_source_file,
|
||||||
|
.target = options.target,
|
||||||
|
.optimize = .ReleaseSmall,
|
||||||
|
.code_model = .large,
|
||||||
|
.single_threaded = !options.threaded, // a threaded binary needs real atomics/TLS
|
||||||
|
.sanitize_c = .off,
|
||||||
|
.stack_check = false,
|
||||||
|
.stack_protector = false,
|
||||||
|
// The root shim itself imports only start (_start + panic) and logging
|
||||||
|
// (std_options); the program's own file reaches the full default set.
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "start", .module = findImport(options.default_imports, "start") },
|
||||||
|
.{ .name = "logging", .module = findImport(options.default_imports, "logging") },
|
||||||
|
.{ .name = "program", .module = program_module },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
exe.setLinkerScript(options.linker_script);
|
||||||
|
exe.entry = .{ .symbol_name = "_start" };
|
||||||
|
exe.image_base = 0x7000_0000_0000;
|
||||||
|
exe.use_llvm = true;
|
||||||
|
exe.use_lld = true;
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `program` module of a binary built by `userBinaryFromImports` — the
|
||||||
|
/// module rooted at the program's own source file. Per-binary imports
|
||||||
|
/// (protocol modules, bus ABIs) go here, not on the root shim: module imports
|
||||||
|
/// are not transitive, so an import added to the root would be invisible to
|
||||||
|
/// the program's code.
|
||||||
|
pub fn programModule(exe: *std.Build.Step.Compile) *std.Build.Module {
|
||||||
|
return exe.root_module.import_table.get("program").?;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.{
|
||||||
|
.name = .build_support,
|
||||||
|
.version = "0.0.0",
|
||||||
|
.fingerprint = 0xad91962994f4be41, // Changing this has security and trust implications.
|
||||||
|
.minimum_zig_version = "0.16.0",
|
||||||
|
.dependencies = .{},
|
||||||
|
.paths = .{""},
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
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.
|
||||||
|
const build_support = @import("build-support");
|
||||||
|
|
||||||
/// danos is developed against Zig 0.16.x. Pre-1.0 Zig makes breaking API changes
|
/// 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
|
/// between minor releases, and the .zon's `minimum_zig_version` only enforces a
|
||||||
@@ -48,18 +51,10 @@ fn timestamp(b: *std.Build) []const u8 {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build one user-space binary the same way for every program (init, and later
|
/// One user-space binary via the shared build-support recipe (freestanding,
|
||||||
/// the VFS server + drivers): freestanding, ReleaseSmall, `.large` code model
|
/// ReleaseSmall, `.large` code model, root shim + user link script — see
|
||||||
/// (the image base is above 4 GiB — smaller models emit 32-bit relocations that
|
/// build-support/build.zig for the full story). The program's file becomes the
|
||||||
/// can't reach), linked against the `runtime` runtime library with the shared user
|
/// `program` module; reach it through `programModule` to add per-binary imports.
|
||||||
/// 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.
|
|
||||||
///
|
|
||||||
/// The compilation root is not the program's own file but the shared shim
|
|
||||||
/// library/kernel/root.zig, which supplies the root declarations (`main`
|
|
||||||
/// re-export, panic handler, `_start` pull) so a program only defines
|
|
||||||
/// `pub fn main`. The program's file becomes the `program` module the shim
|
|
||||||
/// imports; reach it through `programModule` to add per-binary imports.
|
|
||||||
fn addUserBinary(
|
fn addUserBinary(
|
||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
target: std.Build.ResolvedTarget,
|
target: std.Build.ResolvedTarget,
|
||||||
@@ -83,15 +78,6 @@ fn addThreadedUserBinary(
|
|||||||
return addUserBinaryImpl(b, target, default_imports, name, root, true);
|
return addUserBinaryImpl(b, target, default_imports, name, root, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The module registered under `name` in `imports` — the root shim reaches the
|
|
||||||
/// couple of concern modules it needs (start, logging) out of the default set.
|
|
||||||
fn findImport(imports: []const std.Build.Module.Import, name: []const u8) *std.Build.Module {
|
|
||||||
for (imports) |import| {
|
|
||||||
if (std.mem.eql(u8, import.name, name)) return import.module;
|
|
||||||
}
|
|
||||||
@panic("default_imports is missing a module the root shim needs");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addUserBinaryImpl(
|
fn addUserBinaryImpl(
|
||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
target: std.Build.ResolvedTarget,
|
target: std.Build.ResolvedTarget,
|
||||||
@@ -100,41 +86,15 @@ fn addUserBinaryImpl(
|
|||||||
root: []const u8,
|
root: []const u8,
|
||||||
threaded: bool,
|
threaded: bool,
|
||||||
) *std.Build.Step.Compile {
|
) *std.Build.Step.Compile {
|
||||||
// Every user binary gets the same default set of importable modules — the library/kernel
|
return build_support.userBinaryFromImports(b, .{
|
||||||
// concern modules (ipc, memory, process, time, logging, file-system, ...), the device/
|
|
||||||
// service clients (driver, block, display, input), mmio, acpi-ids, and xkeyboard-config.
|
|
||||||
// Per-binary extras go through programModule(exe).addImport. Settings (target, optimize,
|
|
||||||
// code model, ...) live on the root module only; the program module inherits them.
|
|
||||||
const program_module = b.createModule(.{
|
|
||||||
.root_source_file = b.path(root),
|
|
||||||
.imports = default_imports,
|
|
||||||
});
|
|
||||||
const exe = b.addExecutable(.{
|
|
||||||
.name = name,
|
.name = name,
|
||||||
.root_module = b.createModule(.{
|
.root_source_file = b.path(root),
|
||||||
.root_source_file = b.path("library/kernel/root.zig"),
|
.shim_source_file = b.path("library/kernel/root.zig"),
|
||||||
.target = target,
|
.linker_script = b.path("library/kernel/user.ld"),
|
||||||
.optimize = .ReleaseSmall,
|
.target = target,
|
||||||
.code_model = .large,
|
.default_imports = default_imports,
|
||||||
.single_threaded = !threaded, // a threaded binary needs real atomics/TLS
|
.threaded = threaded,
|
||||||
.sanitize_c = .off,
|
|
||||||
.stack_check = false,
|
|
||||||
.stack_protector = false,
|
|
||||||
// The root shim itself imports only start (_start + panic) and logging
|
|
||||||
// (std_options); the program's own file reaches the full default set.
|
|
||||||
.imports = &.{
|
|
||||||
.{ .name = "start", .module = findImport(default_imports, "start") },
|
|
||||||
.{ .name = "logging", .module = findImport(default_imports, "logging") },
|
|
||||||
.{ .name = "program", .module = program_module },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
exe.setLinkerScript(b.path("library/kernel/user.ld"));
|
|
||||||
exe.entry = .{ .symbol_name = "_start" };
|
|
||||||
exe.image_base = 0x7000_0000_0000;
|
|
||||||
exe.use_llvm = true;
|
|
||||||
exe.use_lld = true;
|
|
||||||
return exe;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `program` module of a binary built by `addUserBinary` — the module rooted
|
/// The `program` module of a binary built by `addUserBinary` — the module rooted
|
||||||
@@ -142,7 +102,7 @@ fn addUserBinaryImpl(
|
|||||||
/// ABIs) go here, not on the root shim: module imports are not transitive, so an
|
/// ABIs) go here, not on the root shim: module imports are not transitive, so an
|
||||||
/// import added to the root would be invisible to the program's code.
|
/// import added to the root would be invisible to the program's code.
|
||||||
fn programModule(exe: *std.Build.Step.Compile) *std.Build.Module {
|
fn programModule(exe: *std.Build.Step.Compile) *std.Build.Module {
|
||||||
return exe.root_module.import_table.get("program").?;
|
return build_support.programModule(exe);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The modules the kernel imports, gathered once so both kernel variants (the
|
/// The modules the kernel imports, gathered once so both kernel variants (the
|
||||||
@@ -597,14 +557,8 @@ pub fn build(b: *std.Build) void {
|
|||||||
const diagnose = b.option(bool, "diagnose", "Boot without the display service so the timestamped boot transcript stays on screen (real-hardware debugging)") orelse false;
|
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 ---
|
// --- 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,
|
// (See build-support/build.zig for why SSE2 stays enabled.)
|
||||||
// so we keep it: disabling it forces soft-float and makes the compiler unable
|
const kernel_target = build_support.freestandingTarget(b);
|
||||||
// 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 kernel_modules = KernelModules{
|
const kernel_modules = KernelModules{
|
||||||
.boot_handoff = boot_handoff_module,
|
.boot_handoff = boot_handoff_module,
|
||||||
|
|||||||
+4
-8
@@ -32,6 +32,9 @@
|
|||||||
// Once all dependencies are fetched, `zig build` no longer requires
|
// Once all dependencies are fetched, `zig build` no longer requires
|
||||||
// internet connectivity.
|
// internet connectivity.
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
|
// The danos build API — the shared user-binary recipe every build file
|
||||||
|
// (root and per-binary packages) consumes (docs/build-packages-plan.md).
|
||||||
|
.@"build-support" = .{ .path = "build-support" },
|
||||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||||
//.example = .{
|
//.example = .{
|
||||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||||
@@ -70,12 +73,5 @@
|
|||||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||||
// the build root itself.
|
// the build root itself.
|
||||||
// A directory listed here means that all files within, recursively, are included.
|
// A directory listed here means that all files within, recursively, are included.
|
||||||
.paths = .{
|
.paths = .{""},
|
||||||
"build.zig",
|
|
||||||
"build.zig.zon",
|
|
||||||
"src",
|
|
||||||
// For example...
|
|
||||||
//"LICENSE",
|
|
||||||
//"README.md",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user