codegen Gradle: introduce com.facebook.react.codegen plugin to replace architecture.gradle script

Summary:
Instead of applying configs from gradle scripts, this introduces a proper Gradle plugin to enable Codegen in an application or library project. In the build.gradle, one enables it by:

```
plugins {
    id("com.android.application")
    id("com.facebook.react.codegen") // <---
}

// ...

react { // <--- the new plugin extension
    enableCodegen = System.getenv("USE_CODEGEN")
    jsRootDir = file("$rootDir/RNTester")
    reactNativeRootDir = file("$rootDir")
}
```

The plugin supports `react` plugin extension as demonstrated above. Adding this:
* automatically generates all TurboModule Java files via react-native-codegen **before the `preBuild` Gradle task**
* automatically adds the files to the `android {}` project configuration
* is done per project (build.gradle)

This will be the foundation for future React Native gradle plugin beyond just for react-native-codegen.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D23065685

fbshipit-source-id: 4ea67e48fab33b238c0973463cdb00de8cdadfcc
This commit is contained in:
Kevin Gozali 2020-08-12 11:10:33 -07:00 committed by Facebook GitHub Bot
parent b7d6b325c2
commit e8c1eeeb06
9 changed files with 188 additions and 112 deletions

View File

@ -123,7 +123,7 @@ android {
}
dexOptions {
javaMaxHeapSize "4g"
javaMaxHeapSize "4g"
}
flavorDimensions "vm"
@ -222,3 +222,9 @@ dependencies {
androidTestImplementation('com.wix:detox:+') { transitive = true }
androidTestImplementation 'junit:junit:4.12'
}
react {
enableCodegen = System.getenv("USE_CODEGEN") ?: false
jsRootDir = file("$rootDir/RNTester")
reactNativeRootDir = file("$rootDir")
}

View File

@ -7,9 +7,9 @@
plugins {
id("com.android.library")
id("com.facebook.react.codegen")
id("maven")
id("de.undercouch.download")
id("com.facebook.react.codegen")
}
import java.nio.file.Paths
@ -465,8 +465,9 @@ dependencies {
}
apply(from: "release.gradle")
apply(from: "../architecture.gradle");
generateNativeArtifactsFromJavaScript([
enableCodegen: System.getenv("USE_CODEGEN"),
jsRootDir: "${file('../Libraries')}",
])
react {
enableCodegen = System.getenv("USE_CODEGEN") ?: false
jsRootDir = file("../Libraries")
reactNativeRootDir = file("$rootDir")
}

View File

@ -1,88 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
def codegenDir = "$rootDir/packages/react-native-codegen";
def codegenGenerateSchemaCli = "$codegenDir/lib/cli/combine/combine-js-to-schema-cli.js";
def codegenGenerateNativeModuleSpecsCli = "$rootDir/scripts/generate-native-modules-specs-cli.js";
def schemaFile = "schema.json";
// TODO: Consider building Gradle plugin instead.
/**
* The config is derived from app-level `project.react`.
*/
ext.generateNativeArtifactsFromJavaScript = { Map config ->
def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"];
def jsRootDir = config.jsRootDir ?: config.root;
if (!config.enableCodegen) {
return;
}
// 1. Create build dir
def generatedSrcDir = file("$buildDir/generated/source/codegen");
// 2. Produce schema from JS files.
task generateCodegenSchemaFromJavaScript(type: Exec) {
doFirst {
generatedSrcDir.deleteDir()
generatedSrcDir.mkdirs()
}
def schemaOutputFile = file("$generatedSrcDir/$schemaFile");
inputs.files fileTree(dir: codegenDir)
inputs.files fileTree(dir: jsRootDir)
outputs.file(schemaOutputFile)
def execCommands = ["yarn"] + nodeExecutableAndArgs + [
codegenGenerateSchemaCli,
"$schemaOutputFile",
jsRootDir,
];
commandLine execCommands
ext.schema = {
schemaOutputFile
}
}
// 3. Generate Java code from schema
task generateCodegenArtifactsFromSchema(dependsOn: 'generateCodegenSchemaFromJavaScript', type: Exec) {
def generatedSchema = tasks.generateCodegenSchemaFromJavaScript.schema();
def javaPackageName = config.codegenJavaPackageName ?: "com.facebook.fbreact.specs.beta";
def javaPackageDir = javaPackageName.replace(".", "/");
def outputDir = file("$generatedSrcDir/java/$javaPackageDir");
inputs.files fileTree(dir: codegenDir)
inputs.files generatedSchema
outputs.dir(outputDir)
def execCommands = ["yarn"] + nodeExecutableAndArgs + [
codegenGenerateNativeModuleSpecsCli,
"android",
"$generatedSchema",
"$outputDir",
];
commandLine execCommands
}
// 4. add deps + srcs
preBuild.dependsOn generateCodegenArtifactsFromSchema
android {
sourceSets {
main {
java {
srcDirs += "$generatedSrcDir/java"
}
}
}
}
}

View File

@ -19,5 +19,7 @@ gradlePlugin {
}
dependencies {
implementation 'com.android.tools.build:gradle:3.5.3'
implementation 'com.google.guava:guava:29.0-jre'
implementation 'com.squareup:javapoet:1.13.0'
}

View File

