*ฅ^•ﻌ•^ฅ* ✨✨  HWisnu's blog  ✨✨ о ฅ^•ﻌ•^ฅ

Zig Package Manager

Introduction

There's not a lot of references about Zig Package Manager and the ones that exist in my opinion are either too long or too complex / not simple enough to be understood.

Existing references

  1. WTF is Zon
  2. WTF is build.zig.zon
  3. Youtube vid-1 : WTF is build.zig
  4. Youtube vid-2 : Zig in Depth: Build Dependencies

Read and watch these references to get an understanding how Zig Package Manager works ~ spoiler: it does just what a package manager do! With some quirks and twists!

Package repository

As usual the main repo: Github, Gitlab, Codeberg, etc. However there are some websites that tries to be like the NPM of JS or Crate of Rust: zig.pm and zigistry.dev.

However not all available Zig packages are listed on either two of them, so if you cannot find the package you need please try Github or Codeberg instead.

Simplified ZPM method

I am one of those who dislike having to create a specific directories for each project. So in my starter/ Zig directory I created other directories inside it and throw everything inside categorically. tree-zig

Below is the misc/ directory, where I usually throw in tests / prototypes / throwaway codes: tree-zig-1

Zig files without external modules don't need ZPM, in this exercise I will show you how to manage importing external modules using two ZPM cores: build.zig and build.zig.zon

Managing external modules

We are going to use zcsv, zbench and ziglyph as the external modules we are going to install and import into our projects. First we need to open build.zig.zon and add the modules required. I've deleted most of the comments to save space.

If you're wondering what these comments are, when you initialize a Zig project, you'll be automatically provided build.zig and build.zig.zon complete with the comments.

build.zig.zon
Use this command to fetch the Zig package: zig fetch --save <package#branchTag>
Pay closer attention to the #branchTag, if you want the main branch then package#main but if you want to specify a certain tag: package#V0.7.1

For example in zig_csv get to the installation section in the Github page:

zig-fetch

Do the same for all of your dependencies, in my case: zbench and ziglyph.

.{
    .name = "starter",
    .version = "0.0.0",

    .dependencies = .{
        .zcsv = .{
            .url = "git+https://github.com/matthewtolman/zig_csv?ref=main#2d0ccd9e853114853e5e0cf671297d5a1654b01e",
            .hash = "12206e76339a58db8ba53a67d7161eb4744144709815f7516b83fc51ede86fb6b544",
        },
        .zbench = .{
            .url = "https://github.com/hendriknielaender/zbench/archive/0c595ab.tar.gz", 
            .hash = "1220b19e117897a42abc5e58633766a3be92f9ebade400116afea57957bebe41aed0" },
        .ziglyph = .{
            .url = "https://codeberg.org/dude_the_builder/ziglyph/archive/v0.13.1.tar.gz",
            .hash = "12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25",
        },
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

Next let's deal with build.zig

const std = @import("std");

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

    const opts = .{ .target = target, .optimize = optimize };
    const zbench = b.dependency("zbench", opts);//.module("zbench");
    const zcsv = b.dependency("zcsv", opts);
    const ziglyph = b.dependency("ziglyph", opts);

    // 1. Static library (starter):
    const lib = b.addStaticLibrary(.{
        .name = "starter",
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });

    b.installArtifact(lib);

    // 2. Main executable:
    const exe = b.addExecutable(.{
        .name = "starter",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    exe.root_module.addImport("zbench", zbench.module("zbench"));
    exe.root_module.addImport("zcsv", zcsv.module("zcsv"));
    exe.root_module.addImport("ziglyph", ziglyph.module("ziglyph"));

    b.installArtifact(exe);

    // 3. Run step for the main executable
    const run_cmd = b.addRunArtifact(exe);

    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);

    // 4. Executables for test_zbench:
    const test_zbench_exe = b.addExecutable(.{
        .name = "test_zbench",
        .root_source_file = b.path("misc/test_zbench.zig"),
        .target = target,
        .optimize = optimize,
    });
    test_zbench_exe.root_module.addImport("zbench", zbench.module("zbench"));
    b.installArtifact(test_zbench_exe);

    // 5. Run step for test_zbench:
    const run_test_zbench_cmd = b.addRunArtifact(test_zbench_exe);
    run_test_zbench_cmd.step.dependOn(b.getInstallStep());
    const run_test_zbench_step = b.step("run_zbench", "Run the test zbench app");
    run_test_zbench_step.dependOn(&run_test_zbench_cmd.step);

    // 6. Executables for test_zcsv:
    const test_zcsv_exe = b.addExecutable(.{
        .name = "test_zcsv",
        .root_source_file = b.path("misc/test_zcsv.zig"),
        .target = target,
        .optimize = optimize,
    });
    test_zcsv_exe.root_module.addImport("zcsv", zcsv.module("zcsv"));
    b.installArtifact(test_zcsv_exe);

    // 7. Run step for test_zbench:
    const run_test_zcsv_cmd = b.addRunArtifact(test_zcsv_exe);
    run_test_zcsv_cmd.step.dependOn(b.getInstallStep());
    const run_test_zcsv_step = b.step("run_zcsv", "Run the test zcsv app");
    run_test_zcsv_step.dependOn(&run_test_zcsv_cmd.step);

    // Creates a step for unit testing. This only builds the test executable
    // but does not run it.
    const lib_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });

    const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);

    const exe_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);

    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_lib_unit_tests.step);
    test_step.dependOn(&run_exe_unit_tests.step);
}

Please note the highlights provided on the images:

Pay a closer attention to each of the root_source_file --> this is the path where you put your zig project file. I put mine in the misc/ directory.

The pattern is clear and the same each time you want to set up executables and run step for your next projects requiring external modules.

Time to BUILD!

Execute the command: zig build to start compiling your projects. zig-build-0

Once done, go to your zig-out/ dir: zig-out-0

Voila! The executables of your projects!

Conclusion

This concludes the Zig Package Manager post, as you can see it's not as intuitive compared to Rust's Cargo or Python's UV or Typescript's Bun. But as always, it's only difficult the first time you try it. Once you get the gist of it, they functions similarly.