diff --git a/packages/react-native-reanimated/android/CMakeLists.txt b/packages/react-native-reanimated/android/CMakeLists.txt index 3f87cab8570b..10e782a33ea8 100644 --- a/packages/react-native-reanimated/android/CMakeLists.txt +++ b/packages/react-native-reanimated/android/CMakeLists.txt @@ -1,5 +1,5 @@ project(Reanimated) -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.16) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -50,6 +50,9 @@ find_package(react-native-worklets REQUIRED CONFIG) add_library(reanimated SHARED ${REANIMATED_COMMON_CPP_SOURCES} ${REANIMATED_ANDROID_CPP_SOURCES}) +target_precompile_headers(reanimated PRIVATE + "${ANDROID_CPP_DIR}/ReanimatedPCH.h") + if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80) include( "${REACT_NATIVE_DIR}/ReactCommon/cmake-utils/react-native-flags.cmake") diff --git a/packages/react-native-reanimated/android/build.gradle b/packages/react-native-reanimated/android/build.gradle index 8167d16df2b2..5485d4aaf9b8 100644 --- a/packages/react-native-reanimated/android/build.gradle +++ b/packages/react-native-reanimated/android/build.gradle @@ -130,6 +130,8 @@ if (project == rootProject) { apply plugin: "com.android.library" apply plugin: "maven-publish" +apply from: "./generate-stub-pch.gradle.kts" + android { compileSdkVersion safeExtGet("compileSdkVersion", 36) diff --git a/packages/react-native-reanimated/android/generate-stub-pch.gradle.kts b/packages/react-native-reanimated/android/generate-stub-pch.gradle.kts new file mode 100644 index 000000000000..a900f9015baa --- /dev/null +++ b/packages/react-native-reanimated/android/generate-stub-pch.gradle.kts @@ -0,0 +1,84 @@ +import groovy.json.JsonSlurper + +// Workaround for Android Studio's C++ analyzer surfacing errors during Gradle +// Sync when CMake's precompiled header binary (`cmake_pch.hxx.pch`, generated +// from `ReanimatedPCH.h`) hasn't been built yet. The project compiles fine, +// but on a clean sync the IDE can't index sources without the PCH binary. See +// the underlying issue: https://issuetracker.google.com/issues/187448826 + +// Generates minimal stub `.pch` files from an empty header so the IDE's C++ +// engine doesn't fail during sync. The actual build regenerates the real PCH +// files via ninja because we set each stub PCH's mtime older than its source +// (`cmake_pch.hxx.cxx`). + +// Adapted from Expo's PR: https://github.com/expo/expo/pull/45921 + +val cxxDir = project.file(".cxx") + +val generateStubPCHTask = tasks.register("generateStubPCH") { + dependsOn("configureCMakeDebug") + doLast { + if (!cxxDir.exists()) { + return@doLast + } + cxxDir.walkTopDown().forEach { file -> + if (file.name != "compile_commands.json") { + return@forEach + } + @Suppress("UNCHECKED_CAST") + val entries = JsonSlurper().parseText(file.readText()) as List> + entries.forEach entry@{ entry -> + val entryFile = entry["file"] as String + if (!entryFile.endsWith("cmake_pch.hxx.cxx")) { + return@entry + } + val pchFile = File(entryFile.substring(0, entryFile.length - ".cxx".length) + ".pch") + if (!pchFile.exists() || pchFile.length() == 0L) { + pchFile.parentFile.mkdirs() + + val command = entry["command"] as String + val compiler = command.split(" ").first() + val target = Regex("""--target=\S+""").find(command)!!.value + val sysroot = Regex("""--sysroot=\S+""").find(command)!!.value + + val stubHeader = File(pchFile.parentFile, "stub_pch.hxx") + stubHeader.writeText("") + + val process = ProcessBuilder( + compiler, + target, + sysroot, + "-x", "c++-header", + "-o", pchFile.absolutePath, + stubHeader.absolutePath, + ) + .directory(File(entry["directory"] as String)) + .redirectErrorStream(true) + .start() + process.outputStream.close() + if (process.waitFor() != 0) { + throw GradleException( + "Stub PCH generation failed: ${process.inputStream.bufferedReader().readText()}", + ) + } + } + // Ensure PCH is older than source so ninja rebuilds the real one during build + pchFile.setLastModified(File(entryFile).lastModified() - 1) + } + } + } +} + +// Register `prepareKotlinBuildScriptModel` if absent (Android Studio sync needs it +// to exist so the stub PCH generation runs) or configure it if some other plugin +// already registered it (e.g. on CI, where re-registering throws `Cannot add task +// 'prepareKotlinBuildScriptModel' as a task with that name already exists.`). +if (tasks.findByName("prepareKotlinBuildScriptModel") == null) { + tasks.register("prepareKotlinBuildScriptModel") { + dependsOn(generateStubPCHTask) + } +} else { + tasks.named("prepareKotlinBuildScriptModel") { + dependsOn(generateStubPCHTask) + } +} diff --git a/packages/react-native-reanimated/android/src/main/cpp/ReanimatedPCH.h b/packages/react-native-reanimated/android/src/main/cpp/ReanimatedPCH.h new file mode 100644 index 000000000000..6f8e23b160cd --- /dev/null +++ b/packages/react-native-reanimated/android/src/main/cpp/ReanimatedPCH.h @@ -0,0 +1,46 @@ +#pragma once + +// IWYU pragma: begin_keep + +// C++ standard library +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// fbjni +#include + +// folly +#include +#include +#include +#include +#include + +// jsi +#include +#include + +// react-native renderer (Fabric) +#include +#include +#include +#include +#include +#include +#include +#include + +// IWYU pragma: end_keep diff --git a/packages/react-native-worklets/android/CMakeLists.txt b/packages/react-native-worklets/android/CMakeLists.txt index 120f2cebb56f..608f9bb2413e 100644 --- a/packages/react-native-worklets/android/CMakeLists.txt +++ b/packages/react-native-worklets/android/CMakeLists.txt @@ -1,5 +1,5 @@ project(Worklets) -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.16) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -68,6 +68,9 @@ endif() add_library(worklets SHARED ${WORKLETS_COMMON_CPP_SOURCES} ${WORKLETS_ANDROID_CPP_SOURCES}) +target_precompile_headers(worklets PRIVATE + "${ANDROID_CPP_DIR}/WorkletsPCH.h") + if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80) include( "${REACT_NATIVE_DIR}/ReactCommon/cmake-utils/react-native-flags.cmake") diff --git a/packages/react-native-worklets/android/build.gradle b/packages/react-native-worklets/android/build.gradle index 159d9fd841a9..1594cd245c5e 100644 --- a/packages/react-native-worklets/android/build.gradle +++ b/packages/react-native-worklets/android/build.gradle @@ -174,6 +174,7 @@ if (project == rootProject) { } apply from: "./fix-prefab.gradle" +apply from: "./generate-stub-pch.gradle.kts" android { diff --git a/packages/react-native-worklets/android/generate-stub-pch.gradle.kts b/packages/react-native-worklets/android/generate-stub-pch.gradle.kts new file mode 100644 index 000000000000..12585edd630c --- /dev/null +++ b/packages/react-native-worklets/android/generate-stub-pch.gradle.kts @@ -0,0 +1,84 @@ +import groovy.json.JsonSlurper + +// Workaround for Android Studio's C++ analyzer surfacing errors during Gradle +// Sync when CMake's precompiled header binary (`cmake_pch.hxx.pch`, generated +// from `WorkletsPCH.h`) hasn't been built yet. The project compiles fine, +// but on a clean sync the IDE can't index sources without the PCH binary. See +// the underlying issue: https://issuetracker.google.com/issues/187448826 + +// Generates minimal stub `.pch` files from an empty header so the IDE's C++ +// engine doesn't fail during sync. The actual build regenerates the real PCH +// files via ninja because we set each stub PCH's mtime older than its source +// (`cmake_pch.hxx.cxx`). + +// Adapted from Expo's PR: https://github.com/expo/expo/pull/45921 + +val cxxDir = project.file(".cxx") + +val generateStubPCHTask = tasks.register("generateStubPCH") { + dependsOn("configureCMakeDebug") + doLast { + if (!cxxDir.exists()) { + return@doLast + } + cxxDir.walkTopDown().forEach { file -> + if (file.name != "compile_commands.json") { + return@forEach + } + @Suppress("UNCHECKED_CAST") + val entries = JsonSlurper().parseText(file.readText()) as List> + entries.forEach entry@{ entry -> + val entryFile = entry["file"] as String + if (!entryFile.endsWith("cmake_pch.hxx.cxx")) { + return@entry + } + val pchFile = File(entryFile.substring(0, entryFile.length - ".cxx".length) + ".pch") + if (!pchFile.exists() || pchFile.length() == 0L) { + pchFile.parentFile.mkdirs() + + val command = entry["command"] as String + val compiler = command.split(" ").first() + val target = Regex("""--target=\S+""").find(command)!!.value + val sysroot = Regex("""--sysroot=\S+""").find(command)!!.value + + val stubHeader = File(pchFile.parentFile, "stub_pch.hxx") + stubHeader.writeText("") + + val process = ProcessBuilder( + compiler, + target, + sysroot, + "-x", "c++-header", + "-o", pchFile.absolutePath, + stubHeader.absolutePath, + ) + .directory(File(entry["directory"] as String)) + .redirectErrorStream(true) + .start() + process.outputStream.close() + if (process.waitFor() != 0) { + throw GradleException( + "Stub PCH generation failed: ${process.inputStream.bufferedReader().readText()}", + ) + } + } + // Ensure PCH is older than source so ninja rebuilds the real one during build + pchFile.setLastModified(File(entryFile).lastModified() - 1) + } + } + } +} + +// Register `prepareKotlinBuildScriptModel` if absent (Android Studio sync needs it +// to exist so the stub PCH generation runs) or configure it if some other plugin +// already registered it (e.g. on CI, where re-registering throws `Cannot add task +// 'prepareKotlinBuildScriptModel' as a task with that name already exists.`). +if (tasks.findByName("prepareKotlinBuildScriptModel") == null) { + tasks.register("prepareKotlinBuildScriptModel") { + dependsOn(generateStubPCHTask) + } +} else { + tasks.named("prepareKotlinBuildScriptModel") { + dependsOn(generateStubPCHTask) + } +} diff --git a/packages/react-native-worklets/android/src/main/cpp/WorkletsPCH.h b/packages/react-native-worklets/android/src/main/cpp/WorkletsPCH.h new file mode 100644 index 000000000000..0347fc99b98c --- /dev/null +++ b/packages/react-native-worklets/android/src/main/cpp/WorkletsPCH.h @@ -0,0 +1,26 @@ +#pragma once + +// IWYU pragma: begin_keep + +// C++ standard library +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// fbjni +#include + +// jsi +#include + +// IWYU pragma: end_keep diff --git a/scripts/clang-tidy-lint.sh b/scripts/clang-tidy-lint.sh index 5449aaf4f3f6..79358b8472a7 100755 --- a/scripts/clang-tidy-lint.sh +++ b/scripts/clang-tidy-lint.sh @@ -15,4 +15,7 @@ if [ ! -f "compile_commands.json" ]; then ) fi -run-clang-tidy -quiet -p . -header-filter="^.*/$1/.*\.h$" "$1" +ndk_bin="$(grep -oE '/[^ "]+/clang\+\+' compile_commands.json | head -1 | xargs dirname)" + +run-clang-tidy -quiet -p . -clang-tidy-binary "$ndk_bin/clang-tidy" \ + -header-filter="^.*/$1/.*\.h$" "$1"