Repository navigation
Expand file tree
/
Copy pathbuild.zig
More file actions
291 lines (264 loc) · 13.6 KB
/
Copy pathbuild.zig
File metadata and controls
291 lines (264 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
//! Build graph for stdx (docs/design.md §8 step 0): `zig build` compiles every module,
//! `zig build lint` scores every function's cognitive complexity and runs the rules of tools/lint
//! over the tree, `zig build test` runs the lint and then every module's unit tests, and
//! `zig build test-<module>` runs one module's tests with nothing else in the graph, which is
//! what a mutation is measured against.
//!
//! `zig build graph-check` is step 0's own check: it compiles sources that import a wrapper or
//! another codec from inside `src/deflate/` and requires each compile to fail, which shows the
//! module graph of design §3 is enforced by the build rather than by review.
//!
//! `zig build lint-commits` checks the commit messages this branch adds and `zig build hooks`
//! points this clone's core.hooksPath at .githooks; neither is part of `zig build test`, because
//! commit shape is a property of the history, not of the code.
//!
//! The library has no dependencies (CLAUDE.md, Ask before). The tools take one: pepegrillo, a lazy
//! package in build.zig.zon that only the root build requests, so a project depending on stdx
//! never fetches it (decision 7). The module graph is build/modules.zig.
const std = @import("std");
const assert = std.debug.assert;
const guide = @import("build/guide.zig");
const modules = @import("build/modules.zig");
const lint = @import("build/lint.zig");
const oracle = @import("build/oracle.zig");
const self_hosted = @import("build/self_hosted.zig");
const avx512 = @import("build/avx512.zig");
const brotli_tables = @import("build/brotli_tables.zig");
const lean = @import("build/lean.zig");
const platform_check = @import("build/platform_check.zig");
const checkfree = @import("build/checkfree.zig");
/// Every directory `zig build lint` scores and `zig build fmt` checks, beside build.zig itself.
const source_directories = [_][]const u8{ "bench", "build", "src", "tools" };
/// Every directory the tools/lint rules read: the sources above plus the documents, which the
/// markdown rule covers.
const lint_rule_directories = [_][]const u8{ "bench", "build", "src", "tools", "docs" };
/// Every Markdown file at the top of the tree, which the markdown rule reads beside `docs/`.
const lint_rule_files = [_][]const u8{ "CLAUDE.md", "README.md" };
/// Every tool built on pepegrillo whose own tests `zig build test` runs. A build that does not run
/// the checkers' own tests lets a rule lose its own test without the build reporting it.
const tool_test_roots = [_][]const u8{
"tools/lint/main.zig",
"tools/cognitive_complexity.zig",
"tools/commit_lint.zig",
"tools/checkfree.zig",
};
/// Every tool that needs neither pepegrillo nor a package, whose own tests `zig build test` runs.
const plain_tool_test_roots = [_][]const u8{
"tools/graph_check.zig",
"tools/corpus/cut.zig",
"tools/brotli_tables.zig",
"bench/costs/costs.zig",
};
/// The git revision range `zig build lint-commits` checks.
const commit_lint_range = "origin/main..HEAD";
/// The directory `zig build hooks` points this clone's core.hooksPath at.
const hooks_directory = ".githooks";
/// The pre-push hook: a copy of pepegrillo's, which `zig build test` compares byte for byte.
const pre_push_hook = hooks_directory ++ "/pre-push";
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
// Assertions stay on in production (CLAUDE.md, Non-negotiables), so the build offers Debug and
// ReleaseSafe only: `-Drelease` selects ReleaseSafe, and the `-Doptimize` option that would
// admit ReleaseFast or ReleaseSmall is never declared.
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSafe });
assert(optimize == .Debug or optimize == .ReleaseSafe);
const graph = modules.add(b, .{ .target = target, .optimize = optimize, .visibility = .exported });
// Everything below is stdx's own build: the tests, the checks and the tools. A project that
// depends on stdx stops here, before the tools request pepegrillo.
if (b.pkg_hash.len != 0) return;
const pepegrillo_dependency = b.lazyDependency("pepegrillo", .{}) orelse return;
const pepegrillo = pepegrillo_dependency.module("pepegrillo");
guide.add_guide_step(b, pepegrillo_dependency);
const install_step = b.getInstallStep();
const test_step = b.step("test", "Run the lint, then every module's unit tests");
test_step.dependOn(lint.add(b, .{
.source_directories = &source_directories,
.rule_directories = &lint_rule_directories,
.rule_files = &lint_rule_files,
.complexity = b.addExecutable(.{
.name = "cognitive_complexity",
.root_module = tool_module(b, pepegrillo, "tools/cognitive_complexity.zig"),
}),
.rules = b.addExecutable(.{
.name = "lint",
.root_module = tool_module(b, pepegrillo, "tools/lint/main.zig"),
}),
}));
const unit_test_modules = [_]struct { name: []const u8, module: *std.Build.Module }{
.{ .name = "codec", .module = graph.codec },
.{ .name = "checksum", .module = graph.checksum },
.{ .name = "deflate", .module = graph.deflate },
.{ .name = "zlib", .module = graph.zlib },
.{ .name = "gzip", .module = graph.gzip },
.{ .name = "zstd", .module = graph.zstd },
.{ .name = "brotli", .module = graph.brotli },
.{ .name = "json", .module = graph.json },
.{ .name = "base64", .module = graph.base64 },
.{ .name = "platform", .module = graph.platform },
};
for (unit_test_modules) |entry| {
// LLVM compiles the tests, as it compiles every release build: its assembler takes the fast
// paths' assembly (decision 23), and Zig's own x86-64 backend, the Debug default there,
// takes none, which would leave that assembly untested.
const unit_tests = b.addTest(.{ .name = entry.name, .root_module = entry.module, .use_llvm = true });
install_step.dependOn(&unit_tests.step);
const run = &b.addRunArtifact(unit_tests).step;
test_step.dependOn(run);
add_narrow_test_step(b, entry.name).dependOn(run);
}
// The tools verify the tree, so they run on the host in Debug: a tool never ships.
const tool_test_step = add_narrow_test_step(b, "tools");
for (tool_test_roots) |root| {
const tool_tests = b.addTest(.{
.name = std.fs.path.stem(root),
.root_module = tool_module(b, pepegrillo, root),
});
const run = &b.addRunArtifact(tool_tests).step;
test_step.dependOn(run);
tool_test_step.dependOn(run);
}
for (plain_tool_test_roots) |root| {
const tool_tests = b.addTest(.{
.name = std.fs.path.stem(root),
.root_module = host_module(b, root),
});
const run = &b.addRunArtifact(tool_tests).step;
test_step.dependOn(run);
tool_test_step.dependOn(run);
}
// The tests again, built by Zig's own x86-64 backend, the Debug default on x86-64 Linux; and
// built by LLVM in Debug for a CPU with AVX-512.
_ = self_hosted.add(b);
_ = avx512.add(b);
// Decisions 8 and 15: the oracles and the corpora are lazy packages, requested only when the
// build is given -Doracles, so a plain `zig build test` fetches none of them.
add_costs_step(b);
oracle.add(b, .{
.enabled = b.option(bool, "oracles", "Fetch the oracles and the corpora (decisions 8, 15)") orelse false,
.encode_optimize = b.option(std.builtin.OptimizeMode, "encode-optimize", "The mode differential-encode builds stdx in (invariant 5)") orelse .ReleaseSafe,
});
test_step.dependOn(add_graph_check_step(b));
const platform_steps = platform_check.add(b);
test_step.dependOn(platform_steps.check);
test_step.dependOn(platform_steps.tests);
tool_test_step.dependOn(platform_steps.tests);
test_step.dependOn(brotli_tables.add(b, host_module(b, "tools/brotli_tables.zig")));
const host_graph = modules.add(b, .{ .target = b.graph.host, .optimize = .ReleaseFast, .visibility = .private });
test_step.dependOn(brotli_tables.add_budget_check(b, host_graph.brotli));
test_step.dependOn(add_hook_check_step(b, pepegrillo_dependency));
_ = checkfree.add(b, tool_module(b, pepegrillo, "tools/checkfree.zig"));
add_commit_lint_step(b, pepegrillo, install_step);
lean.add(b, tool_module(b, pepegrillo, "tools/lean.zig"));
add_hooks_step(b);
const fmt_step = b.step("fmt", "Check formatting of every Zig source");
fmt_step.dependOn(&b.addFmt(.{
.paths = &(.{"build.zig"} ++ source_directories),
.check = true,
}).step);
}
/// `zig build test-<name>`: the tests of one module, or of the tools, with nothing else in the
/// graph. `zig build test` is the check that must pass; these steps are the inner loop of a
/// mutation, which is run against the narrowest target that can catch it.
fn add_narrow_test_step(b: *std.Build, name: []const u8) *std.Build.Step {
return b.step(
b.fmt("test-{s}", .{name}),
b.fmt("Run the {s} tests alone, with nothing else in the graph", .{name}),
);
}
/// A module compiled for the build host in Debug: every tool, and nothing else.
fn host_module(b: *std.Build, root_source_file: []const u8) *std.Build.Module {
return b.createModule(.{
.root_source_file = b.path(root_source_file),
.target = b.graph.host,
.optimize = .Debug,
});
}
/// A host module that imports `pepegrillo`: every tool built on pepegrillo's engines.
fn tool_module(
b: *std.Build,
pepegrillo: *std.Build.Module,
root_source_file: []const u8,
) *std.Build.Module {
const module = host_module(b, root_source_file);
module.addImport("pepegrillo", pepegrillo);
return module;
}
/// `zig build graph-check`: design §8 step 0's check. A module can import only what
/// build/modules.zig gives it, and the way to show that is to try the imports that must fail.
/// `tools/graph_check.zig` first compiles the control `tools/fixtures/deflate_imports_codec.zig`
/// as a module of the `deflate` shape and requires it to compile, then compiles one fixture per
/// module `deflate` must not reach and requires each compile to fail. A check that asserted the
/// rule in a linter would only be checking what the source says; this checks what the build does.
fn add_graph_check_step(b: *std.Build) *std.Build.Step {
const check = b.addExecutable(.{
.name = "graph_check",
.root_module = host_module(b, "tools/graph_check.zig"),
});
const check_run = b.addRunArtifact(check);
check_run.addArg(b.graph.zig_exe);
check_run.addDirectoryArg(b.path("src"));
check_run.addDirectoryArg(b.path("tools/fixtures"));
// Re-run the check when the graph it checks changes, not only when the tool does.
check_run.addFileInput(b.path("build/modules.zig"));
const step = b.step("graph-check", "Require that src/deflate/ cannot import a wrapper or codec");
step.dependOn(&check_run.step);
return step;
}
/// `zig build hook-check`: .githooks/pre-push must be byte-identical to the hook of the pinned
/// pepegrillo. After a pepegrillo bump, copy the new hook over it.
fn add_hook_check_step(b: *std.Build, pepegrillo: *std.Build.Dependency) *std.Build.Step {
const compare = b.addSystemCommand(&.{"cmp"});
compare.addFileArg(pepegrillo.path("hooks/pre-push"));
compare.addFileArg(b.path(pre_push_hook));
const step = b.step(
"hook-check",
"Require " ++ pre_push_hook ++ " to match pepegrillo's hooks/pre-push; copy it when not",
);
step.dependOn(&compare.step);
return step;
}
/// `zig build lint-commits`: the Conventional Commit rules of CLAUDE.md over the commits this
/// branch adds. Not part of `zig build test`: commit shape is a property of the history.
/// `zig build install-commit-lint` installs the linter alone, which .githooks/pre-push runs when
/// zig-out/bin/commit_lint is missing.
fn add_commit_lint_step(
b: *std.Build,
pepegrillo: *std.Build.Module,
install_step: *std.Build.Step,
) void {
const tool = b.addExecutable(.{
.name = "commit_lint",
.root_module = tool_module(b, pepegrillo, "tools/commit_lint.zig"),
});
const install_tool = b.addInstallArtifact(tool, .{});
install_step.dependOn(&install_tool.step);
const install_tool_step = b.step("install-commit-lint", "Install the commit-message linter alone");
install_tool_step.dependOn(&install_tool.step);
const run = b.addRunArtifact(tool);
run.addArgs(&.{ "--range", commit_lint_range });
const step = b.step("lint-commits", "Check the commit messages this branch adds");
step.dependOn(&run.step);
}
/// `zig build costs`: the microbenchmarks behind docs/costs.md, built ReleaseFast for the host. It
/// is a measuring device, never the library (decision 17), and decision 20's workflow runs it on
/// each hosted runner, pinned to one core.
fn add_costs_step(b: *std.Build) void {
const costs = b.addExecutable(.{
.name = "costs",
.root_module = b.createModule(.{
.root_source_file = b.path("bench/costs/costs.zig"),
.target = b.graph.host,
.optimize = .ReleaseFast,
}),
});
b.installArtifact(costs);
const run = b.addRunArtifact(costs);
run.has_side_effects = true;
const step = b.step("costs", "Measure the costs of docs/costs.md on this host");
step.dependOn(&run.step);
}
fn add_hooks_step(b: *std.Build) void {
const run = b.addSystemCommand(&.{ "git", "config", "core.hooksPath", hooks_directory });
const step = b.step("hooks", "Point this clone's core.hooksPath at " ++ hooks_directory);
step.dependOn(&run.step);
}