Skip to content

feat(#461): production generate cutover — auto-detect + use backend.manifest.v2.zon - #473

Merged
apotema merged 4 commits into
mainfrom
feat/453-generate-v2-cutover
Jul 1, 2026
Merged

apotema merged 4 commits into
mainfrom
feat/453-generate-v2-cutover

Conversation

@apotema

@apotema apotema commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

The production generate cutover for the manifest-v2 epic (#461). Real generate now auto-detects a backend's backend.manifest.v2.zon and drives the v2 codegen path — no caller-passed backend_manifest_name needed. Closes the #472 P2 finding. Production no-op today (no external repo ships a v2 manifest yet; the in-tree backends/*_v2/ are test fixtures) — the next step is shipping v2 to the external repos one at a time.

Mechanism

  • manifest_v2.V2_MANIFEST_NAME = "backend.manifest.v2.zon".
  • detectV2ManifestName(allocator, cfg, project_dir) in root.zig: resolves the backend package (resolveBackendPackage), accesses <pkg>/backend.manifest.v2.zon, returns the name if present else null. Called once at the top of generate.
  • Threaded through the 4 sites with the single resolved name: requireManifestIfExternal preflight (so a v2-only external backend isn't rejected as manifest-less), generateBuildZigZon, generateBuildZig, and stageBackendBuildHook (only when non-null → the generated @import("backend_build_hook.zig") resolves). Tests-target path (Backend-agnostic test target: .labelle/tests/ via null backend #83) covered automatically (it calls generate).
  • Graceful degradation: every probe failure (catch return null) falls back to v1/enum; real external misconfig still surfaced by requireManifestIfExternal.

Tests (drive the REAL generate, not the unit helper)

GenerateOptions has no backend_manifest_name field, so a v2 build.zig can only come from generate's own probe:

  • auto-detect: acme_foo (v2-only, no enum tag) → b.dependency("acme_foo") + generic unifyCoreDiamond + valid AST (was ExternalBackendNeedsManifest before).
  • no v1 regression: new backends/sokol_v1only/ fixture → v1 splice output (unifyGfxSubpackageCore, no unifyCoreDiamond/hook import).
  • production no-op: sokol (dual manifest) auto-detected v2 output == v1/enum baseline (byte-identical).

Production byte-unchanged

No external repo ships v2, so the probe returns null on every real generate; examples-integration CI (fetches the real v1 backends) unchanged. All prior unit-helper tests, the sokol-desktop byte anchor, and the android/ios/wasm/wgpu/null goldens stay green.

zig build test + zig build exit 0.

Ref #461, #453.

https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

Summary by CodeRabbit

  • New Features
    • Added support for backend manifests in the newer v2 format, including auto-detection during generation, v2-based template selection, and loop-style overrides.
    • Added a full desktop callback-based template for v2-only Sokol backends.
  • Bug Fixes
    • Improved v2 capability/provider contract validation and ensured v1-only and mixed backends keep their existing run-loop behavior.
  • Tests
    • Added production cutover and seam-level tests, plus new fixture packages and generation helpers to cover v1/v2 edge cases.
  • Chores
    • Updated Sokol v1-only build fragments and added macOS/iOS framework linking for iOSSurface/CoreFoundation.

Make the real `generate` entry auto-detect a backend's
`backend.manifest.v2.zon` in the resolved backend package and drive the
manifest-v2 codegen path — without the caller passing
`backend_manifest_name`. Closes the #472 P2 finding.

- Add `manifest_v2.V2_MANIFEST_NAME` (canonical `backend.manifest.v2.zon`).
- `root.zig`: probe ONCE via `detectV2ManifestName` (resolveBackendPackage +
  access), thread the result through the 4 sites:
  `requireManifestIfExternal` (so a v2-only external isn't rejected as
  manifest-less), `generateBuildZigZon`, `generateBuildZig`, and
  `stageBackendBuildHook` (stages the hook next to build.zig when the v2
  manifest declares one). The tests-target path (#83) inherits this since the
  probe lives inside `generate`.
- Graceful degradation: any probe I/O error (resolution/access/OOM) falls back
  to null (v1/enum), never crashes.

Production NO-OP today: no external backend repo ships a v2 manifest yet, so
every real `generate` returns null and output is byte-identical.

Tests (drive the REAL `generate`, not the `generateBuildZig` unit helper):
- acme_foo (v2-only) → generate auto-detects v2 and emits v2 build.zig
  (`b.dependency("acme_foo")` + generic `unifyCoreDiamond` walk), no opt-in.
- new `backends/sokol_v1only` fixture → generate stays on v1/enum (no v2
  markers, no hook import).
- sokol (dual manifest) → auto-detected v2 output == v1/enum baseline
  (production-no-op guarantee).

`zig build` + `zig build test` exit 0; existing goldens/byte-anchors unchanged.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
@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: 50b24984-2af1-4603-8aef-9cf138817cb6

📥 Commits

Reviewing files that changed from the base of the PR and between 5787092 and 9ea2d15.

📒 Files selected for processing (5)
  • backends/sokol_v1inv2/backend.manifest.v2.zon
  • backends/sokol_v1inv2/templates/desktop.txt
  • src/root.zig
  • test/build_zig_tests.zig
  • test/helpers.zig
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/helpers.zig
  • src/root.zig

📝 Walkthrough

Walkthrough

Adds v2 manifest detection in generation, threads the detected manifest name through validation, build output, and template selection, and adds sokol fixture/template files plus tests for v1-only and v2 cutover behavior.

Changes

Manifest v2 cutover

Layer / File(s) Summary
V2 manifest and sokol fixture files
src/codegen/manifest_v2.zig, backends/sokol_v1only/backend.manifest.zon, backends/sokol_v1only/build_fragments/backend_dep.txt, backends/sokol_v1only/build_fragments/link.txt, backends/sokol_v1inv2/backend.manifest.v2.zon, backends/sokol_v1inv2/templates/desktop.txt
Adds the canonical v2 manifest filename constant plus sokol v1-only and v1-in-v2 fixture manifests, templates, and backend dependency/link fragments.
Root v2 detection and generation wiring
src/root.zig
Detects backend.manifest.v2.zon in the backend package and threads the detected name through provider validation, build generation, build-hook staging, and backend template selection.
V2 desktop template and cutover tests
backends/sokol_v2only/templates/desktop.txt, test/helpers.zig, test/build_zig_tests.zig
Adds the sokol v2 desktop runtime template, helper support for reading generated build.zig, and tests covering v2 auto-detection, seam behavior, and loop-style resolution.

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

Possibly related issues

Possibly related PRs

Poem

I’m a rabbit with manifest nose,
sniffing v1, then v2 as it grows.
Hop hop through templates, bright and keen,
build hooks pop in where they’ve not been seen.
Thump! The cutover lands just right 🐇

🚥 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: production generate now auto-detects and uses backend.manifest.v2.zon.
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-generate-v2-cutover

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

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements the manifest-v2 production cutover by introducing auto-detection of the backend.manifest.v2.zon file within the resolved backend package. The generate entry point now automatically probes for this v2 manifest, threading its presence through downstream codegen steps while maintaining graceful fallback to the legacy v1/enum path if absent. To support this, a v1-only test fixture was added, along with corresponding integration tests and helper functions to verify the auto-detection behavior and ensure backward compatibility. There are no review comments to address, so I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@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/root.zig`:
- Around line 464-466: Thread the detected manifest name into provider-contract
validation so v2-only backends are validated against the correct manifest.
Update the call site in root.zig to pass backend_manifest_name into
validateProviderContracts, and adjust validateProviderContracts to load the v2
manifest when that name is present instead of always reading the legacy provider
manifest path. Make sure the id and capabilities checks run against the manifest
chosen by detectV2ManifestName.
- Around line 897-904: Thread v2 manifest detection through main-template
generation: `generate` currently stages v2 hooks but `loadBackendTemplate` still
re-checks manifests with `null` and falls back to the legacy gate, so v2-only
backends can fail later when building `main.zig`. Pass `backend_manifest_name`
through `loadBackendTemplate` and the loop-style/template selection path, and
use it to resolve the v2 `.platforms.<platform>.entry` when present instead of
relying only on the legacy manifest check.
🪄 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: 82a24671-31fa-44f5-a91d-d25634af4fe0

📥 Commits

Reviewing files that changed from the base of the PR and between 61c1430 and 991eeba.

📒 Files selected for processing (7)
  • backends/sokol_v1only/backend.manifest.zon
  • backends/sokol_v1only/build_fragments/backend_dep.txt
  • backends/sokol_v1only/build_fragments/link.txt
  • src/codegen/manifest_v2.zig
  • src/root.zig
  • test/build_zig_tests.zig
  • test/helpers.zig

Comment thread src/root.zig Outdated
Comment thread src/root.zig

@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: 991eeba0b6

ℹ️ 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 thread src/root.zig
Comment on lines +464 to +465
const backend_manifest_name = detectV2ManifestName(allocator, cfg, game_dir);
try manifest_splice.requireManifestIfExternal(allocator, cfg, game_dir, backend_manifest_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.

P1 Badge Thread v2 manifests into executable template generation

When generate is called for a non-test target with a v2-only external backend, this detected name is only passed to the build.zig/zon and hook paths; the later loadBackendTemplate call still probes requireManifestIfExternal(..., null) and manifestPathEnabled(..., null), so it looks for the legacy backend.manifest.zon and fails with ExternalBackendNeedsManifest before main.zig can be emitted. The added tests drive generate with is_tests_target = true, which skips that executable-only path, so the real cutover still breaks for the v2-only backend shape this change is enabling.

Useful? React with 👍 / 👎.

Comment thread src/root.zig
// Passing the detected name to `requireManifestIfExternal` is load-bearing: a
// v2-ONLY external backend (no legacy `backend.manifest.zon`) must not be
// rejected as manifest-less (the requirement keys off THIS name).
const backend_manifest_name = detectV2ManifestName(allocator, cfg, game_dir);

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 Validate provider contracts from the selected manifest

When a backend ships both manifests during migration, this switches build generation to backend.manifest.v2.zon, but the following validateProviderContracts call still reads only the legacy backend.manifest.zon. If the v2 manifest corrects or tightens .id/.capabilities while the v1 file is retained for older assemblers, generate can reject, warn, or accept based on stale v1 metadata before the v2 validation in generateBuildZig runs; thread the detected manifest into the provider-contract check or skip the legacy check once v2 is selected.

Useful? React with 👍 / 👎.

Comment thread src/root.zig
// manifest-v2 cutover: the auto-detected v2 manifest name (null → v1/enum,
// byte-unchanged). When a v2 manifest is present this routes the
// backend-dep + link sections to the v2 codegen (`manifest_v2_splice`).
.backend_manifest_name = backend_manifest_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 Keep tests target on the null backend

When this path is reached through generateTestsTarget, is_tests_target is true but the original backend_package can still be set; passing the auto-detected v2 manifest here makes the tests build use the user's external backend instead of the documented .null backend. For a v2 third-party backend with native deps or no desktop cell, zig build test will now require that backend or fail, even though the tests target is meant to be backend-agnostic.

Useful? React with 👍 / 👎.

…seams (PR #473)

PR #473 review: `generate` auto-detects a `backend.manifest.v2.zon` and threads
`backend_manifest_name` through build-file emission, but two downstream sites
still ignored it. A v2-ONLY backend (no legacy `backend.manifest.zon`) therefore
passed build.zig emission and then either failed main.zig template loading or
had its identity/capability contract silently skipped.

Finding 1 (main-template loading, `loadBackendTemplate`): now takes
`backend_manifest_name`; when a v2 manifest is detected it resolves the
entry-point template from `.platforms[<platform>].entry` and keys
`requireManifestIfExternal` off that same name (so a v2-only external is not
rejected as manifest-less). The per-platform run-loop style is likewise read
from `.platforms[<platform>].loop_style`. v1/enum path unchanged (name null →
falls through to the existing v1 splice / enum mappings).

Finding 2 (provider-contract validation, `validateProviderContracts`): now takes
`backend_manifest_name`; when a v2 manifest is detected it runs the identity +
capability-gate checks against the v2 `.id`/`.capabilities` instead of the
(absent) legacy provider manifest. Shared body factored into
`validateProviderContractsInner` so v1 and v2 run the same negotiation.

Both seams load the detected v2 manifest in place (matching the existing v1
style, which loads per-site) and degrade gracefully (probe/parse failure or a
file that parses as v1 → the v1/enum path).

Tests: strengthened the real-`generate` cutover coverage with a capability
mismatch caught through the production entry point, plus direct seam tests
proving `loadBackendTemplate` + `validateProviderContracts` honor a passed
`backend_manifest_name` (v2-only sokol variant for the template, acme_foo for
the contract) — each with a null-name counter-test proving the detected name is
load-bearing. Added `backends/sokol_v2only/templates/desktop.txt` so the v2-only
fixture can be driven through template loading. Production byte-unchanged (no
external repo ships v2 → probe null → legacy behavior).

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

@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: 1

🧹 Nitpick comments (1)
test/build_zig_tests.zig (1)

713-736: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Please validate the v2-only desktop main.zig path too.

These additions cover build.zig generation and template lookup, but the new backends/sokol_v2only/templates/desktop.txt is never parsed or compile-checked. loadBackendTemplate(...).len > 0 would still pass with a broken placeholder or callback signature. Add a fixture that generates main.zig for sokol_v2only and at least AST-parses it.

Also applies to: 797-810

🤖 Prompt for 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.

In `@test/build_zig_tests.zig` around lines 713 - 736, Add a new test around the
v2-only desktop main.zig generation path to cover the new sokol_v2only template,
since the current coverage only validates build.zig and template presence. Use
the existing generate/loadBackendTemplate flow with the sokol_v2only fixture to
generate main.zig, then AST-parse the rendered output to ensure the desktop
template in desktop.txt is actually syntactically valid. Keep the test aligned
with the existing generate and generateAndReadBuildZig helpers so it exercises
the same backend selection path and catches broken placeholders or callback
signatures.
🤖 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/root.zig`:
- Around line 1051-1066: The loop-style resolution logic in the
`backend_manifest_name` handling skips legacy `.v1` fallback because the `else
if` prevents `manifest_splice.manifestPathEnabled(...)` from running after
`loadNamedManifest()` returns a v1 manifest. Update the control flow around
`loadNamedManifest`, the `.v1` switch arm, and the
`manifest_splice.manifestPathEnabled` check so legacy handling runs in a
separate pass unless a real v2 entry in `manifest_v2_splice.platformEntry`
already set `main_zig.main_template.loop_style_override`.

---

Nitpick comments:
In `@test/build_zig_tests.zig`:
- Around line 713-736: Add a new test around the v2-only desktop main.zig
generation path to cover the new sokol_v2only template, since the current
coverage only validates build.zig and template presence. Use the existing
generate/loadBackendTemplate flow with the sokol_v2only fixture to generate
main.zig, then AST-parse the rendered output to ensure the desktop template in
desktop.txt is actually syntactically valid. Keep the test aligned with the
existing generate and generateAndReadBuildZig helpers so it exercises the same
backend selection path and catches broken placeholders or callback signatures.
🪄 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: e2b67e87-645a-4c4b-bcb8-5a14a0f68ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 991eeba and 70c7d95.

📒 Files selected for processing (3)
  • backends/sokol_v2only/templates/desktop.txt
  • src/root.zig
  • test/build_zig_tests.zig

Comment thread src/root.zig Outdated
 Major)

The loop_style resolution chained the legacy `manifestPathEnabled` branch as
an `else if (backend_manifest_name == null)`. When a detected named manifest
(`backend.manifest.v2.zon`) parsed as v1 (`manifest_version <= 1`), the v2 arm
no-oped AND the legacy `else if` was skipped because the name was non-null —
silently leaving `loop_style_override` unset and dropping that backend's
loop_style.

Extract the resolution into `resolveLoopStyleOverride` and restructure so the
legacy path is a SECOND guarded pass (`!v2_resolved`), not an else-if: it runs
whenever a REAL v2 manifest (union tag .v2) did NOT handle it — name null, a
named file that parsed as v1, or a swallowed v2 load error. Mirrors the
.v2-returns / .v1-falls-through shape loadBackendTemplate +
validateProviderContracts already use correctly (verified; neither had the bug,
since their v2 arms return early so the legacy code after runs unconditionally).

Production byte-unchanged (name is null in production today, so the legacy pass
runs exactly as before).

Tests: add three resolveLoopStyleOverride tests to MANIFEST_V2_CUTOVER_SEAMS —
(1) the regression lock: a v1-by-name file (sokol_v1only) still resolves its
legacy .callback loop_style instead of null; (2) a v2-only backend resolves
from its per-platform matrix despite shipping no legacy manifest; (3) the same
bgfx v2 manifest yields desktop=.loop, android=.callback.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

@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: 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 `@src/root.zig`:
- Around line 502-505: The fallback path in root.zig drops a detected `.v1`
manifest when `backend.manifest.v2.zon` parses as v1, so preserve that parsed
manifest instead of reloading only the canonical legacy file. Update the
`v2_resolved` fallback flow around `manifest_splice.manifestPathEnabled` and the
later load path to carry the detected `name`/parsed v1 data through the legacy
gate, ensuring `loop_style` and template fields survive even when no
`backend.manifest.zon` sibling exists.
🪄 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: c5c0338e-7462-4d22-bd82-5de978b33fc7

📥 Commits

Reviewing files that changed from the base of the PR and between 70c7d95 and 5787092.

📒 Files selected for processing (2)
  • src/root.zig
  • test/build_zig_tests.zig
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/build_zig_tests.zig

Comment thread src/root.zig Outdated
…#473 Major)

`detectV2ManifestName` keys off file EXISTENCE, so a backend shipping ONLY
`backend.manifest.v2.zon` whose CONTENT is v1 (manifest_version 1/omitted) is
threaded as the detected name. Both fallback seams then retried the legacy pass
with the canonical (null) name, probing the ABSENT `backend.manifest.zon` — the
loop_style override dropped to null and the template fell to the enum path
(reading the closed `cfg.backend` for a backend with no tag).

Fix both sites to resolve from the file actually found:
- resolveLoopStyleOverride (~505): the `.v1` arm now resolves loop_style straight
  from the parsed v1 manifest and marks it handled, so the canonical-name pass is
  suppressed. Renamed `v2_resolved` -> `handled`.
- loadBackendTemplate (~1258): the `.v1` arm now resolves `main_loop_template`
  from the parsed v1 manifest and reads that template, instead of freeing and
  falling through to the canonical-name legacy pass.

Order is now (1) `.v2` per-platform matrix; (2) detected name parsed as `.v1` ->
resolve from THAT file; (3) name null / load error -> canonical `backend.manifest.zon`.
Production is byte-unchanged (name is null -> straight to canonical, as before).

Fixture: `backends/sokol_v1inv2` ships ONLY `backend.manifest.v2.zon` with v1
content (loop_style = .loop, no canonical sibling). Two seam tests assert the
loop_style + template resolve from that file (not dropped to null, not erroring
on a missing canonical).

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
@apotema
apotema merged commit cd8730d 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: 9ea2d157b3

ℹ️ 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 thread src/root.zig
defer parsed.free(allocator);
return validateProviderContractsInner(allocator, cfg, m.id, m.capabilities);
},
.v1 => parsed.free(allocator), // not actually a v2 manifest — fall through

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 Keep provider validation on detected v1 manifests

When backend_manifest_name points at a detected file that parses as .v1, this branch discards the parsed manifest and falls back to loadProviderManifest, which always probes the canonical backend.manifest.zon. Fresh evidence in this patch is the new v1-in-v2 path (sokol_v1inv2) that supports a backend shipping only backend.manifest.v2.zon; in that scenario the canonical file is absent, so manifest_id becomes null and declared is empty, causing capability enforcement to downgrade to warnings and skipping validation of the selected manifest's id/capabilities. Validate against the parsed v1 manifest here, matching the selected-manifest handling added for templates and loop style.

Useful? React with 👍 / 👎.

Comment thread src/root.zig
return error.TemplateNotFound;
};
},
.v1 => |m| {

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 Gate v1-by-name templates to desktop

When a detected backend.manifest.v2.zon parses as .v1 on a non-desktop target, this arm still reads the v1 main_loop_template. The build generator's v1 handling is desktop-only and falls back to enum wiring off desktop, so an enum-backed migration package on Android/iOS/WASM can get enum build wiring but a desktop manifest template/loop style for main.zig; for bgfx Android the top-level .loop style is also wrong. Restrict the named-v1 path to desktop, or fall through/error for non-desktop targets.

Useful? React with 👍 / 👎.

Comment thread src/root.zig
Comment on lines +1266 to +1268
const tmpl_path = try std.fs.path.join(allocator, &.{ backend_path, entry.entry });
defer allocator.free(tmpl_path);
return std.Io.Dir.cwd().readFileAlloc(config.globalIo(), tmpl_path, allocator, .limited(64 * 1024)) catch |err| {

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 Keep sokol wasm on an existing template

When a v2 manifest is auto-selected for a sokol wasm target, this now reads the manifest's .platforms.wasm.entry verbatim. The checked in sokol v2 manifest points that entry at templates/wasm.txt, but the package only ships templates/desktop.txt and templates/mobile.txt; the old enum path deliberately mapped sokol wasm to desktop.txt. As soon as a sokol package with this v2 manifest is used for wasm, generate will fail with TemplateNotFound before emitting main.zig, so either keep the wasm entry on desktop.txt or add the template before cutover.

Useful? React with 👍 / 👎.

apotema added a commit that referenced this pull request Jul 1, 2026
…tests target

The prior fix (be826cd) skipped only the ROOT-level `validateProviderContracts`
capability requirement for the tests target. But `build_files.generateBuildZig`
runs its OWN v2 capability validation (added in the #472 open-config PR), and
the tests target calls `generateBuildZig` directly via `generateTestsTarget`.
So the forced-null (`.headless`-only null-v2) tests harness still hard-failed
`UnsupportedCapability` for GUI/gamepad projects — the #474 examples-integration
gamepad example's tests-target generate.

Guard `generateBuildZig`'s v2 capability REQUIREMENT check with
`if (!opts.is_tests_target)`, consistent with the root-level skip. The provider
IDENTITY check stays ON for the tests target (cheap + still valid); only the
capability requirement is skipped. The real exe target (`is_tests_target =
false`) is unchanged. `is_tests_target` was already threaded from the
tests-target generate call site (root.zig:1006-1007).

Adds two DIRECT `generateBuildZig` tests (via `h.genNullV2BuildZig`): a
raw_backend (imgui) GUI project generating build.zig against forced-null
(null-v2) does NOT error with `is_tests_target = true`, and STILL errors
`UnsupportedCapability` with `is_tests_target = false`. This mirrors the CI
scenario the root-level `validateProviderContracts` tests could not reach.

Also re-points the "#473 finding 2" test to drive the REAL exe target
(`is_tests_target = false`): it was passing only because `generateBuildZig`'s
second gate caught the mismatch on the tests-target path (`generateAndReadBuildZig`
forces `is_tests_target = true`), which this fix now correctly skips. The
finding-2 catch is a resolve-time `validateProviderContracts` concern, so the
exe path errors before any engine-template work.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
apotema added a commit that referenced this pull request Jul 1, 2026
…2.0) (#474)

* feat(#461): bump null pin to 0.2.0 — flip null to v2 in production

labelle-null 0.2.0 ships backend.manifest.v2.zon. With the generate cutover
(#473) live, bumping builtinProvider(.null) means production `generate` now
fetches null 0.2.0, auto-detects its v2 manifest, and builds the null backend
via the declarative v2 build graph. First real production flip of the epic.
null is the safest first backend (headless, desktop-only, pure-declarative,
hookless). Validated by the null headless examples-integration CI (build+run
on v2).

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

* fix(#83): skip capability gate for the tests-target forced-null

The tests target (#83) force-substitutes `cfg.backend = .null` as a
headless test harness while keeping the rest of the project config (e.g.
`resolved_gui = imgui`), so `requiredCapabilities(cfg)` still derives the
REAL backend's needs (`.raw_gui_adapter`, …). Now that null ships a v2
manifest declaring only `.headless`, the opted-in capability gate
hard-failed `zig build test` (`UnsupportedCapability`) for every
GUI/gamepad project — surfaced by the null→v2 flip in #474 CI.

Thread `is_tests_target` into `validateProviderContracts` /
`validateProviderContractsInner` and skip ONLY the capability requirement
check for the forced-null tests harness. Identity + id-collision checks
still run (cheap + valid). The real exe target is unaffected: a GUI project
whose CHOSEN backend lacks `.raw_gui_adapter` still fails.

Adds both-directions test: same imgui (raw_backend) project against the
null-v2 fixture passes with `is_tests_target = true` and still errors
`UnsupportedCapability` with `is_tests_target = false`.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

* fix(#83): skip the SECOND capability gate (generateBuildZig) for the tests target

The prior fix (be826cd) skipped only the ROOT-level `validateProviderContracts`
capability requirement for the tests target. But `build_files.generateBuildZig`
runs its OWN v2 capability validation (added in the #472 open-config PR), and
the tests target calls `generateBuildZig` directly via `generateTestsTarget`.
So the forced-null (`.headless`-only null-v2) tests harness still hard-failed
`UnsupportedCapability` for GUI/gamepad projects — the #474 examples-integration
gamepad example's tests-target generate.

Guard `generateBuildZig`'s v2 capability REQUIREMENT check with
`if (!opts.is_tests_target)`, consistent with the root-level skip. The provider
IDENTITY check stays ON for the tests target (cheap + still valid); only the
capability requirement is skipped. The real exe target (`is_tests_target =
false`) is unchanged. `is_tests_target` was already threaded from the
tests-target generate call site (root.zig:1006-1007).

Adds two DIRECT `generateBuildZig` tests (via `h.genNullV2BuildZig`): a
raw_backend (imgui) GUI project generating build.zig against forced-null
(null-v2) does NOT error with `is_tests_target = true`, and STILL errors
`UnsupportedCapability` with `is_tests_target = false`. This mirrors the CI
scenario the root-level `validateProviderContracts` tests could not reach.

Also re-points the "#473 finding 2" test to drive the REAL exe target
(`is_tests_target = false`): it was passing only because `generateBuildZig`'s
second gate caught the mismatch on the tests-target path (`generateAndReadBuildZig`
forces `is_tests_target = true`), which this fix now correctly skips. The
finding-2 catch is a resolve-time `validateProviderContracts` concern, so the
exe path errors before any engine-template work.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
apotema added a commit that referenced this pull request Jul 1, 2026
…releases) (#476)

wgpu 0.2.0, sdl 0.2.0, bgfx 0.3.0 each ship backend.manifest.v2.zon (+ bgfx's
android hook). With generate auto-detect (#473) + the tests-target-null
capability fix (#474), these backends now build via the declarative v2 build
graph in production. Desktop paths fully CI-validated; bgfx-android cross-build
validated by the bgfx-android-build job (on-device render unchanged from v1 —
same source). wgpu/sdl are desktop-only, hookless, golden-reviewed.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
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