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 08350da88d9..5dfb3737ee9 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 @@ -28,6 +28,14 @@ abstract class ReactExtension @Inject constructor(project: Project) { val root: DirectoryProperty = objects.directoryProperty().convention(project.rootProject.layout.projectDirectory.dir("../")) + /** + * The path to the react-native NPM package folder. + * + * Default: ${rootProject.dir}/../node_modules/react-native-codegen + */ + val reactNativeDir: DirectoryProperty = + objects.directoryProperty().convention(root.dir("node_modules/react-native")) + /** * The path to the JS entry file. If not specified, the plugin will try to resolve it using a list * of known locations (e.g. `index.android.js`, `index.js`, etc.). @@ -35,10 +43,11 @@ abstract class ReactExtension @Inject constructor(project: Project) { val entryFile: RegularFileProperty = objects.fileProperty() /** - * The path to the React Native CLI. If not specified, the plugin will try to resolve it looking - * for `react-native` CLI inside `node_modules` in [root]. + * The reference to the React Native CLI. If not specified, the plugin will try to resolve it + * looking for `react-native` CLI inside `node_modules` in [root]. */ - val cliPath: Property = objects.property(String::class.java) + val cliFile: RegularFileProperty = + objects.fileProperty().convention(reactNativeDir.file("cli.js")) /** * The path to the Node executable and extra args. By default it assumes that you have `node` @@ -107,15 +116,6 @@ abstract class ReactExtension @Inject constructor(project: Project) { val hermesFlags: ListProperty = objects.listProperty(String::class.java).convention(listOf("-O", "-output-source-map")) - /** - * The path to the Compose Source Map script. Default: - * "node_modules/react-native/scripts/compose-source-maps.js" - */ - val composeSourceMapsPath: Property = - objects - .property(String::class.java) - .convention("node_modules/react-native/scripts/compose-source-maps.js") - /** Codegen Config */ /** @@ -126,14 +126,6 @@ abstract class ReactExtension @Inject constructor(project: Project) { val codegenDir: DirectoryProperty = objects.directoryProperty().convention(root.dir("node_modules/react-native-codegen")) - /** - * The path to the react-native NPM package folder. - * - * Default: ${rootProject.dir}/../node_modules/react-native-codegen - */ - val reactNativeDir: DirectoryProperty = - objects.directoryProperty().convention(root.dir("node_modules/react-native")) - /** * The root directory for all JS files for the app. * 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 757c80e99e1..bb3699d58df 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 @@ -12,7 +12,7 @@ import com.facebook.react.tasks.BundleHermesCTask import com.facebook.react.utils.NdkConfiguratorUtils.configureJsEnginePackagingOptions import com.facebook.react.utils.NdkConfiguratorUtils.configureNewArchPackagingOptions import com.facebook.react.utils.ProjectUtils.isHermesEnabled -import com.facebook.react.utils.detectedCliPath +import com.facebook.react.utils.detectedCliFile import com.facebook.react.utils.detectedEntryFile import java.io.File import org.gradle.api.Project @@ -32,8 +32,8 @@ internal fun Project.configureReactTasks(variant: Variant, config: ReactExtensio // Intermediate compiler: intermediates/sourcemaps/react/path/index.android.bundle.compiler.map val jsIntermediateSourceMapsDir = File(buildDir, "intermediates/sourcemaps/react/$targetPath") - // Additional node and packager commandline arguments - val cliPath = detectedCliPath(project.projectDir, config) + // The location of the cli.js file for React Native + val cliFile = detectedCliFile(config) val isHermesEnabledInProject = project.isHermesEnabled val isHermesEnabledInThisVariant = @@ -54,7 +54,7 @@ internal fun Project.configureReactTasks(variant: Variant, config: ReactExtensio tasks.register("createBundle${targetName}JsAndAssets", BundleHermesCTask::class.java) { it.root.set(config.root) it.nodeExecutableAndArgs.set(config.nodeExecutableAndArgs) - it.cliPath.set(cliPath) + it.cliFile.set(cliFile) it.bundleCommand.set(config.bundleCommand) it.entryFile.set(detectedEntryFile(config)) it.extraPackagerArgs.set(config.extraPackagerArgs) @@ -69,7 +69,7 @@ internal fun Project.configureReactTasks(variant: Variant, config: ReactExtensio it.jsSourceMapsDir.set(jsSourceMapsDir) it.hermesCommand.set(config.hermesCommand) it.hermesFlags.set(config.hermesFlags) - it.composeSourceMapsPath.set(config.composeSourceMapsPath) + it.reactNativeDir.set(config.reactNativeDir) } variant.sources.res?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::resourcesDir) variant.sources.assets?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::jsBundleDir) 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 index 66490377b02..2801d3aea63 100644 --- 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 @@ -42,9 +42,9 @@ abstract class BundleHermesCTask : DefaultTask() { @get:Input abstract val nodeExecutableAndArgs: ListProperty - @get:Input abstract val cliPath: Property + @get:InputFile abstract val cliFile: RegularFileProperty - @get:Input abstract val composeSourceMapsPath: Property + @get:Internal abstract val reactNativeDir: DirectoryProperty @get:Input abstract val bundleCommand: Property @@ -101,8 +101,12 @@ abstract class BundleHermesCTask : DefaultTask() { if (hermesFlags.get().contains("-output-source-map")) { val hermesTempSourceMapFile = File("$bytecodeFile.map") hermesTempSourceMapFile.moveTo(compilerSourceMap) + + val reactNativeDir = reactNativeDir.get().asFile + val composeScriptFile = File(reactNativeDir, "scripts/compose-source-maps.js") val composeSourceMapsCommand = - getComposeSourceMapsCommand(packagerSourceMap, compilerSourceMap, outputSourceMap) + getComposeSourceMapsCommand( + composeScriptFile, packagerSourceMap, compilerSourceMap, outputSourceMap) runCommand(composeSourceMapsCommand) } } @@ -132,7 +136,7 @@ abstract class BundleHermesCTask : DefaultTask() { windowsAwareCommandLine( buildList { addAll(nodeExecutableAndArgs.get()) - add(cliPath.get()) + add(cliFile.get().asFile.absolutePath) add(bundleCommand.get()) add("--platform") add("android") @@ -171,13 +175,14 @@ abstract class BundleHermesCTask : DefaultTask() { *hermesFlags.get().toTypedArray()) internal fun getComposeSourceMapsCommand( + composeScript: File, packagerSourceMap: File, compilerSourceMap: File, outputSourceMap: File ): List = windowsAwareCommandLine( *nodeExecutableAndArgs.get().toTypedArray(), - composeSourceMapsPath.get(), + composeScript.absolutePath, packagerSourceMap.toString(), compilerSourceMap.toString(), "-o", diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt index 41130173eda..74fe49ca17a 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt @@ -28,20 +28,16 @@ internal fun detectedEntryFile(config: ReactExtension): File = entryFile = config.entryFile.orNull?.asFile, reactRoot = config.root.get().asFile) /** - * Computes the CLI location for React Native. The Algo follows this order: - * 1. The path provided by the `cliPath` config in the `reactApp` Gradle extension + * Computes the CLI file for React Native. The Algo follows this order: + * 1. The path provided by the `cliFile` config in the `react {}` Gradle extension * 2. The output of `node --print "require.resolve('react-native/cli');"` if not failing. * 3. The `node_modules/react-native/cli.js` file if exists * 4. Fails otherwise */ -internal fun detectedCliPath( - projectDir: File, - config: ReactExtension, -): String = - detectCliPath( - projectDir = projectDir, - reactRoot = config.root.get().asFile, - preconfiguredCliPath = config.cliPath.orNull) +internal fun detectedCliFile(config: ReactExtension): File = + detectCliFile( + reactNativeRoot = config.root.get().asFile, + preconfiguredCliFile = config.cliFile.asFile.orNull) /** * Computes the `hermesc` command location. The Algo follows this order: @@ -64,24 +60,11 @@ private fun detectEntryFile(entryFile: File?, reactRoot: File): File = else -> File(reactRoot, "index.js") } -private fun detectCliPath( - projectDir: File, - reactRoot: File, - preconfiguredCliPath: String? -): String { +private fun detectCliFile(reactNativeRoot: File, preconfiguredCliFile: File?): File { // 1. preconfigured path - if (preconfiguredCliPath != null) { - val preconfiguredCliJsAbsolute = File(preconfiguredCliPath) - if (preconfiguredCliJsAbsolute.exists()) { - return preconfiguredCliJsAbsolute.absolutePath - } - val preconfiguredCliJsRelativeToReactRoot = File(reactRoot, preconfiguredCliPath) - if (preconfiguredCliJsRelativeToReactRoot.exists()) { - return preconfiguredCliJsRelativeToReactRoot.absolutePath - } - val preconfiguredCliJsRelativeToProject = File(projectDir, preconfiguredCliPath) - if (preconfiguredCliJsRelativeToProject.exists()) { - return preconfiguredCliJsRelativeToProject.absolutePath + if (preconfiguredCliFile != null) { + if (preconfiguredCliFile.exists()) { + return preconfiguredCliFile } } @@ -91,27 +74,32 @@ private fun detectCliPath( .exec( arrayOf("node", "--print", "require.resolve('react-native/cli');"), emptyArray(), - reactRoot) + reactNativeRoot) val nodeProcessOutput = nodeProcess.inputStream.use { it.bufferedReader().readText().trim() } if (nodeProcessOutput.isNotEmpty()) { val nodeModuleCliJs = File(nodeProcessOutput) if (nodeModuleCliJs.exists()) { - return nodeModuleCliJs.absolutePath + return nodeModuleCliJs } } // 3. cli.js in the root folder - val rootCliJs = File(reactRoot, "node_modules/react-native/cli.js") + val rootCliJs = File(reactNativeRoot, "node_modules/react-native/cli.js") if (rootCliJs.exists()) { - return rootCliJs.absolutePath + return rootCliJs } error( - "Couldn't determine CLI location. " + - "Please set `project.react.cliPath` to the path of the react-native cli.js file. " + - "This file typically resides in `node_modules/react-native/cli.js`") + """ + Couldn't determine CLI location! + + Please set `react { cliFile = file(...) }` inside your + build.gradle to the path of the react-native cli.js file. + This file typically resides in `node_modules/react-native/cli.js` + """ + .trimIndent()) } /** 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 index d8563d663a9..fbe7fed6304 100644 --- 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 @@ -85,9 +85,6 @@ class BundleHermesCTaskTest { 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) @@ -99,10 +96,6 @@ class BundleHermesCTaskTest { } 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()) @@ -116,28 +109,34 @@ class BundleHermesCTaskTest { @Test fun bundleTask_filesInput_areSetCorrectly() { val entryFile = tempFolder.newFile("entry.js") + val cliFile = tempFolder.newFile("cli.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 reactNativeDir = tempFolder.newFolder("node_modules/react-native") val task = createTestTask { it.entryFile.set(entryFile) + it.cliFile.set(cliFile) it.jsBundleDir.set(jsBundleDir) it.resourcesDir.set(resourcesDir) it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) it.jsSourceMapsDir.set(jsSourceMapsDir) it.bundleConfig.set(bundleConfig) + it.reactNativeDir.set(reactNativeDir) } assertEquals(entryFile, task.entryFile.get().asFile) + assertEquals(cliFile, task.cliFile.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) + assertEquals(reactNativeDir, task.reactNativeDir.get().asFile) } @Test @@ -198,6 +197,7 @@ class BundleHermesCTaskTest { @Test fun getBundleCommand_returnsCorrectCommand() { val entryFile = tempFolder.newFile("index.js") + val cliFile = tempFolder.newFile("cli.js") val bundleFile = tempFolder.newFile("bundle.js") val sourceMapFile = tempFolder.newFile("bundle.js.map") val resourcesDir = tempFolder.newFolder("res") @@ -205,7 +205,7 @@ class BundleHermesCTaskTest { val task = createTestTask { it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) - it.cliPath.set("../node_modules/react-native/cli.js") + it.cliFile.set(cliFile) it.bundleCommand.set("bundle") it.devEnabled.set(true) it.entryFile.set(entryFile) @@ -220,7 +220,7 @@ class BundleHermesCTaskTest { assertEquals("node", bundleCommand[0]) assertEquals("arg1", bundleCommand[1]) assertEquals("arg2", bundleCommand[2]) - assertEquals("../node_modules/react-native/cli.js", bundleCommand[3]) + assertEquals(cliFile.absolutePath, bundleCommand[3]) assertEquals("bundle", bundleCommand[4]) assertEquals("--platform", bundleCommand[5]) assertEquals("android", bundleCommand[6]) @@ -247,13 +247,14 @@ class BundleHermesCTaskTest { @Test fun getBundleCommand_withoutConfig_returnsCommandWithoutConfig() { val entryFile = tempFolder.newFile("index.js") + val cliFile = tempFolder.newFile("cli.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.cliFile.set(cliFile) it.bundleCommand.set("bundle") it.devEnabled.set(true) it.entryFile.set(entryFile) @@ -291,21 +292,20 @@ class BundleHermesCTaskTest { val packagerMap = tempFolder.newFile("bundle.js.packager.map") val compilerMap = tempFolder.newFile("bundle.js.compiler.map") val outputMap = tempFolder.newFile("bundle.js.map") + val reactNativeDir = tempFolder.newFolder("node_modules/react-native") + val composeSourceMapsFile = File(reactNativeDir, "scripts/compose-source-maps.js") 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) + task.getComposeSourceMapsCommand(composeSourceMapsFile, 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(composeSourceMapsFile.absolutePath, composeSourcemapCommand[3]) assertEquals(packagerMap.absolutePath, composeSourcemapCommand[4]) assertEquals(compilerMap.absolutePath, composeSourcemapCommand[5]) assertEquals("-o", composeSourcemapCommand[6]) diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt index b0f7474cd23..fb230f49551 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt @@ -59,83 +59,31 @@ class PathUtilsTest { } @Test - fun detectedCliPath_withCliPathFromExtensionAbsolute() { + fun detectedCliPath_withCliPathFromExtensionAndFileExists_returnsIt() { val project = ProjectBuilder.builder().build() + val cliFile = tempFolder.newFile("cli.js").apply { createNewFile() } val extension = TestReactExtension(project) - val expected = - File(project.projectDir, "abs/fake-cli.sh").apply { - parentFile.mkdirs() - writeText("") - } - extension.cliPath.set(project.projectDir.toString() + "/abs/fake-cli.sh") + extension.cliFile.set(cliFile) - val actual = detectedCliPath(project.projectDir, extension) + val actual = detectedCliFile(extension) - assertEquals(expected.toString(), actual) - } - - @Test - fun detectedCliPath_withCliPathFromExtensionInReactFolder() { - val project = ProjectBuilder.builder().build() - val extension = TestReactExtension(project) - val expected = - File(project.projectDir, "/react-root/fake-cli.sh").apply { - parentFile.mkdirs() - writeText("") - } - extension.cliPath.set("fake-cli.sh") - extension.root.set(File(project.projectDir.toString(), "react-root")) - - val actual = detectedCliPath(project.projectDir, extension) - - assertEquals(expected.toString(), actual) - } - - @Test - fun detectedCliPath_withCliPathFromExtensionInProjectFolder() { - val project = ProjectBuilder.builder().build() - val extension = TestReactExtension(project) - val expected = - File(project.projectDir, "fake-cli.sh").apply { - parentFile.mkdirs() - writeText("") - } - extension.cliPath.set("fake-cli.sh") - - val actual = detectedCliPath(project.projectDir, extension) - - assertEquals(expected.toString(), actual) - } - - @Test - fun detectedCliPath_withCliPathFromExtensionInParentFolder() { - val rootProject = ProjectBuilder.builder().build() - val project = ProjectBuilder.builder().withParent(rootProject).build() - project.projectDir.mkdirs() - val extension = TestReactExtension(project) - val expected = File(rootProject.projectDir, "cli-in-root.sh").apply { writeText("#!/bin/bash") } - extension.cliPath.set("../cli-in-root.sh") - - val actual = detectedCliPath(project.projectDir, extension) - - assertEquals(expected.canonicalPath, File(actual).canonicalPath) + assertEquals(cliFile, actual) } @Test fun detectedCliPath_withCliFromNodeModules() { val project = ProjectBuilder.builder().build() val extension = TestReactExtension(project) - val expected = - File(tempFolder.root, "node_modules/react-native/cli.js").apply { - parentFile.mkdirs() - writeText("") - } + File(tempFolder.root, "node_modules/react-native/cli.js").apply { + parentFile.mkdirs() + writeText("") + } val locationToResolveFrom = File(tempFolder.root, "a-subdirectory").apply { mkdirs() } extension.root.set(locationToResolveFrom) - val actual = detectedCliPath(project.projectDir, extension) + val actual = detectedCliFile(extension) - assertEquals(expected.canonicalPath, actual) + assertEquals("", actual.readText()) } @Test(expected = IllegalStateException::class) @@ -143,7 +91,7 @@ class PathUtilsTest { val project = ProjectBuilder.builder().build() val extension = TestReactExtension(project) - detectedCliPath(project.projectDir, extension) + detectedCliFile(extension) } @Test diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle index c26d4444cf3..1bfd0ebc649 100644 --- a/packages/rn-tester/android/app/build.gradle +++ b/packages/rn-tester/android/app/build.gradle @@ -76,18 +76,17 @@ plugins { */ react { - cliPath = "../../../../cli.js" + cliFile = file("$rootDir/cli.js") bundleAssetName = "RNTesterApp.android.bundle" entryFile = file("../../js/RNTesterApp.android.js") root = file("../../") - composeSourceMapsPath = "$rootDir/scripts/compose-source-maps.js" hermesCommand = "$rootDir/ReactAndroid/hermes-engine/build/hermes/bin/hermesc" debuggableVariants = ["hermesDebug", "jscDebug"] enableHermesOnlyInVariants = ["hermesDebug", "hermesRelease"] // Codegen Configs reactNativeDir = rootDir - codegenDir = new File(rootDir, "node_modules/react-native-codegen") + codegenDir = file("$rootDir/node_modules/react-native-codegen") } /**