Repository navigation
Epic: build-graph manifest v2 + build hook (#453 item 3) #461
Description
Activity
- added 7 commits that reference this issue
on Jul 1, 2026 Milestone: PRs 1–11 merged — the v2 mechanism is complete (gated-dark)
PR PR #462 v2 types + parse #467 sokol ios #463 core-diamond walk #468 sokol wasm #464 wire + sokol byte anchor (0 diff) #469 null + wgpu (generic desktop path) #465 shared packager #470 raylib + sdl #466 sokol android (first hook) #471 bgfx all platforms #472 open config (3rd-party) + capability gate All 6 backends are v2-modeled (byte-anchor for sokol-desktop; golden cells for every other platform, with the hooks compiled + run in the gate). A third-party backend (
acme.foo) generates a valid build.zig with no enum tag. All of this is opt-in / gated-dark — productiongeneratestill uses the v1/enum path, byte-unchanged.Remaining = the production cutover (distinct from the above)
PR 12 ("delete enum-path + v1 splice") can't happen until production actually runs on v2, which needs:
generatecutover — production auto-detects/usesbackend.manifest.v2.zoninstead of preflighting the legacy v1 name (the feat(#461): manifest-v2 PR11 — open config for 3rd-party backends + capability gate #472 P2 finding).- External-repo v2 adoption — the 6
labelle-<backend>repos ship theirbackend.manifest.v2.zon(today those live only as in-tree test fixtures). A per-repo sweep like the Backend ecosystem-hardening: enable 3rd-party backend authoring (follow-up to #386) #453 metadata adoption. - Then delete the enum sections + v1 splice + convert the sokol-desktop anchor to a golden.
This phase changes production output and spans all 6 backend repos, so it's a deliberate rollout rather than a mechanical delete.
- added 4 commits that reference this issue
on Jul 1, 2026 Milestone: production cutover DONE — all 6 backends flipped to v2
Following the mechanism (PRs 1–11) + the design-doc hardening, the production cutover landed:
- Generate cutover (feat(#461): production generate cutover — auto-detect + use backend.manifest.v2.zon #473):
generateauto-detects a backend package'sbackend.manifest.v2.zonand uses the v2 path (hardened across the full v1/v2 named-manifest matrix over 4 review rounds). - v2 shipped to all 6 backend repos (labelle-{null,wgpu,sdl,raylib,sokol,bgfx}) as
backend.manifest.v2.zon(+ hooks for android/ios/wasm), released: null 0.2.0, wgpu 0.2.0, sdl 0.2.0, raylib 0.2.0, sokol 0.2.0, bgfx 0.3.0. - Pins flipped (feat(#461): flip null to v2 in production (bump pin → labelle-null 0.2.0) #474 null, feat(#461): flip wgpu/sdl/bgfx to v2 in production (pin bumps) #476 wgpu/sdl/bgfx, feat(#461): flip raylib/sokol to v2 — completes Phase A-flip (all 6 backends) #477 raylib/sokol):
builtinProvidernow points every backend at its v2-shipping release. Productiongeneratebuilds all 6 via the v2 build graph.
Real bugs the incremental flip surfaced (all fixed)
- Tests-target capability gate ×2 sites (feat(#461): flip null to v2 in production (bump pin → labelle-null 0.2.0) #474): the Backend-agnostic test target: .labelle/tests/ via null backend #83 forced-null tests target tripped the v2 capability gate (
raw_gui_adapter) for GUI projects. Fixed at bothroot.validateProviderContractsandbuild_files.generateBuildZig(skip capability requirement for the tests target; identity checks still run). raw_gui_adapterderivation (feat(#461): flip raylib/sokol to v2 — completes Phase A-flip (all 6 backends) #477): required for any raw_backend GUI, but raylib renders imgui via the rlImGui bridge, not an in-backend adapter. Fixed: only require it whenbridge_dir == null(in-backend path); bridge GUIs don't.- emcc hook (raylib/sokol):
pathJoin→forward-slash; ReleaseSafe keeps assertions (-sASSERTIONS=1).
Validation: desktop + wasm fully CI-covered (raylib gamepad, null headless, emsdk); bgfx-android + sokol cross-build in CI.
Remaining = Phase C (PR 12): delete the enum-path sections + v1 splice
Gated on on-device android/ios render validation for sokol + bgfx on v2 — CI covers the android/ios cross-build (compile/link), but not device render, and deleting the enum path removes the fallback. Per the extraction discipline (bgfx-android was render-validated on-device on a Tab A7), the enum deletion should follow an on-device v2 render check. The byte-anchor test also converts to a golden snapshot at that point (design §6 step 5). This step needs a device, so it's the deliberate final gate.
- Generate cutover (feat(#461): production generate cutover — auto-detect + use backend.manifest.v2.zon #473):
- added 4 commits that reference this issue
on Jul 1, 2026 Follow-on: lifecycle SHAPE off the
Backendenum (#461 literal-agnosticism)Migrating the last per-backend codegen predicates that keyed off
cfg.backend— starting with the callback-lifecycle shape (the biggest one,lifecycle/render.zig). Cascade so far:- ✅ feat(#461): select callback-lifecycle shape from manifest data, not the backend enum #529 — mechanism:
PlatformEntry.Lifecyclegainsimgui_dispatch/preview/android_register;render.zigbuilds the shape from manifest data first, enum branches demoted to a byte-identical fallback; privilegedsokol_readback/bgfx_shellgated to thelabelle.*namespace (assertLifecyclePrivilege). Proven by differential tests (mutation-verified) + unchanged goldens. - ✅ labelle-sokol#8 → v0.2.1 / labelle-bgfx#28 → v0.6.4 — external v2 manifests declare the shape (backward-compatible:
ignore_unknown_fields). - ✅ feat(#461): bump sokol 0.2.1 / bgfx 0.6.4 — built-ins declare their lifecycle shape #530 — bump the pins → production now selects the shape from manifest data; the
cfg.backend == .sokol/is_bgfx_androidshape branches are dead in production.
Remaining (the deletion):
- Delete the
render.zigsokol / bgfx-android shape fallback branches + repoint the existing sokol/bgfx main.zig unit tests to setlifecycle_override(they currently rely on the enum branch). - Migrate the other
is_bgfx_android/cfg.backend == .sokolsites:callback_dispatch_handled, the platform/entry comments, andbuild_files.zig's bgfxbackend_appimport (→ a manifestextra_modulessignal). - (Separate, device-gated) the enum-PATH build_zig.txt sections + v1 splice — still gated on on-device sokol/bgfx render validation.
Step 1–2 removes a safety-net fallback + rewrites several tests, so it's the highest-care step; doing it deliberately rather than at the tail of a long session.
- ✅ feat(#461): select callback-lifecycle shape from manifest data, not the backend enum #529 — mechanism:
Lifecycle-enum reduction landed (#531) — full removal is coupled to v1-drop
Attempted to delete the
Backend-enum branches from the lifecycle codegen (render.zigshape +use_callback_lifecycle+callback_dispatch_handled, andbuild_files.zig's bgfxbackend_appimport). Review (codex + gemini) surfaced that the enum is load-bearing for the v1/legacy callback path:resolveLifecycleOverrideis v2-only andmanifestPathEnabledskips non-desktop v1, so a tag-matched sokol/bgfx resolving only a v1 (or no) manifest reaches the callback lifecycle with both overrides null — deleting the enum rendered it as a loop with unfilled callback holes / wrong shape /ExternalCallbackBackendUnsupported.So the shape-enum can't be deleted independently of dropping v1-manifest support — which is the device-gated enum-path deletion. #531 therefore landed the safely-separable subset:
- v2 (production) lifecycle is fully manifest-driven — dispatch reordered so
lifecycle_ovr != nullshort-circuits before the enum; production never consults it. - bgfx
backend_appimport is manifest-driven — reads the platform entry'sextra_moduleseffective alias (backend_app), so a name-only third-party backend works too; fixed an alias bug (was matchingmod.name, now the emittedbackend_<name>alias). - Comments derive from
shape.android == .bgfx_shell; tests exercise the v2 path. - The enum remains only as a documented v1/legacy fallback (structurally unreachable for v2), alongside
androidNeedsAppImport's v1 fallback and the untouchedandroid_link_bgfxlink site.
Remaining for full literal name-free: the device-gated enum-PATH deletion (§6 step 5) now also removes these v1 lifecycle-enum residuals in one pass, once the on-device sokol/bgfx v2 render check is done. That's the single remaining step; it needs a device.
- v2 (production) lifecycle is fully manifest-driven — dispatch reordered so
- added a commit that references this issue
on Jul 3, 2026 ✅ v1/legacy codegen path FULLY REMOVED (#532) — the assembler is now literally manifest-v2-only
The final deletions PR (§6 step 5) landed. The
Backendenum no longer appears anywhere in the codegen path —grep -E 'switch (cfg.backend)|cfg.backend == .|is_bgfx_android' src/codegen src/build_files.zig→ zero.Removed (~1100 lines net):
build_zig.txt: the 30 enum-path sections (.backend_*/.link_*/.header_{ios,android,wasm}/.ios_deps/.android_deps/.android_package/.wasm_*/.*_link*). Kept only the scaffolding the v2 splice also emits.manifest_splice.zig: the v1 struct + renderers (BackendManifest,loadManifest,renderBackendDepSection/renderLinkSection,loopStyle, …). Relocated the v2-shared survivors (ProviderManifest/identity slice,LEGACY_MANIFEST_NAME,platformEntry).parseManifestnow returnsBackendManifestV2directly and rejectsmanifest_version < 2with a readable error.build_files.zig: the threeswitch (cfg.backend)sites +usesEnumBackendPath; v2 is now mandatory (v2_manifest orelse return error.ExternalBackendNeedsManifest).render.zig: the lifecycle enum residual (the feat(#461): delete the dead lifecycle enum branches (manifest-driven lifecycle) #531 v1 fallback).- Fixtures:
backends/sdl,backends/sokol_v1only,backends/sokol_v1inv2+genSokolBuildZig.
Guard: the sokol-desktop byte-anchor converted to 4 committed goldens (
test/goldens/sokol_desktop_v2*.build.zig, captured while still proven byte-identical to the enum baseline). Every other golden unchanged.zig build testgreen.Cross-repo fallout fixed: the
external-nullexample'snullfixturebackend was v1-only → generation failed under v2-mandatory. Added a v2 manifest to labelle-nullfixture-backend v0.3.0 (mirrors labelle-null) and bumped the example's pin; Examples-integration is green end-to-end.⚠️ On-device caveat (for the human): this removed the enum/v1 build-graph fallback the design gated on on-device sokol+bgfx v2 render validation. CI covers the Android/iOS cross-build (compile/link) and the text goldens are blind to render behavior — please run the on-device v2 render check to confirm.The only residual
switch (cfg.backend)left anywhere is indeps_linker.zig(shared sub-package staging, defensive/dead — not the build-graph codegen). #461 is complete modulo that on-device confirmation.✅ ON-DEVICE RENDER VALIDATION PASSED — the device gate is cleared
Ran both backends on a Galaxy Tab A7 (SM-T505, Android 12) with the v2-only assembler (main @ #532,
LABELLE_ASSEMBLERoverride confirmed in the generate log —using assembler: …/labelle-assembler-282/…),labelle android run --release:- bgfx-android (
.backend = .bgfx, core 1.22 / engine 1.65 / gfx 1.19) — generate → NDK ReleaseFast build → APK → install → launch → renders cleanly: the shape demo (magenta rect + teal square w/ orange diamond + teal circle on cream). Crash-free, foregrounded NativeActivity. - sokol-android (
gamepad-android, imgui GUI, core 1.24 / engine 1.66 / gfx 1.20) — same pipeline → renders cleanly: the imgui "Gamepad Demo" HUD (title bar + all text). Crash-free, foregrounded NativeActivity.
Both generated entirely through the v2 manifest path (the enum/v1 fallback is gone) and render correctly on-device — so removing the enum/v1 build-graph fallback caused zero on-device regression. This was the last thing gating #461; the epic is now fully validated end-to-end (unit goldens byte-identical + Examples-integration CI + on-device render on both real backends).
- bgfx-android (
Status narrowing: the manifest-v2 migration is effectively COMPLETE — #529–#532 removed the v1/enum codegen path entirely (v2 mandatory, all 6 backends external, ~0 live
cfg.backend==branches). What remains open here is ONLY the gated on-device render validation (sokol + bgfx on v2, which CI can't run — it covers cross-build compile/link + goldens, not device render). Everything else in the §8 plan (PRs 1–12) landed. Suggest this stays open solely as the on-device-render tracking item, or closes once that human check is done.
The capstone of the backend-agnostic assembler (#386) / ecosystem-hardening (#453 item 3). Supersede the two current codegen routes (enum-path
build_zig.txtsections + the thin v1build_fragments/*.txtsplice) with a declarative v2backend.manifest.zon(modules/artifacts/system_libs/frameworks/platforms) + a constrained, versioned build hook for the genuinely-imperative residuals (NDK sysroot+libc.txt, iOS xcrun SDK, emcc).Design:
docs/design/manifest-v2-build-graph.md(merged #456, hardened through review in #459 — incorporates the comptime dep-options correction, dynamic target selection,backend.hook.zigas a separate file, capabilities retained in v2, required Android SDK, hook-aware golden gate, etc.).PR plan (from the design's §8)
android_approot_alias, NDK ordering) — High/LMigration is flag-day-safe: v1/enum path stays alive, v2 gated on
manifest_version >= 2, one backend + one platform at a time (sokol desktop first), enum sections deleted only after all 6 are v2. Regression gate = one anchored byte-diff then golden snapshots (hook-bearing cells snapshot the hook source / run it against a fixture Build).Tracking each PR here. Ref #453, #386, RFC #378.
https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw