Skip to content

fix(codegen): let a tag-matched external backend use the enum path on non-desktop (#386) - #417

Merged
apotema merged 2 commits into
mainfrom
fix/386-external-enum-fallthrough
Jun 30, 2026
Merged

fix(codegen): let a tag-matched external backend use the enum path on non-desktop (#386)#417
apotema merged 2 commits into
mainfrom
fix/386-external-enum-fallthrough

Conversation

@apotema

@apotema apotema commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

The second (and final) flip-blocker — found by on-device validation

Validating Flying Platform against the out-of-tree labelle-bgfx on a real Galaxy Tab A7 surfaced this: generate --platform=android failed with ExternalBackendNeedsManifest.

Root cause: the backend-dep section rejected every external backend that didn't reach the manifest splice (else if (cfg.isExternal()) return ExternalBackendNeedsManifest). But the splice is desktop-only — so on Android an external backend has no splice, and the guard wrongly rejected it instead of falling through to the enum path.

Fix

An external backend whose package name matches its enum tag — e.g. extracted bgfx via .backend = .bgfx (the enum-as-shorthand resolves to a package while preserving the tag) — should use the enum switch (cfg.backend) on non-desktop targets. That's the correct codegen: the android sections pull the backend from b.dependency("labelle_<tag>"), which resolves to the fetched package. Only a backend named only by string (no matching tag, cfg.backend at its .raylib default) must still hard-error, since the switch would emit raylib codegen.

Gate the reject on cfg.backendName() != @tagName(cfg.backend). This is the second of the two flip-blockers (the first was the render.zig callback guard, #416).

Validated on-device (Galaxy Tab A7, Android 12)

Flying Platform built against the out-of-tree labelle-bgfx: generate → NDK cross-compile → APK → install → launch → runs crash-free. The external libgame.so loads, NativeActivity + GL context come up, no crash.

It behaves identically to bundled bgfx — both render black under the version skew this setup forces (local main assembler vs FP's released engine 1.65.0; the build prints cli 1.50.0 / gfx 1.16.1 may be incompatible with core 1.21.0). That black reproduces with bundled bgfx too, so it is not the extraction — a clean render check needs a version-matched assembler release (which ships the extraction). The extraction introduces no on-device regression.

Tests

  • A tag-matched external bgfx on android uses the enum path (no error, emits the android backend section).
  • A string-only external backend still errors (ExternalBackendNeedsManifest).
  • zig build test green.

Net effect on the flip

With #416 + this, both flip-blockers are resolved. The default .backend = .bgfx → external flip is now codegen-ready on desktop and android; the remaining gate is cutting an assembler release (so the version skew goes away) + the test-harness updates for the flipped default + an on-device render confirmation against that release.

Summary by CodeRabbit

  • Bug Fixes
    • Improved external backend handling during build file generation so enum-matching external backend packages follow the normal backend selection path instead of failing unnecessarily.
    • Manifest-needed errors are now raised only when an external backend package name does not match the selected backend enum tag, making error reporting more accurate across targets.
  • Tests
    • Added coverage for external backend scenarios, including enum-matched and non-matched cases across different target types.

)

The backend-dep section rejected EVERY external backend that didn't reach the
manifest splice (`else if (cfg.isExternal()) return ExternalBackendNeedsManifest`).
But the splice is DESKTOP-ONLY, so on Android (and other non-desktop targets)
an external backend has no splice — and the guard wrongly rejected it.

For an external backend whose package name MATCHES its enum tag — e.g. the
extracted bgfx selected via `.backend = .bgfx` (the enum-as-shorthand resolves
to a package while preserving the tag, #386 Phase 6c) — the enum
`switch (cfg.backend)` is the correct codegen on non-desktop: the android
sections pull the backend from `b.dependency("labelle_<tag>")`, which resolves
to the fetched package. Only a backend named ONLY by string (no matching tag,
`cfg.backend` at its `.raylib` default) must still hard-error, since the switch
would emit the wrong (raylib) codegen.

Gate the reject on `cfg.backendName() != @TagName(cfg.backend)`. This is the
second of the two flip-blockers (the first was the render.zig callback guard,
#416).

VALIDATED ON-DEVICE (Galaxy Tab A7 / Android 12): Flying Platform built against
the out-of-tree labelle-bgfx package — generate → NDK cross-compile → APK →
install → launch — runs crash-free, and behaves IDENTICALLY to the bundled
bgfx (both render black under the local-assembler-vs-released-engine version
skew the build warns about; that black is NOT the extraction — it reproduces
with bundled bgfx too). The extraction introduces no on-device regression.

Tests: a tag-matched external bgfx on android uses the enum path (no error,
emits the android backend section); a string-only external backend still errors.
@coderabbitai

coderabbitai Bot commented Jun 29, 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: c8e8e998-1d8e-4af9-b0d3-79016631edc5

📥 Commits

Reviewing files that changed from the base of the PR and between 03a6c3d and 3f601eb.

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

📝 Walkthrough

Walkthrough

generateBuildZig now only raises error.ExternalBackendNeedsManifest for external backends whose package name does not match the selected enum tag. Matching external backends can continue through existing codegen paths, and new tests cover both the passing and failing cases.

Changes

External backend enum-path fallthrough fix

Layer / File(s) Summary
Conditional error guard
src/build_files.zig
Adds externalUsesEnumPath(cfg) and uses it to gate the backend dependency and desktop native link checks, so only non-matching external backends hit error.ExternalBackendNeedsManifest.
External backend tests
test/build_zig_tests.zig
Adds tests for Android enum-tag matches, non-matching string-only externals without project_dir, and enum-matched externals on WASM still failing with error.ExternalBackendNeedsManifest.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

🐇 I hopped by the build with a tag and a name,
When they matched just right, the error stayed tame.
On Android I bounced through the code with a grin,
And mismatched externals still bounced back in.

🚥 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 clearly describes the main change: tag-matched external backends now use the enum path on non-desktop targets.
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 fix/386-external-enum-fallthrough

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 updates the build file generation logic in src/build_files.zig to allow external backends whose package names match their enum tags to fall through to the built-in backend switch on non-desktop targets, and adds corresponding unit tests. Feedback highlights a missed guard in the desktop link section (around line 583) that still uses the unconditional cfg.isExternal() check and should be updated to match the new logic.

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.

Comment thread src/build_files.zig Outdated
// external backend reaches the splice branch. Guard so an external
// backend never falls into the built-in `switch (cfg.backend)` (which
// reads a meaningless enum tag for a backend named only by string).
} else if (cfg.isExternal() and !std.mem.eql(u8, cfg.backendName(), @tagName(cfg.backend))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

While this condition correctly allows tag-matched external backends to fall through to the built-in switch when the manifest splice doesn't run (e.g., on non-desktop or when project_dir is null), a corresponding guard in the desktop link section at line 583 was missed and still uses the unconditional cfg.isExternal() check:\n\nzig\n} else if (cfg.isExternal()) {\n return error.ExternalBackendNeedsManifest;\n}\n\n\nThis will cause tag-matched external backends on desktop to fail with ExternalBackendNeedsManifest when project_dir is null (such as in tests or dry-runs), even though they should be allowed to fall through to the built-in link switch. Please update the guard at line 583 to match this one:\n\nzig\n} else if (cfg.isExternal() and !std.mem.eql(u8, cfg.backendName(), @tagName(cfg.backend))) {\n

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — the desktop link guard (line ~589) now routes through the same new externalUsesEnumPath(cfg) helper as the backend-dep guard, so the two can't drift. (It's the desktop exe path, where the helper is false, so an external backend still errors there — but they're now symmetric.)

@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/build_files.zig`:
- Around line 244-256: The fallthrough in the backend selection logic is too
broad: it now lets all tag-matched external backends reach the enum path, but
the WASM-specific codegen in the later backend sections still treats
cfg.isExternal() as a hard skip. Narrow the exception in the backend switch so
it only applies to the validated Android path, or propagate the same tag-matched
external allowance through the WASM handling in build_files.zig where the
cfg.isExternal() guards control the wasm_emsdk_* and link_*_wasm sections.
🪄 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: 10b144a4-956b-4d9a-a709-7401e12239bd

📥 Commits

Reviewing files that changed from the base of the PR and between 9ea5f67 and 03a6c3d.

📒 Files selected for processing (2)
  • src/build_files.zig
  • test/build_zig_tests.zig

Comment thread src/build_files.zig Outdated
…oderabbit/gemini)

Two bot findings on the fallthrough guard:
- coderabbit: it was too broad — a tag-matched external on wasm would pass here
  but miss the wasm-specific sections. Scope the exception to the validated
  ANDROID path via a new externalUsesEnumPath(cfg) helper (android + name==tag).
- gemini: the desktop LINK guard (line ~589) still used a bare cfg.isExternal().
  Route it through the SAME helper so the two guards can't drift (it's the
  desktop exe path, so the helper is false there and external still errors).

Tests: tag-matched external on wasm now errors (locks the android-only scope);
the android tag-matched + string-only cases unchanged.
@apotema
apotema merged commit 8b8c3e8 into main Jun 30, 2026
6 checks passed
@apotema
apotema deleted the fix/386-external-enum-fallthrough branch June 30, 2026 00:40
apotema added a commit that referenced this pull request Jun 30, 2026
… (#418)

Pluggable-backends Phases 5+6 (epic #386): the assembler can now FETCH, verify,
and build out-of-tree backend packages, and bgfx is extracted to its own repo.

- #409 6a: remote .backend_package cache-fetch (fetched like a plugin)
- #410 6b: contract-verify an external backend (assertBackend/Window/Input)
- #411: conform raylib + null windows to the canonical window contract
- #412 Phase 5: enum-as-shorthand resolution (a built-in tag can resolve to a package)
- #413 6c: bgfx extracted -> github.com/labelle-toolkit/labelle-bgfx; opt-in CI-verified
- #414: select the GUI bridge by backend name, not the enum (external backends)
- #415: labelle-bgfx v0.2.0 — gamepad sources extracted to their own packages
- #416/#417: the two flip-blockers (callback-external guard; android enum-fallthrough)

Opt-in today via .backend_package; built-in .backend = .bgfx still ships bundled
(the default-flip is a follow-up gated on this release). Built-in backends are
byte-identical. External bgfx validated on-device (Galaxy Tab A7): builds, runs
crash-free, behaves identically to bundled.
apotema added a commit that referenced this pull request Jun 30, 2026
…lt (#386 Phase 6c)

`builtinProvider(.bgfx)` now returns the labelle-bgfx provider package
(github.com/labelle-toolkit/labelle-bgfx v0.2.0), so `.backend = .bgfx`
transparently fetches + builds the OUT-OF-TREE backend instead of the bundled
`backends/bgfx` slot. bgfx is the first backend fully external by default.

The enum-as-shorthand preserves the `.bgfx` tag, so every platform path keeps
working: desktop via the manifest splice, android via the enum sections (which
pull from `b.dependency("labelle_bgfx")` → fetched package). Both flip-blockers
are already fixed (#416 callback guard, #417 enum-fallthrough).

Test rework (bgfx is now external, not bundled):
- backend_registry: the all-bundled guard now expects bgfx external (tag
  preserved, named "bgfx"); the "not external" case uses raylib (still bundled).
- deps_linker: stagesSdlGamepad/stagesAndroidGamepad no longer stage siblings
  for bgfx (external = self-contained, carries its own gamepad packages).
- build_zig: the bgfx-artifacts test overrides to the in-tree package via a
  local path + project_dir (desktop splice); the exe-naming tests switch to
  raylib (they test naming, not bgfx); the core-import loop drops bgfx.

Existing projects on older assembler releases are unaffected (they pin those
versions). On-device (Galaxy Tab A7) external bgfx builds + runs crash-free and
behaves identically to bundled. NOTE: a clean-render confirmation against a
VERSION-MATCHED stack is the recommended final check before this ships in a
release (FP's current pins have a gfx/core mismatch that black-screens BOTH
bundled and external — not the flip).
apotema added a commit that referenced this pull request Jun 30, 2026
…lt (#386 Phase 6c) (#419)

* feat(config): flip .backend = .bgfx to the extracted package by default (#386 Phase 6c)

`builtinProvider(.bgfx)` now returns the labelle-bgfx provider package
(github.com/labelle-toolkit/labelle-bgfx v0.2.0), so `.backend = .bgfx`
transparently fetches + builds the OUT-OF-TREE backend instead of the bundled
`backends/bgfx` slot. bgfx is the first backend fully external by default.

The enum-as-shorthand preserves the `.bgfx` tag, so every platform path keeps
working: desktop via the manifest splice, android via the enum sections (which
pull from `b.dependency("labelle_bgfx")` → fetched package). Both flip-blockers
are already fixed (#416 callback guard, #417 enum-fallthrough).

Test rework (bgfx is now external, not bundled):
- backend_registry: the all-bundled guard now expects bgfx external (tag
  preserved, named "bgfx"); the "not external" case uses raylib (still bundled).
- deps_linker: stagesSdlGamepad/stagesAndroidGamepad no longer stage siblings
  for bgfx (external = self-contained, carries its own gamepad packages).
- build_zig: the bgfx-artifacts test overrides to the in-tree package via a
  local path + project_dir (desktop splice); the exe-naming tests switch to
  raylib (they test naming, not bgfx); the core-import loop drops bgfx.

Existing projects on older assembler releases are unaffected (they pin those
versions). On-device (Galaxy Tab A7) external bgfx builds + runs crash-free and
behaves identically to bundled. NOTE: a clean-render confirmation against a
VERSION-MATCHED stack is the recommended final check before this ships in a
release (FP's current pins have a gfx/core mismatch that black-screens BOTH
bundled and external — not the flip).

* fix(cache): fetch/validate the EFFECTIVE backend package (covers the flip)

validateCache + ensureCache checked the raw `.backend_package` field, which is
null for a flipped built-in (`.backend = .bgfx` resolves via builtinProvider, not
an explicit package). So a flipped backend was never fetched — ensureCache said
'all cached' and generate then failed staging with ExternalBackendNeedsManifest.
Route both through `cfg.effectiveBackendPackage()` (the same accessor
resolveBackendPackage uses) so an extracted built-in is fetched like any
external backend.

Caught by the bgfx Android .so cross-build CI job once #419 flipped bgfx; locally
masked by the monorepo sibling-fallback + explicit-config paths.

* doc: update builtinProvider comment — bgfx is extracted, not 'all bundled' (gemini)
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