Add function to set build from source to true (#53674)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53674

## 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 updates the PAckage.swift file to set BUILD_FROM_SOURCE to true

## Changelog:
[Internal] -

Reviewed By: cortinico

Differential Revision: D81778460

fbshipit-source-id: b06ebc546aa8e87acdb15706a0559e9046173784
This commit is contained in:
Riccardo Cipolleschi
2025-10-13 16:30:51 -07:00
committed by meta-codesync[bot]
parent 87d6043788
commit d45523639d
2 changed files with 274 additions and 0 deletions
@@ -15,6 +15,7 @@ const {
findXcodeProjectDirectory,
runIosPrebuild,
runPodDeintegrate,
setBuildFromSource,
} = require('../prepare-app-utils');
// Mock child_process module
@@ -189,6 +190,237 @@ describe('findXcodeProjectDirectory', () => {
});
});
describe('setBuildFromSource', () => {
let mockFs;
let mockPath;
let mockConsoleLog;
let mockConsoleWarn;
beforeEach(() => {
// Setup mocks
mockFs = require('fs');
mockPath = require('path');
mockConsoleLog = console.log;
mockConsoleWarn = console.warn;
// Clear and reset all mocks completely
jest.clearAllMocks();
jest.resetAllMocks();
// Set up fresh mock implementations
mockFs.existsSync = jest.fn();
mockFs.readFileSync = jest.fn();
mockFs.writeFileSync = jest.fn();
// Mock path.join to return realistic paths
mockPath.join.mockImplementation((...args) => args.join('/'));
});
it('should update BUILD_FROM_SOURCE from false to true successfully', async () => {
// Setup
const reactNativePath = '/path/to/react-native';
const mockPackageSwiftContent = `
// Package.swift
import PackageDescription
let BUILD_FROM_SOURCE = false
let package = Package(
name: "ReactNative",
platforms: [.iOS(.v13)],
// rest of package
)
`;
const expectedUpdatedContent = `
// Package.swift
import PackageDescription
let BUILD_FROM_SOURCE = true
let package = Package(
name: "ReactNative",
platforms: [.iOS(.v13)],
// rest of package
)
`;
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(mockPackageSwiftContent);
mockFs.writeFileSync.mockImplementation(() => {});
// Execute
await setBuildFromSource(reactNativePath);
// Assert
expect(mockFs.existsSync).toHaveBeenCalledWith(
'/path/to/react-native/Package.swift',
);
expect(mockFs.readFileSync).toHaveBeenCalledWith(
'/path/to/react-native/Package.swift',
'utf8',
);
expect(mockFs.writeFileSync).toHaveBeenCalledWith(
'/path/to/react-native/Package.swift',
expectedUpdatedContent,
'utf8',
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'Updating BUILD_FROM_SOURCE in: /path/to/react-native/Package.swift',
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'✓ BUILD_FROM_SOURCE set to true in Package.swift',
);
expect(mockConsoleWarn).not.toHaveBeenCalled();
});
it('should handle when BUILD_FROM_SOURCE is already true', async () => {
// Setup
const reactNativePath = '/path/to/react-native';
const mockPackageSwiftContent = `
// Package.swift
import PackageDescription
let BUILD_FROM_SOURCE = true
let package = Package(
name: "ReactNative",
platforms: [.iOS(.v13)],
// rest of package
)
`;
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(mockPackageSwiftContent);
mockFs.writeFileSync.mockImplementation(() => {});
// Execute
await setBuildFromSource(reactNativePath);
// Assert
expect(mockFs.existsSync).toHaveBeenCalledWith(
'/path/to/react-native/Package.swift',
);
expect(mockFs.readFileSync).toHaveBeenCalledWith(
'/path/to/react-native/Package.swift',
'utf8',
);
expect(mockFs.writeFileSync).not.toHaveBeenCalled(); // Should not write when already true
expect(mockConsoleLog).toHaveBeenCalledWith(
'Updating BUILD_FROM_SOURCE in: /path/to/react-native/Package.swift',
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'✓ BUILD_FROM_SOURCE is already set to true in Package.swift',
);
expect(mockConsoleWarn).not.toHaveBeenCalled();
});
it('should warn when BUILD_FROM_SOURCE declaration is not found', async () => {
// Setup
const reactNativePath = '/path/to/react-native';
const mockPackageSwiftContent = `
// Package.swift
import PackageDescription
let package = Package(
name: "ReactNative",
platforms: [.iOS(.v13)],
// rest of package without BUILD_FROM_SOURCE
)
`;
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(mockPackageSwiftContent);
mockFs.writeFileSync.mockImplementation(() => {});
// Execute
await setBuildFromSource(reactNativePath);
// Assert
expect(mockFs.existsSync).toHaveBeenCalledWith(
'/path/to/react-native/Package.swift',
);
expect(mockFs.readFileSync).toHaveBeenCalledWith(
'/path/to/react-native/Package.swift',
'utf8',
);
expect(mockFs.writeFileSync).not.toHaveBeenCalled(); // Should not write when declaration not found
expect(mockConsoleLog).toHaveBeenCalledWith(
'Updating BUILD_FROM_SOURCE in: /path/to/react-native/Package.swift',
);
expect(mockConsoleWarn).toHaveBeenCalledWith(
'⚠️ BUILD_FROM_SOURCE declaration not found in Package.swift',
);
});
it('should throw error when Package.swift does not exist', async () => {
// Setup
const reactNativePath = '/path/to/react-native';
mockFs.existsSync.mockReturnValue(false);
// Execute & Assert
await expect(setBuildFromSource(reactNativePath)).rejects.toThrow(
'Package.swift not found at: /path/to/react-native/Package.swift',
);
expect(mockFs.existsSync).toHaveBeenCalledWith(
'/path/to/react-native/Package.swift',
);
expect(mockFs.readFileSync).not.toHaveBeenCalled();
expect(mockFs.writeFileSync).not.toHaveBeenCalled();
expect(mockConsoleLog).not.toHaveBeenCalled();
});
it('should handle multiple BUILD_FROM_SOURCE occurrences', async () => {
// Setup
const reactNativePath = '/path/to/react-native';
const mockPackageSwiftContent = `
// Package.swift
import PackageDescription
let BUILD_FROM_SOURCE = false
// Some comment about BUILD_FROM_SOURCE = false
let anotherVar = "BUILD_FROM_SOURCE = false in string"
let package = Package(
name: "ReactNative",
platforms: [.iOS(.v13)],
// Another BUILD_FROM_SOURCE = false comment
)
`;
const expectedUpdatedContent = `
// Package.swift
import PackageDescription
let BUILD_FROM_SOURCE = true
// Some comment about BUILD_FROM_SOURCE = false
let anotherVar = "BUILD_FROM_SOURCE = false in string"
let package = Package(
name: "ReactNative",
platforms: [.iOS(.v13)],
// Another BUILD_FROM_SOURCE = false comment
)
`;
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(mockPackageSwiftContent);
mockFs.writeFileSync.mockImplementation(() => {});
// Execute
await setBuildFromSource(reactNativePath);
// Assert - should replace only the declaration, not comments or strings
expect(mockFs.writeFileSync).toHaveBeenCalledWith(
'/path/to/react-native/Package.swift',
expectedUpdatedContent,
'utf8',
);
});
});
describe('runIosPrebuild', () => {
let mockExecSync;
let mockConsoleLog;
@@ -165,9 +165,51 @@ async function configureAppForSwift(
}
}
/**
* Set BUILD_FROM_SOURCE to true in Package.swift
*/
async function setBuildFromSource(
reactNativePath /*: string */,
) /*: Promise<void> */ {
const packageSwiftPath = path.join(reactNativePath, 'Package.swift');
if (!fs.existsSync(packageSwiftPath)) {
throw new Error(`Package.swift not found at: ${packageSwiftPath}`);
}
try {
console.log(`Updating BUILD_FROM_SOURCE in: ${packageSwiftPath}`);
const content = fs.readFileSync(packageSwiftPath, 'utf8');
// Check if BUILD_FROM_SOURCE = false exists and replace it with true
if (content.includes('let BUILD_FROM_SOURCE = false')) {
const updatedContent = content.replace(
/let BUILD_FROM_SOURCE = false/g,
'let BUILD_FROM_SOURCE = true',
);
fs.writeFileSync(packageSwiftPath, updatedContent, 'utf8');
console.log('✓ BUILD_FROM_SOURCE set to true in Package.swift');
} else if (content.includes('let BUILD_FROM_SOURCE = true')) {
console.log(
'✓ BUILD_FROM_SOURCE is already set to true in Package.swift',
);
} else {
console.warn(
'⚠️ BUILD_FROM_SOURCE declaration not found in Package.swift',
);
}
} catch (error) {
throw new Error(
`Failed to update BUILD_FROM_SOURCE in Package.swift: ${error.message}`,
);
}
}
module.exports = {
findXcodeProjectDirectory,
runPodDeintegrate,
runIosPrebuild,
configureAppForSwift,
setBuildFromSource,
};