Skip to content

Commit 910c530

Browse files
authored
Make public app declarations available from root (ZigEmbeddedGroup#775)
Fixes ZigEmbeddedGroup#465. One of the weaknesses of MicroZig's build system is that packages that use root declarations for comptime-configuration don't work. We make the application a package to the real root of an executable, and break out useful stdlib options (namely logging) through `microzig_options`. This is also notable because MicroZig changes the defaults for logging because logging will cause compiler errors from not having an operating system. I want to avoid boiler-plate like the following: ```zig const microzig = @import("microzig"); comptime { microzig.cpu.export_startup_logic(); // would export startup logic } pub fn main() { // ... } ``` So this patch creates a code generation step in our build system that takes the existing `startup.zig` code that currently makes up the root of the executable and appends the public declarations from the application. This gives us everything we want, no boiler plate, and packages will now be able to use comptime-configuration. The special handling of the Standard Library logging function is temporary. This needs to be fixed upstream so that the regular logging function is a no-op on freestanding targets.
1 parent 81681fa commit 910c530

File tree

4 files changed

+80
-12
lines changed

4 files changed

+80
-12
lines changed

build.zig

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,21 @@ pub fn MicroBuild(port_select: PortSelect) type {
558558
app_mod.addImport("microzig", core_mod);
559559
core_mod.addImport("app", app_mod);
560560

561+
const generate_root_exe = mb.builder.addExecutable(.{
562+
.name = "generate_root",
563+
.root_module = mb.builder.createModule(.{
564+
.root_source_file = mb.core_dep.path("src/generate_root.zig"),
565+
.target = mb.builder.graph.host,
566+
.optimize = .Debug,
567+
.imports = &.{
568+
.{ .name = "app", .module = app_mod },
569+
},
570+
}),
571+
});
572+
573+
const generate_root_run = mb.builder.addRunArtifact(generate_root_exe);
574+
const root_file = generate_root_run.addOutputFileArg("main.zig");
575+
561576
const fw = mb.builder.allocator.create(Firmware) catch @panic("out of memory");
562577
fw.* = .{
563578
.mb = mb,
@@ -567,7 +582,7 @@ pub fn MicroBuild(port_select: PortSelect) type {
567582
.root_module = b.createModule(.{
568583
.optimize = options.optimize,
569584
.target = zig_resolved_target,
570-
.root_source_file = mb.core_dep.path("src/start.zig"),
585+
.root_source_file = root_file,
571586
.single_threaded = options.single_threaded orelse target.single_threaded,
572587
.strip = options.strip,
573588
.unwind_tables = options.unwind_tables,
@@ -588,6 +603,9 @@ pub fn MicroBuild(port_select: PortSelect) type {
588603
fw.artifact.link_data_sections = options.strip_unused_symbols;
589604
fw.artifact.entry = options.entry orelse target.entry orelse .default;
590605

606+
for (options.imports) |import|
607+
fw.artifact.root_module.addImport(import.name, import.module);
608+
591609
fw.artifact.root_module.addImport("microzig", core_mod);
592610
fw.artifact.root_module.addImport("app", app_mod);
593611

core/src/generate_root.zig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const std = @import("std");
2+
const app = @import("app");
3+
4+
pub fn main() !void {
5+
const gpa = std.heap.page_allocator;
6+
const args = try std.process.argsAlloc(gpa);
7+
defer std.process.argsFree(gpa, args);
8+
9+
if (args.len < 2)
10+
return error.InvalidArgs;
11+
12+
const output_path = args[1];
13+
const file = try std.fs.cwd().createFile(output_path, .{});
14+
defer file.close();
15+
16+
var buf: [4096]u8 = undefined;
17+
var file_writer = file.writer(&buf);
18+
const writer = &file_writer.interface;
19+
20+
try writer.writeAll(@embedFile("start.zig"));
21+
22+
inline for (@typeInfo(app).@"struct".decls) |decl| {
23+
if (!std.mem.eql(u8, "panic", decl.name) and
24+
!std.mem.eql(u8, "std_options", decl.name))
25+
{
26+
try writer.print("pub const {f} = app.{f};\n", .{ std.zig.fmtId(decl.name), std.zig.fmtId(decl.name) });
27+
}
28+
}
29+
30+
try writer.flush();
31+
}

core/src/start.zig

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,29 @@ pub const panic = if (!@hasDecl(app, "panic")) microzig.panic else app.panic;
1010
// defined. Parts of microzig use the stdlib logging facility and
1111
// compilations will now fail on freestanding systems that use it but do
1212
// not explicitly set `root.std_options.logFn`
13-
pub const std_options: std.Options = .{
14-
.log_level = microzig.options.log_level,
15-
.log_scope_levels = microzig.options.log_scope_levels,
16-
.logFn = microzig.options.logFn,
13+
pub const std_options: std.Options = blk: {
14+
var options = if (@hasDecl(app, "std_options"))
15+
app.std_options
16+
else
17+
std.Options{};
18+
19+
if (options.logFn != std.log.defaultLog)
20+
@compileError("It seems that you're trying to change the stdlib's " ++
21+
"logFn. Please set this in the microzig_options, we require this " ++
22+
"so that embedded executables don't give compile errors by default.");
23+
24+
if (options.log_level != std.log.default_level)
25+
@compileError("It seems that you're trying to change the stdlib's " ++
26+
"log_level. Please set this in the microzig_options.");
27+
28+
if (options.log_scope_levels.len > 0)
29+
@compileError("It seems that you're trying to change the stdlib's " ++
30+
"log_scope_levels. Please set this in the microzig_options.");
31+
32+
options.logFn = microzig.options.logFn;
33+
options.log_level = microzig.options.log_level;
34+
options.log_scope_levels = microzig.options.log_scope_levels;
35+
break :blk options;
1736
};
1837

1938
// Startup logic:
@@ -40,8 +59,8 @@ export fn microzig_main() noreturn {
4059
if (!@hasDecl(app, "main"))
4160
@compileError("The root source file must provide a public function main!");
4261

43-
const main = @field(app, "main");
44-
const info: std.builtin.Type = @typeInfo(@TypeOf(main));
62+
const app_main = @field(app, "main");
63+
const info: std.builtin.Type = @typeInfo(@TypeOf(app_main));
4564

4665
const invalid_main_msg = "main must be either 'pub fn main() void' or 'pub fn main() !void'.";
4766
if (info != .@"fn" or info.@"fn".params.len > 0)
@@ -62,7 +81,7 @@ export fn microzig_main() noreturn {
6281
microzig.hal.init();
6382

6483
if (@typeInfo(return_type) == .error_union) {
65-
main() catch |err| {
84+
app_main() catch |err| {
6685
// Although here we could use @errorReturnTrace similar to
6786
// `std.start` and just dump the trace (without panic), the user
6887
// might not use logging and have the panic handler just blink an
@@ -90,7 +109,7 @@ export fn microzig_main() noreturn {
90109
}
91110
};
92111
} else {
93-
main();
112+
app_main();
94113
}
95114

96115
// Main returned, just hang around here a bit.

examples/raspberrypi/rp2xxx/src/rp2040_only/flash_id.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noretu
1515
while (true) {}
1616
}
1717

18-
pub const std_options = struct {
19-
pub const log_level = .debug;
20-
pub const logFn = rp2xxx.uart.log;
18+
pub const microzig_options: microzig.Options = .{
19+
.log_level = .debug,
20+
.logFn = rp2xxx.uart.log,
2121
};
2222

2323
pub fn main() !void {

0 commit comments

Comments
 (0)