UNPKG

20.8 kBPlain TextView Raw
1import com.android.Version
2import org.apache.tools.ant.filters.ReplaceTokens
3import org.apache.tools.ant.taskdefs.condition.Os
4import groovy.json.JsonSlurper
5import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask
6
7import javax.inject.Inject
8import java.nio.file.Files
9import java.nio.file.Paths
10
11/**
12 * Finds the path of the installed npm package with the given name using Node's
13 * module resolution algorithm, which searches "node_modules" directories up to
14 * the file system root. This handles various cases, including:
15 *
16 * - Working in the open-source RN repo:
17 * Gradle: /path/to/react-native/ReactAndroid
18 * Node module: /path/to/react-native/node_modules/[package]
19 *
20 * - Installing RN as a dependency of an app and searching for hoisted
21 * dependencies:
22 * Gradle: /path/to/app/node_modules/react-native/ReactAndroid
23 * Node module: /path/to/app/node_modules/[package]
24 *
25 * - Working in a larger repo (e.g., Facebook) that contains RN:
26 * Gradle: /path/to/repo/path/to/react-native/ReactAndroid
27 * Node module: /path/to/repo/node_modules/[package]
28 *
29 * The search begins at the given base directory (a File object). The returned
30 * path is a string.
31 */
32static def findNodeModulePath(baseDir, packageName) {
33 def basePath = baseDir.toPath().normalize()
34 // Node's module resolution algorithm searches up to the root directory,
35 // after which the base path will be null
36 while (basePath) {
37 def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
38 if (candidatePath.toFile().exists()) {
39 return candidatePath.toString()
40 }
41 basePath = basePath.getParent()
42 }
43 return null
44}
45
46def safeExtGet(prop, fallback) {
47 rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
48}
49
50def safeAppExtGet(prop, fallback) {
51 def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
52 appProject?.ext?.has(prop) ? appProject.ext.get(prop) : fallback
53}
54
55def resolveBuildType() {
56 Gradle gradle = getGradle()
57 String tskReqStr = gradle.getStartParameter().getTaskRequests()['args'].toString()
58 return tskReqStr.contains('Release') ? 'release' : 'debug'
59}
60
61def isReanimatedExampleApp() {
62 return safeAppExtGet("isReanimatedExampleApp", false)
63}
64
65def isNewArchitectureEnabled() {
66 // To opt-in for the New Architecture, you can either:
67 // - Set `newArchEnabled` to true inside the `gradle.properties` file
68 // - Invoke gradle with `-newArchEnabled=true`
69 // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
70 return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
71}
72
73def resolveReactNativeDirectory() {
74 def reactNativeLocation = safeAppExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
75 if (reactNativeLocation != null) {
76 return file(reactNativeLocation)
77 }
78
79 // Fallback to node resolver for custom directory structures like monorepos.
80 def reactNativePackage = file(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim())
81 if(reactNativePackage.exists()) {
82 return reactNativePackage.parentFile
83 }
84
85 throw new GradleException(
86 "[Reanimated] Unable to resolve react-native location in node_modules. You should project extension property (in `app/build.gradle`) `REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
87 )
88}
89
90def getPlaygroundAppName() { // only for the development
91 String playgroundAppName = ""
92 try {
93 rootProject.getSubprojects().forEach({project ->
94 if (project.plugins.hasPlugin("com.android.application")) {
95 var projectCatalogAbsolutePath = project.projectDir.toString().replace("/android/app", "")
96 var slashPosition = projectCatalogAbsolutePath.lastIndexOf("/")
97 playgroundAppName = projectCatalogAbsolutePath.substring(slashPosition + 1)
98 }
99 })
100 } catch (_) {
101 throw new GradleException("[Reanimated] Couldn't determine playground app name.")
102 }
103 return playgroundAppName
104}
105
106def getReanimatedVersion() {
107 def inputFile = file(projectDir.path + '/../package.json')
108 def json = new JsonSlurper().parseText(inputFile.text)
109 return json.version
110}
111
112def getReanimatedMajorVersion() {
113 def (major, minor, patch) = getReanimatedVersion().tokenize('.')
114 return major.toInteger()
115}
116
117def toPlatformFileString(String path) {
118 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
119 path = path.replace(File.separatorChar, '/' as char)
120 }
121 return path
122}
123
124if (isNewArchitectureEnabled()) {
125 apply plugin: "com.facebook.react"
126}
127
128def reactNativeRootDir = resolveReactNativeDirectory()
129
130def reactProperties = new Properties()
131file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
132
133def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
134def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
135def REANIMATED_VERSION = getReanimatedVersion()
136def REANIMATED_MAJOR_VERSION = getReanimatedMajorVersion()
137def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled()
138
139// We download various C++ open-source dependencies into downloads.
140// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk.
141// After that we build native code from src/main/jni with module path pointing at third-party-ndk.
142
143def customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
144def downloadsDir = customDownloadsDir ? new File(customDownloadsDir) : new File("$buildDir/downloads")
145def thirdPartyNdkDir = new File("$buildDir/third-party-ndk")
146
147def reactNativeThirdParty = new File("$reactNativeRootDir/ReactAndroid/src/main/jni/third-party")
148def reactNativeAndroidDownloadDir = new File("$reactNativeRootDir/ReactAndroid/build/downloads")
149
150def workletsPrefabHeadersDir = project.file("$buildDir/prefab-headers/worklets")
151def reanimatedPrefabHeadersDir = project.file("$buildDir/prefab-headers/reanimated")
152
153def JS_RUNTIME = {
154 // Override JS runtime with environment variable
155 if (System.getenv("JS_RUNTIME")) {
156 return System.getenv("JS_RUNTIME")
157 }
158
159 // Enable V8 runtime if react-native-v8 is installed
160 def v8Project = rootProject.getSubprojects().find { project -> project.name == "react-native-v8" }
161 if (v8Project != null) {
162 return "v8"
163 }
164
165 // Check if Hermes is enabled in app setup
166 def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
167 if (appProject?.hermesEnabled?.toBoolean() || appProject?.ext?.react?.enableHermes?.toBoolean()) {
168 return "hermes"
169 }
170
171 // Use JavaScriptCore (JSC) by default
172 return "jsc"
173}.call()
174
175def jsRuntimeDir = {
176 if (JS_RUNTIME == "hermes") {
177 return Paths.get(reactNativeRootDir.path, "sdks", "hermes")
178 } else if (JS_RUNTIME == "v8") {
179 return findProject(":react-native-v8").getProjectDir().getParent()
180 } else {
181 return Paths.get(reactNativeRootDir.path, "ReactCommon", "jsi")
182 }
183}.call()
184
185def reactNativeArchitectures() {
186 def value = project.getProperties().get("reactNativeArchitectures")
187 return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
188}
189
190buildscript {
191 repositories {
192 google()
193 mavenCentral()
194 maven {
195 url "https://plugins.gradle.org/m2/"
196 }
197 }
198 dependencies {
199 classpath "com.android.tools.build:gradle:7.3.1"
200 classpath "de.undercouch:gradle-download-task:5.0.1"
201 classpath "com.diffplug.spotless:spotless-plugin-gradle:6.11.0"
202 }
203}
204
205if (project == rootProject) {
206 apply from: "spotless.gradle"
207}
208
209apply plugin: "com.android.library"
210apply plugin: "maven-publish"
211apply plugin: "de.undercouch.download"
212
213android {
214 compileSdkVersion safeExtGet("compileSdkVersion", 30)
215
216 def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
217 if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
218 namespace "com.swmansion.reanimated"
219 }
220
221 if (rootProject.hasProperty("ndkPath")) {
222 ndkPath rootProject.ext.ndkPath
223 }
224 if (rootProject.hasProperty("ndkVersion")) {
225 ndkVersion rootProject.ext.ndkVersion
226 }
227
228 buildFeatures {
229 prefab true
230 prefabPublishing true
231 buildConfig true
232 }
233
234 prefab {
235 worklets {
236 headers workletsPrefabHeadersDir.absolutePath
237 }
238 reanimated {
239 headers reanimatedPrefabHeadersDir.absolutePath
240 }
241 }
242
243 defaultConfig {
244 minSdkVersion safeExtGet("minSdkVersion", 16)
245 targetSdkVersion safeExtGet("targetSdkVersion", 30)
246 versionCode 1
247 versionName "1.0"
248 buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", IS_NEW_ARCHITECTURE_ENABLED.toString())
249 buildConfigField("String", "REANIMATED_VERSION_JAVA", "\"${REANIMATED_VERSION}\"")
250 externalNativeBuild {
251 cmake {
252 arguments "-DANDROID_STL=c++_shared",
253 "-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}",
254 "-DANDROID_TOOLCHAIN=clang",
255 "-DREACT_NATIVE_DIR=${toPlatformFileString(reactNativeRootDir.path)}",
256 "-DJS_RUNTIME=${JS_RUNTIME}",
257 "-DJS_RUNTIME_DIR=${jsRuntimeDir}",
258 "-DIS_NEW_ARCHITECTURE_ENABLED=${IS_NEW_ARCHITECTURE_ENABLED}",
259 "-DIS_REANIMATED_EXAMPLE_APP=${isReanimatedExampleApp()}",
260 "-DREANIMATED_VERSION=${REANIMATED_VERSION}"
261 abiFilters (*reactNativeArchitectures())
262 }
263 }
264
265 buildConfigField("boolean", "IS_INTERNAL_BUILD", "false")
266 buildConfigField("int", "EXOPACKAGE_FLAGS", "0")
267 buildConfigField("int", "REACT_NATIVE_MINOR_VERSION", REACT_NATIVE_MINOR_VERSION.toString())
268
269 consumerProguardFiles 'proguard-rules.pro'
270 }
271 externalNativeBuild {
272 cmake {
273 version = System.getenv("CMAKE_VERSION") ?: "3.22.1"
274 path "CMakeLists.txt"
275 }
276 }
277 buildTypes {
278 debug {
279 externalNativeBuild {
280 cmake {
281 if (JS_RUNTIME == "hermes") {
282 arguments "-DHERMES_ENABLE_DEBUGGER=1"
283 } else {
284 arguments "-DHERMES_ENABLE_DEBUGGER=0"
285 }
286 }
287 }
288 }
289 release {
290 externalNativeBuild {
291 cmake {
292 arguments "-DHERMES_ENABLE_DEBUGGER=0"
293 }
294 }
295 }
296 }
297 lintOptions {
298 abortOnError false
299 }
300 packagingOptions {
301 doNotStrip resolveBuildType() == 'debug' ? "**/**/*.so" : ''
302 // For some reason gradle only complains about the duplicated version of librrc_root and libreact_render libraries
303 // while there are more libraries copied in intermediates folder of the lib build directory, we exclude
304 // only the ones that make the build fail (ideally we should only include libreanimated but we
305 // are only allowed to specify exclude patterns)
306 excludes = [
307 "META-INF",
308 "META-INF/**",
309 "**/libc++_shared.so",
310 "**/libfbjni.so",
311 "**/libjsi.so",
312 "**/libfolly_json.so",
313 "**/libfolly_runtime.so",
314 "**/libglog.so",
315 "**/libhermes.so",
316 "**/libhermes-executor-debug.so",
317 "**/libhermes_executor.so",
318 "**/libhermestooling.so",
319 "**/libreactnativejni.so",
320 "**/libturbomodulejsijni.so",
321 "**/libreactnative.so",
322 "**/libreact_nativemodule_core.so",
323 "**/libreact_render*.so",
324 "**/librrc_root.so",
325 "**/libjscexecutor.so",
326 "**/libv8executor.so",
327 ]
328 }
329 tasks.withType(JavaCompile) {
330 compileTask ->
331 compileTask.dependsOn(packageNdkLibs)
332 }
333 configurations {
334 extractHeaders
335 extractSO
336 }
337 compileOptions {
338 sourceCompatibility JavaVersion.VERSION_1_8
339 targetCompatibility JavaVersion.VERSION_1_8
340 }
341 sourceSets.main {
342 java {
343 if (IS_NEW_ARCHITECTURE_ENABLED) {
344 srcDirs += "src/fabric/java"
345 } else {
346 srcDirs += "src/paper/java"
347 }
348
349 // messageQueueThread
350 if (REANIMATED_MAJOR_VERSION > 2) {
351 if (REACT_NATIVE_MINOR_VERSION <= 72) {
352 srcDirs += "src/reactNativeVersionPatch/messageQueueThread/72"
353 } else {
354 srcDirs += "src/reactNativeVersionPatch/messageQueueThread/latest"
355 }
356 }
357
358 // ReanimatedUIManager & ReanimatedUIImplementation
359 if (REACT_NATIVE_MINOR_VERSION <= 73) {
360 srcDirs += "src/reactNativeVersionPatch/ReanimatedUIManager/73"
361 } else if (REACT_NATIVE_MINOR_VERSION <= 74) {
362 srcDirs += "src/reactNativeVersionPatch/ReanimatedUIManager/74"
363 } else {
364 srcDirs += "src/reactNativeVersionPatch/ReanimatedUIManager/latest"
365 }
366
367 // ReactHost
368 if (REACT_NATIVE_MINOR_VERSION <= 72) {
369 srcDirs += "src/reactNativeVersionPatch/ReactHost/72"
370 } else {
371 srcDirs += "src/reactNativeVersionPatch/ReactHost/latest"
372 }
373
374 // ReactFeatureFlags
375 if (IS_NEW_ARCHITECTURE_ENABLED) {
376 if (REACT_NATIVE_MINOR_VERSION <= 72) {
377 srcDirs += "src/reactNativeVersionPatch/ReactFeatureFlagsWrapper/72"
378 } else if (REACT_NATIVE_MINOR_VERSION <= 74) {
379 srcDirs += "src/reactNativeVersionPatch/ReactFeatureFlagsWrapper/74"
380 } else {
381 srcDirs += "src/reactNativeVersionPatch/ReactFeatureFlagsWrapper/latest"
382 }
383 }
384
385 if (IS_NEW_ARCHITECTURE_ENABLED) {
386 // RuntimeExecutor and CallInvokerHolder
387 if (REACT_NATIVE_MINOR_VERSION <= 73) {
388 srcDirs += "src/reactNativeVersionPatch/NativeProxyFabric/73"
389 } else if (REACT_NATIVE_MINOR_VERSION <= 74) {
390 srcDirs += "src/reactNativeVersionPatch/NativeProxyFabric/74"
391 } else {
392 srcDirs += "src/reactNativeVersionPatch/NativeProxyFabric/latest"
393 }
394 } else {
395 // CallInvokerHolder
396 if (REACT_NATIVE_MINOR_VERSION <= 74) {
397 srcDirs += "src/reactNativeVersionPatch/NativeProxyPaper/74"
398 } else {
399 srcDirs += "src/reactNativeVersionPatch/NativeProxyPaper/latest"
400 }
401 }
402
403 // BorderRadiiDrawableUtils
404 if (REACT_NATIVE_MINOR_VERSION <= 74) {
405 srcDirs += "src/reactNativeVersionPatch/BorderRadiiDrawableUtils/74"
406 } else if (REACT_NATIVE_MINOR_VERSION <= 75) {
407 srcDirs += "src/reactNativeVersionPatch/BorderRadiiDrawableUtils/75"
408 } else {
409 srcDirs += "src/reactNativeVersionPatch/BorderRadiiDrawableUtils/latest"
410 }
411
412 // ReanimatedNativeHierarchyManager
413 if (REACT_NATIVE_MINOR_VERSION <= 75) {
414 srcDirs += "src/reactNativeVersionPatch/ReanimatedNativeHierarchyManager/75"
415 } else {
416 srcDirs += "src/reactNativeVersionPatch/ReanimatedNativeHierarchyManager/latest"
417 }
418 }
419 }
420 tasks.withType(ExternalNativeBuildJsonTask) {
421 compileTask ->
422 compileTask.doLast {
423 if (!isReanimatedExampleApp()) {
424 return
425 }
426 def monorepoDir = new File("${project.projectDir}/../../..")
427
428 def generated = new File("${compileTask.abi.getCxxBuildFolder()}/compile_commands.json")
429 def output = new File("${monorepoDir}/compile_commands.json")
430
431 output.text = generated.text
432
433 println("Generated clangd metadata.")
434 }
435 }
436}
437
438def assertLatestReactNativeWithNewArchitecture = task assertLatestReactNativeWithNewArchitectureTask {
439 onlyIf { IS_NEW_ARCHITECTURE_ENABLED && REANIMATED_MAJOR_VERSION == 3 && REACT_NATIVE_MINOR_VERSION < 74 }
440 doFirst {
441 // If you change the minimal React Native version remember to update Compatibility Table in docs
442 throw new GradleException(
443 "[Reanimated] Outdated version of React Native for New Architecture. Reanimated " + REANIMATED_VERSION + " supports the New Architecture on React Native 0.74.0+. See https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#outdated-version-of-react-native-for-new-architecture for more information."
444 )
445 }
446}
447
448def assertMinimalReactNativeVersion = task assertMinimalReactNativeVersionTask {
449 onlyIf { REACT_NATIVE_MINOR_VERSION < 71 }
450 doFirst {
451 // If you change the minimal React Native version remember to update Compatibility Table in docs
452 throw new GradleException("[Reanimated] Unsupported React Native version. Please use 0.71 or newer.")
453 }
454}
455
456task prepareWorkletsHeadersForPrefabs(type: Copy) {
457 from("$projectDir/src/main/cpp")
458 from("$projectDir/../Common/cpp")
459 include("worklets/**/*.h")
460 into(workletsPrefabHeadersDir)
461}
462
463task prepareReanimatedHeadersForPrefabs(type: Copy) {
464 from("$projectDir/src/main/cpp")
465 from("$projectDir/../Common/cpp")
466 include("reanimated/**/*.h")
467 into(reanimatedPrefabHeadersDir)
468}
469
470tasks.preBuild {
471 dependsOn assertLatestReactNativeWithNewArchitecture, assertMinimalReactNativeVersion
472}
473
474task cleanCmakeCache() {
475 tasks.getByName("clean").dependsOn(cleanCmakeCache)
476 doFirst {
477 delete "${projectDir}/.cxx"
478 }
479}
480
481task printVersions {
482 println "Android gradle plugin: ${Version.ANDROID_GRADLE_PLUGIN_VERSION}"
483 println "Gradle: ${project.gradle.gradleVersion}"
484}
485
486task createNativeDepsDirectories() {
487 downloadsDir.mkdirs()
488 thirdPartyNdkDir.mkdirs()
489 workletsPrefabHeadersDir.mkdirs()
490 reanimatedPrefabHeadersDir.mkdirs()
491}
492
493task packageNdkLibs(type: Copy) {
494 from("$buildDir/reanimated-ndk/all")
495 include("**/libreanimated.so")
496 include("**/libworklets.so")
497 into("$projectDir/src/main/jniLibs")
498}
499
500repositories {
501 mavenCentral()
502 mavenLocal()
503 maven {
504 // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
505 url "$reactNativeRootDir/android"
506 }
507 maven {
508 // Android JSC is installed from npm
509 url "$reactNativeRootDir/../jsc-android/dist"
510 }
511 google()
512}
513
514dependencies {
515 implementation "com.facebook.yoga:proguard-annotations:1.19.0"
516 implementation "androidx.transition:transition:1.1.0"
517 implementation "androidx.core:core:1.6.0"
518
519 implementation "com.facebook.react:react-android" // version substituted by RNGP
520 if (JS_RUNTIME == "hermes") {
521 implementation "com.facebook.react:hermes-android" // version substituted by RNGP
522 }
523}
524
525def nativeBuildDependsOn(dependsOnTask) {
526 def buildTasks = tasks.findAll({ task -> (
527 !task.name.contains("Clean")
528 && (task.name.contains("externalNative")
529 || task.name.contains("CMake")
530 || task.name.contains("generateJsonModel")
531 )
532 ) })
533 buildTasks.forEach { task -> task.dependsOn(dependsOnTask) }
534}
535
536afterEvaluate {
537 preBuild.dependsOn(prepareWorkletsHeadersForPrefabs)
538 preBuild.dependsOn(prepareReanimatedHeadersForPrefabs)
539
540 tasks.forEach({ task ->
541 if (task.name.contains("JniLibFolders")) {
542 task.dependsOn(packageNdkLibs)
543 }
544 })
545
546 if (JS_RUNTIME == "hermes") {
547 // Do nothing
548 } else if (JS_RUNTIME == "v8") {
549 def buildTasks = tasks.findAll({ task ->
550 !task.name.contains("Clean") && (task.name.contains("externalNative") || task.name.contains("CMake") || task.name.startsWith("generateJsonModel")) })
551 buildTasks.forEach { task ->
552 def buildType = task.name.endsWith('Debug') ? 'Debug' : 'Release'
553 task.dependsOn(":react-native-v8:copy${buildType}JniLibsProjectOnly")
554 }
555 } else if (JS_RUNTIME == "jsc") {
556 // Do nothing
557 } else {
558 throw GradleScriptException("[Reanimated] Unknown JS runtime ${JS_RUNTIME}.")
559 }
560}