352 lines
15 KiB
Groovy
352 lines
15 KiB
Groovy
|
/*
|
||
|
* 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
|
||
|
}
|
||
|
|
||
|
def cliPath = ["node", "-e", "console.log(require('react-native/cli').bin);"].execute([], projectDir).text.trim()
|
||
|
|
||
|
if (cliPath) {
|
||
|
return cliPath
|
||
|
} else if (new File("${projectDir}/../../node_modules/react-native/cli.js").exists()) {
|
||
|
return "${projectDir}/../../node_modules/react-native/cli.js"
|
||
|
} else {
|
||
|
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 = config.extraPackagerArgs ?: [];
|
||
|
|
||
|
if (bundleConfig) {
|
||
|
extraArgs = extraArgs.clone()
|
||
|
extraArgs.add("--config");
|
||
|
extraArgs.add(bundleConfig);
|
||
|
}
|
||
|
|
||
|
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")
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
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 releated 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) {
|
||
|
def task = tasks.findByName("package${targetName}")
|
||
|
task.doFirst(vmSelectionAction)
|
||
|
}
|
||
|
}
|
||
|
}
|