Repository navigation
feat(#461): select callback-lifecycle shape from manifest data, not the backend enum - #529
Conversation
…ot the backend enum The generated main.zig's callback-lifecycle shape (runner / cleanup / allocator / gui-event / imgui-dispatch / preview / Android-register) was chosen by `cfg.backend == .sokol` / `is_bgfx_android` enum branches in `lifecycle/render.zig` — the last per-backend codegen the assembler keys off the closed `Backend` enum. Move the SELECTION to manifest data so the enum stops driving codegen (#461, the literal-agnosticism follow-on to the completed v2-manifest cutover). - Extend `PlatformEntry.Lifecycle` (manifest_v2) with `imgui_dispatch`, `preview` (none/callback_basic/sokol_readback) and `android_register` (none/bgfx_shell). Additive + defaulted, so every existing manifest parses unchanged and an undeclared callback backend keeps the safe third-party shape. - `render.zig` now builds `LifecycleShape` from the manifest `lifecycle_ovr` FIRST; the `cfg.backend == .sokol` / `is_bgfx_android` literals remain only as a byte-identical FALLBACK for a built-in whose (older) manifest predates these fields — deleted in the final #461 step once the external manifests declare their shape. - `preview = .sokol_readback` and `android_register = .bgfx_shell` emit backend-PRIVATE Zig, so they are PRIVILEGED: a new `backend_registry.assertLifecyclePrivilege` (wired into the v2 validation in root.zig) rejects any non-`labelle.*` provider that declares them, preserving the third-party foot-gun protection the enum implicitly gave. - Declare the full shape in the in-tree fixtures (`backends/sokol` all four callback platforms; `backends/bgfx_v2` android), so codegen tests exercise the manifest path — not the fallback. Proof: new differential tests generate the sokol-desktop and bgfx-android main.zig twice, varying ONLY `lifecycle_override` (null → enum branch, the decl → manifest branch), and assert byte-identical output. Mutation-verified the two paths are genuinely distinct + live. Existing goldens/anchors unchanged. `zig build` + `zig build test` green. Production behavior is untouched (external manifests don't declare `.lifecycle` yet — that's the next PR); this lands the mechanism + proves it byte-identical in isolation. Claude-Session: https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
📝 WalkthroughWalkthroughThis PR introduces manifest-declared privileged lifecycle configuration for backend callback shapes (preview readback, Android registration). It extends the v2 manifest schema, updates bgfx/sokol manifests with lifecycle data, modifies codegen render logic to prefer manifest-declared shapes, and adds namespace-based validation restricting privileged declarations to labelle.*-owned backends. ChangesPrivileged manifest lifecycle declarations
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Root as root.zig
participant Registry as backend_registry.zig
participant Manifest as manifest_v2.zig
participant Render as render.zig
Root->>Manifest: parse backend.manifest.v2.zon
Manifest-->>Root: declaresPrivilegedLifecycle() result
Root->>Registry: assertLifecyclePrivilege(cfg, declares_privileged, manifest_id)
alt declares_privileged and id not under labelle.*
Registry-->>Root: error.PrivilegedLifecycleRequiresReservedNamespace
else allowed or bundled backend
Registry-->>Root: ok
Root->>Render: generate main.zig using lifecycle_ovr
Render-->>Root: LifecycleShape (runner, preview, android, imgui_dispatch)
end
Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
test/main_zig_tests.zig (1)
255-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDifferential tests look correct; consider extending coverage to other sokol platforms.
Both tests correctly isolate the
lifecycle_overridethreadlocal withdefer, and the byte-identity assertions plus sanity substring checks (_sokol_preview,android_app.setInitCallback(&gameInit)) are appropriate anchors for the enum→manifest migration.Per the PR objectives, the sokol manifest additions cover desktop/iOS/Android/wasm platforms, but this differential guard only exercises sokol desktop. If the other sokol platform lifecycle declarations (mobile/wasm) diverge from their enum-path shape, that regression wouldn't be caught here.
Example extension sketch (sokol Android, mirroring the desktop test)
+ test "sokol android: manifest-declared lifecycle == enum-path main (byte-identical, `#461`)" { + const enum_main = try generate.generateMainZigFromTemplate(std.testing.allocator, engine_template, .{ .y_axis = .up, + .name = "test-game", + .backend = .sokol, + .platform = .android, + .ecs = .mock, + }, sokol_mobile_lifecycle, empty_entries, empty_names, empty_names, empty_scene_manifests, empty_names, empty_names, empty_names, empty_names, empty_names, empty_names, empty_names, empty_plugin_events, empty_plugin_flow_nodes, empty_plugin_pin_styles, empty_plugin_coercions); + defer std.testing.allocator.free(enum_main); + + generate.main_template.lifecycle_override = h.sokol_android_lifecycle_decl; // new fixture + defer generate.main_template.lifecycle_override = null; + const manifest_main = try generate.generateMainZigFromTemplate(std.testing.allocator, engine_template, .{ .y_axis = .up, + .name = "test-game", + .backend = .sokol, + .platform = .android, + .ecs = .mock, + }, sokol_mobile_lifecycle, empty_entries, empty_names, empty_names, empty_scene_manifests, empty_names, empty_names, empty_names, empty_names, empty_names, empty_names, empty_names, empty_plugin_events, empty_plugin_flow_nodes, empty_plugin_pin_styles, empty_plugin_coercions); + defer std.testing.allocator.free(manifest_main); + + try std.testing.expectEqualStrings(enum_main, manifest_main); + }🤖 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/main_zig_tests.zig` around lines 255 - 300, The differential coverage only verifies sokol desktop, so extend the same byte-identical enum-path vs manifest-declared lifecycle check in main_zig_tests.zig to the other sokol platform cases covered by the PR (especially mobile/wasm variants). Reuse generate.generateMainZigFromTemplate and the lifecycle_override threadlocal pattern, and add platform-specific sanity anchors alongside the existing sokol_lifecycle test so regressions in those lifecycle declarations are also caught.src/codegen/manifest_v2.zig (1)
288-339: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winLGTM overall; consider adding direct test coverage for the new fields.
The new
imgui_dispatch,preview,android_registerfields and their defaults are consistent with the documented privilege model. However,synthetic_v2(the fixture exercised by this file's own tests) is not updated to include these fields, sodeclaresPrivilegedLifecycle()and the new enum fields have no direct unit test within this file — coverage currently relies entirely on downstreambackend_registry.zigtests and the real fixture manifests.🤖 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 `@src/codegen/manifest_v2.zig` around lines 288 - 339, Add direct unit-test coverage for the new Lifecycle fields in manifest_v2.zig by updating the synthetic_v2 fixture so it declares imgui_dispatch, preview, and android_register with the documented defaults and privilege combinations. Then extend the tests around declaresPrivilegedLifecycle() and the Lifecycle enum handling to assert the fixture exercises both privileged and unprivileged cases, instead of relying only on backend_registry.zig and external manifests.src/codegen/lifecycle/render.zig (1)
306-315: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStale comment:
lifecycle_ovris now matched first, not sokol/bgfx-android.This block still states "Sokol/bgfx-android are matched FIRST, so the declared branch only ever covers a genuine non-enum-tag callback backend." After this change the
if (lifecycle_ovr) |decl|branch (Line 316) is evaluated before thecfg.backend == .sokol/is_bgfx_androidbranches, so a built-in whose v2 manifest declares its lifecycle now takes the declared branch too. The inline comment at Lines 317-327 already describes the new precedence correctly, so this header contradicts it.🤖 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 `@src/codegen/lifecycle/render.zig` around lines 306 - 315, Update the stale block comment in render.zig so it matches the actual precedence in the callback lifecycle selection logic: the `lifecycle_ovr` declared branch is now checked before the `cfg.backend == .sokol` and `is_bgfx_android` branches. Adjust the wording near `use_callback_lifecycle` to reflect that built-ins with a v2 manifest lifecycle can also take the declared branch, and remove the outdated claim that Sokol/bgfx-android are matched first.
🤖 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.
Nitpick comments:
In `@src/codegen/lifecycle/render.zig`:
- Around line 306-315: Update the stale block comment in render.zig so it
matches the actual precedence in the callback lifecycle selection logic: the
`lifecycle_ovr` declared branch is now checked before the `cfg.backend ==
.sokol` and `is_bgfx_android` branches. Adjust the wording near
`use_callback_lifecycle` to reflect that built-ins with a v2 manifest lifecycle
can also take the declared branch, and remove the outdated claim that
Sokol/bgfx-android are matched first.
In `@src/codegen/manifest_v2.zig`:
- Around line 288-339: Add direct unit-test coverage for the new Lifecycle
fields in manifest_v2.zig by updating the synthetic_v2 fixture so it declares
imgui_dispatch, preview, and android_register with the documented defaults and
privilege combinations. Then extend the tests around
declaresPrivilegedLifecycle() and the Lifecycle enum handling to assert the
fixture exercises both privileged and unprivileged cases, instead of relying
only on backend_registry.zig and external manifests.
In `@test/main_zig_tests.zig`:
- Around line 255-300: The differential coverage only verifies sokol desktop, so
extend the same byte-identical enum-path vs manifest-declared lifecycle check in
main_zig_tests.zig to the other sokol platform cases covered by the PR
(especially mobile/wasm variants). Reuse generate.generateMainZigFromTemplate
and the lifecycle_override threadlocal pattern, and add platform-specific sanity
anchors alongside the existing sokol_lifecycle test so regressions in those
lifecycle declarations are also caught.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6c88b9a8-513b-4b5b-b64a-331c169e7fb6
📒 Files selected for processing (8)
backends/bgfx_v2/backend.manifest.v2.zonbackends/sokol/backend.manifest.v2.zonsrc/backend_registry.zigsrc/codegen/lifecycle/render.zigsrc/codegen/manifest_v2.zigsrc/root.zigtest/helpers.zigtest/main_zig_tests.zig
…e their lifecycle shape (#530) labelle-sokol 0.2.1 and labelle-bgfx 0.6.4 declare the callback-lifecycle SHAPE in their v2 manifests (labelle-sokol#8, labelle-bgfx#28). Bumping the builtinProvider pins means PRODUCTION generate now selects the sokol/bgfx callback shape from manifest data (assembler#529's manifest path), not the `cfg.backend == .sokol` / `is_bgfx_android` enum branches. Those branches are now dead in production and get deleted in the final #461 step. Additive/no-behavior-change: the manifest-declared shape is byte-identical to the enum shape (proven by #529's differential tests + unchanged goldens). Claude-Session: https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog
…#532) * feat(#461): fully remove the v1/legacy backend codegen path The v1/enum build-graph splice — the raw-text `build_fragments` renderers and the `switch (cfg.backend)` template sections — was the dead fallback beneath the manifest-v2 codegen after #529/#530/#531 made v2 the sole production path. This deletes it: the v2 `backend.manifest.v2.zon` is now the only codegen input. - manifest_splice.zig: delete the v1 `BackendManifest` struct + renderers (loadManifest/renderBackendDepSection/renderLinkSection/loopStyle/ mainLoopTemplateRel/…); keep the resolve-time survivors (requireManifestIfExternal, manifestPathEnabled, ProviderManifest/loadProviderManifest). - manifest_v2.zig: parseManifest/loadNamedManifest now return BackendManifestV2 directly and REJECT manifest_version < 2 (v1 is no longer a codegen input). - build_files.zig: v2 manifest is mandatory (early guard); delete the enum backend-dep/link/wasm-emsdk switches + usesEnumBackendPath/externalUsesEnumPath. - root.zig: loadBackendTemplate/resolveLoopStyleOverride/validateProviderContracts are v2-only. - lifecycle/render.zig: drop the `cfg.backend == .sokol`/`is_bgfx_android` enum residual; run-loop + shape come from the manifest. - build_zig.txt: delete the 30 enum-only sections (backend_*/link_*/header_ios| android|wasm/ios_deps/android_deps/android_link*/android_package/wasm_*). - packager.zig owns the apk/web fixtures (the enum sections are gone). - tests: the sokol-desktop byte anchor becomes committed goldens; enum-path and v1-fixture tests removed; remove the sdl / sokol_v1only / sokol_v1inv2 fixtures. Claude-Session: https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog * test(external-null): bump nullfixture pin to v0.3.0 (v2 manifest) The v1/enum codegen path is removed, so the external-null example's nullfixture backend must ship a v2 manifest. nullfixture-backend v0.3.0 adds one; bump the pin so the remote-fetch → generate → build integration exercises the v2 path. Claude-Session: https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog * refactor: androidNeedsAppImport is allocation-free (gemini) The effective root alias equals 'backend_app' iff root_alias is 'backend_app' or (no root_alias) the name is 'app' — a direct comparison, no allocPrint/dupe. Drops the allocator param + error return. Addresses the gemini finding on #532. Claude-Session: https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog
What
The generated
main.zig's callback-lifecycle shape (runner / cleanup / allocator / gui-event / imgui-dispatch / preview / Android-register) was chosen bycfg.backend == .sokol/is_bgfx_androidenum branches inlifecycle/render.zig— the last per-backend codegen keyed off the closedBackendenum. This moves the selection to manifest data, so the enum stops driving codegen.This is PR 1 of the "make the
Backendenum stop driving codegen" follow-on to the completed v2-manifest cutover (#461 steps 1–11). It lands the mechanism and proves it byte-identical in isolation; production behavior is unchanged (the external backend manifests don't declare.lifecycleyet — that's the next PR).Changes
manifest_v2.zig): extendPlatformEntry.Lifecyclewithimgui_dispatch,preview(none/callback_basic/sokol_readback),android_register(none/bgfx_shell). Additive + defaulted → every existing manifest parses unchanged; an undeclared callback backend keeps the safe third-party shape.render.zig): buildLifecycleShapefrom the manifestlifecycle_ovrfirst; thecfg.backend == .sokol/is_bgfx_androidliterals remain only as a byte-identical fallback for a built-in whose (older) manifest predates these fields — deleted in the final Epic: build-graph manifest v2 + build hook (#453 item 3) #461 step.preview = .sokol_readbackandandroid_register = .bgfx_shellemit backend-private Zig, so they're privileged — a newbackend_registry.assertLifecyclePrivilege(wired into v2 validation inroot.zig) rejects any non-labelle.*provider that declares them, preserving the third-party foot-gun protection the enum implicitly gave.backends/sokol(all 4 callback platforms) +backends/bgfx_v2(android) so codegen tests exercise the manifest path, not the fallback.Proof
main_zig_tests.zig): generate sokol-desktop and bgfx-androidmain.zigtwice, varying ONLYlifecycle_override(null → enum branch; the decl → manifest branch), assert byte-identical.someone.vulkan) declaring a privileged block is rejected; alabelle.*provider is accepted.zig build+zig build testgreen.Not in this PR (the cascade)
.lifecycleshape to the externallabelle-sokol/labelle-bgfxv2 manifests + release → 3. bump the assembler pins (production built-ins now declare their shape) → 4. delete the enum fallback branches. Refs Epic: build-graph manifest v2 + build hook (#453 item 3) #461.https://claude.ai/code/session_01P7B7UzgrWEbBYLT3YBrAog
Summary by CodeRabbit
New Features
Bug Fixes