Skip to content

Commit e5bfb82

Browse files
committed
Detect ccache and use when building for Android and Apple
1 parent ea84c71 commit e5bfb82

File tree

6 files changed

+97
-20
lines changed

6 files changed

+97
-20
lines changed

.changeset/tall-snakes-beg.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"cmake-rn": minor
3+
---
4+
5+
Detect ccache and use when building for Android and Apple

packages/cmake-rn/src/ccache.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import assert from "node:assert";
2+
import cp from "node:child_process";
3+
4+
export function getCcachePath(): string | null {
5+
const result = cp.spawnSync("which", ["ccache"]);
6+
if (result.status === 0) {
7+
return result.stdout.toString().trim();
8+
} else {
9+
return null;
10+
}
11+
}
12+
13+
export function getCmakeVersion(ccachePath: string): string {
14+
const result = cp.spawnSync(ccachePath, ["--print-version"]);
15+
assert.equal(result.status, 0, "Failed to get ccache version");
16+
return result.stdout.toString().trim();
17+
}

packages/cmake-rn/src/cli.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@ import {
2020
platformHasTriplet,
2121
} from "./platforms.js";
2222
import { Platform } from "./platforms/types.js";
23+
import { getCcachePath, getCmakeVersion } from "./ccache.js";
2324

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

