RNGP - Monorepo: Make sure libraries are honoring codegenDir provided by app (#36128)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/36128

This commit fixes a problem which is making harder to use the New Architecture in monorepos.
Specifically if a user specifies a `codegenDir` in their app, libraries should honor it.
This is not the case today.

The fix is to register an extension on the root project which will "pass" values from app
to libraries.

I've also cleaned up some of the logic in `readPackageJsonFile` function restricting
the access to those functions only to `.root` which is the only field they're accessing.

Fixes #35495

Changelog:
[Android] [Fixed] - Better Monorepo support for New Architecture

Reviewed By: cipolleschi

Differential Revision: D43186767

fbshipit-source-id: 5c5ca39397306120b6b6622cb728633bd331e021
This commit is contained in:
Nicola Corti 2023-02-10 10:04:55 -08:00 коммит произвёл Facebook GitHub Bot
Родитель b67a4ae90f
Коммит 0487108461
7 изменённых файлов: 98 добавлений и 45 удалений

Просмотреть файл

@ -667,12 +667,7 @@ react {
// TODO: The library name is chosen for parity with Fabric components & iOS
// This should be changed to a more generic name, e.g. `ReactCoreSpec`.
libraryName = "rncore"
root = file("..")
jsRootDir = file("../Libraries")
reactNativeDir = file("$projectDir/..")
// We search for the codegen in either one of the `node_modules` folder or in the
// root packages folder (that's for when we build from source without calling `yarn install`).
codegenDir = file(findNodeModulePath(projectDir, "@react-native/codegen") ?: "../packages/react-native-codegen/")
}
apply plugin: "org.jetbrains.kotlin.android"

Просмотреть файл

@ -9,6 +9,7 @@ package com.facebook.react
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.internal.PrivateReactExtension
import com.facebook.react.tasks.BuildCodegenCLITask
import com.facebook.react.tasks.GenerateCodegenArtifactsTask
import com.facebook.react.tasks.GenerateCodegenSchemaTask
@ -34,8 +35,22 @@ class ReactPlugin : Plugin<Project> {
checkJvmVersion(project)
val extension = project.extensions.create("react", ReactExtension::class.java, project)
// We register a private extension on the rootProject so that project wide configs
// like codegen config can be propagated from app project to libraries.
val rootExtension =
project.rootProject.extensions.findByType(PrivateReactExtension::class.java)
?: project.rootProject.extensions.create(
"privateReact", PrivateReactExtension::class.java, project)
// App Only Configuration
project.pluginManager.withPlugin("com.android.application") {
// We wire the root extension with the values coming from the app (either user populated or
// defaults).
rootExtension.root.set(extension.root)
rootExtension.reactNativeDir.set(extension.reactNativeDir)
rootExtension.codegenDir.set(extension.codegenDir)
rootExtension.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)
project.afterEvaluate {
val reactNativeDir = extension.reactNativeDir.get().asFile
val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")
@ -54,12 +69,12 @@ class ReactPlugin : Plugin<Project> {
project.configureReactTasks(variant = variant, config = extension)
}
}
configureCodegen(project, extension, isLibrary = false)
configureCodegen(project, extension, rootExtension, isLibrary = false)
}
// Library Only Configuration
project.pluginManager.withPlugin("com.android.library") {
configureCodegen(project, extension, isLibrary = true)
configureCodegen(project, extension, rootExtension, isLibrary = true)
}
}
@ -82,12 +97,14 @@ class ReactPlugin : Plugin<Project> {
}
}
/**
* A plugin to enable react-native-codegen in Gradle environment. See the Gradle API docs for more
* information: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
*/
/** This function sets up `react-native-codegen` in our Gradle plugin. */
@Suppress("UnstableApiUsage")
private fun configureCodegen(project: Project, extension: ReactExtension, isLibrary: Boolean) {
private fun configureCodegen(
project: Project,
localExtension: ReactExtension,
rootExtension: PrivateReactExtension,
isLibrary: Boolean
) {
// First, we set up the output dir for the codegen.
val generatedSrcDir = File(project.buildDir, "generated/source/codegen")
@ -95,21 +112,21 @@ class ReactPlugin : Plugin<Project> {
// It's the root folder for apps (so ../../ from the Gradle project)
// and the package folder for library (so ../ from the Gradle project)
if (isLibrary) {
extension.jsRootDir.convention(project.layout.projectDirectory.dir("../"))
localExtension.jsRootDir.convention(project.layout.projectDirectory.dir("../"))
} else {
extension.jsRootDir.convention(extension.root)
localExtension.jsRootDir.convention(localExtension.root)
}
val buildCodegenTask =
project.tasks.register("buildCodegenCLI", BuildCodegenCLITask::class.java) {
it.codegenDir.set(extension.codegenDir)
it.codegenDir.set(rootExtension.codegenDir)
val bashWindowsHome = project.findProperty("REACT_WINDOWS_BASH") as String?
it.bashWindowsHome.set(bashWindowsHome)
// Please note that appNeedsCodegen is triggering a read of the package.json at
// configuration time as we need to feed the onlyIf condition of this task.
// Therefore, the appNeedsCodegen needs to be invoked inside this lambda.
val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(extension)
val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
it.onlyIf { isLibrary || needsCodegenFromPackageJson }
}
@ -118,23 +135,24 @@ class ReactPlugin : Plugin<Project> {
project.tasks.register(
"generateCodegenSchemaFromJavaScript", GenerateCodegenSchemaTask::class.java) { it ->
it.dependsOn(buildCodegenTask)
it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)
it.codegenDir.set(extension.codegenDir)
it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
it.codegenDir.set(rootExtension.codegenDir)
it.generatedSrcDir.set(generatedSrcDir)
// We're reading the package.json at configuration time to properly feed
// the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the
// parsePackageJson should be invoked inside this lambda.
val packageJson = findPackageJsonFile(project, extension)
val packageJson = findPackageJsonFile(project, rootExtension.root)
val parsedPackageJson = packageJson?.let { JsonUtils.fromCodegenJson(it) }
val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir
if (jsSrcsDirInPackageJson != null) {
it.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson))
} else {
it.jsRootDir.set(extension.jsRootDir)
it.jsRootDir.set(localExtension.jsRootDir)
}
val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(extension)
val needsCodegenFromPackageJson =
project.needsCodegenFromPackageJson(rootExtension.root)
it.onlyIf { isLibrary || needsCodegenFromPackageJson }
}
@ -143,17 +161,18 @@ class ReactPlugin : Plugin<Project> {
project.tasks.register(
"generateCodegenArtifactsFromSchema", GenerateCodegenArtifactsTask::class.java) {
it.dependsOn(generateCodegenSchemaTask)
it.reactNativeDir.set(extension.reactNativeDir)
it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)
it.reactNativeDir.set(rootExtension.reactNativeDir)
it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
it.generatedSrcDir.set(generatedSrcDir)
it.packageJsonFile.set(findPackageJsonFile(project, extension))
it.codegenJavaPackageName.set(extension.codegenJavaPackageName)
it.libraryName.set(extension.libraryName)
it.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root))
it.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
it.libraryName.set(localExtension.libraryName)
// Please note that appNeedsCodegen is triggering a read of the package.json at
// configuration time as we need to feed the onlyIf condition of this task.
// Therefore, the appNeedsCodegen needs to be invoked inside this lambda.
val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(extension)
val needsCodegenFromPackageJson =
project.needsCodegenFromPackageJson(rootExtension.root)
it.onlyIf { isLibrary || needsCodegenFromPackageJson }
}

