From da6a5e0439c168147271ef66ad5ebbeebd6fce3b Mon Sep 17 00:00:00 2001 From: CFKevinRef Date: Fri, 3 Aug 2018 16:55:37 -0700 Subject: [PATCH] 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: https://github.com/facebook/react-native/pull/20526 Differential Revision: D9164762 Pulled By: hramos fbshipit-source-id: 544798a912df11c7d93070ddad5a535191cc3284 --- react.gradle | 220 +++++++++++++++++++++++++-------------------------- 1 file changed, 108 insertions(+), 112 deletions(-) diff --git a/react.gradle b/react.gradle index f048c07d4..d71b5c14d 100644 --- a/react.gradle +++ b/react.gradle @@ -6,131 +6,127 @@ def cliPath = config.cliPath ?: "node_modules/react-native/local-cli/cli.js" def bundleAssetName = config.bundleAssetName ?: "index.android.bundle" def entryFile = config.entryFile ?: "index.android.js" def bundleCommand = config.bundleCommand ?: "bundle" - -// because elvis operator -def elvisFile(thing) { - return thing ? file(thing) : null; -} - -def reactRoot = elvisFile(config.root) ?: file("../../") +def reactRoot = file(config.root ?: "../../") def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"] 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 - } -} afterEvaluate { - def isAndroidLibrary = plugins.hasPlugin("com.android.library") - // Grab all build types and product flavors - def buildTypes = android.buildTypes.collect { type -> type.name } - def productFlavors = android.productFlavors.collect { flavor -> flavor.name } + android.applicationVariants.all { def variant -> + // Create variant and target names + def targetName = variant.name.capitalize() + def targetPath = variant.dirName - // When no product flavors defined, use empty - if (!productFlavors) productFlavors.add('') + // React js bundle directories + def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}") + def resourcesDir = file("$buildDir/generated/res/react/${targetPath}") - productFlavors.each { productFlavorName -> - 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}" + def jsBundleFile = file("$jsBundleDir/$bundleAssetName") - // React js bundle directories - def jsBundleDirConfigName = "jsBundleDir${targetName}" - def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?: - file("$buildDir/intermediates/assets/${targetPath}") + // Additional node and packager commandline arguments + def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"] + def extraPackagerArgs = config.extraPackagerArgs ?: [] - def resourcesDirConfigName = "resourcesDir${targetName}" - def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?: - file("$buildDir/intermediates/res/merged/${targetPath}") - def jsBundleFile = file("$jsBundleDir/$bundleAssetName") + def currentBundleTask = tasks.create( + name: "bundle${targetName}JsAndAssets", + type: Exec) { + group = "react" + description = "bundle JS and assets for ${targetName}." - // Bundle task name for variant - def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets" - - // Additional node and packager commandline arguments - def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"] - def extraPackagerArgs = config.extraPackagerArgs ?: [] - - 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") - - if (isAndroidLibrary) { - doLast { - def moveFunc = { resSuffix -> - File originalDir = file("${resourcesDir}/drawable-${resSuffix}") - if (originalDir.exists()) { - File destDir = file("${resourcesDir}/drawable-${resSuffix}-v4") - ant.move(file: originalDir, tofile: destDir) - } - } - moveFunc.curry("ldpi").call() - moveFunc.curry("mdpi").call() - moveFunc.curry("hdpi").call() - moveFunc.curry("xhdpi").call() - moveFunc.curry("xxhdpi").call() - moveFunc.curry("xxxhdpi").call() - } - } + // Create dirs if they are not there (e.g. the "clean" task just ran) + doFirst { + jsBundleDir.deleteDir() + jsBundleDir.mkdirs() + resourcesDir.deleteDir() + resourcesDir.mkdirs() } - // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process - currentBundleTask.dependsOn("merge${targetName}Resources") - currentBundleTask.dependsOn("merge${targetName}Assets") + // Set up inputs and outputs so gradle can cache the result + inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) + outputs.dir jsBundleDir + outputs.dir resourcesDir - runBefore("process${flavorNameCapitalized}Armeabi-v7a${buildNameCapitalized}Resources", currentBundleTask) - runBefore("process${flavorNameCapitalized}X86${buildNameCapitalized}Resources", currentBundleTask) - runBefore("processUniversal${targetName}Resources", currentBundleTask) - runBefore("process${targetName}Resources", currentBundleTask) - runBefore("dataBindingProcessLayouts${targetName}", currentBundleTask) + // 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${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.mergeResources.dependsOn(currentBundleTask) + + // packageApplication for Android plugin 3.x + def packageTask = variant.hasProperty("packageApplication") + ? variant.packageApplication + : tasks.findByName("package${targetName}") + + 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) + } + + 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 + } + + packageTask.dependsOn(currentAssetsCopyTask) } }