From ba221e1015f5269ea5093c289896a35ce721fff4 Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Fri, 22 Aug 2025 03:26:54 -0700 Subject: [PATCH] Fix copy symbol files in RNDeps precompile (#53353) Summary: Symbol files wasn't copied correctly when building - as with bundles we did overwrite the files and ended up with only the last symbol file. This commit fixes this by mapping the framework build folder architecture type to the xcframework slices creating the correct file structure under the Symbols folder. - Each slice gets a folder with the architecture name under Symbols containing the dSym folder for that slice - Refactored getting correct architecture folder into a separate function. - Refactored target folder lookup in copyBundles - Removed unused async modifier on function ## Changelog: [IOS] [FIXED] - Fixed how we copy and build the Symbols folder when precompiling ReactNativeDependencies Pull Request resolved: https://github.com/facebook/react-native/pull/53353 Test Plan: Run nightlies and verify that ReactNativeDependencies.framework.dSym files contains symbol files for all architectures. Reviewed By: cortinico Differential Revision: D80692019 Pulled By: cipolleschi fbshipit-source-id: 77983bc29d1965edf3bc0fcbd9cb3177071991d3 --- .../workflows/prebuild-ios-dependencies.yml | 5 +- .../ios-prebuild/compose-framework.js | 138 +++++++++++++----- 2 files changed, 105 insertions(+), 38 deletions(-) diff --git a/.github/workflows/prebuild-ios-dependencies.yml b/.github/workflows/prebuild-ios-dependencies.yml index 3ae43889238..70ca3fe2a5c 100644 --- a/.github/workflows/prebuild-ios-dependencies.yml +++ b/.github/workflows/prebuild-ios-dependencies.yml @@ -179,8 +179,9 @@ jobs: - name: Compress and Rename dSYM if: steps.restore-xcframework.outputs.cache-hit != 'true' run: | - tar -cz -f packages/react-native/third-party/Symbols/ReactNativeDependencies${{ matrix.flavor }}.framework.dSYM.tar.gz \ - packages/react-native/third-party/Symbols/ReactNativeDependencies.framework.dSYM + cd packages/react-native/third-party/Symbols/ + tar -cz -f ../ReactNativeDependencies${{ matrix.flavor }}.framework.dSYM.tar.gz . + mv ../ReactNativeDependencies${{ matrix.flavor }}.framework.dSYM.tar.gz ./ReactNativeDependencies${{ matrix.flavor }}.framework.dSYM.tar.gz - name: Upload XCFramework Artifact uses: actions/upload-artifact@v4 with: diff --git a/scripts/releases/ios-prebuild/compose-framework.js b/scripts/releases/ios-prebuild/compose-framework.js index e3887b38365..599622af6bc 100644 --- a/scripts/releases/ios-prebuild/compose-framework.js +++ b/scripts/releases/ios-prebuild/compose-framework.js @@ -63,17 +63,12 @@ async function createFramework( // Copy bundles into the framework copyBundles(scheme, dependencies, output, frameworkPaths); - // Copy headers to the framework - start by building the Header folder - await copyHeaders(scheme, dependencies, rootFolder); + // Copy Symbols to symbols folder - copy before headers since we're using the folders inside the xcframework + // to get the arch slices. + copySymbols(scheme, output, frameworkPaths); - // Copy Symbols to symbols folder - const symbolPaths = frameworkPaths.map(framework => - path.join(framework, `${scheme}.framework.dSYM`), - ); - console.log('Copying symbols to symbols folder...'); - const symbolOutput = path.join(rootFolder, 'Symbols'); - fs.mkdirSync(symbolOutput, {recursive: true}); - symbolPaths.forEach(symbol => execSync(`cp -r ${symbol} ${symbolOutput}`)); + // Copy headers to the framework - start by building the Header folder + copyHeaders(scheme, dependencies, rootFolder); if (identity) { signXCFramework(identity, output); @@ -84,7 +79,7 @@ async function createFramework( * Copies headers needed from the package to a Header folder that we'll pass to * each framework arch type */ -async function copyHeaders( +function copyHeaders( scheme /*: string */, dependencies /*: $ReadOnlyArray */, rootFolder /*: string */, @@ -134,9 +129,15 @@ function copyBundles( // A bundle is the name of the framework + _ + target name + .bundle. We can // check if the target has a bundle by checking if it defines one or more resources. frameworkPaths.forEach(frameworkPath => { - const frameworkPlatforms = execSync( - `vtool -show-build ${path.join(frameworkPath, 'PackageFrameworks', scheme + '.framework', scheme)}|grep platform`, - ).toString(); + const frameworkPlatforms = getArchsFromFramework( + path.join( + frameworkPath, + 'PackageFrameworks', + scheme + '.framework', + scheme, + ), + ); + dependencies.forEach(dep => { const resources = dep.files.resources; if (!resources || resources.length === 0) { @@ -147,29 +148,25 @@ function copyBundles( const sourceBundlePath = path.join(frameworkPath, bundleName); if (fs.existsSync(sourceBundlePath)) { // Target folder - needs to be copied to the resulting framework - let targetArchFolderFound = false; - targetArchFolders.forEach(targetArchFolder => { - const targetPlatforms = execSync( - `vtool -show-build ${path.join(targetArchFolder, scheme + '.framework', scheme)}|grep platform`, - ).toString(); + const targetFolder = targetArchFolders.find( + targetArchFolder => + getArchsFromFramework( + path.join(targetArchFolder, scheme + '.framework', scheme), + ) === frameworkPlatforms, + ); + if (targetFolder) { + console.log( + ` ${path.relative(outputFolder, sourceBundlePath)} → ${path.basename(targetFolder)}`, + ); + const targetBundlePath = path.join( + targetFolder, + `${scheme}.framework`, + bundleName, + ); - if (targetPlatforms === frameworkPlatforms) { - console.log( - ` ${path.relative(outputFolder, sourceBundlePath)} → ${path.basename(targetArchFolder)}`, - ); - const targetBundlePath = path.join( - targetArchFolder, - `${scheme}.framework`, - bundleName, - ); - - // A bundle is a directory, so we need to copy the whole directory - execSync(`cp -r "${sourceBundlePath}/" "${targetBundlePath}"`); - targetArchFolderFound = true; - } - }); - - if (!targetArchFolderFound) { + // A bundle is a directory, so we need to copy the whole directory + execSync(`cp -r "${sourceBundlePath}/" "${targetBundlePath}"`); + } else { throw Error( `Could not find target architecture for folder ${path.relative(outputFolder, frameworkPath)}. Expected to find ${frameworkPlatforms}`, ); @@ -181,6 +178,75 @@ function copyBundles( }); } +function copySymbols( + scheme /*: string */, + outputFolder /*:string*/, + frameworkPaths /*:Array*/, +) { + console.log('Copying dSym files...'); + + const targetArchFolders = fs + .readdirSync(outputFolder) + .map(p => path.join(outputFolder, p)) + .filter(p => fs.statSync(p).isDirectory()); + + // For each framework (in frameworkPaths), copy the symbols from the source folder. + frameworkPaths.forEach(frameworkPath => { + const frameworkPlatforms = getArchsFromFramework( + path.join( + frameworkPath, + 'PackageFrameworks', + scheme + '.framework', + scheme, + ), + ); + + // Find the correct target folder based on the current architectures + const targetFolder = targetArchFolders.find( + targetArchFolder => + frameworkPlatforms === + getArchsFromFramework( + path.join(targetArchFolder, scheme + '.framework', scheme), + ), + ); + + if (!targetFolder) { + throw new Error(`Could not find target folder for ${frameworkPath}`); + } + const sourceSymbolPath = path.join( + frameworkPath, + scheme + '.framework.dSYM', + ); + if (!fs.existsSync(sourceSymbolPath)) { + throw new Error(`dSYM folder ${sourceSymbolPath} not found`); + } + + const archName = path.basename(targetFolder); + console.log( + ` ${path.relative(outputFolder, sourceSymbolPath)} → ${archName}`, + ); + + const targetSymbolPath = path.join( + outputFolder, + '..', + 'Symbols', + archName, + scheme + '.framework.dSYM', + ); + fs.mkdirSync(targetSymbolPath, {recursive: true}); + execSync(`cp -r "${sourceSymbolPath}/" "${targetSymbolPath}"`); + }); +} + +function getArchsFromFramework(frameworkPath /*:string*/) { + return execSync(`vtool -show-build ${frameworkPath}|grep platform`) + .toString() + .split('\n') + .map(p => p.trim().split(' ')[1]) + .sort((a, b) => a.localeCompare(b)) + .join(' '); +} + function signXCFramework( identity /*: string */, xcframeworkPath /*: string */,