|
@@ -0,0 +1,379 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * 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.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+import org.apache.tools.ant.taskdefs.condition.Os
|
|
|
|
|
+
|
|
|
|
|
+def config = project.hasProperty("react") ? project.react : [:];
|
|
|
|
|
+
|
|
|
|
|
+def detectEntryFile(config) {
|
|
|
|
|
+ if (System.getenv('ENTRY_FILE')) {
|
|
|
|
|
+ return System.getenv('ENTRY_FILE')
|
|
|
|
|
+ } else if (config.entryFile) {
|
|
|
|
|
+ return config.entryFile
|
|
|
|
|
+ } else if ((new File("${projectDir}/../../index.android.js")).exists()) {
|
|
|
|
|
+ return "index.android.js"
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return "index.js";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Detects CLI location in a similar fashion to the React Native CLI
|
|
|
|
|
+ */
|
|
|
|
|
+def detectCliPath(config) {
|
|
|
|
|
+ if (config.cliPath) {
|
|
|
|
|
+ return config.cliPath
|
|
|
|
|
+ }
|
|
|
|
|
+ if (new File("${projectDir}/../../node_modules/react-native/cli.js").exists()) {
|
|
|
|
|
+ return "${projectDir}/../../node_modules/react-native/cli.js"
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new Exception("Couldn't determine CLI location. " +
|
|
|
|
|
+ "Please set `project.ext.react.cliPath` to the path of the react-native cli.js");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+def composeSourceMapsPath = config.composeSourceMapsPath ?: "node_modules/react-native/scripts/compose-source-maps.js"
|
|
|
|
|
+def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
|
|
|
|
|
+def entryFile = detectEntryFile(config)
|
|
|
|
|
+def bundleCommand = config.bundleCommand ?: "bundle"
|
|
|
|
|
+def reactRoot = file(config.root ?: "../../")
|
|
|
|
|
+def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
|
|
|
|
|
+def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;
|
|
|
|
|
+def enableVmCleanup = config.enableVmCleanup == null ? true : config.enableVmCleanup
|
|
|
|
|
+def hermesCommand = config.hermesCommand ?: "../../node_modules/hermes-engine/%OS-BIN%/hermesc"
|
|
|
|
|
+
|
|
|
|
|
+def reactNativeDevServerPort() {
|
|
|
|
|
+ def value = project.getProperties().get("reactNativeDevServerPort")
|
|
|
|
|
+ return value != null ? value : "8081"
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+def reactNativeInspectorProxyPort() {
|
|
|
|
|
+ def value = project.getProperties().get("reactNativeInspectorProxyPort")
|
|
|
|
|
+ return value != null ? value : reactNativeDevServerPort()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+def getHermesOSBin() {
|
|
|
|
|
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) return "win64-bin";
|
|
|
|
|
+ if (Os.isFamily(Os.FAMILY_MAC)) return "osx-bin";
|
|
|
|
|
+ if (Os.isOs(null, "linux", "amd64", null)) return "linux64-bin";
|
|
|
|
|
+ throw new Exception("OS not recognized. Please set project.ext.react.hermesCommand " +
|
|
|
|
|
+ "to the path of a working Hermes compiler.");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Make sure not to inspect the Hermes config unless we need it,
|
|
|
|
|
+// to avoid breaking any JSC-only setups.
|
|
|
|
|
+def getHermesCommand = {
|
|
|
|
|
+ // If the project specifies a Hermes command, don't second guess it.
|
|
|
|
|
+ if (!hermesCommand.contains("%OS-BIN%")) {
|
|
|
|
|
+ return hermesCommand
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Execution on Windows fails with / as separator
|
|
|
|
|
+ return hermesCommand
|
|
|
|
|
+ .replaceAll("%OS-BIN%", getHermesOSBin())
|
|
|
|
|
+ .replace('/' as char, File.separatorChar);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Set enableHermesForVariant to a function to configure per variant,
|
|
|
|
|
+// or set `enableHermes` to True/False to set all of them
|
|
|
|
|
+def enableHermesForVariant = config.enableHermesForVariant ?: {
|
|
|
|
|
+ def variant -> config.enableHermes ?: false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+android {
|
|
|
|
|
+ buildTypes.all {
|
|
|
|
|
+ resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort()
|
|
|
|
|
+ resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+afterEvaluate {
|
|
|
|
|
+ def isAndroidLibrary = plugins.hasPlugin("com.android.library")
|
|
|
|
|
+ def variants = isAndroidLibrary ? android.libraryVariants : android.applicationVariants
|
|
|
|
|
+ variants.all { def variant ->
|
|
|
|
|
+ // Create variant and target names
|
|
|
|
|
+ def targetName = variant.name.capitalize()
|
|
|
|
|
+ def targetPath = variant.dirName
|
|
|
|
|
+
|
|
|
|
|
+ // React js bundle directories
|
|
|
|
|
+ def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
|
|
|
|
|
+ def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
|
|
|
|
|
+
|
|
|
|
|
+ def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
|
|
|
|
|
+ def jsSourceMapsDir = file("$buildDir/generated/sourcemaps/react/${targetPath}")
|
|
|
|
|
+ def jsIntermediateSourceMapsDir = file("$buildDir/intermediates/sourcemaps/react/${targetPath}")
|
|
|
|
|
+ def jsPackagerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.packager.map")
|
|
|
|
|
+ def jsCompilerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.compiler.map")
|
|
|
|
|
+ def jsOutputSourceMapFile = file("$jsSourceMapsDir/${bundleAssetName}.map")
|
|
|
|
|
+
|
|
|
|
|
+ // Additional node and packager commandline arguments
|
|
|
|
|
+ def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
|
|
|
|
|
+ def cliPath = detectCliPath(config)
|
|
|
|
|
+
|
|
|
|
|
+ def execCommand = []
|
|
|
|
|
+
|
|
|
|
|
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
|
|
|
|
+ execCommand.addAll(["cmd", "/c", *nodeExecutableAndArgs, cliPath])
|
|
|
|
|
+ } else {
|
|
|
|
|
+ execCommand.addAll([*nodeExecutableAndArgs, cliPath])
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ def enableHermes = enableHermesForVariant(variant)
|
|
|
|
|
+
|
|
|
|
|
+ def currentBundleTask = tasks.create(
|
|
|
|
|
+ name: "bundle${targetName}JsAndAssets",
|
|
|
|
|
+ type: Exec) {
|
|
|
|
|
+ group = "react"
|
|
|
|
|
+ description = "bundle JS and assets for ${targetName}."
|
|
|
|
|
+
|
|
|
|
|
+ // Create dirs if they are not there (e.g. the "clean" task just ran)
|
|
|
|
|
+ doFirst {
|
|
|
|
|
+ jsBundleDir.deleteDir()
|
|
|
|
|
+ jsBundleDir.mkdirs()
|
|
|
|
|
+ resourcesDir.deleteDir()
|
|
|
|
|
+ resourcesDir.mkdirs()
|
|
|
|
|
+ jsIntermediateSourceMapsDir.deleteDir()
|
|
|
|
|
+ jsIntermediateSourceMapsDir.mkdirs()
|
|
|
|
|
+ jsSourceMapsDir.deleteDir()
|
|
|
|
|
+ jsSourceMapsDir.mkdirs()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set up inputs and outputs so gradle can cache the result
|
|
|
|
|
+ inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
|
|
|
|
|
+ outputs.dir(jsBundleDir)
|
|
|
|
|
+ outputs.dir(resourcesDir)
|
|
|
|
|
+
|
|
|
|
|
+ // Set up the call to the react-native cli
|
|
|
|
|
+ workingDir(reactRoot)
|
|
|
|
|
+
|
|
|
|
|
+ // Set up dev mode
|
|
|
|
|
+ def devEnabled = !(config."devDisabledIn${targetName}"
|
|
|
|
|
+ || targetName.toLowerCase().contains("release"))
|
|
|
|
|
+
|
|
|
|
|
+ def extraArgs = []
|
|
|
|
|
+
|
|
|
|
|
+ if (bundleConfig) {
|
|
|
|
|
+ extraArgs.add("--config")
|
|
|
|
|
+ extraArgs.add(bundleConfig)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Hermes doesn't require JS minification.
|
|
|
|
|
+ if (enableHermes && !devEnabled) {
|
|
|
|
|
+ extraArgs.add("--minify")
|
|
|
|
|
+ extraArgs.add("false")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (config.extraPackagerArgs) {
|
|
|
|
|
+ extraArgs.addAll(config.extraPackagerArgs)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ commandLine(*execCommand, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
|
|
|
|
|
+ "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir,
|
|
|
|
|
+ "--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ if (enableHermes) {
|
|
|
|
|
+ doLast {
|
|
|
|
|
+ def hermesFlags;
|
|
|
|
|
+ def hbcTempFile = file("${jsBundleFile}.hbc")
|
|
|
|
|
+ exec {
|
|
|
|
|
+ if (targetName.toLowerCase().contains("release")) {
|
|
|
|
|
+ // Can't use ?: since that will also substitute valid empty lists
|
|
|
|
|
+ hermesFlags = config.hermesFlagsRelease
|
|
|
|
|
+ if (hermesFlags == null) hermesFlags = ["-O", "-output-source-map"]
|
|
|
|
|
+ } else {
|
|
|
|
|
+ hermesFlags = config.hermesFlagsDebug
|
|
|
|
|
+ if (hermesFlags == null) hermesFlags = []
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
|
|
|
|
+ commandLine("cmd", "/c", getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ commandLine(getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ant.move(
|
|
|
|
|
+ file: hbcTempFile,
|
|
|
|
|
+ toFile: jsBundleFile
|
|
|
|
|
+ );
|
|
|
|
|
+ if (hermesFlags.contains("-output-source-map")) {
|
|
|
|
|
+ ant.move(
|
|
|
|
|
+ // Hermes will generate a source map with this exact name
|
|
|
|
|
+ file: "${jsBundleFile}.hbc.map",
|
|
|
|
|
+ tofile: jsCompilerSourceMapFile
|
|
|
|
|
+ );
|
|
|
|
|
+ exec {
|
|
|
|
|
+ // TODO: set task dependencies for caching
|
|
|
|
|
+
|
|
|
|
|
+ // Set up the call to the compose-source-maps script
|
|
|
|
|
+ workingDir(reactRoot)
|
|
|
|
|
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
|
|
|
|
+ commandLine("cmd", "/c", *nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ commandLine(*nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ enabled config."bundleIn${targetName}" != null
|
|
|
|
|
+ ? config."bundleIn${targetName}"
|
|
|
|
|
+ : config."bundleIn${variant.buildType.name.capitalize()}" != null
|
|
|
|
|
+ ? config."bundleIn${variant.buildType.name.capitalize()}"
|
|
|
|
|
+ : targetName.toLowerCase().contains("release")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Expose a minimal interface on the application variant and the task itself:
|
|
|
|
|
+ variant.ext.bundleJsAndAssets = currentBundleTask
|
|
|
|
|
+ currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
|
|
|
|
|
+ currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
|
|
|
|
|
+
|
|
|
|
|
+ // registerGeneratedResFolders for Android plugin 3.x
|
|
|
|
|
+ if (variant.respondsTo("registerGeneratedResFolders")) {
|
|
|
|
|
+ variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ variant.registerResGeneratingTask(currentBundleTask)
|
|
|
|
|
+ }
|
|
|
|
|
+ variant.mergeResourcesProvider.get().dependsOn(currentBundleTask)
|
|
|
|
|
+
|
|
|
|
|
+ // packageApplication for Android plugin 3.x
|
|
|
|
|
+ def packageTask = variant.hasProperty("packageApplication")
|
|
|
|
|
+ ? variant.packageApplicationProvider.get()
|
|
|
|
|
+ : tasks.findByName("package${targetName}")
|
|
|
|
|
+ if (variant.hasProperty("packageLibrary")) {
|
|
|
|
|
+ packageTask = variant.packageLibrary
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // pre bundle build task for Android plugin 3.2+
|
|
|
|
|
+ def buildPreBundleTask = tasks.findByName("build${targetName}PreBundle")
|
|
|
|
|
+
|
|
|
|
|
+ def resourcesDirConfigValue = config."resourcesDir${targetName}"
|
|
|
|
|
+ if (resourcesDirConfigValue) {
|
|
|
|
|
+ def currentCopyResTask = tasks.create(
|
|
|
|
|
+ name: "copy${targetName}BundledResources",
|
|
|
|
|
+ type: Copy) {
|
|
|
|
|
+ group = "react"
|
|
|
|
|
+ description = "copy bundled resources into custom location for ${targetName}."
|
|
|
|
|
+
|
|
|
|
|
+ from(resourcesDir)
|
|
|
|
|
+ into(file(resourcesDirConfigValue))
|
|
|
|
|
+
|
|
|
|
|
+ dependsOn(currentBundleTask)
|
|
|
|
|
+
|
|
|
|
|
+ enabled(currentBundleTask.enabled)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ packageTask.dependsOn(currentCopyResTask)
|
|
|
|
|
+ if (buildPreBundleTask != null) {
|
|
|
|
|
+ buildPreBundleTask.dependsOn(currentCopyResTask)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ def currentAssetsCopyTask = tasks.create(
|
|
|
|
|
+ name: "copy${targetName}BundledJs",
|
|
|
|
|
+ type: Copy) {
|
|
|
|
|
+ group = "react"
|
|
|
|
|
+ description = "copy bundled JS into ${targetName}."
|
|
|
|
|
+
|
|
|
|
|
+ if (config."jsBundleDir${targetName}") {
|
|
|
|
|
+ from(jsBundleDir)
|
|
|
|
|
+ into(file(config."jsBundleDir${targetName}"))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ into ("$buildDir/intermediates")
|
|
|
|
|
+ if (isAndroidLibrary) {
|
|
|
|
|
+ into ("library_assets/${variant.name}/out") {
|
|
|
|
|
+ from(jsBundleDir)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ into ("assets/${targetPath}") {
|
|
|
|
|
+ from(jsBundleDir)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Workaround for Android Gradle Plugin 3.2+ new asset directory
|
|
|
|
|
+ into ("merged_assets/${variant.name}/merge${targetName}Assets/out") {
|
|
|
|
|
+ from(jsBundleDir)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Workaround for Android Gradle Plugin 3.4+ new asset directory
|
|
|
|
|
+ into ("merged_assets/${variant.name}/out") {
|
|
|
|
|
+ from(jsBundleDir)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // mergeAssets must run first, as it clears the intermediates directory
|
|
|
|
|
+ dependsOn(variant.mergeAssetsProvider.get())
|
|
|
|
|
+
|
|
|
|
|
+ enabled(currentBundleTask.enabled)
|
|
|
|
|
+ dependsOn(currentBundleTask)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 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
|
|
|
|
|
+ def mergeResourcesTask = tasks.findByName("merge${targetName}Resources")
|
|
|
|
|
+ mergeResourcesTask.dependsOn(currentAssetsCopyTask)
|
|
|
|
|
+
|
|
|
|
|
+ packageTask.dependsOn(currentAssetsCopyTask)
|
|
|
|
|
+ if (buildPreBundleTask != null) {
|
|
|
|
|
+ buildPreBundleTask.dependsOn(currentAssetsCopyTask)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 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.
|
|
|
|
|
+ def isRelease = targetName.toLowerCase().contains("release")
|
|
|
|
|
+ def libDir = "$buildDir/intermediates/transforms/"
|
|
|
|
|
+ def vmSelectionAction = {
|
|
|
|
|
+ fileTree(libDir).matching {
|
|
|
|
|
+ if (enableHermes) {
|
|
|
|
|
+ // For Hermes, delete all the libjsc* files
|
|
|
|
|
+ include "**/libjsc*.so"
|
|
|
|
|
+
|
|
|
|
|
+ if (isRelease) {
|
|
|
|
|
+ // Reduce size by deleting the debugger/inspector
|
|
|
|
|
+ include '**/libhermes-inspector.so'
|
|
|
|
|
+ include '**/libhermes-executor-debug.so'
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Release libs take precedence and must be removed
|
|
|
|
|
+ // to allow debugging
|
|
|
|
|
+ include '**/libhermes-executor-release.so'
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // For JSC, delete all the libhermes* files
|
|
|
|
|
+ include "**/libhermes*.so"
|
|
|
|
|
+ }
|
|
|
|
|
+ }.visit { details ->
|
|
|
|
|
+ def targetVariant = ".*/transforms/[^/]*/${targetPath}/.*"
|
|
|
|
|
+ def path = details.file.getAbsolutePath().replace(File.separatorChar, '/' as char)
|
|
|
|
|
+ if (path.matches(targetVariant) && details.file.isFile()) {
|
|
|
|
|
+ details.file.delete()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (enableVmCleanup) {
|
|
|
|
|
+ packageTask.doFirst(vmSelectionAction)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Patch needed for https://github.com/facebook/react-native/issues/35210
|
|
|
|
|
+// This is a patch to short-circuit the "+" dependencies inside the
|
|
|
|
|
+// users' app/build.gradle file and the various .gradle files of libraries.
|
|
|
|
|
+// As using plain "+" dependencies causes Gradle to always download the latest,
|
|
|
|
|
+// this logic forces Gradle to use latest release in the minor series.
|
|
|
|
|
+project.rootProject.allprojects {
|
|
|
|
|
+ configurations.all {
|
|
|
|
|
+ resolutionStrategy {
|
|
|
|
|
+ force "com.facebook.react:react-native:0.64.+"
|
|
|
|
|
+ force "com.facebook.react:hermes-engine:0.64.+"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|