Hey,
I wondered if anyone might be able to help me understand the behavior of the build system, specifically when trying to use it in the style of Zine or the Tigerbeetle docs: Why We Designed TigerBeetle's Docs from Scratch.
My build.zig currently looks like this:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimise = b.standardOptimizeOption(.{});
const server = b.addExecutable(.{ .name = "boringsite", .root_module = b.createModule(.{
.root_source_file = b.path("src/server.zig"),
.target = target,
.optimize = optimise,
}) });
b.installArtifact(server);
const run_server = b.addRunArtifact(server);
b.step("serve", "Build site and run the server").dependOn(&run_server.step);
const pagegen = b.addExecutable(.{ .name = "pagegen", .root_module = b.createModule(.{
.root_source_file = b.path("src/pagegen.zig"),
.target = b.graph.host,
}) });
const website = b.addWriteFiles();
// TODO: make this walk the pages dir
const pages = [_][]const u8{ "index", "about" };
for (pages) |page| {
const out_name = b.fmt("{s}.html", .{page});
const gen = b.addRunArtifact(pagegen);
gen.addFileArg(b.path("src/partials/_header.html"));
gen.addFileArg(b.path(b.fmt("site/{s}.html", .{page})));
gen.addFileArg(b.path("src/partials/_footer.html"));
const out = gen.addOutputFileArg(out_name);
_ = website.addCopyFile(out, out_name);
}
_ = website.addCopyFile((b.path("src/style.css")), "style.css");
b.installDirectory(.{
.source_dir = website.getDirectory(),
.install_dir = .prefix,
.install_subdir = "site",
});
}
which, for now, takes a couple of HTML files sans headers and footers, sandwiches them with the missing content and serves them like so:
// server.zig
const std = @import("std");
const log = std.log.scoped(.boringsite);
pub fn main(init: std.process.Init) !void {
const io = init.io;
const alloc = init.gpa;
const addr = std.Io.net.IpAddress.parseIp4("127.0.0.1", 8000) catch unreachable;
var server = try addr.listen(io, .{ .reuse_address = true });
defer server.deinit(io);
log.info("listening on http://127.0.0.1:8000", .{});
while (true) {
var stream = try server.accept(io);
defer stream.close(io);
var read_buf: [4096]u8 = undefined;
var write_buf: [4096]u8 = undefined;
var reader = stream.reader(io, &read_buf);
var writer = stream.writer(io, &write_buf);
var http_server = std.http.Server.init(&reader.interface, &writer.interface);
while (true) {
var request = http_server.receiveHead() catch break;
handle(alloc, io, &request) catch |err| {
log.err("request failed: {}", .{err});
break;
};
}
}
}
fn handle(alloc: std.mem.Allocator, io: std.Io, req: *std.http.Server.Request) !void {
const method = req.head.method;
const target = try alloc.dupe(u8, req.head.target);
if (method == .GET)
return serveStatic(alloc, io, req, target);
try respondNotFound(req);
}
fn respondNotFound(req: *std.http.Server.Request) !void {
try req.respond("<h1>404 Not Found</h1>", .{
.status = .not_found,
.extra_headers = &.{
.{ .name = "content-type", .value = "text/html; charset=utf-8" },
},
});
}
fn serveStatic(
alloc: std.mem.Allocator,
io: std.Io,
req: *std.http.Server.Request,
target: []const u8,
) !void {
var rel = if (std.mem.eql(u8, target, "/")) "index.html" else target[1..];
if (std.mem.findScalar(u8, rel, '.') == null)
rel = try std.fmt.allocPrint(alloc, "{s}.html", .{rel});
const path = try std.fmt.allocPrint(alloc, "zig-out/site/{s}", .{rel});
const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch
return respondNotFound(req);
defer file.close(io);
var buf: [4096]u8 = undefined;
var file_reader = file.reader(io, &buf);
const data = try file_reader.interface.allocRemaining(alloc, .unlimited);
const content_type = if (std.mem.endsWith(u8, rel, ".css"))
"text/css"
else
"text/html; charset=utf-8";
try req.respond(data, .{ .extra_headers = &.{
.{ .name = "content-type", .value = content_type },
} });
}
My expectation was that running zig build serve --watch would both serve the site and result in an automatic update for any HTML if the relevant header/footer free HTML source file is updated, but that doesn’t happen, I’m guessing because server.zig contains an infinite loop, and the --watch flag only matters once the program as a whole terminates (right?).
My solution has been to open two tabs and simultaneously run zig build --watch and zig build serve, but this feels wrong. What am I missing?