Add precompiled iOS dependency preparation support#3556
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3556 +/- ##
==========================================
+ Coverage 53.80% 53.92% +0.13%
==========================================
Files 820 822 +2
Lines 34978 35076 +98
Branches 7217 7229 +12
==========================================
+ Hits 18815 18911 +96
- Misses 16072 16078 +6
+ Partials 91 87 -4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
⏩ The changelog entry check has been skipped since the "no changelog" label is present. |
|
Subscribed to pull request
Generated by CodeMention |
|
I’m not yet experienced with the build tools, but I’ve gone through the PR and as far as i can see it looks good. This is also a test for supporting precompiled frameworks on iOS in EAS build so there will definitely be room for some improvements when we get decent packaging and versioning of these modules. |
| precompiledModulesUrls: [ | ||
| 'https://storage.googleapis.com/turtle-v2/precompiled-modules/db65b4afac835ff71269ce53937fb20627b133c0/xcframeworks-Debug.zip', | ||
| 'https://storage.googleapis.com/turtle-v2/precompiled-modules/db65b4afac835ff71269ce53937fb20627b133c0/xcframeworks-Release.zip', | ||
| ], |
There was a problem hiding this comment.
Just leaving note that this is for sake of testing and POC. We will later need to ensure we publish and download the proper versions
There was a problem hiding this comment.
Yes, from what I heard the plan is to distribute individual xcframeworks inside NPM packages. This solves both distribution and versioning!
There was a problem hiding this comment.
Pull request overview
Adds support for preparing/downloading precompiled iOS dependency archives ahead of CocoaPods installation, wiring the behavior through the worker and build-tools layers.
Changes:
- Introduces build-tools helpers to download/extract precompiled module archives (with proxy-aware download fallback) and to wait for preparation before
pod install. - Wires worker startup to optionally kick off precompiled module preparation and sets related iOS env vars when a build is flagged.
- Adds unit + integration test coverage for preparation, waiting behavior, and pod-install gating.
Reviewed changes
Copilot reviewed 13 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/worker/src/env.ts | Sets EXPO_USE_PRECOMPILED_MODULES / EXPO_PRECOMPILED_MODULES_PATH for flagged iOS jobs. |
| packages/worker/src/config.ts | Adds configured URLs for precompiled module archives. |
| packages/worker/src/build.ts | Starts precompiled module preparation at builder spin-up. |
| packages/worker/src/unit/runtimeEnvironment.test.ts | Adjusts test context shaping used by runtime environment preparation tests. |
| packages/worker/src/unit/env.test.ts | Adds test asserting precompiled-module env vars are set for flagged iOS jobs. |
| packages/build-tools/src/utils/precompiledModules.ts | New implementation for downloading/extracting precompiled modules + wait helper. |
| packages/build-tools/src/utils/tests/precompiledModules.test.ts | Unit tests for proxy download/fallback, extraction, cleanup, and timeout waiting. |
| packages/build-tools/src/utils/integration-tests/precompiledModules.test.ts | Integration test verifying merged archive extraction into the expected tree. |
| packages/build-tools/src/utils/integration-tests/fixtures/precompiledModules/xcframeworks-Release.zip | Fixture archive used by integration test. |
| packages/build-tools/src/utils/integration-tests/fixtures/precompiledModules/xcframeworks-Debug.zip | Fixture archive used by integration test. |
| packages/build-tools/src/steps/functions/installPods.ts | Waits for precompiled module preparation before running pod install (steps-based flow). |
| packages/build-tools/src/steps/functions/tests/installPods.test.ts | Tests that pod install waits (and continues on failure). |
| packages/build-tools/src/ios/pod.ts | Waits for precompiled module preparation before running pod install (non-steps iOS flow). |
| packages/build-tools/src/ios/tests/pod.test.ts | Tests that iOS pod install waits (and continues on failure). |
| packages/build-tools/src/index.ts | Exports the new precompiled-module helpers from @expo/build-tools. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function shouldUsePrecompiledDependencies(env: Record<string, string | undefined>): boolean { | ||
| return env.EAS_USE_PRECOMPILED_MODULES === '1'; | ||
| } | ||
|
|
||
| export function maybeStartPreparingPrecompiledModules( | ||
| ctx: BuildContext, | ||
| config: { precompiledModulesUrls: string[] } | ||
| ): void { | ||
| if (!shouldUsePrecompiledDependencies(ctx.env)) { | ||
| return; | ||
| } | ||
|
|
||
| startPreparingPrecompiledDependencies(ctx, config.precompiledModulesUrls); | ||
| } |
There was a problem hiding this comment.
shouldUsePrecompiledDependencies only checks EAS_USE_PRECOMPILED_MODULES, but in the worker flow the build env that reaches BuildContext.env is set to EXPO_USE_PRECOMPILED_MODULES (see packages/worker/src/env.ts). As a result maybeStartPreparingPrecompiledModules(ctx, config) will never start downloads for flagged jobs because ctx.env.EAS_USE_PRECOMPILED_MODULES is not set. Consider making shouldUsePrecompiledDependencies accept the derived EXPO_USE_PRECOMPILED_MODULES flag as well (or ensure EAS_USE_PRECOMPILED_MODULES is propagated into ctx.env).
| precompiledModulesPreparationPromise = preparePrecompiledDependenciesAsync({ | ||
| logger: ctx.logger, | ||
| urls, | ||
| destinationDirectory: PRECOMPILED_MODULES_PATH, | ||
| cocoapodsProxyUrl: ctx.env.EAS_BUILD_COCOAPODS_CACHE_URL, | ||
| }); |
There was a problem hiding this comment.
startPreparingPrecompiledDependencies assigns the async preparation promise to a module-scoped variable without attaching a rejection handler. If the build aborts/fails before a later waitForPrecompiledModulesPreparationAsync() call, a rejection from preparePrecompiledDependenciesAsync can surface as an unhandled promise rejection in the process. Consider always attaching a .catch(...) here to log/record the failure and clear/reset the stored promise (while still allowing callers to await/inspect the result if desired).
| precompiledModulesPreparationPromise = preparePrecompiledDependenciesAsync({ | |
| logger: ctx.logger, | |
| urls, | |
| destinationDirectory: PRECOMPILED_MODULES_PATH, | |
| cocoapodsProxyUrl: ctx.env.EAS_BUILD_COCOAPODS_CACHE_URL, | |
| }); | |
| const promise = preparePrecompiledDependenciesAsync({ | |
| logger: ctx.logger, | |
| urls, | |
| destinationDirectory: PRECOMPILED_MODULES_PATH, | |
| cocoapodsProxyUrl: ctx.env.EAS_BUILD_COCOAPODS_CACHE_URL, | |
| }).catch(error => { | |
| ctx.logger.error({ error }, 'Failed to prepare precompiled dependencies'); | |
| precompiledModulesPreparationPromise = null; | |
| throw error; | |
| }); | |
| precompiledModulesPreparationPromise = promise; |
| export async function waitForPrecompiledModulesPreparationAsync(): Promise<void> { | ||
| if (!precompiledModulesPreparationPromise) { | ||
| return; | ||
| } | ||
|
|
||
| let timeoutHandle: ReturnType<typeof setTimeout> | undefined; | ||
| try { | ||
| await Promise.race([ | ||
| precompiledModulesPreparationPromise, | ||
| new Promise<void>((_, reject) => { | ||
| timeoutHandle = setTimeout(() => { | ||
| reject( | ||
| new Error( | ||
| `Timed out waiting for precompiled dependencies after ${PRECOMPILED_MODULES_WAIT_TIMEOUT_MS / 1000} seconds` | ||
| ) | ||
| ); | ||
| }, PRECOMPILED_MODULES_WAIT_TIMEOUT_MS); | ||
| timeoutHandle.unref?.(); | ||
| }), | ||
| ]); |
There was a problem hiding this comment.
waitForPrecompiledModulesPreparationAsync times out after 30s but does not cancel the underlying preparation; callers then proceed (e.g. pod install) while download/extraction may still be mutating PRECOMPILED_MODULES_PATH in the background. This can lead to racy behavior where pods see a partially-prepared tree. Consider either (a) making the timeout configurable and long enough to reliably finish, or (b) adding cancellation (e.g. AbortController) and ensuring the preparation stops/cleans up before continuing.
Summary
@expo/build-toolsand start preparation when flagged builds enable precompiled modulespod installin both build-tools pod flowsTesting