Output two distinct binaries according to a build comptime variable

Hello everyone!

I have a project in which I want to make it change behavior according to a compile time variable, kinda like a #ifdef C preprocessor style. Let me explain:

According to a value known for the build system, I want my code to change what branch gets executed:

const version = 1;
if (comptime version == 1) {
    doSmthOne();
else {
    doSmthTwo();
}

This is obviously possible, but what I actually want is that to be setted by the build system, like this:

  • zig build v1: output one binary, version 1.
  • zig build v2: output another different binary, version 2.
  • zig build: create both binaries, v1 and v2.

What I am actually struggling is how to:

  1. Define the version variable in build.zig and make my program access that variable.
  2. Make the default behaviour zig build output two binaries instead of one.

I am definetly sure this is possible (bc comptime and build system are amazing!) but I am struggling to kind in the std docs a function that allows me to do this.

Thank you and have a nice day :))

In build script you need to define an option. Something like this:

const version: u32 = 1;
const options = b.addOptions();
options.addOption(u32, "version", version);
const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .target = target,
            .optimize = optimize,
            .root_source_file = b.path("src/main.zig"),
        }),
});

exe.root_module.addOptions(“my_options”, options);

then you import it in you main.zig:

const my_options = @import(“my_options”);
if (my_options.version == 1) { .. }

EDIT: Then in your build script you need to have two different executables, passing different options to its root module (I think).

2 Likes

I don’t think you need two executables, unless you want to compile with both values of the option in one go.

Indeed, option allows me to access the value of the variable in my code! Now I am trying to make the build system do what I want haha thank you :))

1 Like

Idk if I get you, but yeah I want to be able to build the two versions of the program at the same time.

Maybe version is not a good word for what I am trying to achieve, but function 1 and function 2 behave very differently while still sharing the same underlying code. When I compile I want to have the two distinct ones to compare them.

1 Like

Okay, i did this, which is not elegant, but it works!

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

    const versions: [2][]const u8 = .{"v1", "v2"};

    inline for (versions) |version| {
        const options = b.addOptions();
        options.addOption([]const u8, "build", version);

        const exe = b.addExecutable(.{
            .name = "bskysim",
            .root_module = b.createModule(.{
                .root_source_file = b.path("src/main.zig"),
                .target = target,
                .optimize = optimize,
            }),
        });
        
        exe.root_module.addOptions("build", options);

        const eazy_args_dep = b.dependency("eazy_args", .{
            .target = target,
            .optimize = optimize,
        });
        const eazy_args_mod = eazy_args_dep.module("eazy_args");

        const heap_dep = b.dependency("heap", .{
            .target = target,
            .optimize = optimize,
        });
        const heap_mod = heap_dep.module("heap");

        const distributions_dep = b.dependency("distributions", .{
            .target = target,
            .optimize = optimize,
        });
        const distributions_mod = distributions_dep.module("distributions");

        // link the dependencies in here
        exe.root_module.addImport("eazy_args", eazy_args_mod);
        exe.root_module.addImport("heap", heap_mod);
        exe.root_module.addImport("distributions", distributions_mod);
    
        b.installArtifact(exe); // creates the binari in the folder
        
        const run_cmd = b.addRunArtifact(exe);

        // Install it as the module
        run_cmd.step.dependOn(b.getInstallStep());

        if (b.args) |args| {
            run_cmd.addArgs(args);
        }

        const run_step = b.step("run" ++ version, "Run the app");
        run_step.dependOn(&run_cmd.step);

        const release_step = b.step("release" ++ version, "Build for Windows (x64), Linux (x64) and Mac (ARM64)");

        const targets: []const std.Target.Query = &.{
            //.{ .cpu_arch = .x86_64, .os_tag = .windows },
            .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .gnu },
            .{ .cpu_arch = .aarch64, .os_tag = .macos },
        };

        for (targets) |t| {
            const release_exe = b.addExecutable(.{
                .name = "bskysim" ++ version,
                .root_module = b.createModule(.{
                    .root_source_file = b.path("src/main.zig"),
                    .target = b.resolveTargetQuery(t),
                    .optimize = .ReleaseSafe, // Force optimized builds for release
                }),
            });

            release_exe.root_module.addOptions("build", options);
            release_exe.root_module.addImport("eazy_args", eazy_args_mod);
            release_exe.root_module.addImport("heap", heap_mod);
            release_exe.root_module.addImport("distributions", distributions_mod);
            
            // This installs the artifact into a subfolder named after the target
            // e.g., zig-out/x86_64-windows/busstop_simulation.exe
            const target_output = b.addInstallArtifact(release_exe, .{
                .dest_dir = .{
                    .override = .{
                        .custom = try t.zigTriple(b.allocator),
                    },
                },
            });

            release_step.dependOn(&target_output.step);

        }
    }
}

Now i can do:

  • zig build: builds everything
  • zig build releasev1/releasev2: compile with release the appropiate version
  • zib runv1/runv2: run the appropiate versoin

I think I could restructurate the code to be prettier and to make it read better, and maybe add an option to make zig build release/run → makes both of those at the same time, but fn this works for me!

1 Like

This really should be a -Dversion= option… maybe it is already, if you run zig build help?

i think also you could have it receive a list via multiple -Dversion= commands, and then make one artifact per version received …