mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
feat: implement modification of the Xcodeproject
This commit is contained in:
+52
-5
@@ -22,6 +22,7 @@ const {execSync} = require('child_process');
|
||||
// Import functions from other scripts
|
||||
const {prepareAppDependenciesHeaders} = require('./prepare-app-dependencies-headers');
|
||||
const {createSymlinks: createSymlinksFunction} = require('./create-symlinks');
|
||||
const {integrateSwiftPackagesInXcode} = require('./update-xcodeproject');
|
||||
const codegenExecutor = require('../codegen/generate-artifacts-executor');
|
||||
|
||||
/**
|
||||
@@ -29,8 +30,9 @@ const codegenExecutor = require('../codegen/generate-artifacts-executor');
|
||||
* @param {string} appPath - Path to the app (e.g., '../../private/helloworld')
|
||||
* @param {string} reactNativePath - Path to the React Native package (e.g., '.')
|
||||
* @param {string} appXcodeProject - Name of the Xcode project (e.g., 'HelloWorld.xcodeproj')
|
||||
* @param {string} targetName - Name of the app target (e.g., 'HelloWorld')
|
||||
*/
|
||||
async function prepareApp(appPath = '../../private/helloworld', reactNativePath = '.', appXcodeProject = 'HelloWorld.xcodeproj') {
|
||||
async function prepareApp(appPath = '../../private/helloworld', reactNativePath = '.', appXcodeProject = 'HelloWorld.xcodeproj', targetName = 'HelloWorld') {
|
||||
console.log('🚀 Starting app preparation for SwiftPM build from source...');
|
||||
|
||||
// Resolve absolute paths
|
||||
@@ -80,8 +82,12 @@ async function prepareApp(appPath = '../../private/helloworld', reactNativePath
|
||||
console.log('\n🔧 Step 7: Fixing REACT_NATIVE_PATH in Xcode project...');
|
||||
await fixReactNativePath(appIosPath, absoluteReactNativePath, appXcodeProject);
|
||||
|
||||
// Step 8: Open Xcode project
|
||||
console.log('\n📱 Step 8: Opening Xcode project...');
|
||||
// Step 8: Integrate SwiftPM packages in Xcode
|
||||
console.log('\n📦 Step 8: Integrating SwiftPM packages in Xcode...');
|
||||
await integrateSwiftPMPackages(appIosPath, absoluteReactNativePath, absoluteAppPath, appXcodeProject, targetName);
|
||||
|
||||
// Step 9: Open Xcode project
|
||||
console.log('\n📱 Step 9: Opening Xcode project...');
|
||||
await openXcodeProject(appIosPath, appXcodeProject);
|
||||
|
||||
console.log('\n✅ App preparation completed successfully!');
|
||||
@@ -268,6 +274,40 @@ async function fixReactNativePath(appIosPath, reactNativePath, appXcodeProject)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrate SwiftPM packages into Xcode project
|
||||
*/
|
||||
async function integrateSwiftPMPackages(appIosPath, reactNativePath, appPath, appXcodeProject, targetName) {
|
||||
try {
|
||||
console.log('Preparing SwiftPM package integrations...');
|
||||
|
||||
// Calculate relative paths from appIosPath
|
||||
const relativeReactNativePath = path.relative(appIosPath, reactNativePath);
|
||||
const relativeGeneratedPath = path.join('build', 'generated', 'ios');
|
||||
|
||||
// Create PackageSwift objects
|
||||
const packageSwiftObjects = [
|
||||
{
|
||||
"relativePath": relativeReactNativePath,
|
||||
"targets": ["React"]
|
||||
},
|
||||
{
|
||||
"relativePath": relativeGeneratedPath,
|
||||
"targets": ["ReactCodegen", "ReactAppDependencyProvider"]
|
||||
}
|
||||
];
|
||||
|
||||
const xcodeProjectPath = path.join(appIosPath, appXcodeProject);
|
||||
|
||||
// Call integrateSwiftPackagesInXcode function
|
||||
integrateSwiftPackagesInXcode(xcodeProjectPath, packageSwiftObjects, targetName);
|
||||
|
||||
console.log('✓ SwiftPM packages integrated into Xcode project');
|
||||
} catch (error) {
|
||||
throw new Error(`SwiftPM integration failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Xcode project
|
||||
*/
|
||||
@@ -296,6 +336,7 @@ if (require.main === module) {
|
||||
let appPath = '../../private/helloworld';
|
||||
let reactNativePath = '.';
|
||||
let appXcodeProject = 'HelloWorld.xcodeproj';
|
||||
let targetName = 'HelloWorld';
|
||||
|
||||
if (args.length >= 1) {
|
||||
appPath = args[0];
|
||||
@@ -309,12 +350,17 @@ if (require.main === module) {
|
||||
appXcodeProject = args[2];
|
||||
}
|
||||
|
||||
console.log('Usage: node prepare-app.js [appPath] [reactNativePath] [appXcodeProject]');
|
||||
if (args.length >= 4) {
|
||||
targetName = args[3];
|
||||
}
|
||||
|
||||
console.log('Usage: node prepare-app.js [appPath] [reactNativePath] [appXcodeProject] [targetName]');
|
||||
console.log(`Using App path: ${appPath}`);
|
||||
console.log(`Using React Native path: ${reactNativePath}`);
|
||||
console.log(`Using App Xcode project: ${appXcodeProject}`);
|
||||
console.log(`Using Target name: ${targetName}`);
|
||||
|
||||
prepareApp(appPath, reactNativePath, appXcodeProject)
|
||||
prepareApp(appPath, reactNativePath, appXcodeProject, targetName)
|
||||
.then(() => {
|
||||
console.log('\n🎉 All done! Your app is ready for SwiftPM build from source.');
|
||||
process.exit(0);
|
||||
@@ -334,5 +380,6 @@ module.exports = {
|
||||
generateCodegenArtifacts,
|
||||
prepareHeaders,
|
||||
fixReactNativePath,
|
||||
integrateSwiftPMPackages,
|
||||
openXcodeProject
|
||||
};
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
const crypto = require('crypto');
|
||||
const {execSync} = require('child_process');
|
||||
|
||||
/**
|
||||
* Generate a random string of 24 HEX characters (capital letters) for Xcode object IDs
|
||||
* @returns {string} A 24-character hexadecimal string in uppercase
|
||||
*/
|
||||
function generateXcodeObjectId() {
|
||||
return crypto.randomBytes(12).toString('hex').toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Xcode project.pbxproj file to JSON format
|
||||
* @param {string} projectPath - Path to the project.pbxproj file
|
||||
* @returns {Object} Parsed JSON object of the Xcode project
|
||||
*/
|
||||
function convertXcodeProjectToJSON(projectPath) {
|
||||
const command = `plutil -convert json -o - "${projectPath}"`;
|
||||
const jsonOutput = execSync(command, { encoding: 'utf8' });
|
||||
return JSON.parse(jsonOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add local SwiftPM package references and product dependencies to Xcode project
|
||||
* @param {string} relativePath - The relative path of where the Package.swift is located
|
||||
* @param {Array<string>} productNames - List of product names exposed by the Package.swift files
|
||||
* @param {Object} xcodeProject - The xcode project converted in JSON format
|
||||
* @param {string} targetName - The name of the target to add dependencies to
|
||||
*/
|
||||
function addLocalSwiftPM(relativePath, productNames, xcodeProject, targetName) {
|
||||
// For the relative path: create XCLocalSwiftPackageReference
|
||||
const packageReferenceId = generateXcodeObjectId();
|
||||
xcodeProject.objects[packageReferenceId] = {
|
||||
"isa": "XCLocalSwiftPackageReference",
|
||||
"relativePath": relativePath
|
||||
};
|
||||
|
||||
// Find PBXProject object and update packageReferences
|
||||
const objects = xcodeProject.objects;
|
||||
for (const objectId in objects) {
|
||||
const object = objects[objectId];
|
||||
if (object.isa === "PBXProject") {
|
||||
if (!object.packageReferences) {
|
||||
object.packageReferences = [];
|
||||
}
|
||||
object.packageReferences.push(packageReferenceId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// For each product: create XCSwiftPackageProductDependency and PBXBuildFile
|
||||
for (const productName of productNames) {
|
||||
// Generate XcodeID for XCSwiftPackageProductDependency
|
||||
const productDependencyId = generateXcodeObjectId();
|
||||
xcodeProject.objects[productDependencyId] = {
|
||||
"isa": "XCSwiftPackageProductDependency",
|
||||
"productName": productName
|
||||
};
|
||||
|
||||
// Generate second XcodeID for PBXBuildFile
|
||||
const buildFileId = generateXcodeObjectId();
|
||||
xcodeProject.objects[buildFileId] = {
|
||||
"isa": "PBXBuildFile",
|
||||
"productRef": productDependencyId
|
||||
};
|
||||
|
||||
// Find PBXNativeTarget with matching name
|
||||
for (const objectId in objects) {
|
||||
const object = objects[objectId];
|
||||
if (object.isa === "PBXNativeTarget" && object.name === targetName) {
|
||||
// Iterate over buildPhases to find PBXFrameworksBuildPhase
|
||||
for (const buildPhaseId of object.buildPhases) {
|
||||
const buildPhaseObject = objects[buildPhaseId];
|
||||
if (buildPhaseObject && buildPhaseObject.isa === "PBXFrameworksBuildPhase") {
|
||||
// Add buildFileId to the files array
|
||||
if (!buildPhaseObject.files) {
|
||||
buildPhaseObject.files = [];
|
||||
}
|
||||
buildPhaseObject.files.push(buildFileId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrate Swift packages into Xcode project
|
||||
* @param {string} xcodeProjectPath - Path to the app.xcodeproj file
|
||||
* @param {Array<Object>} packageSwiftObjects - List of PackageSwift objects with relativePath and targets
|
||||
* @param {string} appTargetName - Name of the app target
|
||||
*/
|
||||
function integrateSwiftPackagesInXcode(xcodeProjectPath, packageSwiftObjects, appTargetName) {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Construct path to project.pbxproj
|
||||
const projectPbxprojPath = path.join(xcodeProjectPath, 'project.pbxproj');
|
||||
|
||||
if (!fs.existsSync(projectPbxprojPath)) {
|
||||
throw new Error(`Project file not found: ${projectPbxprojPath}`);
|
||||
}
|
||||
|
||||
// Convert to JSON
|
||||
const xcodeProject = convertXcodeProjectToJSON(projectPbxprojPath);
|
||||
|
||||
// Iterate over PackageSwift objects and execute addLocalSwiftPM
|
||||
for (const packageSwift of packageSwiftObjects) {
|
||||
addLocalSwiftPM(
|
||||
packageSwift.relativePath,
|
||||
packageSwift.targets,
|
||||
xcodeProject,
|
||||
appTargetName
|
||||
);
|
||||
}
|
||||
|
||||
// Write JSON directly to the project.pbxproj file
|
||||
fs.writeFileSync(projectPbxprojPath, JSON.stringify(xcodeProject));
|
||||
}
|
||||
|
||||
// CLI usage
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 3) {
|
||||
console.log('Usage: node update-xcodeproject.js <xcodeProjectPath> <appTargetName> <packageSwiftObjectsJSON>');
|
||||
console.log('Example: node update-xcodeproject.js ./MyApp.xcodeproj MyApp \'[{"relativePath":"../react-native","targets":["ReactCommon","React-Core"]}]\'');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const xcodeProjectPath = args[0];
|
||||
const appTargetName = args[1];
|
||||
const packageSwiftObjectsJSON = args[2];
|
||||
|
||||
try {
|
||||
const packageSwiftObjects = JSON.parse(packageSwiftObjectsJSON);
|
||||
integrateSwiftPackagesInXcode(xcodeProjectPath, packageSwiftObjects, appTargetName);
|
||||
console.log('✅ Successfully integrated Swift packages into Xcode project');
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateXcodeObjectId,
|
||||
convertXcodeProjectToJSON,
|
||||
addLocalSwiftPM,
|
||||
integrateSwiftPackagesInXcode
|
||||
};
|
||||
Reference in New Issue
Block a user