Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-reanimated-pod-install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vitest-mobile": patch
---

Fix `bootstrap` failing on iOS for projects that depend on `react-native-reanimated@4`. The React Native community CLI's `--force-pods` path calls `pod install` with `RCT_NEW_ARCH_ENABLED='0'` even when the template defaults the New Architecture to on, which trips Reanimated's podspec assertion. The harness builder now prepends `ENV['RCT_NEW_ARCH_ENABLED'] = '1'` to the scaffolded Podfile so the env is corrected inside the same pod-install process, before any podspecs are evaluated.
78 changes: 27 additions & 51 deletions packages/vitest-mobile/src/node/harness-builder/ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { execSync } from 'node:child_process';
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { log } from '../logger';
import { applyTemplateTree, fillFilePlaceholders } from '../templates';
Expand Down Expand Up @@ -46,14 +46,13 @@ export function customizeIOS(projectDir: string, cacheKey: string): void {
if (existsSync(podfilePath)) {
let podfile = readFileSync(podfilePath, 'utf8');
podfile = podfile.replace(/platform\s+:ios,\s*.+/, "platform :ios, '16.0'");
podfile = injectNewArchEnvOverride(podfile);
podfile = injectFmtCxx17Fix(podfile);
writeFileSync(podfilePath, podfile);
}

updateIOSBundleId(projectDir);

seedNewArchSnifferBait(iosDir);

// PlistBuddy is macOS-only; skip on Linux (Android-only CI runners still
// customize both platforms for the shared project, but only build one).
if (process.platform === 'darwin') {
Expand All @@ -80,9 +79,8 @@ export async function buildIOS(projectDir: string): Promise<void> {
// xcodebuild to fail with "Unable to load contents of file list" against
// the Pods xcfilelists.
//
// For why `customizeIOS` pre-seeds a sniffer-bait pbxproj instead of us
// running pod install ourselves with the right env, see
// `seedNewArchSnifferBait` below.
// For why `customizeIOS` injects an `RCT_NEW_ARCH_ENABLED` override at
// the top of the Podfile, see `injectNewArchEnvOverride`.
const buildCmd = [
'npx --yes react-native build-ios',
`--scheme ${HARNESS_APP_NAME}`,
Expand Down Expand Up @@ -135,55 +133,33 @@ export function trimIOSBuildArtifacts(projectDir: string): void {
// ── Internals ───────────────────────────────────────────────────────

/**
* Pre-seed `ios/Pods/Pods.xcodeproj/project.pbxproj` with a one-line file
* containing the literal string `-DRCT_NEW_ARCH_ENABLED=1`. This defeats the
* React Native community CLI's New-Architecture detection sniffer so that
* `react-native build-ios --force-pods` runs `pod install` with new arch on
* (matching the `RCTNewArchEnabled=true` the template writes to Info.plist).
*
* Why we need this: the CLI's `installPods` step hard-overrides
* `RCT_NEW_ARCH_ENABLED` to `'0'` or `'1'` in the pod-install child env
* based on the result of `getArchitecture(iosSourceDir)`, which is literally
*
* const project = await readFile('<iosDir>/Pods/Pods.xcodeproj/project.pbxproj');
* return project.includes('-DRCT_NEW_ARCH_ENABLED=1');
* Prepend `ENV['RCT_NEW_ARCH_ENABLED'] = '1'` to the Podfile so the New
* Architecture is on regardless of what the React Native community CLI
* tells `pod install` via the child-process env.
*
* — see `@react-native-community/cli-platform-apple/tools/getArchitecture.ts`.
* On a virgin scaffold that file doesn't exist (CocoaPods only creates it
* during `pod install`), the sniffer returns `false`, the CLI passes
* `RCT_NEW_ARCH_ENABLED='0'`, and Reanimated 4.x's podspec assertion
* aborts the install:
*
* [!] Invalid `RNReanimated.podspec` file: [Reanimated] Reanimated requires
* the New Architecture to be enabled. If you have `RCT_NEW_ARCH_ENABLED=0`
* set in your environment you should remove it.
*
* Pre-seeding a dummy file that contains the marker substring is enough to
* make the sniffer return `true` on the first build. After `pod install`
* runs successfully with new arch on, CocoaPods regenerates this file with
* a real Xcode project that also contains `-DRCT_NEW_ARCH_ENABLED=1` (in the
* pod target's `OTHER_CFLAGS`), so subsequent rebuilds keep returning `true`
* — the workaround is self-sustaining.
* Why we need this: in the `--force-pods` / `--only-pods` path the CLI's
* `resolvePods` calls `install()`, which calls `installPods()` *without*
* passing `newArchEnabled` — so the env always gets `RCT_NEW_ARCH_ENABLED
* ='0'`, regardless of what the architecture sniffer found. Reanimated 4
* .x's podspec asserts on the env var (`ENV['RCT_NEW_ARCH_ENABLED'] !=
* '0'`) and aborts the install. Setting the env in Ruby at the top of the
* Podfile happens *inside* the same pod-install process the CLI just
* spawned with `'0'`, but *before* `use_native_modules!` triggers
* `RNReanimated.podspec`, so the override sticks for the rest of the
* install — including the `NewArchitectureHelper.new_arch_enabled` read
* later that drives `use_react_native!`'s codegen and Info.plist write-
* back.
*
* Upstream fix that would remove the env injection entirely:
* https://github.com/react-native-community/cli/pull/2773 — approved by the
* maintainer but unmerged as of CLI 20.1.3. Once it ships and we adopt it
* the bait file becomes a harmless one-line scratch file that pod install
* overwrites unconditionally.
* https://github.com/react-native-community/cli/pull/2773 — approved by
* the maintainer but unmerged as of CLI 20.1.3. Once it ships and we
* adopt it, this override becomes a redundant reassignment of an env var
* that's already `'1'` (or unset, which Reanimated treats as on).
*/
function seedNewArchSnifferBait(iosDir: string): void {
const podsXcodeprojDir = resolve(iosDir, 'Pods', 'Pods.xcodeproj');
mkdirSync(podsXcodeprojDir, { recursive: true });
const pbxprojPath = resolve(podsXcodeprojDir, 'project.pbxproj');
if (existsSync(pbxprojPath)) return;
writeFileSync(
pbxprojPath,
'// vitest-mobile new-arch sniffer bait: ensures react-native-community/cli ' +
'passes RCT_NEW_ARCH_ENABLED=1 to pod install on the first run. CocoaPods ' +
'overwrites this file during pod install, and the regenerated copy carries ' +
'the same marker since new arch ends up on.\n' +
'// -DRCT_NEW_ARCH_ENABLED=1\n',
);
function injectNewArchEnvOverride(podfile: string): string {
const line = "ENV['RCT_NEW_ARCH_ENABLED'] = '1'\n";
if (podfile.includes(line)) return podfile;
return line + podfile;
}

/**
Expand Down
Loading