Skip to content

chore(ios): make the live-activity embed-phase reorder survive expo prebuild #424

Description

@RonenMars

The trap

PR #419 adds the expo-widgets widget-extension target. Xcode appends the generated "Embed Foundation Extensions" copy phase last in the Threadbase target's buildPhases, after three script phases:

  • [Expo Dev Launcher] Strip Local Network Keys for Release
  • [CP] Embed Pods Frameworks
  • Upload Debug Symbols to Sentry

Those scripts consume Threadbase.app/Info.plist, whose ProcessInfoPlistFile command Xcode orders after the PlugIns copy. The copy therefore transitively depends on itself and the build dies:

Cycle inside Threadbase; building could produce unreliable results.
→ Target 'Threadbase' has copy command from '.../ExpoWidgetsTarget.appex' to '.../Threadbase.app/PlugIns/ExpoWidgetsTarget.appex'
○ That command depends on command in Target 'Threadbase': script phase "Upload Debug Symbols to Sentry"
○ That command depends on command in Target 'Threadbase': script phase "[CP] Embed Pods Frameworks"
○ That command depends on command in Target 'Threadbase': script phase "[Expo Dev Launcher] Strip Local Network Keys for Release"
○ Target 'Threadbase' has process command with output '.../Threadbase.app/Info.plist'
○ Target 'Threadbase' has copy command from '.../ExpoWidgetsTarget.appex' to '.../Threadbase.app/PlugIns/ExpoWidgetsTarget.appex'

The fix applied in #419 moves the embed phase to its conventional position, immediately after Resources. That resolves the cycle and the build succeeds.

It is a hand edit to ios/Threadbase.xcodeproj/project.pbxproj. The next expo prebuild regenerates the phase order and reintroduces the cycle — and it breaks for whoever runs prebuild next, not for whoever introduced it. That person gets an opaque Cycle inside Threadbase with no obvious link to Live Activities.

What to build

plugins/withLiveActivityTarget.js, registered in app.json after the expo-widgets plugin entry so it runs on the already-generated target.

Requirements, modelled on the conventions in plugins/withAndroidReleaseSigning.js:

  • Tagged — carry a TAG const (e.g. live-activity-embed-order) used in any generated markers and in every error message, matching the existing plugin's style.
  • Idempotent across prebuild — reordering an already-correct buildPhases array must be a no-op, not a second move. Check the phase's current index before touching it.
  • Loud throw when the anchor stops matching — if the "Embed Foundation Extensions" phase or the Resources phase can't be found, throw with the tag and enough context to act on, rather than silently leaving the cycle in place. The existing Android plugin's alreadyApplied / anchor-miss branches are the pattern to copy: a silent failure here surfaces as a confusing build error much later.

One deviation from the Android plugin

withAndroidReleaseSigning.js uses mergeContents from @expo/config-plugins/build/utils/generateCode because it patches Groovy source text with regex anchors.

A pbxproj should not be text-patched. Use withXcodeProject, which hands over the parsed xcode project object — locate the native target by name, find its buildPhases entries by comment, and reorder the array. Same tagging/idempotence/throw discipline, different mechanism.

Resolve @expo/config-plugins through expo (as the Android plugin does) rather than adding a top-level dependency, so the same copy prebuild uses is picked up.

Verification

After implementing, from a clean checkout:

npx expo prebuild --platform ios --no-clean
cd ios && pod install && cd ..
xcodebuild -workspace ios/Threadbase.xcworkspace -scheme Threadbase \
  -configuration Debug -destination 'generic/platform=iOS Simulator' \
  CODE_SIGNING_ALLOWED=NO build

Expected: BUILD SUCCEEDED, no Cycle inside Threadbase, and Threadbase.app/PlugIns/ExpoWidgetsTarget.appex present. Running prebuild twice in a row must produce an identical project.pbxproj the second time.

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions