Skip to content

Commit

Permalink
RNGP - Monorepo: Make sure libraries are honoring codegenDir provided…
Browse files Browse the repository at this point in the history
… by app (#36128)

Summary:
Pull Request resolved: #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
  • Loading branch information
cortinico authored and facebook-github-bot committed Feb 10, 2023
1 parent b67a4ae commit 0487108
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 45 deletions.
5 changes: 0 additions & 5 deletions ReactAndroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand All @@ -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)
}
}

Expand All @@ -82,34 +97,36 @@ 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")

// We specify the default value (convention) for jsRootDir.
// 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 }
}

Expand All @@ -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 }
}

Expand All @@ -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 }
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
}
Expand All @@ -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) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class ProjectUtilsTest {
.trimIndent())
}
extension.root.set(tempFolder.root)
assertTrue(project.needsCodegenFromPackageJson(extension))
assertTrue(project.needsCodegenFromPackageJson(extension.root))
}

@Test
Expand All @@ -143,7 +143,7 @@ class ProjectUtilsTest {
.trimIndent())
}
extension.root.set(tempFolder.root)
assertFalse(project.needsCodegenFromPackageJson(extension))
assertFalse(project.needsCodegenFromPackageJson(extension.root))
}

@Test
Expand All @@ -167,7 +167,7 @@ class ProjectUtilsTest {
val project = createProject()
val extension = TestReactExtension(project)

assertFalse(project.needsCodegenFromPackageJson(extension))
assertFalse(project.needsCodegenFromPackageJson(extension.root))
}

@Test
Expand Down

0 comments on commit 0487108

Please sign in to comment.