Better Android Gradle Plugin 3.x integration

Summary:
Better integration with the Android Gradle-based build process, especially the changes introduced by the [Android Gradle Plugin 3.x and AAPT2](https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html).

Fixes #16906, the `android.enableAapt2=false` workaround is no longer required.

Bases the task generation process on the actual application variants present in the project. The current manual process of iterating build types and product flavors could break down when more than one dimension type is present (see https://developer.android.com/studio/build/build-variants.html#flavor-dimensions).

This also exposes a very basic set of properties in the build tasks, so that other tasks can more reliably access them:

```groovy
android.applicationVariants.all { variant ->
    // This is the generated task itself:
    def reactBundleTask = variant.bundleJsAndAssets
    // These are the outputs by type:
    def resFileCollection = reactBundleTask.generatedResFolders
    def assetsFileCollection = reactBundleTask.generatedAssetsFolders
}
```

I've tested various combinations of product flavors and build types ([Build Variants](https://developer.android.com/studio/build/build-variants.html)) to make sure this is consistent. This is a port of what we're currently deploying to our CI process.

[ ANDROID ] [ BUGFIX ] [ react.gradle ] - Support Android Gradle Plugin 3.x and AAPT2
[ ANDROID ] [ FEATURE ] [ react.gradle ] - Expose the bundling task and its outputs via ext properties
Closes https://github.com/facebook/react-native/pull/17967

Differential Revision: D7017148

Pulled By: hramos

fbshipit-source-id: e52b3365e5807430b9caced51349abf72332a587
This commit is contained in:
Kevin Cassidy Jr 2018-02-16 16:50:45 -08:00 committed by Facebook Github Bot
parent 5447ca6707
commit d16ff3bd8b

View File

@ -6,112 +6,117 @@ def cliPath = config.cliPath ?: "node_modules/react-native/local-cli/cli.js"
def bundleAssetName = config.bundleAssetName ?: "index.android.bundle" def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
def entryFile = config.entryFile ?: "index.android.js" def entryFile = config.entryFile ?: "index.android.js"
def bundleCommand = config.bundleCommand ?: "bundle" def bundleCommand = config.bundleCommand ?: "bundle"
def reactRoot = file(config.root ?: "../../")
// because elvis operator
def elvisFile(thing) {
return thing ? file(thing) : null;
}
def reactRoot = elvisFile(config.root) ?: file("../../")
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"] def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ; def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;
void runBefore(String dependentTaskName, Task task) {
Task dependentTask = tasks.findByPath(dependentTaskName);
if (dependentTask != null) {
dependentTask.dependsOn task
}
}
gradle.projectsEvaluated { gradle.projectsEvaluated {
// Grab all build types and product flavors android.applicationVariants.all { def variant ->
def buildTypes = android.buildTypes.collect { type -> type.name } // Create variant and target names
def productFlavors = android.productFlavors.collect { flavor -> flavor.name } def targetName = variant.name.capitalize()
def targetPath = variant.dirName
// When no product flavors defined, use empty // React js bundle directories
if (!productFlavors) productFlavors.add('') def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
productFlavors.each { productFlavorName -> def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
buildTypes.each { buildTypeName ->
// Create variant and target names
def flavorNameCapitalized = "${productFlavorName.capitalize()}"
def buildNameCapitalized = "${buildTypeName.capitalize()}"
def targetName = "${flavorNameCapitalized}${buildNameCapitalized}"
def targetPath = productFlavorName ?
"${productFlavorName}/${buildTypeName}" :
"${buildTypeName}"
// React js bundle directories // Additional node and packager commandline arguments
def jsBundleDirConfigName = "jsBundleDir${targetName}" def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?: def extraPackagerArgs = config.extraPackagerArgs ?: []
file("$buildDir/intermediates/assets/${targetPath}")
def resourcesDirConfigName = "resourcesDir${targetName}" def currentBundleTask = tasks.create(
def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?: name: "bundle${targetName}JsAndAssets",
file("$buildDir/intermediates/res/merged/${targetPath}") type: Exec) {
def jsBundleFile = file("$jsBundleDir/$bundleAssetName") group = "react"
description = "bundle JS and assets for ${targetName}."
// Bundle task name for variant // Create dirs if they are not there (e.g. the "clean" task just ran)
def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets" doFirst {
jsBundleDir.deleteDir()
// Additional node and packager commandline arguments jsBundleDir.mkdirs()
def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"] resourcesDir.deleteDir()
def extraPackagerArgs = config.extraPackagerArgs ?: [] resourcesDir.mkdirs()
def currentBundleTask = tasks.create(
name: bundleJsAndAssetsTaskName,
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.mkdirs()
resourcesDir.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 = extraPackagerArgs;
if (bundleConfig) {
extraArgs = extraArgs.clone()
extraArgs.add("--config");
extraArgs.add(bundleConfig);
}
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
} else {
commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
}
enabled config."bundleIn${targetName}" ||
config."bundleIn${buildTypeName.capitalize()}" ?:
targetName.toLowerCase().contains("release")
} }
// Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process // Set up inputs and outputs so gradle can cache the result
currentBundleTask.dependsOn("merge${targetName}Resources") inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
currentBundleTask.dependsOn("merge${targetName}Assets") outputs.dir jsBundleDir
outputs.dir resourcesDir
runBefore("process${flavorNameCapitalized}Armeabi-v7a${buildNameCapitalized}Resources", currentBundleTask) // Set up the call to the react-native cli
runBefore("process${flavorNameCapitalized}X86${buildNameCapitalized}Resources", currentBundleTask) workingDir reactRoot
runBefore("processUniversal${targetName}Resources", currentBundleTask)
runBefore("process${targetName}Resources", currentBundleTask) // Set up dev mode
runBefore("dataBindingProcessLayouts${targetName}", currentBundleTask) def devEnabled = !(config."devDisabledIn${targetName}"
|| targetName.toLowerCase().contains("release"))
def extraArgs = extraPackagerArgs;
if (bundleConfig) {
extraArgs = extraArgs.clone()
extraArgs.add("--config");
extraArgs.add(bundleConfig);
}
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
} else {
commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
}
enabled config."bundleIn${targetName}" ||
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)
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
variant.mergeResources.dependsOn(currentBundleTask)
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
}
variant.packageApplication.dependsOn(currentCopyResTask)
}
def currentAssetsCopyTask = tasks.create(
name: "copy${targetName}BundledJs",
type: Copy) {
group = "react"
description = "copy bundled JS into ${targetName}."
from jsBundleDir
into file(config."jsBundleDir${targetName}" ?:
"$buildDir/intermediates/assets/${targetPath}")
// mergeAssets must run first, as it clears the intermediates directory
dependsOn(variant.mergeAssets)
enabled currentBundleTask.enabled
}
variant.packageApplication.dependsOn(currentAssetsCopyTask)
} }
} }