From fc25f288fe553cb7e8f04b8ce4b56297b7fa40d5 Mon Sep 17 00:00:00 2001 From: James Ide Date: Sun, 13 Oct 2019 23:24:59 -0700 Subject: [PATCH] Use Node's module resolution algo to find JSC & Hermes (#26773) Summary: The Gradle build file looks up jsc-android and hermes-engine using hard-coded paths. Rather than assuming the location of these packages, which are distributed and installed as npm packages, this commit makes the Gradle file use Node's standard module resolution algorithm. It looks up the file hierarchy until it finds a matching npm package or reaches the root directory. ## Changelog: [Android] [Changed] - ReactAndroid's Gradle file uses Node's module resolution algorithm to find JSC & Hermes Pull Request resolved: https://github.com/facebook/react-native/pull/26773 Test Plan: Ensure that CI tests pass, and that `./gradlew :ReactAndroid:installArchives` works. Printed out the paths that the Gradle script found for jsc-android and hermes-engine (both were `/react-native/node_modules/jsc-android|hermes-engine`). Differential Revision: D17903179 Pulled By: cpojer fbshipit-source-id: 9ac3ba509974f39f87b511d5bc3398451c12393f --- ReactAndroid/build.gradle | 72 ++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index ecd367703cd647..2082afa5f003f6 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -9,6 +9,8 @@ plugins { id("de.undercouch.download") } +import java.nio.file.Paths + import de.undercouch.gradle.tasks.download.Download import org.apache.tools.ant.taskdefs.condition.Os import org.apache.tools.ant.filters.ReplaceTokens @@ -92,17 +94,16 @@ task prepareFolly(dependsOn: dependenciesPath ? [] : [downloadFolly], type: Copy } task prepareHermes() { - def hermesAAR = file("$projectDir/../node_modules/hermes-engine/android/hermes-debug.aar") - if (!hermesAAR.exists()) { - // For an app to build from RN source, hermes-engine is located at /path/to/app/node_modules - // and $projectDir is located at /path/to/app/node_modules/react-native/ReactAndroid - hermesAAR = file("$projectDir/../../hermes-engine/android/hermes-debug.aar") + def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine") + if (!hermesPackagePath) { + throw new GradleScriptException("Could not find the hermes-engine npm package") + } - if (!hermesAAR.exists()) { - // At Facebook, this file is in a different folder - hermesAAR = file("$projectDir/../../node_modules/hermes-engine/android/hermes-debug.aar") - } + def hermesAAR = file("$hermesPackagePath/android/hermes-debug.aar") + if (!hermesAAR.exists()) { + throw new GradleScriptException("The hermes-engine npm package is missing \"android/hermes-debug.aar\"") } + def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" }) copy { @@ -160,16 +161,20 @@ task prepareGlog(dependsOn: dependenciesPath ? [] : [downloadGlog], type: Copy) // Create Android.mk library module based on jsc from npm task prepareJSC { doLast { - def jscPackageRoot = file("$projectDir/../node_modules/jsc-android/dist") - if (!jscPackageRoot.exists()) { - // For an app to build from RN source, the jsc-android is located at /path/to/app/node_modules - // and $projectDir may located at /path/to/app/node_modules/react-native/ReactAndroid - jscPackageRoot = file("$projectDir/../../jsc-android/dist") + def jscPackagePath = findNodeModulePath(projectDir, "jsc-android") + if (!jscPackagePath) { + throw new GradleScriptException("Could not find the jsc-android npm package") + } + + def jscDist = file("$jscPackagePath/dist") + if (!jscDist.exists()) { + throw new GradleScriptException("The jsc-android npm package is missing its \"dist\" directory") } - def jscAAR = fileTree(jscPackageRoot).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile + + def jscAAR = fileTree(jscDist).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile def soFiles = zipTree(jscAAR).matching({ it.include "**/*.so" }) - def headerFiles = fileTree(jscPackageRoot).matching({ it.include "**/include/*.h" }) + def headerFiles = fileTree(jscDist).matching({ it.include "**/include/*.h" }) copy { from(soFiles) @@ -192,6 +197,41 @@ task downloadNdkBuildDependencies { dependsOn(downloadGlog) } +/** + * Finds the path of the installed npm package with the given name using Node's + * module resolution algorithm, which searches "node_modules" directories up to + * the file system root. This handles various cases, including: + * + * - Working in the open-source RN repo: + * Gradle: /path/to/react-native/ReactAndroid + * Node module: /path/to/react-native/node_modules/[package] + * + * - Installing RN as a dependency of an app and searching for hoisted + * dependencies: + * Gradle: /path/to/app/node_modules/react-native/ReactAndroid + * Node module: /path/to/app/node_modules/[package] + * + * - Working in a larger repo (e.g., Facebook) that contains RN: + * Gradle: /path/to/repo/path/to/react-native/ReactAndroid + * Node module: /path/to/repo/node_modules/[package] + * + * The search begins at the given base directory (a File object). The returned + * path is a string. + */ +def findNodeModulePath(baseDir, packageName) { + def basePath = baseDir.toPath().normalize() + // Node's module resolution algorithm searches up to the root directory, + // after which the base path will be null + while (basePath) { + def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName) + if (candidatePath.toFile().exists()) { + return candidatePath.toString() + } + basePath = basePath.getParent() + } + return null +} + def getNdkBuildName() { if (Os.isFamily(Os.FAMILY_WINDOWS)) { return "ndk-build.cmd"