Skip to content

Commit b33830a

Browse files
authored
Merge pull request #64 from Iterable/feature/SDK-515-upgrade-expo-to-latest-sdk-versio8n
feat: add iOS fmt workaround for Xcode 26.4 compatibility
2 parents 3683ec7 + 500ce2c commit b33830a

10 files changed

Lines changed: 154 additions & 20 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ yarn-error.log
6060
example/ios/
6161
example/android/
6262
example/google-services.json
63+
.yarn/install-state.gz
64+
example/.yarn/install-state.gz
6365

6466
# Coverage
65-
coverage/
67+
coverage/

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,21 @@
33
All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
and this project adheres to [Semantic
7+
Versioning](https://semver.org/spec/v2.0.0.html).
8+
9+
## [1.1.0] - 2026-07-01
10+
11+
### Changed
12+
- Updated `expo` to `^55.0.0` (Expo SDK 55)
13+
- Updated `react` to `19.2.0`
14+
- Updated `react-native` to `0.83.2`
15+
- Updated `@iterable/react-native-sdk` peer dependency to `^3.0.0`
16+
- Updated minimum Node.js version to `20.19.4`
17+
18+
### Notes
19+
- Expo SDK 55 requires React Native's New Architecture
20+
- Tested with Expo SDK 55
721

822
## [1.0.1] - 2025-07-16
923

example/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ npx expo run:android
9797
- Check that `google-services.json` is properly placed (Android)
9898
- Verify certificates and provisioning profiles (iOS)
9999

100+
4. **iOS build fails on `fmt` / `FMT_STRING` / `library 'fmt' not found` (Xcode 26.4+)**
101+
- This is a known React Native + Xcode 26.4 compatibility issue
102+
- `@iterable/expo-plugin` injects a Podfile workaround automatically during prebuild
103+
- Rebuild native code: `npx expo prebuild --clean`, then `cd ios && pod install && cd ..`
104+
- If you still see the error, confirm your generated `ios/Podfile` contains the
105+
`@iterable/expo-plugin: fmt workaround for Xcode 26.4` comment inside `post_install`
106+
100107
### Development Tips
101108

102109
- Use `yarn start` to start the Metro bundler

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"open:android": "open -a \"Android Studio\" android"
1212
},
1313
"dependencies": {
14-
"@iterable/react-native-sdk": "^2.0.2",
14+
"@iterable/react-native-sdk": "^3.0.0",
1515
"@react-navigation/native": "^7.0.19",
1616
"@react-navigation/stack": "^7.2.3",
1717
"expo": "^53.0.19",

example/yarn.lock

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,20 +1481,19 @@ __metadata:
14811481
languageName: node
14821482
linkType: hard
14831483

1484-
"@iterable/react-native-sdk@npm:^2.0.2":
1485-
version: 2.0.2
1486-
resolution: "@iterable/react-native-sdk@npm:2.0.2"
1484+
"@iterable/react-native-sdk@npm:^3.0.0":
1485+
version: 3.0.0
1486+
resolution: "@iterable/react-native-sdk@npm:3.0.0"
14871487
peerDependencies:
14881488
"@react-navigation/native": "*"
14891489
react: "*"
14901490
react-native: "*"
14911491
react-native-safe-area-context: "*"
1492-
react-native-vector-icons: "*"
14931492
react-native-webview: "*"
14941493
peerDependenciesMeta:
14951494
expo:
14961495
optional: true
1497-
checksum: 10c0/d1d6485dd3b982642df9569a4793aae0ff71191f8f6faffadbb580594db8e9cf2089f8a9b668d09c7b53e93ecbcd9d054833a8b82e0bb2a1d0d9a68a269d5967
1496+
checksum: 10c0/986244429e63646cd641a7f7be1a7c93be05f12000d80d1fa5cebce29bc4d87566e72766d1a3f57fe7f5c0a579a8c7d4b5b238cc815c8be161aef0a022314cfc
14981497
languageName: node
14991498
linkType: hard
15001499

@@ -3309,7 +3308,7 @@ __metadata:
33093308
resolution: "expo-plugin-example@workspace:."
33103309
dependencies:
33113310
"@babel/core": "npm:^7.25.2"
3312-
"@iterable/react-native-sdk": "npm:^2.0.2"
3311+
"@iterable/react-native-sdk": "npm:^3.0.0"
33133312
"@react-navigation/native": "npm:^7.0.19"
33143313
"@react-navigation/stack": "npm:^7.2.3"
33153314
"@types/react": "npm:~19.0.10"

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
"main": "build/withIterable.js",
66
"types": "build/withIterable.d.ts",
77
"scripts": {
8-
"build": "expo-module build",
8+
"build": "expo-module build && yarn build:plugin",
9+
"build:plugin": "tsc -p plugin",
910
"clean": "expo-module clean",
1011
"lint": "expo-module lint",
1112
"test": "expo-module test",
1213
"test:coverage": "jest test --coverage --config jest.config.js",
13-
"prepare": "expo-module prepare",
14+
"prepare": "expo-module prepare && yarn build:plugin",
1415
"prepublishOnly": "expo-module prepublishOnly",
1516
"expo-module": "expo-module",
1617
"open:ios": "xed example/ios",
@@ -36,7 +37,7 @@
3637
"devDependencies": {
3738
"@commitlint/config-conventional": "^19.6.0",
3839
"@evilmartians/lefthook": "^1.5.0",
39-
"@iterable/react-native-sdk": "^2.0.2",
40+
"@iterable/react-native-sdk": "^3.0.0",
4041
"@react-native/eslint-config": "^0.79.5",
4142
"@release-it/conventional-changelog": "^9.0.2",
4243
"@types/jest": "^29.5.14",
@@ -60,7 +61,7 @@
6061
"typescript": "^5.3.3"
6162
},
6263
"peerDependencies": {
63-
"@iterable/react-native-sdk": "^2.0.2",
64+
"@iterable/react-native-sdk": "^3.0.0",
6465
"expo": "*",
6566
"react": "*",
6667
"react-native": "*"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
createMockPodfileConfig,
3+
createTestConfig,
4+
type WithIterableResult,
5+
} from '../__mocks__';
6+
import { FMT_WORKAROUND_MARKER } from '../src/withIosFmtWorkaround';
7+
import withIterable from '../src/withIterable';
8+
9+
const EXPO_PODFILE_SNIPPET = `
10+
post_install do |installer|
11+
react_native_post_install(
12+
installer,
13+
config[:reactNativePath],
14+
:mac_catalyst_enabled => false,
15+
:ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true',
16+
)
17+
end
18+
`;
19+
20+
describe('withIosFmtWorkaround', () => {
21+
it('injects the fmt workaround after react_native_post_install', async () => {
22+
const result = withIterable(createTestConfig(), {}) as WithIterableResult;
23+
const modifiedPodfile = await result.mods.ios.podfile(
24+
createMockPodfileConfig({ contents: EXPO_PODFILE_SNIPPET })
25+
);
26+
27+
expect(modifiedPodfile.modResults.contents).toContain(
28+
FMT_WORKAROUND_MARKER
29+
);
30+
expect(modifiedPodfile.modResults.contents).toContain(
31+
"config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'"
32+
);
33+
});
34+
35+
it('does not inject the workaround twice', async () => {
36+
const result = withIterable(createTestConfig(), {}) as WithIterableResult;
37+
const firstPass = await result.mods.ios.podfile(
38+
createMockPodfileConfig({ contents: EXPO_PODFILE_SNIPPET })
39+
);
40+
const secondPass = await result.mods.ios.podfile(
41+
createMockPodfileConfig({ contents: firstPass.modResults.contents })
42+
);
43+
44+
const markerCount = (
45+
secondPass.modResults.contents.match(
46+
new RegExp(
47+
FMT_WORKAROUND_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
48+
'g'
49+
)
50+
) || []
51+
).length;
52+
53+
expect(markerCount).toBe(1);
54+
});
55+
});

plugin/src/withIosFmtWorkaround.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { ConfigPlugin, withPodfile } from 'expo/config-plugins';
2+
3+
/** Marker comment used to avoid injecting the workaround more than once. */
4+
export const FMT_WORKAROUND_MARKER =
5+
'# @iterable/expo-plugin: fmt workaround for Xcode 26.4';
6+
7+
/**
8+
* Compiles the fmt pod in C++17 mode so FMT_USE_CONSTEVAL stays disabled.
9+
* Required for Xcode 26.4+ when building React Native 0.79+.
10+
*
11+
* @see https://github.com/Iterable/react-native-sdk/blob/main/example/ios/Podfile
12+
*/
13+
const FMT_WORKAROUND_RUBY = `
14+
${FMT_WORKAROUND_MARKER}
15+
installer.pods_project.targets.each do |target|
16+
target.build_configurations.each do |config|
17+
# fmt/base.h unconditionally redefines FMT_USE_CONSTEVAL based on __cplusplus,
18+
# so preprocessor defines are overwritten. The reliable fix is to compile fmt
19+
# in C++17 mode: FMT_CPLUSPLUS (201703L) < 201709L → FMT_USE_CONSTEVAL = 0.
20+
# All other pods stay in C++20 (React-perflogger needs std::unordered_map::contains).
21+
if target.name == 'fmt'
22+
config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
23+
end
24+
end
25+
end`;
26+
27+
export const withIosFmtWorkaround: ConfigPlugin = (config) => {
28+
return withPodfile(config, (newConfig) => {
29+
const { contents } = newConfig.modResults;
30+
31+
if (contents.includes(FMT_WORKAROUND_MARKER)) {
32+
return newConfig;
33+
}
34+
35+
const postInstallMatch = contents.match(
36+
/(react_native_post_install\([\s\S]*?\n\s*\))/
37+
);
38+
39+
if (!postInstallMatch) {
40+
console.warn(
41+
'@iterable/expo-plugin: Could not inject fmt workaround into Podfile — react_native_post_install block not found'
42+
);
43+
return newConfig;
44+
}
45+
46+
newConfig.modResults.contents = contents.replace(
47+
postInstallMatch[0],
48+
`${postInstallMatch[0]}${FMT_WORKAROUND_RUBY}`
49+
);
50+
51+
return newConfig;
52+
});
53+
};
54+
55+
export default withIosFmtWorkaround;

plugin/src/withIterable.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ConfigPlugin, withPlugins } from 'expo/config-plugins';
22

33
import { withDeepLinks } from './withDeepLinks';
4+
import { withIosFmtWorkaround } from './withIosFmtWorkaround';
45
import {
56
type ConfigPluginProps,
67
type ConfigPluginPropsWithDefaults,
@@ -24,6 +25,7 @@ const withIterable: ConfigPlugin<ConfigPluginProps> = (config, props = {}) => {
2425
[withStoreConfigValues, propsWithDefaults],
2526
[withPushNotifications, propsWithDefaults],
2627
[withDeepLinks, propsWithDefaults],
28+
withIosFmtWorkaround,
2729
]);
2830
};
2931

