Set noCompress for only one productFlavor - android

I'm trying to set androidResources.noCompress for only one product flavor
When I try this
flavorDimensions "ring"
productFlavors {
innerRing {
dimension "ring"
}
outerRing {
dimension "ring"
androidResources {
noCompress 'so'
}
}
}
Both innerRing and outerRing end up with .so files uncompressed. I believe this is due to gradle configuring all the product flavors statically. (see here)
But when I try to change noCompress afterEvaluate
afterEvaluate {
android.applicationVariants.all { variant ->
def ring = variant.getProductFlavors().get(0).name
if (ring == "outerRing") {
println("Don't compress .so files for outer ring build")
android.androidResources.noCompress = ['so']
}
}
}
I get this error
com.android.build.gradle.internal.dsl.AgpDslLockedException:
It is too late to modify internalNoCompressList
It has already been read to configure this project.
Consider either moving this call to be during evaluation,
or using the variant API.
How can I use the variant API to fix this?
Any help is appreciated!

If you are on AGP 7.2
android {
...
androidComponents {
onVariants(selector().all()) { variant ->
if(variant.name.startsWith("outerRing")) {
variant.androidResources.noCompress.add('so')
}
}
}
...
}
If AGP is below 7.2 then from the documentation theoretically the following code should work, but I've had no success with it
android {
...
androidComponents {
onVariants(selector().all()) { variant ->
if(variant.name.startsWith("outerRing")) {
def aaptParams = variant.androidResources.aaptAdditionalParameters
aaptParams.add("-0")
aaptParams.add("so")
}
}
}
...
}

Related

Android - How to NOT use some dependencies for a specified flavor?

For example, There is a dependency ZZZ, and 6 flavors flavor1, flavor2, ... , flavor6.
If only want flavor1 add the dependency, I can use this:
dependencies {
flavor1Implementation `ZZZ`
}
But if I want all flavors except flavor1 to add this dependency, like this:
dependencies {
flavor2Implementation `ZZZ`
flavor3Implementation `ZZZ`
flavor4Implementation `ZZZ`
flavor5Implementation `ZZZ`
flavor6Implementation `ZZZ`
}
It's a disaster when I have many flavors...
Is there a better way to do this?
One option will be to use variantFilter, for instance:
android {
productFlavors {
flavor1 {
}
flavor2 {
}
flavor3 {
}
...
}
variantFilter { variant ->
if (!variant.flavors.name.contains("flavor1")) {
var nameVariant = variant.flavors.name[0]
getDependencies().add("${nameVariant}Implementation", "ZZZ")
}
}
}
With variantFilter you can loop through each combination between buildTypes and productFlavors, avoiding to add dependencies on flavor1 and add the required dependencies programmatically in all the others.
Hope this helps

Android studio flavor dimensions processed all, not selected one

I have a flavor dimensions in module build.gradle file, and gradle build process runs through all build variants whatever actual build variant is. Here is module build.gradle:
flavorDimensions 'type', 'jnitype'
productFlavors {
demo {
dimension 'type'
versionNameSuffix '.demo'
}
production {
dimension 'type'
versionNameSuffix '.production'
}
usejni {
dimension 'jnitype'
versionNameSuffix '.usejni'
copy {
from('../jnilib/data') {
include 'sdk_data.gpu'
....
}
into 'src/main/assets/data'
}
}
nojni {
dimension 'jnitype'
versionNameSuffix '.nojni'
delete('src/main/assets/data/*.*')
packagingOptions {
exclude 'lib/arm64-v8a/sdk.so'
...
}
}
}
So no matter what build variant selected, demoUsejni or demoNojni, gradle runs through 'usejni' and then 'nojni' variants - it copies files and libraries, and then deletes them. I used gradle debug to confirm this.
How can I tell gradle to use just a selected build flavor?
AS 3.5.2, gradle plugin 5.4.1, android build tools 3.5.2.
Finally, the solution is found: use gradle task name to check for required flavor. For an action at compile type gradle task name will be ":module_name:assembleFlavor1Flavor2...FlavornBuildtype". So i will check for "assemble" and "Nojni"/"Usejni".
String taskName = "";
if (gradle.startParameter.taskNames.size > 0)
taskName = gradle.startParameter.taskNames.get(0)
usejni {
dimension 'jnitype'
versionNameSuffix '.usejni'
if (taskName.contains("Usejni") && taskName.contains("assemble")) {
copy {
from('../jnilib/data') {
include 'sdk_data.gpu'
....
}
into 'src/main/assets/data'
}
}
}
nojni {
dimension 'jnitype'
versionNameSuffix '.nojni'
if (taskName.contains("Nojni") && taskName.contains("assemble")) {
delete('src/main/assets/data/*.*')
packagingOptions {
exclude 'lib/arm64-v8a/sdk.so'
...
}
}
}
Like a charm!
Hint for solution found in this answer: How to get current buildType in Android Gradle configuration

How to set different applicationId for each flavor combination using flavorDimensions?

I have and old android app that I am trying to migrate to the android gradle build system. The app is currently built in a multi project setup and published as four different apps (two different data sets included and free/paid versions for both datasets). I have managed to get away from the multi project setup by using flavorDimensions (previously called flavorGroups), but I can not figure out how to set a different applicationId for each flavor combination.
Since the app versions are already published, I need to keep the same applicationid as they currently have. Because of how my original package naming was done, I can not simply use flavor-buildtype combination with "packageNameSuffix" (which would have been a great option if it was an unpublished app).
https://stackoverflow.com/a/20956353/4177090 is answering how to use different source folders for flavor combinations, but not how (if even possible) to set specific configuration for each combination in the build file.
Gradle build file snippet:
flavorDimensions "price", "dataset"
productFlavors {
free { flavorDimension "price" }
paid { flavorDimension "price" }
dataset1 { flavorDimension "dataset" }
dataset2 { flavorDimension "dataset" }
}
I would like to have something like the following in my gradle build file (notice how unlogic my naming is, which is why I cannot use packageNameSuffix):
freeDataset1 { applicationId "com.beansys.freeappdataset1" }
freeDataset2 { applicationId "com.beansys.freedataset2" }
paidDataset1 { applicationId "com.beansys.dataset1paid" }
paidDataset2 { applicationId "com.beansys.mypaiddataset2" }
The solution proposed by Fredrik stopped working after upgrading Android Studio to 1.0.2 (and gradle plugin to 1.0.0) so I had to add following changes, current as of gradle plugin 1.3.1:
flavorDimensions "price", "dataset"
productFlavors {
free { dimension "price" }
paid { dimension "price" }
dataset1 { dimension "dataset" }
dataset2 { dimension "dataset" }
}
android.applicationVariants.all { variant ->
def mergedFlavor = variant.mergedFlavor
switch (variant.flavorName) {
case "freeDataset1":
mergedFlavor.setApplicationId("com.beansys.freeappdataset1")
break
case "freeDataset2":
mergedFlavor.setApplicationId("com.beansys.freedataset2")
break
case "paidDataset1":
mergedFlavor.setApplicationId("com.beansys.dataset1paid")
break
case "paidDataset2":
mergedFlavor.setApplicationId("com.beansys.mypaiddataset2")
break
}
}
I finally managed to solve this. I think the solution is elegant (although the actual code could most likely be written a lot nicer by someone with groovy knowledge).
Solution for setting a specific applicationId for each combined flavor:
flavorDimensions "price", "dataset"
productFlavors {
free { flavorDimension "price" }
paid { flavorDimension "price" }
dataset1 { flavorDimension "dataset" }
dataset2 { flavorDimension "dataset" }
}
android.variantFilter { variant ->
def flavorString = ""
def flavors = variant.getFlavors()
for (int i = 0; i < flavors.size(); i++) {
flavorString += flavors[i].name;
}
if(flavorString.equalsIgnoreCase("freeDataset1")) {
variant.getDefaultConfig().applicationId "com.beansys.freeappdataset1"
}
if(flavorString.equalsIgnoreCase("freeDataset2")) {
variant.getDefaultConfig().applicationId "com.beansys.freedataset2"
}
if(flavorString.equalsIgnoreCase("paidDataset1")) {
variant.getDefaultConfig().applicationId "com.beansys.dataset1paid"
}
if(flavorString.equalsIgnoreCase("paidDataset2")) {
variant.getDefaultConfig().applicationId "com.beansys.mypaiddataset2"
}
}
I had problems converting the answers given to the gradle kotlin dsl. I found a solution and shared it here: https://stackoverflow.com/a/60103604/2793394
With AGP 7.0, you should use onVariants:
androidComponents {
onVariants(selector().all(), { variant ->
switch (variant.name) {
case "freeDataset1":
variant.applicationId = "com.beansys.freeappdataset1"
break
case "freeDataset2":
variant.setApplicationId = "com.beansys.freedataset2"
break
...
...
...
}
})
}
You can even pass your custom "selector" and do something for example for only a specific variant:
androidComponents {
onVariants(selector().withName("paidDataset1"), { variant ->
variant.applicationId = "com.beansys.dataset1paid"
})
}

How to get current buildType in Android Gradle configuration

I want to dynamically add a dependency in an Android Gradle project based on the current buildType. I know I can specify the buildType in the dependency:
compile project(path: ':lib1', configuration: 'debug')
But how can I use the current buildType to specify which variant of the library I want to import, so that a debug or release build automatically imports the debug or release variant of the library? What I want is something like this (where currentBuildType is a variable containing the name of the currently used buildType):
compile project(path: ':lib1', configuration: currentBuildType)
The library project I want to import has set publishNonDefault true, so all buildTypes are published.
You can use
if (gradle.startParameter.taskNames.contains("assembleExample")) {
// do stuff
}
That variable will be set before the buildConfig block is evaluated
I could not find a clean way to get the current build type during the configuration phase of Gradle. Instead I define the dependency for each build type separately like that:
debugCompile project(path: ':lib1', configuration: 'debug')
releaseCompile project(path: ':lib1', configuration: 'release')
If you have many build types and many project dependencies this can get very verbose, but it is possible to add a function to make the dependency a one-liner. You need to add this to your main Gradle build file:
subprojects {
android {
dependencies.metaClass.allCompile { dependency ->
buildTypes.each { buildType ->
"${buildType.name}Compile" project(path: ":${dependency.name}", configuration: buildType.name)
}
}
}
}
Then you can add project dependencies in your Gradle modules like this:
allCompile project(':lib1')
If you also use build flavors you would have to adapt the solution. See this link for a documentation of the feature:
http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Library-Publication
Please note that the Android team is working on an improvement for this behaviour:
https://code.google.com/p/android/issues/detail?id=52962
Add a task which depends on each assembleXxx task and property setting up after it invoked
ext {
currentConfig = ""
}
task generateConfigProperty(dependsOn: 'installDebug') {
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def taskName = "taskindicate$output.name"
task "$taskName"() << {
project.ext.set("currentConfig", "$output.name")
}
output.assemble.dependsOn "taskindicate$output.name"
}
}
}
task getConfig(dependsOn: ['installDebug', 'generateConfigProperty']) << {
println("My config is $currentConfig")
}
took idea from the answer
This one is quite simple:
android {
applicationVariants.all { variant ->
variant.buildType.name // this is the value!
}
}
Edit: Apparently at some point with gradle update, this is not working as I mentioned in a comment below. So I'd recommend checking other options.
Based on Shooky's answer and JLund's comment, this is what works for me. I have this code in my app build.gradle towards the end:
ext.getCurrentBuildType = {
def isDebug = gradle.startParameter.taskRequests.any {
it.args.any { it.endsWith("Debug") } }
return isDebug ? "Debug" : "Release"
}
Just call it afterEvaluate. Example:
afterEvaluate {
tasks.getByName("externalNativeBuild${ext.getCurrentBuildType()}")
.dependsOn myOtherTask
}
Apparently, gradle.startParameter has plenty of info about current run. Take a look at the doc.
// declare a custom task class so you can reuse it for the different
// variants
class MyTask extends DefaultTask {
String mVariantName;
public void setVariantName(String variant) {mVariantName = variant;}
public String getVariantName() { return mVariantName; }
#TaskAction
void action(){
// do stuff
}
}
// put this after your `android{}` block.
android.applicationVariants.all { variant ->
def taskName = "myTask_$variant.name"
task "$taskName"(type: MyTask) << {
// you can setup this task to various info regarding
// variant
variantName = variant.name
}
variant.assemble.dependsOn (taskName)
}
See Advance customization for more details of what you can extract from the variant variable.
now you it will properly hookup your MyTask into to the chain. Doing it this way should also cleanly handle building multiple flavors all at once as it creates a new instance of MyTask for each variant.
The idea of executing a certain code based on the environment should be through dependency injection.
Take into account to use the following declaration only for exceptional configurations because in general it should not happen that in the code there are conditionals that make references to the environments.
build.gradle
android {
def TRUE = "true"
def FALSE = "false"
def IS_DEV = "IS_DEV"
def IS_RELEASE = "IS_RELEASE"
defaultConfig {
//...
buildConfigField BOOLEAN, IS_DEV, FALSE
buildConfigField BOOLEAN, IS_RELEASE, FALSE
}
buildTypes {
debug {
buildConfigField BOOLEAN, IS_DEV, TRUE
}
release {
buildConfigField BOOLEAN, IS_RELEASE, TRUE
}
}
}
GL
Running Gradle task 'assembleRelease'...

Exclude specific build variants

I have the two default build types: debug / release and a couple of flavors: prod / dev.
Now I want to exclude the build variant dev-release, but keep all other possible combinations. Is there a way to achieve this?
Variant filter
Use the variantFilter of the gradle android plugin to mark certain combinations as ignored. Here is an example from the official documentation that works with flavor dimensions and shows how it can be used:
android {
...
buildTypes {...}
flavorDimensions "api", "mode"
productFlavors {
demo {...}
full {...}
minApi24 {...}
minApi23 {...}
minApi21 {...}
}
variantFilter { variant ->
def names = variant.flavors*.name
// To check for a certain build type, use variant.buildType.name == "<buildType>"
if (names.contains("minApi21") && names.contains("demo")) {
// Gradle ignores any variants that satisfy the conditions above.
setIgnore(true)
}
}
}
As the comment says, you can also check the buildType like so:
android {
variantFilter { variant ->
def names = variant.flavors*.name
if(variant.buildType.name == 'release' && names.contains("myforbiddenflavor")) {
setIgnore(true)
}
}
}
Using variant filters like others I found it was easiest to do this by comparing the variant name against a list of variants that I want to keep.
So in my app/build.gradle file I have something like:
android {
variantFilter { variant ->
def needed = variant.name in [
'stagingQuickDebug', // for development
'stagingFullDebug', // for debugging all configurations
'stagingFullCandidate', // for local builds before beta release
'stagingFullRelease', // for beta releases
'productionFullCandidate', // for local builds before going public
'productionFullRelease' // for public releases
]
variant.setIgnore(!needed)
}
buildTypes {
debug {
}
release {
}
candidate.initWith(release)
}
flavorDimensions "server", "build"
productFlavors {
staging {
dimension "server"
buildConfigField "String", "API_URL", '"https://example-preprod.com/"'
}
production {
dimension "server"
buildConfigField "String", "API_URL", '"https://example.com/"'
}
quick {
dimension "build"
minSdkVersion 21
resConfigs("en", "xxhdpi")
}
full {
dimension "build"
}
}
}
When working with flavor dimensions try this one
variantFilter { variant ->
def dim = variant.flavors.collectEntries {
[(it.productFlavor.dimension): it.productFlavor.name]
}
if (dim.dimensionOne == 'paid' && dim.dimensionSecond == 'someVal') {
variant.setIgnore(true);
}
}
If you use flavor dimensions do this:
flavorDimensions "device", "server"
productFlavors {
emulator {
dimension = "device"
}
phone {
dimension = "device"
}
staging {
dimension = "server"
}
production {
dimension = "server"
}
}
android.variantFilter { variant ->
def device = variant.getFlavors().get(0).name
def server = variant.getFlavors().get(1).name
def isRelease = variant.buildType.name.equals('release')
def isDebug = variant.buildType.name.equals('debug')
// Disable emulatorProductionRelease build variant
if (device.equals('emulator') && server.equals('production') && isRelease) {
variant.setIgnore(true)
}
}
It's easy to read and you can target specific build variants.
The solutions here didn't work for me - I run into this post and added this to build.gradle in my app and it solved the issue for me
gradle.taskGraph.whenReady { graph ->
graph.allTasks.findAll { it.name ==~ /.*MyVariant.*/ }*.enabled = false
}
This is what it does - waits for gradle to assemble the complete list of tasks to execute and then it marks all the tasks that match the name pattern as disabled
NOTE
The match is exact - the expression above lets you match any task that has "MyVariant" somewhere in it's name and it is case sensitive
One more simpler way
android.variantFilter { variant ->
if (variant.name == "qaDebug" || variant.name == "devRelease") {
setIgnore(true)
}
}
Or if you place this code inside android {} closure, android. can be omitted
android {
// Please always specify the reason for such filtering
variantFilter { variant ->
if (variant.name == "qaDebug" || variant.name == "devRelease") {
setIgnore(true)
}
}
}
Please always put a meaningful comment for things like this.
UPD: For Kotlin Gradle DSL there is another way:
android {
variantFilter {
ignore = listOf("qaDebug", "devRelease").contains(name)
}
}
The answer of #ade.se didn't work for me. But I've struggled a little, and written this, that works great:
android {
compileSdkVersion 22
buildToolsVersion '20.0.0'
variantFilter { variant ->
if (variant.buildType.name.equals('debug') || variant.buildType.name.equals('release')) {
variant.setIgnore(true);
}
}
defaultConfig {
applicationId "com.fewlaps.quitnow"
minSdkVersion 15
targetSdkVersion 22
versionCode 35
versionName "1.35"
}
The code you have to add is the variantFilter one, but I've pasted a little of the context to make it easy to understand.
See Variant filter answer above.
Old Answer:
It's not possible at the moment, but it's something we want to add. Probably soon.
In the meantime you could disable the assemble task I think. Something like this:
android.applicationVariants.all { variant ->
if ("devRelease".equals(variant.name)) {
variant.assembleTask.enabled = false
}
}
In Gradle's Kotlin DSL (i.e. build.gradle.kts), that would be:
variantFilter {
ignore = buildType.name == "release" &&
flavors.map { it.name }.contains("dev")
}

Categories

Resources