This example demonstrates how to set up a freestanding static library for a cross-compiled target, which can be used to incrementally replace C functions with Zig functions. In this case we’ll be compiling for an nRF52 Development Kit, containing a Cortex-M4 processor with FPU. We use the freestanding OS tag since we are not using libc on our device. See the Zig docs for a full list of supported targets

It will output a static library called ziglaunch.a. An existing C program compiled with arm-none-eabi-gcc can then call these functions by passing -lziglaunch to the linker.

src/main.zig

const std = @import("std");
const testing = std.testing;
const c = @cImport({
    @cDefine("FLOAT_ABI_HARD", {});
    @cDefine("NRF52840_XXAA", {});
    @cDefine("BOARD_PCA10056", {});
//  use @cInclude to parse C header files for unions, typedefs, structs, etc
//  @cInclude("/path/to/sdk/headers");
});

export fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "basic add functionality" {
    testing.expect(add(3, 7) == 10);
}

build.zig

const std = @import("std");
const Builder = @import("std").build.Builder;

const target = std.zig.CrossTarget{
    .cpu_arch = .thumb,
    .os_tag = .freestanding,
    .abi = .gnueabihf,
    .cpu_model = std.zig.CrossTarget.CpuModel{ .explicit = &std.Target.arm.cpu.cortex_m4 },
};

pub fn build(b: *Builder) void {
    const mode = b.standardReleaseOptions();
    const lib = b.addStaticLibrary("ziglaunch", "src/main.zig");
    lib.addIncludeDir("/usr/arm-none-eabi/include");
    // Add other include directories here (SDKs etc)

    lib.setTarget(target);
    lib.setBuildMode(mode);
    lib.install();

    var main_tests = b.addTest("src/main.zig");
    main_tests.setBuildMode(mode);

    const test_step = b.step("test", "Run library tests");
    test_step.dependOn(&main_tests.step);
}