From ae220a096b6e1e4fe24e9a05d2e0a01f653efb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Thu, 4 Dec 2025 19:31:54 +0100 Subject: [PATCH 1/2] chore: modernize build.gradle scripts --- .../android/build.gradle | 120 +++++++++--------- .../android/build.gradle | 90 +++++++------ 2 files changed, 111 insertions(+), 99 deletions(-) diff --git a/packages/react-native-reanimated/android/build.gradle b/packages/react-native-reanimated/android/build.gradle index 86bace765ea9..b281ed5d0545 100644 --- a/packages/react-native-reanimated/android/build.gradle +++ b/packages/react-native-reanimated/android/build.gradle @@ -1,7 +1,7 @@ import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask import groovy.json.JsonSlurper -import java.nio.file.Paths import org.apache.tools.ant.taskdefs.condition.Os +import javax.inject.Inject def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback @@ -27,19 +27,15 @@ def resolveReactNativeDirectory() { } // Fallback to node resolver for custom directory structures like monorepos. - def reactNativePackage = file( - providers.exec { - workingDir(rootDir) - commandLine("node", "--print", "require.resolve('react-native/package.json')") - }.standardOutput.asText.get().trim() - ) + def reactNativePackage = file(providers.exec { + workingDir(rootDir) + commandLine("node", "--print", "require.resolve('react-native/package.json')") + }.standardOutput.asText.get().trim()) if (reactNativePackage.exists()) { return reactNativePackage.parentFile } - throw new GradleException( - "[Reanimated] Unable to resolve react-native location in node_modules. You should set project extension property (in `app/build.gradle`) named `REACT_NATIVE_NODE_MODULES_DIR` with the path to react-native in node_modules." - ) + throw new GradleException("[Reanimated] Unable to resolve react-native location in node_modules. You should set project extension property (in `app/build.gradle`) named `REACT_NATIVE_NODE_MODULES_DIR` with the path to react-native in node_modules.") } def resolveReactNativeWorkletsDirectory() { @@ -49,19 +45,15 @@ def resolveReactNativeWorkletsDirectory() { } // Fallback to node resolver for custom directory structures like monorepos. - def reactNativeWorkletsPackage = file( - providers.exec { - workingDir(rootDir) - commandLine("node", "--print", "require.resolve('react-native-worklets/package.json')") - }.standardOutput.asText.get().trim() - ) + def reactNativeWorkletsPackage = file(providers.exec { + workingDir(rootDir) + commandLine("node", "--print", "require.resolve('react-native-worklets/package.json')") + }.standardOutput.asText.get().trim()) if (reactNativeWorkletsPackage.exists()) { return reactNativeWorkletsPackage.parentFile } - throw new GradleException( - "[Reanimated] Unable to resolve react-native-worklets location in node_modules. You should set project extension property (in `app/build.gradle`) named `REACT_NATIVE_WORKLETS_NODE_MODULES_DIR` with the path to react-native-worklets in node_modules." - ) + throw new GradleException("[Reanimated] Unable to resolve react-native-worklets location in node_modules. You should set project extension property (in `app/build.gradle`) named `REACT_NATIVE_WORKLETS_NODE_MODULES_DIR` with the path to react-native-worklets in node_modules.") } def getReactNativeVersion() { @@ -82,11 +74,11 @@ def getReanimatedVersion() { return json.version } -def toPlatformFileString(String path) { - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - path = path.replace(File.separatorChar, '/' as char) - } - return path +static def toPlatformFileString(String path) { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + path = path.replace(File.separatorChar, '/' as char) + } + return path } def getReanimatedStaticFeatureFlags() { @@ -96,15 +88,13 @@ def getReanimatedStaticFeatureFlags() { if (!staticFeatureFlagsFile.exists()) { throw new GradleException("[Reanimated] Feature flags file not found at ${staticFeatureFlagsFile.absolutePath}.") } - new JsonSlurper().parseText(staticFeatureFlagsFile.text).each { key, value -> - featureFlags[key] = value.toString() + new JsonSlurper().parseText(staticFeatureFlagsFile.text).each { key, value -> featureFlags[key] = value.toString() } def packageJsonFile = file(rootDir.path + "/../package.json") if (packageJsonFile.exists()) { def packageJson = new JsonSlurper().parseText(packageJsonFile.text) - packageJson.reanimated?.staticFeatureFlags?.each { key, value -> - featureFlags[key] = value.toString() + packageJson.reanimated?.staticFeatureFlags?.each { key, value -> featureFlags[key] = value.toString() } } @@ -129,7 +119,7 @@ def REANIMATED_FEATURE_FLAGS = getReanimatedStaticFeatureFlags() // Set version for prefab version REANIMATED_VERSION -def reanimatedPrefabHeadersDir = project.file("$buildDir/prefab-headers/reanimated") +def reanimatedPrefabHeadersDir = project.file("${getLayout().getBuildDirectory()}/prefab-headers/reanimated") def reactNativeArchitectures() { def value = project.getProperties().get("reactNativeArchitectures") @@ -142,9 +132,9 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:8.2.1" + classpath "com.android.tools.build:gradle:8.13.1" classpath "de.undercouch:gradle-download-task:5.6.0" - classpath "com.diffplug.spotless:spotless-plugin-gradle:6.25.0" + classpath "com.diffplug.spotless:spotless-plugin-gradle:8.1.0" } } @@ -204,7 +194,7 @@ android { "-DREANIMATED_VERSION=${REANIMATED_VERSION}", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DREANIMATED_FEATURE_FLAGS=${REANIMATED_FEATURE_FLAGS}" - abiFilters (*reactNativeArchitectures()) + abiFilters(*reactNativeArchitectures()) targets("reanimated") } } @@ -232,25 +222,23 @@ android { // while there are more libraries copied in intermediates folder of the lib build directory, we exclude // only the ones that make the build fail (ideally we should only include libreanimated but we // are only allowed to specify exclude patterns) - excludes = [ - "META-INF", - "META-INF/**", - "**/libc++_shared.so", - "**/libfbjni.so", - "**/libjsi.so", - "**/libhermes.so", - "**/libhermesvm.so", - "**/libhermestooling.so", - "**/libreactnative.so", - "**/libjscexecutor.so", - ] + excludes = ["META-INF", + "META-INF/**", + "**/libc++_shared.so", + "**/libfbjni.so", + "**/libjsi.so", + "**/libhermes.so", + "**/libhermesvm.so", + "**/libhermestooling.so", + "**/libreactnative.so", + "**/libjscexecutor.so",] } compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } - tasks.withType(ExternalNativeBuildJsonTask) { - compileTask -> + tasks.withType(ExternalNativeBuildJsonTask).tap { + configureEach { compileTask -> compileTask.doLast { if (!IS_REANIMATED_EXAMPLE_APP) { return @@ -263,6 +251,7 @@ android { println("Generated clangd metadata.") } + } } } @@ -272,7 +261,7 @@ def validateReactNativeVersionResult = providers.exec { ignoreExitValue = true } -task assertMinimalReactNativeVersionTask { +tasks.register('assertMinimalReactNativeVersionTask') { doFirst { if (validateReactNativeVersionResult.getResult().get().exitValue != 0) { throw new GradleException(validateReactNativeVersionResult.getStandardError().getAsText().get().trim()) @@ -282,7 +271,7 @@ task assertMinimalReactNativeVersionTask { preBuild.dependsOn(assertMinimalReactNativeVersionTask) -task assertNewArchitectureEnabledTask { +tasks.register('assertNewArchitectureEnabledTask') { onlyIf { !IS_NEW_ARCHITECTURE_ENABLED } doFirst { throw new GradleException("[Reanimated] Reanimated requires new architecture to be enabled. Please enable it by setting `newArchEnabled` to `true` in `gradle.properties`.") @@ -297,7 +286,7 @@ def validateWorkletsBuildResult = providers.exec { ignoreExitValue = true } -task assertWorkletsVersionTask { +tasks.register('assertWorkletsVersionTask') { doFirst { if (validateWorkletsBuildResult.getResult().get().exitValue != 0) { throw new GradleException(validateWorkletsBuildResult.getStandardError().getAsText().get().trim()) @@ -307,17 +296,25 @@ task assertWorkletsVersionTask { preBuild.dependsOn(assertWorkletsVersionTask) -task prepareReanimatedHeadersForPrefabs(type: Copy) { +tasks.register('prepareReanimatedHeadersForPrefabs', Copy) { from("$projectDir/src/main/cpp") from("$projectDir/../Common/cpp") include("reanimated/**/*.h") into(reanimatedPrefabHeadersDir) } -task cleanCmakeCache() { - tasks.getByName("clean").dependsOn(cleanCmakeCache) +interface FSService { + @Inject + FileSystemOperations getFs() +} + +tasks.register('cleanCMakeCache') { + def fsProvider = project.objects.newInstance(FSService) + def cxxDir = file("${projectDir}/.cxx") doFirst { - delete "${projectDir}/.cxx" + fsProvider.fs.delete { + delete cxxDir + } } } @@ -328,11 +325,11 @@ repositories { dependencies { implementation "com.facebook.yoga:proguard-annotations:1.19.0" - implementation "androidx.transition:transition:1.1.0" - implementation "androidx.core:core:1.6.0" + implementation "androidx.transition:transition:1.6.0" + implementation "androidx.core:core:1.17.0" implementation "com.facebook.react:react-android" // version substituted by RNGP - + if (project == rootProject) { // This is needed for linting in Reanimated's repo. } else { @@ -350,7 +347,14 @@ if (project != rootProject) { evaluationDependsOn(":react-native-worklets") afterEvaluate { - tasks.getByName("externalNativeBuildDebug").dependsOn(findProject(":react-native-worklets").tasks.getByName("externalNativeBuildDebug")) - tasks.getByName("externalNativeBuildRelease").dependsOn(findProject(":react-native-worklets").tasks.getByName("externalNativeBuildRelease")) + tasks.named("externalNativeBuildDebug").configure { + dependsOn(findProject(":react-native-worklets").tasks.named("externalNativeBuildDebug")) + } + tasks.named("externalNativeBuildRelease").configure { + dependsOn(findProject(":react-native-worklets").tasks.named("externalNativeBuildRelease")) + } + tasks.named("clean") { + it.finalizedBy(cleanCMakeCache) + } } } diff --git a/packages/react-native-worklets/android/build.gradle b/packages/react-native-worklets/android/build.gradle index 61c565b3b26c..e7019d51feab 100644 --- a/packages/react-native-worklets/android/build.gradle +++ b/packages/react-native-worklets/android/build.gradle @@ -1,7 +1,7 @@ import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask import groovy.json.JsonSlurper -import java.nio.file.Paths import org.apache.tools.ant.taskdefs.condition.Os +import javax.inject.Inject def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback @@ -27,19 +27,15 @@ def resolveReactNativeDirectory() { } // Fallback to node resolver for custom directory structures like monorepos. - def reactNativePackage = file( - providers.exec { - workingDir(rootDir) - commandLine("node", "--print", "require.resolve('react-native/package.json')") - }.standardOutput.asText.get().trim() - ) + def reactNativePackage = file(providers.exec { + workingDir(rootDir) + commandLine("node", "--print", "require.resolve('react-native/package.json')") + }.standardOutput.asText.get().trim()) if (reactNativePackage.exists()) { return reactNativePackage.parentFile } - throw new GradleException( - "[Worklets] Unable to resolve react-native location in node_modules. You should set project extension property (in `app/build.gradle`) named `REACT_NATIVE_NODE_MODULES_DIR` with the path to react-native in node_modules." - ) + throw new GradleException("[Worklets] Unable to resolve react-native location in node_modules. You should set project extension property (in `app/build.gradle`) named `REACT_NATIVE_NODE_MODULES_DIR` with the path to react-native in node_modules.") } def getReactNativeVersion() { @@ -60,7 +56,7 @@ def getWorkletsVersion() { return json.version } -def toPlatformFileString(String path) { +static def toPlatformFileString(String path) { if (Os.isFamily(Os.FAMILY_WINDOWS)) { path = path.replace(File.separatorChar, '/' as char) } @@ -74,15 +70,13 @@ def getStaticFeatureFlags() { if (!staticFeatureFlagsFile.exists()) { throw new GradleException("[Worklets] Feature flags file not found at ${staticFeatureFlagsFile.absolutePath}.") } - new JsonSlurper().parseText(staticFeatureFlagsFile.text).each { key, value -> - featureFlags[key] = value.toString() + new JsonSlurper().parseText(staticFeatureFlagsFile.text).each { key, value -> featureFlags[key] = value.toString() } def packageJsonFile = file(rootDir.path + "/../package.json") if (packageJsonFile.exists()) { def packageJson = new JsonSlurper().parseText(packageJsonFile.text) - packageJson.worklets?.staticFeatureFlags?.each { key, value -> - featureFlags[key] = value.toString() + packageJson.worklets?.staticFeatureFlags?.each { key, value -> featureFlags[key] = value.toString() } } @@ -107,7 +101,7 @@ def HERMES_V1_ENABLED = safeAppExtGet("hermesV1Enabled", false) // Set version for prefab version WORKLETS_VERSION -def workletsPrefabHeadersDir = project.file("$buildDir/prefab-headers/worklets") +def workletsPrefabHeadersDir = project.file("${getLayout().getBuildDirectory()}/prefab-headers/worklets") def JS_RUNTIME = { // Override JS runtime with environment variable @@ -136,9 +130,9 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:8.2.1" + classpath "com.android.tools.build:gradle:8.13.1" classpath "de.undercouch:gradle-download-task:5.6.0" - classpath "com.diffplug.spotless:spotless-plugin-gradle:6.25.0" + classpath "com.diffplug.spotless:spotless-plugin-gradle:8.1.0" } } @@ -201,7 +195,7 @@ android { "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DWORKLETS_FEATURE_FLAGS=${WORKLETS_FEATURE_FLAGS}", "-DHERMES_V1_ENABLED=${HERMES_V1_ENABLED}" - abiFilters (*reactNativeArchitectures()) + abiFilters(*reactNativeArchitectures()) targets("worklets") } } @@ -242,18 +236,16 @@ android { // while there are more libraries copied in intermediates folder of the lib build directory, we exclude // only the ones that make the build fail (ideally we should only include libreanimated but we // are only allowed to specify exclude patterns) - excludes = [ - "META-INF", - "META-INF/**", - "**/libc++_shared.so", - "**/libfbjni.so", - "**/libjsi.so", - "**/libhermes.so", - "**/libhermesvm.so", - "**/libhermestooling.so", - "**/libreactnative.so", - "**/libjscexecutor.so", - ] + excludes = ["META-INF", + "META-INF/**", + "**/libc++_shared.so", + "**/libfbjni.so", + "**/libjsi.so", + "**/libhermes.so", + "**/libhermesvm.so", + "**/libhermestooling.so", + "**/libreactnative.so", + "**/libjscexecutor.so",] } compileOptions { sourceCompatibility JavaVersion.VERSION_17 @@ -270,8 +262,8 @@ android { } } } - tasks.withType(ExternalNativeBuildJsonTask) { - compileTask -> + tasks.withType(ExternalNativeBuildJsonTask).tap { + configureEach { compileTask -> compileTask.doLast { if (!IS_REANIMATED_EXAMPLE_APP) { return @@ -284,6 +276,7 @@ android { println("Generated clangd metadata.") } + } } } @@ -293,7 +286,7 @@ def validateReactNativeVersionResult = providers.exec { ignoreExitValue = true } -task assertMinimalReactNativeVersionTask { +tasks.register('assertMinimalReactNativeVersionTask') { doFirst { if (validateReactNativeVersionResult.getResult().get().exitValue != 0) { throw new GradleException(validateReactNativeVersionResult.getStandardError().getAsText().get().trim()) @@ -303,7 +296,7 @@ task assertMinimalReactNativeVersionTask { preBuild.dependsOn(assertMinimalReactNativeVersionTask) -task assertNewArchitectureEnabledTask { +tasks.register('assertNewArchitectureEnabledTask') { onlyIf { !IS_NEW_ARCHITECTURE_ENABLED } doFirst { throw new GradleException("[Worklets] Worklets require new architecture to be enabled. Please enable it by setting `newArchEnabled` to `true` in `gradle.properties`.") @@ -312,17 +305,25 @@ task assertNewArchitectureEnabledTask { preBuild.dependsOn(assertNewArchitectureEnabledTask) -task prepareWorkletsHeadersForPrefabs(type: Copy) { +tasks.register('prepareWorkletsHeadersForPrefabs', Copy) { from("$projectDir/src/main/cpp") from("$projectDir/../Common/cpp") include("worklets/**/*.h") into(workletsPrefabHeadersDir) } -task cleanCmakeCache() { - tasks.getByName("clean").dependsOn(cleanCmakeCache) +interface FSService { + @Inject + FileSystemOperations getFs() +} + +tasks.register('cleanCMakeCache') { + def fsProvider = project.objects.newInstance(FSService) + def cxxDir = file("${projectDir}/.cxx") doFirst { - delete "${projectDir}/.cxx" + fsProvider.fs.delete { + delete cxxDir + } } } @@ -333,8 +334,8 @@ repositories { dependencies { implementation "com.facebook.yoga:proguard-annotations:1.19.0" - implementation "androidx.transition:transition:1.1.0" - implementation "androidx.core:core:1.6.0" + implementation "androidx.transition:transition:1.6.0" + implementation "androidx.core:core:1.17.0" implementation "com.facebook.react:react-android" // version substituted by RNGP if (JS_RUNTIME == "hermes") { @@ -343,3 +344,10 @@ dependencies { } preBuild.dependsOn(prepareWorkletsHeadersForPrefabs) + +afterEvaluate { + tasks.named("clean") { + it.finalizedBy(cleanCMakeCache) + } +} + From 31d7d16fd94d532ce46d539ed1d93e7c320c786a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Thu, 4 Dec 2025 19:51:40 +0100 Subject: [PATCH 2/2] chore: bump gradle distribution link --- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-worklets/android/gradle/wrapper/gradle-wrapper.properties b/packages/react-native-worklets/android/gradle/wrapper/gradle-wrapper.properties index 510e4efae0b8..d30212c04be1 100644 --- a/packages/react-native-worklets/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/react-native-worklets/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists