Is there a way to have compile dependencies by multiple flavors in Android Studio (build.gradle)?
I have 2 flavorGroups, and in each 2 variants. Out of the 4 possible combinations I would like to be able to depend on a lib only if I'm both in latest and in free flavor. latestCompile or freeCompile works, but latestFreeCompile doesn't. this is the relevant part of my build.gradle:
android {
defaultConfig {
minSdkVersion 7
targetSdkVersion 19
versionCode 15
versionName "1.9." + versionCode
}
flavorGroups 'sdk', 'cost'
productFlavors {
latest {
flavorGroup 'sdk'
minSdkVersion 8
}
sdk7 {
flavorGroup 'sdk'
minSdkVersion 7
versionName android.defaultConfig.versionName + ".sdk7"
}
free {
flavorGroup 'cost'
}
pro {
flavorGroup 'cost'
}
}
}
dependencies {
// this works:
freeCompile files('libs/StartAppInApp-2.2.1.jar')
// and I would like something like this:
latestFreeCompile 'com.google.android.gms:play-services:4.1.32' // minSdkVersion:8
}
If I would use:
latestCompile 'com.google.android.gms:play-services:4.1.32'
then it would be included in latestPro as well (not needed)
and if I'd use:
freeCompile 'com.google.android.gms:play-services:4.1.32'
then it would be included in sdk7Free as well (although it needs SDK 8)
As now described on the official Android Developers website, if you want to declare a dependency for a specific combination of product flavor AND build type you need to declare that configuration first.
For example for the free flavor and the debug type:
configurations {
freeDebugImplementation {}
}
dependencies {
freeDebugImplementation 'com.google.firebase:firebase-ads:9.8.0'
}
Advanced: multiple flavor dimensions
Note that if your app has multiple flavor dimensions, you must create the configuration corresponding to the full build variant (ALL flavors, then build type), like so:
flavorDimensions "money", "image"
productFlavors {
free { dimension "money" }
paid { dimension "money" }
picasso { dimension "image" }
glide { dimension "image" }
}
configurations {
freePicassoDebugImplementation {}
freeGlideDebugImplementation {}
}
dependencies {
freePicassoDebugImplementation 'com.google.firebase:firebase-ads:9.8.0'
freeGlideDebugImplementation 'com.google.firebase:firebase-ads:9.8.0'
}
I had the same problem. I resolved that with some gradle code in my build.gradle:
// global variables
ext {
buildType = ""
groupCost = ""
groupSdk = ""
}
def splitCamelCase(String word) {
def result = []
int nextStart = 0;
for (int i = 1; i < word.length(); i++) {
if(word.charAt(i).isUpperCase()) {
result.add(word.substring(nextStart, i));
nextStart = i;
}
}
result.add(word.substring(nextStart));
return result;
}
// start parameters
println "Start parametes: tasks = " + gradle.startParameter.getTaskNames()
gradle.startParameter.getTaskNames().each { task ->
// This line is needed to skip other projects' tasks
// You can safely remove it if you have only one project
if(!task.startsWith(':<your_application_name>:')) return;
def taskParts = splitCamelCase(task.split(":").last());
def groupCostPrefix = taskParts[taskParts.size() - 3];
def groupSdkPrefix = taskParts[taskParts.size() - 2];
def buildTypePrefix = taskParts[taskParts.size() - 1];
if("Debug".startsWith(buildTypePrefix)) {
buildType = 'debug';
}
else if("Release".startsWith(buildTypePrefix)) {
buildType = 'release';
}
else {
return; // do not process tasks that are not ending with proper build type.
}
if("Free".startsWith(groupCostPrefix)) {
groupCost = 'free';
}
else if("Pro".startsWith(groupCostPrefix)) {
groupCost = 'pro';
}
if("Sdk7".startsWith(groupSdkPrefix)) {
groupSdk = 'froyo';
}
else if("Latest".startsWith(groupSdkPrefix)) {
groupSdk = 'latest';
}
}
Then all you need is to add following code inside your 'dependencies' section:
if(groupSdk == 'latest' && groupCost == 'free') {
compile 'com.google.android.gms:play-services:4.1.32'
}
Same issue here, but Pawel's solution didn't work because gradle dependencies have other issues in which it starts building not only the selected flavors/build type, but all of them and it requires a more dynamic solution.
Still I found this issue tracker:
https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=52962
There is also a reference to fixing the rebuild-all bug I mentioned above, but I didn't try it yet.
And implemented this solution (as per issue tracker reply #60):
Map<String, Dependency> customDeps = new HashMap<String, Dependency>()
customDeps.put('flavor1GrpXflabor1GrpYDebugCompile', dependencies.project(path: ':lib', configuration: 'debug'))
customDeps.put('flavor1GrpXflavor1GrpYReleaseCompile', dependencies.project(path: ':lib', configuration: 'release'))
customDeps.put('flavor2GrpXflavor1GrpYDebugCompile', dependencies.project(path: ':other_lib', configuration: 'debug'))
customDeps.put('flavor2GrpXflavor1GrpYReleaseCompile', dependencies.project(path: ':other_lib', configuration: 'release'))
....
configurations.all() { config ->
Dependency d = customDeps.get(config.name)
if (d != null) {
config.dependencies.add(d)
}
}
Related
Problem
I want to create a custom gradle test task to only run JUNIT tests and omit Robolectric tests. I am attempting to achieve this task by creating a new test annotation and omitting any tests that include that new annotation.
Error
JUNIT packages are not included when I run the the gradle task.
error: package android.test.suitebuilder.annotation does not exist
import android.test.suitebuilder.annotation.SmallTest;
Details
New Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface RobolectricTest {
}
Gradle File
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
repositories {
mavenCentral()
maven { url 'http://artifactory.ops.am1.qa.ext.bamgrid.com/artifactory/mobile-resources' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.google.gms:google-services:1.3.0-beta1'
}
}
android {
compileSdkVersion rootProject.ext.compileSDKVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.example"
minSdkVersion 17
targetSdkVersion rootProject.ext.targetSdkVersion
buildConfigField "String", "BUILD_TIME", "\"" + getDateAndTime() + "\""
buildConfigField "String", "VERSION_BUILD", "\"" + project["VERSION_BUILD"] + "\""
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION_NAME
}
signingConfigs {
debug {
storeFile file(project.DEBUG_KEYSTORE)
storePassword project.DEBUG_KEYSTORE_PASSWORD
keyAlias project.DEBUG_KEYSTORE_ALIAS
keyPassword project.DEBUG_KEY_PASS
}
release {
storeFile file(project.RELEASE_KEYSTORE)
storePassword project.RELEASE_KEYSTORE_PASSWORD
keyAlias project.RELEASE_KEYSTORE_ALIAS
keyPassword project.RELEASE_KEY_PASS
}
}
sourceSets {
main {
res.srcDirs = ['src/main/res/',
'src/main/abc']
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
zipAlignEnabled true
proguardFile getDefaultProguardFile('proguard-android-optimize.txt')
proguardFile 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
testCoverageEnabled = true
debuggable true
signingConfig signingConfigs.debug
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile ('junit:junit:4.12')
testCompile ('org.apache.maven:maven-ant-tasks:2.1.3')
testCompile ('org.robolectric:robolectric:3.0')
testCompile ('org.robolectric:shadows-support-v4:3.0')
}
sourceSets {
unitTest {
java.srcDirs = ['src/test/java']
resources.srcDirs = ['src/test/resources']
}
}
ClassLoader getClassLoader() {
List urls = sourceSets.test.runtimeClasspath.collect {
it.toURI().toURL()
}
return URLClassLoader.newInstance( urls as URL[] )
}
/**
* Filters out files that have specific annotation
* #param map - map of things to filter
* #return - list of acceptable files after filter
*/
List annotationFilter( Map map ) {
map.prefix = map?.prefix ?: '' // prefix: provide convenience for passing in annotation names
ClassLoader loader = classLoader
List result
// filter with annotations
if( !map.includes ) {
result = map?.names
} else {
result = []
map?.names.each { name ->
Class klass = loader.loadClass( name )
map?.includes.each { annotationName ->
String fullName = map.prefix + annotationName
Class annotation = loader.loadClass( fullName ).asSubclass( Annotation )
if( !klass.isAnnotationPresent( annotation ) ) {
result << name
}
}
}
}
if( result?.size() == 0 ) result = [ 'no.tests.to.run' ]
return result
}
/**
* Gradle task to run only robolectric tests.
*/
task unitTest( type: Test, description: 'Run all junit tests' ) {
android.sourceSets.main.java.srcDirs.each { dir ->
def buildDir = dir.getAbsolutePath().split('/')
buildDir = (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join('/')
sourceSets.unitTest.compileClasspath += files(buildDir)
sourceSets.unitTest.runtimeClasspath += files(buildDir)
}
testClassesDir = sourceSets.unitTest.output.classesDir
classpath = sourceSets.unitTest.runtimeClasspath
doLast {
println "Doing Last"
List names = testClassNames()
List filtered = annotationFilter( names: names, includes: ['testUtils.RobolectricTest'] )
println 'Running ' + filtered.size() + ' tests:\n' + filtered*.toString()*.replaceAll('^','\t').join('\n')
filter {
setIncludePatterns( filtered as String[] )
}
}
}
Not so much Robolectric-specific, but in regards to declaring a custom test task for Android with Gradle, I ran into a lot of trouble with this. As you found, all of the documentation and examples are using the Java plugin, but the Android plugin subverts most of it.
The only solution I found with the Android plugin is to create another build type, which will then result, under the hood, in the Android plugin creating new test tasks for me. Then I can easily modify the tasks.
Here is the relevant setup, which I keep in a <rootProject>/gradle/test.gradle file (This specific example uses Category annotation to filter unit tests into one task and integration tests into a separate task. For information on that part of it see https://github.com/junit-team/junit4/wiki/Categories):
android {
buildTypes {
integration
}
testOptions {
unitTests.all {
useJUnit()
if (it.name == 'testIntegrationUnitTest') {
options {
excludeCategories 'com.example.categories.UnitTest'
}
} else {
options {
excludeCategories 'com.example.categories.IntegrationTest'
}
}
}
}
}
Then the <module>/build.gradle file applies from this file with apply from: "../gradle/test.gradle"
This causes the android closure from the two files to be merged, results in a new integration build type, which in turn results in a new testIntegrationUnitTest task on the project in addition to the standard testDebugUnitTest and testReleaseUnitTest tasks.
But now, testDebugUnitTest runs only "real" unit tests, while testIntegrationUnitTest runs only integration tests.
Your mileage/implementation may vary. Once you're inside the unitTest.all closure you can do whatever manipulation you need to the options.
Custom Gradle Task To Only Run Specific Tests
Building on #alphonzo79 answer, I was able to solve the issue.
The things to know
Android gradle omits common testing features in gradle, like exclude and custom sourceSets.
Retention and Target was not helpful. Only categories worked.
You don't need a new build type, you only need to change the test option when compiling your own tasks.
Don't use this - https://github.com/pkainulainen/gradle-examples/blob/master/integration-tests/build.gradle
The complete answer was to create a custom task that changed a flag for the android testOptions to excludeCategories.
LINK
CODE
def integrationTests = false
...
testOptions {
unitTests.all {
useJUnit()
if (integrationTests.toBoolean()) {
println "Integration Tests Only for " + it.name
options {
excludeCategories 'com.example.reactivemvp.categories.UnitTest'
}
} else {
println "Unit Tests Only for " + it.name
options {
excludeCategories 'com.example.reactivemvp.categories.IntegrationTest'
}
}
}
}
...
task integrationTest(
type: Test,
description: 'Run integration tests only. Pass in \'-Pintegration=true\'',
dependsOn: ['testDebugUnitTest', 'clean'] ) {
//Here for task completion, not actually used since sub task of testDebugUnitTest
testClassesDir = file("src/integrationTest/java/");
classpath = files("$System.env.ANDROID_HOME/sources/android-18")
//
//Turn on integration testing when argument exists and is true
//
if (project.hasProperty('integration')) {
println integration
if (integration == 'true') {
integrationTests = true
}
}
}
We can use the following configuration to exclude multiple tests by name:
def integrationTests = project.hasProperty('integrationTests') ? project.getProperty('integrationTests') : false //Default value false
android {
//...
testOptions {
unitTests {
includeAndroidResources = true
returnDefaultValues = true
all {
test {
filter {
if (integrationTests.toBoolean()) {
includeTestsMatching "*IntegrationTest"
} else {
includeTestsMatching "*UnitTest"
}
}
}
}
}
}
}
In command line:
gradlew test // Run only *UnitTest, Default value is false.
gradlew test -PintegrationTests=false // Run only *UnitTest
gradlew test -PintegrationTests=true // Run only *IntegrationTest
You said Robolectric can't find AndroidManifest.xml when testing multidimensional flavor project on Ubuntu
try to explicitly give the path to your manifest file in the #Config annotation
#Config(constants = BuildConfig.class, manifest = "../<path to>/AndroidManifest.xml")
When using Gradle flavorDimensions, is it possible to exclude specific variants?
For example -
android {
...
flavorDimensions "abi", "version"
productFlavors {
freeapp {
flavorDimension "version"
...
}
x86 {
flavorDimension "abi"
...
}
}
the following build variants will be created:
x86-freeapp-debug
x86-freeapp-release
arm-freeapp-debug
arm-freeapp-release
mips-freeapp-debug
mips-freeapp-release
x86-paidapp-debug
x86-paidapp-release
arm-paidapp-debug
arm-paidapp-release
mips-paidapp-debug
mips-paidapp-release
Can "mips-paidapp-release" be manually removed?
Since Gradle 0.9 you can apply a variant filter and iterate over them:
productFlavors {
freeapp {
dimension "version"
}
x86 {
dimension "abi"
}
paidapp {
dimension "mips"
}
}
// Loop variants
android.variantFilter { variant ->
// Loop flavors
variant.getFlavors().each { flavor ->
println variant.buildType.name + " " + flavor.name + " " + flavor.dimension
if (variant.buildType.name.equals('release') &&
flavor.name.equals('paidapp') &&
flavor.dimension.equals('mips')) {
variant.setIgnore(true)
}
}
}
Note: that I changed flavorDimension to dimension because the latter is now the preferred way specify it.
Note2: the above note requires you to use the newer gradle version:
Project/build.gradle should have the following:
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
while Project/app/build.gradle should have this:
android {
buildToolsVersion "22.0.1"
...
}
This is how I do it:
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)
}
}
I like it because it's easy to read and you can target specific build variants.
I am working in an Android project which has many flavors, I used this code in build.gradle to set the package name for each flavor:
flavorDimensions "type", "feature"
productFlavors {
abc { flavorDimension "type" }
def { flavorDimension "type" }
ABC { flavorDimension "feature" }
DEF { flavorDimension "feature" }
}
android.variantFilter { variant ->
def flavorString = ""
def flavors = variant.getFlavors()
for (int i = 0; i < flavors.size(); i++) {
flavorString += flavors[i].name;
}
if(flavorString.equalsIgnoreCase("abcABC")) {
variant.getDefaultConfig().applicationId "com.my.app.abc.abc"
}
if(flavorString.equalsIgnoreCase("abcDEF")) {
variant.getDefaultConfig().applicationId "com.my.app.abc.def"
}
if(flavorString.equalsIgnoreCase("defABC")) {
variant.getDefaultConfig().applicationId "com.my.app.def.abc"
}
if(flavorString.equalsIgnoreCase("defDEF")) {
variant.getDefaultConfig().applicationId "com.my.app.def.def"
}
}
Everything was fine until I updated my gradle from 1.10.0 to 2.2.1, the build is failed and I get this error message:
Error:(63, 0) No signature of method: com.android.build.gradle.internal.api.ReadOnlyProductFlavor.applicationId() is applicable for argument types: (java.lang.String) values: [com.hac.apps.megahd.acc]
Possible solutions: getApplicationId()
It seems like the variant.getDefaultConfig().applicationId is working different now. I searched the internet for the document for android.variantFilter but it seems not to be exist.
Anyone can tell me how do I get this code work in gradle 2.2.1? Thanks a lot.
The variantFilter is meant to be used only to allow or disallow builds of certain combinations. The defaultConfig object is shared and read-only. If you want to assign applicationId's to variants, I think you should something more similar to this:
buildTypes {
applicationVariants.all { variant ->
def projectFlavorNames = []
variant.productFlavors.each() { flavor ->
projectFlavorNames.add(flavor.name)
}
project.logger.debug('Application variant ' + variant.name + '. Flavor names list: ' + projectFlavorNames)
if (projectFlavorNames.contains('customer1') && projectFlavorNames.contains('variant1')) {
variant.mergedFlavor.applicationId = 'com.customer1.variant1'
} else if (projectFlavorNames.contains('customer2') && projectFlavorNames.contains('variant2')) {
variant.mergedFlavor.applicationId = 'com.customer2.variant2'
} // else use standard package name
project.logger.debug('Using project name: ' + variant.packageName)
}
// ...
}
From Dynamically generate package name for multi-flavors configuration
I'm trying to list all of mine Android Gradle project's actual dependencies in the BuildConfig.java (see How to determine during the build which exactly version of dependency is used). For that I'm using code similar to this:
def getProjectAndBuildscriptDependencies() {
project.allprojects.collectMany{ proj ->
def configurations = (proj.configurations + proj.buildscript.configurations)
configurations.collectMany { it.allDependencies }
}.findAll { it instanceof ExternalDependency }
}
private def resolveActualDependencyVersion(Dependency dependency) {
def version = dependency.version
boolean mightBeDynamicVersion = version != null && (version.endsWith('+') || version.endsWith(']') || version.endsWith(')') || version.startsWith('latest.'))
if (!mightBeDynamicVersion){
return version
}
def actualVersion = resolveWithAllRepositories{
project.configurations.detachedConfiguration(dependency).resolvedConfiguration.lenientConfiguration
.getFirstLevelModuleDependencies(org.gradle.api.specs.Specs.SATISFIES_ALL).find()?.moduleVersion ?: version
}
return actualVersion
}
This function is supposed to gather all the dependencies I have. If I call it from some task, it does what it has to:
task printCurrentDependencies << {
def current = getProjectAndBuildscriptDependencies()
def currentVersions = [:]
current.each { dependency ->
def actualVersion = resolveActualDependencyVersion(dependency)
currentVersions.put(keyOf(dependency), actualVersion)
}
}
So that currentVersions contain all the dependencies with their actual version.
However when I call it from defaultConfig of android section, it only gives me one dependency, which is the Gradle itself.
android {
defaultConfig {
buildConfigField "int", "depsCount", getProjectDependencies().size().toString()
}
}
This difference in behavior is confusing me. Why this happens, and how to fix it and achieve what I really want?
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")
}