Search code examples
compilationzig

Using zig compiler as a library


Is there a way to use zig compiler as a library inside zig? After looking both in zig documentation, issues and on the internet, I can't find an answer to this question.

In one of the issues it is said that this can be done at the current time, but I couldn't find any examples of how to do it.


Solution

  • It is currently non-trivial to use the zig compiler as a library due to the difficulty in including libraries with complicated build requirements. Once the package manager is released, it will likely become much easier.

    For now, I would recommend calling the zig compiler as a system command

    var zig = std.ChildProcess.init(&.{"zig", "build-exe", "demo.zig"}, allocator);
    try zig.spawn();
    try zig.wait();
    

    However, if you really want to, here is how to use zig 0.9.0-dev.1611+f3ba72cf5 as a library

    Reasons not to do this before the package manager is released:

    • This requires making a mess of your build.zig file, and your build.zig file has to be kept up to date with the compiler's build.zig file
    • This will make your project build really slowly due to the current slow speed of the stage1 zig compiler
    • This example does not show how to enable stage1 and the LLVM extensions in the zig package it makes. Enabling stage1 and LLVM extensions will require looking at zig's build.zig file and copying in stuff about linking libraries and include directories and c source and stuff.

    This build.zig was made by finding all the instances of .addOption in the zig compiler's build.zig and recreating them in our own build.zig file.

    // build.zig
    
    const std = @import("std");
    
    pub fn build(b: *std.build.Builder) !void {
        const target = b.standardTargetOptions(.{});
    
        const mode = b.standardReleaseOptions();
    
        const exe_options = b.addOptions();
    
        const mem_leak_frames: u32 = b.option(u32, "mem-leak-frames", "How many stack frames to print when a memory leak occurs. Tests get 2x this amount.") orelse 4;
        const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;
    
        const enable_logging = b.option(bool, "log", "Whether to enable logging") orelse false;
        const enable_link_snapshots = b.option(bool, "link-snapshot", "Whether to enable linker state snapshots") orelse false;
    
        exe_options.addOption(u32, "mem_leak_frames", mem_leak_frames);
        exe_options.addOption(bool, "skip_non_native", skip_non_native);
        exe_options.addOption(bool, "have_llvm", false);
        exe_options.addOption(bool, "llvm_has_m68k", false);
        exe_options.addOption(bool, "llvm_has_csky", false);
        exe_options.addOption(bool, "llvm_has_ve", false);
        exe_options.addOption(bool, "llvm_has_arc", false);
    
        const version = "0.0.0";
    
        exe_options.addOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version));
    
        const semver = try std.SemanticVersion.parse(version);
        exe_options.addOption(std.SemanticVersion, "semver", semver);
    
        exe_options.addOption(bool, "enable_logging", enable_logging);
        exe_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);
        exe_options.addOption(bool, "enable_tracy", false);
        exe_options.addOption(bool, "enable_tracy_callstack", false);
        exe_options.addOption(bool, "enable_tracy_allocation", false);
        exe_options.addOption(bool, "is_stage1", false);
        exe_options.addOption(bool, "omit_stage2", false);
    
        const exe = b.addExecutable("tmp", "sample.zig");
        exe.setTarget(target);
        exe.setBuildMode(mode);
        exe.addOptions("build_options", exe_options);
        exe.addPackage(.{
            .name = "zig",
            .path = .{ .path = "src/main.zig" },
            .dependencies = &[_]std.build.Pkg{
                .{ .name = "build_options", .path = exe_options.getSource() },
            },
        });
        exe.install();
    
        const run_cmd = exe.run();
        run_cmd.step.dependOn(b.getInstallStep());
        if (b.args) |args| {
            run_cmd.addArgs(args);
        }
    
        const run_step = b.step("run", "Run the app");
        run_step.dependOn(&run_cmd.step);
    }
    
    // sample.zig
    
    const zig = @import("zig");
    
    pub fn main() !void {
        return zig.main(); // just calls into the zig compiler main function. you'll have to look into how https://github.com/ziglang/zig/blob/master/src/main.zig works in order to do more complicated stuff.
    }
    

    note: I recommend building this with -Dskip-non-native otherwise it will take a very long time to build due to the speed of the current stage1 compiler

    zig build run -Dskip-non-native -- build-exe demo.zig