diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppExtension.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppExtension.kt index eb923245fdd..01343bcc126 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppExtension.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppExtension.kt @@ -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 = 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 ) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt index 9eb19c534b8..14265ca6647 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt @@ -17,8 +17,7 @@ import org.gradle.kotlin.dsl.getByType class ReactAppPlugin : Plugin { override fun apply(project: Project) { - // todo rename codegen or combine extensions - val config = project.extensions.create("reactApp", project.projectDir) + val config = project.extensions.create("reactApp", project) project.afterEvaluate { val androidConfiguration = extensions.getByType() diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt index 652e1997841..f929168d5d3 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt @@ -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("bundle${targetName}JsAndAssets") { - group = "react" - description = "bundle JS and assets for $targetName." + val bundleTask = tasks.register("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() @@ -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("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) } } diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleJsAndAssetsTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleJsAndAssetsTask.kt new file mode 100644 index 00000000000..b793469dce9 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleJsAndAssetsTask.kt @@ -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 + @get:Input + lateinit var bundleCommand: String + @get:Input + var devEnabled: Boolean = true + @get:Input + lateinit var entryFile: File + @get:Input + var extraArgs: List = 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() + } +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/HermesBinaryTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/HermesBinaryTask.kt new file mode 100644 index 00000000000..eb05b60b667 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/HermesBinaryTask.kt @@ -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 = emptyList() + @get:InputFile + lateinit var jsBundleFile: File + + @get:Input + lateinit var composeSourceMapsCommand: List + @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() + } +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/TaskUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/TaskUtils.kt index 8596cd76c11..84aac7af043 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/TaskUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/TaskUtils.kt @@ -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()) } }