From 156f54979de4563f56f948894ebd1dc7872f4d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 24 Jun 2025 01:45:49 +0200 Subject: [PATCH 1/3] Install ccache on CI --- .github/workflows/check.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 86f4e799..52c48780 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -51,6 +51,8 @@ jobs: with: packages: tools platform-tools ndk;${{ env.NDK_VERSION }} - run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android aarch64-apple-ios-sim + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 - run: npm ci - run: npm run bootstrap - run: npm test @@ -73,6 +75,8 @@ jobs: with: packages: tools platform-tools ndk;${{ env.NDK_VERSION }} - run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android aarch64-apple-ios-sim + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 - run: npm ci - run: npm run bootstrap - run: npm run pod-install @@ -101,6 +105,8 @@ jobs: with: packages: tools platform-tools ndk;${{ env.NDK_VERSION }} - run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android aarch64-apple-ios-sim + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 - run: npm ci - run: npm run bootstrap - name: Clone patched Hermes version From 8771536ccd7bde08439419ff00acd8183c1e6afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 24 Jun 2025 01:46:08 +0200 Subject: [PATCH 2/3] Add note in Podfile on use of ccache --- apps/test-app/ios/Podfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/test-app/ios/Podfile b/apps/test-app/ios/Podfile index fefbbc4f..c363cdef 100644 --- a/apps/test-app/ios/Podfile +++ b/apps/test-app/ios/Podfile @@ -4,4 +4,13 @@ ws_dir = ws_dir.parent until ws_dir.expand_path.to_s == '/' require "#{ws_dir}/node_modules/react-native-test-app/test_app.rb" +# Disabling ccache for now, as I'm getting lots of warnings: +# Explicit modules is enabled but the compiler was not recognized; disable explicit modules with CLANG_ENABLE_EXPLICIT_MODULES=NO, or use C_COMPILER_LAUNCHER with CLANG_ENABLE_EXPLICIT_MODULES_WITH_COMPILER_LAUNCHER=YES if using a compatible launcher +# I could get this working by patching React Native's utils.rb to set +# config.build_settings["CLANG_ENABLE_EXPLICIT_MODULES"] = "NO" +# config.build_settings["COMPILER_INDEX_STORE_ENABLE"] = "NO" +# but I'm still seeing compiler errors after that. + +# ENV['USE_CCACHE'] = ENV['USE_CCACHE'] || Pod::Executable::which('ccache') ? '1' : '0' + use_test_app! :hermes_enabled => true, :fabric_enabled => true, :bridgeless_enabled => true From 3ed4a1e0824826b3a3f3be7b4164bccf7b1a0996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 24 Jun 2025 01:46:38 +0200 Subject: [PATCH 3/3] Detect and use ccache in cmake-rn --- packages/cmake-rn/scripts/ccache-clang++.sh | 16 +++++++++++++ packages/cmake-rn/scripts/ccache-clang.sh | 16 +++++++++++++ packages/cmake-rn/src/android.ts | 14 ++++++++---- packages/cmake-rn/src/apple.ts | 23 ++++++++++++++++++- packages/cmake-rn/src/ccache.ts | 10 +++++++++ packages/cmake-rn/src/cli.ts | 25 +++++++++++++++++++-- 6 files changed, 97 insertions(+), 7 deletions(-) create mode 100755 packages/cmake-rn/scripts/ccache-clang++.sh create mode 100755 packages/cmake-rn/scripts/ccache-clang.sh create mode 100644 packages/cmake-rn/src/ccache.ts diff --git a/packages/cmake-rn/scripts/ccache-clang++.sh b/packages/cmake-rn/scripts/ccache-clang++.sh new file mode 100755 index 00000000..8a0e92ce --- /dev/null +++ b/packages/cmake-rn/scripts/ccache-clang++.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# reference for xcode specific cacche settings: reactnative docs and ccache manpage +# https://reactnative.dev/docs/build-speed#xcode-specific-setup +# https://ccache.dev/manual/4.3.html + +export CCACHE_MAXSIZE=10G +export CCACHE_CPP2=true +export CCACHE_DIRECT=true +export CCACHE_DEPEND=true +export CCACHE_HARDLINK=true +export CCACHE_FILECLONE=true +export CCACHE_INODECACHE=true +export CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches,modules,ivfsoverlay,pch_defines,system_headers + +exec ccache clang++ "$@" \ No newline at end of file diff --git a/packages/cmake-rn/scripts/ccache-clang.sh b/packages/cmake-rn/scripts/ccache-clang.sh new file mode 100755 index 00000000..c2876037 --- /dev/null +++ b/packages/cmake-rn/scripts/ccache-clang.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# reference for xcode specific cacche settings: reactnative docs and ccache manpage +# https://reactnative.dev/docs/build-speed#xcode-specific-setup +# https://ccache.dev/manual/4.3.html + +export CCACHE_MAXSIZE=10G +export CCACHE_CPP2=true +export CCACHE_DIRECT=true +export CCACHE_DEPEND=true +export CCACHE_HARDLINK=true +export CCACHE_FILECLONE=true +export CCACHE_INODECACHE=true +export CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches,modules,ivfsoverlay,pch_defines,system_headers + +exec ccache clang "$@" \ No newline at end of file diff --git a/packages/cmake-rn/src/android.ts b/packages/cmake-rn/src/android.ts index af550cb1..02a95fd5 100644 --- a/packages/cmake-rn/src/android.ts +++ b/packages/cmake-rn/src/android.ts @@ -24,12 +24,14 @@ type AndroidConfigureOptions = { triplet: AndroidTriplet; ndkVersion: string; sdkVersion: string; + ccache: boolean; }; export function getAndroidConfigureCmakeArgs({ triplet, ndkVersion, sdkVersion, + ccache, }: AndroidConfigureOptions) { const { ANDROID_HOME } = process.env; assert(typeof ANDROID_HOME === "string", "Missing env variable ANDROID_HOME"); @@ -72,10 +74,14 @@ export function getAndroidConfigureCmakeArgs({ // `CMAKE_BUILD_TYPE=${configuration}`, "-D", "CMAKE_MAKE_PROGRAM=ninja", - // "-D", - // "CMAKE_C_COMPILER_LAUNCHER=ccache", - // "-D", - // "CMAKE_CXX_COMPILER_LAUNCHER=ccache", + ...(ccache + ? [ + "-D", + "CMAKE_C_COMPILER_LAUNCHER=ccache", + "-D", + "CMAKE_CXX_COMPILER_LAUNCHER=ccache", + ] + : []), "-D", `ANDROID_NDK=${ndkPath}`, "-D", diff --git a/packages/cmake-rn/src/apple.ts b/packages/cmake-rn/src/apple.ts index 1b92409b..cdc71d92 100644 --- a/packages/cmake-rn/src/apple.ts +++ b/packages/cmake-rn/src/apple.ts @@ -1,7 +1,12 @@ import assert from "node:assert/strict"; +import path from "node:path"; import { AppleTriplet, isAppleTriplet } from "react-native-node-api"; +const scriptsPath = path.join(import.meta.dirname, "..", "scripts"); +const ccacheClangPath = path.join(scriptsPath, "ccache-clang.sh"); +const ccacheClangPPPath = path.join(scriptsPath, "ccache-clang++.sh"); + export const DEFAULT_APPLE_TRIPLETS = [ "arm64;x86_64-apple-darwin", "arm64-apple-ios", @@ -82,9 +87,13 @@ export function createPlistContent(values: Record) { type AppleConfigureOptions = { triplet: AppleTriplet; + ccache: boolean; }; -export function getAppleConfigureCmakeArgs({ triplet }: AppleConfigureOptions) { +export function getAppleConfigureCmakeArgs({ + triplet, + ccache, +}: AppleConfigureOptions) { assert(isAppleTriplet(triplet)); const systemName = CMAKE_SYSTEM_NAMES[triplet]; @@ -100,6 +109,18 @@ export function getAppleConfigureCmakeArgs({ triplet }: AppleConfigureOptions) { // Set the target architecture "-D", `CMAKE_OSX_ARCHITECTURES=${APPLE_ARCHITECTURES[triplet]}`, + ...(ccache + ? [ + "-D", + `CMAKE_XCODE_ATTRIBUTE_CC=${ccacheClangPath}`, + "-D", + `CMAKE_XCODE_ATTRIBUTE_CXX=${ccacheClangPPPath}`, + "-D", + `CMAKE_XCODE_ATTRIBUTE_LD=${ccacheClangPath}`, + "-D", + `CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS=${ccacheClangPPPath}`, + ] + : []), ]; } diff --git a/packages/cmake-rn/src/ccache.ts b/packages/cmake-rn/src/ccache.ts new file mode 100644 index 00000000..7e703725 --- /dev/null +++ b/packages/cmake-rn/src/ccache.ts @@ -0,0 +1,10 @@ +import cp from "node:child_process"; + +export function getCcacheVersion(): string | undefined { + const { status, stdout } = cp.spawnSync("ccache", ["--print-version"], { + encoding: "utf8", + }); + if (status === 0) { + return stdout.trim(); + } +} diff --git a/packages/cmake-rn/src/cli.ts b/packages/cmake-rn/src/cli.ts index d7d621f5..4e748535 100644 --- a/packages/cmake-rn/src/cli.ts +++ b/packages/cmake-rn/src/cli.ts @@ -33,6 +33,7 @@ import { createXCframework, determineXCFrameworkFilename, } from "react-native-node-api"; +import { getCcacheVersion } from "./ccache.js"; // We're attaching a lot of listeners when spawning in parallel EventEmitter.defaultMaxListeners = 100; @@ -109,6 +110,11 @@ const xcframeworkExtensionOption = new Option( "Don't rename the xcframework to .apple.node" ).default(false); +const noCcacheOption = new Option( + "--no-ccache", + "Don't detect and use ccache to speed up builds" +); + export const program = new Command("cmake-rn") .description("Build React Native Node API modules with CMake") .addOption(verboseOption) @@ -120,6 +126,7 @@ export const program = new Command("cmake-rn") .addOption(buildPathOption) .addOption(outPathOption) .addOption(cleanOption) + .addOption(noCcacheOption) .addOption(ndkVersionOption) .addOption(androidSdkVersionOption) .addOption(noAutoLinkOption) @@ -127,6 +134,16 @@ export const program = new Command("cmake-rn") .addOption(xcframeworkExtensionOption) .action(async ({ triplet: tripletValues, ...globalContext }) => { try { + const ccacheVersion = globalContext.ccache + ? getCcacheVersion() + : undefined; + if (ccacheVersion) { + console.log("⚡️Using ccache", chalk.dim(`(${ccacheVersion})`)); + } else { + // Disable ccache if not found + globalContext.ccache = false; + } + const buildPath = getBuildPath(globalContext); if (globalContext.clean) { await fs.promises.rm(buildPath, { recursive: true, force: true }); @@ -364,9 +381,10 @@ function getTripletConfigureCmakeArgs( { ndkVersion, androidSdkVersion, + ccache, }: Pick< GlobalContext, - "ndkVersion" | "androidSdkVersion" | "weakNodeApiLinkage" + "ndkVersion" | "androidSdkVersion" | "weakNodeApiLinkage" | "ccache" > ) { if (isAndroidTriplet(triplet)) { @@ -374,9 +392,10 @@ function getTripletConfigureCmakeArgs( triplet, ndkVersion, sdkVersion: androidSdkVersion, + ccache, }); } else if (isAppleTriplet(triplet)) { - return getAppleConfigureCmakeArgs({ triplet }); + return getAppleConfigureCmakeArgs({ triplet, ccache }); } else { throw new Error(`Support for '${triplet}' is not implemented yet`); } @@ -398,6 +417,7 @@ async function configureProject(context: TripletScopedContext) { triplet, tripletBuildPath, source, + ccache, ndkVersion, androidSdkVersion, weakNodeApiLinkage, @@ -411,6 +431,7 @@ async function configureProject(context: TripletScopedContext) { tripletBuildPath, ...getVariablesArgs(getVariables(context)), ...getTripletConfigureCmakeArgs(triplet, { + ccache, ndkVersion, weakNodeApiLinkage, androidSdkVersion,