Skip to content

perf(mobile): preserve Android native outputs during clean prebuild - #18073

Open
bompus wants to merge 3 commits into
pingdotgg:mainfrom
bompus:perf/android-prebuild-native-cache
Open

bompus wants to merge 3 commits into
pingdotgg:mainfrom
bompus:perf/android-prebuild-native-cache

Conversation

@bompus

@bompus bompus commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Problem

mobile-native-client runs a clean Expo prebuild before building an Android development client. Deleting the generated Android directory also deletes .gradle and app/.cxx, so repeated builds lose Gradle task history and the app's native compilation outputs even when their inputs are unchanged.

Change

Move those two directories aside on the same filesystem before Android prebuild, then restore them before Gradle runs. Skip caches behind symlinked directories and refuse restoration through a symlinked parent. Clean prebuild still regenerates native source, autolinking and JS bundle outputs. The existing tracked-native-file guard remains in place, and iOS follows its existing path.

Restore saved outputs after prebuild failure or interruption too. If recovery fails or a regenerated cache directory conflicts, retain the saved files and print their recovery path. This also reports the path when a pending interruption suppresses the returned error. If recovery also fails after a prebuild or save failure, the helper returns the original cause and prints the recovery instructions.

Scope and approval

This is a focused fix for cache loss in the existing native-client command. It preserves outputs that Gradle/Ninja invalidate against their inputs; it adds no build mode, dependency or product setting. I am submitting it under the focused obvious-bug exception without a prior issue or maintainer approval.

Replaces #15412, which was closed because its body did not name the model and harness. The code is unchanged; they are stated at the end.

Fixes #18161.

Verification

  • Eleven filesystem tests pass, covering successful regeneration, prebuild failure, partial save failure, conflicting directories, restore failure and interruption. One test exercises all six regeneration/completion/interruption orders with both restoration outcomes. Symlink tests verify that caches stay at their original targets and recovery does not write through a regenerated symlinked parent.
  • Removing cache preservation makes the successful-regeneration test fail with a missing cache file. Removing recovery reporting makes the interruption/recovery-failure test fail.
  • Focused formatting and lint checks pass. vp exec tsc -p scripts/tsconfig.json --noEmit passes.
  • On Linux, at f0ebcf6, the production helper completed two real expo prebuild --clean --platform android --no-install runs followed by ARM64 :app:assembleDebug builds. Both APKs passed signature verification. A further clean prebuild and debug build passed after rebasing onto the current upstream base.

Before-and-after timing

A controlled comparison of clean Expo prebuild plus ARM64 debug Gradle build on 4cff81a reduced mean elapsed time from 67.7 s to 46.2 s, saving 21.6 s (31.8%).

Build stage Measured runs (s) Mean (s) Sample standard deviation (s)
Before: clean prebuild discards native outputs 71.166, 65.872, 66.185 67.741 2.970
After: same prebuild through prebuildAndroid 48.697, 44.199, 45.647 46.181 2.296

Both versions used the same source, checkout, environment and warm shared Gradle cache. Each received three warm-ups and an excluded pilot before six measured runs in ABBAAB order. An earlier sequence was discarded after another job briefly overlapped. The reported sequence had no competing heavy job or swap activity. Hyperfine timed the commands; APK signature and content checks ran outside the timed interval.

Environment: Ubuntu/WSL, Ryzen 9 9950X3D with 16 exposed vCPUs, 47 GiB RAM, Node 24.21.0, Java 17 and Android build tools 37.0.0. Both versions used APP_VARIANT=development, NODE_ENV=development, a 12 GiB memory cap and nice -n 10. The prebuild command was expo prebuild --clean --platform android --no-install; the Gradle command was:

./gradlew :app:assembleDebug -PreactNativeArchitectures=arm64-v8a \
  --build-cache --parallel --max-workers=2 --no-daemon \
  -Dorg.gradle.jvmargs='-Xmx3072m -XX:MaxMetaspaceSize=768m' \
  -Pkotlin.compiler.execution.strategy=in-process

All six reported builds succeeded, and their APKs passed signature verification. All 3,242 non-signature ZIP entries had identical content hashes across both versions. Generated project/build outputs were regenerated each time.

