Skip to content

Commit 2358e8d

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

File tree

5 files changed

+94
-16
lines changed

5 files changed

+94
-16
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/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: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,18 @@ async function readCmakeSharedLibraryTarget(
193193
return sharedLibrary;
194194
}
195195

196+
function getCompilerPath(
197+
name: "clang" | "clang++",
198+
rootBuildPath: string,
199+
allowMissing?: boolean,
200+
) {
201+
const result = path.join(rootBuildPath, "bin", name);
202+
if (!allowMissing) {
203+
assert(fs.existsSync(result), `Expected ${name} link to exist: ${result}`);
204+
}
205+
return result;
206+
}
207+
196208
export const platform: Platform<Triplet[], AppleOpts> = {
197209
id: "apple",
198210
name: "Apple",
@@ -245,16 +257,29 @@ export const platform: Platform<Triplet[], AppleOpts> = {
245257
},
246258
async configure(
247259
triplets,
248-
{ source, build, define, weakNodeApiLinkage, cmakeJs },
249-
spawn,
260+
{ source, build, define, weakNodeApiLinkage, cmakeJs, ccachePath },
250261
) {
262+
// When using ccache, we're creating symlinks for the clang and clang++ binaries to the ccache binary
263+
// This is needed for ccache to understand it's being invoked as clang and clang++ respectively.
264+
if (ccachePath) {
265+
const buildBinPath = path.join(build, "bin");
266+
await fs.promises.mkdir(buildBinPath, { recursive: true });
267+
await fs.promises.symlink(
268+
ccachePath,
269+
getCompilerPath("clang", build, true),
270+
);
271+
await fs.promises.symlink(
272+
ccachePath,
273+
getCompilerPath("clang++", build, true),
274+
);
275+
}
251276
// Ideally, we would generate a single Xcode project supporting all architectures / platforms
252277
// However, CMake's Xcode generator does not support that well, so we generate one project per triplet
253278
// Specifically, the linking of weak-node-api breaks, since the sdk / arch specific framework
254279
// from the xcframework is picked at configure time, not at build time.
255280
// See https://gitlab.kitware.com/cmake/cmake/-/issues/21752#note_1717047 for more information.
256281
await Promise.all(
257-
triplets.map(async ({ triplet }) => {
282+
triplets.map(async ({ triplet, spawn }) => {
258283
const buildPath = getBuildPath(build, triplet);
259284
// We want to use the CMake File API to query information later
260285
// TODO: Or do we?
@@ -272,16 +297,14 @@ export const platform: Platform<Triplet[], AppleOpts> = {
272297
"Xcode",
273298
...toDefineArguments([
274299
...define,
275-
...(weakNodeApiLinkage ? [getWeakNodeApiVariables("apple")] : []),
276-
...(cmakeJs ? [getCmakeJSVariables("apple")] : []),
300+
weakNodeApiLinkage ? getWeakNodeApiVariables("apple") : {},
301+
cmakeJs ? getCmakeJSVariables("apple") : {},
277302
{
278303
CMAKE_SYSTEM_NAME: CMAKE_SYSTEM_NAMES[triplet],
279304
CMAKE_OSX_SYSROOT: XCODE_SDK_NAMES[triplet],
280305
CMAKE_OSX_ARCHITECTURES: APPLE_ARCHITECTURES[triplet],
281306
// Passing a linker flag to increase the header pad size to allow renaming the install name when linking it into the app.
282307
CMAKE_SHARED_LINKER_FLAGS: "-Wl,-headerpad_max_install_names",
283-
},
284-
{
285308
// Setting the output directories works around an issue with Xcode generator
286309
// where an unexpanded variable would emitted in the artifact paths.
287310
// This is okay, since we're generating per triplet build directories anyway.
@@ -296,7 +319,7 @@ export const platform: Platform<Triplet[], AppleOpts> = {
296319
},
297320
async build(
298321
{ spawn, triplet },
299-
{ build, target, configuration, appleBundleIdentifier },
322+
{ build, target, configuration, appleBundleIdentifier, ccachePath },
300323
) {
301324
// We expect the final application to sign these binaries
302325
if (target.length > 1) {
@@ -305,6 +328,13 @@ export const platform: Platform<Triplet[], AppleOpts> = {
305328

306329
const buildPath = getBuildPath(build, triplet);
307330

331+
const compilerPaths = ccachePath
332+
? {
333+
clang: getCompilerPath("clang", build),
334+
clangPlusPlus: getCompilerPath("clang++", build),
335+
}
336+
: null;
337+
308338
const sharedLibrary = await readCmakeSharedLibraryTarget(
309339
buildPath,
310340
configuration,
@@ -345,6 +375,12 @@ export const platform: Platform<Triplet[], AppleOpts> = {
345375
configuration,
346376
"-destination",
347377
DESTINATION_BY_TRIPLET[triplet],
378+
...(compilerPaths
379+
? [
380+
`CC=${compilerPaths.clang}`,
381+
`CXX=${compilerPaths.clangPlusPlus}`,
382+
]
383+
: []),
348384
],
349385
buildPath,
350386
);

0 commit comments

Comments
 (0)