Skip to content

feat(#461): manifest-v2 PR7 — sokol wasm (sokol now fully v2) - #468

Merged
apotema merged 2 commits into
mainfrom
feat/453-manifest-v2-pr7-sokol-wasm
Jul 1, 2026
Merged

apotema merged 2 commits into
mainfrom
feat/453-manifest-v2-pr7-sokol-wasm

Conversation

@apotema

@apotema apotema commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

PR 7 of the manifest-v2 epic (#461, #453 item 3). Converts sokol wasm — the last sokol platform. With this, sokol is fully v2 across desktop/android/ios/wasm.

wasm post_wire (emcc) — backends/sokol/backend.hook.zig

The enum path uses @import("labelle_sokol").emLinkStep, but the hook is compiled std-only (its own test target) and can't import the provider package. Reconstructed emLinkStep faithfully using only std.Build + the emsdk dependency (b.dependency("emsdk").path(...) locates emcc; sokol-zig's emLinkStep is itself pure std.Build), so the emitted emcc command line matches the enum .link_sokol_wasm flags (use_webgl2, release_use_closure=false, -sSTACK_SIZE=512KB). No resolve_target (static wasm32-emscripten triple).

root_build_deps → generated build.zig.zon

emitRootBuildDepsV2: a v2 manifest's root_build_deps become generated zon deps by resolution — .builtin "emsdk" reuses the pinned dep_emsdk section (byte-identical to enum), .remote emits url+hash, .path a path; unknown .builtin hard-errors. Enum path (backend_manifest_name==null) unchanged. (Addresses #459 review finding 2.)

Golden

test/goldens/sokol_wasm_v2.build.zig + asserts: imports backend_build_hook, post_wire{.platform=.wasm}, no inline emLinkStep/@import("labelle_sokol")/emcc_step, no resolve_target, generic unifyCoreDiamond, linkLibrary(sokol_clib); + 2 zon tests (emsdk present + byte-identical to enum zon).

⚠️ Documented deviation — wasm packaging stays in post_wire, not renderPackageV2(.web)

post_wire returns void, so it can't export the emcc_step local to a separate packager footer that references it for install/run. So post_wire owns wasm install/run. The .web packager fixture (PR 4) remains for the enum path / future use but isn't invoked for v2 wasm. Reviewers: is this the intended model, or should HookContext/post_wire be extended to hand the emcc step to the packager? Flagging for a call.

Invariants

Desktop byte anchor 0-diff; android + ios goldens unchanged (git status clean); v1/enum unchanged (v2 gated on opt-in). zig build test + zig build exit 0.

Ref #461, #453.

https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

Summary by CodeRabbit

  • New Features
    • Improved WebAssembly build support with a complete hook-driven build-and-run flow.
    • Added manifest-v2 support for WASM, updating generated build scripts, dependency declarations, and footer/link composition.
  • Bug Fixes
    • Fixed WebAssembly linking to apply the correct Emscripten/stack-size configuration automatically.
    • Ensured emsdk handling is consistent for manifest-v2 projects via generated root build dependency wiring.
  • Tests
    • Added golden-based coverage for manifest-v2 WASM generation and strengthened checks for build script correctness.
    • Added fixtures to confirm failures from invalid manifest-v2 input surface as errors.

Converts sokol **wasm** to the v2 build-graph manifest, the last sokol
platform (epic #453 item 3). wasm is the emcc residual (design §2 (c)):

- Hook `post_wire` .wasm arm supplies residual (c) — the Emscripten emcc
  link step. The enum path reaches emcc via
  `@import("labelle_sokol").emLinkStep`, but the hook is std-only and
  cannot import the provider package, so `emLinkStep` is reconstructed in
  `backend.hook.zig` from ONLY `std.Build` + the emsdk dependency
  (resolved via `b.dependency("emsdk", .{})`). Because `post_wire`
  returns void it also owns the install/run wiring the enum `.wasm_footer`
  did (the enum `emcc_step` local cannot escape a void hook), so the wasm
  v2 path does NOT route packaging through the packager `.web` footer —
  the documented enum-vs-v2 boundary. No `resolve_target` (static
  wasm32-emscripten triple, design §3).
- `.root_build_deps = emsdk` (`.builtin`) is emitted into the generated
  build.zig.zon via `emitRootBuildDepsV2` (design §3 RootBuildDep, review
  #459 finding 2); `.builtin` reuses the pinned `dep_emsdk` section so the
  emitted zon stays byte-identical to the enum path.
- v2 wasm codegen: hook-importing header + inline static-triple target,
  deps decls without the unrolled diamond, backend-dep + generic
  `unifyCoreDiamond` walk, declarative `linkLibrary(sokol_clib)` +
  `post_wire` call, and a footer (build-fn close + helpers) + the walk def.

Golden cell `test/goldens/sokol_wasm_v2.build.zig` + hook/zon asserts:
imports the hook, post_wire does the emcc step, no inline emLinkStep /
`@import("labelle_sokol")` in build.zig, generic walk, emsdk in the
generated zon (and byte-identical to the enum zon). Pure decision
(512 KB stack arg) + emitRootBuildDepsV2 unit-tested; wasm_footer_helpers
drift-guarded against the live android_footer tail.

Invariants hold: desktop byte anchor 0-diff; android + ios goldens
unchanged; v1/enum path unchanged (v2 via opt-in `backend_manifest_name`
only). `zig build test` and `zig build` both exit 0.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f189b0f4-d4cb-4c98-a5e1-6e8bb5f0f987

📥 Commits

Reviewing files that changed from the base of the PR and between 144a1cd and 322e666.

📒 Files selected for processing (5)
  • backends/sokol_v2broken/backend.manifest.v2.zon
  • src/build_files.zig
  • src/codegen/manifest_v2_splice.zig
  • test/build_zig_tests.zig
  • test/helpers.zig
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/codegen/manifest_v2_splice.zig
  • src/build_files.zig

📝 Walkthrough

Walkthrough

Adds manifest-v2 WASM generation and hook wiring so wasm builds route through a reconstructed emcc link step, with v2-specific header/deps/footer emission, zon root-dependency handling, golden output coverage, and broken-manifest error-path tests.

Changes

WASM v2 build hook and codegen

Layer / File(s) Summary
Wasm emcc residual in backend.hook.zig
backends/sokol/backend.hook.zig
Adds wasm_stack_size_arg, EmLinkOptions, and emLinkStep to reconstruct the emcc link step; wires post_wire's wasm branch to resolve emsdk, call emLinkStep, and chain install/run steps; adds a stack-size arg test.
Manifest-v2 wasm codegen emitters
src/codegen/manifest_v2_splice.zig
Adds wasm v2 header, deps, backend-dep, link, and footer emitters; dispatches wasm platform rendering in the backend-dep and link section generators; and extends root build-dep emission tests and footer drift checks.
build_files.zig wiring for wasm v2
src/build_files.zig
Switches wasm header/deps/link/footer emission to v2 splice functions when a v2 manifest is present, skips legacy emsdk import wiring, and adds backend_manifest_name-driven emission of wasm root build-deps in build.zig.zon generation.
Wasm v2 golden and failure-path tests
test/goldens/sokol_wasm_v2.build.zig, test/build_zig_tests.zig, backends/sokol_v2broken/backend.manifest.v2.zon, test/helpers.zig
Adds a golden build.zig implementing module unification and post_wire delegation, plus tests for byte-identical wasm v2 output, build.zig.zon emsdk content, and broken-manifest parse errors using the new fixture package and zon fixture.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Poem

A rabbit hopped through wasm's gate,
And v2 builds arrived on time;
emcc spun up, the links were straight,
The golden build matched line for line.
🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: converting sokol wasm to the manifest-v2 path.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/453-manifest-v2-pr7-sokol-wasm

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 `@src/build_files.zig`:
- Around line 1003-1006: The ZON generation path in loadNamedManifest is
swallowing v2 manifest load failures via catch null, which causes silent
fallback instead of a hard failure. Update the opts.backend_manifest_name
handling in src/build_files.zig so manifest_v2.loadNamedManifest errors are
propagated or reported during generation, and only proceed to the parsed branch
when the manifest loads successfully. Use the existing
manifest_v2.loadNamedManifest and the surrounding parsed/pd/name logic to locate
the change.

In `@src/codegen/manifest_v2_splice.zig`:
- Around line 875-881: The ZON emission in manifest_v2_splice.zig writes
dependency .remote and .path values with raw string interpolation, so special
characters can break the generated output. Update the printing in the dependency
emission logic to escape .url, .hash, and .path using the existing
writeZigString helper from idents.zig instead of direct {s} formatting. Keep the
change localized to the code path that formats remote and path dependencies so
all build-dep strings are emitted safely.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e2c92181-b42f-4773-9cd1-105830117327

📥 Commits

Reviewing files that changed from the base of the PR and between 4a0a003 and 144a1cd.

📒 Files selected for processing (5)
  • backends/sokol/backend.hook.zig
  • src/build_files.zig
  • src/codegen/manifest_v2_splice.zig
  • test/build_zig_tests.zig
  • test/goldens/sokol_wasm_v2.build.zig

Comment thread src/build_files.zig Outdated
Comment thread src/codegen/manifest_v2_splice.zig Outdated
… escaping

Finding 1 (build_files.zig): generateBuildZigZon loaded the v2 manifest
with `catch null` and fell back to enum output on error, which could pair
a build.zig that resolved its hook deps against a v2 manifest with a
build.zig.zon that silently fell back — a divergent (broken) pair. Now
the zon path mirrors generateBuildZig: gate the load on
`manifestPathEnabled` (missing manifest → enum fallback in BOTH) and
propagate load errors with `try`. A v2 manifest that fails to load now
errors in BOTH generators, so they always agree (both succeed or both
error). New fixture backends/sokol_v2broken + two tests prove the pair.

Finding 2 (manifest_v2_splice.zig): emitRootBuildDepsV2 wrote url/hash/path
into the generated build.zig.zon with raw `{s}`; a value containing a quote
or backslash produced malformed ZON. Escape via std.zig.fmtString (`{f}`)
for url/hash/path (hash is hex/low-risk but escaped for consistency).
Added a test with quote+backslash values.

zig build + zig build test both exit 0 (incl. desktop byte anchor 0-diff,
android/ios/wasm goldens, emsdk-zon byte-identity, and the new tests).

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
@apotema
apotema merged commit 0f58ac8 into main Jul 1, 2026
4 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 322e666e30

ℹ️ 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".

Comment on lines +884 to +885
" .{s} = .{{\n .path = \"{f}\",\n }},\n",
.{ dep.name, std.zig.fmtString(p) },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rebase relative root-dependency paths before emitting

When a v2 manifest uses a relative .root_build_deps path, this writes that path verbatim into the generated build.zig.zon. Zig resolves dependency .path values relative to the generated build root, not relative to the backend package that owns the manifest, so a backend-shipped helper dependency like tools/helper will point under .labelle/<target>/tools/helper and fail to resolve. Resolve relative paths against the backend package/manifest location and emit a rebased or absolute path instead.

Useful? React with 👍 / 👎.

Comment on lines +880 to +881
" .{s} = .{{\n .url = \"{f}\",\n .hash = \"{f}\",\n }},\n",
.{ dep.name, std.zig.fmtString(r.url), std.zig.fmtString(r.hash) },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Escape root dependency field names

For manifests whose root_build_deps key is not a Zig identifier (for example a third-party build-time dep named emsdk-tool or foo.bar), interpolating dep.name into .{s} emits invalid ZON such as .emsdk-tool = .{ ... }. Since the schema treats name as data and b.dependency accepts a string key, quote or validate the generated field name (e.g. emit a quoted identifier) before writing the dependency entry.

Useful? React with 👍 / 👎.

Comment thread src/build_files.zig
Comment on lines +1012 to +1014
if (opts.backend_manifest_name) |name| {
if (manifest_splice.manifestPathEnabled(allocator, cfg, pd, name)) {
const parsed = try manifest_v2.loadNamedManifest(allocator, cfg, pd, name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require missing external manifests in zon generation

When backend_manifest_name is set for an external backend and that manifest is missing, generateBuildZig reports ExternalBackendNeedsManifest, but this ZON path only checks manifestPathEnabled and silently falls back to enum-style output. That leaves callers able to get a successful build.zig.zon for a configuration whose build.zig cannot be generated, exactly the divergent pair this block is trying to avoid. Mirror generateBuildZig by calling requireManifestIfExternal before treating a missing named manifest as fallback.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant