Commit da19bad
authored
ci: reuse native E2E builds across commits and PRs (#29247)
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.
In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->
## **Description**
Replaces the branch-keyed `cirruslabs/cache` reuse layer for native E2E
builds with a content-addressable cross-run lookup, driven by a
`build-source-hash` commit status that is computed once on a stable
runner.
### How it works
```mermaid
flowchart TD
A["post-build-source-hash<br/>(ubuntu-latest, pinned to PR head SHA)<br/>fingerprint = yarn fingerprint:generate"] -->|posts build-source-hash status<br/>+ exposes as job output| B["Build iOS / Android job"]
B --> C{"check-force-builds<br/>label OR [force-builds] in commit?"}
C -- yes --> H["Heavy native-build path<br/>setup-e2e-env + Gradle/Xcode + compile"]
C -- no --> D["find-reusable-build<br/>(GitHub Actions REST API)"]
D --> D1["Tier 1: same-branch runs"]
D1 -- miss --> D2["Tier 2: base-branch (main) runs"]
D2 -- miss --> D3["Tier 3: cross-PR runs<br/>(event=pull_request, no branch filter)"]
D1 -- hit --> E["actions/download-artifact@v4<br/>by run-id"]
D2 -- hit --> E
D3 -- hit --> E
D3 -- miss --> H
E --> F["Lean repack path<br/>Node + yarn install + repack:{ios,android}"]
H --> I["Upload .app / release.apk / androidTest.apk"]
F --> I
I -.->|feeds future runs| D
```
The probe runs **before** any heavy setup. On a hit, iOS skips
Ruby/Bundler/CocoaPods/`pod install`; Android skips
`setup-e2e-env`/Gradle. Only the JS bundle is rebuilt and re-packed into
the cached native shell.
### Benefits vs the previous `cirruslabs/cache` layer
| | Before | After (this PR) |
|---|---|---|
| **Reuse keyspace** | `${ref_name}` baked into key → every PR isolated;
only `main → PR` fallback crossed branches | Content-addressable by
fingerprint; any PR can reuse any other PR's build |
| **When the lookup runs** | *After* `setup-e2e-env` (cache key needs
`node_modules` to compute fingerprint) | Before any heavy setup |
| **Fingerprint stability** | Recomputed per runner → drifts across
macOS / Linux build / Linux CI runners; shifts on every `main` push |
Computed once on `ubuntu-latest`, pinned to `pull_request.head.sha` |
| **iOS reuse-hit wall time** | ~6m setup + ~5m repack | ~30-60s setup +
~1-2m repack |
| **Android reuse-hit wall time** | ~5m23s | ~1-2m warm / ~2-3m
cold-cache |
| **Cold build (no reuse)** | ~20-25m | unchanged |
### What changed
- **`ci.yml`** — new `post-build-source-hash` job emits the canonical
fingerprint and exposes it as a job output consumed by both build jobs.
- **New composite actions** — `find-reusable-build` (3-tier scan),
`check-force-builds` (label + `[force-builds]` commit-message escape
hatch; reads commit message via REST API to survive shallow checkout),
`post-build-source-hash` (factored out for reuse).
- **`build-{ios,android}-e2e.yml`** — probe → gate → lean-repack vs
heavy-native paths. Lean paths skip the work `yarn build:repack:*`
doesn't need.
- **`setup-e2e-env`** — new `install-foundry` input (default `true`,
opt-out for build workflows where `yarn setup:github-ci` already runs
`install:foundryup`).
- **Repack throughput** — `METRO_MAX_WORKERS` bumped `2 → 6` on the lean
path (no Gradle/Xcode competing for RAM).
- **Removed** — branch- and main-scoped `cirruslabs/cache` for
`MetaMask.app` / `release.apk` / `release-androidTest.apk`. Gradle,
Xcode DerivedData, and `.metamask` caches kept (gated to the heavy
path).
### Safety
- Fingerprint job failure or forked PR (no `statuses: write`) → empty
`source-fingerprint` → all reuse steps skipped → fresh build runs. Never
"no build".
- The same `build-source-hash` status gates the OTA path in
`push-eas-update.yml`, so a JS-only OTA cannot ship after native code
changes.
- Full architecture, decision tables, and failure-mode catalog:
`docs/ci-build-reuse.md` (new in this PR).
## **Changelog**
CHANGELOG entry: null
## **Related issues**
Refs: Prior art in MetaMask extension —
MetaMask/metamask-extension#41435
No issue: CI infrastructure improvement; no Jira ticket.
## **Manual testing steps**
```gherkin
Feature: Reusable native E2E builds on CI
Scenario: Fresh run populates the reuse pool
Given this branch has no prior completed CI run with a matching fingerprint
When CI runs on the latest commit
Then post-build-source-hash posts a build-source-hash commit status on the PR head SHA
And Build iOS E2E Apps runs setup-e2e-env and the full Xcode native compile
And Build Android E2E APKs runs setup-e2e-env and the full Gradle build
And both jobs upload artifacts named ${build_type}-${env}-MetaMask.app, ${build_type}-${env}-release.apk, and ${build_type}-${env}-release-androidTest.apk
Scenario: Empty commit reuses the prior native build (same-branch tier)
Given the branch has a prior successful run with non-expired artifacts
When an empty commit is pushed
Then post-build-source-hash computes and posts the same fingerprint as before
And find-reusable-build reports a hit via the same-branch tier
And Build iOS E2E Apps skips Ruby/Bundler/CocoaPods/Xcode setup and only runs yarn build:repack:ios
And Build Android E2E APKs skips setup-e2e-env and only runs yarn build:repack:android
And E2E tests run against the repacked artifacts
Scenario: Cross-PR reuse finds an unrelated PR with matching fingerprint
Given PR A on branch feature-a completed a fresh native build and uploaded artifacts
And PR B on branch feature-b touches only JS/tests/docs (no native-affecting files)
When PR B's CI runs
Then find-reusable-build misses the same-branch tier (feature-b runs)
And misses the base-branch tier (no recent main run with the matching fingerprint)
And matches PR A's run via the cross-PR tier (event=pull_request, no branch filter)
And PR B downloads PR A's artifacts by run-id and runs yarn build:repack:*
Scenario: force-builds label bypasses reuse
Given a PR that would otherwise hit the reuse path
When the force-builds label is added and CI re-runs
Then check-force-builds reports force=true
And find-reusable-build does not run
And both iOS and Android run a fresh native compile
And when the label is removed and CI re-runs, reuse resumes normally
Scenario: [force-builds] commit tag bypasses reuse under shallow checkout
Given a PR whose head commit message contains [force-builds]
When CI runs
Then check-force-builds reads the commit message via the GitHub REST API (not git show)
And reports force=true even with actions/checkout's default fetch-depth: 1
And both iOS and Android run a fresh native compile
Scenario: Fingerprint job failure degrades gracefully to fresh build
Given post-build-source-hash fails (e.g. broken fingerprint.config.js, transient yarn install error, or missing statuses: write on a fork)
When the build workflows run
Then inputs.source-fingerprint is empty
And every fingerprint-keyed step is skipped
And both iOS and Android run a fresh native compile
And no E2E build is blocked
Scenario: Metro transform cache persists across repack runs
Given a reuse hit on the repack path
When yarn build:repack:{ios,android} runs
Then Metro uses METRO_CACHE_DIR as its FileStore root
And the persisted transform cache is honored (no --reset-cache)
And the E2E build runs with updates disabled (no app.manifest generation)
```
## **Screenshots/Recordings**
### **Before**
N/A — CI-only change; no UI impact.
### **After**
N/A — CI-only change; no UI impact.
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
#### Performance checks (if applicable)
- [x] I've tested on Android
- [x] I've tested with a power user scenario
- [x] I've instrumented key operations with Sentry traces for production
performance metrics
For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
<!-- Generated with the help of the pr-description AI skill -->
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes CI build/reuse logic and caching paths for iOS/Android E2E
artifacts, which could cause unexpected stale/invalid artifacts or
missed rebuilds if the fingerprint/status or artifact lookup is wrong.
Scoped to CI/workflows, but failures can block or slow builds across
PRs.
>
> **Overview**
> Enables **cross-run, cross-PR reuse** of native E2E build artifacts by
introducing a canonical `@expo/fingerprint` published as a
`build-source-hash` commit status and using it to locate/download
matching artifacts from prior workflow runs.
>
> Build workflows now **gate between a full native build vs a lean
repack path**: they first check a `force-builds` override (PR label or
`[force-builds]` commit token), then attempt to find/download reusable
artifacts; on a hit they skip heavy setup (Gradle/Xcode) and only run
repack + lightweight dependency setup.
>
> CI wiring is updated to add the `post-build-source-hash` job and pass
`source-fingerprint` into iOS/Android build jobs (with added
`actions/statuses/pull-requests` read permissions), `setup-e2e-env`
gains an `install-foundry` toggle to avoid redundant installs, and
repack throughput is tuned (e.g., `METRO_MAX_WORKERS` increased on reuse
paths) with clearer guidance when repack detects a broken cached iOS
app.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
89e6bc5. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent f244684 commit da19bad
8 files changed
Lines changed: 762 additions & 128 deletions
File tree
- .github
- actions
- check-force-builds
- find-reusable-build
- post-build-source-hash
- setup-e2e-env
- workflows
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
0 commit comments