yarn.lock

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2669,7 +2669,7 @@ __metadata:
26692669
dependencies:
26702670
"@commitlint/config-conventional": "npm:^19.6.0"
26712671
"@evilmartians/lefthook": "npm:^1.5.0"
2672-
"@iterable/react-native-sdk": "npm:^2.0.2"
2672+
"@iterable/react-native-sdk": "npm:^3.0.0"
26732673
"@react-native/eslint-config": "npm:^0.79.5"
26742674
"@release-it/conventional-changelog": "npm:^9.0.2"
26752675
"@types/jest": "npm:^29.5.14"
@@ -2692,7 +2692,7 @@ __metadata:
26922692
release-it: "npm:^17.10.0"
26932693
typescript: "npm:^5.3.3"
26942694
peerDependencies:
2695-
"@iterable/react-native-sdk": ^2.0.2
2695+
"@iterable/react-native-sdk": ^3.0.0
26962696
expo: "*"
26972697
react: "*"
26982698
react-native: "*"
@@ -2702,20 +2702,19 @@ __metadata:
27022702
languageName: unknown
27032703
linkType: soft
27042704

2705-
"@iterable/react-native-sdk@npm:^2.0.2":
2706-
version: 2.0.2
2707-
resolution: "@iterable/react-native-sdk@npm:2.0.2"
2705+
"@iterable/react-native-sdk@npm:^3.0.0":
2706+
version: 3.0.0
2707+
resolution: "@iterable/react-native-sdk@npm:3.0.0"
27082708
peerDependencies:
27092709
"@react-navigation/native": "*"
27102710
react: "*"
27112711
react-native: "*"
27122712
react-native-safe-area-context: "*"
2713-
react-native-vector-icons: "*"
27142713
react-native-webview: "*"
27152714
peerDependenciesMeta:
27162715
expo:
27172716
optional: true
2718-
checksum: 10c0/d1d6485dd3b982642df9569a4793aae0ff71191f8f6faffadbb580594db8e9cf2089f8a9b668d09c7b53e93ecbcd9d054833a8b82e0bb2a1d0d9a68a269d5967
2717+
checksum: 10c0/986244429e63646cd641a7f7be1a7c93be05f12000d80d1fa5cebce29bc4d87566e72766d1a3f57fe7f5c0a579a8c7d4b5b238cc815c8be161aef0a022314cfc
27192718
languageName: node
27202719
linkType: hard
27212720

0 commit comments

Comments
 (0)