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
5 changes: 5 additions & 0 deletions .changeset/evil-vans-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"cmake-rn": patch
---

Use CMake file API to read shared library target paths
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/cmake-rn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
},
"dependencies": {
"@react-native-node-api/cli-utils": "0.1.0",
"cmake-file-api": "0.1.0",
"react-native-node-api": "0.5.1"
},
"peerDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions packages/cmake-rn/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
wrapAction,
} from "@react-native-node-api/cli-utils";
import { isSupportedTriplet } from "react-native-node-api";
import * as cmakeFileApi from "cmake-file-api";

import {
getCmakeJSVariables,
Expand Down Expand Up @@ -327,6 +328,8 @@ async function configureProject<T extends string>(
{ CMAKE_LIBRARY_OUTPUT_DIRECTORY: outputPath },
];

await cmakeFileApi.createSharedStatelessQuery(buildPath, "codemodel", "2");

await spawn(
"cmake",
[
Expand Down
104 changes: 59 additions & 45 deletions packages/cmake-rn/src/platforms/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import path from "node:path";
import { Option, oraPromise, chalk } from "@react-native-node-api/cli-utils";
import {
createAndroidLibsDirectory,
determineAndroidLibsFilename,
AndroidTriplet as Triplet,
} from "react-native-node-api";
import * as cmakeFileApi from "cmake-file-api";

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

Expand Down Expand Up @@ -121,50 +121,64 @@ export const platform: Platform<Triplet[], AndroidOpts> = {
const { ANDROID_HOME } = process.env;
return typeof ANDROID_HOME === "string" && fs.existsSync(ANDROID_HOME);
},
async postBuild({ outputPath, triplets }, { autoLink }) {
// TODO: Include `configuration` in the output path
const libraryPathByTriplet = Object.fromEntries(
await Promise.all(
triplets.map(async ({ triplet, outputPath }) => {
assert(
fs.existsSync(outputPath),
`Expected a directory at ${outputPath}`,
);
// Expect binary file(s), either .node or .so
const dirents = await fs.promises.readdir(outputPath, {
withFileTypes: true,
});
const result = dirents
.filter(
(dirent) =>
dirent.isFile() &&
(dirent.name.endsWith(".so") || dirent.name.endsWith(".node")),
)
.map((dirent) => path.join(dirent.parentPath, dirent.name));
assert.equal(result.length, 1, "Expected exactly one library file");
return [triplet, result[0]] as const;
}),
),
) as Record<Triplet, string>;
const androidLibsFilename = determineAndroidLibsFilename(
Object.values(libraryPathByTriplet),
);
const androidLibsOutputPath = path.resolve(outputPath, androidLibsFilename);
async postBuild({ outputPath, triplets }, { autoLink, configuration }) {
const prebuilds: Record<
string,
{ triplet: Triplet; libraryPath: string }[]
> = {};

await oraPromise(
createAndroidLibsDirectory({
outputPath: androidLibsOutputPath,
libraryPathByTriplet,
autoLink,
}),
{
text: "Assembling Android libs directory",
successText: `Android libs directory assembled into ${chalk.dim(
path.relative(process.cwd(), androidLibsOutputPath),
)}`,
failText: ({ message }) =>
`Failed to assemble Android libs directory: ${message}`,
},
);
for (const { triplet, buildPath } of triplets) {
assert(fs.existsSync(buildPath), `Expected a directory at ${buildPath}`);
const targets = await cmakeFileApi.readCurrentTargetsDeep(
buildPath,
configuration,
"2.0",
);
const sharedLibraries = targets.filter(
(target) => target.type === "SHARED_LIBRARY",
);
assert.equal(
sharedLibraries.length,
1,
"Expected exactly one shared library",
);
const [sharedLibrary] = sharedLibraries;
const { artifacts } = sharedLibrary;
assert(
artifacts && artifacts.length === 1,
"Expected exactly one artifact",
);
const [artifact] = artifacts;
// Add prebuild entry, creating a new entry if needed
if (!(sharedLibrary.name in prebuilds)) {
prebuilds[sharedLibrary.name] = [];
}
prebuilds[sharedLibrary.name].push({
triplet,
libraryPath: path.join(buildPath, artifact.path),
});
}

for (const [libraryName, libraries] of Object.entries(prebuilds)) {
const prebuildOutputPath = path.resolve(
outputPath,
`${libraryName}.android.node`,
);
await oraPromise(
createAndroidLibsDirectory({
outputPath: prebuildOutputPath,
libraries,
autoLink,
}),
{
text: `Assembling Android libs directory (${libraryName})`,
successText: `Android libs directory (${libraryName}) assembled into ${chalk.dim(
path.relative(process.cwd(), prebuildOutputPath),
)}`,
failText: ({ message }) =>
`Failed to assemble Android libs directory (${libraryName}): ${message}`,
},
);
}
},
};
111 changes: 59 additions & 52 deletions packages/cmake-rn/src/platforms/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {
AppleTriplet as Triplet,
createAppleFramework,
createXCframework,
determineXCFrameworkFilename,
} from "react-native-node-api";

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

type XcodeSDKName =
| "iphoneos"
Expand Down Expand Up @@ -135,56 +135,63 @@ export const platform: Platform<Triplet[], AppleOpts> = {
{ outputPath, triplets },
{ configuration, autoLink, xcframeworkExtension },
) {
const libraryPaths = await Promise.all(
triplets.map(async ({ outputPath }) => {
const configSpecificPath = path.join(outputPath, configuration);
assert(
fs.existsSync(configSpecificPath),
`Expected a directory at ${configSpecificPath}`,
);
// Expect binary file(s), either .node or .dylib
const files = await fs.promises.readdir(configSpecificPath);
const result = files.map(async (file) => {
const filePath = path.join(configSpecificPath, file);
if (filePath.endsWith(".dylib")) {
return filePath;
} else if (file.endsWith(".node")) {
// Rename the file to .dylib for xcodebuild to accept it
const newFilePath = filePath.replace(/\.node$/, ".dylib");
await fs.promises.rename(filePath, newFilePath);
return newFilePath;
} else {
throw new Error(
`Expected a .node or .dylib file, but found ${file}`,
);
}
});
assert.equal(result.length, 1, "Expected exactly one library file");
return await result[0];
}),
);
const frameworkPaths = libraryPaths.map(createAppleFramework);
const xcframeworkFilename = determineXCFrameworkFilename(
frameworkPaths,
xcframeworkExtension ? ".xcframework" : ".apple.node",
);

// Create the xcframework
const xcframeworkOutputPath = path.resolve(outputPath, xcframeworkFilename);

await oraPromise(
createXCframework({
outputPath: xcframeworkOutputPath,
frameworkPaths,
autoLink,
}),
{
text: "Assembling XCFramework",
successText: `XCFramework assembled into ${chalk.dim(
path.relative(process.cwd(), xcframeworkOutputPath),
)}`,
failText: ({ message }) => `Failed to assemble XCFramework: ${message}`,
},
);
const prebuilds: Record<string, string[]> = {};
for (const { buildPath } of triplets) {
assert(fs.existsSync(buildPath), `Expected a directory at ${buildPath}`);
const targets = await cmakeFileApi.readCurrentTargetsDeep(
buildPath,
configuration,
"2.0",
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: CMake API Version Mismatch

The CMake file API query uses version "2", but readCurrentTargetsDeep reads targets with version "2.0". This version string inconsistency can cause the read operation to fail or return unexpected results.

Additional Locations (1)

Fix in Cursor Fix in Web

const sharedLibraries = targets.filter(
(target) => target.type === "SHARED_LIBRARY",
);
assert.equal(
sharedLibraries.length,
1,
"Expected exactly one shared library",
);
const [sharedLibrary] = sharedLibraries;
const { artifacts } = sharedLibrary;
assert(
artifacts && artifacts.length === 1,
"Expected exactly one artifact",
);
const [artifact] = artifacts;
// Add prebuild entry, creating a new entry if needed
if (!(sharedLibrary.name in prebuilds)) {
prebuilds[sharedLibrary.name] = [];
}
prebuilds[sharedLibrary.name].push(path.join(buildPath, artifact.path));
}

const extension = xcframeworkExtension ? ".xcframework" : ".apple.node";

for (const [libraryName, libraryPaths] of Object.entries(prebuilds)) {
const frameworkPaths = await Promise.all(
libraryPaths.map(createAppleFramework),
);
// Create the xcframework
const xcframeworkOutputPath = path.resolve(
outputPath,
`${libraryName}${extension}`,
);

await oraPromise(
createXCframework({
outputPath: xcframeworkOutputPath,
frameworkPaths,
autoLink,
}),
{
text: `Assembling XCFramework (${libraryName})`,
successText: `XCFramework (${libraryName}) assembled into ${chalk.dim(
path.relative(process.cwd(), xcframeworkOutputPath),
)}`,
failText: ({ message }) =>
`Failed to assemble XCFramework (${libraryName}): ${message}`,
},
);
}
},
};
18 changes: 9 additions & 9 deletions packages/ferric/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,15 +204,13 @@ export const buildCommand = new Command("build")
);

if (androidLibraries.length > 0) {
const libraryPathByTriplet = Object.fromEntries(
androidLibraries.map(([target, outputPath]) => [
ANDROID_TRIPLET_PER_TARGET[target],
outputPath,
]),
) as Record<AndroidTriplet, string>;
const libraries = androidLibraries.map(([target, outputPath]) => ({
triplet: ANDROID_TRIPLET_PER_TARGET[target],
libraryPath: outputPath,
}));

const androidLibsFilename = determineAndroidLibsFilename(
Object.values(libraryPathByTriplet),
libraries.map(({ libraryPath }) => libraryPath),
);
const androidLibsOutputPath = path.resolve(
outputPath,
Expand All @@ -222,7 +220,7 @@ export const buildCommand = new Command("build")
await oraPromise(
createAndroidLibsDirectory({
outputPath: androidLibsOutputPath,
libraryPathByTriplet,
libraries,
autoLink: true,
}),
{
Expand All @@ -238,7 +236,9 @@ export const buildCommand = new Command("build")

if (appleLibraries.length > 0) {
const libraryPaths = await combineLibraries(appleLibraries);
const frameworkPaths = libraryPaths.map(createAppleFramework);
const frameworkPaths = await Promise.all(
libraryPaths.map(createAppleFramework),
);
const xcframeworkFilename = determineXCFrameworkFilename(
frameworkPaths,
xcframeworkExtension ? ".xcframework" : ".apple.node",
Expand Down
8 changes: 4 additions & 4 deletions packages/host/src/node/prebuilds/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,24 @@ export function determineAndroidLibsFilename(libraryPaths: string[]) {

type AndroidLibsDirectoryOptions = {
outputPath: string;
libraryPathByTriplet: Record<AndroidTriplet, string>;
libraries: { triplet: AndroidTriplet; libraryPath: string }[];
autoLink: boolean;
};

export async function createAndroidLibsDirectory({
outputPath,
libraryPathByTriplet,
libraries,
autoLink,
}: AndroidLibsDirectoryOptions) {
// Delete and recreate any existing output directory
await fs.promises.rm(outputPath, { recursive: true, force: true });
await fs.promises.mkdir(outputPath, { recursive: true });
for (const [triplet, libraryPath] of Object.entries(libraryPathByTriplet)) {
for (const { triplet, libraryPath } of libraries) {
assert(
fs.existsSync(libraryPath),
`Library not found: ${libraryPath} for triplet ${triplet}`,
);
const arch = ANDROID_ARCHITECTURES[triplet as AndroidTriplet];
const arch = ANDROID_ARCHITECTURES[triplet];
const archOutputPath = path.join(outputPath, arch);
await fs.promises.mkdir(archOutputPath, { recursive: true });
// Strip the ".node" extension from the library name
Expand Down
Loading