Skip to content

Commit 7a86ee0

Browse files
cipolleschimeta-codesync[bot]
authored andcommitted
Add function to create hard links for the app (#53675)
Summary: Pull Request resolved: #53675 ## Context When configuring an app to build with SwiftPM from source, there is a sequence of operations we need to run in order to prepare the project correctly. ## Changed Add a function that create hardlinks for React Native so it can build from source ## Changelog: [Internal] - Reviewed By: cortinico Differential Revision: D81778454 fbshipit-source-id: 702a603629e4fad154b5d79dea6d96fcd80235b9
1 parent d455236 commit 7a86ee0

2 files changed

Lines changed: 236 additions & 0 deletions

File tree

packages/react-native/scripts/swiftpm/__tests__/prepare-app-utils-test.js

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
const {
1414
configureAppForSwift,
15+
createHardlinks,
1516
findXcodeProjectDirectory,
1617
runIosPrebuild,
1718
runPodDeintegrate,
@@ -24,6 +25,12 @@ jest.mock('child_process');
2425
// Mock fs module
2526
jest.mock('fs');
2627

28+
// Mock headers-utils module
29+
jest.mock('../headers-utils');
30+
31+
// Mock prepare-app-dependencies-headers module
32+
jest.mock('../prepare-app-dependencies-headers');
33+
2734
// Mock path module for absolute paths
2835
jest.mock('path', () => {
2936
const actualPath = jest.requireActual('path');
@@ -34,6 +41,204 @@ jest.mock('path', () => {
3441
};
3542
});
3643

44+
describe('createHardlinks', () => {
45+
let mockSymlinkReactNativeHeaders;
46+
let mockSymlinkThirdPartyDependenciesHeaders;
47+
let mockPath;
48+
let mockConsoleLog;
49+
50+
beforeEach(() => {
51+
// Setup mocks
52+
const headersUtils = require('../headers-utils');
53+
const prepareAppDependenciesHeaders = require('../prepare-app-dependencies-headers');
54+
55+
mockSymlinkReactNativeHeaders =
56+
prepareAppDependenciesHeaders.symlinkReactNativeHeaders;
57+
mockSymlinkThirdPartyDependenciesHeaders =
58+
headersUtils.symlinkThirdPartyDependenciesHeaders;
59+
60+
mockPath = require('path');
61+
mockConsoleLog = console.log;
62+
63+
// Clear and reset all mocks completely
64+
jest.clearAllMocks();
65+
jest.resetAllMocks();
66+
67+
// Set up fresh mock implementations
68+
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
69+
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {});
70+
71+
// Mock path.join to return realistic paths
72+
mockPath.join.mockImplementation((...args) => args.join('/'));
73+
});
74+
75+
it('should create hard links successfully', async () => {
76+
// Setup
77+
const reactNativePath = '/path/to/react-native';
78+
79+
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
80+
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {});
81+
82+
// Execute
83+
await createHardlinks(reactNativePath);
84+
85+
// Assert
86+
const expectedReactIncludesPath = '/path/to/react-native/React';
87+
88+
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
89+
reactNativePath,
90+
expectedReactIncludesPath,
91+
'includes',
92+
);
93+
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith(
94+
reactNativePath,
95+
expectedReactIncludesPath,
96+
'includes',
97+
);
98+
99+
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledTimes(1);
100+
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledTimes(1);
101+
102+
// Verify console output
103+
expect(mockConsoleLog).toHaveBeenCalledWith(
104+
'Creating hard links for React Native headers...',
105+
);
106+
expect(mockConsoleLog).toHaveBeenCalledWith(
107+
'✓ React Native hard links created in React/includes',
108+
);
109+
expect(mockConsoleLog).toHaveBeenCalledWith(
110+
'Creating hard links for third-party dependencies...',
111+
);
112+
expect(mockConsoleLog).toHaveBeenCalledWith(
113+
'✓ Third-party dependencies hard links created in React/includes',
114+
);
115+
});
116+
117+
it('should throw error when symlinkReactNativeHeaders fails', async () => {
118+
// Setup
119+
const reactNativePath = '/path/to/react-native';
120+
const originalError = new Error('React Native headers linking failed');
121+
122+
mockSymlinkReactNativeHeaders.mockImplementation(() => {
123+
throw originalError;
124+
});
125+
126+
// Execute & Assert
127+
await expect(createHardlinks(reactNativePath)).rejects.toThrow(
128+
'Hard link creation failed: React Native headers linking failed',
129+
);
130+
131+
const expectedReactIncludesPath = '/path/to/react-native/React';
132+
133+
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
134+
reactNativePath,
135+
expectedReactIncludesPath,
136+
'includes',
137+
);
138+
expect(mockSymlinkThirdPartyDependenciesHeaders).not.toHaveBeenCalled();
139+
140+
expect(mockConsoleLog).toHaveBeenCalledWith(
141+
'Creating hard links for React Native headers...',
142+
);
143+
expect(mockConsoleLog).not.toHaveBeenCalledWith(
144+
'✓ React Native hard links created in React/includes',
145+
);
146+
});
147+
148+
it('should throw error when symlinkThirdPartyDependenciesHeaders fails', async () => {
149+
// Setup
150+
const reactNativePath = '/path/to/react-native';
151+
const originalError = new Error('Third-party dependencies linking failed');
152+
153+
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
154+
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {
155+
throw originalError;
156+
});
157+
158+
// Execute & Assert
159+
await expect(createHardlinks(reactNativePath)).rejects.toThrow(
160+
'Hard link creation failed: Third-party dependencies linking failed',
161+
);
162+
163+
const expectedReactIncludesPath = '/path/to/react-native/React';
164+
165+
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
166+
reactNativePath,
167+
expectedReactIncludesPath,
168+
'includes',
169+
);
170+
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith(
171+
reactNativePath,
172+
expectedReactIncludesPath,
173+
'includes',
174+
);
175+
176+
expect(mockConsoleLog).toHaveBeenCalledWith(
177+
'Creating hard links for React Native headers...',
178+
);
179+
expect(mockConsoleLog).toHaveBeenCalledWith(
180+
'✓ React Native hard links created in React/includes',
181+
);
182+
expect(mockConsoleLog).toHaveBeenCalledWith(
183+
'Creating hard links for third-party dependencies...',
184+
);
185+
expect(mockConsoleLog).not.toHaveBeenCalledWith(
186+
'✓ Third-party dependencies hard links created in React/includes',
187+
);
188+
});
189+
190+
it('should handle different React Native paths correctly', async () => {
191+
// Setup
192+
const reactNativePath = '/Users/developer/custom-react-native-path';
193+
194+
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
195+
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {});
196+
197+
// Execute
198+
await createHardlinks(reactNativePath);
199+
200+
// Assert
201+
const expectedReactIncludesPath =
202+
'/Users/developer/custom-react-native-path/React';
203+
204+
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
205+
reactNativePath,
206+
expectedReactIncludesPath,
207+
'includes',
208+
);
209+
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith(
210+
reactNativePath,
211+
expectedReactIncludesPath,
212+
'includes',
213+
);
214+
});
215+
216+
it('should handle paths with spaces correctly', async () => {
217+
// Setup
218+
const reactNativePath = '/path/to/react native project';
219+
220+
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
221+
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {});
222+
223+
// Execute
224+
await createHardlinks(reactNativePath);
225+
226+
// Assert
227+
const expectedReactIncludesPath = '/path/to/react native project/React';
228+
229+
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
230+
reactNativePath,
231+
expectedReactIncludesPath,
232+
'includes',
233+
);
234+
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith(
235+
reactNativePath,
236+
expectedReactIncludesPath,
237+
'includes',
238+
);
239+
});
240+
});
241+
37242
// Mock console methods - disable React Native's strict console checking
38243
const originalConsole = global.console;
39244

packages/react-native/scripts/swiftpm/prepare-app-utils.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
* @format
99
*/
1010

11+
const {symlinkThirdPartyDependenciesHeaders} = require('./headers-utils');
12+
const {
13+
symlinkReactNativeHeaders,
14+
} = require('./prepare-app-dependencies-headers');
1115
const {execSync} = require('child_process');
1216
const fs = require('fs');
1317
const path = require('path');
@@ -206,10 +210,37 @@ async function setBuildFromSource(
206210
}
207211
}
208212

213+
/**
214+
* Create hard links for React Native headers in React/includes
215+
*/
216+
async function createHardlinks(
217+
reactNativePath /*: string */,
218+
) /*: Promise<void> */ {
219+
try {
220+
console.log('Creating hard links for React Native headers...');
221+
const reactIncludesPath = path.join(reactNativePath, 'React');
222+
symlinkReactNativeHeaders(reactNativePath, reactIncludesPath, 'includes');
223+
console.log('✓ React Native hard links created in React/includes');
224+
225+
console.log('Creating hard links for third-party dependencies...');
226+
symlinkThirdPartyDependenciesHeaders(
227+
reactNativePath,
228+
reactIncludesPath,
229+
'includes',
230+
);
231+
console.log(
232+
'✓ Third-party dependencies hard links created in React/includes',
233+
);
234+
} catch (error) {
235+
throw new Error(`Hard link creation failed: ${error.message}`);
236+
}
237+
}
238+
209239
module.exports = {
210240
findXcodeProjectDirectory,
211241
runPodDeintegrate,
212242
runIosPrebuild,
213243
configureAppForSwift,
214244
setBuildFromSource,
245+
createHardlinks,
215246
};

0 commit comments

Comments
 (0)