I couldn’t help myself, here’s the fixed version that properly checks if the package has already been fetched and doesn’t try to download it from the network every time:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const stb = ZigFetch.create(b, .{
.url = "https://github.com/nothings/stb/archive/31707d14fdb75da66b3eed52a2236a70af0d0960.tar.gz",
.hash = "1220aefdc5ff6261afb86675b54f987d9e86c575049b2050ee8f23d49c954ff4970a",
});
const exe = b.addExecutable(.{
.name = "example",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.addIncludePath(stb.getLazyPath());
exe.linkLibC();
b.installArtifact(exe);
}
const ZigFetchOptions = struct {
url: []const u8,
hash: []const u8,
};
const ZigFetch = struct {
step: std.Build.Step,
url: []const u8,
hash: []const u8,
already_fetched: bool,
pkg_path_dont_use_me_directly: []const u8,
lazy_fetch_stdout: std.Build.LazyPath,
generated_directory: std.Build.GeneratedFile,
pub fn create(b: *std.Build, opt: ZigFetchOptions) *ZigFetch {
const run = b.addSystemCommand(&.{ b.graph.zig_exe, "fetch", opt.url });
const fetch = b.allocator.create(ZigFetch) catch @panic("OOM");
const pkg_path = b.pathJoin(&.{
b.graph.global_cache_root.path.?,
"p",
opt.hash,
});
const already_fetched = if (std.fs.cwd().access(pkg_path, .{}))
true
else |err| switch (err) {
error.FileNotFound => false,
else => |e| std.debug.panic("access '{s}' failed with {s}", .{pkg_path, @errorName(e)}),
};
fetch.* = .{
.step = std.Build.Step.init(.{
.id = .custom,
.name = b.fmt("zig fetch {s}", .{opt.url}),
.owner = b,
.makeFn = make,
}),
.url = b.allocator.dupe(u8, opt.url) catch @panic("OOM"),
.hash = b.allocator.dupe(u8, opt.hash) catch @panic("OOM"),
.pkg_path_dont_use_me_directly = pkg_path,
.already_fetched = already_fetched,
.lazy_fetch_stdout = run.captureStdOut(),
.generated_directory = .{
.step = &fetch.step,
},
};
if (!already_fetched) {
fetch.step.dependOn(&run.step);
}
return fetch;
}
pub fn getLazyPath(self: *const ZigFetch) std.Build.LazyPath {
return .{ .generated = .{ .file = &self.generated_directory } };
}
pub fn path(self: *ZigFetch, sub_path: []const u8) std.Build.LazyPath {
return self.getLazyPath().path(self.step.owner, sub_path);
}
fn make(step: *std.Build.Step, prog_node: std.Progress.Node) !void {
_ = prog_node;
const b = step.owner;
const fetch: *ZigFetch = @fieldParentPtr("step", step);
if (!fetch.already_fetched) {
const sha = blk: {
var file = try std.fs.openFileAbsolute(fetch.lazy_fetch_stdout.getPath(b), .{});
defer file.close();
break :blk try file.readToEndAlloc(b.allocator, 999);
};
const sha_stripped = std.mem.trimRight(u8, sha, "\r\n");
if (!std.mem.eql(u8, sha_stripped, fetch.hash)) return step.fail(
"hash mismatch: declared {s} but the fetched package has {s}",
.{ fetch.hash, sha_stripped },
);
}
fetch.generated_directory.path = fetch.pkg_path_dont_use_me_directly;
}
};