diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f78a6af837..dbc3ce4a040f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/packages/gradle-plugin/gradle/libs.versions.toml b/packages/gradle-plugin/gradle/libs.versions.toml index c7502fcefd98..9922e5575473 100644 --- a/packages/gradle-plugin/gradle/libs.versions.toml +++ b/packages/gradle-plugin/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] -agp = "8.12.0" +agp = "9.1.0" gson = "2.8.9" guava = "31.0.1-jre" javapoet = "1.13.0" junit = "4.13.2" -kotlin = "2.1.20" +kotlin = "2.3.0" assertj = "3.25.1" ktfmt = "0.22.0" diff --git a/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 37f78a6af837..dbc3ce4a040f 100644 --- a/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/packages/gradle-plugin/react-native-gradle-plugin/build.gradle.kts b/packages/gradle-plugin/react-native-gradle-plugin/build.gradle.kts index 85dadeba0c42..8f3b931b8e43 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/build.gradle.kts +++ b/packages/gradle-plugin/react-native-gradle-plugin/build.gradle.kts @@ -64,7 +64,7 @@ kotlin { jvmToolchain(17) } tasks.withType().configureEach { compilerOptions { - apiVersion.set(KotlinVersion.KOTLIN_1_8) + apiVersion.set(KotlinVersion.KOTLIN_2_3) // See comment above on JDK 11 support jvmTarget.set(JvmTarget.JVM_11) allWarningsAsErrors.set( diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index e7859ff6ad0b..9440eee3f3fe 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -234,12 +234,12 @@ class ReactPlugin : Plugin { if (isLibrary) { project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext -> - ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile) + ext.sourceSets.getByName("main").java.directories.add(generatedSrcDir.get().dir("java").asFile.absolutePath) } } else { project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl { ext -> - ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile) + ext.sourceSets.getByName("main").java.directories.add(generatedSrcDir.get().dir("java").asFile.absolutePath) } } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt index c4a7b359d542..4aab7b9f7579 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt @@ -9,7 +9,6 @@ package com.facebook.react.utils import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.LibraryAndroidComponentsExtension -import com.android.build.gradle.LibraryExtension import com.facebook.react.ReactExtension import com.facebook.react.utils.ProjectUtils.isEdgeToEdgeEnabled import com.facebook.react.utils.ProjectUtils.isHermesEnabled @@ -59,18 +58,21 @@ internal object AgpConfiguratorUtils { project.extensions .getByType(ApplicationAndroidComponentsExtension::class.java) .finalizeDsl { ext -> - ext.buildFeatures.buildConfig = true - ext.defaultConfig.buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true") - ext.defaultConfig.buildConfigField( - "boolean", - "IS_HERMES_ENABLED", - project.isHermesEnabled.toString(), - ) - ext.defaultConfig.buildConfigField( - "boolean", - "IS_EDGE_TO_EDGE_ENABLED", - project.isEdgeToEdgeEnabled.toString(), - ) + ext.buildFeatures { buildConfig = true } + // In AGP 9.1, buildConfigField is accessed via defaultConfig + ext.defaultConfig { + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true") + buildConfigField( + "boolean", + "IS_HERMES_ENABLED", + project.isHermesEnabled.toString(), + ) + buildConfigField( + "boolean", + "IS_EDGE_TO_EDGE_ENABLED", + project.isEdgeToEdgeEnabled.toString(), + ) + } } } project.pluginManager.withPlugin("com.android.application", action) @@ -82,7 +84,7 @@ internal object AgpConfiguratorUtils { subproject.pluginManager.withPlugin("com.android.library") { subproject.extensions .getByType(LibraryAndroidComponentsExtension::class.java) - .finalizeDsl { ext -> ext.buildFeatures.buildConfig = true } + .finalizeDsl { ext -> ext.buildFeatures { buildConfig = true } } } } } @@ -97,13 +99,16 @@ internal object AgpConfiguratorUtils { project.extensions .getByType(ApplicationAndroidComponentsExtension::class.java) .finalizeDsl { ext -> - ext.buildFeatures.resValues = true - ext.defaultConfig.resValue( - "string", - "react_native_dev_server_ip", - devServerIp, - ) - ext.defaultConfig.resValue("integer", "react_native_dev_server_port", devServerPort) + ext.buildFeatures { resValues = true } + // In AGP 9.1, resValue is accessed via defaultConfig + ext.defaultConfig { + resValue( + "string", + "react_native_dev_server_ip", + devServerIp, + ) + resValue("integer", "react_native_dev_server_port", devServerPort) + } } } @@ -114,22 +119,22 @@ internal object AgpConfiguratorUtils { fun configureNamespaceForLibraries(appProject: Project) { appProject.rootProject.allprojects { subproject -> subproject.pluginManager.withPlugin("com.android.library") { - subproject.extensions + val components = subproject.extensions .getByType(LibraryAndroidComponentsExtension::class.java) - .finalizeDsl { ext -> - if (ext.namespace == null) { - val android = subproject.extensions.getByType(LibraryExtension::class.java) - val manifestFile = android.sourceSets.getByName("main").manifest.srcFile - - manifestFile - .takeIf { it.exists() } - ?.let { file -> - getPackageNameFromManifest(file)?.let { packageName -> - ext.namespace = packageName - } - } - } - } + + components.finalizeDsl { dsl -> + if (dsl.namespace.isNullOrEmpty()) { + val manifestFile = subproject.file("src/main/AndroidManifest.xml") + + manifestFile + .takeIf { it.exists() } + ?.let { file -> + getPackageNameFromManifest(file)?.let { packageName -> + dsl.namespace = packageName + } + } + } + } } } } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt index 119c80de03c6..54da853e3fb9 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt @@ -20,10 +20,6 @@ internal object NdkConfiguratorUtils { project.pluginManager.withPlugin("com.android.application") { project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl { ext -> - // We enable prefab so users can consume .so/headers from ReactAndroid and hermes-engine - // .aar - ext.buildFeatures.prefab = true - // If the user has not provided a CmakeLists.txt path, let's provide // the default one from the framework if (ext.externalNativeBuild.cmake.path == null) { @@ -36,23 +32,32 @@ internal object NdkConfiguratorUtils { // Parameters should be provided in an additive manner (do not override what // the user provided, but allow for sensible defaults). - val cmakeArgs = ext.defaultConfig.externalNativeBuild.cmake.arguments - if (cmakeArgs.none { it.startsWith("-DPROJECT_BUILD_DIR") }) { - cmakeArgs.add("-DPROJECT_BUILD_DIR=${project.layout.buildDirectory.get().asFile}") - } - if (cmakeArgs.none { it.startsWith("-DPROJECT_ROOT_DIR") }) { - cmakeArgs.add("-DPROJECT_ROOT_DIR=${project.rootProject.layout.projectDirectory.asFile}") - } - if (cmakeArgs.none { it.startsWith("-DREACT_ANDROID_DIR") }) { - cmakeArgs.add( - "-DREACT_ANDROID_DIR=${extension.reactNativeDir.file("ReactAndroid").get().asFile}" - ) - } - if (cmakeArgs.none { it.startsWith("-DANDROID_STL") }) { - cmakeArgs.add("-DANDROID_STL=c++_shared") - } - if (cmakeArgs.none { it.startsWith("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES") }) { - cmakeArgs.add("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON") + // In AGP 9.1, defaultConfig is accessed as a lambda + ext.defaultConfig { + // Access cmake arguments through externalNativeBuild + val cmakeArgs = externalNativeBuild.cmake.arguments + if (cmakeArgs.none { it.startsWith("-DPROJECT_BUILD_DIR") }) { + cmakeArgs.add("-DPROJECT_BUILD_DIR=${project.layout.buildDirectory.get().asFile}") + } + if (cmakeArgs.none { it.startsWith("-DPROJECT_ROOT_DIR") }) { + cmakeArgs.add("-DPROJECT_ROOT_DIR=${project.rootProject.layout.projectDirectory.asFile}") + } + if (cmakeArgs.none { it.startsWith("-DREACT_ANDROID_DIR") }) { + cmakeArgs.add( + "-DREACT_ANDROID_DIR=${extension.reactNativeDir.file("ReactAndroid").get().asFile}" + ) + } + if (cmakeArgs.none { it.startsWith("-DREACT_COMMON_DIR") }) { + cmakeArgs.add( + "-DREACT_COMMON_DIR=${extension.reactNativeDir.file("ReactCommon").get().asFile}" + ) + } + if (cmakeArgs.none { it.startsWith("-DANDROID_STL") }) { + cmakeArgs.add("-DANDROID_STL=c++_shared") + } + if (cmakeArgs.none { it.startsWith("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES") }) { + cmakeArgs.add("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON") + } } val architectures = project.getReactNativeArchitectures() diff --git a/packages/gradle-plugin/settings-plugin/build.gradle.kts b/packages/gradle-plugin/settings-plugin/build.gradle.kts index 2fbeafd502c9..165bf38a8665 100644 --- a/packages/gradle-plugin/settings-plugin/build.gradle.kts +++ b/packages/gradle-plugin/settings-plugin/build.gradle.kts @@ -54,7 +54,7 @@ kotlin { jvmToolchain(17) } tasks.withType().configureEach { compilerOptions { - apiVersion.set(KotlinVersion.KOTLIN_1_8) + apiVersion.set(KotlinVersion.KOTLIN_2_3) // See comment above on JDK 11 support jvmTarget.set(JvmTarget.JVM_11) allWarningsAsErrors.set( diff --git a/packages/gradle-plugin/shared-testutil/build.gradle.kts b/packages/gradle-plugin/shared-testutil/build.gradle.kts index 26173fb55578..19dd3a1333df 100644 --- a/packages/gradle-plugin/shared-testutil/build.gradle.kts +++ b/packages/gradle-plugin/shared-testutil/build.gradle.kts @@ -27,7 +27,7 @@ kotlin { jvmToolchain(17) } tasks.withType().configureEach { compilerOptions { - apiVersion.set(KotlinVersion.KOTLIN_1_8) + apiVersion.set(KotlinVersion.KOTLIN_2_3) // See comment above on JDK 11 support jvmTarget.set(JvmTarget.JVM_11) allWarningsAsErrors.set( diff --git a/packages/gradle-plugin/shared/build.gradle.kts b/packages/gradle-plugin/shared/build.gradle.kts index 315984875ad8..87fb331634ed 100644 --- a/packages/gradle-plugin/shared/build.gradle.kts +++ b/packages/gradle-plugin/shared/build.gradle.kts @@ -33,7 +33,7 @@ kotlin { jvmToolchain(17) } tasks.withType().configureEach { compilerOptions { - apiVersion.set(KotlinVersion.KOTLIN_1_8) + apiVersion.set(KotlinVersion.KOTLIN_2_3) // See comment above on JDK 11 support jvmTarget.set(JvmTarget.JVM_11) allWarningsAsErrors.set( diff --git a/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt b/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt index 0544a8775470..ebcddcc7f086 100644 --- a/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt +++ b/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/JsonUtilsTest.kt @@ -85,10 +85,12 @@ class JsonUtilsTest { ) val parsed = JsonUtils.fromPackageJson(validJson)!! + val codegenConfig = parsed.codegenConfig!! + val android = codegenConfig.android!! - assertThat("an awesome library").isEqualTo(parsed.codegenConfig!!.name) - assertThat("../js/").isEqualTo(parsed.codegenConfig!!.jsSrcsDir) - assertThat("com.awesome.library").isEqualTo(parsed.codegenConfig!!.android!!.javaPackageName) + assertThat("an awesome library").isEqualTo(codegenConfig.name) + assertThat("../js/").isEqualTo(codegenConfig.jsSrcsDir) + assertThat("com.awesome.library").isEqualTo(android.javaPackageName) } @Test @@ -180,15 +182,17 @@ class JsonUtilsTest { .trimIndent() ) val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!! - - assertThat("./packages/rn-tester").isEqualTo(parsed.project!!.android!!.sourceDir) - assertThat("RN-Tester").isEqualTo(parsed.project!!.android!!.appName) - assertThat("com.facebook.react.uiapp").isEqualTo(parsed.project!!.android!!.packageName) - assertThat("com.facebook.react.uiapp").isEqualTo(parsed.project!!.android!!.applicationId) - assertThat(".RNTesterActivity").isEqualTo(parsed.project!!.android!!.mainActivity) + val project = parsed.project!! + val android = project.android!! + + assertThat("./packages/rn-tester").isEqualTo(android.sourceDir) + assertThat("RN-Tester").isEqualTo(android.appName) + assertThat("com.facebook.react.uiapp").isEqualTo(android.packageName) + assertThat("com.facebook.react.uiapp").isEqualTo(android.applicationId) + assertThat(".RNTesterActivity").isEqualTo(android.mainActivity) assertThat("--mode HermesDebug") - .isEqualTo(parsed.project!!.android!!.watchModeCommandParams!![0]) - assertThat("implementation").isEqualTo(parsed.project!!.android!!.dependencyConfiguration) + .isEqualTo(android.watchModeCommandParams!![0]) + assertThat("implementation").isEqualTo(android.dependencyConfiguration) } @Test @@ -224,20 +228,22 @@ class JsonUtilsTest { "dependencyConfiguration": "implementation" } } - } + } """ .trimIndent() ) val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!! - - assertThat("./packages/rn-tester").isEqualTo(parsed.project!!.android!!.sourceDir) - assertThat("RN-Tester").isEqualTo(parsed.project!!.android!!.appName) - assertThat("com.facebook.react.uiapp").isEqualTo(parsed.project!!.android!!.packageName) - assertThat("com.facebook.react.uiapp").isEqualTo(parsed.project!!.android!!.applicationId) - assertThat(".RNTesterActivity").isEqualTo(parsed.project!!.android!!.mainActivity) + val project = parsed.project!! + val android = project.android!! + + assertThat("./packages/rn-tester").isEqualTo(android.sourceDir) + assertThat("RN-Tester").isEqualTo(android.appName) + assertThat("com.facebook.react.uiapp").isEqualTo(android.packageName) + assertThat("com.facebook.react.uiapp").isEqualTo(android.applicationId) + assertThat(".RNTesterActivity").isEqualTo(android.mainActivity) assertThat("--mode HermesDebug") - .isEqualTo(parsed.project!!.android!!.watchModeCommandParams!![0]) - assertThat("implementation").isEqualTo(parsed.project!!.android!!.dependencyConfiguration) + .isEqualTo(android.watchModeCommandParams!![0]) + assertThat("implementation").isEqualTo(android.dependencyConfiguration) } @Test @@ -282,51 +288,52 @@ class JsonUtilsTest { .trimIndent() ) val parsed = JsonUtils.fromAutolinkingConfigJson(validJson)!! + val dependencies = parsed.dependencies!! assertThat("./node_modules/@react-native/oss-library-example") - .isEqualTo(parsed.dependencies!!["@react-native/oss-library-example"]!!.root) + .isEqualTo(dependencies["@react-native/oss-library-example"]!!.root) assertThat("@react-native/oss-library-example") - .isEqualTo(parsed.dependencies!!["@react-native/oss-library-example"]!!.name) + .isEqualTo(dependencies["@react-native/oss-library-example"]!!.name) assertThat("react-native_oss-library-example") - .isEqualTo(parsed.dependencies!!["@react-native/oss-library-example"]!!.nameCleansed) + .isEqualTo(dependencies["@react-native/oss-library-example"]!!.nameCleansed) assertThat("./node_modules/@react-native/oss-library-example/android") .isEqualTo( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .sourceDir ) assertThat("import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;") .isEqualTo( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .packageImportPath ) assertThat("new OSSLibraryExamplePackage()") .isEqualTo( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .packageInstance ) assertThat(listOf("staging", "debug", "release")) .isEqualTo( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .buildTypes ) assertThat("OSSLibraryExampleSpec") .isEqualTo( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .libraryName ) assertThat(listOf("SampleNativeComponentComponentDescriptor")) .isEqualTo( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .componentDescriptors @@ -335,27 +342,27 @@ class JsonUtilsTest { "./node_modules/@react-native/oss-library-example/android/build/generated/source/codegen/jni/CMakeLists.txt" ) .isEqualTo( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .cmakeListsPath ) assertThat( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .cxxModuleHeaderName ) .isNull() assertThat( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .cxxModuleCMakeListsPath ) .isNull() assertThat( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .cxxModuleCMakeListsModuleName @@ -363,13 +370,13 @@ class JsonUtilsTest { .isNull() assertThat("implementation") .isEqualTo( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .dependencyConfiguration ) assertThat( - parsed.dependencies!!["@react-native/oss-library-example"]!! + dependencies["@react-native/oss-library-example"]!! .platforms!! .android!! .isPureCxxDependency!! diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index c8435862e17c..21acc2683c82 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -10,8 +10,12 @@ import com.facebook.react.internal.PrivateReactExtension import com.facebook.react.tasks.internal.* import com.facebook.react.tasks.internal.utils.* import de.undercouch.gradle.tasks.download.Download +import java.io.File import java.nio.file.Paths import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.gradle.kotlin.dsl.* +import java.util.zip.ZipEntry +import java.util.zip.ZipFile plugins { id("maven-publish") @@ -84,6 +88,7 @@ val preparePrefab by prepareFmt, prepareFolly, prepareGlog, + prepareFbjni, ) dependsOn("generateCodegenArtifactsFromSchema") // To export to a ReactNativePrefabProcessingEntities.kt once all @@ -435,6 +440,68 @@ val prepareGlog by outputDir.set(File(thirdPartyNdkDir, "glog")) } +val fbjniVersion = libs.versions.fbjni.get() +val downloadFbjniAarDest = File(downloadsDir, "fbjni-${fbjniVersion}.aar") +val downloadFbjniAar by + tasks.registering(Download::class) { + dependsOn(createNativeDepsDirectories) + src("https://repo1.maven.org/maven2/com/facebook/fbjni/fbjni/${fbjniVersion}/fbjni-${fbjniVersion}.aar") + onlyIfModified(true) + overwrite(false) + retries(5) + quiet(true) + dest(downloadFbjniAarDest) + } + +// Extract fbjni headers from AAR for prefab +val prepareFbjni by + tasks.registering { + dependsOn(downloadFbjniAar) + val inputAar = downloadFbjniAarDest + val headersOutputDir = File(prefabHeadersDir, "fbjni") + val libsOutputDir = buildDir.resolve("intermediates/prefab_package/debug/prefab/modules/fbjni/libs") + outputs.dir(headersOutputDir) + outputs.dir(libsOutputDir) + doLast { + headersOutputDir.mkdirs() + libsOutputDir.mkdirs() + // Extract only the fbjni headers from the AAR + val zip = ZipFile(inputAar) + try { + zip.entries().asSequence().forEach { entry: ZipEntry -> + when { + entry.name.startsWith("prefab/modules/fbjni/include/fbjni/") -> { + // Extract headers + val destFile = File(headersOutputDir, entry.name.removePrefix("prefab/modules/fbjni/include/")) + destFile.parentFile.mkdirs() + if (!entry.name.endsWith("/")) { + destFile.outputStream().use { output -> + zip.getInputStream(entry).use { input -> + input.copyTo(output) + } + } + } + } + entry.name.startsWith("prefab/modules/fbjni/libs/android.") -> { + // Extract .so files to prefab_package directory + val destFile = File(libsOutputDir, entry.name.substringAfter("android.")) + destFile.parentFile.mkdirs() + if (!entry.name.endsWith("/")) { + destFile.outputStream().use { output -> + zip.getInputStream(entry).use { input -> + input.copyTo(output) + } + } + } + } + } + } + } finally { + zip.close() + } + } + } + // Tasks used by Fantom to download the Native 3p dependencies used. val prepareNative3pDependencies by tasks.registering { dependsOn( @@ -444,6 +511,7 @@ val prepareNative3pDependencies by tasks.registering { prepareFmt, prepareFolly, prepareGlog, + prepareFbjni, ) } @@ -627,8 +695,6 @@ android { "src/main/res/views/view", ) ) - java.exclude("com/facebook/react/processing") - java.exclude("com/facebook/react/module/processing") } lint { @@ -657,6 +723,9 @@ android { create("jsi") { headers = File(prefabHeadersDir, "jsi").absolutePath } create("reactnative") { headers = File(prefabHeadersDir, "reactnative").absolutePath } create("hermestooling") { headers = File(prefabHeadersDir, "hermestooling").absolutePath } + create("fbjni") { + headers = File(prefabHeadersDir, "fbjni").absolutePath + } } publishing { @@ -679,6 +748,62 @@ android { } } } + + // Generate CMake config file for find_package support + // This is needed for ReactNative-application.cmake to find ReactAndroid via find_package() + val generateReactAndroidConfig by tasks.registering { + val configDir = buildDir.resolve("cmake/ReactAndroid") + outputs.dir(configDir) + doLast { + configDir.mkdirs() + // Convert paths to CMake-style forward slashes to avoid Windows escape sequence issues + val buildDirCmake = buildDir.path.replace("\\", "/") + // The prefab libraries are in build/intermediates/prefab_package/debug/prefab/modules/*/libs/android.* + val prefabLibsDir = buildDir.resolve("intermediates/prefab_package/debug/prefab/modules").path.replace("\\", "/") + val configContent = """ + # Copyright (c) Meta Platforms, Inc. and affiliates. + # + # This source code is licensed under the MIT license found in the + # LICENSE file in the root directory of this source tree. + + # ReactAndroid CMake configuration file + # This file is generated during the Gradle build and used by CMake's find_package() + + # The reactnative library - use the first available architecture + # The actual architecture will be determined by the CMAKE_ANDROID_ARCH_ABI variable + add_library(reactnative SHARED IMPORTED) + set_target_properties(reactnative PROPERTIES + IMPORTED_LOCATION "${prefabLibsDir}/reactnative/libs/android.${'$'}{CMAKE_ANDROID_ARCH_ABI}/libreactnative.so" + INTERFACE_INCLUDE_DIRECTORIES "${buildDirCmake}/prefab-headers/reactnative" + ) + + # The jsi library + add_library(jsi SHARED IMPORTED) + set_target_properties(jsi PROPERTIES + IMPORTED_LOCATION "${prefabLibsDir}/jsi/libs/android.${'$'}{CMAKE_ANDROID_ARCH_ABI}/libjsi.so" + INTERFACE_INCLUDE_DIRECTORIES "${buildDirCmake}/prefab-headers/jsi" + ) + + # The fbjni library - from Gradle dependency's prefab module + add_library(fbjni SHARED IMPORTED) + set_target_properties(fbjni PROPERTIES + IMPORTED_LOCATION "${prefabLibsDir}/fbjni/libs/android.${'$'}{CMAKE_ANDROID_ARCH_ABI}/libfbjni.so" + INTERFACE_INCLUDE_DIRECTORIES "${buildDirCmake}/prefab-headers/fbjni" + ) + + """.trimIndent() + configDir.resolve("ReactAndroidConfig.cmake").writeText(configContent) + } + } + + // Ensure the config file is generated before external native build + // The CMake tasks are created lazily by AGP with names like "configureCMake" + // We use configureEach to handle lazily created tasks in AGP 9.1+ + tasks.withType().configureEach { + if (name.startsWith("configureCMake")) { + dependsOn(generateReactAndroidConfig) + } + } } tasks.withType().configureEach { diff --git a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake index baa0fefa95ec..a6f1ccecd46d 100644 --- a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake +++ b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake @@ -47,6 +47,12 @@ if (PROJECT_ROOT_DIR) # variable is defined if user need to access it. endif () +# Set ReactAndroid_DIR to help find_package() locate ReactAndroidConfig.cmake +# The config file is generated by Gradle during the build and placed in the build directory +if (EXISTS "${REACT_ANDROID_DIR}/build/cmake/ReactAndroid/ReactAndroidConfig.cmake") + set(ReactAndroid_DIR "${REACT_ANDROID_DIR}/build/cmake/ReactAndroid" CACHE PATH "ReactAndroid CMake config directory") +endif () + file(GLOB override_cpp_SRC CONFIGURE_DEPENDS *.cpp) # We check if the user is providing a custom OnLoad.cpp file. If so, we pick that # for compilation. Otherwise we fallback to using the `default-app-setup/OnLoad.cpp` diff --git a/packages/rn-tester/android/app/build.gradle.kts b/packages/rn-tester/android/app/build.gradle.kts index fc1770cbd068..ec4dff6204bf 100644 --- a/packages/rn-tester/android/app/build.gradle.kts +++ b/packages/rn-tester/android/app/build.gradle.kts @@ -184,26 +184,26 @@ tasks.withType().configureEach { } } -afterEvaluate { - if ( - (project.findProperty("react.internal.useHermesNightly") == null || - project.findProperty("react.internal.useHermesNightly").toString() == "false") && - (project.findProperty("react.internal.useHermesStable") == null || - project.findProperty("react.internal.useHermesStable").toString() == "false") - ) { - // As we're consuming Hermes from source, we want to make sure - // `hermesc` is built before we actually invoke the `emit*HermesResource` task - tasks - .getByName("createBundleReleaseJsAndAssets") - .dependsOn(":packages:react-native:ReactAndroid:hermes-engine:buildHermesC") - } +// As we're consuming Hermes from source, we want to make sure +// `hermesc` is built before we actually invoke the `emit*HermesResource` task +tasks.withType().configureEach { + dependsOn(":packages:react-native:ReactAndroid:hermes-engine:buildHermesC") +} + +// As RN-Tester consumes the codegen from source, we need to make sure the codegen exists before +// we can actually invoke it. It's built by the ReactAndroid:buildCodegenCLI task. +tasks.withType().configureEach { + dependsOn(":packages:react-native:ReactAndroid:buildCodegenCLI") +} +tasks.withType().configureEach { + dependsOn(":packages:react-native:ReactAndroid:buildCodegenCLI") +} - // As RN-Tester consumes the codegen from source, we need to make sure the codegen exists before - // we can actually invoke it. It's built by the ReactAndroid:buildCodegenCLI task. - tasks - .getByName("generateCodegenSchemaFromJavaScript") - .dependsOn(":packages:react-native:ReactAndroid:buildCodegenCLI") - tasks - .getByName("createBundleReleaseJsAndAssets") - .dependsOn(":packages:react-native:ReactAndroid:buildCodegenCLI") +// As RN-Tester uses CMake, we need to make sure the ReactAndroidConfig.cmake file is +// generated before CMake runs. This file is generated by ReactAndroid:generateReactAndroidConfig +// We use configureEach to handle lazily created CMake tasks in AGP 9.1+ +tasks.withType().configureEach { + if (name.startsWith("configureCMake")) { + dependsOn(":packages:react-native:ReactAndroid:generateReactAndroidConfig") + } } diff --git a/private/helloworld/android/gradle/wrapper/gradle-wrapper.properties b/private/helloworld/android/gradle/wrapper/gradle-wrapper.properties index 37f78a6af837..dbc3ce4a040f 100644 --- a/private/helloworld/android/gradle/wrapper/gradle-wrapper.properties +++ b/private/helloworld/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME