Skip to content

ci: reuse native E2E builds across commits and PRs#29247

Merged
tommasini merged 48 commits into
mainfrom
ci/reusable-build-e2e-v3
May 8, 2026
Merged

ci: reuse native E2E builds across commits and PRs#29247
tommasini merged 48 commits into
mainfrom
ci/reusable-build-e2e-v3

Conversation

@tommasini
Copy link
Copy Markdown
Contributor

@tommasini tommasini commented Apr 23, 2026

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

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
Loading

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 actionsfind-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 throughputMETRO_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

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

Performance checks (if applicable)

  • I've tested on Android
  • I've tested with a power user scenario
  • I've instrumented key operations with Sentry traces for production performance metrics

For performance guidelines and tooling, see the Performance Guide.

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.

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.

Reviewed by Cursor Bugbot for commit 89e6bc5. Bugbot is set up for automated code reviews on this repo. Configure here.

@tommasini tommasini self-assigned this Apr 23, 2026
@github-actions
Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbotv2 metamaskbotv2 Bot added the team-mobile-platform Mobile Platform team label Apr 23, 2026
@tommasini tommasini marked this pull request as ready for review April 23, 2026 10:52
@tommasini tommasini requested a review from a team as a code owner April 23, 2026 10:52
@github-actions github-actions Bot added the risk:high AI analysis: high risk label Apr 23, 2026
Comment thread .github/workflows/build-android-e2e.yml
Comment thread scripts/repack.js Outdated
Comment thread package.json Outdated
@github-actions
Copy link
Copy Markdown
Contributor

AI PR Analysis

🚫 Merge safe: false | 🟠 Risk: high

Merge decision: AI analysis did not complete — manual review required before merging.

AI analysis did not complete. Manual review recommended.

View run

Comment thread .github/workflows/build-android-e2e.yml Outdated
Comment thread .github/workflows/build-android-e2e.yml Outdated
tommasini added 3 commits May 7, 2026 22:27
…abs/cache runs on Namespace- it would have caused redundant restore and a redundant cache save at end-of-run on every Namespace build. Fixed by re-adding the gate
…K cache restore steps (apk-cache-restore and apk-cache-restore-main). With the step skipped, cache-hit stays empty, the gate's elif doesn't match, and we fall through to needs-native-build=true — the actual fresh build.
Comment thread .github/workflows/build-ios-e2e.yml Outdated
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.52%. Comparing base (8208502) to head (d2959a4).
⚠️ Report is 43 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #29247      +/-   ##
==========================================
+ Coverage   81.44%   81.52%   +0.07%     
==========================================
  Files        5318     5328      +10     
  Lines      140909   141136     +227     
  Branches    32109    32157      +48     
==========================================
+ Hits       114767   115055     +288     
+ Misses      18263    18221      -42     
+ Partials     7879     7860      -19     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread .github/workflows/build-ios-e2e.yml
Comment thread .github/workflows/ci.yml
Cal-L
Cal-L previously approved these changes May 7, 2026
Copy link
Copy Markdown
Contributor

@Cal-L Cal-L left a comment

Choose a reason for hiding this comment

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

Lgtm

Comment thread .github/workflows/build-ios-e2e.yml
Copy link
Copy Markdown
Contributor

@Cal-L Cal-L left a comment

Choose a reason for hiding this comment

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

Lgtm

@tommasini tommasini enabled auto-merge May 8, 2026 00:46
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations, SmokeWalletPlatform
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 82%
click to see 🤖 AI reasoning details

E2E Test Selection:
These changes are purely CI/build infrastructure changes implementing a cross-run artifact reuse system for both Android and iOS E2E builds. The key changes are:

  1. New GitHub Actions: check-force-builds, find-reusable-build, post-build-source-hash - these implement a fingerprint-based build artifact reuse system
  2. Modified build-android-e2e.yml and build-ios-e2e.yml: Major refactoring to support a "native-build gate" that either reuses prior artifacts or falls back to fresh builds
  3. Modified setup-e2e-env/action.yml: Added optional install-foundry input
  4. Modified ci.yml: Added post-build-source-hash job and updated build job dependencies
  5. scripts/repack.js: Minor error message update only

No application source code was changed. The risk is that the new build pipeline might produce incorrect or broken test artifacts, which would cause E2E tests to fail. To validate the pipeline works correctly, a representative set of E2E tests should run across core areas (accounts, confirmations, wallet platform) to confirm the built artifacts are functional. Running all tags would be excessive since the app code itself hasn't changed - we just need enough coverage to verify the build artifacts are valid and the pipeline works end-to-end.

The selected tags (SmokeAccounts, SmokeConfirmations, SmokeWalletPlatform) cover the most fundamental wallet flows and will adequately validate that the build artifacts produced by the new pipeline are functional.

Performance Test Selection:
These changes are purely CI/build infrastructure changes with no modifications to application source code, UI components, state management, or any code paths that could affect runtime performance. No performance tests are warranted.

View GitHub Actions results

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 89e6bc5. Configure here.

Comment thread .github/workflows/ci.yml
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: yarn install --immutable
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fingerprint job uses @v4 actions, missing corepack setup

Medium Severity

The post-build-source-hash job uses actions/checkout@v4 and actions/setup-node@v4 while every other job in the workflow uses @v6. More critically, it does not explicitly enable corepack or prepare a specific Yarn version before running yarn install --immutable. The project's package.json declares "packageManager": "yarn@4.10.3", while the build jobs explicitly activate yarn@3.8.7 via corepack prepare. If actions/setup-node@v4 handles corepack differently from @v6 (which other jobs use successfully without explicit corepack steps), the fingerprint job could fail or use a mismatched Yarn version, silently disabling the entire build-reuse feature (fingerprint output stays empty → every build is fresh).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 89e6bc5. Configure here.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 8, 2026

@tommasini tommasini added this pull request to the merge queue May 8, 2026
Merged via the queue into main with commit da19bad May 8, 2026
100 of 102 checks passed
@tommasini tommasini deleted the ci/reusable-build-e2e-v3 branch May 8, 2026 18:01
@github-actions github-actions Bot locked and limited conversation to collaborators May 8, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.78.0 Issue or pull request that will be included in release 7.78.0 label May 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.78.0 Issue or pull request that will be included in release 7.78.0 risk:high AI analysis: high risk size-L team-mobile-platform Mobile Platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants