How to get ABI name in Gradle outputs iterator - android

I'm composing .apk filename using current app version and flavor name. I'd like to add current ABI split name as well, but only if it's a universal apk.
My relevant build.gradle sections:
buildTypes {
release {
applicationVariants.all { variant ->
variant.outputs.each { output ->
def flavor = .... // some code to parse flavor & determine an appropriate string from it
output.outputFile = new File(output.outputFile.parent, "app_" + flavor + "_0" + variant.versionCode + ".apk")
}
}
}
}
productFlavors {
deploy {
splits {
abi {
enable true
reset()
include 'armeabi-v7a' //select ABIs to build APKs for
universalApk true //generate an additional APK that contains all the ABIs
}
}
}
}
Currently this config generates two .apks, but they both have the same file name as I don't know how to get the ABI name, so the one generated later overwrites the one generated before.
So, what is the equivalent variant.productFlavors.get(0) for current ABI split?

That's very strange as flavor and ABI-name is automatically added to build name (if you make corresponding assemble)
can you try completely remove your custom made naming
applicationVariants.all { variant ->
variant.outputs.each { output ->
def flavor = .... // some code to parse flavor & determine an appropriate string from it
output.outputFile = new File(output.outputFile.parent, "app_" + flavor + "_0" + variant.versionCode + ".apk")
}
}
and instead of that try to add to defaultConfig this line
archivesBaseName = "app_${versionCode}"
If this is will not resolve you issues you can try to get abi from output
output.getFilter(com.android.build.OutputFile.ABI)

The equivalent is output.getFilter(com.android.build.OutputFile.ABI).
Note that, starting with Android Studio 3.0, you should use outputFileName and variant.outputs.all instead:
applicationVariants.all { variant ->
variant.outputs.all { output ->
outputFileName = "app_" + output.getFilter(com.android.build.OutputFile.ABI) + "_0" + variant.versionCode + ".apk"
}
}

Related

Setting 'buildNumber' and 'versionNumber' while building APK from gradle

I'm building APKs from command line as follows
./gradlew assembleProductionRelease
Are there any configuration params which will allow to add buildNumber and versionNumber to APK file name automatically? By default APK file will be named as app-production-release.
You can try/adapt this code (add it to your build.gradle):
applicationVariants.all { variant ->
variant.outputs.all { output ->
def sep = '_'
def version = variant.versionName
def build = variant.versionCode
outputFileName = "${rootProject.name}${sep}" +
"${variant.buildType.name}${sep}" +
"${version}${sep}" +
"build${sep}${build}.apk"
}
}

Android. Gradle. How to get flavor property in task

I have an Android Gradle project with several flavors (different styles of the same app). I have a task that downloads some external file and puts it into assets folder. I need to specify an array of strings as a part of each flavor that (urls or filenames) that will be downloaded during the build of a specific flavor.
```
applicationVariants.all { variant ->
if (variant.buildType.name == 'release') {
variant.outputs.all {
def currentProductFlavor = variant.productFlavors.name.get(0)
def apkName = rootProject.ext.app.appName + currentProductFlavor + "_" + rootProject.ext.app.appVersionName + ".apk"
println("======apkName:" + apkName)
outputFileName = apkName
}
}
}
```

Android Studio Gradle specifying output directory specific to Flavor

I have implemented Flavors in Android Studio and am now trying to place each Flavor in it's own directory, with its own unique name - sadly with a name different, in some cases, than the flavor name. :(
We have tools depending on it being the same, so if I can pull that off in gradle, all the better.
I have a sample that is using the version name suffix value as the directory name and that works. But what I would like to do is specify a value somewhere in the flavor config that would be used, however I find that when you set a property with the same name the last one wins - rather than each being used as specified in the config.
So, for example, lets say I have two Flavors : Jimbo and Randolph. However I want to place the Jimbo.apk in the "jimmy" folder and the Randolph.apk in the "randy" folder. How can I specify a value (directory) for each that will be picked up and used to store the generated APK? To add to the complexity I am renaming the APK current in the applicationVariants.all .
In the code below I am looking to somehow replace the versionNameSuffix with a variable I can somehow specify.
Here is what I have:
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
defaultConfig {
applicationId "com.mycompany.default"
minSdkVersion 14
targetSdkVersion 23
versionCode 11
versionName "1.0.11"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
signingConfig signingConfigs.config
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
productFlavors {
Randolph {
applicationId 'com.mycompany.randy'
versionNameSuffix 'randy'
}
Jimbo {
applicationId 'com.mycompany.jimmy'
versionNameSuffix 'jimmy'
}
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def path = "C:/AndroidBuilds/MyCompany.Build/" + variant.productFlavors[0].versionNameSuffix + "/"
logger.error("Path = " + path)
def SEP = "-"
def flavor = variant.productFlavors[0].name
def version = variant.versionCode
def newApkName = path + version + SEP + flavor
logger.error("newApkName = " + newApkName)
output.outputFile = new File(newApkName + ".apk")
}
}
}
dependencies {
}
}
UPDATE
Per the question of using a task, I tried this approach but the problem of setting the directory remains - using a property (archivesBaseName) the last one set is used so all the files are copied to that directory. Here is a sample of that. Since I have upwards of 100 flavors to create I want each sent to it's own directory and config driven. Here is what I tried:
productFlavors {
Randolph {
applicationId 'com.mycompany.randy'
setProperty("archivesBaseName", "randy")
}
Jimbo {
applicationId 'com.mycompany.jimmy'
setProperty("archivesBaseName", "jimmy")
}
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def path = "C:/AndroidBuilds/MyCompany.Build/" + archivesBaseName + "/"
logger.error("Path = " + path)
def SEP = "-"
def flavor = variant.productFlavors[0].name
def version = variant.versionCode
def newApkName = path + version + SEP + flavor
logger.error("newApkName = " + newApkName)
output.outputFile = new File(newApkName + ".apk")
def copyApkTask = tasks.create(name: "copy" + variant.name + "Apk") {
copy {
def newName = newApkName + ".apk"
logger.error("from = " + newName)
logger.error("into = " + path)
logger.error("old name = " + version + SEP + flavor + ".apk")
logger.error("new name = " + flavor + ".apk")
from newName
into path
rename (version + SEP + flavor + ".apk", flavor + ".apk")
}
}
copyApkTask.mustRunAfter variant.assemble
}
}
In the example above I added a task to additionally copy the APK with different name to a flavor specific directory. All the APKs end up copied to the last specified `archivesBaseName, which is "jimmy". So last one wins. I was hoping it would act like a variable. I would prefer not to have to have 100+ if statements to do this and would prefer to do this in Gradle. I am starting to wonder if I will need to make an external Ant call to make this all work.
Ok, in the end this specific link REALLY helped on the variable assignment which is what I needed:
Android Studio: Gradle Product Flavors: Define custom properties
Basically you can assign variables within the flavor. Here is what I ended up doing, which actually went a bit further than when I started, since now I can use the Flavor as the APK name or specify one (I know, it is messed up, but history can be that way!):
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
defaultConfig {
applicationId "com.mycompany.default"
minSdkVersion 14
targetSdkVersion 23
versionCode 11
versionName "1.0.11"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
signingConfig signingConfigs.config
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
productFlavors.whenObjectAdded { flavor ->
// Add the property 'myCustomProperty' to each product flavor and set the default value to 'customPropertyValue'
flavor.ext.set('directoryPath', '')
flavor.ext.set('apkName', '')
}
productFlavors {
Randolph {
applicationId 'com.mycompany.randy'
directoryPath = 'randy'
apkName = 'RandyBoy' // If you want the APK name be different than the flavor
}
Jimbo {
applicationId 'com.mycompany.jimmy'
directoryPath = 'jimmy'
}
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def path = "C:/AndroidBuilds/MyCompany.Build/" + variant.productFlavors[0].directoryPath + "/"
def SEP = "-"
def apkName = variant.productFlavors[0].apkName
def flavor = variant.productFlavors[0].name
if (apkName != '')
flavor = apkName;
def version = variant.versionCode
def newApkName = path + version + SEP + flavor
logger.error("newApkName = " + newApkName)
output.outputFile = new File(newApkName + ".apk")
}
}
}
dependencies {
}
}
So productFlavors.whenObjectAdded sets the default values for each flavor, which are then overridden by each flavor. In the applicationVariants.all a check is made to see if the apkName has been overridden, if so it uses it, otherwise it uses the flavor name (and the version code is tacked in front of it). The directory is set directly by the flavor.
Big thanks to #lionscribe. He got me thinking this thru more clearly.
The problem is that setProperty is setting the Project property, so it is always being overwritten. A simple solution is to rather use the Varients Extra Property. Something like this.
productFlavors {
Randolph {
applicationId 'com.mycompany.randy'
ext.archivesBaseName = "randy" }
Jimbo {
applicationId 'com.mycompany.jimmy'
ext.archivesBaseName=jimmy" }
}
Then you will access it in the task as
def path = "C:/AndroidBuilds/MyCompany.Build/" + variant.ext.archivesBaseName + "/"
I haven't tested it, and it may have a bug, and need some tweaking.
Update
This is not enough, as Gradle will set to the ext property of the flavor object, only if it is defined in the flavor object. Otherwise it will set it in the parent or root object, which is the project. So for this to work, we first have to define the property in the flavor object. This can be done as #Stephen has answered below. Follow his tested method.
There are 3 more options:
1. Use a different variable name for each flavir, by pre-pending the flavor name, like "Jimbo_archivesBaseName". Then access it using property(flavorName + "_archivesBaseName);
2. Use a global HashMap variable, setting a path for each flavor name.
3. Using a function, that returns a path based on flavor name.

ABI Split failed in NativeScript 2.3.0

I’ve asked for help in the general group but most people didnt get it work as well.
Im having a huge problem ridiculous apk size of my simple app. I used #markosko nativescript filter to reduce the app to 14MB from 17.2MB, even nativescript-snapshot couldn't help still 17MB for release version.
I tried using the ABI split sample in the nativescipt documentation but what I noticed is that it’s trying to split but the glade is using same name for all the apks so I came up with this in my app.glade
def tnsBuildMultipleApks=true;
android {
defaultConfig {
generatedDensities = []
applicationId = "com.maliyo.oneclick"
versionCode Integer.parseInt("" + "1" + "0")
}
aaptOptions {
additionalParameters "--no-version-vectors"
}
if (Boolean.valueOf(tnsBuildMultipleApks)) {
splits {
abi {
enable true
reset()
include 'armeabi', 'armeabi-v7a', 'x86', 'mips'
universalApk true
}
}
}
}
def getDate() {
def date = new Date()
def formattedDate = date.format('yyyyMMdd')
return formattedDate
}
// map for the version code that gives each ABI a value
ext.versionCodes = [
'F0F1F2X86Debug':1, 'F0F1F2ArmeabiDebug':2, 'F0F1F2Armeabi-v7aDebug':3, 'F0F1F2MipsDebug':4,
'F0F1F2X86Release':1, 'F0F1F2ArmeabiRelease':2, 'F0F1F2Armeabi-v7aRelease':3, 'F0F1F2MipsRelease':4
]
// For each APK output variant, override versionCode with a combination of
// ABI APK value * 100 + defaultConfig.versionCode
android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
println("******************************************************")
println(output);
println(output.getVariantOutputData().getFullName())
if (Boolean.valueOf(tnsBuildMultipleApks)) {
def file = output.outputFile
// version at the end of each built apk
//output.outputFile = new File(file.parent, file.name.replace(".apk", "-" + android.defaultConfig.versionName + "-" + getDate() + ".apk"))
output.outputFile = new File(file.parent, file.name.replace(".apk", "-" + output.getVariantOutputData().getFullName() + "-" + getDate() + ".apk"))
output.versionCodeOverride =
//(assd++) * 100
project.ext.versionCodes.get(output.getVariantOutputData().getFullName(), 0) * 100
+ android.defaultConfig.versionCode
}
}
}
}
/**/
Fine, it splits but I think because I hacked the filename at output the adb couldn't find one to push the apk to the device or emulator due to the naming pattern, maybe, just saying apk not found.
I tried to manually send the appropriate apk to the device via USB, it app installed successfully but it crashes after splashscreen saying metadata/treeNodeStream.dat could not be loaded
UPDATE
#plamen-petkov thanks so much for your contribution, I agree with you that it work fine, when you build one after another changing the abi filter. But with this in my app.gradle, I managed to build multiple apks successfully and tested and OK.
but is like the the tns is only pushing appname-debug.apk or appname-release.apk to the adb. I can toggle this splitting off with tnsBuildMultipleApks and maybe when Im still testing I can turn it off and use tns run android and when I want to make final build and it turn it one again as it works fine with tns build android --release ....
// Add your native dependencies here:
// Uncomment to add recyclerview-v7 dependency
//dependencies {
// compile 'com.android.support:recyclerview-v7:+'
//}
import groovy.json.JsonSlurper //used to parse package.json
def tnsBuildMultipleApks=true;
String content = new File("$projectDir/../../app/package.json").getText("UTF-8")
def jsonSlurper = new JsonSlurper()
def appPackageJson = jsonSlurper.parseText(content)
android {
defaultConfig {
generatedDensities = []
applicationId = appPackageJson.nativescript.id
versionCode = appPackageJson.version_code ?: 1
}
aaptOptions {
additionalParameters "--no-version-vectors"
}
if (Boolean.valueOf(tnsBuildMultipleApks)) {
splits {
abi {
enable true
reset()
include 'x86', 'armeabi-v7a', 'arm64-v8a'
universalApk true
}
}
}
}
// map for the version code that gives each ABI a value
ext.versionCodes = [
'x86':1, 'armeabi-v7a':2, 'arm64-v8a':3
]
// For each APK output variant, override versionCode with a combination of
// ABI APK value * 100 + android.defaultConfig.versionCode
// getAbiFilter() not working for me so I extracted it from getFullname()
if (Boolean.valueOf(tnsBuildMultipleApks)) {
android.applicationVariants.all { variant ->
println(appPackageJson)
println(android.defaultConfig.versionCode)
println(android.defaultConfig.applicationId)
def name
def flavorNamesConcat = ""
variant.productFlavors.each() { flavor ->
flavorNamesConcat += flavor.name
}
flavorNamesConcat = flavorNamesConcat.toLowerCase()
println(flavorNamesConcat)
variant.outputs.each { output ->
if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
//You may look for this path in your console to see what the values are
println("******************************************************")
println(output); println(output.getVariantOutputData().getFullName())
def abiName = output.getVariantOutputData().getFullName().toLowerCase().replace(flavorNamesConcat, "").replace(project.ext.selectedBuildType, "")
println(abiName)
def file = output.outputFile
output.versionCodeOverride =
project.ext.versionCodes.get(abiName, 0) * 100
+ android.defaultConfig.versionCode
def apkDirectory = output.packageApplication.outputFile.parentFile
def apkNamePrefix = output.outputFile.name.replace(".apk", "-" + abiName)
if (output.zipAlign) {
name = apkNamePrefix + ".apk"
output.outputFile = new File(apkDirectory, name);
}
name = apkNamePrefix + "-unaligned.apk"
output.packageApplication.outputFile = new File(apkDirectory, name);
}
}
}
}
ABI splits can be useful if you use them one at a time. Here's an example:
android {
...
splits {
abi {
enable true
reset()
include 'armeabi-v7a'
}
}
...
}
The resulting .apk file will only contain the libraries necessary for armeabi-v7a devices, because it's the only architecture mentioned in the ABI splits configuration above. The ABIs available are 'arm64-v8a', 'armeabi-v7a', 'x86' as pointed out in the documentation, so you can't use 'armeabi' or 'mips' architectures.
Furthermore you don't need this line: 'universalApk true', because what it does is ignoring the splits and making one .apk file containing all the provided architectures and you want the opposite.
You can also follow the progress of this issue as it will decrease the .apk size even more.
Hope this helps!
This work so well for me now, both generating apks and tns run android runs fine now, thanks.
// Add your native dependencies here:
// Uncomment to add recyclerview-v7 dependency
//dependencies {
// compile 'com.android.support:recyclerview-v7:+'
//}
import groovy.json.JsonSlurper //used to parse package.json
def tnsBuildMultipleApks=true;
String content = new File("$projectDir/../../app/package.json").getText("UTF-8")
def jsonSlurper = new JsonSlurper()
def appPackageJson = jsonSlurper.parseText(content)
android {
defaultConfig {
generatedDensities = []
applicationId = appPackageJson.nativescript.id
versionCode = appPackageJson.version_code ?: 1
}
aaptOptions {
additionalParameters "--no-version-vectors"
}
if (Boolean.valueOf(tnsBuildMultipleApks)) {
splits {
abi {
enable true
reset()
include 'x86', 'armeabi-v7a', 'arm64-v8a'
universalApk true
}
}
}
}
// map for the version code that gives each ABI a value
ext.versionCodes = [
'x86':1, 'armeabi-v7a':2, 'arm64-v8a':3
]
// For each APK output variant, override versionCode with a combination of
// ABI APK value * 100 + android.defaultConfig.versionCode
// getAbiFilter() not working for me so I extracted it from getFullname()
if (Boolean.valueOf(tnsBuildMultipleApks)) {
android.applicationVariants.all { variant ->
println(appPackageJson)
println(android.defaultConfig.versionCode)
println(android.defaultConfig.applicationId)
def name
def flavorNamesConcat = ""
variant.productFlavors.each() { flavor ->
flavorNamesConcat += flavor.name
}
flavorNamesConcat = flavorNamesConcat.toLowerCase()
println(flavorNamesConcat)
variant.outputs.each { output ->
if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
//You may look for this path in your console to see what the values are
println("******************************************************")
println(output); println(output.getVariantOutputData().getFullName())
def abiName = output.getVariantOutputData().getFullName().toLowerCase().replace(flavorNamesConcat, "").replace(project.ext.selectedBuildType, "")
println(abiName)
def file = output.outputFile
def versionCode = project.ext.versionCodes.get(abiName, 0);
output.versionCodeOverride =
project.ext.versionCodes.get(abiName, 0) * 100
+ android.defaultConfig.versionCode
def apkDirectory = output.packageApplication.outputFile.parentFile
println(output.outputFile.name)
def apkNamePrefix = ""
if(versionCode){
apkNamePrefix = output.outputFile.name.replace(".apk", "-" + abiName)
}
else {
apkNamePrefix = output.outputFile.name.replace(".apk", "")
}
if (output.zipAlign) {
name = apkNamePrefix + ".apk"
output.outputFile = new File(apkDirectory, name);
}
name = apkNamePrefix + "-unaligned.apk"
output.packageApplication.outputFile = new File(apkDirectory, name);
}
}
}
}

Android - Only execute Gradle task on release build variant

I am trying to configure my build.gradle file to only execute a gradle task when the release build variant is selected. So far, my task always gets executed, whether it is in my debug or release build types or signing configs. I have tried adding my task inside an applicationsVariants block and check if it is the release variant, but it just loops through all variants.
applicationVariants.all { variant ->
variant.outputs.each { output ->
...
}
}
I know that both the debug and release tasks always run for whichever build variant you choose. Is it possible to execute some code only when creating a build for release? If so, where does that code go? Thanks!
I have read through every Stackoverflow question on this, but none of the answers really did I am wanting. My end goal is when I select the "release" build variant for a Play Store build, a message is posted to our server. I do not want this to happen when just debugging.
Add doFirst or doLast for the build type you are interested in.
android.applicationVariants.all { variant ->
if ( variant.buildType.name == "release"){
variant.assemble.doLast { // Can also use doFirst here to run at the start.
logger.lifecycle("we have successfully built $v.name and can post a messaage to remote server")
}
}
}
I had to do something like this to check build version:
buildTypes {
applicationVariants.all { variant ->
variant.outputs.each {output ->
def project = "AppName"
def separator = "_"
/*def flavor = variant.productFlavors[0].name*/
def buildType = variant.variantData.variantConfiguration.buildType.name
def versionName = variant.versionName
def versionCode = variant.versionCode
def date = new Date();
def formattedDate = date.format('yyyyMMdd_HHmm')
if (variant.buildType.name == "release"){
def newApkName = project + separator + "v" + versionName + separator + versionCode + separator + buildType + separator + formattedDate + ".apk"
output.outputFile = new File(output.outputFile.parent, newApkName)
}
if (variant.buildType.name == "debug"){
def newApkName = project + separator + "v" + versionName + separator + versionCode + separator + buildType + ".apk"
output.outputFile = new File(output.outputFile.parent, newApkName)
}
}
} }

Categories

Resources