Просмотреть файл

@ -0,0 +1,35 @@
/*
* 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.
*/
package com.facebook.react.internal
import javax.inject.Inject
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
/**
* A private extension we set on the rootProject to make easier to share values at execution time
* between app project and library project.
*
* Specifically, the [codegenDir], [reactNativeDir] and other properties should be provided by apps
* (for setups like a monorepo which are app specific) and libraries should honor those values.
*
* Users are not supposed to access directly this extension from their build.gradle file.
*/
abstract class PrivateReactExtension @Inject constructor(project: Project) {
private val objects = project.objects
val root: DirectoryProperty = objects.directoryProperty()
val reactNativeDir: DirectoryProperty = objects.directoryProperty()
val nodeExecutableAndArgs: ListProperty<String> = objects.listProperty(String::class.java)
val codegenDir: DirectoryProperty = objects.directoryProperty()
}

Просмотреть файл

@ -14,6 +14,7 @@ import com.facebook.react.model.ModelPackageJson
import com.facebook.react.utils.Os.cliPath
import java.io.File
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
/**
* Computes the entry file for React Native. The Algo follows this order:
@ -189,13 +190,13 @@ internal fun projectPathToLibraryName(projectPath: String): String =
* Gradle module (generally the case for library projects) or we fallback to looking into the `root`
* folder of a React Native project (generally the case for app projects).
*/
internal fun findPackageJsonFile(project: Project, extension: ReactExtension): File? {
internal fun findPackageJsonFile(project: Project, rootProperty: DirectoryProperty): File? {
val inParent = project.file("../package.json")
if (inParent.exists()) {
return inParent
}
val fromExtension = extension.root.file("package.json").orNull?.asFile
val fromExtension = rootProperty.file("package.json").orNull?.asFile
if (fromExtension?.exists() == true) {
return fromExtension
}
@ -207,12 +208,15 @@ internal fun findPackageJsonFile(project: Project, extension: ReactExtension): F
* Function to look for the `package.json` and parse it. It returns a [ModelPackageJson] if found or
* null others.
*
* Please note that this function access the [ReactExtension] field properties and calls .get() on
* them, so calling this during apply() of the ReactPlugin is not recommended. It should be invoked
* inside lazy lambdas or at execution time.
* Please note that this function access the [DirectoryProperty] parameter and calls .get() on them,
* so calling this during apply() of the ReactPlugin is not recommended. It should be invoked inside
* lazy lambdas or at execution time.
*/
internal fun readPackageJsonFile(project: Project, extension: ReactExtension): ModelPackageJson? {
val packageJson = findPackageJsonFile(project, extension)
internal fun readPackageJsonFile(
project: Project,
rootProperty: DirectoryProperty
): ModelPackageJson? {
val packageJson = findPackageJsonFile(project, rootProperty)
return packageJson?.let { JsonUtils.fromCodegenJson(it) }
}

Просмотреть файл

@ -7,9 +7,9 @@
package com.facebook.react.utils
import com.facebook.react.ReactExtension
import com.facebook.react.model.ModelPackageJson
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
internal object ProjectUtils {
internal val Project.isNewArchEnabled: Boolean
@ -35,8 +35,8 @@ internal object ProjectUtils {
HERMES_FALLBACK
}
internal fun Project.needsCodegenFromPackageJson(extension: ReactExtension): Boolean {
val parsedPackageJson = readPackageJsonFile(this, extension)
internal fun Project.needsCodegenFromPackageJson(rootProperty: DirectoryProperty): Boolean {
val parsedPackageJson = readPackageJsonFile(this, rootProperty)
return needsCodegenFromPackageJson(parsedPackageJson)
}

Просмотреть файл

@ -231,7 +231,7 @@ class PathUtilsTest {
project.plugins.apply("com.facebook.react")
val extension = project.extensions.getByType(ReactExtension::class.java)
assertEquals(project.file("../package.json"), findPackageJsonFile(project, extension))
assertEquals(project.file("../package.json"), findPackageJsonFile(project, extension.root))
}
@Test
@ -245,7 +245,7 @@ class PathUtilsTest {
val extension =
project.extensions.getByType(ReactExtension::class.java).apply { root.set(moduleFolder) }
assertEquals(localFile, findPackageJsonFile(project, extension))
assertEquals(localFile, findPackageJsonFile(project, extension.root))
}
@Test
@ -257,7 +257,7 @@ class PathUtilsTest {
val extension =
project.extensions.getByType(ReactExtension::class.java).apply { root.set(moduleFolder) }
val actual = readPackageJsonFile(project, extension)
val actual = readPackageJsonFile(project, extension.root)
assertNull(actual)
}
@ -272,7 +272,7 @@ class PathUtilsTest {
val extension =
project.extensions.getByType(ReactExtension::class.java).apply { root.set(moduleFolder) }
val actual = readPackageJsonFile(project, extension)
val actual = readPackageJsonFile(project, extension.root)
assertNotNull(actual)
assertNull(actual!!.codegenConfig)
@ -298,7 +298,7 @@ class PathUtilsTest {
val extension =
project.extensions.getByType(ReactExtension::class.java).apply { root.set(moduleFolder) }
val actual = readPackageJsonFile(project, extension)
val actual = readPackageJsonFile(project, extension.root)
assertNotNull(actual)
assertNotNull(actual!!.codegenConfig)

Просмотреть файл

@ -125,7 +125,7 @@ class ProjectUtilsTest {
.trimIndent())
}
extension.root.set(tempFolder.root)
assertTrue(project.needsCodegenFromPackageJson(extension))
assertTrue(project.needsCodegenFromPackageJson(extension.root))
}
@Test
@ -143,7 +143,7 @@ class ProjectUtilsTest {
.trimIndent())
}
extension.root.set(tempFolder.root)
assertFalse(project.needsCodegenFromPackageJson(extension))
assertFalse(project.needsCodegenFromPackageJson(extension.root))
}
@Test
@ -167,7 +167,7 @@ class ProjectUtilsTest {
val project = createProject()
val extension = TestReactExtension(project)
assertFalse(project.needsCodegenFromPackageJson(extension))
assertFalse(project.needsCodegenFromPackageJson(extension.root))
}
@Test