Skip to content

Commit

Permalink
Extend the React Native Gradle plugin to accept a config from package…
Browse files Browse the repository at this point in the history
….json

Summary:
This extends the Gradle plugin to allow configuration for `codegenConfig` from the
`package.json` that lives in one of the root folder.

There are a couple of points open for discussion. The most important one
is that now we're moving from absolute paths to relative paths, from the
package.json location. I'm not entirely sure this will work correctly
for users in monorepos, so we might consider this carefully.

Moreover, I've moved the `codegenJavaPackageName` to be `android.javaPackageName`.
Happy to discuss this further.

Changelog:
[Android] [Added] - Extend the React Native Gradle plugin to accept a config from package.json

Reviewed By: cipolleschi

Differential Revision: D36374475

fbshipit-source-id: fe669ebd5bc92abbbe57677c1995d0e01f2400d7
  • Loading branch information
cortinico authored and facebook-github-bot committed May 23, 2022
1 parent 406a474 commit 5f3c5aa
Showing 14 changed files with 298 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.tasks.BuildCodegenCLITask
import com.facebook.react.tasks.GenerateCodegenArtifactsTask
import com.facebook.react.tasks.GenerateCodegenSchemaTask
import com.facebook.react.utils.JsonUtils
import java.io.File
import kotlin.system.exitProcess
import org.gradle.api.Plugin
@@ -67,10 +68,10 @@ 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/6.5.1/javadoc/org/gradle/api/Project.html
* information: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
*/
private fun applyCodegenPlugin(project: Project, extension: ReactExtension) {
// 1. Set up build dir.
// First, we set up the output dir for the codegen.
val generatedSrcDir = File(project.buildDir, "generated/source/codegen")

val buildCodegenTask =
@@ -80,18 +81,33 @@ class ReactPlugin : Plugin<Project> {
it.bashWindowsHome.set(bashWindowsHome)
}

// 2. Task: produce schema from JS files.
// We create the task to produce schema from JS files.
val generateCodegenSchemaTask =
project.tasks.register(
"generateCodegenSchemaFromJavaScript", GenerateCodegenSchemaTask::class.java) {
"generateCodegenSchemaFromJavaScript", GenerateCodegenSchemaTask::class.java) { it ->
it.dependsOn(buildCodegenTask)
it.jsRootDir.set(extension.jsRootDir)
it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)
it.codegenDir.set(extension.codegenDir)
it.generatedSrcDir.set(generatedSrcDir)

// We're reading the package.json at configuration time to properly feed
// the `jsRootDir` @Input property of this task. Therefore, the
// parsePackageJson should be invoked here.
val parsedPackageJson =
extension.root.file("package.json").orNull?.asFile?.let {
JsonUtils.fromCodegenJson(it)
}

val parsedJsRootDir =
parsedPackageJson?.codegenConfig?.jsSrcsDir?.let { relativePath ->
extension.root.dir(relativePath)
}
?: extension.jsRootDir

it.jsRootDir.set(parsedJsRootDir)
}

// 3. Task: generate Java code from schema.
// We create the task to generate Java code from schema.
val generateCodegenArtifactsTask =
project.tasks.register(
"generateCodegenArtifactsFromSchema", GenerateCodegenArtifactsTask::class.java) {
@@ -100,12 +116,13 @@ class ReactPlugin : Plugin<Project> {
it.deprecatedReactRoot.set(extension.reactRoot)
it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)
it.codegenDir.set(extension.codegenDir)
it.generatedSrcDir.set(generatedSrcDir)
it.packageJsonFile.set(extension.root.file("package.json"))
it.codegenJavaPackageName.set(extension.codegenJavaPackageName)
it.libraryName.set(extension.libraryName)
it.generatedSrcDir.set(generatedSrcDir)
}

