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/tall-snakes-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"cmake-rn": minor
---

Detect ccache and use when building for Android and Apple
42 changes: 35 additions & 7 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: lts/krypton
- name: Setup clang-format
- name: Setup cpp tools
uses: aminya/setup-cpp@v1
with:
clang-format: true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ github.job }}-${{ runner.os }}
# Set up JDK and Android SDK only because we need weak-node-api, to build ferric-example and to run the linting
# TODO: Remove this once we have a way to run linting without building the native code
- name: Set up JDK 17
Expand Down Expand Up @@ -71,10 +75,14 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: lts/krypton
- name: Setup clang-format
- name: Setup cpp tools
uses: aminya/setup-cpp@v1
with:
clang-format: true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ github.job }}-${{ runner.os }}
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
Expand Down Expand Up @@ -104,10 +112,14 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: lts/krypton
- name: Setup clang-format
- name: Setup cpp tools
uses: aminya/setup-cpp@v1
with:
clang-format: true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ github.job }}-${{ runner.os }}
- run: npm ci
- run: npm run build
- name: Prepare weak-node-api
Expand All @@ -127,10 +139,14 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: lts/krypton
- name: Setup clang-format
- name: Setup cpp tools
uses: aminya/setup-cpp@v1
with:
clang-format: true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ github.job }}-${{ runner.os }}
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
Expand Down Expand Up @@ -164,10 +180,14 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: lts/krypton
- name: Setup clang-format
- name: Setup cpp tools
uses: aminya/setup-cpp@v1
with:
clang-format: true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ github.job }}-${{ runner.os }}
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
Expand Down Expand Up @@ -199,10 +219,14 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: lts/krypton
- name: Setup clang-format
- name: Setup cpp tools
uses: aminya/setup-cpp@v1
with:
clang-format: true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ github.job }}-${{ runner.os }}
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
Expand Down Expand Up @@ -283,10 +307,14 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: lts/krypton
- name: Setup clang-format
- name: Setup cpp tools
uses: aminya/setup-cpp@v1
with:
clang-format: true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ github.job }}-${{ runner.os }}
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: lts/krypton
- name: Setup clang-format
- name: Setup cpp tools
uses: aminya/setup-cpp@v1
with:
clang-format: true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ github.job }}-${{ runner.os }}
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
Expand Down
13 changes: 13 additions & 0 deletions packages/cmake-rn/src/ccache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import cp from "node:child_process";

export function getCcachePath(): string | null {
const result = cp.spawnSync(
process.platform === "win32" ? "where" : "which",
["ccache"],
);
if (result.status === 0) {
return result.stdout.toString().trim();
} else {
return null;
}
}
33 changes: 28 additions & 5 deletions packages/cmake-rn/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ import {
platformHasTriplet,
} from "./platforms.js";
import { Platform } from "./platforms/types.js";
import { getCcachePath } from "./ccache.js";

// We're attaching a lot of listeners when spawning in parallel
EventEmitter.defaultMaxListeners = 100;

// TODO: Add automatic ccache support

