From 7f10daf445b6a8443400c2695c1565ab10f11b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 5 Nov 2025 10:51:41 +0100 Subject: [PATCH 1/3] Add weak-node-api headers to the Apple framework --- packages/weak-node-api/CMakeLists.txt | 35 +++++++++++++++----- packages/weak-node-api/weak-node-api.podspec | 3 -- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/weak-node-api/CMakeLists.txt b/packages/weak-node-api/CMakeLists.txt index de2784e0..e53c71cd 100644 --- a/packages/weak-node-api/CMakeLists.txt +++ b/packages/weak-node-api/CMakeLists.txt @@ -1,26 +1,43 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.19) project(weak-node-api) -add_library(${PROJECT_NAME} SHARED - generated/weak_node_api.cpp +# Read version from package.json +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/package.json" PACKAGE_JSON) +string(JSON PACKAGE_VERSION GET ${PACKAGE_JSON} version) + +add_library(${PROJECT_NAME} SHARED) + +set(INCLUDE_DIR "include") +set(GENERATED_SOURCE_DIR "generated") + +target_sources(${PROJECT_NAME} + PUBLIC + ${GENERATED_SOURCE_DIR}/weak_node_api.cpp + PUBLIC FILE_SET HEADERS + BASE_DIRS ${GENERATED_SOURCE_DIR} ${INCLUDE_DIR} FILES + ${GENERATED_SOURCE_DIR}/weak_node_api.hpp + ${INCLUDE_DIR}/js_native_api_types.h + ${INCLUDE_DIR}/js_native_api.h + ${INCLUDE_DIR}/node_api_types.h + ${INCLUDE_DIR}/node_api.h ) +get_target_property(PUBLIC_HEADER_FILES ${PROJECT_NAME} HEADER_SET) + # Stripping the prefix from the library name # to make sure the name of the XCFramework will match the name of the library if(APPLE) set_target_properties(${PROJECT_NAME} PROPERTIES FRAMEWORK TRUE MACOSX_FRAMEWORK_IDENTIFIER com.callstack.${PROJECT_NAME} - MACOSX_FRAMEWORK_SHORT_VERSION_STRING 1.0 - MACOSX_FRAMEWORK_BUNDLE_VERSION 1.0 + MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${PACKAGE_VERSION} + MACOSX_FRAMEWORK_BUNDLE_VERSION ${PACKAGE_VERSION} + VERSION ${PACKAGE_VERSION} XCODE_ATTRIBUTE_SKIP_INSTALL NO + PUBLIC_HEADER "${PUBLIC_HEADER_FILES}" ) endif() -target_include_directories(${PROJECT_NAME} - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/include -) target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) target_compile_definitions(${PROJECT_NAME} PRIVATE NAPI_VERSION=8) diff --git a/packages/weak-node-api/weak-node-api.podspec b/packages/weak-node-api/weak-node-api.podspec index 236c6ec2..26e69f74 100644 --- a/packages/weak-node-api/weak-node-api.podspec +++ b/packages/weak-node-api/weak-node-api.podspec @@ -22,11 +22,8 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/callstackincubator/react-native-node-api.git", :tag => "#{s.version}" } - # TODO: These headers could be included in the Xcframework? - # (tracked by https://github.com/callstackincubator/react-native-node-api/issues/315) s.source_files = "generated/*.hpp", "include/*.h" s.public_header_files = "generated/*.hpp", "include/*.h" - s.vendored_frameworks = "build/*/weak-node-api.xcframework" # Avoiding the header dir to allow for idiomatic Node-API includes From 8abc3c8e6aae753a3fd179b4715b468c9320eccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 5 Nov 2025 10:51:55 +0100 Subject: [PATCH 2/3] Refactor restoring symlinks --- .../src/restore-xcframework-symlinks.ts | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/packages/weak-node-api/src/restore-xcframework-symlinks.ts b/packages/weak-node-api/src/restore-xcframework-symlinks.ts index 41373809..0939f2c4 100644 --- a/packages/weak-node-api/src/restore-xcframework-symlinks.ts +++ b/packages/weak-node-api/src/restore-xcframework-symlinks.ts @@ -4,24 +4,45 @@ import path from "node:path"; import { applePrebuildPath } from "./weak-node-api.js"; -async function restoreVersionedFrameworkSymlinks(frameworkPath: string) { - const currentLinkPath = path.join(frameworkPath, "Versions", "Current"); - - if (!fs.existsSync(currentLinkPath)) { - await fs.promises.symlink("A", currentLinkPath); - } - - const binaryLinkPath = path.join(frameworkPath, "weak-node-api"); - - if (!fs.existsSync(binaryLinkPath)) { - await fs.promises.symlink("Versions/Current/weak-node-api", binaryLinkPath); +async function restoreSymlink(target: string, path: string) { + if (!fs.existsSync(path)) { + await fs.promises.symlink(target, path); } +} - const resourcesLinkPath = path.join(frameworkPath, "Resources"); +async function guessCurrentFrameworkVersion(frameworkPath: string) { + const versionsPath = path.join(frameworkPath, "Versions"); + assert(fs.existsSync(versionsPath)); + + const versionDirectoryEntries = await fs.promises.readdir(versionsPath, { + withFileTypes: true, + }); + const versions = versionDirectoryEntries + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + assert.equal( + versions.length, + 1, + `Expected exactly one directory in ${versionsPath}, found ${JSON.stringify(versions)}`, + ); + const [version] = versions; + return version; +} - if (!fs.existsSync(resourcesLinkPath)) { - await fs.promises.symlink("Versions/Current/Resources", resourcesLinkPath); - } +async function restoreVersionedFrameworkSymlinks(frameworkPath: string) { + const currentVersionName = await guessCurrentFrameworkVersion(frameworkPath); + await restoreSymlink( + currentVersionName, + path.join(frameworkPath, "Versions", "Current"), + ); + await restoreSymlink( + "Versions/Current/weak-node-api", + path.join(frameworkPath, "weak-node-api"), + ); + await restoreSymlink( + "Versions/Current/Resources", + path.join(frameworkPath, "Resources"), + ); } if (process.platform === "darwin") { From e1b824b7216d4bbe434c7ecedcda980d658f6822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 5 Nov 2025 10:52:13 +0100 Subject: [PATCH 3/3] Restore Headers symlink too --- packages/host/src/node/cli/apple.test.ts | 12 +-- packages/host/src/node/cli/apple.ts | 88 +++++++++++-------- .../src/restore-xcframework-symlinks.ts | 4 + 3 files changed, 59 insertions(+), 45 deletions(-) diff --git a/packages/host/src/node/cli/apple.test.ts b/packages/host/src/node/cli/apple.test.ts index 7d32afde..797ce905 100644 --- a/packages/host/src/node/cli/apple.test.ts +++ b/packages/host/src/node/cli/apple.test.ts @@ -9,7 +9,7 @@ import { readAndParsePlist, readFrameworkInfo, readXcframeworkInfo, - restoreFrameworkLinks, + restoreVersionedFrameworkSymlinks, } from "./apple"; import { setupTempDirectory } from "../test-utils"; @@ -269,7 +269,7 @@ describe("apple", { skip: process.platform !== "darwin" }, () => { }); }); - describe("restoreFrameworkLinks", () => { + describe("restoreVersionedFrameworkSymlinks", () => { it("restores a versioned framework", async (context) => { const infoPlistContents = ` @@ -335,11 +335,11 @@ describe("apple", { skip: process.platform !== "darwin" }, () => { ); } - await restoreFrameworkLinks(frameworkPath); + await restoreVersionedFrameworkSymlinks(frameworkPath); await assertVersionedFramework(); // Calling again to expect a no-op - await restoreFrameworkLinks(frameworkPath); + await restoreVersionedFrameworkSymlinks(frameworkPath); await assertVersionedFramework(); }); @@ -366,8 +366,8 @@ describe("apple", { skip: process.platform !== "darwin" }, () => { const frameworkPath = path.join(tempDirectoryPath, "foo.framework"); await assert.rejects( - () => restoreFrameworkLinks(frameworkPath), - /Expected "Versions" directory inside versioned framework/, + () => restoreVersionedFrameworkSymlinks(frameworkPath), + /Expected 'Versions' directory inside versioned framework/, ); }); }); diff --git a/packages/host/src/node/cli/apple.ts b/packages/host/src/node/cli/apple.ts index 3ff64cef..31aa6779 100644 --- a/packages/host/src/node/cli/apple.ts +++ b/packages/host/src/node/cli/apple.ts @@ -192,52 +192,62 @@ export async function linkFlatFramework({ } } -/** - * NPM packages aren't preserving internal symlinks inside versioned frameworks. - * This function attempts to restore those. - */ -export async function restoreFrameworkLinks(frameworkPath: string) { - // Reconstruct missing symbolic links if needed - const versionsPath = path.join(frameworkPath, "Versions"); - const versionCurrentPath = path.join(versionsPath, "Current"); +async function restoreSymlink(target: string, linkPath: string) { + if ( + !fs.existsSync(linkPath) && + fs.existsSync(path.resolve(path.dirname(linkPath), target)) + ) { + await fs.promises.symlink(target, linkPath); + } +} +async function guessCurrentFrameworkVersion(frameworkPath: string) { + const versionsPath = path.join(frameworkPath, "Versions"); assert( fs.existsSync(versionsPath), - `Expected "Versions" directory inside versioned framework '${frameworkPath}'`, + "Expected 'Versions' directory inside versioned framework", ); - if (!fs.existsSync(versionCurrentPath)) { - const versionDirectoryEntries = await fs.promises.readdir(versionsPath, { - withFileTypes: true, - }); - const versionDirectoryPaths = versionDirectoryEntries - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => path.join(dirent.parentPath, dirent.name)); - assert.equal( - versionDirectoryPaths.length, - 1, - `Expected a single directory in ${versionsPath}, found ${JSON.stringify(versionDirectoryPaths)}`, - ); - const [versionDirectoryPath] = versionDirectoryPaths; - await fs.promises.symlink( - path.relative(path.dirname(versionCurrentPath), versionDirectoryPath), - versionCurrentPath, - ); - } + const versionDirectoryEntries = await fs.promises.readdir(versionsPath, { + withFileTypes: true, + }); + const versions = versionDirectoryEntries + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + assert.equal( + versions.length, + 1, + `Expected exactly one directory in ${versionsPath}, found ${JSON.stringify(versions)}`, + ); + const [version] = versions; + return version; +} - const { CFBundleExecutable } = await readFrameworkInfo( - path.join(versionCurrentPath, "Resources", "Info.plist"), +/** + * NPM packages aren't preserving internal symlinks inside versioned frameworks. + * This function attempts to restore those. + */ +export async function restoreVersionedFrameworkSymlinks(frameworkPath: string) { + const currentVersionName = await guessCurrentFrameworkVersion(frameworkPath); + const currentVersionPath = path.join(frameworkPath, "Versions", "Current"); + await restoreSymlink(currentVersionName, currentVersionPath); + await restoreSymlink( + "Versions/Current/Resources", + path.join(frameworkPath, "Resources"), + ); + await restoreSymlink( + "Versions/Current/Headers", + path.join(frameworkPath, "Headers"), ); - const libraryRealPath = path.join(versionCurrentPath, CFBundleExecutable); - const libraryLinkPath = path.join(frameworkPath, CFBundleExecutable); - // Reconstruct missing symbolic links if needed - if (fs.existsSync(libraryRealPath) && !fs.existsSync(libraryLinkPath)) { - await fs.promises.symlink( - path.relative(path.dirname(libraryLinkPath), libraryRealPath), - libraryLinkPath, - ); - } + const { CFBundleExecutable: executableName } = await readFrameworkInfo( + path.join(currentVersionPath, "Resources", "Info.plist"), + ); + + await restoreSymlink( + path.join("Versions", "Current", executableName), + path.join(frameworkPath, executableName), + ); } export async function linkVersionedFramework({ @@ -250,7 +260,7 @@ export async function linkVersionedFramework({ "Linking Apple addons are only supported on macOS", ); - await restoreFrameworkLinks(frameworkPath); + await restoreVersionedFrameworkSymlinks(frameworkPath); const frameworkInfoPath = path.join( frameworkPath, diff --git a/packages/weak-node-api/src/restore-xcframework-symlinks.ts b/packages/weak-node-api/src/restore-xcframework-symlinks.ts index 0939f2c4..575d9ce3 100644 --- a/packages/weak-node-api/src/restore-xcframework-symlinks.ts +++ b/packages/weak-node-api/src/restore-xcframework-symlinks.ts @@ -43,6 +43,10 @@ async function restoreVersionedFrameworkSymlinks(frameworkPath: string) { "Versions/Current/Resources", path.join(frameworkPath, "Resources"), ); + await restoreSymlink( + "Versions/Current/Headers", + path.join(frameworkPath, "Headers"), + ); } if (process.platform === "darwin") {