Skip to content

Commit d146192

Browse files
MatiPl01tomekzawtshmieldev
authored
cherry-pick(4.3-stable): precompiled headers on Android (#9434, #9455) (#9739)
## Summary Cherry-picking for Reanimated 4.3.2 release. #9455 is the clang-tidy CI fix for the PCH setup introduced in #9434, so both are included in this single PR (cherry-picked in order). - #9434 - #9455 ## Cherry-pick notes 4.3-stable still uses the Groovy `build.gradle` (main migrated to `build.gradle.kts`), so the `apply from: "./generate-stub-pch.gradle.kts"` line was added to the Groovy build files instead of the `.kts` ones. The `generate-stub-pch.gradle.kts` script itself is unchanged and applied cross-DSL (Gradle 9.3 supports applying a Kotlin script plugin from a Groovy build). The CMake `target_precompile_headers` changes and the `*PCH.h` files applied as-is. Not build-verified locally; relying on CI for the Android build and lint jobs. --------- Co-authored-by: Tomasz Zawadzki <tomekzawadzki98@gmail.com> Co-authored-by: Wojciech Rok <58606210+tshmieldev@users.noreply.github.com>
1 parent d3c74bd commit d146192

9 files changed

Lines changed: 255 additions & 3 deletions

File tree

packages/react-native-reanimated/android/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
project(Reanimated)
2-
cmake_minimum_required(VERSION 3.8)
2+
cmake_minimum_required(VERSION 3.16)
33

44
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
55

@@ -50,6 +50,9 @@ find_package(react-native-worklets REQUIRED CONFIG)
5050
add_library(reanimated SHARED ${REANIMATED_COMMON_CPP_SOURCES}
5151
${REANIMATED_ANDROID_CPP_SOURCES})
5252

53+
target_precompile_headers(reanimated PRIVATE
54+
"${ANDROID_CPP_DIR}/ReanimatedPCH.h")
55+
5356
if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
5457
include(
5558
"${REACT_NATIVE_DIR}/ReactCommon/cmake-utils/react-native-flags.cmake")

packages/react-native-reanimated/android/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ if (project == rootProject) {
130130
apply plugin: "com.android.library"
131131
apply plugin: "maven-publish"
132132

133+
apply from: "./generate-stub-pch.gradle.kts"
134+
133135
android {
134136
compileSdkVersion safeExtGet("compileSdkVersion", 36)
135137

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import groovy.json.JsonSlurper
2+
3+
// Workaround for Android Studio's C++ analyzer surfacing errors during Gradle
4+
// Sync when CMake's precompiled header binary (`cmake_pch.hxx.pch`, generated
5+
// from `ReanimatedPCH.h`) hasn't been built yet. The project compiles fine,
6+
// but on a clean sync the IDE can't index sources without the PCH binary. See
7+
// the underlying issue: https://issuetracker.google.com/issues/187448826
8+
9+
// Generates minimal stub `.pch` files from an empty header so the IDE's C++
10+
// engine doesn't fail during sync. The actual build regenerates the real PCH
11+
// files via ninja because we set each stub PCH's mtime older than its source
12+
// (`cmake_pch.hxx.cxx`).
13+
14+
// Adapted from Expo's PR: https://github.com/expo/expo/pull/45921
15+
16+
val cxxDir = project.file(".cxx")
17+
18+
val generateStubPCHTask = tasks.register("generateStubPCH") {
19+
dependsOn("configureCMakeDebug")
20+
doLast {
21+
if (!cxxDir.exists()) {
22+
return@doLast
23+
}
24+
cxxDir.walkTopDown().forEach { file ->
25+
if (file.name != "compile_commands.json") {
26+
return@forEach
27+
}
28+
@Suppress("UNCHECKED_CAST")
29+
val entries = JsonSlurper().parseText(file.readText()) as List<Map<String, Any>>
30+
entries.forEach entry@{ entry ->
31+
val entryFile = entry["file"] as String
32+
if (!entryFile.endsWith("cmake_pch.hxx.cxx")) {
33+
return@entry
34+
}
35+
val pchFile = File(entryFile.substring(0, entryFile.length - ".cxx".length) + ".pch")
36+
if (!pchFile.exists() || pchFile.length() == 0L) {
37+
pchFile.parentFile.mkdirs()
38+
39+
val command = entry["command"] as String
40+
val compiler = command.split(" ").first()
41+
val target = Regex("""--target=\S+""").find(command)!!.value
42+
val sysroot = Regex("""--sysroot=\S+""").find(command)!!.value
43+
44+
val stubHeader = File(pchFile.parentFile, "stub_pch.hxx")
45+
stubHeader.writeText("")
46+
47+
val process = ProcessBuilder(
48+
compiler,
49+
target,
50+
sysroot,
51+
"-x", "c++-header",
52+
"-o", pchFile.absolutePath,
53+
stubHeader.absolutePath,
54+
)
55+
.directory(File(entry["directory"] as String))
56+
.redirectErrorStream(true)
57+
.start()
58+
process.outputStream.close()
59+
if (process.waitFor() != 0) {
60+
throw GradleException(
61+
"Stub PCH generation failed: ${process.inputStream.bufferedReader().readText()}",
62+
)
63+
}
64+
}
65+
// Ensure PCH is older than source so ninja rebuilds the real one during build
66+
pchFile.setLastModified(File(entryFile).lastModified() - 1)
67+
}
68+
}
69+
}
70+
}
71+
72+
// Register `prepareKotlinBuildScriptModel` if absent (Android Studio sync needs it
73+
// to exist so the stub PCH generation runs) or configure it if some other plugin
74+
// already registered it (e.g. on CI, where re-registering throws `Cannot add task
75+
// 'prepareKotlinBuildScriptModel' as a task with that name already exists.`).
76+
if (tasks.findByName("prepareKotlinBuildScriptModel") == null) {
77+
tasks.register("prepareKotlinBuildScriptModel") {
78+
dependsOn(generateStubPCHTask)
79+
}
80+
} else {
81+
tasks.named("prepareKotlinBuildScriptModel") {
82+
dependsOn(generateStubPCHTask)
83+
}
84+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#pragma once
2+
3+
// IWYU pragma: begin_keep
4+
5+
// C++ standard library
6+
#include <algorithm>
7+
#include <atomic>
8+
#include <functional>
9+
#include <map>
10+
#include <memory>
11+
#include <mutex>
12+
#include <optional>
13+
#include <ostream>
14+
#include <string>
15+
#include <string_view>
16+
#include <type_traits>
17+
#include <unordered_map>
18+
#include <unordered_set>
19+
#include <utility>
20+
#include <vector>
21+
22+
// fbjni
23+
#include <fbjni/fbjni.h>
24+
25+
// folly
26+
#include <folly/Conv.h>
27+
#include <folly/Expected.h>
28+
#include <folly/Format.h>
29+
#include <folly/dynamic.h>
30+
#include <folly/json/dynamic.h>
31+
32+
// jsi
33+
#include <jsi/JSIDynamic.h>
34+
#include <jsi/jsi.h>
35+
36+
// react-native renderer (Fabric)
37+
#include <react/renderer/components/root/RootProps.h>
38+
#include <react/renderer/components/root/RootShadowNode.h>
39+
#include <react/renderer/components/view/BaseViewProps.h>
40+
#include <react/renderer/components/view/ViewProps.h>
41+
#include <react/renderer/core/EventDispatcher.h>
42+
#include <react/renderer/core/ShadowNode.h>
43+
#include <react/renderer/mounting/ShadowTree.h>
44+
#include <react/renderer/uimanager/UIManager.h>
45+
46+
// IWYU pragma: end_keep

packages/react-native-worklets/android/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
project(Worklets)
2-
cmake_minimum_required(VERSION 3.8)
2+
cmake_minimum_required(VERSION 3.16)
33

44
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
55

@@ -68,6 +68,9 @@ endif()
6868
add_library(worklets SHARED ${WORKLETS_COMMON_CPP_SOURCES}
6969
${WORKLETS_ANDROID_CPP_SOURCES})
7070

71+
target_precompile_headers(worklets PRIVATE
72+
"${ANDROID_CPP_DIR}/WorkletsPCH.h")
73+
7174
if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
7275
include(
7376
"${REACT_NATIVE_DIR}/ReactCommon/cmake-utils/react-native-flags.cmake")

packages/react-native-worklets/android/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ if (project == rootProject) {
174174
}
175175

176176
apply from: "./fix-prefab.gradle"
177+
apply from: "./generate-stub-pch.gradle.kts"
177178

178179

179180
android {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import groovy.json.JsonSlurper
2+
3+
// Workaround for Android Studio's C++ analyzer surfacing errors during Gradle
4+
// Sync when CMake's precompiled header binary (`cmake_pch.hxx.pch`, generated
5+
// from `WorkletsPCH.h`) hasn't been built yet. The project compiles fine,
6+
// but on a clean sync the IDE can't index sources without the PCH binary. See
7+
// the underlying issue: https://issuetracker.google.com/issues/187448826
8+
9+
// Generates minimal stub `.pch` files from an empty header so the IDE's C++
10+
// engine doesn't fail during sync. The actual build regenerates the real PCH
11+
// files via ninja because we set each stub PCH's mtime older than its source
12+
// (`cmake_pch.hxx.cxx`).
13+
14+
// Adapted from Expo's PR: https://github.com/expo/expo/pull/45921
15+
16+
val cxxDir = project.file(".cxx")
17+
18+
val generateStubPCHTask = tasks.register("generateStubPCH") {
19+
dependsOn("configureCMakeDebug")
20+
doLast {
21+
if (!cxxDir.exists()) {
22+
return@doLast
23+
}
24+
cxxDir.walkTopDown().forEach { file ->
25+
if (file.name != "compile_commands.json") {
26+
return@forEach
27+
}
28+
@Suppress("UNCHECKED_CAST")
29+
val entries = JsonSlurper().parseText(file.readText()) as List<Map<String, Any>>
30+
entries.forEach entry@{ entry ->
31+
val entryFile = entry["file"] as String
32+
if (!entryFile.endsWith("cmake_pch.hxx.cxx")) {
33+
return@entry
34+
}
35+
val pchFile = File(entryFile.substring(0, entryFile.length - ".cxx".length) + ".pch")
36+
if (!pchFile.exists() || pchFile.length() == 0L) {
37+
pchFile.parentFile.mkdirs()
38+
39+
val command = entry["command"] as String
40+
val compiler = command.split(" ").first()
41+
val target = Regex("""--target=\S+""").find(command)!!.value
42+
val sysroot = Regex("""--sysroot=\S+""").find(command)!!.value
43+
44+
val stubHeader = File(pchFile.parentFile, "stub_pch.hxx")
45+
stubHeader.writeText("")
46+
47+
val process = ProcessBuilder(
48+
compiler,
49+
target,
50+
sysroot,
51+
"-x", "c++-header",
52+
"-o", pchFile.absolutePath,
53+
stubHeader.absolutePath,
54+
)
55+
.directory(File(entry["directory"] as String))
56+
.redirectErrorStream(true)
57+
.start()
58+
process.outputStream.close()
59+
if (process.waitFor() != 0) {
60+
throw GradleException(
61+
"Stub PCH generation failed: ${process.inputStream.bufferedReader().readText()}",
62+
)
63+
}
64+
}
65+
// Ensure PCH is older than source so ninja rebuilds the real one during build
66+
pchFile.setLastModified(File(entryFile).lastModified() - 1)
67+
}
68+
}
69+
}
70+
}
71+
72+
// Register `prepareKotlinBuildScriptModel` if absent (Android Studio sync needs it
73+
// to exist so the stub PCH generation runs) or configure it if some other plugin
74+
// already registered it (e.g. on CI, where re-registering throws `Cannot add task
75+
// 'prepareKotlinBuildScriptModel' as a task with that name already exists.`).
76+
if (tasks.findByName("prepareKotlinBuildScriptModel") == null) {
77+
tasks.register("prepareKotlinBuildScriptModel") {
78+
dependsOn(generateStubPCHTask)
79+
}
80+
} else {
81+
tasks.named("prepareKotlinBuildScriptModel") {
82+
dependsOn(generateStubPCHTask)
83+
}
84+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
// IWYU pragma: begin_keep
4+
5+
// C++ standard library
6+
#include <algorithm>
7+
#include <atomic>
8+
#include <functional>
9+
#include <map>
10+
#include <memory>
11+
#include <mutex>
12+
#include <optional>
13+
#include <string>
14+
#include <string_view>
15+
#include <type_traits>
16+
#include <unordered_map>
17+
#include <utility>
18+
#include <vector>
19+
20+
// fbjni
21+
#include <fbjni/fbjni.h>
22+
23+
// jsi
24+
#include <jsi/jsi.h>
25+
26+
// IWYU pragma: end_keep

scripts/clang-tidy-lint.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ if [ ! -f "compile_commands.json" ]; then
1515
)
1616
fi
1717

18-
run-clang-tidy -quiet -p . -header-filter="^.*/$1/.*\.h$" "$1"
18+
ndk_bin="$(grep -oE '/[^ "]+/clang\+\+' compile_commands.json | head -1 | xargs dirname)"
19+
20+
run-clang-tidy -quiet -p . -clang-tidy-binary "$ndk_bin/clang-tidy" \
21+
-header-filter="^.*/$1/.*\.h$" "$1"

0 commit comments

Comments
 (0)