27-
// TODO: Add automatic ccache support
28-
2928
const verboseOption = new Option(
3029
"--verbose",
3130
"Print more output during the build",
@@ -125,6 +124,11 @@ const cmakeJsOption = new Option(
125124
"Define CMAKE_JS_* variables used for compatibility with cmake-js",
126125
).default(false);
127126

127+
const ccachePathOption = new Option(
128+
"--ccache-path <path>",
129+
"Specify the path to the ccache executable",
130+
).default(getCcachePath());
131+
128132
let program = new Command("cmake-rn")
129133
.description("Build React Native Node API modules with CMake")
130134
.addOption(tripletOption)
@@ -139,7 +143,8 @@ let program = new Command("cmake-rn")
139143
.addOption(stripOption)
140144
.addOption(noAutoLinkOption)
141145
.addOption(noWeakNodeApiLinkageOption)
142-
.addOption(cmakeJsOption);
146+
.addOption(cmakeJsOption)
147+
.addOption(ccachePathOption);
143148

144149
for (const platform of platforms) {
145150
const allOption = new Option(
@@ -169,7 +174,14 @@ program = program.action(
169174
process.cwd(),
170175
expandTemplate(baseOptions.out, baseOptions),
171176
);
172-
const { verbose, clean, source, out, build: buildPath } = baseOptions;
177+
const {
178+
verbose,
179+
clean,
180+
source,
181+
out,
182+
build: buildPath,
183+
ccachePath,
184+
} = baseOptions;
173185

174186
assertFixable(
175187
fs.existsSync(path.join(source, "CMakeLists.txt")),
@@ -179,6 +191,11 @@ program = program.action(
179191
},
180192
);
181193

194+
if (ccachePath) {
195+
const cmakeVersion = getCmakeVersion(ccachePath);
196+
console.log("♻️ Using ccache version", cmakeVersion);
197+
}
198+
182199
if (clean) {
183200
await fs.promises.rm(buildPath, { recursive: true, force: true });
184201
}

packages/cmake-rn/src/helpers.ts

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

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export const platform: Platform<Triplet[], AndroidOpts> = {
133133
build,
134134
weakNodeApiLinkage,
135135
cmakeJs,
136+
ccachePath,
136137
},
137138
) {
138139
const ndkPath = getNdkPath(ndkVersion);
@@ -145,16 +146,18 @@ export const platform: Platform<Triplet[], AndroidOpts> = {
145146
CMAKE_SYSTEM_NAME: "Android",
146147
// "CMAKE_INSTALL_PREFIX": installPath,
147148
CMAKE_MAKE_PROGRAM: "ninja",
148-
// "-D",
149-
// "CMAKE_C_COMPILER_LAUNCHER=ccache",
150-
// "-D",
151-
// "CMAKE_CXX_COMPILER_LAUNCHER=ccache",
152149
ANDROID_NDK: ndkPath,
153150
ANDROID_TOOLCHAIN: "clang",
154151
ANDROID_PLATFORM: androidSdkVersion,
155152
// TODO: Make this configurable
156153
ANDROID_STL: "c++_shared",
157154
},
155+
ccachePath
156+
? {
157+
CMAKE_C_COMPILER_LAUNCHER: ccachePath,
158+
CMAKE_CXX_COMPILER_LAUNCHER: ccachePath,
159+
}
160+
: {},
158161
];
159162

160163
await Promise.all(

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

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,17 @@ async function readCmakeSharedLibraryTarget(
193193
return sharedLibrary;
194194
}
195195

196+
async function getCompilerPath(
197+
name: "clang" | "clang++",
198+
{ buildBinPath, ccachePath }: { buildBinPath: string; ccachePath: string },
199+
) {
200+
const result = path.join(buildBinPath, name);
201+
if (!fs.existsSync(result)) {
202+
await fs.promises.symlink(ccachePath, result);
203+
}
204+
return result;
205+
}
206+
196207
export const platform: Platform<Triplet[], AppleOpts> = {
197208
id: "apple",
198209
name: "Apple",
@@ -245,16 +256,40 @@ export const platform: Platform<Triplet[], AppleOpts> = {
245256
},
246257
async configure(
247258
triplets,
248-
{ source, build, define, weakNodeApiLinkage, cmakeJs },
249-
spawn,
259+
{ source, build, define, weakNodeApiLinkage, cmakeJs, ccachePath },
250260
) {
261+
// When using ccache, we're creating symlinks for the clang and clang++ binaries to the ccache binary
262+
// This is needed for ccache to understand it's being invoked as clang and clang++ respectively.
263+
const buildBinPath = path.join(build, "bin");
264+
await fs.promises.mkdir(buildBinPath, { recursive: true });
265+
const compilerDefinitions = ccachePath
266+
? {
267+
CMAKE_XCODE_ATTRIBUTE_CC: await getCompilerPath("clang", {
268+
buildBinPath,
269+
ccachePath,
270+
}),
271+
CMAKE_XCODE_ATTRIBUTE_CXX: await getCompilerPath("clang++", {
272+
buildBinPath,
273+
ccachePath,
274+
}),
275+
CMAKE_XCODE_ATTRIBUTE_LD: await getCompilerPath("clang", {
276+
buildBinPath,
277+
ccachePath,
278+
}),
279+
CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS: await getCompilerPath("clang++", {
280+
buildBinPath,
281+
ccachePath,
282+
}),
283+
}
284+
: {};
285+
251286
// Ideally, we would generate a single Xcode project supporting all architectures / platforms
252287
// However, CMake's Xcode generator does not support that well, so we generate one project per triplet
253288
// Specifically, the linking of weak-node-api breaks, since the sdk / arch specific framework
254289
// from the xcframework is picked at configure time, not at build time.
255290
// See https://gitlab.kitware.com/cmake/cmake/-/issues/21752#note_1717047 for more information.
256291
await Promise.all(
257-
triplets.map(async ({ triplet }) => {
292+
triplets.map(async ({ triplet, spawn }) => {
258293
const buildPath = getBuildPath(build, triplet);
259294
// We want to use the CMake File API to query information later
260295
// TODO: Or do we?
@@ -272,16 +307,15 @@ export const platform: Platform<Triplet[], AppleOpts> = {
272307
"Xcode",
273308
...toDefineArguments([
274309
...define,
275-
...(weakNodeApiLinkage ? [getWeakNodeApiVariables("apple")] : []),
276-
...(cmakeJs ? [getCmakeJSVariables("apple")] : []),
310+
weakNodeApiLinkage ? getWeakNodeApiVariables("apple") : {},
311+
cmakeJs ? getCmakeJSVariables("apple") : {},
312+
compilerDefinitions,
277313
{
278314
CMAKE_SYSTEM_NAME: CMAKE_SYSTEM_NAMES[triplet],
279315
CMAKE_OSX_SYSROOT: XCODE_SDK_NAMES[triplet],
280316
CMAKE_OSX_ARCHITECTURES: APPLE_ARCHITECTURES[triplet],
281317
// Passing a linker flag to increase the header pad size to allow renaming the install name when linking it into the app.
282318
CMAKE_SHARED_LINKER_FLAGS: "-Wl,-headerpad_max_install_names",
283-
},
284-
{
285319
// Setting the output directories works around an issue with Xcode generator
286320
// where an unexpanded variable would emitted in the artifact paths.
287321
// This is okay, since we're generating per triplet build directories anyway.

0 commit comments

Comments
 (0)