Skip to content

Commit a23af5a

Browse files
authored
Use CMake file API to read shared library target paths (#254)
* Use CMake file API to read shared library target paths * Refactor createAppleFramework into an async function * Correcting createAndroidLibsDirectory types * Support multiple shared library outputs * Fix precondition for exactly one artifact * Take array of triplet + path when creating Android prebuilds * Use bufout spawn in createAppleFramework
1 parent 9c6d606 commit a23af5a

File tree

9 files changed

+154
-122
lines changed

9 files changed

+154
-122
lines changed

.changeset/evil-vans-love.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"cmake-rn": patch
3+
---
4+
5+
Use CMake file API to read shared library target paths

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cmake-rn/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
},
2424
"dependencies": {
2525
"@react-native-node-api/cli-utils": "0.1.0",
26+
"cmake-file-api": "0.1.0",
2627
"react-native-node-api": "0.5.1"
2728
},
2829
"peerDependencies": {

packages/cmake-rn/src/cli.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
wrapAction,
1414
} from "@react-native-node-api/cli-utils";
1515
import { isSupportedTriplet } from "react-native-node-api";
16+
import * as cmakeFileApi from "cmake-file-api";
1617

1718
import {
1819
getCmakeJSVariables,
@@ -327,6 +328,8 @@ async function configureProject<T extends string>(
327328
{ CMAKE_LIBRARY_OUTPUT_DIRECTORY: outputPath },
328329
];
329330

331+
await cmakeFileApi.createSharedStatelessQuery(buildPath, "codemodel", "2");
332+
330333
await spawn(
331334
"cmake",
332335
[

packages/cmake-rn/src/platforms/android.ts

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import path from "node:path";
55
import { Option, oraPromise, chalk } from "@react-native-node-api/cli-utils";
66
import {
77
createAndroidLibsDirectory,
8-
determineAndroidLibsFilename,
98
AndroidTriplet as Triplet,
109
} from "react-native-node-api";
10+
import * as cmakeFileApi from "cmake-file-api";
1111

1212
import type { Platform } from "./types.js";
1313

@@ -121,50 +121,64 @@ export const platform: Platform<Triplet[], AndroidOpts> = {
121121
const { ANDROID_HOME } = process.env;
122122
return typeof ANDROID_HOME === "string" && fs.existsSync(ANDROID_HOME);
123123
},
124-
async postBuild({ outputPath, triplets }, { autoLink }) {
125-
// TODO: Include `configuration` in the output path
126-
const libraryPathByTriplet = Object.fromEntries(
127-
await Promise.all(
128-
triplets.map(async ({ triplet, outputPath }) => {
129-
assert(
130-
fs.existsSync(outputPath),
131-
`Expected a directory at ${outputPath}`,
132-
);
133-
// Expect binary file(s), either .node or .so
134-
const dirents = await fs.promises.readdir(outputPath, {
135-
withFileTypes: true,
136-
});
137-
const result = dirents
138-
.filter(
139-
(dirent) =>
140-
dirent.isFile() &&
141-
(dirent.name.endsWith(".so") || dirent.name.endsWith(".node")),
142-
)
143-
.map((dirent) => path.join(dirent.parentPath, dirent.name));
144-
assert.equal(result.length, 1, "Expected exactly one library file");
145-
return [triplet, result[0]] as const;
146-
}),
147-
),
148-
) as Record<Triplet, string>;
149-
const androidLibsFilename = determineAndroidLibsFilename(
150-
Object.values(libraryPathByTriplet),
151-
);
152-
const androidLibsOutputPath = path.resolve(outputPath, androidLibsFilename);
124+
async postBuild({ outputPath, triplets }, { autoLink, configuration }) {
125+
const prebuilds: Record<
126+
string,
127+
{ triplet: Triplet; libraryPath: string }[]
128+
> = {};
153129

154-
await oraPromise(
155-
createAndroidLibsDirectory({
156-
outputPath: androidLibsOutputPath,
157-
libraryPathByTriplet,
158-
autoLink,
159-
}),
160-
{
161-
text: "Assembling Android libs directory",
162-
successText: `Android libs directory assembled into ${chalk.dim(
163-
path.relative(process.cwd(), androidLibsOutputPath),
164-
)}`,
165-
failText: ({ message }) =>
166-
`Failed to assemble Android libs directory: ${message}`,
167-
},
168-
);
130+
for (const { triplet, buildPath } of triplets) {
131+
assert(fs.existsSync(buildPath), `Expected a directory at ${buildPath}`);
132+
const targets = await cmakeFileApi.readCurrentTargetsDeep(
133+
buildPath,
134+
configuration,
135+
"2.0",
136+
);
137+
const sharedLibraries = targets.filter(
138+
(target) => target.type === "SHARED_LIBRARY",
139+
);
140+
assert.equal(
141+
sharedLibraries.length,
142+
1,
143+
"Expected exactly one shared library",
144+
);
145+
const [sharedLibrary] = sharedLibraries;
146+
const { artifacts } = sharedLibrary;
147+
assert(
148+
artifacts && artifacts.length === 1,
149+
"Expected exactly one artifact",
150+
);
151+
const [artifact] = artifacts;
152+
// Add prebuild entry, creating a new entry if needed
153+
if (!(sharedLibrary.name in prebuilds)) {
154+
prebuilds[sharedLibrary.name] = [];
155+
}
156+
prebuilds[sharedLibrary.name].push({
157+
triplet,
158+
libraryPath: path.join(buildPath, artifact.path),
159+
});
160+
}
161+
162+
for (const [libraryName, libraries] of Object.entries(prebuilds)) {
163+
const prebuildOutputPath = path.resolve(
164+
outputPath,
165+
`${libraryName}.android.node`,
166+
);
167+
await oraPromise(
168+
createAndroidLibsDirectory({
169+
outputPath: prebuildOutputPath,
170+
libraries,
171+
autoLink,
172+
}),
173+
{
174+
text: `Assembling Android libs directory (${libraryName})`,
175+
successText: `Android libs directory (${libraryName}) assembled into ${chalk.dim(
176+
path.relative(process.cwd(), prebuildOutputPath),
177+
)}`,
178+
failText: ({ message }) =>
179+
`Failed to assemble Android libs directory (${libraryName}): ${message}`,
180+
},
181+
);
182+
}
169183
},
170184
};

packages/cmake-rn/src/platforms/apple.ts

Lines changed: 59 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import {
77
AppleTriplet as Triplet,
88
createAppleFramework,
99
createXCframework,
10-
determineXCFrameworkFilename,
1110
} from "react-native-node-api";
1211

1312
import type { Platform } from "./types.js";
13+
import * as cmakeFileApi from "cmake-file-api";
1414

1515
type XcodeSDKName =
1616
| "iphoneos"
@@ -135,56 +135,63 @@ export const platform: Platform<Triplet[], AppleOpts> = {
135135
{ outputPath, triplets },
136136
{ configuration, autoLink, xcframeworkExtension },
137137
) {
138-
const libraryPaths = await Promise.all(
139-
triplets.map(async ({ outputPath }) => {
140-
const configSpecificPath = path.join(outputPath, configuration);
141-
assert(
142-
fs.existsSync(configSpecificPath),
143-
`Expected a directory at ${configSpecificPath}`,
144-
);
145-
// Expect binary file(s), either .node or .dylib
146-
const files = await fs.promises.readdir(configSpecificPath);
147-
const result = files.map(async (file) => {
148-
const filePath = path.join(configSpecificPath, file);
149-
if (filePath.endsWith(".dylib")) {
150-
return filePath;
151-
} else if (file.endsWith(".node")) {
152-
// Rename the file to .dylib for xcodebuild to accept it
153-
const newFilePath = filePath.replace(/\.node$/, ".dylib");
154-
await fs.promises.rename(filePath, newFilePath);
155-
return newFilePath;
156-
} else {
157-
throw new Error(
158-
`Expected a .node or .dylib file, but found ${file}`,
159-
);
160-
}
161-
});
162-
assert.equal(result.length, 1, "Expected exactly one library file");
163-
return await result[0];
164-
}),
165-
);
166-
const frameworkPaths = libraryPaths.map(createAppleFramework);
167-
const xcframeworkFilename = determineXCFrameworkFilename(
168-
frameworkPaths,
169-
xcframeworkExtension ? ".xcframework" : ".apple.node",
170-
);
171-
172-
// Create the xcframework
173-
const xcframeworkOutputPath = path.resolve(outputPath, xcframeworkFilename);
174-
175-
await oraPromise(
176-
createXCframework({
177-
outputPath: xcframeworkOutputPath,
178-
frameworkPaths,
179-
autoLink,
180-
}),
181-
{
182-
text: "Assembling XCFramework",
183-
successText: `XCFramework assembled into ${chalk.dim(
184-
path.relative(process.cwd(), xcframeworkOutputPath),
185-
)}`,
186-
failText: ({ message }) => `Failed to assemble XCFramework: ${message}`,
187-
},
188-
);
138+
const prebuilds: Record<string, string[]> = {};
139+
for (const { buildPath } of triplets) {
140+
assert(fs.existsSync(buildPath), `Expected a directory at ${buildPath}`);
141+
const targets = await cmakeFileApi.readCurrentTargetsDeep(
142+
buildPath,
143+
configuration,
144+
"2.0",
145+
);
146+
const sharedLibraries = targets.filter(
147+
(target) => target.type === "SHARED_LIBRARY",
148+
);
149+
assert.equal(
150+
sharedLibraries.length,
151+
1,
152+
"Expected exactly one shared library",
153+
);
154+
const [sharedLibrary] = sharedLibraries;
155+
const { artifacts } = sharedLibrary;
156+
assert(
157+
artifacts && artifacts.length === 1,
158+
"Expected exactly one artifact",
159+
);
160+
const [artifact] = artifacts;
161+
// Add prebuild entry, creating a new entry if needed
162+
if (!(sharedLibrary.name in prebuilds)) {
163+
prebuilds[sharedLibrary.name] = [];
164+
}
165+
prebuilds[sharedLibrary.name].push(path.join(buildPath, artifact.path));
166+
}
167+
168+
const extension = xcframeworkExtension ? ".xcframework" : ".apple.node";
169+
170+
for (const [libraryName, libraryPaths] of Object.entries(prebuilds)) {
171+
const frameworkPaths = await Promise.all(
172+
libraryPaths.map(createAppleFramework),
173+
);
174+
// Create the xcframework
175+
const xcframeworkOutputPath = path.resolve(
176+
outputPath,
177+
`${libraryName}${extension}`,
178+
);
179+
180+
await oraPromise(
181+
createXCframework({
182+
outputPath: xcframeworkOutputPath,
183+
frameworkPaths,
184+
autoLink,
185+
}),
186+
{
187+
text: `Assembling XCFramework (${libraryName})`,
188+
successText: `XCFramework (${libraryName}) assembled into ${chalk.dim(
189+
path.relative(process.cwd(), xcframeworkOutputPath),
190+
)}`,
191+
failText: ({ message }) =>
192+
`Failed to assemble XCFramework (${libraryName}): ${message}`,
193+
},
194+
);
195+
}
189196
},
190197
};

packages/ferric/src/build.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -204,15 +204,13 @@ export const buildCommand = new Command("build")
204204
);
205205

206206
if (androidLibraries.length > 0) {
207-
const libraryPathByTriplet = Object.fromEntries(
208-
androidLibraries.map(([target, outputPath]) => [
209-
ANDROID_TRIPLET_PER_TARGET[target],
210-
outputPath,
211-
]),
212-
) as Record<AndroidTriplet, string>;
207+
const libraries = androidLibraries.map(([target, outputPath]) => ({
208+
triplet: ANDROID_TRIPLET_PER_TARGET[target],
209+
libraryPath: outputPath,
210+
}));
213211

214212
const androidLibsFilename = determineAndroidLibsFilename(
215-
Object.values(libraryPathByTriplet),
213+
libraries.map(({ libraryPath }) => libraryPath),
216214
);
217215
const androidLibsOutputPath = path.resolve(
218216
outputPath,
@@ -222,7 +220,7 @@ export const buildCommand = new Command("build")
222220
await oraPromise(
223221
createAndroidLibsDirectory({
224222
outputPath: androidLibsOutputPath,
225-
libraryPathByTriplet,
223+
libraries,
226224
autoLink: true,
227225
}),
228226
{
@@ -238,7 +236,9 @@ export const buildCommand = new Command("build")
238236

239237
if (appleLibraries.length > 0) {
240238
const libraryPaths = await combineLibraries(appleLibraries);
241-
const frameworkPaths = libraryPaths.map(createAppleFramework);
239+
const frameworkPaths = await Promise.all(
240+
libraryPaths.map(createAppleFramework),
241+
);
242242
const xcframeworkFilename = determineXCFrameworkFilename(
243243
frameworkPaths,
244244
xcframeworkExtension ? ".xcframework" : ".apple.node",

packages/host/src/node/prebuilds/android.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,24 @@ export function determineAndroidLibsFilename(libraryPaths: string[]) {
3232

3333
type AndroidLibsDirectoryOptions = {
3434
outputPath: string;
35-
libraryPathByTriplet: Record<AndroidTriplet, string>;
35+
libraries: { triplet: AndroidTriplet; libraryPath: string }[];
3636
autoLink: boolean;
3737
};
3838

3939
export async function createAndroidLibsDirectory({
4040
outputPath,
41-
libraryPathByTriplet,
41+
libraries,
4242
autoLink,
4343
}: AndroidLibsDirectoryOptions) {
4444
// Delete and recreate any existing output directory
4545
await fs.promises.rm(outputPath, { recursive: true, force: true });
4646
await fs.promises.mkdir(outputPath, { recursive: true });
47-
for (const [triplet, libraryPath] of Object.entries(libraryPathByTriplet)) {
47+
for (const { triplet, libraryPath } of libraries) {
4848
assert(
4949
fs.existsSync(libraryPath),
5050
`Library not found: ${libraryPath} for triplet ${triplet}`,
5151
);
52-
const arch = ANDROID_ARCHITECTURES[triplet as AndroidTriplet];
52+
const arch = ANDROID_ARCHITECTURES[triplet];
5353
const archOutputPath = path.join(outputPath, arch);
5454
await fs.promises.mkdir(archOutputPath, { recursive: true });
5555
// Strip the ".node" extension from the library name

0 commit comments

Comments
 (0)