From d6df7cfc90b6ec38e41bb2f707f9f4b543cdedaf Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Mon, 18 Sep 2023 01:41:22 -0700 Subject: [PATCH] Implement version parsing and connect to RNTester Summary: This diff adds the parsing of the package.json to retrieve the version of react native and it matches it against a regex to decide whether to enable or not the New Architecture. This change depends on [this CLI's PR](https://github.com/react-native-community/cli/pull/2076). ## Changelog: [Android][Added] - Parse RN Version to decide whether to enable the New Arch or not. Reviewed By: cortinico Differential Revision: D49270012 fbshipit-source-id: f22b912f67bc67d4b0b544371b1c334a92892666 --- .../kotlin/com/facebook/react/ReactPlugin.kt | 2 +- .../com/facebook/react/TaskConfiguration.kt | 2 +- .../react/utils/AgpConfiguratorUtils.kt | 7 +- .../react/utils/NdkConfiguratorUtils.kt | 7 +- .../com/facebook/react/utils/ProjectUtils.kt | 51 +++++++- .../facebook/react/utils/ProjectUtilsTest.kt | 121 +++++++++++++++++- packages/rn-tester/android/app/build.gradle | 35 ++++- 7 files changed, 202 insertions(+), 23 deletions(-) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index b5a68d87e25316..a3a02b58335242 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -64,7 +64,7 @@ class ReactPlugin : Plugin { } configureReactNativeNdk(project, extension) - configureBuildConfigFields(project) + configureBuildConfigFields(project, extension) configureDevPorts(project) configureBackwardCompatibilityReactMap(project) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt index 296d2a4800fdc5..ed5551d0987f3f 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt @@ -48,7 +48,7 @@ internal fun Project.configureReactTasks(variant: Variant, config: ReactExtensio val isDebuggableVariant = config.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) } - configureNewArchPackagingOptions(project, variant) + configureNewArchPackagingOptions(project, config, variant) configureJsEnginePackagingOptions(config, variant, isHermesEnabledInThisVariant) if (!isDebuggableVariant) { diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt index 3e9f8fc5e7a2ff..0312095b0fe7dd 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt @@ -8,6 +8,7 @@ package com.facebook.react.utils import com.android.build.api.variant.AndroidComponentsExtension +import com.facebook.react.ReactExtension import com.facebook.react.utils.ProjectUtils.isHermesEnabled import com.facebook.react.utils.ProjectUtils.isNewArchEnabled import org.gradle.api.Action @@ -17,13 +18,15 @@ import org.gradle.api.plugins.AppliedPlugin @Suppress("UnstableApiUsage") internal object AgpConfiguratorUtils { - fun configureBuildConfigFields(project: Project) { + fun configureBuildConfigFields(project: Project, extension: ReactExtension) { val action = Action { project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext -> ext.buildFeatures.buildConfig = true ext.defaultConfig.buildConfigField( - "boolean", "IS_NEW_ARCHITECTURE_ENABLED", project.isNewArchEnabled.toString()) + "boolean", + "IS_NEW_ARCHITECTURE_ENABLED", + project.isNewArchEnabled(extension).toString()) ext.defaultConfig.buildConfigField( "boolean", "IS_HERMES_ENABLED", project.isHermesEnabled.toString()) } diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt index 490d61fb85801f..9434b5a3c52f84 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt @@ -20,7 +20,7 @@ internal object NdkConfiguratorUtils { fun configureReactNativeNdk(project: Project, extension: ReactExtension) { project.pluginManager.withPlugin("com.android.application") { project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext -> - if (!project.isNewArchEnabled) { + if (!project.isNewArchEnabled(extension)) { // For Old Arch, we don't need to setup the NDK return@finalizeDsl } @@ -74,9 +74,10 @@ internal object NdkConfiguratorUtils { */ fun configureNewArchPackagingOptions( project: Project, - variant: Variant, + extension: ReactExtension, + variant: Variant ) { - if (!project.isNewArchEnabled) { + if (!project.isNewArchEnabled(extension)) { // For Old Arch, we set a pickFirst only on libraries that we know are // clashing with our direct dependencies (mainly FBJNI and Hermes). variant.packaging.jniLibs.pickFirsts.addAll( diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt index b7d1f28cc86deb..c0ffc3b2140977 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt @@ -7,6 +7,7 @@ package com.facebook.react.utils +import com.facebook.react.ReactExtension import com.facebook.react.model.ModelPackageJson import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat import com.facebook.react.utils.KotlinStdlibCompatUtils.toBooleanStrictOrNullCompat @@ -16,19 +17,22 @@ import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_NEW_ARCH_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_REACT_NATIVE_ARCHITECTURES +import java.io.File import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty internal object ProjectUtils { - internal val Project.isNewArchEnabled: Boolean - get() = - (project.hasProperty(NEW_ARCH_ENABLED) && - project.property(NEW_ARCH_ENABLED).toString().toBoolean()) || - (project.hasProperty(SCOPED_NEW_ARCH_ENABLED) && - project.property(SCOPED_NEW_ARCH_ENABLED).toString().toBoolean()) const val HERMES_FALLBACK = true + internal fun Project.isNewArchEnabled(extension: ReactExtension): Boolean { + return (project.hasProperty(NEW_ARCH_ENABLED) && + project.property(NEW_ARCH_ENABLED).toString().toBoolean()) || + (project.hasProperty(SCOPED_NEW_ARCH_ENABLED) && + project.property(SCOPED_NEW_ARCH_ENABLED).toString().toBoolean()) || + shouldEnableNewArchForReactNativeVersion(project.reactNativeDir(extension)) + } + internal val Project.isHermesEnabled: Boolean get() = if (project.hasProperty(HERMES_ENABLED) || project.hasProperty(SCOPED_HERMES_ENABLED)) { @@ -75,4 +79,39 @@ internal object ProjectUtils { } return architectures } + + internal fun Project.reactNativeDir(extension: ReactExtension): String = + extension.reactNativeDir.get().asFile.absolutePath + + internal fun shouldEnableNewArchForReactNativeVersion(reactNativeDir: String): Boolean { + val packageJsonFile = File(reactNativeDir, "package.json") + if (!packageJsonFile.exists()) { + return false + } + + val rnPackageJson = JsonUtils.fromReactNativePackageJson(packageJsonFile) + if (rnPackageJson == null) { + return false + } + + // This regex describe the version syntax for React Native in the shape of + // major.minor.patch[-[[-.]k]] + // Where + // major is a number + // minor is a number + // patch is a number + // [-.]k is optional, but if present is preceeded by a `-` + // the tag is a string. + // it can be followed by `-` or `.` and k is a number. + val regex = """^(\d+)\.(\d+)\.(\d+)(?:-(\w+(?:[-.]\d+)?))?$""".toRegex() + + val matchResult = regex.find(rnPackageJson.version) + + if (matchResult == null) { + return false + } + + val major = matchResult.groupValues[1].toInt() + return major > 0 && major < 1000 + } } diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt index d1e80d814c8f3c..3d8fd6e1d48bf2 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt @@ -27,28 +27,141 @@ class ProjectUtilsTest { @Test fun isNewArchEnabled_returnsFalseByDefault() { - assertFalse(createProject().isNewArchEnabled) + val project = createProject() + val extension = TestReactExtension(project) + assertFalse(createProject().isNewArchEnabled(extension)) } @Test fun isNewArchEnabled_withDisabled_returnsFalse() { val project = createProject() project.extensions.extraProperties.set("newArchEnabled", "false") - assertFalse(project.isNewArchEnabled) + val extension = TestReactExtension(project) + assertFalse(project.isNewArchEnabled(extension)) } @Test fun isNewArchEnabled_withEnabled_returnsTrue() { val project = createProject() project.extensions.extraProperties.set("newArchEnabled", "true") - assertTrue(project.isNewArchEnabled) + val extension = TestReactExtension(project) + assertTrue(project.isNewArchEnabled(extension)) } @Test fun isNewArchEnabled_withInvalid_returnsFalse() { val project = createProject() project.extensions.extraProperties.set("newArchEnabled", "¯\\_(ツ)_/¯") - assertFalse(project.isNewArchEnabled) + val extension = TestReactExtension(project) + assertFalse(project.isNewArchEnabled(extension)) + } + + @Test + fun isNewArchEnabled_withRNVersion0_returnFalse() { + val project = createProject() + val extension = TestReactExtension(project) + File(tempFolder.root, "package.json").apply { + writeText( + // language=json + """ + { + "version": "0.73.0" + } + """ + .trimIndent()) + } + extension.reactNativeDir.set(tempFolder.root) + assertFalse(project.isNewArchEnabled(extension)) + } + + @Test + fun isNewArchEnabled_withRNVersion1_returnTrue() { + val project = createProject() + val extension = TestReactExtension(project) + File(tempFolder.root, "package.json").apply { + writeText( + // language=json + """ + { + "version": "1.2.3" + } + """ + .trimIndent()) + } + extension.reactNativeDir.set(tempFolder.root) + assertTrue(project.isNewArchEnabled(extension)) + } + + @Test + fun isNewArchEnabled_withRNVersion1PrereleaseString_returnTrue() { + val project = createProject() + val extension = TestReactExtension(project) + File(tempFolder.root, "package.json").apply { + writeText( + // language=json + """ + { + "version": "1.2.3-prealpha0" + } + """ + .trimIndent()) + } + extension.reactNativeDir.set(tempFolder.root) + assertTrue(project.isNewArchEnabled(extension)) + } + + @Test + fun isNewArchEnabled_withRNVersion1PrereleaseStringDotNumber_returnTrue() { + val project = createProject() + val extension = TestReactExtension(project) + File(tempFolder.root, "package.json").apply { + writeText( + // language=json + """ + { + "version": "1.2.3-prealpha.0" + } + """ + .trimIndent()) + } + extension.reactNativeDir.set(tempFolder.root) + assertTrue(project.isNewArchEnabled(extension)) + } + + @Test + fun isNewArchEnabled_withRNVersion1PrereleaseStringDashNumber_returnTrue() { + val project = createProject() + val extension = TestReactExtension(project) + File(tempFolder.root, "package.json").apply { + writeText( + // language=json + """ + { + "version": "1.2.3-prealpha-0" + } + """ + .trimIndent()) + } + extension.reactNativeDir.set(tempFolder.root) + assertTrue(project.isNewArchEnabled(extension)) + } + + @Test + fun isNewArchEnabled_withRNVersion1000_returnFalse() { + val project = createProject() + val extension = TestReactExtension(project) + File(tempFolder.root, "package.json").apply { + writeText( + // language=json + """ + { + "version": "1000.0.0" + } + """ + .trimIndent()) + } + extension.reactNativeDir.set(tempFolder.root) + assertFalse(project.isNewArchEnabled(extension)) } @Test diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle index bcaa729ebde2fd..b35e80be387c14 100644 --- a/packages/rn-tester/android/app/build.gradle +++ b/packages/rn-tester/android/app/build.gradle @@ -5,12 +5,35 @@ * LICENSE file in the root directory of this source tree. */ +import groovy.json.JsonSlurper + plugins { id("com.facebook.react") alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) } +def reactNativeVersionRequireNewArchEnabled(reactNativeDirPath) { + def reactNativePackageJson = "$reactNativeDirPath/package.json" + def slurper = new JsonSlurper() + def jsonData = slurper.parse(new File(reactNativePackageJson)) + def rnVersion = jsonData.version + def regexPattern = /^(\d+)\.(\d+)\.(\d+)(?:-(\w+(?:[-.]\d+)?))?$/ + + + if (rnVersion =~ regexPattern) { + def result = (rnVersion =~ regexPattern).findAll().first() + + def major = result[1].toInteger() + if (major > 0 && major < 1000) { + return true + } + } + return false +} + +def reactNativeDirPath = "$rootDir/packages/react-native" +def isNewArchEnabled = project.property("newArchEnabled") == "true" || reactNativeVersionRequireNewArchEnabled(reactNativeDirPath) /** * This is the configuration block to customize your React Native Android app. * By default you don't need to apply any configuration, just uncomment the lines you need. @@ -20,11 +43,11 @@ react { // The root of your project, i.e. where "package.json" lives. Default is '..' root = file("../../") // The folder where the react-native NPM package is. Default is ../node_modules/react-native - reactNativeDir = file("$rootDir/packages/react-native") + reactNativeDir = file(reactNativeDirPath) // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen codegenDir = file("$rootDir/node_modules/@react-native/codegen") // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js - cliFile = file("$rootDir/packages/react-native/cli.js") + cliFile = file("$reactNativeDirPath/cli.js") /* Variants */ // The list of variants to that are debuggable. For those we're going to @@ -54,7 +77,7 @@ react { /* Hermes Commands */ // The hermes compiler command to run. By default it is 'hermesc' - hermesCommand = "$rootDir/packages/react-native/ReactAndroid/hermes-engine/build/hermes/bin/hermesc" + hermesCommand = "$reactNativeDirPath/ReactAndroid/hermes-engine/build/hermes/bin/hermesc" enableHermesOnlyInVariants = ["hermesDebug", "hermesRelease"] } @@ -148,7 +171,7 @@ android { java { // SampleTurboModule. srcDirs += [ - "$rootDir/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android", + "$reactNativeDirPath/ReactCommon/react/nativemodule/samples/platform/android", ] } } @@ -172,7 +195,7 @@ android { cmake { // RN Tester is doing custom linking of C++ libraries therefore needs // a dedicated CMakeLists.txt file. - if (project.property("newArchEnabled") == "true") { + if (isNewArchEnabled) { path("src/main/jni/CMakeLists.txt") } } @@ -189,7 +212,7 @@ afterEvaluate { // As we're building 4 native flavors in parallel, there is clash on the `.cxx/Debug` and // `.cxx/Release` folder where the CMake intermediates are stored. // We fixing this by instructing Gradle to always mergeLibs after they've been built. - if (project.property("newArchEnabled") == "true") { + if (isNewArchEnabled) { mergeHermesDebugNativeLibs.mustRunAfter(externalNativeBuildJscDebug) mergeHermesReleaseNativeLibs.mustRunAfter(externalNativeBuildJscRelease) mergeJscDebugNativeLibs.mustRunAfter(externalNativeBuildHermesDebug)