Add function to create hard links for the app (#53675)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/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
This commit is contained in:
Riccardo Cipolleschi
2025-10-13 16:30:51 -07:00
committed by meta-codesync[bot]
parent d45523639d
commit 7a86ee0aef
2 changed files with 236 additions and 0 deletions
@@ -12,6 +12,7 @@
const {
configureAppForSwift,
createHardlinks,
findXcodeProjectDirectory,
runIosPrebuild,
runPodDeintegrate,
@@ -24,6 +25,12 @@ jest.mock('child_process');
// Mock fs module
jest.mock('fs');
// Mock headers-utils module
jest.mock('../headers-utils');
// Mock prepare-app-dependencies-headers module
jest.mock('../prepare-app-dependencies-headers');
// Mock path module for absolute paths
jest.mock('path', () => {
const actualPath = jest.requireActual('path');
@@ -34,6 +41,204 @@ jest.mock('path', () => {
};
});
describe('createHardlinks', () => {
let mockSymlinkReactNativeHeaders;
let mockSymlinkThirdPartyDependenciesHeaders;
let mockPath;
let mockConsoleLog;
beforeEach(() => {
// Setup mocks
const headersUtils = require('../headers-utils');
const prepareAppDependenciesHeaders = require('../prepare-app-dependencies-headers');
mockSymlinkReactNativeHeaders =
prepareAppDependenciesHeaders.symlinkReactNativeHeaders;
mockSymlinkThirdPartyDependenciesHeaders =
headersUtils.symlinkThirdPartyDependenciesHeaders;
mockPath = require('path');
mockConsoleLog = console.log;
// Clear and reset all mocks completely
jest.clearAllMocks();
jest.resetAllMocks();
// Set up fresh mock implementations
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {});
// Mock path.join to return realistic paths
mockPath.join.mockImplementation((...args) => args.join('/'));
});
it('should create hard links successfully', async () => {
// Setup
const reactNativePath = '/path/to/react-native';
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {});
// Execute
await createHardlinks(reactNativePath);
// Assert
const expectedReactIncludesPath = '/path/to/react-native/React';
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
reactNativePath,
expectedReactIncludesPath,
'includes',
);
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith(
reactNativePath,
expectedReactIncludesPath,
'includes',
);
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledTimes(1);
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledTimes(1);
// Verify console output
expect(mockConsoleLog).toHaveBeenCalledWith(
'Creating hard links for React Native headers...',
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'✓ React Native hard links created in React/includes',
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'Creating hard links for third-party dependencies...',
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'✓ Third-party dependencies hard links created in React/includes',
);
});
it('should throw error when symlinkReactNativeHeaders fails', async () => {
// Setup
const reactNativePath = '/path/to/react-native';
const originalError = new Error('React Native headers linking failed');
mockSymlinkReactNativeHeaders.mockImplementation(() => {
throw originalError;
});
// Execute & Assert
await expect(createHardlinks(reactNativePath)).rejects.toThrow(
'Hard link creation failed: React Native headers linking failed',
);
const expectedReactIncludesPath = '/path/to/react-native/React';
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
reactNativePath,
expectedReactIncludesPath,
'includes',
);
expect(mockSymlinkThirdPartyDependenciesHeaders).not.toHaveBeenCalled();
expect(mockConsoleLog).toHaveBeenCalledWith(
'Creating hard links for React Native headers...',
);
expect(mockConsoleLog).not.toHaveBeenCalledWith(
'✓ React Native hard links created in React/includes',
);
});
it('should throw error when symlinkThirdPartyDependenciesHeaders fails', async () => {
// Setup
const reactNativePath = '/path/to/react-native';
const originalError = new Error('Third-party dependencies linking failed');
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {
throw originalError;
});
// Execute & Assert
await expect(createHardlinks(reactNativePath)).rejects.toThrow(
'Hard link creation failed: Third-party dependencies linking failed',
);
const expectedReactIncludesPath = '/path/to/react-native/React';
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
reactNativePath,
expectedReactIncludesPath,
'includes',
);
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith(
reactNativePath,
expectedReactIncludesPath,
'includes',
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'Creating hard links for React Native headers...',
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'✓ React Native hard links created in React/includes',
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'Creating hard links for third-party dependencies...',
);
expect(mockConsoleLog).not.toHaveBeenCalledWith(
'✓ Third-party dependencies hard links created in React/includes',
);
});
it('should handle different React Native paths correctly', async () => {
// Setup
const reactNativePath = '/Users/developer/custom-react-native-path';
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {});
// Execute
await createHardlinks(reactNativePath);
// Assert
const expectedReactIncludesPath =
'/Users/developer/custom-react-native-path/React';
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
reactNativePath,
expectedReactIncludesPath,
'includes',
);
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith(
reactNativePath,
expectedReactIncludesPath,
'includes',
);
});
it('should handle paths with spaces correctly', async () => {
// Setup
const reactNativePath = '/path/to/react native project';
mockSymlinkReactNativeHeaders.mockImplementation(() => {});
mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {});
// Execute
await createHardlinks(reactNativePath);
// Assert
const expectedReactIncludesPath = '/path/to/react native project/React';
expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith(
reactNativePath,
expectedReactIncludesPath,
'includes',
);
expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith(
reactNativePath,
expectedReactIncludesPath,
'includes',
);
});
});
// Mock console methods - disable React Native's strict console checking
const originalConsole = global.console;
@@ -8,6 +8,10 @@
* @format
*/
const {symlinkThirdPartyDependenciesHeaders} = require('./headers-utils');
const {
symlinkReactNativeHeaders,
} = require('./prepare-app-dependencies-headers');
const {execSync} = require('child_process');
const fs = require('fs');
const path = require('path');
@@ -206,10 +210,37 @@ async function setBuildFromSource(
}
}
/**
* Create hard links for React Native headers in React/includes
*/
async function createHardlinks(
reactNativePath /*: string */,
) /*: Promise<void> */ {
try {
console.log('Creating hard links for React Native headers...');
const reactIncludesPath = path.join(reactNativePath, 'React');
symlinkReactNativeHeaders(reactNativePath, reactIncludesPath, 'includes');
console.log('✓ React Native hard links created in React/includes');
console.log('Creating hard links for third-party dependencies...');
symlinkThirdPartyDependenciesHeaders(
reactNativePath,
reactIncludesPath,
'includes',
);
console.log(
'✓ Third-party dependencies hard links created in React/includes',
);
} catch (error) {
throw new Error(`Hard link creation failed: ${error.message}`);
}
}
module.exports = {
findXcodeProjectDirectory,
runPodDeintegrate,
runIosPrebuild,
configureAppForSwift,
setBuildFromSource,
createHardlinks,
};