1 | import com.android.Version
|
2 | import org.apache.tools.ant.filters.ReplaceTokens
|
3 | import org.apache.tools.ant.taskdefs.condition.Os
|
4 | import groovy.json.JsonSlurper
|
5 | import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask
|
6 |
|
7 | import javax.inject.Inject
|
8 | import java.nio.file.Files
|
9 | import java.nio.file.Paths
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | static def findNodeModulePath(baseDir, packageName) {
|
33 | def basePath = baseDir.toPath().normalize()
|
34 |
|
35 |
|
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 |
|
46 | def safeExtGet(prop, fallback) {
|
47 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
48 | }
|
49 |
|
50 | def 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 |
|
55 | def resolveBuildType() {
|
56 | Gradle gradle = getGradle()
|
57 | String tskReqStr = gradle.getStartParameter().getTaskRequests()['args'].toString()
|
58 | return tskReqStr.contains('Release') ? 'release' : 'debug'
|
59 | }
|
60 |
|
61 | def isReanimatedExampleApp() {
|
62 | return safeAppExtGet("isReanimatedExampleApp", false)
|
63 | }
|
64 |
|
65 | def isNewArchitectureEnabled() {
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
|
71 | }
|
72 |
|
73 | def resolveReactNativeDirectory() {
|
74 | def reactNativeLocation = safeAppExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
|
75 | if (reactNativeLocation != null) {
|
76 | return file(reactNativeLocation)
|
77 | }
|
78 |
|
79 |
|
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 |
|
90 | def getPlaygroundAppName() {
|
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 |
|
106 | def getReanimatedVersion() {
|
107 | def inputFile = file(projectDir.path + '/../package.json')
|
108 | def json = new JsonSlurper().parseText(inputFile.text)
|
109 | return json.version
|
110 | }
|
111 |
|
112 | def getReanimatedMajorVersion() {
|
113 | def (major, minor, patch) = getReanimatedVersion().tokenize('.')
|
114 | return major.toInteger()
|
115 | }
|
116 |
|
117 | def toPlatformFileString(String path) {
|
118 | if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
119 | path = path.replace(File.separatorChar, '/' as char)
|
120 | }
|
121 | return path
|
122 | }
|
123 |
|
124 | if (isNewArchitectureEnabled()) {
|
125 | apply plugin: "com.facebook.react"
|
126 | }
|
127 |
|
128 | def reactNativeRootDir = resolveReactNativeDirectory()
|
129 |
|
130 | def reactProperties = new Properties()
|
131 | file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
|
132 |
|
133 | def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
|
134 | def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
|
135 | def REANIMATED_VERSION = getReanimatedVersion()
|
136 | def REANIMATED_MAJOR_VERSION = getReanimatedMajorVersion()
|
137 | def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled()
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 | def customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
|
144 | def downloadsDir = customDownloadsDir ? new File(customDownloadsDir) : new File("$buildDir/downloads")
|
145 | def thirdPartyNdkDir = new File("$buildDir/third-party-ndk")
|
146 |
|
147 | def reactNativeThirdParty = new File("$reactNativeRootDir/ReactAndroid/src/main/jni/third-party")
|
148 | def reactNativeAndroidDownloadDir = new File("$reactNativeRootDir/ReactAndroid/build/downloads")
|
149 |
|
150 | def workletsPrefabHeadersDir = project.file("$buildDir/prefab-headers/worklets")
|
151 | def reanimatedPrefabHeadersDir = project.file("$buildDir/prefab-headers/reanimated")
|
152 |
|
153 | def JS_RUNTIME = {
|
154 |
|
155 | if (System.getenv("JS_RUNTIME")) {
|
156 | return System.getenv("JS_RUNTIME")
|
157 | }
|
158 |
|
159 |
|
160 | def v8Project = rootProject.getSubprojects().find { project -> project.name == "react-native-v8" }
|
161 | if (v8Project != null) {
|
162 | return "v8"
|
163 | }
|
164 |
|
165 |
|
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 |
|
172 | return "jsc"
|
173 | }.call()
|
174 |
|
175 | def 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 |
|
185 | def reactNativeArchitectures() {
|
186 | def value = project.getProperties().get("reactNativeArchitectures")
|
187 | return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
188 | }
|
189 |
|
190 | buildscript {
|
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 |
|
205 | if (project == rootProject) {
|
206 | apply from: "spotless.gradle"
|
207 | }
|
208 |
|
209 | apply plugin: "com.android.library"
|
210 | apply plugin: "maven-publish"
|
211 | apply plugin: "de.undercouch.download"
|
212 |
|
213 | android {
|
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 |
|
303 |
|
304 |
|
305 |
|
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 |
|
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 |
|
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 |
|
368 | if (REACT_NATIVE_MINOR_VERSION <= 72) {
|
369 | srcDirs += "src/reactNativeVersionPatch/ReactHost/72"
|
370 | } else {
|
371 | srcDirs += "src/reactNativeVersionPatch/ReactHost/latest"
|
372 | }
|
373 |
|
374 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
438 | def assertLatestReactNativeWithNewArchitecture = task assertLatestReactNativeWithNewArchitectureTask {
|
439 | onlyIf { IS_NEW_ARCHITECTURE_ENABLED && REANIMATED_MAJOR_VERSION == 3 && REACT_NATIVE_MINOR_VERSION < 74 }
|
440 | doFirst {
|
441 |
|
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 |
|
448 | def assertMinimalReactNativeVersion = task assertMinimalReactNativeVersionTask {
|
449 | onlyIf { REACT_NATIVE_MINOR_VERSION < 71 }
|
450 | doFirst {
|
451 |
|
452 | throw new GradleException("[Reanimated] Unsupported React Native version. Please use 0.71 or newer.")
|
453 | }
|
454 | }
|
455 |
|
456 | task prepareWorkletsHeadersForPrefabs(type: Copy) {
|
457 | from("$projectDir/src/main/cpp")
|
458 | from("$projectDir/../Common/cpp")
|
459 | include("worklets/**/*.h")
|
460 | into(workletsPrefabHeadersDir)
|
461 | }
|
462 |
|
463 | task prepareReanimatedHeadersForPrefabs(type: Copy) {
|
464 | from("$projectDir/src/main/cpp")
|
465 | from("$projectDir/../Common/cpp")
|
466 | include("reanimated/**/*.h")
|
467 | into(reanimatedPrefabHeadersDir)
|
468 | }
|
469 |
|
470 | tasks.preBuild {
|
471 | dependsOn assertLatestReactNativeWithNewArchitecture, assertMinimalReactNativeVersion
|
472 | }
|
473 |
|
474 | task cleanCmakeCache() {
|
475 | tasks.getByName("clean").dependsOn(cleanCmakeCache)
|
476 | doFirst {
|
477 | delete "${projectDir}/.cxx"
|
478 | }
|
479 | }
|
480 |
|
481 | task printVersions {
|
482 | println "Android gradle plugin: ${Version.ANDROID_GRADLE_PLUGIN_VERSION}"
|
483 | println "Gradle: ${project.gradle.gradleVersion}"
|
484 | }
|
485 |
|
486 | task createNativeDepsDirectories() {
|
487 | downloadsDir.mkdirs()
|
488 | thirdPartyNdkDir.mkdirs()
|
489 | workletsPrefabHeadersDir.mkdirs()
|
490 | reanimatedPrefabHeadersDir.mkdirs()
|
491 | }
|
492 |
|
493 | task 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 |
|
500 | repositories {
|
501 | mavenCentral()
|
502 | mavenLocal()
|
503 | maven {
|
504 |
|
505 | url "$reactNativeRootDir/android"
|
506 | }
|
507 | maven {
|
508 |
|
509 | url "$reactNativeRootDir/../jsc-android/dist"
|
510 | }
|
511 | google()
|
512 | }
|
513 |
|
514 | dependencies {
|
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"
|
520 | if (JS_RUNTIME == "hermes") {
|
521 | implementation "com.facebook.react:hermes-android"
|
522 | }
|
523 | }
|
524 |
|
525 | def 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 |
|
536 | afterEvaluate {
|
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 |
|
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 |
|
557 | } else {
|
558 | throw GradleScriptException("[Reanimated] Unknown JS runtime ${JS_RUNTIME}.")
|
559 | }
|
560 | }
|