const verboseOption = new Option(
"--verbose",
"Print more output during the build",
Expand Down Expand Up @@ -125,6 +124,11 @@ const cmakeJsOption = new Option(
"Define CMAKE_JS_* variables used for compatibility with cmake-js",
).default(false);

const ccachePathOption = new Option(
"--ccache-path <path>",
"Specify the path to the ccache executable",
).default(getCcachePath());

let program = new Command("cmake-rn")
.description("Build React Native Node API modules with CMake")
.addOption(tripletOption)
Expand All @@ -139,7 +143,8 @@ let program = new Command("cmake-rn")
.addOption(stripOption)
.addOption(noAutoLinkOption)
.addOption(noWeakNodeApiLinkageOption)
.addOption(cmakeJsOption);
.addOption(cmakeJsOption)
.addOption(ccachePathOption);

for (const platform of platforms) {
const allOption = new Option(
Expand Down Expand Up @@ -169,7 +174,14 @@ program = program.action(
process.cwd(),
expandTemplate(baseOptions.out, baseOptions),
);
const { verbose, clean, source, out, build: buildPath } = baseOptions;
const {
verbose,
clean,
source,
out,
build: buildPath,
ccachePath,
} = baseOptions;

assertFixable(
fs.existsSync(path.join(source, "CMakeLists.txt")),
Expand All @@ -179,6 +191,10 @@ program = program.action(
},
);

if (ccachePath) {
console.log(`♻️ Using ccache: ${chalk.dim(ccachePath)}`);
}

if (clean) {
await fs.promises.rm(buildPath, { recursive: true, force: true });
}
Expand Down Expand Up @@ -228,9 +244,16 @@ program = program.action(
triplet,
platform,
async spawn(command: string, args: string[], cwd?: string) {
const outputPrefix = verbose ? chalk.dim(`[${triplet}] `) : undefined;
if (verbose) {
console.log(
`${outputPrefix}» ${command} ${args.map((arg) => chalk.dim(`${arg}`)).join(" ")}`,
cwd ? `(in ${chalk.dim(cwd)})` : "",
);
}
await spawn(command, args, {
outputMode: verbose ? "inherit" : "buffered",
outputPrefix: verbose ? chalk.dim(`[${triplet}] `) : undefined,
outputPrefix,
cwd,
});
},
Expand Down
11 changes: 6 additions & 5 deletions packages/cmake-rn/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export function toDefineArguments(declarations: Array<Record<string, string>>) {
export function toDefineArguments(
declarations: Array<Record<string, string | undefined>>,
) {
return declarations.flatMap((values) =>
Object.entries(values).flatMap(([key, definition]) => [
"-D",
`${key}=${definition}`,
]),
Object.entries(values)
.filter(([, definition]) => definition)
.flatMap(([key, definition]) => ["-D", `${key}=${definition}`]),
);
}
11 changes: 7 additions & 4 deletions packages/cmake-rn/src/platforms/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export const platform: Platform<Triplet[], AndroidOpts> = {
build,
weakNodeApiLinkage,
cmakeJs,
ccachePath,
},
) {
const ndkPath = getNdkPath(ndkVersion);
Expand All @@ -145,16 +146,18 @@ export const platform: Platform<Triplet[], AndroidOpts> = {
CMAKE_SYSTEM_NAME: "Android",
// "CMAKE_INSTALL_PREFIX": installPath,
CMAKE_MAKE_PROGRAM: "ninja",
// "-D",
// "CMAKE_C_COMPILER_LAUNCHER=ccache",
// "-D",
// "CMAKE_CXX_COMPILER_LAUNCHER=ccache",
ANDROID_NDK: ndkPath,
ANDROID_TOOLCHAIN: "clang",
ANDROID_PLATFORM: androidSdkVersion,
// TODO: Make this configurable
ANDROID_STL: "c++_shared",
},
ccachePath
? {
CMAKE_C_COMPILER_LAUNCHER: ccachePath,
CMAKE_CXX_COMPILER_LAUNCHER: ccachePath,
}
: {},
];

await Promise.all(
Expand Down
48 changes: 41 additions & 7 deletions packages/cmake-rn/src/platforms/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,17 @@ async function readCmakeSharedLibraryTarget(
return sharedLibrary;
}

async function getCompilerPath(
name: "clang" | "clang++",
{ buildBinPath, ccachePath }: { buildBinPath: string; ccachePath: string },
) {
const result = path.join(buildBinPath, name);
if (!fs.existsSync(result)) {
await fs.promises.symlink(ccachePath, result);
}
return result;
}

export const platform: Platform<Triplet[], AppleOpts> = {
id: "apple",
name: "Apple",
Expand Down Expand Up @@ -245,16 +256,40 @@ export const platform: Platform<Triplet[], AppleOpts> = {
},
async configure(
triplets,
{ source, build, define, weakNodeApiLinkage, cmakeJs },
spawn,
{ source, build, define, weakNodeApiLinkage, cmakeJs, ccachePath },
) {
// When using ccache, we're creating symlinks for the clang and clang++ binaries to the ccache binary
// This is needed for ccache to understand it's being invoked as clang and clang++ respectively.
const buildBinPath = path.join(build, "bin");
await fs.promises.mkdir(buildBinPath, { recursive: true });
const compilerDefinitions = ccachePath
? {
CMAKE_XCODE_ATTRIBUTE_CC: await getCompilerPath("clang", {
buildBinPath,
ccachePath,
}),
CMAKE_XCODE_ATTRIBUTE_CXX: await getCompilerPath("clang++", {
buildBinPath,
ccachePath,
}),
CMAKE_XCODE_ATTRIBUTE_LD: await getCompilerPath("clang", {
buildBinPath,
ccachePath,
}),
CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS: await getCompilerPath("clang++", {
buildBinPath,
ccachePath,
}),
}
: {};

// Ideally, we would generate a single Xcode project supporting all architectures / platforms
// However, CMake's Xcode generator does not support that well, so we generate one project per triplet
// Specifically, the linking of weak-node-api breaks, since the sdk / arch specific framework
// from the xcframework is picked at configure time, not at build time.
// See https://gitlab.kitware.com/cmake/cmake/-/issues/21752#note_1717047 for more information.
await Promise.all(
triplets.map(async ({ triplet }) => {
triplets.map(async ({ triplet, spawn }) => {
const buildPath = getBuildPath(build, triplet);
// We want to use the CMake File API to query information later
// TODO: Or do we?
Expand All @@ -272,16 +307,15 @@ export const platform: Platform<Triplet[], AppleOpts> = {
"Xcode",
...toDefineArguments([
...define,
...(weakNodeApiLinkage ? [getWeakNodeApiVariables("apple")] : []),
...(cmakeJs ? [getCmakeJSVariables("apple")] : []),
weakNodeApiLinkage ? getWeakNodeApiVariables("apple") : {},
cmakeJs ? getCmakeJSVariables("apple") : {},
compilerDefinitions,
{
CMAKE_SYSTEM_NAME: CMAKE_SYSTEM_NAMES[triplet],
CMAKE_OSX_SYSROOT: XCODE_SDK_NAMES[triplet],
CMAKE_OSX_ARCHITECTURES: APPLE_ARCHITECTURES[triplet],
// Passing a linker flag to increase the header pad size to allow renaming the install name when linking it into the app.
CMAKE_SHARED_LINKER_FLAGS: "-Wl,-headerpad_max_install_names",
},
{
// Setting the output directories works around an issue with Xcode generator
// where an unexpanded variable would emitted in the artifact paths.
// This is okay, since we're generating per triplet build directories anyway.
Expand Down