Gcc Arm to Zig [GATZ]

Hey all,

I ran into enough friction and headaches determining how precisely to map flags like -mcpu, -mfpu, -mfloat-abi using gcc-arm-none-eabi to Zig’s build system that I made a whole utility to take care of it for you:

The github link will have the full info but the highlights are:

  • Utility to easily translate gcc-arm-none-eabi into Zig build targets
  • Works for all ARM Cortex-M based devices
  • A gatz translate command for decoding gcc-arm-none-eabi args into args for zig build:
gatz translate --mcpu=cortex-m7 --mfloat-abi=hard --mfpu=fpv5-sp-d16
Translated `zig build` options: -Dtarget=thumb-freestanding-eabihf -Dcpu=cortex_m7+fp_armv8d16sp
  • A gatz info command for seeing all available options for a target:
gatz info --mcpu=cortex-m7
CPU: cortex-m7       | Float ABIS: soft,softfp,hard | Fpu Types: vfpv4,vfpv4-d16,fpv4-sp-d16,fpv5-d16,fpv5-sp-d16
  • Build helpers that let you declare a target using GCC familiar flags, and translate it into a Zig target:
// Note that the fields in "gatz.Target" match up with the arguments
// to their corresponding flags (-mcpu, -mthumb/-marm, -mfloat-abi, -mfpu)
const gatz_target = gatz.Target{
    .cpu = gatz.cpu.@"cortex-m7",
    .instruction_set = .thumb,
    .endianness = .little,
    .float_abi = .hard,
    .fpu = gatz.fpu.@"fpv5-sp-d16",

// This converts our gatz "target" into an actual Zig target query
const target_query = gatz_target.toTargetQuery() catch |err| {
    std.log.err("Something is misconfigured, see: {any}\n", .{err});

// Using the normal Zig API to resolve a target query
const target = b.resolveTargetQuery(target_query);
  • Makes including the pre-compiled Newlib libc from gcc-arm-none-eabi a one liner:
// Linking in the arm-none-eabi-gcc supplied newlib is now a single function call!
// Automatically grabs the correct pre-built libraries based on target. Will also
// check to make sure a compatible target is being used
gatz.linkNewlib(b, target, blinky_exe) catch |err| switch (err) {
    gatz.NewlibError.CompilerNotFound => {
        std.log.err("Couldn't find arm-none-eabi-gcc compiler!\n", .{});
    gatz.NewlibError.IncompatibleCpu => {
        std.log.err("Cpu: {s} isn't compatible with gatz!\n", .{target.result.cpu.model.name});

And more!

Any and all feedback is welcome, and hopefully people get some use out of this!


Was particularly delighted with this piece of comptime magic:

Given a struct that is essentially just a namespace, for example:

const namespace = struct {
    pub const a: u8 = 1;
    pub const b: u8 = 2;

This function returns a variable based on a string matching the name of the variable declaration. In my mind a poor man’s way of simulating Rust’s enums that can be ANY type, rather than just integer types.