Forgotten Elixir

An enchanting engine where magic and technology intertwine

Using zig as the build system

created: 2025-03-07 06:05:07 UTC
last updated: 2025-03-08 06:10:50 UTC

After learning the basics of pseudo 3-D rendering with C, I initially started this project with the plan to use Zig. Well, that ended up not happening due to a few factors. First, Zig is a great language. Things that I have written in it, I have enjoyed. It is a pleasure to right with and I think calling it a modern spiritual successor to C is pretty fitting. I did find it a bit of a pain to work with SDL. More specially because I was pretty early into using SDL3. As of today, they have had a public release for a month, maybe a bit longer, but I was experimenting with SDL3 and Zig a month or two before that. So there was no nice library for wrapping C structs in Zig structs or anything. I also did not feel like spending tons of tons managing Opaque pointers. Otherwise, the Zig-C interop is fantastic and the best I have experienced. Swift interop is also pretty good, but Zig felt a little more seamless.

However, while the engine's source may not be written in Zig, I have stuck with using Zig as a build system. Makefiles are manageable, but CMake is atrocious. I also did not feel like learning one of the other build systems to use with this project. But I was familiar somewhat with using Zig as the build system from playing with Zig. So why not keep it?

Here is so far an example of my build.zig file.

const std = @import("std");
const builtin = @import("builtin");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    // const mode = b.standardReleaseOptions();

    // const c_flags = [_][]const u8{
    //     "-Wall",
    //     "-pedantic",
    // };

    //   const source_files = [_][]const u8{
    //       "src/graphics.c",
    //       "src/main.c",
    //       "src/map.c",
    // "src/font.c",
    //       "src/player.c",
    //       "src/ray.c",
    //       "src/sprite.c",
    //       "src/textures.c",
    //       "src/upng.c",
    //       "src/utils.c",
    //       "src/wall.c",
    //       "src/level.c",
    // "src/texts.c",
    //   };

    const flags = [_][]const u8{
        "-Wall",
        "-Wextra",
        "-pedantic",
        "-g",
    };

    const source_files = [_][]const u8{
        "src-cpp/Main.cpp",
        "src-cpp/Game.cpp",
        "src-cpp/Graphics.cpp",
        "src-cpp/Actor.cpp",
        "src-cpp/Component.cpp",
        "src-cpp/Player.cpp",
        "src-cpp/RenderComponent.cpp",
        "src-cpp/BoxSprite.cpp",
        "src-cpp/Map.cpp",
        "src-cpp/Ray.cpp",
        "src-cpp/MathUtils.cpp",
        "src-cpp/Wall.cpp",
        "src-cpp/Components/MoveComponent.cpp",
        "src-cpp/Components/InputComponent.cpp",
    };

    var exe = b.addExecutable(.{
        .name = "eotf",
        .target = target,
        .optimize = optimize,
    });

    exe.addCSourceFiles(.{ .files = &source_files, .flags = &flags });

    // exe.linkLibC();
    exe.linkLibCpp();
    exe.linkSystemLibrary("sdl3");
    exe.linkSystemLibrary("sdl3-ttf");
    exe.linkSystemLibrary("libcyaml");

    b.installArtifact(exe);
    const run_artifact = b.addRunArtifact(exe);
    const run_step = b.step("run", "Run the project");
    run_step.dependOn(&run_artifact.step);
}

There is some code commented out because while I am converting my C code to C++, I still have the C files under the 'src' folder with the new C++ code under 'src-cpp.'  ˆ can quickly comment and uncomment a few lines to run the C-version of the code, but I do plan to do away with that once the C++ code is feature parity.

Otherwise, I find a build.zig file to be fairly straight forward to set up, read and understand.