-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathbuild.zig
366 lines (314 loc) · 15 KB
/
build.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
const std = @import("std");
const log = std.log.scoped(.builder);
const builtin = @import("builtin");
const rt = @import("test/runtime_test.zig");
const RuntimeStep = rt.RuntimeStep;
const Allocator = std.mem.Allocator;
const Builder = std.build.Builder;
const Step = std.build.Step;
const Target = std.Target;
const CrossTarget = std.zig.CrossTarget;
const fs = std.fs;
const File = fs.File;
const Mode = std.builtin.Mode;
const TestMode = rt.TestMode;
const ArrayList = std.ArrayList;
const Fat32 = @import("mkfat32.zig").Fat32;
const x86_i686 = CrossTarget{
.cpu_arch = .i386,
.os_tag = .freestanding,
.cpu_model = .{ .explicit = &Target.x86.cpu._i686 },
};
pub fn build(b: *Builder) !void {
const target = b.standardTargetOptions(.{ .whitelist = &[_]CrossTarget{x86_i686}, .default_target = x86_i686 });
const arch = switch (target.getCpuArch()) {
.i386 => "x86",
else => unreachable,
};
const fmt_step = b.addFmt(&[_][]const u8{
"build.zig",
"mkfat32.zig",
"src",
"test",
});
b.default_step.dependOn(&fmt_step.step);
const main_src = "src/kernel/kmain.zig";
const arch_root = "src/kernel/arch";
const linker_script_path = try fs.path.join(b.allocator, &[_][]const u8{ arch_root, arch, "link.ld" });
const output_iso = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "pluto.iso" });
const iso_dir_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "iso" });
const boot_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "iso", "boot" });
const modules_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "iso", "modules" });
const ramdisk_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "initrd.ramdisk" });
const fat32_image_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "fat32.img" });
const test_fat32_image_path = try fs.path.join(b.allocator, &[_][]const u8{ "test", "fat32", "test_fat32.img" });
const build_mode = b.standardReleaseOptions();
comptime var test_mode_desc: []const u8 = "\n ";
inline for (@typeInfo(TestMode).Enum.fields) |field| {
const tm = @field(TestMode, field.name);
test_mode_desc = test_mode_desc ++ field.name ++ " (" ++ TestMode.getDescription(tm) ++ ")";
test_mode_desc = test_mode_desc ++ "\n ";
}
const test_mode = b.option(TestMode, "test-mode", "Run a specific runtime test. This option is for the rt-test step. Available options: " ++ test_mode_desc) orelse .None;
const disable_display = b.option(bool, "disable-display", "Disable the qemu window") orelse false;
const exec = b.addExecutable("pluto.elf", main_src);
const exec_output_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "pluto.elf" });
exec.setOutputDir(b.install_path);
const exec_options = b.addOptions();
exec.addOptions("build_options", exec_options);
exec_options.addOption(TestMode, "test_mode", test_mode);
exec.setBuildMode(build_mode);
exec.setLinkerScriptPath(std.build.FileSource{ .path = linker_script_path });
exec.setTarget(target);
const make_iso = switch (target.getCpuArch()) {
.i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec_output_path, ramdisk_path, output_iso }),
else => unreachable,
};
make_iso.step.dependOn(&exec.step);
var fat32_builder_step = Fat32BuilderStep.create(b, .{}, fat32_image_path);
make_iso.step.dependOn(&fat32_builder_step.step);
var ramdisk_files_al = ArrayList([]const u8).init(b.allocator);
defer ramdisk_files_al.deinit();
if (test_mode == .Initialisation) {
// Add some test files for the ramdisk runtime tests
try ramdisk_files_al.append("test/ramdisk_test1.txt");
try ramdisk_files_al.append("test/ramdisk_test2.txt");
} else if (test_mode == .Scheduler) {
inline for (&[_][]const u8{ "user_program_data", "user_program" }) |user_program| {
// Add some test files for the user mode runtime tests
const user_program_step = b.addExecutable(user_program ++ ".elf", null);
user_program_step.setLinkerScriptPath(.{ .path = "test/user_program.ld" });
user_program_step.addAssemblyFile("test/" ++ user_program ++ ".s");
user_program_step.setOutputDir(b.install_path);
user_program_step.setTarget(target);
user_program_step.setBuildMode(build_mode);
user_program_step.strip = true;
exec.step.dependOn(&user_program_step.step);
const user_program_path = try std.mem.join(b.allocator, "/", &[_][]const u8{ b.install_path, user_program ++ ".elf" });
try ramdisk_files_al.append(user_program_path);
}
}
const ramdisk_step = RamdiskStep.create(b, target, ramdisk_files_al.toOwnedSlice(), ramdisk_path);
make_iso.step.dependOn(&ramdisk_step.step);
b.default_step.dependOn(&make_iso.step);
const test_step = b.step("test", "Run tests");
const unit_tests = b.addTest(main_src);
unit_tests.setBuildMode(build_mode);
unit_tests.setMainPkgPath(".");
const unit_test_options = b.addOptions();
unit_tests.addOptions("build_options", unit_test_options);
unit_test_options.addOption(TestMode, "test_mode", test_mode);
unit_tests.setTarget(.{ .cpu_arch = target.cpu_arch });
if (builtin.os.tag != .windows) {
b.enable_qemu = true;
}
// Run the mock gen
const mock_gen = b.addExecutable("mock_gen", "test/gen_types.zig");
mock_gen.setMainPkgPath(".");
const mock_gen_run = mock_gen.run();
unit_tests.step.dependOn(&mock_gen_run.step);
// Create test FAT32 image
const test_fat32_img_step = Fat32BuilderStep.create(b, .{}, test_fat32_image_path);
const copy_test_files_step = b.addSystemCommand(&[_][]const u8{ "./fat32_cp.sh", test_fat32_image_path });
copy_test_files_step.step.dependOn(&test_fat32_img_step.step);
unit_tests.step.dependOn(©_test_files_step.step);
test_step.dependOn(&unit_tests.step);
const rt_test_step = b.step("rt-test", "Run runtime tests");
var qemu_args_al = ArrayList([]const u8).init(b.allocator);
defer qemu_args_al.deinit();
switch (target.getCpuArch()) {
.i386 => try qemu_args_al.append("qemu-system-i386"),
else => unreachable,
}
try qemu_args_al.append("-serial");
try qemu_args_al.append("stdio");
switch (target.getCpuArch()) {
.i386 => {
try qemu_args_al.append("-boot");
try qemu_args_al.append("d");
try qemu_args_al.append("-cdrom");
try qemu_args_al.append(output_iso);
},
else => unreachable,
}
if (disable_display) {
try qemu_args_al.append("-display");
try qemu_args_al.append("none");
}
var qemu_args = qemu_args_al.toOwnedSlice();
const rt_step = RuntimeStep.create(b, test_mode, qemu_args);
rt_step.step.dependOn(&make_iso.step);
rt_test_step.dependOn(&rt_step.step);
const run_step = b.step("run", "Run with qemu");
const run_debug_step = b.step("debug-run", "Run with qemu and wait for a gdb connection");
const qemu_cmd = b.addSystemCommand(qemu_args);
const qemu_debug_cmd = b.addSystemCommand(qemu_args);
qemu_debug_cmd.addArgs(&[_][]const u8{ "-s", "-S" });
qemu_cmd.step.dependOn(&make_iso.step);
qemu_debug_cmd.step.dependOn(&make_iso.step);
run_step.dependOn(&qemu_cmd.step);
run_debug_step.dependOn(&qemu_debug_cmd.step);
const debug_step = b.step("debug", "Debug with gdb and connect to a running qemu instance");
const symbol_file_arg = try std.mem.join(b.allocator, " ", &[_][]const u8{ "symbol-file", exec_output_path });
const debug_cmd = b.addSystemCommand(&[_][]const u8{
"gdb-multiarch",
"-ex",
symbol_file_arg,
"-ex",
"set architecture auto",
});
debug_cmd.addArgs(&[_][]const u8{
"-ex",
"target remote localhost:1234",
});
debug_step.dependOn(&debug_cmd.step);
}
/// The FAT32 step for creating a FAT32 image.
const Fat32BuilderStep = struct {
/// The Step, that is all you need to know
step: Step,
/// The builder pointer, also all you need to know
builder: *Builder,
/// The path to where the ramdisk will be written to.
out_file_path: []const u8,
/// Options for creating the FAT32 image.
options: Fat32.Options,
///
/// The make function that is called by the builder.
///
/// Arguments:
/// IN step: *Step - The step of this step.
///
/// Error: error{EndOfStream} || File.OpenError || File.ReadError || File.WriteError || File.SeekError || Allocator.Error || Fat32.Error || Error
/// error{EndOfStream} || File.OpenError || File.ReadError || File.WriteError || File.SeekError - Error related to file operations. See std.fs.File.
/// Allocator.Error - If there isn't enough memory to allocate for the make step.
/// Fat32.Error - If there was an error creating the FAT image. This will be invalid options.
///
fn make(step: *Step) (error{EndOfStream} || File.OpenError || File.ReadError || File.WriteError || File.SeekError || Fat32.Error)!void {
const self = @fieldParentPtr(Fat32BuilderStep, "step", step);
// Open the out file
const image = try std.fs.cwd().createFile(self.out_file_path, .{ .read = true });
// If there was an error, delete the image as this will be invalid
errdefer (std.fs.cwd().deleteFile(self.out_file_path) catch unreachable);
defer image.close();
try Fat32.make(self.options, image, false);
}
///
/// Create a FAT32 builder step.
///
/// Argument:
/// IN builder: *Builder - The build builder.
/// IN options: Options - Options for creating FAT32 image.
///
/// Return: *Fat32BuilderStep
/// The FAT32 builder step pointer to add to the build process.
///
pub fn create(builder: *Builder, options: Fat32.Options, out_file_path: []const u8) *Fat32BuilderStep {
const fat32_builder_step = builder.allocator.create(Fat32BuilderStep) catch unreachable;
fat32_builder_step.* = .{
.step = Step.init(.custom, builder.fmt("Fat32BuilderStep", .{}), builder.allocator, make),
.builder = builder,
.options = options,
.out_file_path = out_file_path,
};
return fat32_builder_step;
}
};
/// The ramdisk make step for creating the initial ramdisk.
const RamdiskStep = struct {
/// The Step, that is all you need to know
step: Step,
/// The builder pointer, also all you need to know
builder: *Builder,
/// The target for the build
target: CrossTarget,
/// The list of files to be added to the ramdisk
files: []const []const u8,
/// The path to where the ramdisk will be written to.
out_file_path: []const u8,
/// The possible errors for creating a ramdisk
const Error = (error{EndOfStream} || File.ReadError || File.SeekError || Allocator.Error || File.WriteError || File.OpenError);
///
/// Create and write the files to a raw ramdisk in the format:
/// (NumOfFiles:usize)[(name_length:usize)(name:u8[name_length])(content_length:usize)(content:u8[content_length])]*
///
/// Argument:
/// IN comptime Usize: type - The usize type for the architecture.
/// IN self: *RamdiskStep - Self.
///
/// Error: Error
/// Errors for opening, reading and writing to and from files and for allocating memory.
///
fn writeRamdisk(comptime Usize: type, self: *RamdiskStep) Error!void {
// 1GB, don't think the ram disk should be very big
const max_file_size = 1024 * 1024 * 1024;
// Open the out file
var ramdisk = try fs.cwd().createFile(self.out_file_path, .{});
defer ramdisk.close();
// Get the targets endian
const endian = self.target.getCpuArch().endian();
// First write the number of files/headers
std.debug.assert(self.files.len < std.math.maxInt(Usize));
try ramdisk.writer().writeInt(Usize, @truncate(Usize, self.files.len), endian);
var current_offset: usize = 0;
for (self.files) |file_path| {
// Open, and read the file. Can get the size from this as well
const file_content = try fs.cwd().readFileAlloc(self.builder.allocator, file_path, max_file_size);
// Get the last occurrence of / for the file name, if there isn't one, then the file_path is the name
const file_name_index = if (std.mem.lastIndexOf(u8, file_path, "/")) |index| index + 1 else 0;
// Write the header and file content to the ramdisk
// Name length
std.debug.assert(file_path[file_name_index..].len < std.math.maxInt(Usize));
try ramdisk.writer().writeInt(Usize, @truncate(Usize, file_path[file_name_index..].len), endian);
// Name
try ramdisk.writer().writeAll(file_path[file_name_index..]);
// Length
std.debug.assert(file_content.len < std.math.maxInt(Usize));
try ramdisk.writer().writeInt(Usize, @truncate(Usize, file_content.len), endian);
// File contest
try ramdisk.writer().writeAll(file_content);
// Increment the offset to the new location
current_offset += @sizeOf(Usize) * 3 + file_path[file_name_index..].len + file_content.len;
}
}
///
/// The make function that is called by the builder. This will switch on the target to get the
/// correct usize length for the target.
///
/// Arguments:
/// IN step: *Step - The step of this step.
///
/// Error: Error
/// Errors for opening, reading and writing to and from files and for allocating memory.
///
fn make(step: *Step) Error!void {
const self = @fieldParentPtr(RamdiskStep, "step", step);
switch (self.target.getCpuArch()) {
.i386 => try writeRamdisk(u32, self),
else => unreachable,
}
}
///
/// Create a ramdisk step.
///
/// Argument:
/// IN builder: *Builder - The build builder.
/// IN target: CrossTarget - The target for the build.
/// IN files: []const []const u8 - The file names to be added to the ramdisk.
/// IN out_file_path: []const u8 - The output file path.
///
/// Return: *RamdiskStep
/// The ramdisk step pointer to add to the build process.
///
pub fn create(builder: *Builder, target: CrossTarget, files: []const []const u8, out_file_path: []const u8) *RamdiskStep {
const ramdisk_step = builder.allocator.create(RamdiskStep) catch unreachable;
ramdisk_step.* = .{
.step = Step.init(.custom, builder.fmt("Ramdisk", .{}), builder.allocator, make),
.builder = builder,
.target = target,
.files = files,
.out_file_path = out_file_path,
};
return ramdisk_step;
}
};