// 4. Add dependencies & generated sources to the project.
// We add dependencies & generated sources to the project.
// Note: This last step needs to happen after the project has been evaluated.
project.afterEvaluate {

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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.model

data class ModelCodegenConfig(
val name: String?,
val type: String?,
val jsSrcsDir: String?,
val android: ModelCodegenConfigAndroid?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.model

data class ModelCodegenConfigAndroid(val javaPackageName: String?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.model

data class ModelPackageJson(val codegenConfig: ModelCodegenConfig?)
Original file line number Diff line number Diff line change
@@ -7,14 +7,20 @@

package com.facebook.react.tasks

import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.windowsAwareCommandLine
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.Exec
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory

abstract class GenerateCodegenArtifactsTask : Exec() {

@@ -24,6 +30,8 @@ abstract class GenerateCodegenArtifactsTask : Exec() {

@get:Internal abstract val generatedSrcDir: DirectoryProperty

@get:InputFile abstract val packageJsonFile: RegularFileProperty

@get:Input abstract val nodeExecutableAndArgs: ListProperty<String>

@get:Input abstract val codegenJavaPackageName: Property<String>
@@ -46,7 +54,9 @@ abstract class GenerateCodegenArtifactsTask : Exec() {

override fun exec() {
checkForDeprecatedProperty()
setupCommandLine()

val (resolvedLibraryName, resolvedCodegenJavaPackageName) = resolveTaskParameters()
setupCommandLine(resolvedLibraryName, resolvedCodegenJavaPackageName)
super.exec()
}

@@ -74,7 +84,20 @@ abstract class GenerateCodegenArtifactsTask : Exec() {
}
}

internal fun setupCommandLine() {
internal fun resolveTaskParameters(): Pair<String, String> {
val parsedPackageJson =
if (packageJsonFile.isPresent && packageJsonFile.get().asFile.exists()) {
JsonUtils.fromCodegenJson(packageJsonFile.get().asFile)
} else {
null
}
val resolvedLibraryName = parsedPackageJson?.codegenConfig?.name ?: libraryName.get()
val resolvedCodegenJavaPackageName =
parsedPackageJson?.codegenConfig?.android?.javaPackageName ?: codegenJavaPackageName.get()
return resolvedLibraryName to resolvedCodegenJavaPackageName
}

internal fun setupCommandLine(libraryName: String, codegenJavaPackageName: String) {
commandLine(
windowsAwareCommandLine(
*nodeExecutableAndArgs.get().toTypedArray(),
@@ -86,8 +109,8 @@ abstract class GenerateCodegenArtifactsTask : Exec() {
"--outputDir",
generatedSrcDir.get().asFile.absolutePath,
"--libraryName",
libraryName.get(),
libraryName,
"--javaPackageName",
codegenJavaPackageName.get()))
codegenJavaPackageName))
}
}
Original file line number Diff line number Diff line change
@@ -29,7 +29,12 @@ abstract class GenerateCodegenSchemaTask : Exec() {

@get:Input abstract val nodeExecutableAndArgs: ListProperty<String>

@get:InputFiles val jsInputFiles = project.fileTree(jsRootDir) { it.include("**/*.js") }
@get:InputFiles
val jsInputFiles =
project.fileTree(jsRootDir) {
it.include("**/*.js")
it.exclude("**/generated/source/codegen/**/*")
}

@get:OutputFile
val generatedSchemaFile: Provider<RegularFile> = generatedSrcDir.file("schema.json")
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.utils

import com.facebook.react.model.ModelPackageJson
import com.google.gson.Gson
import java.io.File

object JsonUtils {
private val gsonConverter = Gson()

fun fromCodegenJson(input: File): ModelPackageJson? =
input.bufferedReader().use {
runCatching { gsonConverter.fromJson(it, ModelPackageJson::class.java) }.getOrNull()
}
}
Original file line number Diff line number Diff line change
@@ -58,11 +58,14 @@ class GenerateCodegenArtifactsTaskTest {

@Test
fun generateCodegenSchema_simpleProperties_areInsideInput() {
val packageJsonFile = tempFolder.newFile("package.json")

val task =
createTestTask<GenerateCodegenArtifactsTask> {
it.nodeExecutableAndArgs.set(listOf("npm", "help"))
it.codegenJavaPackageName.set("com.example.test")
it.libraryName.set("example-test")
it.packageJsonFile.set(packageJsonFile)
}

assertEquals(listOf("npm", "help"), task.nodeExecutableAndArgs.get())
@@ -75,7 +78,7 @@ class GenerateCodegenArtifactsTaskTest {

@Test
@WithOs(OS.UNIX)
fun setupCommandLine_withoutJavaGenerator_willSetupCorrectly() {
fun setupCommandLine_willSetupCorrectly() {
val reactNativeDir = tempFolder.newFolder("node_modules/react-native/")
val codegenDir = tempFolder.newFolder("codegen")
val outputDir = tempFolder.newFolder("output")
@@ -86,11 +89,9 @@ class GenerateCodegenArtifactsTaskTest {
it.codegenDir.set(codegenDir)
it.generatedSrcDir.set(outputDir)
it.nodeExecutableAndArgs.set(listOf("--verbose"))
it.codegenJavaPackageName.set("com.example.test")
it.libraryName.set("example-test")
}

task.setupCommandLine()
task.setupCommandLine("example-test", "com.example.test")

assertEquals(
listOf(
@@ -109,4 +110,79 @@ class GenerateCodegenArtifactsTaskTest {
),
task.commandLine.toMutableList())
}

@Test
fun resolveTaskParameters_withConfigInPackageJson_usesIt() {
val packageJsonFile =
tempFolder.newFile("package.json").apply {
// language=JSON
writeText(
"""
{
"name": "@a/libray",
"codegenConfig": {
"name": "an-awesome-library",
"android": {
"javaPackageName": "com.awesome.package"
}
}
}
""".trimIndent())
}

val task =
createTestTask<GenerateCodegenArtifactsTask> {
it.packageJsonFile.set(packageJsonFile)
it.codegenJavaPackageName.set("com.example.ignored")
it.libraryName.set("a-library-name-that-is-ignored")
}

val (libraryName, javaPackageName) = task.resolveTaskParameters()

assertEquals("an-awesome-library", libraryName)
assertEquals("com.awesome.package", javaPackageName)
}

@Test
fun resolveTaskParameters_withConfigMissingInPackageJson_usesGradleOne() {
val packageJsonFile =
tempFolder.newFile("package.json").apply {
// language=JSON
writeText(
"""
{
"name": "@a/libray",
"codegenConfig": {
}
}
""".trimIndent())
}

val task =
createTestTask<GenerateCodegenArtifactsTask> {
it.packageJsonFile.set(packageJsonFile)
it.codegenJavaPackageName.set("com.example.test")
it.libraryName.set("a-library-name-from-gradle")
}

val (libraryName, javaPackageName) = task.resolveTaskParameters()

assertEquals("a-library-name-from-gradle", libraryName)
assertEquals("com.example.test", javaPackageName)
}

@Test
fun resolveTaskParameters_withMissingPackageJson_usesGradleOne() {
val task =
createTestTask<GenerateCodegenArtifactsTask> {
it.packageJsonFile.set(File(tempFolder.root, "package.json"))
it.codegenJavaPackageName.set("com.example.test")
it.libraryName.set("a-library-name-from-gradle")
}

val (libraryName, javaPackageName) = task.resolveTaskParameters()

assertEquals("a-library-name-from-gradle", libraryName)
assertEquals("com.example.test", javaPackageName)
}
}
Loading

0 comments on commit 5f3c5aa

Please sign in to comment.