From d4c347c0524e7d6eb1f333b7529259512a384a4e Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Fri, 10 Sep 2021 08:30:13 -0700 Subject: [PATCH] Export `detected*` functions fron `ReactAppExtension` to its own file Summary: Ideally a Gradle `Extension` should contain only properties as it's the public facing API of our Gradle surface. Here I'm movign a couple of functions away from it. Now they're located inside their own Util file. Moreover I've added tests and documentation to those. Changelog: [Internal] [Changed] - Export `detected*` functions fron `ReactAppExtension` to its own file Reviewed By: mdvacca Differential Revision: D30865494 fbshipit-source-id: 59925414c0eb427161691950f5b9b6495121da00 --- .../com/facebook/react/ReactAppExtension.kt | 49 ---------- .../com/facebook/react/TaskConfiguration.kt | 6 +- .../com/facebook/react/utils/PathUtils.kt | 74 +++++++++++++++ .../com/facebook/react/tests/PathUtilsTest.kt | 91 +++++++++++++++++++ 4 files changed, 169 insertions(+), 51 deletions(-) create mode 100644 packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt create mode 100644 packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/PathUtilsTest.kt 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 09099f7aa0..20c948690d 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 @@ -35,58 +35,9 @@ open class ReactAppExtension(private val project: Project) { var resourcesDir: Map = emptyMap() var jsBundleDir: Map = emptyMap() - internal val detectedEntryFile: File - get() = detectEntryFile(entryFile = entryFile, reactRoot = reactRoot) - - internal val detectedCliPath: String - get() = - detectCliPath( - projectDir = project.projectDir, reactRoot = reactRoot, preconfuredCliPath = cliPath) - internal val osAwareHermesCommand: String get() = getOSAwareHermesCommand(hermesCommand) - private fun detectEntryFile(entryFile: File?, reactRoot: File): File = - when { - System.getenv("ENTRY_FILE") != null -> File(System.getenv("ENTRY_FILE")) - entryFile != null -> entryFile - File(reactRoot, "index.android.js").exists() -> File(reactRoot, "index.android.js") - else -> File(reactRoot, "index.android.js") - } - - private fun detectCliPath( - projectDir: File, - reactRoot: File, - preconfuredCliPath: String? - ): String { - // 1. preconfigured path - if (preconfuredCliPath != null) return preconfuredCliPath - - // 2. node module path - val nodeProcess = - Runtime.getRuntime() - .exec( - arrayOf("node", "-e", "console.log(require('react-native/cli').bin);"), - emptyArray(), - projectDir) - - val nodeProcessOutput = nodeProcess.inputStream.use { it.bufferedReader().readText().trim() } - - if (nodeProcessOutput.isNotEmpty()) { - return nodeProcessOutput - } - - // 3. cli.js in the root folder - val rootCliJs = File(reactRoot, "node_modules/react-native/cli.js") - if (rootCliJs.exists()) { - return rootCliJs.absolutePath - } - - error( - "Couldn't determine CLI location. " + - "Please set `project.react.cliPath` to the path of the react-native cli.js") - } - // Make sure not to inspect the Hermes config unless we need it, // to avoid breaking any JSC-only setups. private fun getOSAwareHermesCommand(hermesCommand: String): String { 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 d1a6b4be07..b4afb4ad9a 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 @@ -13,6 +13,8 @@ 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.facebook.react.utils.detectedCliPath +import com.facebook.react.utils.detectedEntryFile import java.io.File import org.gradle.api.Project import org.gradle.api.tasks.Copy @@ -40,7 +42,7 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactAppE // Additional node and packager commandline arguments val nodeExecutableAndArgs = config.nodeExecutableAndArgs - val cliPath = config.detectedCliPath + val cliPath = detectedCliPath(project.projectDir, config) val execCommand = nodeExecutableAndArgs + cliPath val enableHermes = config.enableHermesForVariant(variant) @@ -57,7 +59,7 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactAppE it.execCommand = execCommand it.bundleCommand = config.bundleCommand it.devEnabled = !(variant.name in config.devDisabledInVariants || isRelease) - it.entryFile = config.detectedEntryFile + it.entryFile = detectedEntryFile(config) val extraArgs = mutableListOf() 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 new file mode 100644 index 0000000000..0cfa2bd0d0 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt @@ -0,0 +1,74 @@ +/* + * 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.utils + +import com.facebook.react.ReactAppExtension +import java.io.File + +/** + * Computes the entry file for React Native. The Algo follows this order: + * 1. The file pointed by the ENTRY_FILE env variable, if set. + * 2. The file provided by the `entryFile` config in the `reactApp` Gradle extension + * 3. The `index.android.js` file, if available. + * 4. Fallback to the `index.js` file. + * + * @param config The [ReactAppExtension] configured for this project + */ +internal fun detectedEntryFile(config: ReactAppExtension): File = + detectEntryFile(entryFile = config.entryFile, reactRoot = config.reactRoot) + +/** + * 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 + * 2. The output of `node -e "console.log(require('react-native/cli').bin);"` if not failing. + * 3. The `node_modules/react-native/cli.js` file if exists + * 4. Fails otherwise + */ +internal fun detectedCliPath( + projectDir: File, + config: ReactAppExtension, +): String = + detectCliPath( + projectDir = projectDir, reactRoot = config.reactRoot, preconfuredCliPath = config.cliPath) + +private fun detectEntryFile(entryFile: File?, reactRoot: File): File = + when { + System.getenv("ENTRY_FILE") != null -> File(System.getenv("ENTRY_FILE")) + entryFile != null -> entryFile + File(reactRoot, "index.android.js").exists() -> File(reactRoot, "index.android.js") + else -> File(reactRoot, "index.js") + } + +private fun detectCliPath(projectDir: File, reactRoot: File, preconfuredCliPath: String?): String { + // 1. preconfigured path + if (preconfuredCliPath != null) return preconfuredCliPath + + // 2. node module path + val nodeProcess = + Runtime.getRuntime() + .exec( + arrayOf("node", "-e", "console.log(require('react-native/cli').bin);"), + emptyArray(), + projectDir) + + val nodeProcessOutput = nodeProcess.inputStream.use { it.bufferedReader().readText().trim() } + + if (nodeProcessOutput.isNotEmpty()) { + return nodeProcessOutput + } + + // 3. cli.js in the root folder + val rootCliJs = File(reactRoot, "node_modules/react-native/cli.js") + if (rootCliJs.exists()) { + return rootCliJs.absolutePath + } + + error( + "Couldn't determine CLI location. " + + "Please set `project.react.cliPath` to the path of the react-native cli.js") +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/PathUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/PathUtilsTest.kt new file mode 100644 index 0000000000..a0c7734663 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/PathUtilsTest.kt @@ -0,0 +1,91 @@ +/* + * 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.tests + +import com.facebook.react.ReactAppExtension +import com.facebook.react.utils.detectedCliPath +import com.facebook.react.utils.detectedEntryFile +import java.io.File +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class PathUtilsTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @Test + fun detectedEntryFile_withProvidedVariable() { + val extension = ReactAppExtension(ProjectBuilder.builder().build()) + val expected = tempFolder.newFile("fake.index.js") + extension.entryFile = expected + + val actual = detectedEntryFile(extension) + + assertEquals(expected, actual) + } + + @Test + fun detectedEntryFile_withAndroidEntryPoint() { + val extension = ReactAppExtension(ProjectBuilder.builder().build()) + extension.reactRoot = tempFolder.root + tempFolder.newFile("index.android.js") + + val actual = detectedEntryFile(extension) + + assertEquals(File(tempFolder.root, "index.android.js"), actual) + } + + @Test + fun detectedEntryFile_withDefaultEntryPoint() { + val extension = ReactAppExtension(ProjectBuilder.builder().build()) + extension.reactRoot = tempFolder.root + + val actual = detectedEntryFile(extension) + + assertEquals(File(tempFolder.root, "index.js"), actual) + } + + @Test + fun detectedCliPath_withCliPathFromExtension() { + val project = ProjectBuilder.builder().build() + val extension = ReactAppExtension(project) + val expected = tempFolder.newFile("fake-cli.sh") + extension.cliPath = expected.toString() + + val actual = detectedCliPath(project.projectDir, extension) + + assertEquals(expected.toString(), actual) + } + + @Test + fun detectedCliPath_withCliFromNodeModules() { + val project = ProjectBuilder.builder().build() + val extension = ReactAppExtension(project) + extension.reactRoot = tempFolder.root + val expected = + File(tempFolder.root, "node_modules/react-native/cli.js").apply { + parentFile.mkdirs() + writeText("") + } + + val actual = detectedCliPath(project.projectDir, extension) + + assertEquals(expected.toString(), actual) + } + + @Test(expected = IllegalStateException::class) + fun detectedCliPath_failsIfNotFound() { + val project = ProjectBuilder.builder().build() + val extension = ReactAppExtension(project) + + detectedCliPath(project.projectDir, extension) + } +}