Skip to content

demo

A program is finished only when another machine can build and run it reliably.

Packaging the Result

A program is finished only when another machine can build and run it reliably.

Packaging means organizing source code, build logic, tests, dependencies, documentation, and release artifacts into a form that others can use.

A small Zig project usually begins like this:

project/
    build.zig
    build.zig.zon
    src/
        main.zig

build.zig describes how to build the project.

build.zig.zon describes package metadata and dependencies.

src/main.zig contains the program entry point.

A minimal build.zig.zon looks like this:

.{
    .name = "demo",
    .version = "0.1.0",
    .minimum_zig_version = "0.16.0",
    .dependencies = .{},
}

The fields are:

FieldMeaning
.namepackage name
.versionpackage version
.minimum_zig_versionminimum supported Zig version
.dependenciespackage dependencies

The build script describes the build graph.

const std = @import("std");

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

    const exe = b.addExecutable(.{
        .name = "demo",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);

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

    const run_step = b.step("run", "Run the application");
    run_step.dependOn(&run_cmd.step);
}

The project can now be built and run with standard commands.

zig build
zig build run

The executable is installed into Zig’s output directory.

Release builds should specify an optimization mode.

zig build -Doptimize=ReleaseFast

Cross compilation is built into Zig. The same source tree can target another operating system or CPU architecture.

zig build -Dtarget=x86_64-linux

A packaged project should define clear outputs:

OutputExample
executabledemo
static librarylibdemo.a
testszig build test
documentationREADME.md

Documentation does not need to be large. It should answer four questions quickly:

  1. What does the program do?
  2. How is it built?
  3. How is it run?
  4. What Zig version is required?

A small README.md is enough.

# demo

Small example Zig application.

## Build

    zig build

## Run

    zig build run

## Release build

    zig build -Doptimize=ReleaseFast

Dependencies belong in build.zig.zon.

Suppose a package named httpz is added.

.{
    .name = "demo",
    .version = "0.1.0",
    .minimum_zig_version = "0.16.0",

    .dependencies = .{
        .httpz = .{
            .url = "https://example.com/httpz.tar.gz",
            .hash = "1220...",
        },
    },
}

The build script imports the dependency.

const httpz = b.dependency("httpz", .{
    .target = target,
    .optimize = optimize,
});

Then its module is added to the executable.

exe.root_module.addImport(
    "httpz",
    httpz.module("httpz"),
);

A package should also define tests as build steps.

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

const run_tests = b.addRunArtifact(tests);

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

Now every important operation has one command:

CommandPurpose
zig buildbuild
zig build runrun
zig build testtest
zig build -Doptimize=ReleaseFastoptimized build

A release artifact should contain only what users need:

  • executable or library
  • license
  • README
  • version information

Do not package temporary build directories or editor files.

Programs that expose command-line interfaces should support:

--help
--version

A simple version flag may be enough.

const std = @import("std");

pub fn main() !void {
    var args = std.process.args();

    _ = args.next();

    if (args.next()) |arg| {
        if (std.mem.eql(u8, arg, "--version")) {
            std.debug.print("demo 0.1.0\n", .{});
            return;
        }
    }

    std.debug.print("normal execution\n", .{});
}

Real packaging also includes compatibility concerns:

ConcernExample
Zig version0.16-specific APIs
target platformLinux, macOS, Windows
libc usageglibc, musl, none
architecturex86_64, ARM64
optimization modedebug vs release

These choices should be visible in documentation and build scripts.

A Zig project scales well when it keeps several properties:

  • explicit build steps
  • explicit dependencies
  • explicit allocators
  • explicit error handling
  • small source files
  • testable functions

The same structure works for small tools and large systems.

Exercise 20-36. Add a --version flag to an earlier program.

Exercise 20-37. Create a README.md for the file copier.

Exercise 20-38. Add a test step to a packaged project.

Exercise 20-39. Build the project for another operating system.

Exercise 20-40. Split a project into multiple source files and modules.