@ -7,31 +7,137 @@
package com.facebook.react.codegen.plugin;
import com.facebook.react.codegen.generator.JavaGenerator;
import java.io.IOException;
import com.android.build.gradle.BaseExtension;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.File;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.Exec;
/** A Gradle plugin to enable code generation from JavaScript in Gradle environment. */
/**
* A Gradle 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
*/
public class CodegenPlugin implements Plugin<Project> {
public void apply(final Project project) {
// Register a task
final CodegenPluginExtension extension =
project.getExtensions().create("react", CodegenPluginExtension.class, project);
// 1. Set up build dir.
final File generatedSrcDir = new File(project.getBuildDir(), "generated/source/codegen");
final File generatedSchemaFile = new File(generatedSrcDir, "schema.json");
// 2. Task: produce schema from JS files.
project
.getTasks()
.register(
"generateJava",
"generateCodegenSchemaFromJavaScript",
Exec.class,
task -> {
task.doLast(
if (!extension.enableCodegen) {
return;
}
task.doFirst(
s -> {
if (System.getenv("USE_CODEGEN").isEmpty()) {
return;
}
try {
JavaGenerator generator = new JavaGenerator("", "");
generator.build();
} catch (IOException e) {
}
generatedSrcDir.delete();
generatedSrcDir.mkdirs();
});
task.getInputs()
.files(project.fileTree(ImmutableMap.of("dir", extension.codegenDir())));
task.getInputs()
.files(
project.fileTree(
ImmutableMap.of(
"dir",
extension.jsRootDir,
"includes",
ImmutableList.of("**/*.js"))));
task.getOutputs().file(generatedSchemaFile);
ImmutableList<String> execCommands =
new ImmutableList.Builder<String>()
.add("yarn")
.addAll(ImmutableList.copyOf(extension.nodeExecutableAndArgs))
.add(extension.codegenGenerateSchemaCLI().getAbsolutePath())
.add(generatedSchemaFile.getAbsolutePath())
.add(extension.jsRootDir.getAbsolutePath())
.build();
task.commandLine(execCommands);
});
// 3. Task: generate Java code from schema.
project
.getTasks()
.register(
"generateCodegenArtifactsFromSchema",
Exec.class,
task -> {
if (!extension.enableCodegen) {
return;
}
task.dependsOn("generateCodegenSchemaFromJavaScript");
// TODO: The codegen tool should produce this outputDir structure based on
// the provided Java package name.
File outputDir =
new File(
generatedSrcDir,
"java/" + extension.codegenJavaPackageName.replace(".", "/"));
task.getInputs()
.files(project.fileTree(ImmutableMap.of("dir", extension.codegenDir())));
task.getInputs().files(generatedSchemaFile);
task.getOutputs().dir(outputDir);
ImmutableList<String> execCommands =
new ImmutableList.Builder<String>()
.add("yarn")
.addAll(ImmutableList.copyOf(extension.nodeExecutableAndArgs))
.add(extension.codegenGenerateNativeModuleSpecsCLI().getAbsolutePath())
.add("android")
.add(generatedSchemaFile.getAbsolutePath())
.add(outputDir.getAbsolutePath())
.build();
task.commandLine(execCommands);
});
// 4. Add dependencies & generated sources to the project.
// Note: This last step needs to happen after the project has been evaluated.
project.afterEvaluate(
s -> {
if (!extension.enableCodegen) {
return;
}
// `preBuild` is one of the base tasks automatically registered by Gradle.
// This will invoke the codegen before compiling the entire project.
Task preBuild = project.getTasks().findByName("preBuild");
if (preBuild != null) {
preBuild.dependsOn("generateCodegenArtifactsFromSchema");
}
/**
* Finally, update the android configuration to include the generated sources. This
* equivalent to this DSL:
*
* <p>android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } }
*
* <p>See documentation at
* https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.BaseExtension.html.
*/
BaseExtension android = (BaseExtension) project.getExtensions().getByName("android");
android
.getSourceSets()
.getByName("main")
.getJava()
.srcDir(new File(generatedSrcDir, "java"));
// TODO: Add JNI sources.
});
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Facebook, Inc. and its 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.codegen.plugin;
import java.io.File;
import org.gradle.api.Project;
public class CodegenPluginExtension {
// TODO: Remove beta.
public String codegenJavaPackageName = "com.facebook.fbreact.specs.beta";
public boolean enableCodegen;
public File jsRootDir;
public String[] nodeExecutableAndArgs = {"node"};
public File reactNativeRootDir;
public CodegenPluginExtension(Project project) {
this.reactNativeRootDir = new File(project.getRootDir(), "node_modules/react-native");
}
public File codegenDir() {
return new File(this.reactNativeRootDir, "packages/react-native-codegen");
}
public File codegenGenerateSchemaCLI() {
return new File(this.codegenDir(), "lib/cli/combine/combine-js-to-schema-cli.js");
}
public File codegenGenerateNativeModuleSpecsCLI() {
return new File(this.reactNativeRootDir, "scripts/generate-native-modules-specs-cli.js");
}
}

View File

@ -5,6 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
pluginManagement {
repositories {
gradlePluginPortal()
mavenLocal()
google()
}
}
rootProject.name = 'react-native-codegen'
include(":generator")

View File

@ -349,6 +349,3 @@ afterEvaluate {
}
}
}
apply(from: "$rootDir/architecture.gradle");
generateNativeArtifactsFromJavaScript(project.react);

View File

@ -5,6 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
pluginManagement {
repositories {
gradlePluginPortal()
mavenLocal()
google()
}
}
include(
":ReactAndroid",
":RNTester:android:app"