Skip to content

fix(ios): Swift header not found under Xcode 26 explicit modules - #278

Merged
kherembourg merged 1 commit into
Purchasely:mainfrom
AurelienV-42:patch-1
Sep 3, 2026
Merged

fix(ios): Swift header not found under Xcode 26 explicit modules#278
kherembourg merged 1 commit into
Purchasely:mainfrom
AurelienV-42:patch-1

Conversation

@AurelienV-42

Copy link
Copy Markdown
Contributor

Problem

Building an app that depends on react-native-purchasely (6.0.0) under Xcode 26, with the default "Explicitly Built Modules" build setting, fails with:

'react_native_purchasely-Swift.h' file not found

pointing at PurchaselyRN.m's #import "react_native_purchasely-Swift.h".

Root cause

PurchaselyRN.m mixes Objective-C and Swift sources in the same CocoaPods target (it imports the compiler-generated Swift interface header to use PLYTransitionFactory, defined in PLYTransitionFactory.swift). The import is a plain, quoted #import "...".

Under Xcode 26's explicit module builds, Clang's dependency scanner doesn't treat a quoted, non-modular #import as an edge to the pod's own module — so it has no guarantee that this target's Swift compilation (which emits the generated header) has run before the ObjC file is scanned/compiled. The header sometimes doesn't exist yet when the ObjC file needs it.

Fix

Prefer the angle-bracket, module-qualified form of the import (<react_native_purchasely/react_native_purchasely-Swift.h>), guarded with __has_include so it falls back to the original quoted import wherever the module path isn't available (older Xcode, non-explicit-modules builds, etc.):

#if __has_include(<react_native_purchasely/react_native_purchasely-Swift.h>)
#import <react_native_purchasely/react_native_purchasely-Swift.h>
#else
#import "react_native_purchasely-Swift.h"
#endif

The module-qualified form lets Clang's explicit-module scanner resolve it as a real dependency on the react_native_purchasely module, so the Swift compile is correctly ordered before this file.

Testing

Verified with a real bun run ios (Expo / React Native 0.85, Xcode 26) build, install, and app launch on iOS Simulator — fails with the exact error above without this change, succeeds with it. No Podfile-level workaround (e.g. disabling SWIFT_ENABLE_EXPLICIT_MODULES/CLANG_ENABLE_EXPLICIT_MODULES for the pod target) was needed once this import is fixed.

@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR conditionally prefers the module-qualified generated Swift header to support Xcode 26 explicit-module dependency scanning while preserving the existing quoted import as a fallback.

  • Adds an __has_include guard around the generated Swift interface import.
  • Uses the pod module path when available and retains compatibility with non-modular CocoaPods layouts.

Confidence Score: 5/5

The PR appears safe to merge with no actionable defects identified.

The module-qualified import is selected only when available, and the unchanged quoted import remains as a compatibility fallback for non-modular CocoaPods builds.

Important Files Changed

Filename Overview
packages/purchasely/ios/PurchaselyRN.m Safely adds a guarded module-qualified Swift header import without changing the fallback behavior of existing build configurations.

Reviews (1): Last reviewed commit: "fix(ios): Swift header not found under X..." | Re-trigger Greptile

Copy link
Copy Markdown

Heads up: this fix unblocks a stuck batch of 7 open Dependabot PRs (#271#277) — every one of them fails build-ios with the exact symptom described here (Xcode 26 explicit-modules, react_native_purchasely-Swift.h not found). Full tracking/table is on #271 since Issues are disabled on this repo. No action needed here beyond normal review — just flagging the downstream impact of merging this.


Generated by Claude Code

chouaibMo added a commit that referenced this pull request Aug 20, 2026
## Summary

`build-ios` has failed on **every** PR since late July — including PRs
that only touch a test-project lockfile. The failure is the CI cache,
not the app code.

```
"typeinfo for facebook::react::YogaStylableProps", referenced from ...
"vtable for facebook::react::DebugStringConvertible", referenced from
    ... in libRNScreens.a(RNSFullWindowOverlay.o)
ld: symbol(s) not found for architecture arm64
```

## Root cause

The CocoaPods cache included `example/ios/Pods` — the **generated**
install tree — behind a loose `restore-keys: ${{ runner.os
}}-cocoapods-` fallback. The v6 migration (#243, merged 2026-07-24)
changed the Podfile/podspec hash, so the exact key has never matched
since. Every run therefore restores a **pre-v6 Pods tree**. From the
failing run's log:

```
key:      macOS-cocoapods-21c48bdc...   (requested)
restored: macOS-cocoapods-a2f4547b...   (stale, different key)
```

`pod install` does not fully reconcile a stale tree. RNScreens then
compiled against **stale React Native headers** while linking the
**current** prebuilt `React.xcframework` — a header/binary mismatch that
surfaces as those missing `facebook::react::*` vtables.

The timeline matches exactly: `build-ios` passed through 2026-07-25 and
has failed on every run since, across unrelated branches.

## Verification

On this same commit, with a clean `pod install` and the exact CI
`xcodebuild` invocation:

```
** BUILD SUCCEEDED **
```

Same prebuilt core, same Xcode 26. **Only the stale cache distinguishes
CI from a working build.** As a control, a *stale* local Pods tree
failed too — with a different stale-artifact error (`Build input file
cannot be found: RCTConvert_PurchaselyRN.m`, a file the v6 reorg
removed), independently showing that a stale Pods tree survives `pod
install` and breaks the build.

## Fix

- **Cache only `~/.cocoapods` and `~/Library/Caches/CocoaPods`.** These
are content-addressed download caches, so a restore-key hit is safe and
still avoids re-downloading pods. The Pods tree is regenerated each run
(~20s locally).
- **Add `yarn.lock` to both cache keys** so a React Native version bump
invalidates them.
- **Drop the DerivedData `restore-keys`** so it is exact-match only. A
partial hit would mix object files built against a different Pods
install — the same staleness class of bug. It was latent here (that
cache was cold in the failing run), but it is the same footgun.

Applied to both jobs that install the example Pods: `build-ios` and
`ios-unit-tests`.

## Note on what this does *not* change

No app, Podfile, or dependency changes — the diff is workflow-only. That
is deliberate: if `build-ios` goes green here, it confirms the
diagnosis, since nothing but the cache behaviour changed.

Unrelated and left alone: #278 fixes a *different* iOS problem (a Swift
header **compile** error under Xcode 26 explicit modules); it does not
address this **link** failure.

## Test plan

- [x] Clean `pod install` + CI's exact `xcodebuild` command locally —
`** BUILD SUCCEEDED **`
- [x] Confirmed the failing CI run restored a stale cache under a
different key
- [x] Confirmed DerivedData cache was cold in the failing run (so Pods
is the culprit)
- [x] `ruby -ryaml` parses the workflow; all 7 jobs still present
- [ ] `build-ios` green in CI on this PR (the real test)
- [ ] `iOS Unit Tests (bridge)` still green with the narrowed cache

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add conditional import for Swift header based on availability.
@kherembourg
kherembourg merged commit b99a2d1 into Purchasely:main Sep 3, 2026
7 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants