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"
}
}
}
Related
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 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
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
}
How to add date/time stamp to Gradle Android output file name?
Should be like project_v0.5.0_201503110212_public.apk
Already looked at
how append date build to versionNameSuffix on gradle
How to pass arguments from command line to gradle
I'm assuming that you want it in the format you specified, so here's one possible solution.
In your gradle file you can define a new function to get the date time string like you desire:
import java.text.DateFormat
import java.text.SimpleDateFormat
def getDateTime() {
DateFormat df = new SimpleDateFormat("yyyyMMddHHmm");
return df.format(new Date());
}
Then for all variants you can simply run this:
android {
//...
buildTypes {
//...
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def file = output.outputFile
output.outputFile = new File(file.parent, file.name.replace(".apk", "-" + getDateTime() + ".apk"))
}
}
}
}
Note that this doesn't really output the apk name like you posted, but I guess it's enough to help you out.
This code working for me.
applicationVariants.all { variant ->
variant.outputs.each { output ->
def project = "Your App Name"
def SEP = "_"
def flavor = variant.productFlavors[0].name
def buildType = variant.variantData.variantConfiguration.buildType.name
def version = variant.versionName
def date = new Date();
def formattedDate = date.format('ddMMyy_HHmm')
def newApkName = project + SEP + flavor + SEP + buildType + SEP + version + SEP + formattedDate + ".apk"
output.outputFile = new File(output.outputFile.parent, newApkName)
}
}
I also add a formatted date to my build. In first place I used some kind of "now" with new Date(), but this leads to trouble when starting the build with Android Studio, like in one of the comments above. I decided to use the timestamp of the latest commit. I found some inspiration here: https://jdpgrailsdev.github.io/blog/2014/10/14/spring_boot_gradle_git_info.html
Adding the timestamp is handled like below:
def getLatestCommitTimeStamp() {
def revision = 'git rev-list --max-count 1 --timestamp HEAD'.execute().text.trim()
def gitCommitMillis = java.util.concurrent.TimeUnit.SECONDS.toMillis(revision.split(' ').first() as long)
return new Date(gitCommitMillis).format("_HH.mm.ss_dd-MM-yyyy", TimeZone.getTimeZone('Europe/Berlin'))
}
My renaming part looks like this:
android.applicationVariants.all { variant ->
if (variant.buildType.name == 'release') {
def lastCommitFormattedDate = getLatestCommitTimeStamp()
variant.outputs.each { output ->
def alignedOutputFile = output.outputFile
def unalignedOutputFile = output.packageApplication.outputFile
// Customise APK filenames (to include build version)
if (variant.buildType.zipAlignEnabled) {
// normal APK
output.outputFile = new File(alignedOutputFile.parent, alignedOutputFile.name.replace(".apk", "-v" + defaultConfig.versionName + "-" + variant.buildType.name.toUpperCase() + "-${gitSha}" + lastCommitFormattedDate + ".apk").replace("-" + variant.buildType.name, "").replace(project.name, "otherName"))
}
// 'unaligned' APK
output.packageApplication.outputFile = new File(unalignedOutputFile.parent, unalignedOutputFile.name.replace(".apk", "-v" + defaultConfig.versionName + "-" + variant.buildType.name.toUpperCase() + "-${gitSha}" + lastCommitFormattedDate + ".apk").replace("-" + variant.buildType.name, "").replace(project.name, "otherName"))
}
}
An alternative solution is to set $dateTime property in defaultConfig as shown below:
defaultConfig {
setProperty("archivesBaseName", "Appname-$dateTime-v$versionName")
}
This is mine hope to help you
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def file = output.outputFile
output.outputFile = new File(
(String) file.parent,
(String) file.name.replace(
file.name,
// alter this string to change output file name
"APigLive_Android_" + variant.name + "_" + variant.versionName + "_" + releaseTime() + ".apk"
)
)
}
}
def releaseTime(){
return new Date().format("MM:dd:HH:mm", TimeZone.getTimeZone("GMT"))
}
You can just add the code below inside the defaultConfig section located in android section.
setProperty("archivesBaseName", "yourData-$versionName " + (new Date().format("HH-mm-ss")))
Inspired by enter link description here
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);
}
}