Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/host/src/node/cli/apple.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
readAndParsePlist,
readFrameworkInfo,
readXcframeworkInfo,
restoreFrameworkLinks,
restoreVersionedFrameworkSymlinks,
} from "./apple";
import { setupTempDirectory } from "../test-utils";

Expand Down Expand Up @@ -269,7 +269,7 @@ describe("apple", { skip: process.platform !== "darwin" }, () => {
});
});

describe("restoreFrameworkLinks", () => {
describe("restoreVersionedFrameworkSymlinks", () => {
it("restores a versioned framework", async (context) => {
const infoPlistContents = `
<?xml version="1.0" encoding="UTF-8"?>
Expand Down Expand Up @@ -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();
});

Expand All @@ -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/,
);
});
});
Expand Down
88 changes: 49 additions & 39 deletions packages/host/src/node/cli/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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,
Expand Down
35 changes: 26 additions & 9 deletions packages/weak-node-api/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
53 changes: 39 additions & 14 deletions packages/weak-node-api/src/restore-xcframework-symlinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,49 @@ 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);
async function restoreSymlink(target: string, path: string) {
if (!fs.existsSync(path)) {
await fs.promises.symlink(target, path);
}
}

const binaryLinkPath = path.join(frameworkPath, "weak-node-api");
async function guessCurrentFrameworkVersion(frameworkPath: string) {
const versionsPath = path.join(frameworkPath, "Versions");
assert(fs.existsSync(versionsPath));

if (!fs.existsSync(binaryLinkPath)) {
await fs.promises.symlink("Versions/Current/weak-node-api", binaryLinkPath);
}

const resourcesLinkPath = path.join(frameworkPath, "Resources");
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"),
);
await restoreSymlink(
"Versions/Current/Headers",
path.join(frameworkPath, "Headers"),
);
}

if (process.platform === "darwin") {
Expand Down
3 changes: 0 additions & 3 deletions packages/weak-node-api/weak-node-api.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading