A maildir and Maildir++ library for Zig 0.16: delivery, flags, folders and quota.
0

Configure Feed

Select the types of activity you want to include in your feed.

zig-maildir / build.zig
7.7 kB 173 lines
1// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us> 2// SPDX-License-Identifier: MIT 3 4const std = @import("std"); 5 6pub fn build(b: *std.Build) void { 7 const target = b.standardTargetOptions(.{}); 8 const optimize = b.standardOptimizeOption(.{}); 9 10 // Email messages, which this library reads and writes but deliberately 11 // does not parse: <https://git.jcollie.dev/jeff/zig-mime>. A maildir is a 12 // naming convention over a directory, and what it stores is somebody 13 // else's subject. 14 const mime = b.dependency("mime", .{ 15 .target = target, 16 .optimize = optimize, 17 }); 18 19 const mod = b.addModule("maildir", .{ 20 .root_source_file = b.path("src/root.zig"), 21 .target = target, 22 .imports = &.{.{ .name = "mime", .module = mime.module("mime") }}, 23 }); 24 25 // The command-line tool, which exists to show what the library looks like 26 // from outside and to give the NixOS tests a second program to point at a 27 // maildir Dovecot is also looking at. 28 const exe = b.addExecutable(.{ 29 .name = "zig-maildir", 30 .root_module = b.createModule(.{ 31 .root_source_file = b.path("src/main.zig"), 32 .target = target, 33 .optimize = optimize, 34 .imports = &.{ 35 .{ .name = "maildir", .module = mod }, 36 .{ .name = "mime", .module = mime.module("mime") }, 37 }, 38 }), 39 }); 40 b.installArtifact(exe); 41 42 const run_cmd = b.addRunArtifact(exe); 43 run_cmd.step.dependOn(b.getInstallStep()); 44 run_cmd.stdio = .inherit; 45 if (b.args) |args| run_cmd.addArgs(args); 46 const run_step = b.step("run", "Run the command-line tool"); 47 run_step.dependOn(&run_cmd.step); 48 49 // A test executable covers one module, so each needs its own. Missing one 50 // out would not fail: its tests would simply never run. 51 const test_step = b.step("test", "Run tests"); 52 for ([_]*std.Build.Module{ mod, exe.root_module }) |m| { 53 test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = m })).step); 54 } 55 56 // What the library does to a real directory: delivery, flags, folders and 57 // the quota ledger, all against a temporary maildir. A module of its own 58 // so that `zig build test` runs them and a consumer never compiles them. 59 const e2e_mod = b.createModule(.{ 60 .root_source_file = b.path("tests/e2e.zig"), 61 .target = target, 62 .optimize = optimize, 63 .imports = &.{ 64 .{ .name = "maildir", .module = mod }, 65 .{ .name = "mime", .module = mime.module("mime") }, 66 }, 67 }); 68 test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = e2e_mod })).step); 69 70 // The fuzz targets: what the name, flag, folder and quota parsers must do 71 // with input nobody wrote. They are ordinary tests as well as fuzz 72 // targets, so `zig build test` exercises the same properties on the seeds 73 // checked in beside them. 74 const fuzz_mod = b.createModule(.{ 75 .root_source_file = b.path("tests/fuzz.zig"), 76 .target = target, 77 .optimize = optimize, 78 .imports = &.{.{ .name = "maildir", .module = mod }}, 79 }); 80 // Zig's fuzzer takes one test at a time and keeps a coverage file per 81 // test, so naming a target is what you want when a finding is being 82 // chased: `zig build fuzz --fuzz -Dfuzz-filter=name`. 83 const fuzz_filter = b.option( 84 []const u8, 85 "fuzz-filter", 86 "Fuzz or test only the targets whose name contains this", 87 ); 88 const fuzz_tests = b.addTest(.{ 89 .root_module = fuzz_mod, 90 .filters = if (fuzz_filter) |f| &.{f} else &.{}, 91 }); 92 test_step.dependOn(&b.addRunArtifact(fuzz_tests).step); 93 94 // A step of its own for `zig build fuzz --fuzz`, which needs a run step 95 // holding nothing else: the fuzzer takes over the terminal and runs until 96 // it is stopped, so it must not be reached by `zig build test`. 97 const fuzz_step = b.step("fuzz", "The fuzz targets: add --fuzz to fuzz them"); 98 fuzz_step.dependOn(&b.addRunArtifact(fuzz_tests).step); 99 100 // The loop that drives those same targets without Zig's fuzzer, which 101 // this toolchain cannot usefully run: `tools/fuzz.zig` says why, and the 102 // short version is that the coverage table comes back empty. Optimised, 103 // because a fuzzer's whole job is how many inputs it gets through, and 104 // ReleaseSafe keeps every check that makes a failure a failure. 105 const fuzz_run = b.addExecutable(.{ 106 .name = "zig-maildir-fuzz", 107 .root_module = b.createModule(.{ 108 .root_source_file = b.path("tools/fuzz.zig"), 109 .target = b.graph.host, 110 .optimize = .ReleaseSafe, 111 .imports = &.{.{ .name = "fuzz_targets", .module = fuzz_mod }}, 112 }), 113 }); 114 const run_fuzz = b.addRunArtifact(fuzz_run); 115 run_fuzz.stdio = .inherit; 116 if (b.args) |a| run_fuzz.addArgs(a); 117 const fuzz_run_step = b.step("fuzz-run", "Fuzz the targets with a loop of our own"); 118 fuzz_run_step.dependOn(&run_fuzz.step); 119 120 // Nothing else builds the fuzz driver, so without this it could stop 121 // compiling and `zig build test` would not notice. 122 const check_step = b.step("check", "Compile everything without running it"); 123 check_step.dependOn(&fuzz_run.step); 124 125 // -- documentation ------------------------------------------------------- 126 // 127 // Zig emits the API documentation as a side effect of compiling, so the 128 // module is built as a library purely to get at it. What comes out is not 129 // a page but a program: a WebAssembly viewer, its javascript, and a tar of 130 // the sources it reads from. 131 const library = b.addLibrary(.{ .name = "maildir", .root_module = mod }); 132 const install_docs = b.addInstallDirectory(.{ 133 .source_dir = library.getEmittedDocs(), 134 .install_dir = .prefix, 135 .install_subdir = "docs", 136 }); 137 const docs_step = b.step("docs", "Build the API documentation into zig-out/docs"); 138 docs_step.dependOn(&install_docs.step); 139 140 // That viewer fetches `sources.tar` and `main.wasm` at runtime, which a 141 // browser refuses to do from a `file://` page, so reading the docs 142 // locally means serving them. It is the same reason `zig std` runs a 143 // server rather than opening a file. 144 const docs_port = b.option(u16, "docs-port", "Port for `zig build docs-serve` (default 8000)") orelse 8000; 145 146 const docs_server = b.addExecutable(.{ 147 .name = "docs-server", 148 .root_module = b.createModule(.{ 149 .root_source_file = b.path("tools/docs_server.zig"), 150 // Always built for the machine running the build, never for 151 // whatever -Dtarget the library is being built for. 152 .target = b.graph.host, 153 .optimize = .Debug, 154 }), 155 }); 156 157 const run_docs_server = b.addRunArtifact(docs_server); 158 run_docs_server.step.dependOn(&install_docs.step); 159 run_docs_server.addArg(b.getInstallPath(.prefix, "docs")); 160 run_docs_server.addArg(b.fmt("{d}", .{docs_port})); 161 // The server runs until interrupted, so its output has to reach the 162 // terminal rather than being captured by the build runner. 163 run_docs_server.stdio = .inherit; 164 165 const docs_serve_step = b.step("docs-serve", "Serve the API documentation over HTTP"); 166 docs_serve_step.dependOn(&run_docs_server.step); 167 168 // The server has tests of its own; without this they would never run. 169 test_step.dependOn(&b.addRunArtifact( 170 b.addTest(.{ .root_module = docs_server.root_module }), 171 ).step); 172 check_step.dependOn(&docs_server.step); 173}