diff --git a/.changeset/flat-suits-cross.md b/.changeset/flat-suits-cross.md new file mode 100644 index 00000000..2b1535cd --- /dev/null +++ b/.changeset/flat-suits-cross.md @@ -0,0 +1,5 @@ +--- +"react-native-node-api": patch +--- + +Rebuild any dSYM directory when linking frameworks. diff --git a/.changeset/hot-poems-attack.md b/.changeset/hot-poems-attack.md new file mode 100644 index 00000000..dee83154 --- /dev/null +++ b/.changeset/hot-poems-attack.md @@ -0,0 +1,5 @@ +--- +"cmake-rn": patch +--- + +Locate and include debug symbols when creating an Xcframework. diff --git a/.changeset/light-buttons-leave.md b/.changeset/light-buttons-leave.md new file mode 100644 index 00000000..ff0767e0 --- /dev/null +++ b/.changeset/light-buttons-leave.md @@ -0,0 +1,5 @@ +--- +"cmake-rn": patch +--- + +Allow passing "RelWithDebInfo" and "MinSizeRel" as --configuration diff --git a/packages/cmake-rn/src/cli.ts b/packages/cmake-rn/src/cli.ts index f2e45f47..0ddc2dd7 100644 --- a/packages/cmake-rn/src/cli.ts +++ b/packages/cmake-rn/src/cli.ts @@ -36,9 +36,8 @@ const sourcePathOption = new Option( "Specify the source directory containing a CMakeLists.txt file", ).default(process.cwd()); -// TODO: Add "MinSizeRel" and "RelWithDebInfo" const configurationOption = new Option("--configuration ") - .choices(["Release", "Debug"] as const) + .choices(["Release", "Debug", "RelWithDebInfo", "MinSizeRel"] as const) .default("Release"); // TODO: Derive default build triplets diff --git a/packages/cmake-rn/src/platforms/android.ts b/packages/cmake-rn/src/platforms/android.ts index f058802a..baceb452 100644 --- a/packages/cmake-rn/src/platforms/android.ts +++ b/packages/cmake-rn/src/platforms/android.ts @@ -13,7 +13,7 @@ import { } from "react-native-node-api"; import * as cmakeFileApi from "cmake-file-api"; -import type { Platform } from "./types.js"; +import type { BaseOpts, Platform } from "./types.js"; import { toDefineArguments } from "../helpers.js"; import { getCmakeJSVariables, @@ -45,8 +45,12 @@ const androidSdkVersionOption = new Option( type AndroidOpts = { ndkVersion: string; androidSdkVersion: string }; -function getBuildPath(baseBuildPath: string, triplet: Triplet) { - return path.join(baseBuildPath, triplet); +function getBuildPath( + baseBuildPath: string, + triplet: Triplet, + configuration: BaseOpts["configuration"], +) { + return path.join(baseBuildPath, triplet + "-" + configuration); } function getNdkPath(ndkVersion: string) { @@ -147,7 +151,7 @@ export const platform: Platform = { await Promise.all( triplets.map(async ({ triplet, spawn }) => { - const buildPath = getBuildPath(build, triplet); + const buildPath = getBuildPath(build, triplet, configuration); const outputPath = path.join(buildPath, "out"); // We want to use the CMake File API to query information later await cmakeFileApi.createSharedStatelessQuery( @@ -161,6 +165,8 @@ export const platform: Platform = { source, "-B", buildPath, + // Ideally, we would use the "Ninja Multi-Config" generator here, + // but it doesn't support the "RelWithDebInfo" configuration on Android. "-G", "Ninja", "--toolchain", @@ -179,8 +185,8 @@ export const platform: Platform = { }), ); }, - async build({ triplet, spawn }, { target, build }) { - const buildPath = getBuildPath(build, triplet); + async build({ triplet, spawn }, { target, build, configuration }) { + const buildPath = getBuildPath(build, triplet, configuration); await spawn("cmake", [ "--build", buildPath, @@ -201,8 +207,8 @@ export const platform: Platform = { { triplet: Triplet; libraryPath: string }[] > = {}; - for (const { spawn, triplet } of triplets) { - const buildPath = getBuildPath(build, triplet); + for (const { triplet, spawn } of triplets) { + const buildPath = getBuildPath(build, triplet, configuration); assert(fs.existsSync(buildPath), `Expected a directory at ${buildPath}`); const targets = await cmakeFileApi.readCurrentTargetsDeep( buildPath, diff --git a/packages/host/src/node/cli/apple.ts b/packages/host/src/node/cli/apple.ts index 0ccbd82a..c5eec200 100644 --- a/packages/host/src/node/cli/apple.ts +++ b/packages/host/src/node/cli/apple.ts @@ -59,6 +59,7 @@ const XcframeworkInfoSchema = zod.looseObject({ BinaryPath: zod.string(), LibraryIdentifier: zod.string(), LibraryPath: zod.string(), + DebugSymbolsPath: zod.string().optional(), }), ), CFBundlePackageType: zod.literal("XFWK"), @@ -100,13 +101,12 @@ export async function writeFrameworkInfo( type LinkFrameworkOptions = { frameworkPath: string; + debugSymbolsPath?: string; newLibraryName: string; }; -export async function linkFramework({ - frameworkPath, - newLibraryName, -}: LinkFrameworkOptions) { +export async function linkFramework(options: LinkFrameworkOptions) { + const { frameworkPath } = options; assert.equal( process.platform, "darwin", @@ -117,14 +117,15 @@ export async function linkFramework({ `Expected framework at '${frameworkPath}'`, ); if (fs.existsSync(path.join(frameworkPath, "Versions"))) { - await linkVersionedFramework({ frameworkPath, newLibraryName }); + await linkVersionedFramework(options); } else { - await linkFlatFramework({ frameworkPath, newLibraryName }); + await linkFlatFramework(options); } } export async function linkFlatFramework({ frameworkPath, + debugSymbolsPath, newLibraryName, }: LinkFrameworkOptions) { assert.equal( @@ -151,16 +152,44 @@ export async function linkFlatFramework({ ...frameworkInfo, CFBundleExecutable: newLibraryName, }); + // Rename the actual binary await fs.promises.rename( path.join(frameworkPath, frameworkInfo.CFBundleExecutable), path.join(frameworkPath, newLibraryName), ); // Rename the framework directory - await fs.promises.rename( - frameworkPath, - path.join(path.dirname(frameworkPath), `${newLibraryName}.framework`), + const newFrameworkPath = path.join( + path.dirname(frameworkPath), + `${newLibraryName}.framework`, ); + await fs.promises.rename(frameworkPath, newFrameworkPath); + + if (debugSymbolsPath) { + const frameworkDebugSymbolsPath = path.join( + debugSymbolsPath, + `${path.basename(frameworkPath)}.dSYM`, + ); + if (fs.existsSync(frameworkDebugSymbolsPath)) { + // Remove existing DWARF data + await fs.promises.rm(frameworkDebugSymbolsPath, { + recursive: true, + force: true, + }); + // Rebuild DWARF data + await spawn( + "dsymutil", + [ + path.join(newFrameworkPath, newLibraryName), + "-o", + path.join(debugSymbolsPath, newLibraryName + ".dSYM"), + ], + { + outputMode: "buffered", + }, + ); + } + } } export async function linkVersionedFramework({ @@ -282,7 +311,17 @@ export async function linkXcframework({ framework.LibraryIdentifier, framework.LibraryPath, ); - await linkFramework({ frameworkPath, newLibraryName }); + await linkFramework({ + frameworkPath, + newLibraryName, + debugSymbolsPath: framework.DebugSymbolsPath + ? path.join( + outputPath, + framework.LibraryIdentifier, + framework.DebugSymbolsPath, + ) + : undefined, + }); }), ); diff --git a/packages/host/src/node/path-utils.ts b/packages/host/src/node/path-utils.ts index 60fd0ffa..a2a954a6 100644 --- a/packages/host/src/node/path-utils.ts +++ b/packages/host/src/node/path-utils.ts @@ -521,11 +521,17 @@ export function getLatestMtime(fromPath: string): number { // https://github.com/TooTallNate/node-bindings/blob/v1.3.0/bindings.js#L21 const nodeBindingsSubdirs = [ "./", + "./build/MinSizeRel", + "./build/RelWithDebInfo", "./build/Release", "./build/Debug", "./build", + "./out/MinSizeRel", + "./out/RelWithDebInfo", "./out/Release", "./out/Debug", + "./MinSizeRel", + "./RelWithDebInfo", "./Release", "./Debug", ]; diff --git a/packages/host/src/node/prebuilds/apple.ts b/packages/host/src/node/prebuilds/apple.ts index dce6353b..22be74fa 100644 --- a/packages/host/src/node/prebuilds/apple.ts +++ b/packages/host/src/node/prebuilds/apple.ts @@ -99,7 +99,19 @@ export async function createXCframework({ "xcodebuild", [ "-create-xcframework", - ...frameworkPaths.flatMap((p) => ["-framework", p]), + ...frameworkPaths.flatMap((frameworkPath) => { + const debugSymbolPath = frameworkPath + ".dSYM"; + if (fs.existsSync(debugSymbolPath)) { + return [ + "-framework", + frameworkPath, + "-debug-symbols", + debugSymbolPath, + ]; + } else { + return ["-framework", frameworkPath]; + } + }), "-output", xcodeOutputPath, ], diff --git a/packages/node-addon-examples/scripts/build-examples.mts b/packages/node-addon-examples/scripts/build-examples.mts index fd590eb6..bc447e71 100644 --- a/packages/node-addon-examples/scripts/build-examples.mts +++ b/packages/node-addon-examples/scripts/build-examples.mts @@ -6,14 +6,9 @@ const projectDirectories = findCMakeProjects(); for (const projectDirectory of projectDirectories) { console.log(`Running "cmake-rn" in ${projectDirectory}`); - execSync( - "cmake-rn", - // "cmake-rn --android --apple", - // "cmake-rn --triplet aarch64-linux-android --triplet arm64-apple-ios-sim", - { - cwd: projectDirectory, - stdio: "inherit", - }, - ); + execSync("cmake-rn --configuration RelWithDebInfo", { + cwd: projectDirectory, + stdio: "inherit", + }); console.log(); }