🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Project: Build Tool

Build automation is a set of ordered steps that turn source code into a finished result without manual commands. This lesson builds a small task runner that runs steps in order and names the step that fails.

Run steps in order

The runner walks the step list from first to last. Every step runs, then the runner reports that all steps passed.

const std = @import("std");
pub fn main() !void {
    var out = std.io.getStdOut().writer();
    const Step = struct { name: []const u8, ok: bool };
    const steps = [_]Step{ .{ .name = "compile", .ok = true }, .{ .name = "test", .ok = true }, .{ .name = "package", .ok = true } };
    for (steps) |step| {
        try out.print("run {s}\n", .{step.name});
    }
    try out.print("all steps passed\n", .{});
}

Stop at the first failure

When a step fails, the runner names it and stops. Later steps never run, so a broken result is never packaged.

const std = @import("std");
pub fn main() !void {
    var out = std.io.getStdOut().writer();
    const Step = struct { name: []const u8, ok: bool };
    const steps = [_]Step{ .{ .name = "compile", .ok = true }, .{ .name = "test", .ok = false }, .{ .name = "package", .ok = true } };
    for (steps) |step| {
        try out.print("run {s}\n", .{step.name});
        if (!step.ok) {
            try out.print("failed: {s}\n", .{step.name});
            break;
        }
    }
}

Count passes and failures

A runner that continues past failures still names each one. At the end it prints how many steps passed and how many failed.

const std = @import("std");
pub fn main() !void {
    var out = std.io.getStdOut().writer();
    const Step = struct { name: []const u8, ok: bool };
    const steps = [_]Step{ .{ .name = "compile", .ok = true }, .{ .name = "test", .ok = false }, .{ .name = "package", .ok = true } };
    var passed: usize = 0;
    var failed: usize = 0;
    for (steps) |step| {
        if (step.ok) {
            passed += 1;
        } else {
            failed += 1;
            try out.print("failed: {s}\n", .{step.name});
        }
    }
    try out.print("passed {d}\n", .{passed});
    try out.print("failed {d}\n", .{failed});
}

Example

const std = @import("std");
pub fn main() !void {
    var out = std.io.getStdOut().writer();
    const Step = struct { name: []const u8, ok: bool };
    const steps = [_]Step{ .{ .name = "fetch", .ok = true }, .{ .name = "compile", .ok = true }, .{ .name = "test", .ok = false }, .{ .name = "package", .ok = true } };
    var completed: usize = 0;
    var failed: usize = 0;
    for (steps) |step| {
        try out.print("run {s}\n", .{step.name});
        if (!step.ok) {
            failed += 1;
            try out.print("failed: {s}\n", .{step.name});
            break;
        }
        completed += 1;
    }
    try out.print("completed {d}\n", .{completed});
    try out.print("failed {d}\n", .{failed});
}

Key points

  • Task: a task is one named step in a build, such as compiling or testing.
  • Task runner: a task runner is a program that runs tasks in a fixed order.
  • Order: order is the fixed sequence in which tasks run.
  • Failure: a failure is a task that reports it could not finish.
  • Stop on failure: stop on failure is the rule that later tasks do not run after a failed task.
  • Summary: a summary is a short count of tasks that passed and tasks that failed.

The runner executes fetch, then compile, then test, in that fixed order. The test step fails, so the runner names it and stops before packaging. The summary counts the two completed steps and the one failure.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What does the runner do when the test step fails?