Create an aar with all flutter libraries and dependencies inside - android
I need to create an aar with all the libraries of my flutter project inside, I created a flutter module and now I've to create an sdk in android to be embedded in a client app for that it would be nice to have a single aar file. I tried Mobbeel fat AAR Gradle plugin but with no avail. I know I can create a maven repository but that is not the solution I'm looking for right now.
my project build.gradle
buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2/" }
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "com.mobbeel.plugin:fat-aar:2.0.1"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
and the app build.graddle
def flutterPluginVersion = 'managed'
apply plugin: 'com.android.library'
apply plugin: "com.mobbeel.plugin"
android {
compileSdkVersion 28
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
defaultConfig {
minSdkVersion 21
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
buildDir = new File(rootProject.projectDir, "../build/host")
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api (project(':flutter'))
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.lifecycle:lifecycle-common:2.0.0'
}
aarPlugin {
includeAllInnerDependencies false
packagesToInclude = ["mobbeel"]
}
EDIT: I found a solution, but I'm not an android developer so a made some changes to the mobbeel plugin and add it to the build.gradle. After that I was able to add all the libraries to my aar by doing api project(":vibrate")
String archiveAarName
project.afterEvaluate {
project.android.libraryVariants.all { variant ->
variant.outputs.all {
archiveAarName = outputFileName
}
print "afterEvaluate\n"
def copyTask = createBundleDependenciesTask(variant)
String rsDirPath = "${copyTask.temporaryDir.path}/rs/"
String rsCompiledDirPath = "${copyTask.temporaryDir.path}/rs-compiled/"
String sourceAarPath = "${copyTask.temporaryDir.path}/${variant.name}/"
String taskNameCompileRs = "SDKcompileRs${variant.name.capitalize()}"
String taskNameRsJa = "CreateRsJar${variant.name.capitalize()}"
String taskNameCreateZip = "createZip${variant.name.capitalize()}"
def compileRsTask = R2ClassTask(variant, rsDirPath, rsCompiledDirPath, taskNameCompileRs)
def rsJarTask = bundleRJarTask(variant, rsCompiledDirPath, sourceAarPath, taskNameRsJa)
def aarTask = bundleFinalAAR(variant, sourceAarPath, "finalname", taskNameCreateZip)
def assembleTask = project.tasks.findByPath("assemble${variant.name.capitalize()}")
assembleBundleDependenciesTask(variant).finalizedBy(assembleTask)
assembleTask.finalizedBy(copyTask)
copyTask.finalizedBy(compileRsTask)
compileRsTask.finalizedBy(rsJarTask)
rsJarTask.finalizedBy(aarTask)
}
}
Task assembleBundleDependenciesTask(def variant) {
println "assembleBundleDependenciesTask -> ${variant.name}"
return project.getTasks().create("hello_${variant}", {
project.configurations.api.getDependencies().each { dependency ->
if (dependency instanceof ProjectDependency) {
Project dependencyProject = project.parent.findProject(dependency.name)
String dependencyPath = "${dependencyProject.buildDir}"
println "dependencyPath -> ${dependencyPath}"
String variantName = "${variant.name}"
def assembleTask = project.tasks.findByPath(":${dependency.name}:assemble${variant.name.capitalize()}")
assembleTask.finalizedBy(copyTo( "${dependencyPath}/outputs/aar", variantName, dependency.name))
}
println ''
}
})
}
Task copyTo(String fromz, String variant, String dependency) {
println "copyTo fromz -> $fromz "
return project.task(type: Copy, "copyFile$dependency$variant") {
from fromz
into project.projectDir.path + "/build/outputs/aar/"
include('*.aar')
rename { String fileName ->
fileName = "${dependency}-${variant}.aar"
}
}
}
Task createBundleDependenciesTask(def variant) {
println "createBundleDependenciesTask -> ${variant.name}"
String taskName = "copy${variant.name.capitalize()}Dependencies"
return project.getTasks().create(taskName, CopyDependenciesTask.class, {
it.includeInnerDependencies = true
it.dependencies = project.configurations.api.getDependencies()
it.variantName = variant.name
it.gradleVersion = "3.2.1"
it.buildAARDir = project.projectDir.path + "/build/outputs/aar/"
})
}
Task R2ClassTask(def variant, String sourceDir, String destinationDir, String taskName) {
print "R2ClassTask sourceDir -> $sourceDir to destDir -> $destinationDir"
project.mkdir(destinationDir)
def classpath
classpath = project.files(project.projectDir.path +
"/build/intermediates/javac/${variant.name}/compile${variant.name.capitalize()}JavaWithJavac/classes")
return project.getTasks().create(taskName, JavaCompile.class, {
it.source = sourceDir
it.sourceCompatibility = project.android.compileOptions.sourceCompatibility
it.targetCompatibility = project.android.compileOptions.targetCompatibility
it.classpath = classpath
it.destinationDir project.file(destinationDir)
})
}
Task bundleRJarTask(def variant, String fromDir, String aarPath, String taskName) {
print "bundleRJarTask\n"
return project.getTasks().create(taskName, Jar.class, {
it.from fromDir
it.archiveName = "r-classes.jar"
it.destinationDir project.file("${aarPath}/libs")
})
}
Task bundleFinalAAR(def variant, String fromPath, name, String taskName) {
print "bundleFinalAAR -> from ${fromPath} to > " + project.file(project.projectDir.path + "/build/outputs/aar/") + "\n"
return project.getTasks().create(taskName, Zip.class, {
it.from fromPath
it.include "**"
it.archiveName = "${name}-${variant.name}.aar"
it.destinationDir(project.file(project.projectDir.path + "/build/outputs/aar/"))
})
}
import groovy.xml.XmlUtil
class CopyDependenciesTask extends DefaultTask {
Boolean includeInnerDependencies
DependencySet dependencies
String variantName
String gradleVersion
String[] packagesToInclude = [""]
String buildAARDir
#TaskAction
def executeTask() {
if (temporaryDir.exists()) {
temporaryDir.deleteDir()
}
temporaryDir.mkdir()
copyProjectBundles()
analyzeDependencies()
}
def copyProjectBundles() {
println "copyProjectBundles"
if (gradleVersion.contains("3.2")) { // Version 3.4.x
println "packaged-classes -> ${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/packaged-classes/"
project.copy {
from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/packaged-classes/"
include "${variantName}/**"
into temporaryDir.path
}
project.copy {
from("${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/res/symbol-table-with-package/${variantName}") {
include "package-aware-r.txt"
rename '(.*)', 'R.txt'
}
from("${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/aapt_friendly_merged_manifests/" +
"${variantName}/process${variantName.capitalize()}Manifest/aapt/") {
include "AndroidManifest.xml"
}
into "${temporaryDir.path}/${variantName}"
}
println " check this -> ${temporaryDir.path}/${variantName}/R.txt"
processRsAwareFile(new File("${temporaryDir.path}/${variantName}/R.txt"))
project.copy {
from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/packaged_res/${variantName}"
include "**"
into "${temporaryDir.path}/${variantName}/res"
}
project.copy {
from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/library_assets/${variantName}/packageDebugAssets/out/"
include "**"
into "${temporaryDir.path}/${variantName}/assets"
}
} else { // Version 3.0.x
project.copy {
from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/bundles/"
from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/manifests/full/"
include "${variantName}/**"
exclude "**/output.json"
into temporaryDir.path
}
}
}
def analyzeDependencies() {
print "analyzeDependencies\n"
dependencies.each { dependency ->
def dependencyPath
def archiveName
print "dependency -> " + dependency
if (dependency instanceof ProjectDependency) {
print " instanceof -> ProjectDependency\n"
String group = dependency.group
Project dependencyProject
dependencyProject = project.parent.findProject(dependency.name)
println "dependencyProject -> ${dependencyProject}"
if (dependencyProject.plugins.hasPlugin('java-library')) {
println "Internal java dependency detected -> " + dependency.name
archiveName = dependencyProject.jar.archiveName
dependencyPath = "${dependencyProject.buildDir}/libs/"
} else {
println "Internal android dependency detected -> " + dependency.name
dependencyProject.android.libraryVariants.all {
if (it.name == variantName) {
it.outputs.all { archiveName = outputFileName }
}
}
dependencyPath = buildAARDir
}
processDependency(dependency, archiveName, dependencyPath)
} else if (dependency instanceof ExternalModuleDependency) {
println "External dependency detected -> " + dependency.group + ":" + dependency.name + ":" + dependency.version
dependencyPath = project.gradle.getGradleUserHomeDir().path + "/caches/modules-2/files-2.1/"
dependencyPath += dependency.group + "/" + dependency.name + "/" + dependency.version + "/"
processDependency(dependency, archiveName, dependencyPath)
} else {
println "Not recognize type of dependency for " + dependency
println()
}
}
}
/**
* In this case dependency is outside from workspace, download from maven repository if file is
* a jar directly move to lib/ folder and analyze pom file for detect another transitive dependency
* #param dependency
* #return
*/
def processDependency(Dependency dependency, String archiveName, String dependencyPath) {
println "processDependency -> ${archiveName} in ${dependencyPath}"
project.fileTree(dependencyPath).getFiles().each { file ->
println "processDependency file.name -> ${file.name} "
if (file.name.endsWith(".pom")) {
println "POM: " + file.name
processPomFile(file.path)
} else {
if (archiveName == null || file.name == archiveName) {
println "Artifact: " + file.name
if (file.name.endsWith(".aar")) {
processZipFile(file, dependency)
} else if (file.name.endsWith(".jar")) {
if (!file.name.contains("sources")) {
copyArtifactFrom(file.path)
} else {
println " |--> Exclude for source jar"
}
}
}
}
}
println()
}
def processZipFile(File aarFile, Dependency dependency) {
println "processZipFile"
String tempDirPath = "${temporaryDir.path}/${dependency.name}_zip"
println "tempDirPath -> ${tempDirPath}"
project.copy {
from project.zipTree(aarFile.path)
include "**/*"
into tempDirPath
}
File tempFolder = new File(tempDirPath)
println "temporaryDir -> ${temporaryDir.path}/${variantName}/"
project.copy {
from "${tempFolder.path}"
include "classes.jar"
into "${temporaryDir.path}/${variantName}/libs"
def jarName = getJarNameFromDependency(dependency)
rename "classes.jar", jarName
}
project.copy {
from "${tempFolder.path}/libs"
include "**/*.jar"
into "${temporaryDir.path}/${variantName}/libs"
}
project.copy {
from "${tempFolder.path}/jni"
include "**/*.so"
into "${temporaryDir.path}/${variantName}/jni"
}
project.copy {
from "${tempFolder.path}/assets"
include "**/*"
into "${temporaryDir.path}/${variantName}/assets"
}
project.copy {
from "${tempFolder.path}/res"
include "**/*"
exclude "values/**"
into "${temporaryDir.path}/${variantName}/res"
}
processValuesResource(tempFolder.path)
processRsFile(tempFolder)
println "tempFolder.deleteDir()"
tempFolder.deleteDir()
}
def getJarNameFromDependency(Dependency dependency) {
def jarName = ""
if (null != dependency.group) {
jarName += dependency.group.toLowerCase() + "-"
}
jarName += dependency.name.toLowerCase()
if(null != dependency.version && !dependency.version.equalsIgnoreCase('unspecified')) {
jarName += "-" + dependency.version
}
jarName += ".jar"
return jarName
}
def processRsAwareFile(File resAwareFile) {
println "processRsAwareFile"
RandomAccessFile raf = new RandomAccessFile(resAwareFile, "rw")
long writePosition = raf.getFilePointer()
raf.readLine() // Move pointer to second line of file
long readPosition = raf.getFilePointer()
byte[] buffer = new byte[1024]
int bytesInBuffer
while (-1 != (bytesInBuffer = raf.read(buffer))) {
raf.seek(writePosition)
raf.write(buffer, 0, bytesInBuffer)
readPosition += bytesInBuffer
writePosition += bytesInBuffer
raf.seek(readPosition)
}
raf.setLength(writePosition)
raf.seek(0)
if (gradleVersion.contains("3.2")) {
String filePath = "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/symbols/${variantName}/R.txt"
Scanner resourcesOriginal = new Scanner(new File(filePath))
raf.seek(0) // Move pointer to first line
String line
int offset = 0
while (resourcesOriginal.hasNextLine()) {
boolean match = false
line = resourcesOriginal.nextLine()
println line
line += "\n"
byte[] data = line.getBytes()
raf.seek(offset)
raf.write(data, 0, data.length)
offset += data.length
raf.seek(offset + 1)
}
}
raf.close()
}
def processRsFile(File tempFolder) {
println "processRsFile"
def mainManifestFile = project.android.sourceSets.main.manifest.srcFile
def libPackageName = ""
if (mainManifestFile.exists()) {
println "processRsFile -> mainManifestFile.exists()"
libPackageName = new XmlParser().parse(mainManifestFile).#package
}
def manifestFile = new File("$tempFolder/AndroidManifest.xml")
if (manifestFile.exists()) {
println "processRsFile -> manifestFile.exists()"
def aarManifest = new XmlParser().parse(manifestFile)
def aarPackageName = aarManifest.#package
String packagePath = aarPackageName.replace('.', '/')
// Generate the R.java file and map to current project's R.java
// This will recreate the class file
def rTxt = new File("$tempFolder/R.txt")
def rMap = new ConfigObject()
if (rTxt.exists()) {
println "processRsFile -> rTxt.exists()"
rTxt.eachLine { line ->
//noinspection GroovyUnusedAssignment
def (type, subclass, name, value) = line.tokenize(' ')
rMap[subclass].putAt(name, type)
}
}
def sb = "package $aarPackageName;" << '\n' << '\n'
sb << 'public final class R {' << '\n'
rMap.each { subclass, values ->
sb << " public static final class $subclass {" << '\n'
values.each { name, type ->
sb << " public static $type $name = com.company.native_sdk.R.${subclass}.${name};" << '\n'
}
sb << " }" << '\n'
}
sb << '}' << '\n'
new File("${temporaryDir.path}/rs/$packagePath").mkdirs()
FileOutputStream outputStream = new FileOutputStream("${temporaryDir.path}/rs/$packagePath/R.java")
println "R file path -> ${temporaryDir.path}/rs/$packagePath/R.java"
outputStream.write(sb.toString().getBytes())
outputStream.close()
}
}
def processValuesResource(String tempFolder) {
println "processValuesResource"
File valuesSourceFile = new File("${tempFolder}/res/values/values.xml")
File valuesDestFile = new File("${temporaryDir.path}/${variantName}/res/values/values.xml")
if (valuesSourceFile.exists()) {
println "processValuesResource -> valuesSourceFile.exists"
if (!valuesDestFile.exists()) {
println "processValuesResource -> !valuesDestFile.exists"
project.copy {
from "${tempFolder}/res"
include "values/*"
into "${temporaryDir.path}/${variantName}/res"
}
} else {
println "processValuesResource -> valuesDestFile.exists"
def valuesSource = new XmlSlurper().parse(valuesSourceFile)
def valuesDest = new XmlSlurper().parse(valuesDestFile)
valuesSource.children().each {
valuesDest.appendNode(it)
}
FileOutputStream fileOutputStream = new FileOutputStream(valuesDestFile, false)
byte[] myBytes = XmlUtil.serialize(valuesDest).getBytes("UTF-8")
fileOutputStream.write(myBytes)
fileOutputStream.close()
}
} else {
println "processValuesResource -> !valuesSourceFile.exists"
}
}
def copyArtifactFrom(String path) {
project.copy {
includeEmptyDirs false
from path
include "**/*.jar"
into "${temporaryDir.path}/${variantName}/libs"
rename '(.*)', '$1'.toLowerCase()
}
}
def processPomFile(String pomPath) {
def pom = new XmlSlurper().parse(new File(pomPath))
pom.dependencies.children().each {
def subJarLocation = project.gradle.getGradleUserHomeDir().path + "/caches/modules-2/files-2.1/"
if (!it.scope.text().equals("test") && !it.scope.text().equals("provided")) {
String version = it.version.text()
if (version.startsWith("\${") && version.endsWith("}")) {
pom.properties.children().each {
if (version.contains(it.name())) {
version = it.text()
}
}
}
println " |--> Inner dependency: " + it.groupId.text() + ":" + it.artifactId.text() + ":" + version
if (includeInnerDependencies || it.groupId.text() in packagesToInclude) {
subJarLocation += it.groupId.text() + "/" + it.artifactId.text() + "/" + version + "/"
project.fileTree(subJarLocation).getFiles().each { file ->
if (file.name.endsWith(".pom")) {
println " /--> " + file.name
processPomFile(file.path)
} else {
if (!file.name.contains("sources") && !file.name.contains("javadoc")) {
copyArtifactFrom(file.path)
}
}
}
} else {
println " (Exclude inner dependency)"
}
}
}
}
}
Add Android native library into your project in Android Studio: File -> New -> New module -> Android Library.
After that add this plugin https://github.com/kezong/fat-aar-android into the project and replace 'implementation' by 'embed' keyword.
Then your project structure will look like:
In flutter_library directory run command flutter build aar -v. Note: flutter_library contains Flutter related files, e.g lib/, .android, .ios, pubspec.yaml, etc
In root project directory run ./gradlew assemble
aar will be located in library/build/outputs/aar
See my example: https://github.com/askarsyzdykov/native_flutter_lib
The aar file doesn't contain the transitive dependencies and doesn't have a pom file which describes the dependencies used by the library.
It means that, if you are importing a aar file using a flatDir repo you have to specify the dependencies also in your project.
I know that it is not the solution you are looking for but you should use a maven repository to solve this issue.
In this case, gradle downloads the dependencies using the pom file which will contains the dependencies list.
Related
Autoincrement VersionCode with gradle extra properties is not working with Azure Git pipeline
I'm doing automated build number generation via build.gradle using property file. Changes on the property file is not being pushed in the Git Repository after the assembleRelease has been executed on Azure Devops private Integer generateBuildNum() { File buildNumPropsFile = file('../app/src/main/assets/buildnum.properties') if (buildNumPropsFile.canRead()) { Properties buildNumProps = new Properties() buildNumProps.load(new FileInputStream(buildNumPropsFile)) return buildNumProps['VERSION_BUILD'].toInteger() } else { throw new GradleException("Could not read buildnum.properties!") } return null } private void increaseBuildNum() { gradle.taskGraph.whenReady { taskGraph -> println taskGraph.hasTask(assembleRelease) if (taskGraph.hasTask(assembleRelease)) { /* when run release task */ File buildNumPropsFile = file('../app/src/main/assets/buildnum.properties') if (buildNumPropsFile.canRead()) { Properties buildNumProps = new Properties() buildNumProps.load(new FileInputStream(buildNumPropsFile)) def build = buildNumProps['VERSION_BUILD'].toInteger() + 1 if(build != null) { println "*********increaseBuildNum build: "+build } buildNumProps['VERSION_BUILD'] = build.toString() buildNumProps.store(buildNumPropsFile.newWriter(), null) def buildUpdated = buildNumProps['VERSION_BUILD'].toInteger() } else { throw new GradleException("Could not read buildnum.properties!") } } } } android { compileSdkVersion 28 buildToolsVersion "28.0.0" defaultConfig { ..... increaseBuildNum() } The property file is working when the apk is built on local machine but not on Azure Pipeline. How can I update it on the Git Repo.
Cannot invoke method doFirst() on null object : react.gradle
Can somebody help me. I tried to create android dependency using a react native compiled to android studio. and I encountered some error that says : Cannot invoke method doFirst() on null object and its directing to my react.gradle file. Here's environment specs: Android studio build gradle version : 4.0.0 distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip Here's the detailed error FAILURE: Build failed with an exception. * Where: Script 'C:\Users\Nhick\esports-gameclient-mobile-v1.0\node_modules\react-native\react.gradle' line: 312 * What went wrong: A problem occurred configuring project ':app'. > Failed to notify project evaluation listener. > Cannot invoke method doFirst() on null object > Could not get unknown property 'applicationVariants' for extension 'android' of type com.android.build.gradle.LibraryExtension. Here's my react.gradle // 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 cliPath = config.cliPath ?: "node_modules/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 = config.entryFile ?: "index.android.js" 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%/hermes" 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 extraPackagerArgs = config.extraPackagerArgs ?: [] 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 = 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, "--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs) } else { commandLine(*nodeExecutableAndArgs, cliPath, 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) } } }
I didn't see you declare a task like package$..., so that maybe a reason, gradle can't find tasks.findByName("package${targetName}") and it return null.
I've faced the same problem when changed the plugin from application to library. To solve this I've just added a null-check: ... if (enableVmCleanup) { def task = tasks.findByName("package${targetName}") if (task != null) task.doFirst(vmSelectionAction) }
Gradle copy and rename in loop
I'm working with Android Studio 3. For each flavor, I want to copy mapping.txt and rename it. My Gradle task : task deployApks(type: Copy) { android.applicationVariants.all { variant -> if (variant.buildType.name == 'release') { variant.outputs.all { def flavor = variant.productFlavors.name.get(0) def dirApk = System.getProperty("user.dir") + '/app/build/' + flavor + '/release/' def dirMapping = System.getProperty("user.dir") + '/app/build/outputs/mapping/' + flavor + '/release/' //copy apk and mapping.txt from dirApk, dirMapping include '*-release.apk', 'mapping.txt' into dirDeploy //rename mapping.txt from dirDeploy include 'mapping.txt' into dirDeploy rename 'mapping.txt', 'mapping-' + flavor + '.txt' println("Rename mapping.txt tomapping-" + flavor + ".txt") } } } } What I want in deploy directory : flavor1-release.apk mapping-flavor1.txt flavor2-release.apk mapping-flavor2.txt What I get : flavor1-release.apk mapping-flavor1.txt flavor2-release.apk Is gradle copy asynchronous.? It looks like if renaming is done after all copies.
You may not know but gradle build consists of 3 phases: initialisation configuration execution Task (including Copy you used) actions (a task is a collection of actions run in order) are configured in the second phase. Eve if you put loop in task's body the last iteration will win. The easiest way is to change your task to the following (copying manually): task deployApks { doLast { android.applicationVariants.all { variant -> if (variant.buildType.name == 'release') { variant.outputs.all { def flavor = variant.productFlavors.name.get(0) def dirApk = System.getProperty("user.dir") + '/app/build/' + flavor + '/release/' def dirMapping = System.getProperty("user.dir") + '/app/build/outputs/mapping/' + flavor + '/release/' //copy apk and mapping.txt copy { from dirApk, dirMapping include '*-release.apk', 'mapping.txt' into dirDeploy rename 'mapping.txt', 'mapping-' + flavor + '.txt' } } } } } } If that solves the problem - (you don't need to task caching) you can work with. Otherwise you need to configure Copy task appropriately or even write a custom task.
I think the key is variant.assemble.doLast. I'm making first all apk files and when finish run doLast task copying and renaming mapping.txt files. Gradle 4 (compatible) // Map for the version code that gives each ABI a value. ext.abiCodes = ['armeabi':3, 'armeabi-v7a':4, 'arm64-v8a':5, 'mips':6, 'x86':7, 'x86_64':8].withDefault {0} import com.android.build.OutputFile android.applicationVariants.all { variant -> def customName = "" if (project.hasProperty('projectName')) { customName = projectName } else { customName = project.name } def flavorName = variant.productFlavors[0].name def buildType = variant.variantData.variantConfiguration.buildType.name def abiVersionCode = "" def abiName = "" def fileName = "" def mappingDir = "${rootDir}/build/outputs/mapping/${flavorName}/${buildType}" variant.outputs.all { output -> abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) if (abiVersionCode != null) { output.versionCodeOverride = abiVersionCode * 1000 + variant.versionCode } abiName = output.getFilter(OutputFile.ABI) if (abiName == null) { abiName = "universal" output.versionCodeOverride = 1 * 1000 + variant.versionCode } fileName = customName + "_" + variant.versionName + "-" + flavorName + "-" + abiName + "-" + buildType + "-" + output.versionCode outputFileName = new File("${fileName}.apk") } variant.assemble.doLast { variant.outputs.all { output -> if (buildType == "release") { def mappingFile = "${mappingDir}/mapping.txt" def newMappingName = "${fileName}-mapping.txt" delete "${output.outputFile.parent}/${newMappingName}" copy { from "${mappingFile}" into "${rootDir}" rename { String mappingName -> "${output.outputFile.parent}/${newMappingName}" } } } delete "${output.outputFile.parent}/output.json" } } }
Android gradle change resources on merge
I have a json config file and I put it in res/raw folder. previously I have a gradle script that updates the file contents when it was necessary. def fetchMeta(buildVariant) { def flavor = buildVariant.productFlavors.get(0).name def buildType = buildVariant.buildType.name def middleMetaFolder = "${buildDir}/intermediates/meta/${flavor}" def pathToMiddleMeta = "${middleMetaFolder}/latest.json" def rawFolder = "${buildDir}/intermediates/res/merged/${buildVariant.dirName}/raw/" def f = new File(pathToMiddleMeta) boolean doDownload = (!f.exists()) || (f.lastModified() < (System.currentTimeMillis() - 86400000)) // Force on production release if (doDownload || (flavor == "production" && buildType == "release")) { new File(middleMetaFolder).mkdirs() def serverAddress = "https://example.com" String srcUrl = serverAddress + "/latest.json" println "Downloading Meta from: " + srcUrl + " to " + pathToMiddleMeta new URL(srcUrl).withInputStream { i -> f.withOutputStream { it << i } } } else { println "Skipping Meta as it exists here: " + pathToMiddleMeta } copy { from pathToMiddleMeta into rawFolder } } android.applicationVariants.all { variant -> variant.mergeResources.doLast { fetchMeta(variant) } } But as of android gradle plugin 3.0 merge strategy changed and files are with flat extension. How can I update the contents of my file after these changes?
had the same issue, which comes with the update from aapt to aapt2 issue is already assigned at google issue-tracker https://issuetracker.google.com/issues/65220623 as a workaround right now you can set android.enableAapt2=false in your gradle.properties
Access a path from .bashrc in gradle.build on a macbook pro
I have a set a path in my .bashrc which I wanna have access to from my build.gradle file. I'm using the commandLine method in gradle, but I can't seems to get it working. My .bashrc: APK_PATH="echo /Users/CPE/APK" export APK_PATH Which give me this result in a terminal: $APK_PATH /Users/CPE/APK In my gradle.build file I have the following code: def getOutputPath = { -> def stdout = new ByteArrayOutputStream() exec { commandLine 'echo', '$APK_PATH' standardOutput = stdout } return stdout.toString().trim() } applicationVariants.all { variant -> def newName; def versionNumber = variant.versionName; def appName = variant.name.replace("Release",""); def date = getDate(); if (variant.buildType.name == "release") { newName = appName + "_" + date + ".apk"; releaseDir = getOutputPath() + "/" + appName; } else { newName = variant.name; } variant.outputFile = new File(releaseDir, newName); } When i'm trying to make a release build I get the following error: Unable to open '$APK_PATH/ostran/ostran_20141209.apk' as zip archive
Instead of using the .bashrc file you can use your local gradle.properties which I found WAAAY easier! You can place a gradle.properties file in the Gradle user home directory. The properties set in a gradle.properties file can be accessed via the project object. The properties file in the user's home directory has precedence over property files in the project directories. If this property does not exist, an exception will be thrown and the build will fail. If your build script relies on optional properties the user might set, perhaps in a gradle.properties file, you need to check for existence before you access them. You can do this by using the method hasProperty('propertyName') which returns true or false. my flavors.gradle applicationVariants.all { variant -> if (project.hasProperty('APK_PATH')) { def appName = variant.name.replace("Release", ""); def releaseDir = variant.outputFile.parent def newName = variant.name; if (variant.buildType.name == "release") { newName = appName + "_" + getDate() + ".apk"; releaseDir = APK_PATH + appName; } variant.outputFile = new File(releaseDir, newName); } }