Skip to content

Commit bfd07ee

Browse files
authored
Automatically detect and use ccache (#359)
* Print command when spawning a triplet command in --verbose * Detect ccache and use when building for Android and Apple * Setup ccache on CI * Use where on Windows
1 parent 5cd0c51 commit bfd07ee

File tree

8 files changed

+140
-29
lines changed

8 files changed

+140
-29
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

.github/workflows/check.yml

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,14 @@ jobs:
2828
- uses: actions/setup-node@v6
2929
with:
3030
node-version: lts/krypton
31-
- name: Setup clang-format
31+
- name: Setup cpp tools
3232
uses: aminya/setup-cpp@v1
3333
with:
3434
clang-format: true
35+
- name: ccache
36+
uses: hendrikmuhs/ccache-action@v1.2
37+
with:
38+
key: ${{ github.job }}-${{ runner.os }}
3539
# Set up JDK and Android SDK only because we need weak-node-api, to build ferric-example and to run the linting
3640
# TODO: Remove this once we have a way to run linting without building the native code
3741
- name: Set up JDK 17
@@ -71,10 +75,14 @@ jobs:
7175
- uses: actions/setup-node@v6
7276
with:
7377
node-version: lts/krypton
74-
- name: Setup clang-format
78+
- name: Setup cpp tools
7579
uses: aminya/setup-cpp@v1
7680
with:
7781
clang-format: true
82+
- name: ccache
83+
uses: hendrikmuhs/ccache-action@v1.2
84+
with:
85+
key: ${{ github.job }}-${{ runner.os }}
7886
- name: Set up JDK 17
7987
uses: actions/setup-java@v4
8088
with:
@@ -104,10 +112,14 @@ jobs:
104112
- uses: actions/setup-node@v6
105113
with:
106114
node-version: lts/krypton
107-
- name: Setup clang-format
115+
- name: Setup cpp tools
108116
uses: aminya/setup-cpp@v1
109117
with:
110118
clang-format: true
119+
- name: ccache
120+
uses: hendrikmuhs/ccache-action@v1.2
121+
with:
122+
key: ${{ github.job }}-${{ runner.os }}
111123
- run: npm ci
112124
- run: npm run build
113125
- name: Prepare weak-node-api
@@ -127,10 +139,14 @@ jobs:
127139
- uses: actions/setup-node@v6
128140
with:
129141
node-version: lts/krypton
130-
- name: Setup clang-format
142+
- name: Setup cpp tools
131143
uses: aminya/setup-cpp@v1
132144
with:
133145
clang-format: true
146+
- name: ccache
147+
uses: hendrikmuhs/ccache-action@v1.2
148+
with:
149+
key: ${{ github.job }}-${{ runner.os }}
134150
- name: Set up JDK 17
135151
uses: actions/setup-java@v3
136152
with:
@@ -164,10 +180,14 @@ jobs:
164180
- uses: actions/setup-node@v6
165181
with:
166182
node-version: lts/krypton
167-
- name: Setup clang-format
183+
- name: Setup cpp tools
168184
uses: aminya/setup-cpp@v1
169185
with:
170186
clang-format: true
187+
- name: ccache
188+
uses: hendrikmuhs/ccache-action@v1.2
189+
with:
190+
key: ${{ github.job }}-${{ runner.os }}
171191
- name: Set up JDK 17
172192
uses: actions/setup-java@v3
173193
with:
@@ -207,10 +227,14 @@ jobs:
207227
- uses: actions/setup-node@v6
208228
with:
209229
node-version: lts/krypton
210-
- name: Setup clang-format
230+
- name: Setup cpp tools
211231
uses: aminya/setup-cpp@v1
212232
with:
213233
clang-format: true
234+
- name: ccache
235+
uses: hendrikmuhs/ccache-action@v1.2
236+
with:
237+
key: ${{ github.job }}-${{ runner.os }}
214238
- name: Set up JDK 17
215239
uses: actions/setup-java@v4
216240
with:
@@ -291,10 +315,14 @@ jobs:
291315
- uses: actions/setup-node@v6
292316
with:
293317
node-version: lts/krypton
294-
- name: Setup clang-format
318+
- name: Setup cpp tools
295319
uses: aminya/setup-cpp@v1
296320
with:
297321
clang-format: true
322+
- name: ccache
323+
uses: hendrikmuhs/ccache-action@v1.2
324+
with:
325+
key: ${{ github.job }}-${{ runner.os }}
298326
- name: Set up JDK 17
299327
uses: actions/setup-java@v3
300328
with:

.github/workflows/release.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@ jobs:
2121
- uses: actions/setup-node@v6
2222
with:
2323
node-version: lts/krypton
24-
- name: Setup clang-format
24+
- name: Setup cpp tools
2525
uses: aminya/setup-cpp@v1
2626
with:
2727
clang-format: true
28+
- name: ccache
29+
uses: hendrikmuhs/ccache-action@v1.2
30+
with:
31+
key: ${{ github.job }}-${{ runner.os }}
2832
- name: Set up JDK 17
2933
uses: actions/setup-java@v3
3034
with:

packages/cmake-rn/src/ccache.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import cp from "node:child_process";
2+
3+
export function getCcachePath(): string | null {
4+
const result = cp.spawnSync(
5+
process.platform === "win32" ? "where" : "which",
6+
["ccache"],
7+
);
8+
if (result.status === 0) {
9+
return result.stdout.toString().trim();
10+
} else {
11+
return null;
12+
}
13+
}

packages/cmake-rn/src/cli.ts

Lines changed: 28 additions & 5 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 } 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,10 @@ program = program.action(
179191
},
180192
);
181193

194+
if (ccachePath) {
195+
console.log(`♻️ Using ccache: ${chalk.dim(ccachePath)}`);
196+
}
197+
182198
if (clean) {
183199
await fs.promises.rm(buildPath, { recursive: true, force: true });
184200
}
@@ -228,9 +244,16 @@ program = program.action(
228244
triplet,
229245
platform,
230246
async spawn(command: string, args: string[], cwd?: string) {
247+
const outputPrefix = verbose ? chalk.dim(`[${triplet}] `) : undefined;
248+
if (verbose) {
249+
console.log(
250+
`${outputPrefix}» ${command} ${args.map((arg) => chalk.dim(`${arg}`)).join(" ")}`,
251+
cwd ? `(in ${chalk.dim(cwd)})` : "",
252+
);
253+
}
231254
await spawn(command, args, {
232255
outputMode: verbose ? "inherit" : "buffered",
233-
outputPrefix: verbose ? chalk.dim(`[${triplet}] `) : undefined,
256+
outputPrefix,
234257
cwd,
235258
});
236259
},

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)