This is development evidence for warm repeated debug builds of one unchanged app revision. It excludes dependency installation, warm-ups, fingerprint discovery and device installation, and does not establish cold-build or cross-revision performance. macOS/Windows execution and cancellation of an actual Expo subprocess were not checked; interruption tests use Effect fibers with deterministic barriers.

GPT-6.1 Sol, in Codex.

Closes #18161

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 11, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 11, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 4cff81a

Macroscope's review found this PR approvable — This PR is confined to local Android development-build tooling and tests, preserving native caches around an existing clean prebuild without changing product behavior, defaults, schemas, deployment, or static-analysis settings. The recovery and interruption paths are covered by focused filesystem tests.

Notes:

  • Diff unchanged. Approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9f88d7aa-f6cc-42c5-99b3-ee4ed8233909



📥 Commits

Reviewing files that changed from the base of the PR and between 77c8747 and 4cff81a.




📒 Files selected for processing (2)
  • scripts/mobile-native-client.test.ts
  • scripts/mobile-native-client.ts



Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.





📝 Walkthrough
📝 Walkthrough
📝 Walkthrough

Walkthrough

Android clean prebuilds now preserve eligible .gradle and app/.cxx directories. The helper attempts cache recovery after prebuild outcomes, reports recovery failures, and propagates the original prebuild failure. iOS continues to run prebuild directly.

Changes

Android prebuild cache preservation

Layer / File(s) Summary
Preserve caches around Android prebuild
scripts/mobile-native-client.ts, scripts/mobile-native-client.test.ts
Android prebuild saves eligible cache directories in a temporary location and restores them when target paths remain safe and unoccupied. Tests cover cache selection, symlinks, conflicts, save and restore failures, recovery reporting, and interruption. iOS keeps its direct prebuild path.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant BuildFlow
  participant prebuildAndroid
  participant AndroidFilesystem
  participant ExpoPrebuild
  BuildFlow->>prebuildAndroid: Pass Android prebuild effect
  prebuildAndroid->>AndroidFilesystem: Save eligible caches
  prebuildAndroid->>ExpoPrebuild: Run clean prebuild
  ExpoPrebuild-->>prebuildAndroid: Return prebuild outcome
  prebuildAndroid->>AndroidFilesystem: Restore caches when target paths are safe
  prebuildAndroid-->>BuildFlow: Return prebuild outcome
Loading




Merge Risk: ⚪ Minimal · up to 4cff8

Android clean prebuilds now keep the Gradle and native build caches and restore them afterward, which shortens repeat builds. If restoring fails, the saved files stay in place and their location is reported. iOS is unchanged. The change affects only local build tooling, and no concrete defects were found.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 4cff8

The change is confined to local Android development builds and includes safeguards against symlink traversal, conflicting restoration targets and loss of recoverable outputs. No introduced security vulnerability was established. Remaining uncertainty concerns native-cache invalidation and interruption of the actual prebuild subprocess.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The established new mutation scope is the current checkout's Android cache directories and adjacent recovery directories, under the invoking build process's existing filesystem authority. The production caller derives the mobile root from the script location rather than a CLI-supplied path. No cross-service exposure is established.

Trust Boundaries and Controls

  • inferred — The symlink and conflict checks constrain ordinary path redirection, but are separate filesystem operations rather than an atomic path-authority guarantee. Concurrent workspace mutation remains possible. The base already performed destructive clean regeneration in the same shared tree, so this alone does not establish a new privilege boundary bypass.

Resilience and Maintainability Implications

  • observed — The production command runner scopes the spawned process, but the inspected source does not establish whether interruption waits for all prebuild writers to terminate before cache restoration. Cooperative-effect recovery is tested; operating-system process interruption remains an evidence gap, not a verified failure.

Hardening Proposals

  • proposed — Validate the scoped subprocess termination contract with an interruption scenario that establishes that prebuild writers have stopped before restored caches become visible.



Pre-merge checks | Passed 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check Passed The title clearly and concisely describes the main change: preserving Android native outputs during clean prebuild.
Description check Passed The description includes all required sections. It explains the problem, implementation, scope and approval basis, linked issues, focused verification, performance evidence, limitations, and the model…

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR





  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 11, 2026 — with ChatGPT Codex Connector
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 11, 2026 07:07

Dismissing prior approval to re-evaluate 4cff81a

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Android native-client builds lose Gradle history and native compile outputs on every clean prebuild

2 participants