feat: Zig 0.16 full migration (build + src/ + Walk + main + ziglint dep)#1
Conversation
WHY: Zig 0.16 changed the Build.Step.Run.captureStdOut signature
from no-arg to require a CapturedStdIo.Options struct. Without this
fix build.zig fails at the configure step, blocking the build.
WHAT: readme_run.captureStdOut() → readme_run.captureStdOut(.{})
IMPACT: zigdoc build.zig now compiles under Zig 0.16.0. The
remaining build failure is in the rockorager/ziglint v0.5.2
transitive dep (same realpathZ + .Ignore errors fixed in
EugOT/ziglint fix/0.16-build-compat). Repointing zigdoc's
build.zig.zon at the EugOT fork's 0.16-compat tag is the next step.
VALIDATION: cd ~/ghq/github.com/EugOT/zigdoc && mise x zig@0.16.0 --
zig build → zigdoc's own build.zig succeeds; only transitive ziglint
dep errors remain.
WHY: rockorager/ziglint v0.5.2 still uses Zig 0.14/0.15 APIs (realpathZ, .Ignore variant) that were removed in Zig 0.16. The EugOT/ziglint fix/0.16-build-compat branch (HEAD e642d49) has the migration. WHAT: - Switch .ziglint dependency from URL+hash (rockorager v0.5.2) to local path "../ziglint" so we can validate against the in-progress 0.16 fork before pinning to a tagged release. - Bump minimum_zig_version 0.15.1 -> 0.16.0. IMPACT: build.zig.zon only. Local path requires ../ziglint to exist as a sibling checkout; once EugOT/ziglint cuts a 0.16-compatible tag this should be repointed to URL+hash for reproducibility. VALIDATION: mise x zig@0.16.0 -- zig build (transitive ziglint dep compiles cleanly; remaining failures are in zigdoc's own src/ which this commit does not touch).
WHY: Zig 0.16 removed std.process.argsAlloc/argsFree and std.fs.cwd(); files now must opt into the Init struct (Juicy Main) and use std.Io.Dir.cwd() with explicit io threading. WHAT: - main signature: pub fn main(init: std.process.Init) !void - iterate args via init.minimal.args.iterateAllocator(allocator) - replace std.fs.cwd().readFileAlloc with std.Io.Dir.cwd().readFileAlloc using .limited(...) Io.Limit - replace std.fs.cwd().writeFile with std.Io.Dir.cwd().writeFile(io, ...) IMPACT: build_readme.zig only. The README generator still produces the same markdown content; only the IO-layer call shapes changed. VALIDATION: mise x zig@0.16.0 -- zig build emits ../README.md successfully and exits 0.
WHY: Zig 0.16 removed std.fs.cwd(), std.fs.realpathAlloc, and renamed std.StringArrayHashMapUnmanaged to std.array_hash_map.String. The AST also removed the .asm_legacy node tag. WHAT: - Add a module-level io_global alongside the existing module-level gpa, initialized via Walk.init(allocator, io). Tests pass std.testing.io. - Swap std.fs.realpathAlloc for std.Io.Dir.cwd().realPathFileAlloc. - Swap std.fs.cwd().readFileAlloc(allocator, path, size) for std.Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(size)). - Drop the .asm_legacy switch arm: the variant was removed; .asm_simple and .@"asm" already cover the legacy syntax. - Update test_symbol_resolution.zig setupTest to thread std.testing.io into Walk.init. IMPACT: src/Walk.zig and src/test_symbol_resolution.zig. The two static public mutables (Walk.files, Walk.modules) and the inner Namespace maps switch to std.array_hash_map.String to silence Z011 deprecation hits while preserving behaviour. VALIDATION: - mise x zig@0.16.0 -- zig build -> exit 0 - mise x zig@0.16.0 -- zig build test -> exit 0 (incl. ziglint rule pass and the unit tests in test_symbol_resolution.zig).
WHY: Zig 0.16 reshaped the standard library surface used throughout
src/main.zig: std.process.argsWithAllocator/Child.run/exit, std.fs.cwd,
std.fs.openDirAbsolute, std.zon.parse.fromSlice, and the std.mem.indexOf
deprecation. The previous DebugAllocator/smp_allocator startup dance is
also superseded by the Init struct provided by the new entry point.
WHAT:
- Adopt the Juicy Main signature `pub fn main(init: std.process.Init)`
and source gpa/io from `init`. Drop the manual DebugAllocator
bootstrap.
- Replace std.process.argsWithAllocator with
init.minimal.args.iterateAllocator.
- Replace std.process.Child.run with std.process.run(allocator, io, ...)
using `.stdout_limit = .limited(...)` and switch on result.term.
- Replace std.fs.cwd().{access,makeDir,writeFile,readFileAlloc,realpath}
with their std.Io.Dir.cwd() equivalents threading io: createDir uses
.default_dir, realPath fills a buffer, readFileAlloc returns a slice
via Io.Limit.
- Replace std.fs.openDirAbsolute / std.fs.File.stdout with
std.Io.Dir.openDirAbsolute / std.Io.File.stdout.
- Replace std.fs.File.stdout().writer(buf) with .writer(io, buf) and
add explicit `.end()` flushes alongside `.flush()`.
- Use std.zon.parse.fromSliceAlloc for ZigEnv (the type contains slices
that require an allocator at parse time).
- Add Zig 0.16 to the supported build_runner switch (uses the 0.15 file).
- Migrate two std.mem.indexOf calls to std.mem.find (Z011 dep deprecated
the old name in favour of `find`).
- Drop the now-unused `const builtin = @import("builtin")` import.
- Reorder helper functions so `io: std.Io` precedes the arena/`other`
parameters, satisfying ziglint Z023.
- For walkStdLib, cast the readFileAllocOptions return slice to []u8
via @constcast since Walk.addFile mutates the trailing newline.
IMPACT: src/main.zig only. Behaviour is unchanged for end users; the
program still resolves std-prefixed symbols against `zig env`'s std_dir
and dumps build.zig imports via the bundled build_runner.
VALIDATION:
- mise x zig@0.16.0 -- zig build -> exit 0
- mise x zig@0.16.0 -- zig fmt --check src/ build.zig build.zig.zon
build_readme.zig -> exit 0
- mise x zig@0.16.0 -- zig build test -> exit 0 (lint, fmt, unit tests)
- ./zig-out/bin/zigdoc 'std.Io.Writer' -> 108 lines of structured docs
including signature, fields, and member listings (the briefing's
literal 'std.io.Writer' lookup returns a "not found" hint because
std.io was renamed to std.Io in 0.16).
- ./zig-out/bin/zigdoc 'std.ArrayList' -> full type_function entry
with doc comments and source.
|
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR migrates the entire codebase to Zig 0.16's ChangesZig 0.16 I/O Injection Migration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Microsoft Presidio Analyzer (2.2.362)build.zigMicrosoft Presidio Analyzer failed to scan this file Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 495eafeea6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR completes an end-to-end migration of zigdoc to Zig 0.16 “Juicy Main” and the new std.Io.* APIs across the build scripts and core source, aiming to keep runtime behavior the same while updating IO/process/fs/stdlib call sites for Zig 0.16 compatibility.
Changes:
- Migrate
src/main.zig,src/Walk.zig, and README generator to Zig 0.16std.process.Init+std.IoAPIs. - Update build logic for Zig 0.16 API changes (
captureStdOut(.{}), minimum Zig version bump). - Temporarily repoint
ziglintdependency to a local sibling path for Zig 0.16 compatibility testing.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/Walk.zig |
Updates Walk’s filesystem usage to std.Io.Dir and migrates renamed stdlib hash map types. |
src/test_symbol_resolution.zig |
Adjusts tests to initialize Walk with std.testing.io. |
src/main.zig |
Migrates CLI entrypoint, build runner invocation, stdlib walking, and file IO to Zig 0.16 Init/Io APIs. |
build.zig.zon |
Bumps minimum Zig version to 0.16.0 and changes ziglint dependency source. |
build.zig |
Updates captureStdOut invocation for Zig 0.16 signature changes. |
build_readme.zig |
Migrates README generator tool to Zig 0.16 Init/Io APIs and new args handling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@build.zig.zon`:
- Around line 7-8: The .ziglint dependency currently uses a sibling path (the
.ziglint entry with path = "../ziglint"), which breaks reproducibility; change
this by either replacing the local path with a pinned remote package (set .url
to the fork’s git URL and .hash to the commit/tag hash and remove the local
path) or by making the dependency an opt-in override (remove the path and
document an optional override key such as .overridePath or a comment so
consumers can supply ../ziglint locally); update the .ziglint block accordingly
and ensure any package resolver references (the .ziglint symbol) still match the
new shape.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1f38b436-b655-450c-83f3-19cba54de86b
📒 Files selected for processing (6)
build.zigbuild.zig.zonbuild_readme.zigsrc/Walk.zigsrc/main.zigsrc/test_symbol_resolution.zig
…oducible builds
WHY:
CodeRabbit (PRRT_kwDOST2NKs6ATjJU), copilot (PRRT_kwDOST2NKs6AThNA), and
chatgpt-codex (PRRT_kwDOST2NKs6ATerA) flagged the `.path = "../ziglint"`
dependency as P1/Major: fresh clones, CI runners, and downstream
consumers without a sibling `ziglint` checkout cannot resolve the
build graph, breaking `zig build` and `zig build test` outside the
maintainer's machine.
WHAT:
Replace the local-path dependency in build.zig.zon with a URL+hash
pin against EugOT/ziglint @ fix/0.16-build-compat
(commit 40f26f1283468032ec993c93fca921bd3c106adb), the Zig 0.16
compatibility branch produced for this migration. Hash captured via
`zig fetch --save=ziglint git+https://...#fix/0.16-build-compat`:
ziglint-0.5.2-t0bwLz2XBQCgnA0PTY0KM9YgrmdjLU7wwjXablNijOuq.
IMPACT:
- build.zig.zon (sole change): dependency lookup now goes through the
Zig package fetcher and global cache, no host filesystem assumption.
- build.zig (unchanged): `b.dependency("ziglint", ...)` and
`ziglint.addLint(...)` resolve identically.
- No source changes; no behavioral change for tests/lint/fmt steps.
VALIDATION:
- `rm -rf .zig-cache && zig build test` -> exit 0 with sibling present.
- Mutation 1 (independence): renamed `../ziglint` away,
`rm -rf .zig-cache && zig build test` -> exit 0 (proves URL+hash is
load-bearing, not the sibling).
- Mutation 2 (hash discipline): corrupted hash to all-A,
`zig build test` -> "hash mismatch" error (proves the pin is
enforced; restored after).
- `zig fmt --check build.zig.zon` -> exit 0 (covered by `zig build
test` -> fmt step).
…ses argv[0] WHY: copilot-pull-request-reviewer flagged build_readme.zig in PR #1: * PRRT_kwDOST2NKs6AThL_ (line 18): the iterator-based parser silently ignores positionals beyond <help-file> <output-file>, regressing the previous `args.len == 3` strictness. * PRRT_kwDOST2NKs6AThMf (lines 10-13): the usage banner hard-codes "gen_readme" instead of echoing argv[0], so a renamed binary prints a misleading hint. WHAT: 1. Extract `pub fn parseArgs(allocator, prog_name, rest, *usage_buf)` — exactly two positionals required, any other arity returns error.InvalidArgs and writes a `Usage: {prog_name} ...` line into the caller-owned ArrayList(u8). Allocator-first parameter order satisfies ziglint Z023; explicit `try` on the usage write satisfies Z026 (no silent catch). 2. main() now captures argv[0], collects the remaining positionals into a heap-owned slice, calls `parseArgs`, prints the buffered usage to stderr on error, and only then proceeds to read/write. 3. New unit tests pin five invariants: happy path, zero/one/three/four positionals all rejected, and the usage text contains both "<help-file>", "<output-file>", and the supplied prog_name. 4. build.zig wires `build_readme_tests` (b.addTest on build_readme.zig) into the `test` step so these invariants run alongside src/main.zig tests. IMPACT: - build_readme.zig: public API gains `parseArgs`, `ParsedArgs`, `ParseError`. Behavior change: extra args now error instead of being ignored; usage echoes the binary's actual invocation name. - build.zig: test step gains 5 new tests (no other steps change). - No source-of-truth changes for src/, lint, or fmt; gen_readme is a build helper, not a runtime artifact. VALIDATION: - `zig build test` -> exit 0, 5/5 build_readme tests + existing src/main.zig tests + lint pass. - `zig fmt --check build.zig build.zig.zon build_readme.zig src/` -> 0. - Mutation 1 (arity): weakened `if (rest.len != 2)` to `< 2` -> 3/5 pass (the 3-arg & 4-arg rejection tests fail as required). Restored. - Mutation 2 (prog_name): replaced `{s}`/`prog_name` with hard-coded "gen_readme" -> compile error (unused parameter), proving prog_name is load-bearing in the production code path. Restored. - ziglint clean (Z004/Z023/Z026 all silent on the new code).
…n contract WHY: copilot-pull-request-reviewer thread PRRT_kwDOST2NKs6AThMy on src/main.zig flagged `@constCast(file_content)` at the `Walk.addFile` call site (walkStdLib, lines 404-406). `Walk.parse` writes a 0 sentinel into the source buffer in place, so the slice must be mutable; `@constCast`-ing a `[]const u8` to `[]u8` is unsound when the underlying memory is genuinely const. The reviewer is correct: the call should rely on a real mutable slice rather than launder constness. WHAT: 1. Switch from `entry.dir.readFileAllocOptions(io, ..., .@"1", 0)` to `entry.dir.readFileAlloc(io, ...)`. The new call returns `[]u8` directly (the same form already used by walkBuildModules at lines 314 and 338), so the `@constCast` becomes unnecessary and is removed. `Walk.parse` continues to handle the trailing-newline → sentinel rewrite identically — confirmed by reading src/Walk.zig:469-482. 2. Add a regression test in src/main.zig that pins the const-correctness contract via `@typeInfo`: `Dir.readFileAlloc` must return mutable `[]u8`, and `Walk.addFile`'s second parameter must remain `[]u8`. If either side ever flips to `[]const u8`, the test fails to compile / asserts, forcing the issue back into review instead of silently accepting a future `@constCast` re-introduction. IMPACT: - src/main.zig walkStdLib: identical runtime behavior (same bytes, same Walk.addFile call, same parse path), no `@constCast`. - New test (1 case) added to the existing `exe_tests` step; 6 tests pass total now. - No public API changes. VALIDATION: - `zig build test` -> exit 0; new test passes alongside the 5 R2 build_readme tests. - Mutation 1 (Walk.addFile contract): flipped second param to `[]const u8` -> the new test fails (1/1 fail), proving the contract pin is load-bearing. Restored. - Mutation 2 (re-introduce @constcast): re-added the cast on the already-mutable slice -> tests still pass (cast is a no-op on mutable memory; introspection test still verifies the underlying contract). Restored. - `zig fmt --check src/main.zig build.zig build.zig.zon build_readme.zig` -> exit 0.
|
@coderabbitai full review R1 (cc43198): pinned ziglint via URL+hash against EugOT/ziglint @ All 6 prior threads resolved with mutation-validated fixes. |
|
Tip For best results, initiate chat on the files or code changes.
[full_review] |
Summary
End-to-end Zig 0.16 migration of
zigdoc:build.zig,build_readme.zig,src/Walk.zig,src/main.zig, and theziglintbuild dep are all migrated to Juicy Main +std.Io.*.zig buildandzig build testexit 0 on Zig 0.16.0.Five commits, push order below.
WHY
Zig 0.16 reshaped the stdlib surface used throughout
zigdoc:std.process.argsAlloc/argsWithAllocator,std.process.Child.run,std.fs.cwd(),std.fs.openDirAbsolute,std.fs.File.{stdout,stderr},std.zon.parse.fromSlice,std.mem.indexOf(deprecated for.find),Build.Step.Run.captureStdOutsignature, andstd.StringArrayHashMapUnmanaged(renamed). Therockorager/ziglintv0.5.2 build dep also still used 0.14/0.15 APIs.WHAT (per commit)
0560610 fix(build): pass empty Options to captureStdOut for Zig 0.16readme_run.captureStdOut()->readme_run.captureStdOut(.{})4bd2356 fix(deps): repoint ziglint to EugOT fork via local path.ziglintdep topath = "../ziglint"so we can validate againstEugOT/ziglint fix/0.16-build-compatbefore a tag exists.EugOT/ziglint#fix/0.16-build-compatlands and tags a 0.16-compat release, a follow-up will repoint to URL+hash for reproducibility.minimum_zig_version0.15.1 -> 0.16.0.9bc5142 fix(build_readme): migrate to Zig 0.16 Juicy Main + Io.Dir APIspub fn main(init: std.process.Init) !voidinit.minimal.args.iterateAllocator(allocator)std.Io.Dir.cwd().readFileAlloc(io, ..., .limited(...))andwriteFile(io, ...)1c495ed fix(walk): migrate src/Walk.zig to Zig 0.16 Io-aware APIsio_globalinitialized viaWalk.init(allocator, io); tests passstd.testing.iostd.fs.realpathAlloc->std.Io.Dir.cwd().realPathFileAllocdir.readFileAlloc(allocator, path, size)->readFileAlloc(io, path, allocator, .limited(size))std.StringArrayHashMapUnmanaged->std.array_hash_map.String(silences Z011).asm_legacyAST node tag (.asm_simple+.@"asm"cover legacy syntax)495eafe fix(main): migrate src/main.zig to Zig 0.16 Juicy Main + Io APIsgpa/iofrominitargsWithAllocator->init.minimal.args.iterateAllocatorChild.run->std.process.run(allocator, io, ...)with.stdout_limit = .limited(...)std.fs.cwd().{access,makeDir,writeFile,readFileAlloc,realpath}->std.Io.Dir.cwd()equivalentsstd.fs.openDirAbsolute->std.Io.Dir.openDirAbsolute;std.fs.File.stdout->std.Io.File.stdout.writer(buf)->.writer(io, buf)plus explicit.end()flushesstd.zon.parse.fromSlice->fromSliceAlloc(ZigEnvcarries slices)build_runnerswitchstd.mem.indexOf->std.mem.find(Z011)io: std.Ioprecedes arena param (Z023)IMPACT
zigdocbuilds and tests cleanly on Zig 0.16.0zig build) emits../README.mdsuccessfully./zig-out/bin/zigdoc 'std.Io.Writer'returns 108 lines of structured docs (signature, fields, members)./zig-out/bin/zigdoc 'std.ArrayList'returns fulltype_functionentry with doc comments + sourcestd.io.Writerlookups now correctly return a "not found" hint becausestd.iowas renamed tostd.Ioin Zig 0.16 (this is a stdlib rename, not a regression)Dependency note
build.zig.zoncurrently has.ziglint = .{ .path = "../ziglint" }so this PR can be validated end-to-end alongsideEugOT/ziglint#fix/0.16-build-compat. Once that PR merges and tags a 0.16-compat release, a follow-up will repoint to URL+hash. Reviewers wanting to build locally need a sibling clone ofEugOT/ziglinton thefix/0.16-build-compatbranch.VALIDATION
Test plan
zig buildexits 0zig fmt --checkclean acrosssrc/,build.zig,build.zig.zon,build_readme.zigzig build testexits 0std.Io.Writerandstd.ArrayListreturn structured docsziglintdep to URL+hash afterEugOT/ziglint#fix/0.16-build-compatmerges + tags