I am using this code in build.gradle:
android {
applicationVariants.all { variant ->
variant.packageApplicationProvider.configure { packageApplicationTask ->
doLast {
packageApplicationTask.apkNames.each { apkName ->
def apkDir = "./build/outputs/apk/${flavorName}/${buildType.name}"
def apkDestName = apkName.replace("app-", "stickerly-android-" + variant.versionName + "-").replace(".apk", "-" + getDate() + ".apk")
println "#####Rename ${variant.name} Apk File"
copy {
from("$apkDir/$apkName").into(apkDir).rename { String fileName -> apkDestName }
}
println "#####Copy mapping File"
def mappingDir = "./build/outputs/mapping/${flavorName}${buildType.name.capitalize()}"
copy {
from("$mappingDir/mapping.txt").into(mappingDir).rename {
"mapping-stickerly-${variant.versionName}.txt"
}
}
}
}
}
}
}
With this code I rename apk file and copy mapping file. I worked in android gradle plugin 4.0, but it does not work in 4.1 with this message
Where:
Script '/Users/snow/workspace/stickerly-android/app/build-android-extra.gradle' line: 5
What went wrong:
Execution failed for task ':app:packageExternalArm8Debug'.
Could not get unknown property 'apkNames' for task ':app:packageExternalArm8Debug' of type com.android.build.gradle.tasks.PackageApplication.
I think API has changed but I can not find any documents. Someone can help me?
Thanks.
property apkNames is removed in AGP 4.1
you can try this
gradle.taskGraph.afterTask { Task task, TaskState state ->
if (!state.failure
&& task.project.name == project.name
&& task.name.startsWith("package")
&& !task.name.endsWith("UnitTest")) {
def outputDir = task.outputDirectory.getAsFile().get()
task.variantOutputs.getOrNull()?.each { variant ->
println "" + outputDir + "/" + variant.outputFileName.get()
}
}
}
add this at the end of your build.gradle file.
change println to whatever logic you want.
by the way, if you want to check the properties you may use, just add gradle plugin as a dependency of your project, click Sync in Android Stuido, then you can find it in External Librarys (key map: Command + 1, and switch to project view).
like this
dependencies {
implementation 'com.android.tools.build:gradle:4.1.0'
}
and these properties and tasks are intentionally invisible in lib com.android.tools.build:gradle-api, modifications in future releases are expected.
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.
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"
}
}
}
It's necessary to have mapping.txt file to check crashes come from your app (because of ProGuard), in many cases developers forget to copy mapping file and back it up and after next release it will be changed and useless to check previous version bugs.
how to copy mapping file after release and copy version as suffix to it's name in particular path using gradle task automatically?
This is the snippet I use. It does depend on having a productFlavor defined but that is only to help name the file and allow the same snippet to be reused in multiple projects without modification but that dependency could be refactored out if you wanted a different filename format.
As it stands, the apk and the mapping file (if required) will be copied to the defined basePath in the format:
FilePath\appname\appname buildtype versionname (versioncode)
e.g
A:\Common\Apk\MyAppName\MyAppName release 1.0 (1).apk
and
A:\Common\Apk\MyAppName\MyAppName release 1.0 (1).mapping
Amend as you see fit.
android {
productFlavors {
MyAppName {
}
}
//region [ Copy APK and Proguard mapping file to archive location ]
def basePath = "A:\\Common\\Apk\\"
applicationVariants.all { variant ->
variant.outputs.each { output ->
// Ensure the output folder exists
def outputPathName = basePath + variant.productFlavors[0].name
def outputFolder = new File(outputPathName)
if (!outputFolder.exists()) {
outputFolder.mkdirs()
}
// set the base filename
def newName = variant.productFlavors[0].name + " " + variant.buildType.name + " " + defaultConfig.versionName + " (" + defaultConfig.versionCode + ")"
// The location that the mapping file will be copied to
def mappingPath = outputPathName + "\\" + newName + ".mapping"
// delete any existing mapping file
if (file(mappingPath).exists()) {
delete mappingPath
}
// Copy the mapping file if Proguard is turned on for this build
if (variant.getBuildType().isMinifyEnabled()) {
variant.assemble.doLast {
copy {
from variant.mappingFile
into output.outputFile.parent
rename { String fileName ->
newName + ".mapping"
}
}
}
}
// Set the filename and path that the apk will be created at
if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
def path = outputPathName + "\\" + newName + ".apk"
if (file(path).exists()) {
delete path
}
output.outputFile = new File(path)
}
}
}
//endregion
}
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);
}
}