Skip to content

feat(#461): manifest-v2 PR5 — sokol android (first hook-bearing conversion) - #466

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

apotema merged 2 commits into
mainfrom
feat/453-manifest-v2-pr5-sokol-android

Conversation

@apotema

@apotema apotema commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

PR 5 of the manifest-v2 epic (#461, #453 item 3). Converts sokol android to v2 — the first conversion exercising resolve_target + post_wire + the packager apk. Gate = a golden cell (residuals live in the hook, so not a pure byte-anchor per §7).

Hook (backends/sokol/backend.hook.zig)

  • resolve_target — android ABI from -Demulator/-Dandroid_arch + host → resolveTargetQuery(.{cpu_arch, .linux, .android}). Runs before b.dependency.
  • post_wire — the §2(a) residual: NDK sysroot detection, system include paths, addLibraryPath(usr/lib/<triple>/<api>), libc.txt via setLibCFile. ctx.android_target_sdk is REQUIRED — panics if null, no orelse 34 (§4 review correction fix: Android build template — libc.txt, addLibrary, Apple Silicon emulator #6).
  • Pure decision helpers (selectAndroidArch/ndkArchTriple/requireAndroidSdk/libcTxt) unit-tested (7 tests).

Golden + hook gates (§7)

  • test/goldens/sokol_android_v2.build.zig — byte-equality vs generated output + AST-validity (0 parse errors) + hook-boundary asserts (imports backend_build_hook, calls resolve_target/post_wire with android_target_sdk = 34, uses the generic unifyCoreDiamond loop, no inline NDK/unifyGfxSubpackageCore).
  • The hook is compiled as its own test target in build.zig — "run the hook in the gate": typechecks resolve_target/post_wire against real std.Build + runs the pure-helper tests.

Intended enum-vs-v2 diffs (documented)

The v2 android build.zig deliberately differs from the enum path: inline NDK/target/libc.txt move into the hook; the unrolled override diamond + unifyGfxSubpackageCore become the generic unifyCoreDiamond loop (§5); apk packaging delegates to the shared packager (byte-identical to .android_package). Hence a golden cell, not a byte anchor.

Invariants

  • Desktop byte anchor still 0-diff (desktop v2 emitters only refactored behind a platform dispatch).
  • v1/enum path untouched — build_zig.txt zero diff; manifest_splice's only change relaxes manifestPathEnabled for the explicit v2 opt-in on non-desktop (a v1 manifest on non-desktop still uses the enum path).
  • zig build test exit 0 (verified identical to main — the 12 stderr "failed command" lines are pre-existing expectError negative tests, net-neutral). zig build exit 0.

Note: physically staging backend_build_hook.zig next to the generated build.zig is deferred to when the v2 route is promoted to the production generate path (v2 is gated-dark/test-only today per §6).

Ref #461, #453.

https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

Summary by CodeRabbit

  • New Features
    • Added Android v2 backend build support, including target resolution, staged build hooks, and APK packaging.
    • Introduced unified v2 dependency/link/packaging codegen for Android when a v2 manifest is present.
  • Bug Fixes
    • Improved Android architecture validation and NDK sysroot detection (selects the best valid NDK version).
    • Fixed manifest-driven codegen selection so non-desktop platforms don’t accidentally use desktop-only behavior.
    • Ensured Android target is defined correctly when promoted scripts are enabled.
  • Tests
    • Added new Android v2 golden coverage and regression tests for hook staging and output correctness.

…rsion)

Convert sokol Android to the manifest-v2 build-graph path — the first cell
that exercises the build hook (design §4). This is a GOLDEN cell, not a
byte-anchor: the residual moved into the imported hook and the unrolled
core-diamond overrides became the generic `unifyCoreDiamond` loop, so the
generated text legitimately differs from the enum path (§7).

What lands:
- backend.hook.zig: real `resolve_target` (android ABI from -Demulator/
  -Dandroid_arch + host) and `post_wire` (NDK sysroot detection +
  addSystemIncludePath/addLibraryPath + libc.txt). `android_target_sdk` is
  REQUIRED — the hook PANICS on null, never `orelse 34` (§4 correction #6).
  Pure decision helpers (arch select, NDK triple, required-SDK, libc.txt body)
  are unit-tested; the whole file is compiled as a test target so the residual
  typechecks against the real std.Build API.
- manifest_v2_splice.zig: android emitters — header (imports the hook + calls
  resolve_target before any b.dependency), core/gfx/engine dep decls, generic
  b.dependency + modules + artifacts + .pic, the generic core-diamond walk
  CALLS (§5), and the link section (declarative linkLibrary/linkSystemLibrary/
  link_libc + the post_wire hook call). renderBackendDepSectionV2 /
  renderLinkSectionV2 now dispatch by platform (desktop byte anchor unchanged).
- build_files.zig: wire the v2 android path (header/deps/backend-dep/link/
  package/footer + emit the walk def). Manifest loading hoisted above header
  emission so the android header can branch on v2.
- manifest_splice.zig: relax `manifestPathEnabled` so the explicit v2 opt-in
  (`backend_manifest_name`) enables the manifest path on non-desktop targets; a
  v1 manifest on a non-desktop target still falls back to the enum path.
- Golden cell: test/goldens/sokol_android_v2.build.zig + comparison, AST
  validity, and hook-boundary assertions.

Intended enum-vs-v2 diffs (documented): the inline NDK detection / target
resolution / libc.txt move into the imported hook; the unrolled overrideImport
diamond + unifyGfxSubpackageCore become the generic `unifyCoreDiamond` loop.
The APK packaging delegates to the shared packager (byte-identical to
`.android_package`). Desktop byte anchor stays 0-diff; v1/enum path and the
embedded template are untouched.

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

📝 Walkthrough

Walkthrough

This PR adds Android support for the sokol backend v2 hook flow, routes v2 manifest-driven Android codegen through the generator and splice modules, adds hook-focused tests, and validates the generated Android build script with golden and regression coverage.

Changes

Android v2 hook and codegen

Layer / File(s) Summary
Android backend hook implementation
backends/sokol/backend.hook.zig
Adds HookError, target-resolution helpers, resolve_target, NDK sysroot selection, requireAndroidSdk, libcTxt, Android post_wire, and helper tests.
Hook test target wiring
build.zig
Adds a hook_tests target for backend.hook.zig and wires it into the main test step.
Manifest path gating for non-desktop platforms
src/codegen/manifest_splice.zig
Allows manifest-driven codegen on non-desktop targets when an explicit manifest name is supplied.
Android v2 splice emitters
src/codegen/manifest_v2_splice.zig
Adds Android v2 header, deps, backend-dep, and link emitters, plus staging for the backend hook and platform-based dispatch.
build_files.zig v2 manifest wiring
src/build_files.zig
Loads named v1/v2 manifests, switches Android emission to v2 renderers when present, and emits the Android core-diamond walk after footer wiring.
Android v2 golden and validation tests
test/goldens/sokol_android_v2.build.zig, test/build_zig_tests.zig
Adds the committed Android v2 golden build script and tests for byte-for-byte output, AST validity, target aliasing, and hook staging.

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

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Generator as build_files.zig
  participant Splice as manifest_v2_splice.zig
  participant Hook as backend.hook.zig
  participant NDK as Android NDK

  Generator->>Splice: renderAndroidHeaderV2 / renderAndroidDepsDeclsV2
  Generator->>Splice: renderBackendDepSectionV2(ANDROID)
  Splice->>Hook: resolve_target(b, ctx)
  Hook-->>Splice: ResolvedTargetInfo
  Generator->>Splice: renderLinkSectionV2(ANDROID)
  Splice->>Hook: post_wire(b, ctx)
  Hook->>NDK: selectGreatestValidNdk(...)
  Hook-->>Splice: android-libc.txt, include/lib paths
Loading

Poem

A rabbit hopped through build-time glade,
With Android hooks and goldens made.
resolve_target, then post_wire too,
The APK shone bright and true.
🐇✨

🚥 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 matches the main change: the first manifest-v2 Android sokol conversion with hook support.
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-pr5-sokol-android

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

@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: 5fe10ae2a8

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

try w.print(
\\const std = @import("std");
\\
\\const backend_build_hook = @import("{s}");

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 Stage the hook before importing it

When the v2 Android path is exercised (backend_manifest_name opt-in), this generated import requires a sibling backend_build_hook.zig, but the generator only writes the generated build.zig/build.zig.zon and never copies the manifest's build_hook file to that name. As a result, the generated v2 Android project fails at import resolution before resolve_target or post_wire can run.

Useful? React with 👍 / 👎.

@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 `@backends/sokol/backend.hook.zig`:
- Around line 202-220: The NDK selection logic in post_wire currently picks the
lexicographically greatest directory before validating it, so a stray or partial
install can cause the wrong candidate to win. Update the directory scan that
builds latest to validate each directory’s sysroot path (using ndk_dir,
ndkHostTag(), and the sysroot join) before treating it as the best candidate,
and only keep candidates that actually exist. If a newer-looking directory is
invalid, continue scanning so an older valid NDK can still be selected instead
of failing later with “Could not find Android NDK.”

In `@src/build_files.zig`:
- Around line 306-310: The Android build header is missing the target alias when
promoted scripts are present, so `emitPromotedScriptModules` can emit `.target =
target` without a definition. Update the `build_files.zig` Android branch to
also emit `android_target_alias` when promoted scripts exist, alongside the
existing `cfg.plugins`, `cfg.ecs`, and `cfg.hasGui()` conditions. Use the
`emitPromotedScriptModules` and `android_target_alias` symbols to locate the
affected logic and ensure generated build.zig always defines `target` for
Android.
🪄 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: 56640456-8148-4387-a823-96aed5464e9c

📥 Commits

Reviewing files that changed from the base of the PR and between 115f82a and 5fe10ae.

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

Comment thread backends/sokol/backend.hook.zig Outdated
Comment thread src/build_files.zig
… validation, stage v2 hook

Finding 1 (CRITICAL): the android/ios `target` alias (`const target =
<platform>_target;`) was emitted only under `plugins|ecs|gui`, but
`emitPromotedScriptModules` unconditionally references `.target = target`. A game
with promoted (FlowNodes-bearing) scripts and no plugins/ECS/GUI produced an
undefined `target`. The alias guard now also fires on `promoted_scripts.len > 0`.
This guard is SHARED by both the v2 and enum android routes, so the fix covers
BOTH; applied the same fix to the ios guard for parity.

Finding 2 (Minor): `getAndroidNdkSysroot` picked the lexicographically-greatest
NDK dir and only checked its sysroot AFTER, so a stray/partial install could
shadow a valid older NDK and panic. Now collects each candidate with whether its
sysroot exists and picks the greatest VALID one via the new pure, unit-tested
`selectGreatestValidNdk` helper.

Finding 3 (P2): the generated v2 android build.zig `@import`s
`backend_build_hook.zig`, but nothing staged it. Added `stageBackendBuildHook`
(re-exported from the generator) which copies the manifest's `build_hook` file
next to the generated build.zig under that name, mirroring the other sibling
writes.

Tests: hook unit test for the stray-NDK shadow case; regression tests asserting
both v2 and enum android define `target` with promoted scripts + no
plugins/ECS/GUI (and AST-parse); a staging test asserting the fixture hook bytes
land at `backend_build_hook.zig`. Desktop byte anchor and android golden
unchanged (0-diff). `zig build` + `zig build test` green.

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.

🧹 Nitpick comments (1)
src/root.zig (1)

83-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: inline import breaks the file's "imports at top" convention.

PromotedScript is re-exported via a fresh inline @import("codegen/scan.zig") rather than reusing a top-level module alias like every other import in this file (lines 6-24). Purely cosmetic — no functional impact.

♻️ Optional cleanup
+const scan = `@import`("codegen/scan.zig");
 const gui_resolve = `@import`("gui_resolve.zig");
 pub const app_icon = `@import`("app_icon.zig");
@@
 pub const backend_build_hook_name = manifest_v2_splice.hook_import_name;
-pub const PromotedScript = `@import`("codegen/scan.zig").PromotedScript;
+pub const PromotedScript = scan.PromotedScript;
🤖 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/root.zig` around lines 83 - 88, The PromotedScript re-export in root.zig
uses a fresh inline `@import`, which breaks the file’s existing “imports at top”
convention. Add a top-level module alias for codegen/scan.zig alongside the
other imports in this file, then re-export PromotedScript through that alias so
the import pattern stays consistent with the rest of root.zig.
🤖 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/root.zig`:
- Around line 83-88: The PromotedScript re-export in root.zig uses a fresh
inline `@import`, which breaks the file’s existing “imports at top” convention.
Add a top-level module alias for codegen/scan.zig alongside the other imports in
this file, then re-export PromotedScript through that alias so the import
pattern stays consistent with the rest of root.zig.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d7fb5155-2260-4e75-8ad1-12d7196cad6e

📥 Commits

Reviewing files that changed from the base of the PR and between 5fe10ae and 95e8f9e.

📒 Files selected for processing (5)
  • backends/sokol/backend.hook.zig
  • src/build_files.zig
  • src/codegen/manifest_v2_splice.zig
  • src/root.zig
  • test/build_zig_tests.zig
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/build_files.zig
  • src/codegen/manifest_v2_splice.zig
  • backends/sokol/backend.hook.zig

@apotema
apotema merged commit de6004b into main Jul 1, 2026
4 checks passed
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