Skip to content

Commit

Permalink
Implement version parsing and connect to RNTester (facebook#39502)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#39502

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](react-native-community/cli#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: 0a829d3aa75ecd7fb45c1f40b03f5a959744f92e
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Sep 18, 2023
1 parent 3078738 commit 8e333f7
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class ReactPlugin : Plugin<Project> {
}

configureReactNativeNdk(project, extension)
configureBuildConfigFields(project)
configureBuildConfigFields(project, extension)
configureDevPorts(project)
configureBackwardCompatibilityReactMap(project)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<AppliedPlugin> {
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())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)) {
Expand Down Expand Up @@ -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.fromPackageJson(packageJsonFile)
if (rnPackageJson == null) {
return false
}

// This regex describe the version syntax for React Native in the shape of
// major.minor.patch[-<prerelease>[[-.]k]]
// Where
// major is a number
// minor is a number
// patch is a number
// <prerelease>[-.]k is optional, but if present is preceeded by a `-`
// the <prerelease> 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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 29 additions & 6 deletions packages/rn-tester/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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"]
}

Expand Down Expand Up @@ -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",
]
}
}
Expand All @@ -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")
}
}
Expand All @@ -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)
Expand Down

0 comments on commit 8e333f7

Please sign in to comment.