/ build.zig
build.zig
1 const std = @import("std"); 2 3 const Config = struct { 4 pub const RiscvConfig = struct { 5 /// Whether to depend on an SBI (Supervisor Binary Interface) implementation. 6 /// This will assume that an SBI implementation is available and will be used to 7 /// boot the kernel. 8 sbi: bool = true, 9 10 /// When `true`, the kernel assumes that at boot, 11 /// `a0` will contain the boot hart id, 12 /// and `a1` will contain a pointer to a valid FDT (DTB) structure. 13 /// When `false`, provide the bytes for the FDT in the `fdt` field in the main `Config` struct. 14 /// If `false`, and the `fdt` field is not provided, the kernel will be very limited 15 /// in functionality. 16 standard_args: bool = true, 17 }; 18 19 /// RISC-V specific configuration. 20 riscv: RiscvConfig = .{}, 21 22 /// The recommended way to set this is through the `dtb` build option. 23 fdt: ?[]align(8) const u8 = null, 24 }; 25 26 const TargetConfig = struct { 27 target: []const u8, 28 cpu: []const u8, 29 linker_script: ?[]const u8 = null, 30 dtb: ?[]const u8 = null, 31 code_model: ?std.builtin.CodeModel = null, 32 kernel_config: Config = .{}, 33 description: ?[]const u8 = null, 34 }; 35 36 const NamedTargetConfig = struct { 37 name: []const u8, 38 config: TargetConfig, 39 }; 40 41 fn discoverTargetConfigs(b: *std.Build) !std.ArrayList(NamedTargetConfig) { 42 var configs: std.ArrayList(NamedTargetConfig) = .empty; 43 44 var targets_dir = std.fs.cwd().openDir("targets", .{ .iterate = true }) catch |err| switch (err) { 45 error.FileNotFound => { 46 std.log.warn("targets/ directory not found, no target configs available", .{}); 47 return configs; 48 }, 49 else => return err, 50 }; 51 defer targets_dir.close(); 52 53 var iterator = targets_dir.iterate(); 54 while (try iterator.next()) |entry| { 55 if (entry.kind != .file) continue; 56 if (!std.mem.endsWith(u8, entry.name, ".zon")) continue; 57 58 const target_name = entry.name[0 .. entry.name.len - 4]; 59 60 const config_content = targets_dir.readFileAllocOptions( 61 b.allocator, 62 entry.name, 63 std.math.maxInt(usize), 64 null, 65 .of(u8), 66 0, 67 ) catch |err| { 68 std.log.warn("Failed to read target config '{s}': {}", .{ entry.name, err }); 69 continue; 70 }; 71 72 const target_config = std.zon.parse.fromSlice(TargetConfig, b.allocator, config_content, null, .{}) catch |err| { 73 std.log.warn("Failed to parse target config '{s}': {}", .{ entry.name, err }); 74 continue; 75 }; 76 77 try configs.append(b.allocator, .{ .name = try b.allocator.dupe(u8, target_name), .config = target_config }); 78 } 79 80 return configs; 81 } 82 83 const ResolvedTargetConfig = struct { 84 target: std.Build.ResolvedTarget, 85 optimize: std.builtin.OptimizeMode, 86 config: Config, 87 linker_script: ?std.Build.LazyPath, 88 code_model: std.builtin.CodeModel, 89 }; 90 91 fn applyTargetConfig(b: *std.Build, target_config: TargetConfig) !ResolvedTargetConfig { 92 const query = std.Target.Query.parse(.{ 93 .arch_os_abi = target_config.target, 94 .cpu_features = target_config.cpu, 95 }) catch |err| { 96 std.log.err("Invalid target string '{s}': {}", .{ target_config.target, err }); 97 return err; 98 }; 99 100 const target = b.resolveTargetQuery(query); 101 const optimize = b.standardOptimizeOption(.{}); 102 103 var config = target_config.kernel_config; 104 if (target_config.dtb) |dtb_path| { 105 config.fdt = std.fs.cwd().readFileAllocOptions( 106 b.allocator, 107 dtb_path, 108 std.math.maxInt(usize), 109 null, 110 .@"8", 111 null, 112 ) catch |err| { 113 std.log.err("Failed to read DTB file '{s}': {}", .{ dtb_path, err }); 114 return err; 115 }; 116 } 117 118 const linker_script = if (target_config.linker_script) |script_path| 119 b.path(script_path) 120 else 121 null; 122 123 return .{ 124 .target = target, 125 .optimize = optimize, 126 .config = config, 127 .linker_script = linker_script, 128 .code_model = target_config.code_model orelse .default, 129 }; 130 } 131 132 pub fn build(b: *std.Build) !void { 133 const discovered_configs = discoverTargetConfigs(b) catch |err| { 134 std.log.err("Failed to discover target configurations: {}", .{err}); 135 return err; 136 }; 137 138 var help_text: std.ArrayList(u8) = .empty; 139 try help_text.appendSlice(b.allocator, "Available target configurations:"); 140 141 for (discovered_configs.items) |item| { 142 try help_text.appendSlice(b.allocator, "\n "); 143 try help_text.appendSlice(b.allocator, item.name); 144 if (item.config.description) |desc| { 145 try help_text.appendSlice(b.allocator, " - "); 146 try help_text.appendSlice(b.allocator, desc); 147 } 148 try help_text.appendSlice(b.allocator, " (target: "); 149 try help_text.appendSlice(b.allocator, item.config.target); 150 try help_text.appendSlice(b.allocator, ")"); 151 } 152 if (discovered_configs.items.len == 0) { 153 try help_text.appendSlice(b.allocator, "\n none"); 154 } 155 156 const target_config_name = b.option([]const u8, "target-config", help_text.items); 157 158 var build_params: ResolvedTargetConfig = undefined; 159 160 if (target_config_name) |config_name| { 161 var found_config: ?TargetConfig = null; 162 for (discovered_configs.items) |item| { 163 if (std.mem.eql(u8, item.name, config_name)) { 164 found_config = item.config; 165 break; 166 } 167 } 168 169 if (found_config) |target_config| { 170 build_params = try applyTargetConfig(b, target_config); 171 std.log.info("Using target configuration: {s}", .{config_name}); 172 } else { 173 std.log.err("Unknown target configuration: {s}", .{config_name}); 174 std.log.info("Available configurations:", .{}); 175 for (discovered_configs.items) |item| { 176 std.log.info(" {s}", .{item.name}); 177 } 178 return error.UnknownConfig; 179 } 180 } else { 181 const target = b.standardTargetOptions(.{}); 182 const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSafe }); 183 184 const maybe_ld_path = b.option([]const u8, "ld-path", "Path to the linker script"); 185 const maybe_code_model = b.option(std.builtin.CodeModel, "code-model", "Code model to use"); 186 const maybe_dtb = b.option([]const u8, "dtb", "Path to the DTB file"); 187 188 var config_src: [:0]const u8 = ".{}"; 189 config_src = std.fs.cwd().readFileAllocOptions( 190 b.allocator, 191 "config.zon", 192 std.math.maxInt(usize), 193 null, 194 .of(u8), 195 0, 196 ) catch |err| switch (err) { 197 error.FileNotFound => blk: { 198 std.log.warn("config.zon not found, using default config", .{}); 199 break :blk config_src; 200 }, 201 else => return err, 202 }; 203 204 var config = try std.zon.parse.fromSlice(Config, b.allocator, config_src, null, .{}); 205 206 if (maybe_dtb) |dtb| { 207 config.fdt = try std.fs.cwd().readFileAllocOptions( 208 b.allocator, 209 dtb, 210 std.math.maxInt(usize), 211 null, 212 .@"8", 213 null, 214 ); 215 } 216 217 build_params = .{ 218 .target = target, 219 .optimize = optimize, 220 .config = config, 221 .linker_script = if (maybe_ld_path) |ld_path| b.path(ld_path) else null, 222 .code_model = maybe_code_model orelse .default, 223 }; 224 } 225 226 const mod = b.addModule("lightning", .{ 227 .root_source_file = b.path("src/main.zig"), 228 .optimize = build_params.optimize, 229 .target = build_params.target, 230 .code_model = build_params.code_model, 231 }); 232 233 const opts = b.addOptions(); 234 235 opts.addOption(Config, "config", build_params.config); 236 237 mod.addOptions("options", opts); 238 239 const kernel = b.addExecutable(.{ 240 .name = "lightning", 241 .root_module = mod, 242 }); 243 244 if (build_params.linker_script) |ld_path| { 245 kernel.setLinkerScript(ld_path); 246 } 247 248 b.installArtifact(kernel); 249 }