Extract bundle task

Summary:
Changelog: [Internal]

Extracts task definition for bundling and hermes binary into separate tasks in the new plugin.

Reviewed By: mdvacca

Differential Revision: D25915057

fbshipit-source-id: b1d8a4b5e8789c3b7832efea13274435c9391ccb
This commit is contained in:
Andrei Shikov
2021-02-22 08:16:51 -08:00
committed by Facebook GitHub Bot
parent dbbc1c1624
commit 959e135d80
6 changed files with 225 additions and 106 deletions
@@ -9,14 +9,15 @@ package com.facebook.react
import com.android.build.gradle.api.BaseVariant
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.Project
import java.io.File
open class ReactAppExtension(private val projectDir: File) {
open class ReactAppExtension(private val project: Project) {
var composeSourceMapsPath: String = "node_modules/react-native/scripts/compose-source-maps.js"
var bundleAssetName: String = "index.android.bundle"
var entryFile: File? = null
var bundleCommand: String = "bundle"
var reactRoot: File = File(projectDir, "../../")
var reactRoot: File = File(project.projectDir, "../../")
var inputExcludes: List<String> = listOf("android/**", "ios/**")
var bundleConfig: String? = null
var enableVmCleanup: Boolean = true
@@ -39,7 +40,7 @@ open class ReactAppExtension(private val projectDir: File) {
internal val detectedCliPath: String
get() = detectCliPath(
projectDir = projectDir,
projectDir = project.projectDir,
reactRoot = reactRoot,
preconfuredCliPath = cliPath
)
@@ -17,8 +17,7 @@ import org.gradle.kotlin.dsl.getByType
class ReactAppPlugin : Plugin<Project> {
override fun apply(project: Project) {
// todo rename codegen or combine extensions
val config = project.extensions.create<ReactAppExtension>("reactApp", project.projectDir)
val config = project.extensions.create<ReactAppExtension>("reactApp", project)
project.afterEvaluate {
val androidConfiguration = extensions.getByType<BaseExtension>()
@@ -10,16 +10,18 @@ package com.facebook.react
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.api.LibraryVariant
import com.facebook.react.tasks.windowsAwareCommandLine
import com.facebook.react.tasks.BundleJsAndAssetsTask
import com.facebook.react.tasks.HermesBinaryTask
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.Exec
import org.gradle.kotlin.dsl.withGroovyBuilder
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.register
import java.io.File
private const val REACT_GROUP = "react"
@Suppress("SpreadOperator")
internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactAppExtension) {
val targetName = variant.name.capitalize()
@@ -38,43 +40,26 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactAppE
val jsOutputSourceMapFile = File(jsSourceMapsDir, "${config.bundleAssetName}.map")
// Additional node and packager commandline arguments
val nodeExecutableAndArgs = config.nodeExecutableAndArgs.toTypedArray()
val nodeExecutableAndArgs = config.nodeExecutableAndArgs
val cliPath = config.detectedCliPath
val execCommand = nodeExecutableAndArgs + cliPath
val enableHermes = config.enableHermesForVariant(variant)
val bundleEnabled = variant.checkBundleEnabled(config)
val currentBundleTask = tasks.create<Exec>("bundle${targetName}JsAndAssets") {
group = "react"
description = "bundle JS and assets for $targetName."
val bundleTask = tasks.register<BundleJsAndAssetsTask>("bundle${targetName}JsAndAssets") {
val task = this
task.group = REACT_GROUP
task.description = "bundle JS and assets for $targetName."
// Create dirs if they are not there (e.g. the "clean" task just ran)
doFirst {
jsBundleDir.deleteRecursively()
jsBundleDir.mkdirs()
resourcesDir.deleteRecursively()
resourcesDir.mkdirs()
jsIntermediateSourceMapsDir.deleteRecursively()
jsIntermediateSourceMapsDir.mkdirs()
jsSourceMapsDir.deleteRecursively()
jsSourceMapsDir.mkdirs()
task.reactRoot = config.reactRoot
task.sources = fileTree(config.reactRoot) {
setExcludes(config.inputExcludes)
}
// Set up inputs and outputs so gradle can cache the result
inputs.files(
fileTree(config.reactRoot) {
setExcludes(config.inputExcludes)
}
)
outputs.dir(jsBundleDir)
outputs.dir(resourcesDir)
// Set up the call to the react-native cli
workingDir(config.reactRoot)
// Set up dev mode
val devEnabled = !(variant.name in config.devDisabledInVariants || variant.isRelease)
task.execCommand = execCommand
task.bundleCommand = config.bundleCommand
task.devEnabled = !(variant.name in config.devDisabledInVariants || isRelease)
task.entryFile = config.detectedEntryFile
val extraArgs = mutableListOf<String>()
@@ -91,79 +76,48 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactAppE
extraArgs.addAll(config.extraPackagerArgs)
windowsAwareCommandLine(
*execCommand,
config.bundleCommand,
"--platform", "android",
"--dev", "$devEnabled",
"--reset-cache",
"--entry-file", config.detectedEntryFile,
"--bundle-output", jsBundleFile,
"--assets-dest", resourcesDir,
"--sourcemap-output", if (enableHermes) jsPackagerSourceMapFile else jsOutputSourceMapFile,
*extraArgs.toTypedArray()
)
task.extraArgs = emptyList()
if (enableHermes) {
doLast {
val hermesFlags = if (isRelease) {
config.hermesFlagsRelease
} else {
config.hermesFlagsDebug
}.toTypedArray()
val hbcTempFile = file("$jsBundleFile.hbc")
exec {
windowsAwareCommandLine(
config.osAwareHermesCommand,
"-emit-binary",
"-out", hbcTempFile, jsBundleFile,
*hermesFlags
)
}
ant.withGroovyBuilder {
"move"(
"file" to hbcTempFile,
"toFile" to jsBundleFile
)
}
if (hermesFlags.contains("-output-source-map")) {
ant.withGroovyBuilder {
"move"(
"file" to "$jsBundleFile.hbc.map",
"toFile" to jsCompilerSourceMapFile
)
}
exec {
// TODO: set task dependencies for caching
// Set up the call to the compose-source-maps script
workingDir(config.reactRoot)
windowsAwareCommandLine(
*nodeExecutableAndArgs,
config.composeSourceMapsPath,
jsPackagerSourceMapFile,
jsCompilerSourceMapFile,
"-o", jsOutputSourceMapFile)
}
}
}
}
task.jsBundleDir = jsBundleDir
task.jsBundleFile = jsBundleFile
task.resourcesDir = resourcesDir
task.jsIntermediateSourceMapsDir = jsIntermediateSourceMapsDir
task.jsSourceMapsDir = jsSourceMapsDir
task.jsSourceMapsFile = if (enableHermes) jsPackagerSourceMapFile else jsOutputSourceMapFile
enabled = bundleEnabled
}
val hermesTask = tasks.register<HermesBinaryTask>("emit${targetName}HermesResources") {
val task = this
task.group = REACT_GROUP
task.description = "bundle hermes resources for $targetName"
task.reactRoot = config.reactRoot
task.hermesCommand = config.osAwareHermesCommand
task.hermesFlags = if (isRelease) config.hermesFlagsRelease else config.hermesFlagsDebug
task.jsBundleFile = jsBundleFile
task.composeSourceMapsCommand = nodeExecutableAndArgs + config.composeSourceMapsPath
task.jsPackagerSourceMapFile = jsPackagerSourceMapFile
task.jsCompilerSourceMapFile = jsCompilerSourceMapFile
task.jsOutputSourceMapFile = jsOutputSourceMapFile
task.dependsOn(bundleTask)
enabled = bundleEnabled && enableHermes
}
// todo expose bundle task and its generated folders
val generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
// val generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
val generatedResFolders = files(resourcesDir).builtBy(hermesTask, bundleTask)
//val generatedAssetsFolders = files(jsBundleDir).builtBy(hermesTask, bundleTask)
variant.registerGeneratedResFolders(generatedResFolders)
variant.mergeResourcesProvider.get().dependsOn(currentBundleTask)
variant.mergeResourcesProvider.get().dependsOn(bundleTask, hermesTask)
val packageTask = when (variant) {
is ApplicationVariant -> variant.packageApplicationProvider.get()
is LibraryVariant -> variant.packageLibraryProvider.get()
else -> tasks.findByName("package$targetName")!!
else -> tasks.findByName("package$targetName") ?: error("Couldn't find a package task for $targetName")
}
// pre bundle build task for Android plugin 3.2+
@@ -178,9 +132,9 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactAppE
from(resourcesDir)
into(file(resourcesDirConfigValue))
dependsOn(currentBundleTask)
dependsOn(bundleTask)
enabled = currentBundleTask.enabled
enabled = bundleEnabled
}
packageTask.dependsOn(currentCopyResTask)
@@ -215,7 +169,7 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactAppE
// mergeAssets must run first, as it clears the intermediates directory
dependsOn(variant.mergeAssetsProvider.get())
enabled = currentBundleTask.enabled
enabled = bundleEnabled
}
// mergeResources task runs before the bundle file is copied to the intermediate asset directory from Android plugin 4.1+.
@@ -262,8 +216,7 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactAppE
}
if (config.enableVmCleanup) {
val task = tasks.findByName("package$targetName")
task?.doFirst(vmSelectionAction)
packageTask.doFirst(vmSelectionAction)
}
}
@@ -0,0 +1,86 @@
/*
* 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.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileTree
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File
open class BundleJsAndAssetsTask : DefaultTask() {
lateinit var reactRoot: File
@get:InputFiles
@Suppress("UNUSED") // used to invalidate caches
lateinit var sources: FileTree
@get:Input
lateinit var execCommand: List<String>
@get:Input
lateinit var bundleCommand: String
@get:Input
var devEnabled: Boolean = true
@get:Input
lateinit var entryFile: File
@get:Input
var extraArgs: List<String> = emptyList()
@get:OutputDirectory
lateinit var jsBundleDir: File
@get:OutputFile
lateinit var jsBundleFile: File
@get:OutputDirectory
lateinit var resourcesDir: File
@get:OutputDirectory
lateinit var jsIntermediateSourceMapsDir: File
@get:OutputDirectory
lateinit var jsSourceMapsDir: File
@get:OutputFile
lateinit var jsSourceMapsFile: File
@TaskAction
fun run() {
cleanOutputDirectories()
executeBundleCommand()
}
private fun cleanOutputDirectories() {
jsBundleDir.recreateDir()
resourcesDir.recreateDir()
jsIntermediateSourceMapsDir.recreateDir()
jsSourceMapsDir.recreateDir()
}
private fun executeBundleCommand() {
project.exec {
workingDir(reactRoot)
@Suppress("SpreadOperator")
windowsAwareCommandLine(
*execCommand.toTypedArray(),
bundleCommand,
"--platform", "android",
"--dev", devEnabled,
"--reset-cache",
"--entry-file", entryFile,
"--bundle-output", jsBundleFile,
"--assets-dest", resourcesDir,
"--sourcemap-output", jsSourceMapsFile,
*extraArgs.toTypedArray()
)
}
}
private fun File.recreateDir() {
deleteRecursively()
mkdirs()
}
}
@@ -0,0 +1,81 @@
/*
* 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.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File
open class HermesBinaryTask : DefaultTask() {
lateinit var reactRoot: File
@get:Input
lateinit var hermesCommand: String
@get:Input
var hermesFlags: List<String> = emptyList()
@get:InputFile
lateinit var jsBundleFile: File
@get:Input
lateinit var composeSourceMapsCommand: List<String>
@get:Input
lateinit var jsPackagerSourceMapFile: File
@get:OutputFile
lateinit var jsCompilerSourceMapFile: File
@get:OutputFile
lateinit var jsOutputSourceMapFile: File
@TaskAction
fun run() {
val bytecodeTempFile = File("$jsBundleFile.hbc")
emitHermesBinary(outputFile = bytecodeTempFile)
bytecodeTempFile.moveTo(jsBundleFile)
if (hermesFlags.contains("-output-source-map")) {
val hermesTempSourceMapFile = File("$bytecodeTempFile.map")
hermesTempSourceMapFile.moveTo(jsCompilerSourceMapFile)
composeSourceMaps()
}
}
private fun emitHermesBinary(outputFile: File) {
project.exec {
@Suppress("SpreadOperator")
windowsAwareCommandLine(
hermesCommand,
"-emit-binary",
"-out", outputFile,
jsBundleFile,
*hermesFlags.toTypedArray()
)
}
}
private fun composeSourceMaps() {
project.exec {
workingDir(reactRoot)
@Suppress("SpreadOperator")
windowsAwareCommandLine(
*composeSourceMapsCommand.toTypedArray(),
jsPackagerSourceMapFile,
jsCompilerSourceMapFile,
"-o", jsOutputSourceMapFile
)
}
}
private fun File.moveTo(destination: File) {
copyTo(destination, overwrite = true)
delete()
}
}
@@ -10,11 +10,10 @@ package com.facebook.react.tasks
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.process.ExecSpec
@Suppress("SpreadOperator")
internal fun ExecSpec.windowsAwareCommandLine(vararg args: Any) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine("cmd", "/c", *args)
commandLine(listOf("cmd", "/c") + args)
} else {
commandLine(*args)
commandLine(args.toList())
}
}