From 2187edc3211d7dbc8b04afbf3d8740ac2790074d Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 24 Oct 2022 06:08:47 -0700 Subject: [PATCH 1/5] RNGP - Introduce the BundleAndHermesCTask Summary: This is part of a series of tasks to make the React Native Gradle Plugin (RNGP) variant-aware. Here I'm creating a new task BundleAndHermesCTask that is variant-aware. Historically we had problems with the Bundle and the Hermes task being separated and overriding their own output. Consolidating those two steps in a single one will make it easier to support seaprate variants. I'm going to use this task in a subsequent diff. Changelog: [Internal] [Changed] - RNGP - Introduce the BundleAndHermesCTask Differential Revision: D40633108 fbshipit-source-id: eb5e5eeef2933c4296b1b117f2a9d1035337f1a5 --- .../facebook/react/tasks/BundleHermesCTask.kt | 185 ++++++++++ .../com/facebook/react/utils/TaskUtils.kt | 5 +- .../react/tasks/BundleHermesCTaskTest.kt | 315 ++++++++++++++++++ .../com/facebook/react/utils/TaskUtilsTest.kt | 6 + 4 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt create mode 100644 packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt new file mode 100644 index 000000000000..c1dfb58fd136 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt @@ -0,0 +1,185 @@ +/* + * 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.tasks + +import com.facebook.react.utils.detectOSAwareHermesCommand +import com.facebook.react.utils.moveTo +import com.facebook.react.utils.windowsAwareCommandLine +import java.io.File +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* + +abstract class BundleHermesCTask : DefaultTask() { + + init { + group = "react" + } + + @get:Internal abstract val root: DirectoryProperty + + @get:InputFiles + val sources: ConfigurableFileTree = + project.fileTree(root) { + it.include("**/*.js") + it.include("**/*.jsx") + it.include("**/*.ts") + it.include("**/*.tsx") + it.exclude("**/android/**/*") + it.exclude("**/ios/**/*") + it.exclude("**/build/**/*") + it.exclude("**/node_modules/**/*") + } + + @get:Input abstract val nodeExecutableAndArgs: ListProperty + + @get:Input abstract val cliPath: Property + + @get:Input abstract val composeSourceMapsPath: Property + + @get:Input abstract val bundleCommand: Property + + @get:InputFile abstract val entryFile: RegularFileProperty + + @get:InputFile @get:Optional abstract val bundleConfig: RegularFileProperty + + @get:Input abstract val bundleAssetName: Property + + @get:Input abstract val minifyEnabled: Property + + @get:Input abstract val hermesEnabled: Property + + @get:Input abstract val devEnabled: Property + + @get:Input abstract val extraPackagerArgs: ListProperty + + @get:Input abstract val hermesCommand: Property + + @get:Input abstract val hermesFlags: ListProperty + + @get:OutputDirectory abstract val jsBundleDir: DirectoryProperty + + @get:OutputDirectory abstract val resourcesDir: DirectoryProperty + + @get:OutputDirectory abstract val jsIntermediateSourceMapsDir: RegularFileProperty + + @get:OutputDirectory abstract val jsSourceMapsDir: DirectoryProperty + + @TaskAction + fun run() { + jsBundleDir.get().asFile.mkdirs() + resourcesDir.get().asFile.mkdirs() + jsIntermediateSourceMapsDir.get().asFile.mkdirs() + jsSourceMapsDir.get().asFile.mkdirs() + val bundleAssetFilename = bundleAssetName.get() + + val bundleFile = File(jsBundleDir.get().asFile, bundleAssetFilename) + val packagerSourceMap = resolvePackagerSourceMapFile(bundleAssetFilename) + + val bundleCommand = getBundleCommand(bundleFile, packagerSourceMap) + runCommand(bundleCommand) + + if (hermesEnabled.get()) { + val detectedHermesCommand = detectOSAwareHermesCommand(root.get().asFile, hermesCommand.get()) + val bytecodeTempFile = File("${bundleFile}.hbc") + val outputSourceMap = resolveOutputSourceMap(bundleAssetFilename) + val compilerSourceMap = resolveCompilerSourceMap(bundleAssetFilename) + + val hermesCommand = getHermescCommand(detectedHermesCommand, bundleFile, packagerSourceMap) + runCommand(hermesCommand) + bytecodeTempFile.moveTo(bundleFile) + + if (hermesFlags.get().contains("-output-source-map")) { + val hermesTempSourceMapFile = File("$bytecodeTempFile.map") + hermesTempSourceMapFile.moveTo(compilerSourceMap) + val composeSourceMapsCommand = + getComposeSourceMapsCommand(packagerSourceMap, compilerSourceMap, outputSourceMap) + runCommand(composeSourceMapsCommand) + } + } + } + + internal fun resolvePackagerSourceMapFile(bundleAssetName: String) = + if (hermesEnabled.get()) { + File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.packager.map") + } else { + resolveOutputSourceMap(bundleAssetName) + } + + internal fun resolveOutputSourceMap(bundleAssetName: String) = + File(jsSourceMapsDir.get().asFile, "$bundleAssetName.map") + + internal fun resolveCompilerSourceMap(bundleAssetName: String) = + File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.compiler.map") + + private fun runCommand(command: List) { + project.exec { + it.workingDir(root.get().asFile) + it.commandLine(command) + } + } + + internal fun getBundleCommand(bundleFile: File, sourceMapFile: File): List = + windowsAwareCommandLine( + buildList { + addAll(nodeExecutableAndArgs.get()) + add(cliPath.get()) + add(bundleCommand.get()) + add("--platform") + add("android") + add("--dev") + add(devEnabled.get().toString()) + add("--reset-cache") + add("--entry-file") + add(entryFile.get().asFile.toString()) + add("--bundle-output") + add(bundleFile.toString()) + add("--assets-dest") + add(resourcesDir.get().asFile.toString()) + add("--sourcemap-output") + add(sourceMapFile.toString()) + if (bundleConfig.isPresent) { + add("--config") + add(bundleConfig.get().asFile.absolutePath) + } + add("--minify") + add(minifyEnabled.get().toString()) + addAll(extraPackagerArgs.get()) + add("--verbose") + }) + + internal fun getHermescCommand( + hermesCommand: String, + bundleFile: File, + outputFile: File + ): List = + windowsAwareCommandLine( + hermesCommand, + "-emit-binary", + "-out", + outputFile.absolutePath, + bundleFile.absolutePath, + *hermesFlags.get().toTypedArray()) + + internal fun getComposeSourceMapsCommand( + packagerSourceMap: File, + compilerSourceMap: File, + outputSourceMap: File + ): List = + windowsAwareCommandLine( + *nodeExecutableAndArgs.get().toTypedArray(), + composeSourceMapsPath.get(), + packagerSourceMap.toString(), + compilerSourceMap.toString(), + "-o", + outputSourceMap.toString()) +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/TaskUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/TaskUtils.kt index fd557079f59d..df99d06e47bc 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/TaskUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/TaskUtils.kt @@ -8,10 +8,13 @@ package com.facebook.react.utils internal fun windowsAwareCommandLine(vararg args: Any): List = + windowsAwareCommandLine(args.toList()) + +internal fun windowsAwareCommandLine(args: List): List = if (Os.isWindows()) { listOf("cmd", "/c") + args } else { - args.toList() + args } internal fun windowsAwareBashCommandLine( diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt new file mode 100644 index 000000000000..8a180e1080d1 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt @@ -0,0 +1,315 @@ +/* + * 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.tasks + +import com.facebook.react.tests.OsRule +import com.facebook.react.tests.createTestTask +import java.io.File +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class BundleHermesCTaskTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @get:Rule val osRule = OsRule() + + @Test + fun bundleTask_groupIsSetCorrectly() { + val task = createTestTask {} + assertEquals("react", task.group) + } + + @Test + fun bundleTask_inputFiles_areSetCorrectly() { + val rootDir = + tempFolder.newFolder("js").apply { + File(this, "file.js").createNewFile() + File(this, "file.jsx").createNewFile() + File(this, "file.ts").createNewFile() + File(this, "file.tsx").createNewFile() + } + + val task = createTestTask { it.root.set(rootDir) } + + assertEquals(4, task.sources.files.size) + assertEquals( + setOf( + File(rootDir, "file.js"), + File(rootDir, "file.jsx"), + File(rootDir, "file.ts"), + File(rootDir, "file.tsx")), + task.sources.files) + } + + @Test + fun bundleTask_inputFilesInExcludedPath_areExcluded() { + fun File.createFileAndPath() { + parentFile.mkdirs() + createNewFile() + } + + val rootDir = + tempFolder.newFolder("js").apply { + File(this, "afolder/includedfile.js").createFileAndPath() + // Those files should be excluded due to their filepath + File(this, "android/excludedfile.js").createFileAndPath() + File(this, "ios/excludedfile.js").createFileAndPath() + File(this, "build/excludedfile.js").createFileAndPath() + File(this, "node_modules/react-native/excludedfile.js").createFileAndPath() + } + + val task = createTestTask { it.root.set(rootDir) } + + assertEquals( + setOf( + "**/android/**/*", + "**/ios/**/*", + "**/build/**/*", + "**/node_modules/**/*", + ), + task.sources.excludes) + assertEquals(1, task.sources.files.size) + assertEquals(setOf(File(rootDir, "afolder/includedfile.js")), task.sources.files) + } + + @Test + fun bundleTask_staticInputs_areSetCorrectly() { + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) + it.cliPath.set("../node_modules/react-native/cli.js") + it.composeSourceMapsPath.set( + "../node_modules/react-native/scripts/compose-source-maps.js") + it.bundleCommand.set("bundle") + it.bundleAssetName.set("myassetname") + it.minifyEnabled.set(true) + it.hermesEnabled.set(true) + it.devEnabled.set(true) + it.extraPackagerArgs.set(listOf("extra", "arg")) + it.hermesCommand.set("./my-hermesc") + it.hermesFlags.set(listOf("flag1", "flag2")) + } + + assertEquals(listOf("node", "arg1", "arg2"), task.nodeExecutableAndArgs.get()) + assertEquals("../node_modules/react-native/cli.js", task.cliPath.get()) + assertEquals( + "../node_modules/react-native/scripts/compose-source-maps.js", + task.composeSourceMapsPath.get()) + assertEquals("bundle", task.bundleCommand.get()) + assertEquals("myassetname", task.bundleAssetName.get()) + assertTrue(task.minifyEnabled.get()) + assertTrue(task.hermesEnabled.get()) + assertTrue(task.devEnabled.get()) + assertEquals(listOf("extra", "arg"), task.extraPackagerArgs.get()) + assertEquals("./my-hermesc", task.hermesCommand.get()) + assertEquals(listOf("flag1", "flag2"), task.hermesFlags.get()) + } + + @Test + fun bundleTask_filesInput_areSetCorrectly() { + val entryFile = tempFolder.newFile("entry.js") + val jsBundleDir = tempFolder.newFolder("jsbundle") + val resourcesDir = tempFolder.newFolder("resources") + val jsIntermediateSourceMapsDir = tempFolder.newFolder("jsIntermediateSourceMaps") + val jsSourceMapsDir = tempFolder.newFolder("jsSourceMaps") + val bundleConfig = tempFolder.newFile("bundle.config") + + val task = + createTestTask { + it.entryFile.set(entryFile) + it.jsBundleDir.set(jsBundleDir) + it.resourcesDir.set(resourcesDir) + it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) + it.jsSourceMapsDir.set(jsSourceMapsDir) + it.bundleConfig.set(bundleConfig) + } + + assertEquals(entryFile, task.entryFile.get().asFile) + assertEquals(jsBundleDir, task.jsBundleDir.get().asFile) + assertEquals(resourcesDir, task.resourcesDir.get().asFile) + assertEquals(jsIntermediateSourceMapsDir, task.jsIntermediateSourceMapsDir.get().asFile) + assertEquals(jsSourceMapsDir, task.jsSourceMapsDir.get().asFile) + assertEquals(bundleConfig, task.bundleConfig.get().asFile) + } + + @Test + fun resolvePackagerSourceMapFile_withHermesEnabled_returnsCorrectFile() { + val jsIntermediateSourceMapsDir = tempFolder.newFolder("jsIntermediateSourceMaps") + val bundleAssetName = "myassetname" + val task = + createTestTask { + it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) + it.hermesEnabled.set(true) + it.bundleAssetName.set(bundleAssetName) + } + + assertEquals( + File(jsIntermediateSourceMapsDir, "myassetname.packager.map"), + task.resolvePackagerSourceMapFile(bundleAssetName)) + } + + @Test + fun resolvePackagerSourceMapFile_withHermesDisabled_returnsCorrectFile() { + val jsSourceMapsDir = tempFolder.newFolder("jsSourceMaps") + val bundleAssetName = "myassetname" + val task = + createTestTask { + it.jsSourceMapsDir.set(jsSourceMapsDir) + it.hermesEnabled.set(false) + } + + assertEquals( + File(jsSourceMapsDir, "myassetname.map"), + task.resolvePackagerSourceMapFile(bundleAssetName)) + } + + @Test + fun resolveOutputSourceMap_returnsCorrectFile() { + val jsSourceMapsDir = tempFolder.newFolder("jsSourceMaps") + val bundleAssetName = "myassetname" + val task = createTestTask { it.jsSourceMapsDir.set(jsSourceMapsDir) } + + assertEquals( + File(jsSourceMapsDir, "myassetname.map"), task.resolveOutputSourceMap(bundleAssetName)) + } + + @Test + fun resolveCompilerSourceMap_returnsCorrectFile() { + val jsIntermediateSourceMapsDir = tempFolder.newFolder("jsIntermediateSourceMaps") + val bundleAssetName = "myassetname" + val task = + createTestTask { + it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) + } + + assertEquals( + File(jsIntermediateSourceMapsDir, "myassetname.compiler.map"), + task.resolveCompilerSourceMap(bundleAssetName)) + } + + @Test + fun getBundleCommand_returnsCorrectCommand() { + val entryFile = tempFolder.newFile("index.js") + val bundleFile = tempFolder.newFile("bundle.js") + val sourceMapFile = tempFolder.newFile("bundle.js.map") + val resourcesDir = tempFolder.newFolder("res") + val bundleConfig = tempFolder.newFile("bundle.config") + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) + it.cliPath.set("../node_modules/react-native/cli.js") + it.bundleCommand.set("bundle") + it.devEnabled.set(true) + it.entryFile.set(entryFile) + it.resourcesDir.set(resourcesDir) + it.bundleConfig.set(bundleConfig) + it.minifyEnabled.set(true) + it.extraPackagerArgs.set(listOf("--read-global-cache")) + } + + val bundleCommand = task.getBundleCommand(bundleFile, sourceMapFile) + + assertEquals("node", bundleCommand[0]) + assertEquals("arg1", bundleCommand[1]) + assertEquals("arg2", bundleCommand[2]) + assertEquals("../node_modules/react-native/cli.js", bundleCommand[3]) + assertEquals("bundle", bundleCommand[4]) + assertEquals("--platform", bundleCommand[5]) + assertEquals("android", bundleCommand[6]) + assertEquals("--dev", bundleCommand[7]) + assertEquals("true", bundleCommand[8]) + assertEquals("--reset-cache", bundleCommand[9]) + assertEquals("--entry-file", bundleCommand[10]) + assertEquals(entryFile.absolutePath, bundleCommand[11]) + assertEquals("--bundle-output", bundleCommand[12]) + assertEquals(bundleFile.absolutePath, bundleCommand[13]) + assertEquals("--assets-dest", bundleCommand[14]) + assertEquals(resourcesDir.absolutePath, bundleCommand[15]) + assertEquals("--sourcemap-output", bundleCommand[16]) + assertEquals(sourceMapFile.absolutePath, bundleCommand[17]) + assertEquals("--config", bundleCommand[18]) + assertEquals(bundleConfig.absolutePath, bundleCommand[19]) + assertEquals("--minify", bundleCommand[20]) + assertEquals("true", bundleCommand[21]) + assertEquals("--read-global-cache", bundleCommand[22]) + assertEquals("--verbose", bundleCommand[23]) + assertEquals(24, bundleCommand.size) + } + + @Test + fun getBundleCommand_withoutConfig_returnsCommandWithoutConfig() { + val entryFile = tempFolder.newFile("index.js") + val bundleFile = tempFolder.newFile("bundle.js") + val sourceMapFile = tempFolder.newFile("bundle.js.map") + val resourcesDir = tempFolder.newFolder("res") + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) + it.cliPath.set("../node_modules/react-native/cli.js") + it.bundleCommand.set("bundle") + it.devEnabled.set(true) + it.entryFile.set(entryFile) + it.resourcesDir.set(resourcesDir) + it.minifyEnabled.set(true) + it.extraPackagerArgs.set(listOf("--read-global-cache")) + } + + val bundleCommand = task.getBundleCommand(bundleFile, sourceMapFile) + + assertTrue("--config" !in bundleCommand) + } + + @Test + fun getHermescCommand_returnsCorrectCommand() { + val customHermesc = "hermesc" + val bundleFile = tempFolder.newFile("bundle.js") + val outputFile = tempFolder.newFile("bundle.js.packager.map") + val task = + createTestTask { it.hermesFlags.set(listOf("my-custom-hermes-flag")) } + + val hermesCommand = task.getHermescCommand(customHermesc, bundleFile, outputFile) + + assertEquals(customHermesc, hermesCommand[0]) + assertEquals("-emit-binary", hermesCommand[1]) + assertEquals("-out", hermesCommand[2]) + assertEquals(outputFile.absolutePath, hermesCommand[3]) + assertEquals(bundleFile.absolutePath, hermesCommand[4]) + assertEquals("my-custom-hermes-flag", hermesCommand[5]) + assertEquals(6, hermesCommand.size) + } + + @Test + fun getComposeSourceMapsCommand_returnsCorrectCommand() { + val packagerMap = tempFolder.newFile("bundle.js.packager.map") + val compilerMap = tempFolder.newFile("bundle.js.compiler.map") + val outputMap = tempFolder.newFile("bundle.js.map") + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) + it.composeSourceMapsPath.set( + "../node_modules/react-native/scripts/compose-source-maps.js") + } + + val composeSourcemapCommand = + task.getComposeSourceMapsCommand(packagerMap, compilerMap, outputMap) + + assertEquals("node", composeSourcemapCommand[0]) + assertEquals("arg1", composeSourcemapCommand[1]) + assertEquals("arg2", composeSourcemapCommand[2]) + assertEquals( + "../node_modules/react-native/scripts/compose-source-maps.js", composeSourcemapCommand[3]) + assertEquals(packagerMap.absolutePath, composeSourcemapCommand[4]) + assertEquals(compilerMap.absolutePath, composeSourcemapCommand[5]) + assertEquals("-o", composeSourcemapCommand[6]) + assertEquals(outputMap.absolutePath, composeSourcemapCommand[7]) + assertEquals(8, composeSourcemapCommand.size) + } +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/TaskUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/TaskUtilsTest.kt index a1534d365998..cafbba6f7afd 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/TaskUtilsTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/TaskUtilsTest.kt @@ -24,6 +24,12 @@ class TaskUtilsTest { assertTrue(windowsAwareCommandLine().isEmpty()) } + @Test + fun windowsAwareCommandLine_withList_isEqualAsVararg() { + assertEquals( + windowsAwareCommandLine(listOf("a", "b", "c")), windowsAwareCommandLine("a", "b", "c")) + } + @Test @WithOs(OS.MAC) fun windowsAwareCommandLine_onMac_returnsTheList() { From e58acb82cd8ce5604295fc6497c4d465df5a0690 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 24 Oct 2022 06:08:47 -0700 Subject: [PATCH 2/5] RNGP - Top level property enableHermes -> hermesEnabled Summary: This is part of a series of tasks to make the React Native Gradle Plugin (RNGP) variant-aware. Here I'm renaming the top level property to enable hermes from `enableHermes` to `hermesEnabled`. We have a bunch of other properties which are called *Enabled. This one was following a different convention. I'm retaining the backward compatibility for users using `project.ext.react.enableHermes` so this is not going to be a breakign change. Changelog: [Internal] [Changed] - RNGP - Top level property enableHermes -> hermesEnabled Differential Revision: D40633109 fbshipit-source-id: 5b6904eebd675a9f6a857dda86237207a46e379c --- .../main/kotlin/com/facebook/react/utils/ProjectUtils.kt | 4 ++-- .../kotlin/com/facebook/react/utils/ProjectUtilsTest.kt | 6 +++--- packages/rn-tester/android/app/gradle.properties | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt index 17a0982ad83b..f70e14cbccab 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt @@ -19,8 +19,8 @@ internal object ProjectUtils { internal val Project.isHermesEnabled: Boolean get() = - if (project.hasProperty("enableHermes")) { - project.property("enableHermes").toString().lowercase().toBooleanStrictOrNull() ?: true + if (project.hasProperty("hermesEnabled")) { + project.property("hermesEnabled").toString().lowercase().toBooleanStrictOrNull() ?: true } else if (project.extensions.extraProperties.has("react")) { @Suppress("UNCHECKED_CAST") val reactMap = project.extensions.extraProperties.get("react") as? Map diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt index 495715416f76..c41cc65a27cc 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt @@ -50,21 +50,21 @@ class ProjectUtilsTest { @Test fun isNewArchEnabled_withDisabledViaProperty_returnsFalse() { val project = createProject() - project.extensions.extraProperties.set("enableHermes", "false") + project.extensions.extraProperties.set("hermesEnabled", "false") assertFalse(project.isHermesEnabled) } @Test fun isHermesEnabled_withEnabledViaProperty_returnsTrue() { val project = createProject() - project.extensions.extraProperties.set("enableHermes", "true") + project.extensions.extraProperties.set("hermesEnabled", "true") assertTrue(project.isHermesEnabled) } @Test fun isHermesEnabled_withInvalidViaProperty_returnsTrue() { val project = createProject() - project.extensions.extraProperties.set("enableHermes", "¯\\_(ツ)_/¯") + project.extensions.extraProperties.set("hermesEnabled", "¯\\_(ツ)_/¯") assertTrue(project.isHermesEnabled) } diff --git a/packages/rn-tester/android/app/gradle.properties b/packages/rn-tester/android/app/gradle.properties index 159049803a66..18b9871789b1 100644 --- a/packages/rn-tester/android/app/gradle.properties +++ b/packages/rn-tester/android/app/gradle.properties @@ -14,3 +14,5 @@ FLIPPER_VERSION=0.125.0 # RN-Tester is building with NewArch always enabled newArchEnabled=true +# RN-Tester is running with Hermes enabled and filtering variants with enableHermesOnlyInVariants +hermesEnabled=true From 0723237a87977569715c95b6d65b85ae237d78aa Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 24 Oct 2022 06:08:47 -0700 Subject: [PATCH 3/5] RNGP - Introduce enableCodegenInApps Summary: This is part of a series of tasks to make the React Native Gradle Plugin (RNGP) variant-aware. Historically we used to enable the codegen only for library modules. This is not working well for apps like RN Tester which don't have library modules but are running the codegen. Therefore I'm introducing the `enableCodegenInApps` that is allowing apps to turn off the codegen if needed. Changelog: [Internal] [Changed] - RNGP - Introduce enableCodegenInApps Differential Revision: D40633106 fbshipit-source-id: beee31eeae370465c88318fcde3ada6699c61ec2 --- .../com/facebook/react/ReactExtension.kt | 7 +++ .../kotlin/com/facebook/react/ReactPlugin.kt | 54 +++++++++---------- packages/rn-tester/android/app/build.gradle | 1 + 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt index 6c09759a0819..a2bfff06b0d6 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt @@ -221,4 +221,11 @@ abstract class ReactExtension @Inject constructor(project: Project) { */ val codegenJavaPackageName: Property = objects.property(String::class.java).convention("com.facebook.fbreact.specs") + + /** + * Toggles the Codegen for App modules. By default we enable the codegen only for library modules. + * If you want to enable codegen in your app, you will have to set this to true. Default: false + */ + val enableCodegenInApps: Property = + objects.property(Boolean::class.java).convention(false) } diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 034a6be090f8..5ca275ea0501 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -9,7 +9,6 @@ package com.facebook.react import com.android.build.api.variant.AndroidComponentsExtension import com.android.build.gradle.AppExtension -import com.android.build.gradle.LibraryExtension import com.android.build.gradle.internal.tasks.factory.dependsOn import com.facebook.react.tasks.BuildCodegenCLITask import com.facebook.react.tasks.GenerateCodegenArtifactsTask @@ -21,7 +20,6 @@ import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk import com.facebook.react.utils.findPackageJsonFile import java.io.File import kotlin.system.exitProcess -import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task @@ -31,8 +29,25 @@ class ReactPlugin : Plugin { override fun apply(project: Project) { checkJvmVersion(project) val extension = project.extensions.create("react", ReactExtension::class.java, project) - applyAppPlugin(project, extension) - applyCodegenPlugin(project, extension) + + // App Only Configuration + project.pluginManager.withPlugin("com.android.application") { + configureReactNativeNdk(project, extension) + configureBuildConfigFields(project) + configureDevPorts(project) + + project.afterEvaluate { + project.extensions.getByType(AppExtension::class.java).applicationVariants.all { + project.configureReactTasks(variant = it, config = extension) + } + } + configureCodegen(project, extension, isLibrary = false) + } + + // Library Only Configuration + project.pluginManager.withPlugin("com.android.library") { + configureCodegen(project, extension, isLibrary = true) + } } private fun checkJvmVersion(project: Project) { @@ -54,30 +69,12 @@ class ReactPlugin : Plugin { } } - private fun applyAppPlugin(project: Project, config: ReactExtension) { - project.pluginManager.withPlugin("com.android.application") { - configureReactNativeNdk(project, config) - configureBuildConfigFields(project) - configureDevPorts(project) - project.afterEvaluate { - val isAndroidLibrary = project.plugins.hasPlugin("com.android.library") - val variants = - if (isAndroidLibrary) { - project.extensions.getByType(LibraryExtension::class.java).libraryVariants - } else { - project.extensions.getByType(AppExtension::class.java).applicationVariants - } - variants.all { project.configureReactTasks(variant = it, config = config) } - } - } - } - /** * 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 */ @Suppress("UnstableApiUsage") - private fun applyCodegenPlugin(project: Project, extension: ReactExtension) { + private fun configureCodegen(project: Project, extension: ReactExtension, isLibrary: Boolean) { // First, we set up the output dir for the codegen. val generatedSrcDir = File(project.buildDir, "generated/source/codegen") @@ -86,6 +83,7 @@ class ReactPlugin : Plugin { it.codegenDir.set(extension.codegenDir) val bashWindowsHome = project.findProperty("REACT_WINDOWS_BASH") as String? it.bashWindowsHome.set(bashWindowsHome) + it.onlyIf { isLibrary || extension.enableCodegenInApps.get() } } // We create the task to produce schema from JS files. @@ -96,6 +94,7 @@ class ReactPlugin : Plugin { it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs) it.codegenDir.set(extension.codegenDir) it.generatedSrcDir.set(generatedSrcDir) + it.onlyIf { isLibrary || extension.enableCodegenInApps.get() } // We're reading the package.json at configuration time to properly feed // the `jsRootDir` @Input property of this task. Therefore, the @@ -123,6 +122,7 @@ class ReactPlugin : Plugin { it.packageJsonFile.set(findPackageJsonFile(project, extension)) it.codegenJavaPackageName.set(extension.codegenJavaPackageName) it.libraryName.set(extension.libraryName) + it.onlyIf { isLibrary || extension.enableCodegenInApps.get() } } // We update the android configuration to include the generated sources. @@ -133,12 +133,8 @@ class ReactPlugin : Plugin { ext.sourceSets.getByName("main").java.srcDir(File(generatedSrcDir, "java")) } - // `preBuild` is one of the base tasks automatically registered by Gradle. + // `preBuild` is one of the base tasks automatically registered by AGP. // This will invoke the codegen before compiling the entire project. - val androidPluginHandler = Action { _: Plugin<*> -> - project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask) - } - project.plugins.withId("com.android.application", androidPluginHandler) - project.plugins.withId("com.android.library", androidPluginHandler) + project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask) } } diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle index e80589e004d3..a7f7e9100fe0 100644 --- a/packages/rn-tester/android/app/build.gradle +++ b/packages/rn-tester/android/app/build.gradle @@ -88,6 +88,7 @@ react { // Codegen Configs reactNativeDir = rootDir codegenDir = new File(rootDir, "node_modules/react-native-codegen") + enableCodegenInApps = true } /** From 5a963f658bdd3296c9e39cce4a4304a2ce93c432 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 24 Oct 2022 06:08:47 -0700 Subject: [PATCH 4/5] RNGP - Automatically Configure Dependencies for ReactNative & Hermes Summary: This is part of a series of tasks to make the React Native Gradle Plugin (RNGP) variant-aware. I'm extending RNGP to do autoconfiguration of dependencies for React Native and Hermes Engine using the Utils classes which were introduced before. Changelog: [Internal] [Changed] - RNGP - Automatically Configure Dependencies for ReactNative & Hermes Differential Revision: https://internalfb.com/D40633487 fbshipit-source-id: 71fc4136974d5b078decdabef0a17090e4da782b --- .../src/main/kotlin/com/facebook/react/ReactPlugin.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 5ca275ea0501..c40f1491269c 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -15,6 +15,9 @@ import com.facebook.react.tasks.GenerateCodegenArtifactsTask import com.facebook.react.tasks.GenerateCodegenSchemaTask import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFields import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts +import com.facebook.react.utils.DependencyUtils.configureDependencies +import com.facebook.react.utils.DependencyUtils.configureRepositories +import com.facebook.react.utils.DependencyUtils.readVersionString import com.facebook.react.utils.JsonUtils import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk import com.facebook.react.utils.findPackageJsonFile @@ -32,6 +35,14 @@ class ReactPlugin : Plugin { // App Only Configuration project.pluginManager.withPlugin("com.android.application") { + project.afterEvaluate { + val reactNativeDir = extension.reactNativeDir.get().asFile + val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties") + val versionString = readVersionString(propertiesFile) + configureDependencies(project, versionString) + configureRepositories(project, reactNativeDir) + } + configureReactNativeNdk(project, extension) configureBuildConfigFields(project) configureDevPorts(project) From f23dbb516b6df276fd539260216f745a3531a8f0 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 24 Oct 2022 06:09:06 -0700 Subject: [PATCH 5/5] RNGP - Add Variant Support Summary: This is part of a series of tasks to make the React Native Gradle Plugin (RNGP) variant-aware. Here I'm add Variant support to RNGP via the Variant API from AGP 7.x A short summary of changes: - I've pushed a number of breaking changes to ReactExtension (we should document them in the release notes). Specifically I've removed properties which I believe were unnecessary and confusing - I've removed all the extra logic to do the .so cleanups and use the new tasks that use the Artifacts API - I've introduced only a `debuggableVariants` to make all the decisions for the bundling and minification which should be sufficient for users - I've removed all the funcional interfaces are replaced them with lists as they're easy to handle and understand for users. Changelog: [Android] [Changed] - Added Flavor Support to React Native Gradle Plugin (RNGP) Reviewed By: cipolleschi Differential Revision: D40335028 fbshipit-source-id: 483930f71521d7c1ecbc8ab00e05e84bbc11cae8 --- .../com/facebook/react/ReactExtension.kt | 105 ++----- .../kotlin/com/facebook/react/ReactPlugin.kt | 7 +- .../com/facebook/react/TaskConfiguration.kt | 293 +++++------------- .../react/tasks/BundleJsAndAssetsTask.kt | 73 ----- .../facebook/react/tasks/HermesBinaryTask.kt | 77 ----- packages/rn-tester/android/app/build.gradle | 7 +- 6 files changed, 96 insertions(+), 466 deletions(-) delete mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleJsAndAssetsTask.kt delete mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/HermesBinaryTask.kt diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt index a2bfff06b0d6..4e7573876540 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt @@ -7,15 +7,12 @@ package com.facebook.react -import com.android.build.gradle.api.BaseVariant import com.facebook.react.utils.projectPathToLibraryName -import java.io.File import javax.inject.Inject import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property abstract class ReactExtension @Inject constructor(project: Project) { @@ -54,10 +51,10 @@ abstract class ReactExtension @Inject constructor(project: Project) { val bundleCommand: Property = objects.property(String::class.java).convention("bundle") /** - * Custom configuration for the [bundleCommand]. If provided it will be passed over with a + * Custom configuration file for the [bundleCommand]. If provided, it will be passed over with a * `--config` flag to the bundle command. */ - val bundleConfig: Property = objects.property(String::class.java) + val bundleConfig: RegularFileProperty = objects.fileProperty() /** * The Bundle Asset name. This name will be used also for deriving other bundle outputs such as @@ -69,69 +66,24 @@ abstract class ReactExtension @Inject constructor(project: Project) { objects.property(String::class.java).convention("index.android.bundle") /** - * Variant Name to File destination map that allows to specify where is the resource dir for a - * specific variant. If a value is supplied, the plugin will copy the bundled resource for that - * variant from `generated/res/react/` into the custom specified location. Default: {} + * Toggles the .so Cleanup step. If enabled, we will clean up all the unnecessary files before the + * bundle task. If disabled, the developers will have to manually cleanup the files. Default: true */ - val resourcesDir: MapProperty = - objects.mapProperty(String::class.java, File::class.java).convention(emptyMap()) - - /** - * Variant Name to File destination map that allows to specify where is the asset dir for a - * specific variant. If a value is supplied, the plugin will copy the bundled JS for that variant - * from `generated/assets/react/` into the custom specified location. Default: {} - */ - val jsBundleDir: MapProperty = - objects.mapProperty(String::class.java, File::class.java).convention(emptyMap()) - - /** ANT-style excludes for the bundle command. Default: ["android / **", "ios / **"] */ - val inputExcludes: ListProperty = - objects.listProperty(String::class.java).convention(listOf("android/**", "ios/**")) - - /** - * Toggles the VM Cleanup step. If enabled, before the bundle task we will clean up all the - * unnecessary files. If disabled, the developers will have to manually cleanup the files. - * Default: true - */ - val enableVmCleanup: Property = objects.property(Boolean::class.java).convention(true) + val enableSoCleanup: Property = objects.property(Boolean::class.java).convention(true) /** Extra args that will be passed to the [bundleCommand] Default: [] */ val extraPackagerArgs: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) /** - * Allows to disable dev mode for certain variants. That's useful if you have a production variant - * (say `canary`) where you don't want dev mode to be enabled. Default: [] - */ - val devDisabledInVariants: ListProperty = - objects.listProperty(String::class.java).convention(emptyList()) - - /** - * Functional interface to disable dev mode only on specific [BaseVariant] Default: will check - * [devDisabledInVariants] or return True for Release variants and False for Debug variants. - */ - var disableDevForVariant: (BaseVariant) -> Boolean = { variant -> - variant.name in devDisabledInVariants.get() || variant.isRelease - } - - /** - * Variant Name to Boolean map that allows to toggle the bundle command for a specific variant. - * Default: {} - */ - // todo maybe lambda as for hermes? - val bundleIn: MapProperty = - objects.mapProperty(String::class.java, Boolean::class.java).convention(emptyMap()) - - /** - * Functional interface to toggle the bundle command only on specific [BaseVariant] Default: will - * check [bundleIn] or return True for Release variants and False for Debug variants. + * Allows to specify the debuggable variants (by default just 'debug'). Variants in this list + * will: + * - Not be bundled (the bundle file will not be created and won't be copied over). + * - Have the Hermes Debug flags set. That's useful if you have another variant (say `canary`) + * where you want dev mode to be enabled. Default: ['debug'] */ - var bundleForVariant: (BaseVariant) -> Boolean = { variant -> - if (bundleIn.getting(variant.name).isPresent) bundleIn.getting(variant.name).get() - else if (bundleIn.getting(variant.buildType.name).isPresent) - bundleIn.getting(variant.buildType.name).get() - else variant.isRelease - } + val debuggableVariants: ListProperty = + objects.listProperty(String::class.java).convention(listOf("debug")) /** Hermes Config */ @@ -141,35 +93,18 @@ abstract class ReactExtension @Inject constructor(project: Project) { */ val hermesCommand: Property = objects.property(String::class.java).convention("") - /** Toggle Hermes for the whole build. Default: false */ - val enableHermes: Property = objects.property(Boolean::class.java).convention(false) - /** - * Functional interface to selectively enabled Hermes only on specific [BaseVariant] Default: will - * return [enableHermes] for all the variants. - */ - var enableHermesForVariant: (BaseVariant) -> Boolean = { enableHermes.get() } - - /** - * Functional interface specify flags for Hermes on specific [BaseVariant] Default: will return - * [hermesFlagsRelease] for Release variants and [hermesFlagsDebug] for Debug variants. - */ - var hermesFlagsForVariant: (BaseVariant) -> List = { variant -> - if (variant.isRelease) hermesFlagsRelease.get() else hermesFlagsDebug.get() - } - - /** - * Functional interface to delete debug files only on specific [BaseVariant] Default: will return - * True for Release variants and False for Debug variants. + * Whether to enable Hermes only on certain variants. If specified as a non-empty list, hermesc + * and the .so cleanup for Hermes will be executed only for variants in this list. An empty list + * assumes you're either using Hermes for all variants or not (see [enableHermes]). + * + * Default: [] */ - var deleteDebugFilesForVariant: (BaseVariant) -> Boolean = { variant -> variant.isRelease } - - /** Flags to pass to Hermes for Debug variants. Default: [] */ - val hermesFlagsDebug: ListProperty = + val enableHermesOnlyInVariants: ListProperty = objects.listProperty(String::class.java).convention(emptyList()) - /** Flags to pass to Hermes for Release variants. Default: ["-O", "-output-source-map"] */ - val hermesFlagsRelease: ListProperty = + /** Flags to pass to Hermesc. Default: ["-O", "-output-source-map"] */ + val hermesFlags: ListProperty = objects.listProperty(String::class.java).convention(listOf("-O", "-output-source-map")) /** diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index c40f1491269c..24790daa83c5 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -8,7 +8,6 @@ package com.facebook.react import com.android.build.api.variant.AndroidComponentsExtension -import com.android.build.gradle.AppExtension import com.android.build.gradle.internal.tasks.factory.dependsOn import com.facebook.react.tasks.BuildCodegenCLITask import com.facebook.react.tasks.GenerateCodegenArtifactsTask @@ -47,10 +46,8 @@ class ReactPlugin : Plugin { configureBuildConfigFields(project) configureDevPorts(project) - project.afterEvaluate { - project.extensions.getByType(AppExtension::class.java).applicationVariants.all { - project.configureReactTasks(variant = it, config = extension) - } + project.extensions.getByType(AndroidComponentsExtension::class.java).onVariants { variant -> + project.configureReactTasks(variant = variant, config = extension) } configureCodegen(project, extension, isLibrary = false) } 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 bba9e91f959c..4ec27e9f559d 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 @@ -7,246 +7,95 @@ 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.android.build.gradle.internal.tasks.factory.dependsOn -import com.facebook.react.tasks.BundleJsAndAssetsTask -import com.facebook.react.tasks.HermesBinaryTask +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.Variant +import com.facebook.react.tasks.BundleHermesCTask +import com.facebook.react.tasks.NativeLibraryAabCleanupTask +import com.facebook.react.tasks.NativeLibraryApkCleanupTask +import com.facebook.react.utils.ProjectUtils.isHermesEnabled import com.facebook.react.utils.detectedCliPath import com.facebook.react.utils.detectedEntryFile import java.io.File import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.tasks.Copy -import org.gradle.api.tasks.TaskProvider -private const val REACT_GROUP = "react" - -@Suppress("SpreadOperator") -internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactExtension) { +@Suppress("SpreadOperator", "UnstableApiUsage") +internal fun Project.configureReactTasks(variant: Variant, config: ReactExtension) { val targetName = variant.name.replaceFirstChar { it.uppercase() } - val targetPath = variant.dirName + val targetPath = variant.name // React js bundle directories - val jsBundleDir = File(buildDir, "generated/assets/react/$targetPath") val resourcesDir = File(buildDir, "generated/res/react/$targetPath") - - val bundleAssetName = config.bundleAssetName.get() - val jsBundleFile = File(jsBundleDir, bundleAssetName) + // Bundle: generated/assets/react/path/index.android.bundle + val jsBundleDir = File(buildDir, "generated/assets/react/$targetPath") + // Sourcemap: generated/sourcemaps/react/path/index.android.bundle.map val jsSourceMapsDir = File(buildDir, "generated/sourcemaps/react/$targetPath") + // Intermediate packager: intermediates/sourcemaps/react/path/index.android.bundle.packager.map + // Intermediate compiler: intermediates/sourcemaps/react/path/index.android.bundle.compiler.map val jsIntermediateSourceMapsDir = File(buildDir, "intermediates/sourcemaps/react/$targetPath") - val jsPackagerSourceMapFile = File(jsIntermediateSourceMapsDir, "${bundleAssetName}.packager.map") - val jsCompilerSourceMapFile = File(jsIntermediateSourceMapsDir, "${bundleAssetName}.compiler.map") - val jsOutputSourceMapFile = File(jsSourceMapsDir, "${bundleAssetName}.map") // Additional node and packager commandline arguments - val nodeExecutableAndArgs = config.nodeExecutableAndArgs.get() val cliPath = detectedCliPath(project.projectDir, config) - val execCommand = nodeExecutableAndArgs + cliPath - val enableHermes = config.enableHermesForVariant(variant) - val cleanup = config.deleteDebugFilesForVariant(variant) - val bundleEnabled = config.bundleForVariant(variant) - - val bundleTask = - tasks.register("createBundle${targetName}JsAndAssets", BundleJsAndAssetsTask::class.java) { - it.group = REACT_GROUP - it.description = "create JS bundle and assets for $targetName." - - it.reactRoot = config.root.get().asFile - it.sources = - fileTree(config.root) { fileTree -> fileTree.setExcludes(config.inputExcludes.get()) } - it.execCommand = execCommand - it.bundleCommand = config.bundleCommand.get() - it.devEnabled = !config.disableDevForVariant(variant) - it.entryFile = detectedEntryFile(config) - - val extraArgs = mutableListOf() - - if (config.bundleConfig.isPresent) { - extraArgs.add("--config") - extraArgs.add(config.bundleConfig.get()) - } - - // Hermes doesn't require JS minification. - if (enableHermes && !it.devEnabled) { - extraArgs.add("--minify") - extraArgs.add("false") - } - - extraArgs.addAll(config.extraPackagerArgs.get()) - - it.extraArgs = extraArgs - - it.jsBundleDir = jsBundleDir - it.jsBundleFile = jsBundleFile - it.resourcesDir = resourcesDir - it.jsIntermediateSourceMapsDir = jsIntermediateSourceMapsDir - it.jsSourceMapsDir = jsSourceMapsDir - it.jsSourceMapsFile = if (enableHermes) jsPackagerSourceMapFile else jsOutputSourceMapFile - - it.enabled = bundleEnabled - } - - val hermesTask = - tasks.register("emit${targetName}HermesResources", HermesBinaryTask::class.java) { - it.group = REACT_GROUP - it.description = "bundle hermes resources for $targetName" - - it.root = config.root.get().asFile - it.hermesCommand = config.hermesCommand.get() - it.hermesFlags = config.hermesFlagsForVariant(variant) - it.jsBundleFile = jsBundleFile - it.composeSourceMapsCommand = nodeExecutableAndArgs + config.composeSourceMapsPath.get() - it.jsPackagerSourceMapFile = jsPackagerSourceMapFile - it.jsCompilerSourceMapFile = jsCompilerSourceMapFile - it.jsOutputSourceMapFile = jsOutputSourceMapFile - - it.dependsOn(bundleTask) - - it.enabled = bundleEnabled && enableHermes - } - - val aggregatedBundleTask = - tasks.register("bundle${targetName}JsAndAssets") { - it.group = REACT_GROUP - it.description = "bundle JS and resources for $targetName" - - it.dependsOn(bundleTask, hermesTask) - - // this was exposed before, do we still need it? - it.extensions.extraProperties["generatedResFolders"] = files(resourcesDir).builtBy(it) - it.extensions.extraProperties["generatedAssetsFolders"] = files(jsBundleDir).builtBy(it) - } - - val generatedResFolders = files(resourcesDir).builtBy(aggregatedBundleTask) - - // Android configuration - variant.registerGeneratedResFolders(generatedResFolders) - - val packageTask: TaskProvider? = - when (variant) { - is ApplicationVariant -> variant.packageApplicationProvider - is LibraryVariant -> variant.packageLibraryProvider - else -> tasks.named("package$targetName") + val enableHermesInProject = project.isHermesEnabled + val enableHermesInThisVariant = + if (config.enableHermesOnlyInVariants.get().isNotEmpty()) { + config.enableHermesOnlyInVariants.get().contains(variant.name) && enableHermesInProject + } else { + enableHermesInProject } - val stripDebugSymbolsTask: TaskProvider? = tasks.named("strip${targetName}DebugSymbols") - val mergeNativeLibsTask: TaskProvider? = tasks.named("merge${targetName}NativeLibs") - - val mergeResourcesTask = variant.mergeResourcesProvider - val mergeAssetsTask = variant.mergeAssetsProvider - val preBundleTask = tasks.named("build${targetName}PreBundle") - - val resourcesDirConfigValue = config.resourcesDir.getting(variant.name) - if (resourcesDirConfigValue.isPresent) { - val currentCopyResTask = - tasks.register("copy${targetName}BundledResources", Copy::class.java) { - it.group = "react" - it.description = "copy bundled resources into custom location for $targetName." - - it.from(resourcesDir) - it.into(file(resourcesDirConfigValue.get())) - - it.dependsOn(bundleTask) - - it.enabled = bundleEnabled + val isDebuggableVariant = + config.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) } + + if (!isDebuggableVariant) { + val bundleTask = + tasks.register("createBundle${targetName}JsAndAssets", BundleHermesCTask::class.java) { + it.root.set(config.root) + it.nodeExecutableAndArgs.set(config.nodeExecutableAndArgs) + it.cliPath.set(cliPath) + it.bundleCommand.set(config.bundleCommand) + it.entryFile.set(detectedEntryFile(config)) + it.extraPackagerArgs.set(config.extraPackagerArgs) + it.bundleConfig.set(config.bundleConfig) + it.bundleAssetName.set(config.bundleAssetName) + it.jsBundleDir.set(jsBundleDir) + it.resourcesDir.set(resourcesDir) + it.hermesEnabled.set(enableHermesInThisVariant) + it.minifyEnabled.set(!enableHermesInThisVariant) + it.devEnabled.set(false) + it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) + it.jsSourceMapsDir.set(jsSourceMapsDir) + it.hermesCommand.set(config.hermesCommand) + it.hermesFlags.set(config.hermesFlags) + it.composeSourceMapsPath.set(config.composeSourceMapsPath) } - - packageTask?.dependsOn(currentCopyResTask) - preBundleTask.dependsOn(currentCopyResTask) + variant.sources.res?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::resourcesDir) + variant.sources.assets?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::jsBundleDir) } - packageTask?.configure { - if (config.enableVmCleanup.get()) { - val libDir = "$buildDir/intermediates/transforms/" - val targetVariant = ".*/transforms/[^/]*/${variant.name}/.*".toRegex() - it.doFirst { cleanupVMFiles(libDir, targetVariant, enableHermes, cleanup) } - } + if (config.enableSoCleanup.get()) { + val nativeLibraryApkCleanupTask = + project.tasks.register( + "nativeLibrary${targetName}ApkCleanup", NativeLibraryApkCleanupTask::class.java) { + it.debuggableVariant.set(isDebuggableVariant) + it.enableHermes.set(enableHermesInThisVariant) + } + val nativeLibraryBundleCleanupTask = + project.tasks.register( + "nativeLibrary${targetName}BundleCleanup", NativeLibraryAabCleanupTask::class.java) { + it.debuggableVariant.set(isDebuggableVariant) + it.enableHermes.set(enableHermesInThisVariant) + } + + variant.artifacts + .use(nativeLibraryApkCleanupTask) + .wiredWithDirectories( + NativeLibraryApkCleanupTask::inputApkDirectory, + NativeLibraryApkCleanupTask::outputApkDirectory) + .toTransform(SingleArtifact.APK) + variant.artifacts + .use(nativeLibraryBundleCleanupTask) + .wiredWithFiles( + NativeLibraryAabCleanupTask::inputBundle, NativeLibraryAabCleanupTask::outputBundle) + .toTransform(SingleArtifact.BUNDLE) } - - stripDebugSymbolsTask?.configure { - if (config.enableVmCleanup.get()) { - val libDir = "$buildDir/intermediates/stripped_native_libs/${variant.name}/out/lib/" - val targetVariant = ".*/stripped_native_libs/${variant.name}/out/lib/.*".toRegex() - it.doLast { cleanupVMFiles(libDir, targetVariant, enableHermes, cleanup) } - } - } - - mergeNativeLibsTask?.configure { - if (config.enableVmCleanup.get()) { - val libDir = "$buildDir/intermediates/merged_native_libs/${variant.name}/out/lib/" - val targetVariant = ".*/merged_native_libs/${variant.name}/out/lib/.*".toRegex() - it.doLast { cleanupVMFiles(libDir, targetVariant, enableHermes, cleanup) } - } - } - - val currentAssetsCopyTask = - tasks.register("copy${targetName}BundledJs", Copy::class.java) { - it.group = "react" - it.description = "copy bundled JS into $targetName." - - it.from(jsBundleDir) - - val jsBundleDirConfigValue = config.jsBundleDir.getting(targetName) - if (jsBundleDirConfigValue.isPresent) { - it.into(jsBundleDirConfigValue.get()) - } else { - it.into(mergeAssetsTask.map { mergeFoldersTask -> mergeFoldersTask.outputDir.get() }) - // Workaround for Android Gradle Plugin 7.3 asset directory - it.into("$buildDir/intermediates/assets/${variant.name}") - } - - it.dependsOn(mergeAssetsTask) - - it.enabled = bundleEnabled - } - - // mergeResources task runs before the bundle file is copied to the intermediate asset directory - // from Android plugin 4.1+. - // This ensures to copy the bundle file before mergeResources task starts - mergeResourcesTask.dependsOn(currentAssetsCopyTask) - packageTask?.dependsOn(currentAssetsCopyTask) - preBundleTask.dependsOn(currentAssetsCopyTask) } - -private fun Project.cleanupVMFiles( - libDir: String, - targetVariant: Regex, - enableHermes: Boolean, - cleanup: Boolean -) { - // Delete the VM related libraries that this build doesn't need. - // The application can manage this manually by setting 'enableVmCleanup: false' - // - // This should really be done by packaging all Hermes related libs into - // two separate HermesDebug and HermesRelease AARs, but until then we'll - // kludge it by deleting the .so files out of the /transforms/ directory. - fileTree(libDir) { - if (enableHermes) { - // For Hermes, delete all the libjsc* files - it.include("**/libjsc*.so") - - if (cleanup) { - // Reduce size by deleting the debugger/inspector - it.include("**/libhermes-executor-debug.so") - } else { - // Release libs take precedence and must be removed - // to allow debugging - it.include("**/libhermes-executor-release.so") - } - } else { - // For JSC, delete all the libhermes* files - it.include("**/libhermes*.so") - } - } - .visit { visit -> - val path = visit.file.absolutePath.replace(File.separatorChar, '/') - if (path.matches(targetVariant) && visit.file.isFile) { - visit.file.delete() - } - } -} - -internal val BaseVariant.isRelease: Boolean - get() = name.lowercase().contains("release") 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 deleted file mode 100644 index 2e1a0b8be819..000000000000 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleJsAndAssetsTask.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.tasks - -import com.facebook.react.utils.recreateDir -import com.facebook.react.utils.windowsAwareCommandLine -import java.io.File -import org.gradle.api.file.FileTree -import org.gradle.api.tasks.* - -abstract class BundleJsAndAssetsTask : Exec() { - - @get:Internal 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:InputFile 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 - - override fun exec() { - cleanOutputDirectories() - configureBundleCommand() - super.exec() - } - - private fun cleanOutputDirectories() { - jsBundleDir.recreateDir() - resourcesDir.recreateDir() - jsIntermediateSourceMapsDir.recreateDir() - jsSourceMapsDir.recreateDir() - } - - private fun configureBundleCommand() { - workingDir(reactRoot) - - @Suppress("SpreadOperator") - commandLine( - windowsAwareCommandLine( - *execCommand.toTypedArray(), - bundleCommand, - "--platform", - "android", - "--dev", - devEnabled, - "--reset-cache", - "--entry-file", - entryFile, - "--bundle-output", - jsBundleFile, - "--assets-dest", - resourcesDir, - "--sourcemap-output", - jsSourceMapsFile, - *extraArgs.toTypedArray())) - } -} 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 deleted file mode 100644 index 3f9e63597384..000000000000 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/HermesBinaryTask.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.tasks - -import com.facebook.react.utils.detectOSAwareHermesCommand -import com.facebook.react.utils.moveTo -import com.facebook.react.utils.windowsAwareCommandLine -import java.io.File -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction - -open class HermesBinaryTask : DefaultTask() { - - @get:Internal internal lateinit var root: File - - @get:Input internal lateinit var hermesCommand: String - @get:Input internal var hermesFlags: List = emptyList() - @get:InputFile internal lateinit var jsBundleFile: File - - @get:Input internal lateinit var composeSourceMapsCommand: List - @get:InputFile internal lateinit var jsPackagerSourceMapFile: File - - @get:OutputFile internal lateinit var jsCompilerSourceMapFile: File - @get:OutputFile internal lateinit var jsOutputSourceMapFile: File - - @TaskAction - fun run() { - val detectedHermesCommand = detectOSAwareHermesCommand(root, hermesCommand) - val bytecodeTempFile = File("$jsBundleFile.hbc") - emitHermesBinary(hermesCommand = detectedHermesCommand, outputFile = bytecodeTempFile) - bytecodeTempFile.moveTo(jsBundleFile) - - if (hermesFlags.contains("-output-source-map")) { - val hermesTempSourceMapFile = File("$bytecodeTempFile.map") - hermesTempSourceMapFile.moveTo(jsCompilerSourceMapFile) - composeSourceMaps() - } - } - - private fun emitHermesBinary(hermesCommand: String, outputFile: File) { - project.exec { - @Suppress("SpreadOperator") - it.commandLine( - windowsAwareCommandLine( - hermesCommand, - "-emit-binary", - "-out", - outputFile, - jsBundleFile, - *hermesFlags.toTypedArray())) - } - } - - private fun composeSourceMaps() { - project.exec { - it.workingDir(root) - - @Suppress("SpreadOperator") - it.commandLine( - windowsAwareCommandLine( - *composeSourceMapsCommand.toTypedArray(), - jsPackagerSourceMapFile, - jsCompilerSourceMapFile, - "-o", - jsOutputSourceMapFile)) - } - } -} diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle index a7f7e9100fe0..e0fc0dcf655e 100644 --- a/packages/rn-tester/android/app/build.gradle +++ b/packages/rn-tester/android/app/build.gradle @@ -80,10 +80,10 @@ react { bundleAssetName = "RNTesterApp.android.bundle" entryFile = file("../../js/RNTesterApp.android.js") root = file("../../") - inputExcludes = ["android/**", "./**", ".gradle/**"] composeSourceMapsPath = "$rootDir/scripts/compose-source-maps.js" hermesCommand = "$rootDir/ReactAndroid/hermes-engine/build/hermes/bin/hermesc" - enableHermesForVariant { def v -> v.name.contains("hermes") } + debuggableVariants = ["hermesDebug", "jscDebug"] + enableHermesOnlyInVariants = ["hermesDebug", "hermesRelease"] // Codegen Configs reactNativeDir = rootDir @@ -228,6 +228,5 @@ android { afterEvaluate { // As we're consuming Hermes from source, we want to make sure // `hermesc` is built before we actually invoke the `emit*HermesResource` task - emitHermesDebugHermesResources.dependsOn(":ReactAndroid:hermes-engine:buildHermes") - emitHermesReleaseHermesResources.dependsOn(":ReactAndroid:hermes-engine:buildHermes") + createBundleHermesReleaseJsAndAssets.dependsOn(":ReactAndroid:hermes-engine:buildHermes") }