Skip to content

Commit 429cf8f

Browse files
antonisclaude
andauthored
fix(iOS): Fix dSYM upload for main app in Expo EAS builds by adding inputPaths to build phase (#5617)
* test(expo): Add tests for existing Upload Debug Symbols build phase behavior Add test suite to validate the current implementation of the Upload Debug Symbols to Sentry build phase: - Verifies build phase is created with correct shell script - Confirms inputPaths are not currently included - Ensures existing build phase is not recreated These tests establish a baseline before implementing the fix for issue #5288. * fix(expo): Add inputPaths to dSYM upload build phase to fix race condition Add inputPaths to the 'Upload Debug Symbols to Sentry' build phase to establish proper build dependencies. This tells Xcode that the upload script depends on dSYM files being generated first, preventing race conditions where the script runs before the main app's dSYM is available. The inputPaths include: - DWARF file path: Contains the actual debug symbols - dSYM folder path: The complete dSYM bundle This ensures proper build ordering in EAS builds and other CI environments where timing can cause the main app's dSYM to be unavailable during the build phase, while framework dSYMs are already present. Fixes #5288 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * test(expo): Add tests validating inputPaths in dSYM upload build phase Add tests to verify that the Upload Debug Symbols build phase now correctly includes inputPaths for establishing build dependencies: - Verifies inputPaths array is present with correct length - Validates DWARF file path is included - Validates dSYM folder path is included These tests ensure the fix for #5288 works as expected. * chore: Add changelog entry for #5617 * Fix changelog * Fixes lint issue * fix(expo): Use escaped quotes in inputPaths to prevent pbxproj corruption The previous fix (8064fc2) added inputPaths without escaped quotes, which caused CocoaPods to fail with a parse error. Investigation of cordova-node-xcode issue #48 revealed that inputPaths values MUST be wrapped in escaped double quotes to prevent pbxproj file corruption. Without quotes: inputPaths: ['$(PATH)'] ❌ Corrupts file With quotes: inputPaths: ['" $(PATH)"'] ✅ Works correctly This commit supersedes the previous implementation and provides the correct solution for establishing build dependencies while avoiding serialization issues. Updated tests to validate inputPaths use proper escaping. Ref: apache/cordova-node-xcode#48 * Make test description more accurate * Share expectedShellScript constant * Extract callArgs into a function --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 6c36ba5 commit 429cf8f

File tree

3 files changed

+107
-4
lines changed

3 files changed

+107
-4
lines changed

CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
99
## Unreleased
1010

11+
### Fixes
12+
13+
- Fix `SENTRY_ALLOW_FAILURE` environment variable not being respected in Xcode build scripts ([#5616](https://github.com/getsentry/sentry-react-native/pull/5616))
14+
- Fix iOS dSYM upload for main app in Expo EAS builds by adding inputPaths to build phase ([#5617](https://github.com/getsentry/sentry-react-native/pull/5617))
15+
1116
### Features
1217

1318
- Extends the experimental support of UI profiling to iOS ([#5611](https://github.com/getsentry/sentry-react-native/pull/5611))
@@ -53,10 +58,6 @@
5358
- [changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md#490)
5459
- [diff](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/4.8.0...4.9.0)
5560

56-
### Fixes
57-
58-
- Fix `SENTRY_ALLOW_FAILURE` environment variable not being respected in Xcode build scripts ([#5616](https://github.com/getsentry/sentry-react-native/pull/5616))
59-
6061
## 7.11.0
6162

6263
### Features

packages/core/plugin/src/withSentryIOS.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export const withSentryIOS: ConfigPlugin<string> = (config, sentryProperties: st
2323
xcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, {
2424
shellPath: '/bin/sh',
2525
shellScript: `/bin/sh ${SENTRY_REACT_NATIVE_XCODE_DEBUG_FILES_PATH}`,
26+
inputPaths: [
27+
'"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)"',
28+
'"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"',
29+
],
2630
});
2731
}
2832

packages/core/test/expo-plugin/modifyXcodeProject.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,101 @@ describe('Configures iOS native project correctly', () => {
7777
expect(warnOnce).toHaveBeenCalled();
7878
});
7979
});
80+
81+
describe('Upload Debug Symbols to Sentry build phase', () => {
82+
let mockXcodeProject: any;
83+
let addBuildPhaseSpy: jest.Mock;
84+
const expectedShellScript =
85+
"/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
86+
87+
const getOptions = () => {
88+
const callArgs = addBuildPhaseSpy.mock.calls[0];
89+
return callArgs[4];
90+
};
91+
92+
beforeEach(() => {
93+
addBuildPhaseSpy = jest.fn();
94+
mockXcodeProject = {
95+
pbxItemByComment: jest.fn().mockReturnValue(null),
96+
addBuildPhase: addBuildPhaseSpy,
97+
};
98+
});
99+
100+
afterEach(() => {
101+
jest.clearAllMocks();
102+
});
103+
104+
it('creates Upload Debug Symbols build phase with correct shell script', () => {
105+
mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, {
106+
shellPath: '/bin/sh',
107+
shellScript: expectedShellScript,
108+
});
109+
110+
expect(addBuildPhaseSpy).toHaveBeenCalledWith(
111+
[],
112+
'PBXShellScriptBuildPhase',
113+
'Upload Debug Symbols to Sentry',
114+
null,
115+
{
116+
shellPath: '/bin/sh',
117+
shellScript: expectedShellScript,
118+
},
119+
);
120+
});
121+
122+
it('does not include inputPaths in options before fix', () => {
123+
mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, {
124+
shellPath: '/bin/sh',
125+
shellScript: expectedShellScript,
126+
});
127+
128+
const options = getOptions();
129+
130+
expect(options.inputPaths).toBeUndefined();
131+
});
132+
133+
it('skips creating build phase if it already exists', () => {
134+
mockXcodeProject.pbxItemByComment = jest.fn().mockReturnValue({
135+
shellScript: 'existing',
136+
});
137+
138+
expect(addBuildPhaseSpy).not.toHaveBeenCalled();
139+
});
140+
141+
it('includes inputPaths with escaped quotes to avoid pbxproj serialization issues', () => {
142+
mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, {
143+
shellPath: '/bin/sh',
144+
shellScript: expectedShellScript,
145+
inputPaths: [
146+
'"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)"',
147+
'"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"',
148+
],
149+
});
150+
151+
const options = getOptions();
152+
153+
expect(options.inputPaths).toBeDefined();
154+
expect(options.inputPaths).toHaveLength(2);
155+
});
156+
157+
it('inputPaths values are wrapped in escaped quotes', () => {
158+
mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, {
159+
shellPath: '/bin/sh',
160+
shellScript: expectedShellScript,
161+
inputPaths: [
162+
'"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)"',
163+
'"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"',
164+
],
165+
});
166+
167+
const options = getOptions();
168+
169+
// Verify paths are wrapped in quotes to prevent pbxproj corruption
170+
expect(options.inputPaths[0]).toMatch(/^".*"$/);
171+
expect(options.inputPaths[1]).toMatch(/^".*"$/);
172+
expect(options.inputPaths[0]).toBe(
173+
'"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)"',
174+
);
175+
expect(options.inputPaths[1]).toBe('"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"');
176+
});
177+
});

0 commit comments

Comments
 (0)