Skip to content

Commit da6a5e0

Browse files
CFKevinReffacebook-github-bot
authored andcommitted
Better Android Gradle Plugin 3.x integration (#20526)
Summary: Mirrors #17967 which was imported and reverted Original: 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 Pull Request resolved: #20526 Differential Revision: D9164762 Pulled By: hramos fbshipit-source-id: 544798a912df11c7d93070ddad5a535191cc3284
1 parent 7a0af55 commit da6a5e0

File tree

1 file changed

+112
-116
lines changed

1 file changed

+112
-116
lines changed

react.gradle

Lines changed: 112 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -6,131 +6,127 @@ def cliPath = config.cliPath ?: "node_modules/react-native/local-cli/cli.js"
66
def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
77
def entryFile = config.entryFile ?: "index.android.js"
88
def bundleCommand = config.bundleCommand ?: "bundle"
9-
10-
// because elvis operator
11-
def elvisFile(thing) {
12-
return thing ? file(thing) : null;
13-
}
14-
15-
def reactRoot = elvisFile(config.root) ?: file("../../")
9+
def reactRoot = file(config.root ?: "../../")
1610
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
1711
def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;
1812

19-
void runBefore(String dependentTaskName, Task task) {
20-
Task dependentTask = tasks.findByPath(dependentTaskName);
21-
if (dependentTask != null) {
22-
dependentTask.dependsOn task
23-
}
24-
}
2513

2614
afterEvaluate {
27-
def isAndroidLibrary = plugins.hasPlugin("com.android.library")
28-
// Grab all build types and product flavors
29-
def buildTypes = android.buildTypes.collect { type -> type.name }
30-
def productFlavors = android.productFlavors.collect { flavor -> flavor.name }
31-
32-
// When no product flavors defined, use empty
33-
if (!productFlavors) productFlavors.add('')
34-
35-
productFlavors.each { productFlavorName ->
36-
buildTypes.each { buildTypeName ->
37-
// Create variant and target names
38-
def flavorNameCapitalized = "${productFlavorName.capitalize()}"
39-
def buildNameCapitalized = "${buildTypeName.capitalize()}"
40-
def targetName = "${flavorNameCapitalized}${buildNameCapitalized}"
41-
def targetPath = productFlavorName ?
42-
"${productFlavorName}/${buildTypeName}" :
43-
"${buildTypeName}"
44-
45-
// React js bundle directories
46-
def jsBundleDirConfigName = "jsBundleDir${targetName}"
47-
def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?:
48-
file("$buildDir/intermediates/assets/${targetPath}")
49-
50-
def resourcesDirConfigName = "resourcesDir${targetName}"
51-
def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?:
52-
file("$buildDir/intermediates/res/merged/${targetPath}")
53-
def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
54-
55-
// Bundle task name for variant
56-
def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets"
57-
58-
// Additional node and packager commandline arguments
59-
def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
60-
def extraPackagerArgs = config.extraPackagerArgs ?: []
61-
62-
def currentBundleTask = tasks.create(
63-
name: bundleJsAndAssetsTaskName,
64-
type: Exec) {
15+
android.applicationVariants.all { def variant ->
16+
// Create variant and target names
17+
def targetName = variant.name.capitalize()
18+
def targetPath = variant.dirName
19+
20+
// React js bundle directories
21+
def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
22+
def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
23+
24+
def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
25+
26+
// Additional node and packager commandline arguments
27+
def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
28+
def extraPackagerArgs = config.extraPackagerArgs ?: []
29+
30+
def currentBundleTask = tasks.create(
31+
name: "bundle${targetName}JsAndAssets",
32+
type: Exec) {
33+
group = "react"
34+
description = "bundle JS and assets for ${targetName}."
35+
36+
// Create dirs if they are not there (e.g. the "clean" task just ran)
37+
doFirst {
38+
jsBundleDir.deleteDir()
39+
jsBundleDir.mkdirs()
40+
resourcesDir.deleteDir()
41+
resourcesDir.mkdirs()
42+
}
43+
44+
// Set up inputs and outputs so gradle can cache the result
45+
inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
46+
outputs.dir jsBundleDir
47+
outputs.dir resourcesDir
48+
49+
// Set up the call to the react-native cli
50+
workingDir reactRoot
51+
52+
// Set up dev mode
53+
def devEnabled = !(config."devDisabledIn${targetName}"
54+
|| targetName.toLowerCase().contains("release"))
55+
56+
def extraArgs = extraPackagerArgs;
57+
58+
if (bundleConfig) {
59+
extraArgs = extraArgs.clone()
60+
extraArgs.add("--config");
61+
extraArgs.add(bundleConfig);
62+
}
63+
64+
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
65+
commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
66+
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
67+
} else {
68+
commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
69+
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
70+
}
71+
72+
enabled config."bundleIn${targetName}" ||
73+
config."bundleIn${variant.buildType.name.capitalize()}" ?:
74+
targetName.toLowerCase().contains("release")
75+
}
76+
77+
// Expose a minimal interface on the application variant and the task itself:
78+
variant.ext.bundleJsAndAssets = currentBundleTask
79+
currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
80+
currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
81+
82+
// registerGeneratedResFolders for Android plugin 3.x
83+
if (variant.respondsTo("registerGeneratedResFolders")) {
84+
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
85+
} else {
86+
variant.registerResGeneratingTask(currentBundleTask)
87+
}
88+
variant.mergeResources.dependsOn(currentBundleTask)
89+
90+
// packageApplication for Android plugin 3.x
91+
def packageTask = variant.hasProperty("packageApplication")
92+
? variant.packageApplication
93+
: tasks.findByName("package${targetName}")
94+
95+
def resourcesDirConfigValue = config."resourcesDir${targetName}"
96+
if (resourcesDirConfigValue) {
97+
def currentCopyResTask = tasks.create(
98+
name: "copy${targetName}BundledResources",
99+
type: Copy) {
65100
group = "react"
66-
description = "bundle JS and assets for ${targetName}."
67-
68-
// Create dirs if they are not there (e.g. the "clean" task just ran)
69-
doFirst {
70-
jsBundleDir.mkdirs()
71-
resourcesDir.mkdirs()
72-
}
73-
74-
// Set up inputs and outputs so gradle can cache the result
75-
inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
76-
outputs.dir jsBundleDir
77-
outputs.dir resourcesDir
78-
79-
// Set up the call to the react-native cli
80-
workingDir reactRoot
81-
82-
// Set up dev mode
83-
def devEnabled = !(config."devDisabledIn${targetName}"
84-
|| targetName.toLowerCase().contains("release"))
85-
86-
def extraArgs = extraPackagerArgs;
87-
88-
if (bundleConfig) {
89-
extraArgs = extraArgs.clone()
90-
extraArgs.add("--config");
91-
extraArgs.add(bundleConfig);
92-
}
93-
94-
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
95-
commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
96-
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
97-
} else {
98-
commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
99-
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
100-
}
101-
102-
enabled config."bundleIn${targetName}" ||
103-
config."bundleIn${buildTypeName.capitalize()}" ?:
104-
targetName.toLowerCase().contains("release")
105-
106-
if (isAndroidLibrary) {
107-
doLast {
108-
def moveFunc = { resSuffix ->
109-
File originalDir = file("${resourcesDir}/drawable-${resSuffix}")
110-
if (originalDir.exists()) {
111-
File destDir = file("${resourcesDir}/drawable-${resSuffix}-v4")
112-
ant.move(file: originalDir, tofile: destDir)
113-
}
114-
}
115-
moveFunc.curry("ldpi").call()
116-
moveFunc.curry("mdpi").call()
117-
moveFunc.curry("hdpi").call()
118-
moveFunc.curry("xhdpi").call()
119-
moveFunc.curry("xxhdpi").call()
120-
moveFunc.curry("xxxhdpi").call()
121-
}
122-
}
101+
description = "copy bundled resources into custom location for ${targetName}."
102+
103+
from resourcesDir
104+
into file(resourcesDirConfigValue)
105+
106+
dependsOn(currentBundleTask)
107+
108+
enabled currentBundleTask.enabled
123109
}
124110

125-
// Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
126-
currentBundleTask.dependsOn("merge${targetName}Resources")
127-
currentBundleTask.dependsOn("merge${targetName}Assets")
111+
packageTask.dependsOn(currentCopyResTask)
112+
}
113+
114+
def currentAssetsCopyTask = tasks.create(
115+
name: "copy${targetName}BundledJs",
116+
type: Copy) {
117+
group = "react"
118+
description = "copy bundled JS into ${targetName}."
128119

129-
runBefore("process${flavorNameCapitalized}Armeabi-v7a${buildNameCapitalized}Resources", currentBundleTask)
130-
runBefore("process${flavorNameCapitalized}X86${buildNameCapitalized}Resources", currentBundleTask)
131-
runBefore("processUniversal${targetName}Resources", currentBundleTask)
132-
runBefore("process${targetName}Resources", currentBundleTask)
133-
runBefore("dataBindingProcessLayouts${targetName}", currentBundleTask)
120+
from jsBundleDir
121+
into file(config."jsBundleDir${targetName}" ?:
122+
"$buildDir/intermediates/assets/${targetPath}")
123+
124+
// mergeAssets must run first, as it clears the intermediates directory
125+
dependsOn(variant.mergeAssets)
126+
127+
enabled currentBundleTask.enabled
134128
}
129+
130+
packageTask.dependsOn(currentAssetsCopyTask)
135131
}
136132
}

0 commit comments

Comments
 (0)