Skip to content

Commit 4afa6ef

Browse files
authored
Add weak-node-api dynamic library (#37)
* Add option to skip auto-linking * Migrate copy script to TS * Fix tsconfig for scripts * Add tool for building weak-node-api * WIP building Node-API modules for Android * Move types * Add no-weak-node-api-linkage * Make the node-api headers public for weak-node-api * Fix linking with the weak-node-api dynamic library
1 parent cf2f7fc commit 4afa6ef

File tree

15 files changed

+573
-101
lines changed

15 files changed

+573
-101
lines changed

package-lock.json

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-node-api-cmake/src/android.ts

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
1-
import assert from "node:assert";
1+
import assert from "node:assert/strict";
22
import fs from "node:fs";
33
import path from "node:path";
44

5-
import { type SupportedTriplet } from "./triplets.js";
6-
7-
/**
8-
* https://developer.android.com/ndk/guides/other_build_systems
9-
*/
10-
export const ANDROID_TRIPLETS = [
11-
"aarch64-linux-android",
12-
"armv7a-linux-androideabi",
13-
"i686-linux-android",
14-
"x86_64-linux-android",
15-
] as const;
16-
17-
export type AndroidTriplet = (typeof ANDROID_TRIPLETS)[number];
5+
import { AndroidTriplet } from "./triplets.js";
186

197
export const DEFAULT_ANDROID_TRIPLETS = [
208
"aarch64-linux-android",
@@ -23,15 +11,9 @@ export const DEFAULT_ANDROID_TRIPLETS = [
2311
"x86_64-linux-android",
2412
] as const satisfies AndroidTriplet[];
2513

26-
export function isAndroidTriplet(
27-
triplet: SupportedTriplet
28-
): triplet is AndroidTriplet {
29-
return ANDROID_TRIPLETS.includes(triplet as AndroidTriplet);
30-
}
31-
3214
type AndroidArchitecture = "armeabi-v7a" | "arm64-v8a" | "x86" | "x86_64";
3315

34-
export const ARCHITECTURES = {
16+
export const ANDROID_ARCHITECTURES = {
3517
"armv7a-linux-androideabi": "armeabi-v7a",
3618
"aarch64-linux-android": "arm64-v8a",
3719
"i686-linux-android": "x86",
@@ -64,7 +46,14 @@ export function getAndroidConfigureCmakeArgs({
6446
ndkPath,
6547
"build/cmake/android.toolchain.cmake"
6648
);
67-
const architecture = ARCHITECTURES[triplet];
49+
const architecture = ANDROID_ARCHITECTURES[triplet];
50+
51+
const linkerFlags: string[] = [
52+
// `--no-version-undefined`,
53+
// `--whole-archive`,
54+
// `--no-whole-archive`,
55+
];
56+
6857
return [
6958
// Use the XCode as generator for Apple platforms
7059
"-G",
@@ -73,8 +62,8 @@ export function getAndroidConfigureCmakeArgs({
7362
toolchainPath,
7463
"-D",
7564
"CMAKE_SYSTEM_NAME=Android",
76-
"-D",
77-
`CPACK_SYSTEM_NAME=Android-${architecture}`,
65+
// "-D",
66+
// `CPACK_SYSTEM_NAME=Android-${architecture}`,
7867
// "-D",
7968
// `CMAKE_INSTALL_PREFIX=${installPath}`,
8069
// "-D",
@@ -96,8 +85,66 @@ export function getAndroidConfigureCmakeArgs({
9685
"-D",
9786
"ANDROID_STL=c++_shared",
9887
// Pass linker flags to avoid errors from undefined symbols
99-
// TODO: Link against a fake libhermes to avoid this (or whatever other lib which will be providing the symbols)
88+
// TODO: Link against a weak-node-api to avoid this (or whatever other lib which will be providing the symbols)
89+
// "-D",
90+
// `CMAKE_SHARED_LINKER_FLAGS="-Wl,--allow-shlib-undefined"`,
10091
"-D",
101-
`CMAKE_SHARED_LINKER_FLAGS="-Wl,--allow-shlib-undefined"`,
92+
`CMAKE_SHARED_LINKER_FLAGS=${linkerFlags
93+
.map((flag) => `-Wl,${flag}`)
94+
.join(" ")}`,
10295
];
10396
}
97+
98+
/**
99+
* Determine the filename of the Android libs directory based on the framework paths.
100+
* Ensuring that all framework paths have the same base name.
101+
*/
102+
export function determineAndroidLibsFilename(frameworkPaths: string[]) {
103+
const frameworkNames = frameworkPaths.map((p) =>
104+
path.basename(p, path.extname(p))
105+
);
106+
const candidates = new Set<string>(frameworkNames);
107+
assert(
108+
candidates.size === 1,
109+
"Expected all frameworks to have the same name"
110+
);
111+
const [name] = candidates;
112+
return `${name}.android.node`;
113+
}
114+
115+
type AndroidLibsDirectoryOptions = {
116+
outputPath: string;
117+
libraryPathByTriplet: Record<AndroidTriplet, string>;
118+
autoLink: boolean;
119+
};
120+
121+
export async function createAndroidLibsDirectory({
122+
outputPath,
123+
libraryPathByTriplet,
124+
autoLink,
125+
}: AndroidLibsDirectoryOptions) {
126+
// Delete and recreate any existing output directory
127+
await fs.promises.rm(outputPath, { recursive: true, force: true });
128+
await fs.promises.mkdir(outputPath, { recursive: true });
129+
for (const [triplet] of Object.entries(libraryPathByTriplet)) {
130+
const libraryPath = libraryPathByTriplet[triplet as AndroidTriplet];
131+
assert(
132+
fs.existsSync(libraryPath),
133+
`Library not found: ${libraryPath} for triplet ${triplet}`
134+
);
135+
const arch = ANDROID_ARCHITECTURES[triplet as AndroidTriplet];
136+
const archOutputPath = path.join(outputPath, arch);
137+
await fs.promises.mkdir(archOutputPath, { recursive: true });
138+
const libraryName = path.basename(libraryPath, path.extname(libraryPath));
139+
const libraryOutputPath = path.join(archOutputPath, libraryName);
140+
await fs.promises.copyFile(libraryPath, libraryOutputPath);
141+
}
142+
if (autoLink) {
143+
// Write a file to mark the Android libs directory is a Node-API module
144+
await fs.promises.writeFile(
145+
path.join(outputPath, "react-native-node-api-module"),
146+
"",
147+
"utf8"
148+
);
149+
}
150+
}

packages/react-native-node-api-cmake/src/apple.ts

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,9 @@ import cp from "node:child_process";
33
import fs from "node:fs";
44
import path from "node:path";
55

6-
import type { SupportedTriplet } from "./triplets.js";
76
import { spawn } from "bufout";
87

9-
export const APPLE_TRIPLETS = [
10-
"arm64;x86_64-apple-darwin",
11-
"x86_64-apple-darwin",
12-
"arm64-apple-darwin",
13-
"arm64-apple-ios",
14-
"arm64-apple-ios-sim",
15-
"arm64-apple-tvos",
16-
"arm64-apple-tvos-sim",
17-
// "x86_64-apple-tvos",
18-
"arm64-apple-visionos",
19-
"arm64-apple-visionos-sim",
20-
] as const;
21-
22-
export type AppleTriplet = (typeof APPLE_TRIPLETS)[number];
8+
import { AppleTriplet, isAppleTriplet } from "./triplets.js";
239

2410
export const DEFAULT_APPLE_TRIPLETS = [
2511
"arm64;x86_64-apple-darwin",
@@ -41,7 +27,7 @@ type XcodeSDKName =
4127
| "appletvsimulator"
4228
| "macosx";
4329

44-
const SDK_NAMES = {
30+
const XCODE_SDK_NAMES = {
4531
"x86_64-apple-darwin": "macosx",
4632
"arm64-apple-darwin": "macosx",
4733
"arm64;x86_64-apple-darwin": "macosx",
@@ -54,9 +40,24 @@ const SDK_NAMES = {
5440
"arm64-apple-visionos-sim": "xrsimulator",
5541
} satisfies Record<AppleTriplet, XcodeSDKName>;
5642

43+
type CMakeSystemName = "Darwin" | "iOS" | "tvOS" | "watchOS" | "visionOS";
44+
45+
const CMAKE_SYSTEM_NAMES = {
46+
"x86_64-apple-darwin": "Darwin",
47+
"arm64-apple-darwin": "Darwin",
48+
"arm64;x86_64-apple-darwin": "Darwin",
49+
"arm64-apple-ios": "iOS",
50+
"arm64-apple-ios-sim": "iOS",
51+
"arm64-apple-tvos": "tvOS",
52+
// "x86_64-apple-tvos": "appletvos",
53+
"arm64-apple-tvos-sim": "tvOS",
54+
"arm64-apple-visionos": "visionOS",
55+
"arm64-apple-visionos-sim": "visionOS",
56+
} satisfies Record<AppleTriplet, CMakeSystemName>;
57+
5758
type AppleArchitecture = "arm64" | "x86_64" | "arm64;x86_64";
5859

59-
export const ARCHITECTURES = {
60+
export const APPLE_ARCHITECTURES = {
6061
"x86_64-apple-darwin": "x86_64",
6162
"arm64-apple-darwin": "arm64",
6263
"arm64;x86_64-apple-darwin": "arm64;x86_64",
@@ -69,17 +70,15 @@ export const ARCHITECTURES = {
6970
"arm64-apple-visionos-sim": "arm64",
7071
} satisfies Record<AppleTriplet, AppleArchitecture>;
7172

72-
export function isAppleTriplet(
73-
triplet: SupportedTriplet
74-
): triplet is AppleTriplet {
75-
return APPLE_TRIPLETS.includes(triplet as AppleTriplet);
76-
}
77-
7873
export function getAppleSDKPath(triplet: AppleTriplet) {
7974
return cp
80-
.spawnSync("xcrun", ["--sdk", SDK_NAMES[triplet], "--show-sdk-path"], {
81-
encoding: "utf-8",
82-
})
75+
.spawnSync(
76+
"xcrun",
77+
["--sdk", XCODE_SDK_NAMES[triplet], "--show-sdk-path"],
78+
{
79+
encoding: "utf-8",
80+
}
81+
)
8382
.stdout.trim();
8483
}
8584

@@ -98,23 +97,27 @@ export function createPlistContent(values: Record<string, string>) {
9897
].join("\n");
9998
}
10099

101-
export function getAppleConfigureCmakeArgs(triplet: AppleTriplet) {
100+
type AppleConfigureOptions = {
101+
triplet: AppleTriplet;
102+
};
103+
104+
export function getAppleConfigureCmakeArgs({ triplet }: AppleConfigureOptions) {
102105
assert(isAppleTriplet(triplet));
103106
const sdkPath = getAppleSDKPath(triplet);
107+
const systemName = CMAKE_SYSTEM_NAMES[triplet];
104108

105109
return [
106110
// Use the XCode as generator for Apple platforms
107111
"-G",
108112
"Xcode",
109-
// Pass linker flags to avoid errors from undefined symbols
110113
"-D",
111-
`CMAKE_SHARED_LINKER_FLAGS="-Wl,-undefined,dynamic_lookup"`,
114+
`CMAKE_SYSTEM_NAME=${systemName}`,
112115
// Set the SDK path for the target platform
113116
"-D",
114117
`CMAKE_OSX_SYSROOT=${sdkPath}`,
115118
// Set the target architecture
116119
"-D",
117-
`CMAKE_OSX_ARCHITECTURES=${ARCHITECTURES[triplet]}`,
120+
`CMAKE_OSX_ARCHITECTURES=${APPLE_ARCHITECTURES[triplet]}`,
118121
];
119122
}
120123

@@ -126,6 +129,7 @@ export function getAppleBuildArgs() {
126129
type XCframeworkOptions = {
127130
frameworkPaths: string[];
128131
outputPath: string;
132+
autoLink: boolean;
129133
};
130134

131135
export function createFramework(libraryPath: string) {
@@ -171,6 +175,7 @@ export function createFramework(libraryPath: string) {
171175
export async function createXCframework({
172176
frameworkPaths,
173177
outputPath,
178+
autoLink,
174179
}: XCframeworkOptions) {
175180
// Delete any existing xcframework to prevent the error:
176181
// - A library with the identifier 'macos-arm64' already exists.
@@ -189,13 +194,15 @@ export async function createXCframework({
189194
outputMode: "buffered",
190195
}
191196
);
192-
// Write a file to mark the xcframework is a Node-API module
193-
// TODO: Consider including this in the Info.plist file instead
194-
fs.writeFileSync(
195-
path.join(outputPath, "react-native-node-api-module"),
196-
"",
197-
"utf8"
198-
);
197+
if (autoLink) {
198+
// Write a file to mark the xcframework is a Node-API module
199+
// TODO: Consider including this in the Info.plist file instead
200+
fs.writeFileSync(
201+
path.join(outputPath, "react-native-node-api-module"),
202+
"",
203+
"utf8"
204+
);
205+
}
199206
}
200207

201208
/**

0 commit comments

Comments
 (0)