I'm trying to add a custom task to my Android project's build.gradle
to copy the final APK and Proguard's mapping.txt
into a different directory. My task depends on the assembleDevDebug
task:
task publish(dependsOn: 'assembleDevDebug') << {
description 'Copies the final APK to the release directory.'
...
}
I can see how to do a file copy using the standard Copy
task type, as per the docs:
task(copy, type: Copy) {
from(file('srcDir'))
into(buildDir)
}
but that assumes you know the name and location of the file you want to copy.
How can I find the exact name and location of the APK file which was built as part of the assembleDevDebug
task? Is this available as a property? It feels as if I should be able to declare the files as inputs to my task, and declare them as outputs from the assemble
task, but my Gradle-fu isn't strong enough.
I have some custom logic to inject the version number into the APK filename, so my publish
task can't just assume the default name and location.
If you can get the variant object associated with devDebug you could query it with getOutputFile().
So if you wanted to publish all variants you'd something like this:
def publish = project.tasks.create("publishAll")
android.applicationVariants.all { variant ->
def task = project.tasks.create("publish${variant.name}Apk", Copy)
task.from(variant.outputFile)
task.into(buildDir)
task.dependsOn variant.assemble
publish.dependsOn task
}
Now you can call gradle publishAll
and it'll publish all you variants.
One issue with the mapping file is that the Proguard task doesn't give you a getter to the file location, so you cannot currently query it. I'm hoping to get this fixed.
The following code is what I'm using to archive apk and proguard mapping into a zip file for each variant with 'release' build type:
def releasePath = file("${rootDir}/archive/${project.name}")
def releaseTask = tasks.create(name: 'release') {
group 'Build'
description "Assembles and archives all Release builds"
}
android.applicationVariants.all { variant ->
if (variant.buildType.name == 'release') {
def build = variant.name.capitalize()
def releaseBuildTask = tasks.create(name: "release${build}", type: Zip) {
group 'Build'
description "Assembles and archives apk and its proguard mapping for the $build build"
destinationDir releasePath
baseName variant.packageName
if (!variant.buildType.packageNameSuffix) {
appendix variant.buildType.name
}
if (variant.versionName) {
version "${variant.versionName}_${variant.versionCode}"
} else {
version "$variant.versionCode"
}
def archiveBaseName = archiveName.replaceFirst(/\.${extension}$/, '')
from(variant.outputFile.path) {
rename '.*', "${archiveBaseName}.apk"
}
if (variant.buildType.runProguard) {
from(variant.processResources.proguardOutputFile.parent) {
include 'mapping.txt'
rename '(.*)', "${archiveBaseName}-proguard_\$1"
}
}
}
releaseBuildTask.dependsOn variant.assemble
variant.productFlavors.each { flavor ->
def flavorName = flavor.name.capitalize()
def releaseFlavorTaskName = "release${flavorName}"
def releaseFlavorTask
if (tasks.findByName(releaseFlavorTaskName)) {
releaseFlavorTask = tasks[releaseFlavorTaskName]
} else {
releaseFlavorTask = tasks.create(name: releaseFlavorTaskName) {
group 'Build'
description "Assembles and archives all Release builds for flavor $flavorName"
}
releaseTask.dependsOn releaseFlavorTask
}
releaseFlavorTask.dependsOn releaseBuildTask
}
}
}
It creates tasks like the following:
- release - Assembles and archives all Release builds
- releaseFree - Assembles and archives all Release builds for flavor Free
- releaseFreeRelease - Assembles and archives apk and its proguard mapping for the FreeRelease build
- releasePaid - Assembles and archives all Release builds for flavor Paid
- releasePaidRelease - Assembles and archives apk and its proguard mapping for the PaidRelease build
Content of archive/projectName/packageName-buildType-versionName_versionCode.zip would be:
- packageName-buildType-versionName_versionCode.apk
- packageName-buildType-versionName_versionCode-proguard_mapping.txt
This is how I copy mappings.txt whenever proguard runs
tasks.whenTaskAdded { task ->
if (task.name.startsWith("proguard")) {//copy proguard mappings
task << {
copy {
from buildDir.getPath() + "/proguard"
into '../proguard'
include '**/mapping.txt'
}
println "PROGUARD FILES COPIED"
}
}
}
I've got some good pointers here but also had a hard time to get done as I wanted. Here's my final version:
def archiveBuildTypes = ["distribute"];
def archiveFlavors = ["googleplay"]
android.applicationVariants.all { variant ->
if (variant.buildType.name in archiveBuildTypes) {
variant.productFlavors.each { flavor ->
if (flavor.name in archiveFlavors) {
def taskSuffix = variant.name.capitalize()
def version = "${android.defaultConfig.versionCode} (${android.defaultConfig.versionName})" // assumes that versionName was especified here instead of AndroidManifest.xml
def destination = "${rootDir}/${project.name}/archive/${version}"
def assembleTaskName = "assemble${taskSuffix}"
if (tasks.findByName(assembleTaskName)) {
def copyAPKTask = tasks.create(name: "archive${taskSuffix}", type:org.gradle.api.tasks.Copy) {
description "Archive/copy APK and mappings.txt to a versioned folder."
from ("${buildDir}") {
include "**/proguard/${flavor.name}/${variant.buildType.name}/mapping.txt"
include "**/apk/${variant.outputFile.name}"
}
into destination
eachFile { file->
file.path = file.name // so we have a "flat" copy
}
includeEmptyDirs = false
}
tasks[assembleTaskName].finalizedBy = [copyAPKTask]
}
}
}
}
}
def publish = project.tasks.create("publishAll")// publish all task
applicationVariants.all { variant ->
if (variant.buildType.name.equals("release")) {// Only Release
File outDir = file("//192.168.4.11/Android/Release")
File apkFile = variant.outputs[0].outputFile
File mapFile = variant.mappingFile
def task = project.tasks.create("publish${variant.name.capitalize()}Apk", Copy)
task.from apkFile, mapFile
task.into outDir
task.rename "mapping.txt", "${apkFile.name.substring(0, apkFile.name.length() - 3)}mapping.txt"// Rename mapping.txt
task.doLast{
println ">>>publish ${variant.name} success!" +
"\ndir: ${outDir}" +
"\napk: ${apkFile.name}"
}
task.dependsOn variant.assemble
publish.dependsOn task
}
}
Usually the android plugin will put the apks in the APP/build/apk directory.
So, run assembleDebug
then ls APP/build/apk
and you should see:
- APP-debug-unaligned.apk
- APP-release-unaligned.apk
- APP-release-unsigned.apk
- APP-release.apk
etc