Skip to content

Commit 9a5f8e6

Browse files
authored
Include headers in weak-node-api Xcframework (#320)
* Add weak-node-api headers to the Apple framework * Refactor restoring symlinks * Restore Headers symlink too
1 parent 60fae96 commit 9a5f8e6

File tree

5 files changed

+120
-71
lines changed

5 files changed

+120
-71
lines changed

packages/host/src/node/cli/apple.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
readAndParsePlist,
1010
readFrameworkInfo,
1111
readXcframeworkInfo,
12-
restoreFrameworkLinks,
12+
restoreVersionedFrameworkSymlinks,
1313
} from "./apple";
1414
import { setupTempDirectory } from "../test-utils";
1515

@@ -269,7 +269,7 @@ describe("apple", { skip: process.platform !== "darwin" }, () => {
269269
});
270270
});
271271

272-
describe("restoreFrameworkLinks", () => {
272+
describe("restoreVersionedFrameworkSymlinks", () => {
273273
it("restores a versioned framework", async (context) => {
274274
const infoPlistContents = `
275275
<?xml version="1.0" encoding="UTF-8"?>
@@ -335,11 +335,11 @@ describe("apple", { skip: process.platform !== "darwin" }, () => {
335335
);
336336
}
337337

338-
await restoreFrameworkLinks(frameworkPath);
338+
await restoreVersionedFrameworkSymlinks(frameworkPath);
339339
await assertVersionedFramework();
340340

341341
// Calling again to expect a no-op
342-
await restoreFrameworkLinks(frameworkPath);
342+
await restoreVersionedFrameworkSymlinks(frameworkPath);
343343
await assertVersionedFramework();
344344
});
345345

@@ -366,8 +366,8 @@ describe("apple", { skip: process.platform !== "darwin" }, () => {
366366
const frameworkPath = path.join(tempDirectoryPath, "foo.framework");
367367

368368
await assert.rejects(
369-
() => restoreFrameworkLinks(frameworkPath),
370-
/Expected "Versions" directory inside versioned framework/,
369+
() => restoreVersionedFrameworkSymlinks(frameworkPath),
370+
/Expected 'Versions' directory inside versioned framework/,
371371
);
372372
});
373373
});

packages/host/src/node/cli/apple.ts

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -192,52 +192,62 @@ export async function linkFlatFramework({
192192
}
193193
}
194194

195-
/**
196-
* NPM packages aren't preserving internal symlinks inside versioned frameworks.
197-
* This function attempts to restore those.
198-
*/
199-
export async function restoreFrameworkLinks(frameworkPath: string) {
200-
// Reconstruct missing symbolic links if needed
201-
const versionsPath = path.join(frameworkPath, "Versions");
202-
const versionCurrentPath = path.join(versionsPath, "Current");
195+
async function restoreSymlink(target: string, linkPath: string) {
196+
if (
197+
!fs.existsSync(linkPath) &&
198+
fs.existsSync(path.resolve(path.dirname(linkPath), target))
199+
) {
200+
await fs.promises.symlink(target, linkPath);
201+
}
202+
}
203203

204+
async function guessCurrentFrameworkVersion(frameworkPath: string) {
205+
const versionsPath = path.join(frameworkPath, "Versions");
204206
assert(
205207
fs.existsSync(versionsPath),
206-
`Expected "Versions" directory inside versioned framework '${frameworkPath}'`,
208+
"Expected 'Versions' directory inside versioned framework",
207209
);
208210

209-
if (!fs.existsSync(versionCurrentPath)) {
210-
const versionDirectoryEntries = await fs.promises.readdir(versionsPath, {
211-
withFileTypes: true,
212-
});
213-
const versionDirectoryPaths = versionDirectoryEntries
214-
.filter((dirent) => dirent.isDirectory())
215-
.map((dirent) => path.join(dirent.parentPath, dirent.name));
216-
assert.equal(
217-
versionDirectoryPaths.length,
218-
1,
219-
`Expected a single directory in ${versionsPath}, found ${JSON.stringify(versionDirectoryPaths)}`,
220-
);
221-
const [versionDirectoryPath] = versionDirectoryPaths;
222-
await fs.promises.symlink(
223-
path.relative(path.dirname(versionCurrentPath), versionDirectoryPath),
224-
versionCurrentPath,
225-
);
226-
}
211+
const versionDirectoryEntries = await fs.promises.readdir(versionsPath, {
212+
withFileTypes: true,
213+
});
214+
const versions = versionDirectoryEntries
215+
.filter((dirent) => dirent.isDirectory())
216+
.map((dirent) => dirent.name);
217+
assert.equal(
218+
versions.length,
219+
1,
220+
`Expected exactly one directory in ${versionsPath}, found ${JSON.stringify(versions)}`,
221+
);
222+
const [version] = versions;
223+
return version;
224+
}
227225

228-
const { CFBundleExecutable } = await readFrameworkInfo(
229-
path.join(versionCurrentPath, "Resources", "Info.plist"),
226+
/**
227+
* NPM packages aren't preserving internal symlinks inside versioned frameworks.
228+
* This function attempts to restore those.
229+
*/
230+
export async function restoreVersionedFrameworkSymlinks(frameworkPath: string) {
231+
const currentVersionName = await guessCurrentFrameworkVersion(frameworkPath);
232+
const currentVersionPath = path.join(frameworkPath, "Versions", "Current");
233+
await restoreSymlink(currentVersionName, currentVersionPath);
234+
await restoreSymlink(
235+
"Versions/Current/Resources",
236+
path.join(frameworkPath, "Resources"),
237+
);
238+
await restoreSymlink(
239+
"Versions/Current/Headers",
240+
path.join(frameworkPath, "Headers"),
230241
);
231242

232-
const libraryRealPath = path.join(versionCurrentPath, CFBundleExecutable);
233-
const libraryLinkPath = path.join(frameworkPath, CFBundleExecutable);
234-
// Reconstruct missing symbolic links if needed
235-
if (fs.existsSync(libraryRealPath) && !fs.existsSync(libraryLinkPath)) {
236-
await fs.promises.symlink(
237-
path.relative(path.dirname(libraryLinkPath), libraryRealPath),
238-
libraryLinkPath,
239-
);
240-
}
243+
const { CFBundleExecutable: executableName } = await readFrameworkInfo(
244+
path.join(currentVersionPath, "Resources", "Info.plist"),
245+
);
246+
247+
await restoreSymlink(
248+
path.join("Versions", "Current", executableName),
249+
path.join(frameworkPath, executableName),
250+
);
241251
}
242252

243253
export async function linkVersionedFramework({
@@ -250,7 +260,7 @@ export async function linkVersionedFramework({
250260
"Linking Apple addons are only supported on macOS",
251261
);
252262

253-
await restoreFrameworkLinks(frameworkPath);
263+
await restoreVersionedFrameworkSymlinks(frameworkPath);
254264

255265
const frameworkInfoPath = path.join(
256266
frameworkPath,

packages/weak-node-api/CMakeLists.txt

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,43 @@
1-
cmake_minimum_required(VERSION 3.15)
1+
cmake_minimum_required(VERSION 3.19)
22
project(weak-node-api)
33

4-
add_library(${PROJECT_NAME} SHARED
5-
generated/weak_node_api.cpp
4+
# Read version from package.json
5+
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/package.json" PACKAGE_JSON)
6+
string(JSON PACKAGE_VERSION GET ${PACKAGE_JSON} version)
7+
8+
add_library(${PROJECT_NAME} SHARED)
9+
10+
set(INCLUDE_DIR "include")
11+
set(GENERATED_SOURCE_DIR "generated")
12+
13+
target_sources(${PROJECT_NAME}
14+
PUBLIC
15+
${GENERATED_SOURCE_DIR}/weak_node_api.cpp
16+
PUBLIC FILE_SET HEADERS
17+
BASE_DIRS ${GENERATED_SOURCE_DIR} ${INCLUDE_DIR} FILES
18+
${GENERATED_SOURCE_DIR}/weak_node_api.hpp
19+
${INCLUDE_DIR}/js_native_api_types.h
20+
${INCLUDE_DIR}/js_native_api.h
21+
${INCLUDE_DIR}/node_api_types.h
22+
${INCLUDE_DIR}/node_api.h
623
)
724

25+
get_target_property(PUBLIC_HEADER_FILES ${PROJECT_NAME} HEADER_SET)
26+
827
# Stripping the prefix from the library name
928
# to make sure the name of the XCFramework will match the name of the library
1029
if(APPLE)
1130
set_target_properties(${PROJECT_NAME} PROPERTIES
1231
FRAMEWORK TRUE
1332
MACOSX_FRAMEWORK_IDENTIFIER com.callstack.${PROJECT_NAME}
14-
MACOSX_FRAMEWORK_SHORT_VERSION_STRING 1.0
15-
MACOSX_FRAMEWORK_BUNDLE_VERSION 1.0
33+
MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${PACKAGE_VERSION}
34+
MACOSX_FRAMEWORK_BUNDLE_VERSION ${PACKAGE_VERSION}
35+
VERSION ${PACKAGE_VERSION}
1636
XCODE_ATTRIBUTE_SKIP_INSTALL NO
37+
PUBLIC_HEADER "${PUBLIC_HEADER_FILES}"
1738
)
1839
endif()
1940

20-
target_include_directories(${PROJECT_NAME}
21-
PUBLIC
22-
${CMAKE_CURRENT_SOURCE_DIR}/include
23-
)
2441
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
2542
target_compile_definitions(${PROJECT_NAME} PRIVATE NAPI_VERSION=8)
2643

packages/weak-node-api/src/restore-xcframework-symlinks.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,49 @@ import path from "node:path";
44

55
import { applePrebuildPath } from "./weak-node-api.js";
66

7-
async function restoreVersionedFrameworkSymlinks(frameworkPath: string) {
8-
const currentLinkPath = path.join(frameworkPath, "Versions", "Current");
9-
10-
if (!fs.existsSync(currentLinkPath)) {
11-
await fs.promises.symlink("A", currentLinkPath);
7+
async function restoreSymlink(target: string, path: string) {
8+
if (!fs.existsSync(path)) {
9+
await fs.promises.symlink(target, path);
1210
}
11+
}
1312

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

16-
if (!fs.existsSync(binaryLinkPath)) {
17-
await fs.promises.symlink("Versions/Current/weak-node-api", binaryLinkPath);
18-
}
19-
20-
const resourcesLinkPath = path.join(frameworkPath, "Resources");
17+
const versionDirectoryEntries = await fs.promises.readdir(versionsPath, {
18+
withFileTypes: true,
19+
});
20+
const versions = versionDirectoryEntries
21+
.filter((dirent) => dirent.isDirectory())
22+
.map((dirent) => dirent.name);
23+
assert.equal(
24+
versions.length,
25+
1,
26+
`Expected exactly one directory in ${versionsPath}, found ${JSON.stringify(versions)}`,
27+
);
28+
const [version] = versions;
29+
return version;
30+
}
2131

22-
if (!fs.existsSync(resourcesLinkPath)) {
23-
await fs.promises.symlink("Versions/Current/Resources", resourcesLinkPath);
24-
}
32+
async function restoreVersionedFrameworkSymlinks(frameworkPath: string) {
33+
const currentVersionName = await guessCurrentFrameworkVersion(frameworkPath);
34+
await restoreSymlink(
35+
currentVersionName,
36+
path.join(frameworkPath, "Versions", "Current"),
37+
);
38+
await restoreSymlink(
39+
"Versions/Current/weak-node-api",
40+
path.join(frameworkPath, "weak-node-api"),
41+
);
42+
await restoreSymlink(
43+
"Versions/Current/Resources",
44+
path.join(frameworkPath, "Resources"),
45+
);
46+
await restoreSymlink(
47+
"Versions/Current/Headers",
48+
path.join(frameworkPath, "Headers"),
49+
);
2550
}
2651

2752
if (process.platform === "darwin") {

packages/weak-node-api/weak-node-api.podspec

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@ Pod::Spec.new do |s|
2222

2323
s.source = { :git => "https://github.com/callstackincubator/react-native-node-api.git", :tag => "#{s.version}" }
2424

25-
# TODO: These headers could be included in the Xcframework?
26-
# (tracked by https://github.com/callstackincubator/react-native-node-api/issues/315)
2725
s.source_files = "generated/*.hpp", "include/*.h"
2826
s.public_header_files = "generated/*.hpp", "include/*.h"
29-
3027
s.vendored_frameworks = "build/*/weak-node-api.xcframework"
3128

3229
# Avoiding the header dir to allow for idiomatic Node-API includes

0 commit comments

Comments
 (0)