Skip to content

Commit 427673f

Browse files
authored
Use apple frameworks (#274)
* Use Xcode to build frameworks * Generate CMakeLists.txt with frameworks enabled * Handling non-framework dylibs too * Dereference framework directories * Use framework in weak-node-api * Update cmake configs in addon-examples/tests * Assert a symbolic link before dereferencingDirectory * Add "-" to list of chars not escaped from bundle ids * Remove unused comments * Add FRAMEWORK in cmake-rn README.md
1 parent ac3ee34 commit 427673f

File tree

20 files changed

+701
-274
lines changed

20 files changed

+701
-274
lines changed

package-lock.json

Lines changed: 19 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cmake-rn/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,28 @@ To link against `weak-node-api` just include the CMake config exposed through `W
1616
cmake_minimum_required(VERSION 3.15...3.31)
1717
project(tests-buffers)
1818
19+
# Defines the "weak-node-api" target
1920
include(${WEAK_NODE_API_CONFIG})
2021
2122
add_library(addon SHARED addon.c)
2223
target_link_libraries(addon PRIVATE weak-node-api)
2324
target_compile_features(addon PRIVATE cxx_std_20)
25+
26+
if(APPLE)
27+
# Build frameworks when building for Apple (optional)
28+
set_target_properties(addon PROPERTIES
29+
FRAMEWORK TRUE
30+
MACOSX_FRAMEWORK_IDENTIFIER async_test.addon
31+
MACOSX_FRAMEWORK_SHORT_VERSION_STRING 1.0
32+
MACOSX_FRAMEWORK_BUNDLE_VERSION 1.0
33+
XCODE_ATTRIBUTE_SKIP_INSTALL NO
34+
)
35+
else()
36+
set_target_properties(addon PROPERTIES
37+
PREFIX ""
38+
SUFFIX .node
39+
)
40+
endif()
2441
```
2542

2643
This is different from how `cmake-js` "injects" the Node-API for linking (via `${CMAKE_JS_INC}`, `${CMAKE_JS_SRC}` and `${CMAKE_JS_LIB}`). To allow for interoperability between these tools, we inject these when you pass `--cmake-js` to `cmake-rn`.

packages/cmake-rn/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"dependencies": {
2727
"@react-native-node-api/cli-utils": "0.1.0",
2828
"cmake-file-api": "0.1.0",
29-
"react-native-node-api": "0.5.2"
29+
"react-native-node-api": "0.5.2",
30+
"zod": "^4.1.11"
3031
},
3132
"peerDependencies": {
3233
"node-addon-api": "^8.3.1",

packages/cmake-rn/src/cli.ts

Lines changed: 43 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,14 @@ import {
1212
assertFixable,
1313
wrapAction,
1414
} from "@react-native-node-api/cli-utils";
15-
import { isSupportedTriplet } from "react-native-node-api";
16-
import * as cmakeFileApi from "cmake-file-api";
1715

18-
import {
19-
getCmakeJSVariables,
20-
getWeakNodeApiVariables,
21-
} from "./weak-node-api.js";
2216
import {
2317
platforms,
2418
allTriplets as allTriplets,
2519
findPlatformForTriplet,
2620
platformHasTriplet,
2721
} from "./platforms.js";
28-
import { BaseOpts, TripletContext, Platform } from "./platforms/types.js";
22+
import { Platform } from "./platforms/types.js";
2923

3024
// We're attaching a lot of listeners when spawning in parallel
3125
EventEmitter.defaultMaxListeners = 100;
@@ -170,17 +164,17 @@ program = program.action(
170164
process.cwd(),
171165
expandTemplate(baseOptions.out, baseOptions),
172166
);
173-
const { out, build: buildPath } = baseOptions;
167+
const { verbose, clean, source, out, build: buildPath } = baseOptions;
174168

175169
assertFixable(
176-
fs.existsSync(path.join(baseOptions.source, "CMakeLists.txt")),
177-
`No CMakeLists.txt found in source directory: ${chalk.dim(baseOptions.source)}`,
170+
fs.existsSync(path.join(source, "CMakeLists.txt")),
171+
`No CMakeLists.txt found in source directory: ${chalk.dim(source)}`,
178172
{
179173
instructions: `Change working directory into a directory with a CMakeLists.txt, create one or specify the correct source directory using --source`,
180174
},
181175
);
182176

183-
if (baseOptions.clean) {
177+
if (clean) {
184178
await fs.promises.rm(buildPath, { recursive: true, force: true });
185179
}
186180
const triplets = new Set<string>(requestedTriplets);
@@ -217,25 +211,47 @@ program = program.action(
217211

218212
const tripletContexts = [...triplets].map((triplet) => {
219213
const platform = findPlatformForTriplet(triplet);
220-
const tripletBuildPath = getTripletBuildPath(buildPath, triplet);
214+
221215
return {
222216
triplet,
223217
platform,
224-
buildPath: tripletBuildPath,
225-
outputPath: path.join(tripletBuildPath, "out"),
226-
options: baseOptions,
218+
async spawn(command: string, args: string[], cwd?: string) {
219+
await spawn(command, args, {
220+
outputMode: verbose ? "inherit" : "buffered",
221+
outputPrefix: verbose ? chalk.dim(`[${triplet}] `) : undefined,
222+
cwd,
223+
});
224+
},
227225
};
228226
});
229227

230228
// Configure every triplet project
231229
const tripletsSummary = chalk.dim(
232230
`(${getTripletsSummary(tripletContexts)})`,
233231
);
232+
233+
// Perform configure steps for each platform in sequence
234234
await oraPromise(
235235
Promise.all(
236-
tripletContexts.map(({ platform, ...context }) =>
237-
configureProject(platform, context, baseOptions),
238-
),
236+
platforms.map(async (platform) => {
237+
const relevantTriplets = tripletContexts.filter(({ triplet }) =>
238+
platformHasTriplet(platform, triplet),
239+
);
240+
if (relevantTriplets.length > 0) {
241+
await platform.configure(
242+
relevantTriplets,
243+
baseOptions,
244+
(command, args, cwd) =>
245+
spawn(command, args, {
246+
outputMode: verbose ? "inherit" : "buffered",
247+
outputPrefix: verbose
248+
? chalk.dim(`[${platform.name}] `)
249+
: undefined,
250+
cwd,
251+
}),
252+
);
253+
}
254+
}),
239255
),
240256
{
241257
text: `Configuring projects ${tripletsSummary}`,
@@ -249,13 +265,14 @@ program = program.action(
249265
await oraPromise(
250266
Promise.all(
251267
tripletContexts.map(async ({ platform, ...context }) => {
252-
// Delete any stale build artifacts before building
253-
// This is important, since we might rename the output files
254-
await fs.promises.rm(context.outputPath, {
255-
recursive: true,
256-
force: true,
257-
});
258-
await buildProject(platform, context, baseOptions);
268+
// TODO: Consider if this is still important 😬
269+
// // Delete any stale build artifacts before building
270+
// // This is important, since we might rename the output files
271+
// await fs.promises.rm(context.outputPath, {
272+
// recursive: true,
273+
// force: true,
274+
// });
275+
await platform.build(context, baseOptions);
259276
}),
260277
),
261278
{
@@ -274,13 +291,7 @@ program = program.action(
274291
if (relevantTriplets.length == 0) {
275292
continue;
276293
}
277-
await platform.postBuild(
278-
{
279-
outputPath: out,
280-
triplets: relevantTriplets,
281-
},
282-
baseOptions,
283-
);
294+
await platform.postBuild(out, relevantTriplets, baseOptions);
284295
}
285296
}),
286297
);
@@ -302,92 +313,4 @@ function getTripletsSummary(
302313
.join(" / ");
303314
}
304315

305-
/**
306-
* Namespaces the output path with a triplet name
307-
*/
308-
function getTripletBuildPath(buildPath: string, triplet: unknown) {
309-
assert(typeof triplet === "string", "Expected triplet to be a string");
310-
return path.join(buildPath, triplet.replace(/;/g, "_"));
311-
}
312-
313-
async function configureProject<T extends string>(
314-
platform: Platform<T[], Record<string, unknown>>,
315-
context: TripletContext<T>,
316-
options: BaseOpts,
317-
) {
318-
const { triplet, buildPath, outputPath } = context;
319-
const { verbose, source, weakNodeApiLinkage, cmakeJs } = options;
320-
321-
// TODO: Make the two following definitions a part of the platform definition
322-
323-
const nodeApiDefinitions =
324-
weakNodeApiLinkage && isSupportedTriplet(triplet)
325-
? [getWeakNodeApiVariables(triplet)]
326-
: [];
327-
328-
const cmakeJsDefinitions =
329-
cmakeJs && isSupportedTriplet(triplet)
330-
? [getCmakeJSVariables(triplet)]
331-
: [];
332-
333-
const definitions = [
334-
...nodeApiDefinitions,
335-
...cmakeJsDefinitions,
336-
...options.define,
337-
{ CMAKE_LIBRARY_OUTPUT_DIRECTORY: outputPath },
338-
];
339-
340-
await cmakeFileApi.createSharedStatelessQuery(buildPath, "codemodel", "2");
341-
342-
await spawn(
343-
"cmake",
344-
[
345-
"-S",
346-
source,
347-
"-B",
348-
buildPath,
349-
...platform.configureArgs(context, options),
350-
...toDefineArguments(definitions),
351-
],
352-
{
353-
outputMode: verbose ? "inherit" : "buffered",
354-
outputPrefix: verbose ? chalk.dim(`[${triplet}] `) : undefined,
355-
},
356-
);
357-
}
358-
359-
async function buildProject<T extends string>(
360-
platform: Platform<T[], Record<string, unknown>>,
361-
context: TripletContext<T>,
362-
options: BaseOpts,
363-
) {
364-
const { triplet, buildPath } = context;
365-
const { verbose, configuration } = options;
366-
await spawn(
367-
"cmake",
368-
[
369-
"--build",
370-
buildPath,
371-
"--config",
372-
configuration,
373-
...(options.target.length > 0 ? ["--target", ...options.target] : []),
374-
"--",
375-
...platform.buildArgs(context, options),
376-
],
377-
{
378-
outputMode: verbose ? "inherit" : "buffered",
379-
outputPrefix: verbose ? chalk.dim(`[${triplet}] `) : undefined,
380-
},
381-
);
382-
}
383-
384-
function toDefineArguments(declarations: Array<Record<string, string>>) {
385-
return declarations.flatMap((values) =>
386-
Object.entries(values).flatMap(([key, definition]) => [
387-
"-D",
388-
`${key}=${definition}`,
389-
]),
390-
);
391-
}
392-
393316
export { program };

packages/cmake-rn/src/helpers.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function toDefineArguments(declarations: Array<Record<string, string>>) {
2+
return declarations.flatMap((values) =>
3+
Object.entries(values).flatMap(([key, definition]) => [
4+
"-D",
5+
`${key}=${definition}`,
6+
]),
7+
);
8+
}

0 commit comments

Comments
 (0)