Skip to content

core: make the user's main.zig the executable root#937

Merged
mattnite merged 19 commits into
mainfrom
app-root
May 22, 2026
Merged

core: make the user's main.zig the executable root#937
mattnite merged 19 commits into
mainfrom
app-root

Conversation

@mattnite
Copy link
Copy Markdown
Contributor

@mattnite mattnite commented Apr 24, 2026

Three pieces of boilerplate are needed to use MicroZig:

const microzig = @import("microzig");

pub const panic = microzig.panic;
pub const std_options = microzig.std_options(.{});

comptime {
    _ = microzig.export_startup();
}

MicroZig offers a freestanding-friendly panic and std_options, that don't cause compile errors by default. At the time of writing, the standard library ends up pulling in OS-specific declarations which is the cause of these compilation errors and isn't obvious when you're starting out on Zig embedded programming. microzig.std_options() takes an override argument so you can customize it as you like.

The export_startup() call ensures that your hardware specific startup code is linked into the firmware image. These are the parts before main, like setting up bss and data sections.

Background

In the past, MicroZig would use its own module for the root of an embedded application. The code that the user provided was turned into an app module, and the root would call app.main().

This had the benefit of giving MicroZig control over how the application was set up, allowing an easy way for it to link the startup code. It had a downside where @import("root") did not refer to the user's application root, it referred to MicroZig's. Many libraries use that as an avenue for configuration at compile-time. The most notable of which, was the standard library itself.

The standard library is pervasive enough that we were fine forwarding some of its options through our own microzig_options declaration. None of the solutions for forwarding other package "options" felt right.

In addition, this setup complicated the import of external packages. Users needed to understand the module hierarchy, and that adding a package to their executable did not make it available to their program. Instead they needed to add it to their app module, or find the specific option in the firmware build interface that would do that for them.

In the end, it just wasn't worth it. We traded a few less lines of boilerplate for an increasingly complex system, and it was hurting users' understanding. We decided that a few extra lines of code on your end and some documentation on ours was worth the more consistent system.

@Grazfather
Copy link
Copy Markdown
Collaborator

hah whoa. Maybe we run the zig imports fix as a separate cleanup PR to cut down on the noise?

@mattnite
Copy link
Copy Markdown
Contributor Author

@Grazfather nah, too easy to fix

Comment thread .github/workflows/ci.yml Outdated
@mattnite mattnite marked this pull request as draft May 19, 2026 06:16
@mattnite
Copy link
Copy Markdown
Contributor Author

Converting to draft because I need to write accompanying documentation.

mattnite and others added 11 commits May 21, 2026 13:28
The firmware executable's root module is now the user's application
source, not microzig's start.zig shim. This aligns microzig with the
standard Zig build idiom: `@import("root")` from any module resolves to
user code, `pub const panic` / `pub const std_options` live where Zig
expects to find them, and tooling that assumes root-is-the-app now
works naturally.

Core changes:
- microzig.zig reads `microzig_options` via `@import("root")` instead of
  the injected `app` module, and drops the `pub const app` alias.
- utilities.install_startup / core/src/start.zig are replaced by a
  single user-facing primitive, `microzig.export_startup()`, which the
  application opts into from a comptime block. It emits the CPU
  startup symbols and the `microzig_main` wrapper that the CPU `_start`
  calls once `.data`/`.bss` are initialized.
- Startup logic (HAL init / root init / error-returning main / panic
  wrapping) moves out of start.zig into microzig_main, with identical
  semantics.
- A new `microzig.std_options()` helper returns a `std.Options` with
  microzig's freestanding-safe defaults (the critical one being a no-op
  `logFn` that replaces stdlib's stderr-writing default, which fails to
  link on freestanding). The accompanying `StdOptions` struct documents
  which std.Options fields are exposed and which are intentionally
  excluded as hosted-only.
- The log-related fields (`log_level`, `log_scope_levels`, `logFn`) are
  removed from `microzig.Options`; they now live exclusively in
  `std.Options` via the helper.

Build system:
- `add_firmware` sets `exe.root_module = app_mod` directly. The
  separate start.zig root module and the "app"-import plumbing on
  `core_mod` / `cpu_mod` / `chip_mod` / `hal_mod` / `board_mod` are
  all gone.

Example boilerplate (now two re-exports and one comptime line):

    pub const panic = microzig.panic;
    pub const std_options = microzig.std_options(.{});

    comptime {
        _ = microzig.export_startup();
    }

All 131 example main.zig files have been migrated. Examples that
previously declared `pub const microzig_options` with log-related
fields have had those fields moved into `microzig.std_options(...)`,
with any remaining microzig-specific fields (interrupts, hal, cpu,
etc.) kept in a trimmed `pub const microzig_options`.

The website getting-started snippet is updated to match.

This commit only performs the structural move. It is NOT expected to
build against 0.15.x or current 0.16.x compilers as-is; the separate
0.16 migration work comes next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mattnite mattnite marked this pull request as ready for review May 22, 2026 02:15
@mattnite mattnite merged commit 2a4a220 into main May 22, 2026
59 checks passed
@mattnite mattnite deleted the app-root branch May 22, 2026 04:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants