What gradle task is invoked when android studio build variant changes? - android

I'm trying to configure kotlin in my project, and I've found that my kotlin functions are mangled when they have visibility scope of internal. Having names mangled are all fine except that the mangled names include build variants. So in my Java code I have to write kotlinFunction$module_debug or kotlinFunction$module_release.
The problem is, when I switch the build variant, the suffix should be changed too. For example, I was coding in build variant debug, so I wrote kotlinFunction$module_debug, and then when I switch to build variant release, all my method references break and I have to change all kotlin references to kotlinFunction$module_release. This is OK, we can automate this easily, so I wrote a simple script to do this.
However, the problem I face now is that I do not know when to trigger this script. I assume build variant change triggers some gradle action, but my below code did not work.
task javaUnMangle(type: Exec) {
def variants = ["debug", "release", "local_ci"]
for (String variant in variants) {
if (gradle.startParameter.taskRequests.any {it.args.any {it.toLowerCase().contains(variant.toString())}}) {
commandLine "python3", "java_kotlin_internal_call_flavor_changer.py", variant
return
}
}
}
project.afterEvaluate {
preBuild.dependsOn javaUnMangle
}
any tips or feedbacks are appreciated.
Note: Making all functions public is not an option for us as we develop libraries. Thank you.

Related

Gradle task to take a build of specific build flavors (more than one)

In my app, I have more than 30 build variants. Every time when I release the app, I need to publish it to different platforms, therefore I build 5 different build variants.
Currently, I am doing this:
switch to build variant A
wait for the Gradle build
build APK/Bundle of build variant A
the same steps for B, C, E, and D.
What I am looking for is a Gradle task that just builds me these specific build variants when I run it. I know there is a task to build all build variants but it is too much for me.
I searched SO but couldn't find anything related to a point that I started to think it is impossible.
Could someone point me in the right direction? Thanks.
Calling a gradle task from another gradle task is not the best idea. You should rather describe their relationship as it usually works with all other gradle parts - by using mustRunAfter, dependsOn and so on. For your purposes you can use GradleBuild. I think you're searching for this - if I got your point right :D
I would assume that your task will look something like that (add your flavours instead of mine mocked)
task assembleFlavourBuilds(type: GradleBuild) {
description = 'creating flavour builds for the provided config'
tasks = ['assembleFree', 'assemblePro']
}
If you can use CommandLine then Gradle has good support for that.
For example :
task makeDir(type: Exec) {
workingDir "."
commandLine("cmd", "/c", "mkdir", "example")
}
Am just trying to show an example of how this work, by creating a folder here named example.
You can even add this to be automated with an already defined task, Like a build task. This can be done by using finalizeBy
tasks.named("build") { finalizedBy("makeDir") }
This will only call the task after a successful build.
And my suggestion is to make a .bat file with all the needed commands and call it in the same way as the following code :
task BuildAll(type: Exec) {
workingDir "."
commandLine("cmd", "/c", "mybat.bat")
}
And mybat.bat will contain all the needed commands to
switch build variant
build
bundle
repeat
Switch Build variant
productFlavors {
variantA {
dimension "version"
versionNameSuffix ".a"
}
variantB {
dimension "version"
versionNameSuffix ".b"
}
variantC {
dimension "version"
versionNameSuffix ".c"
}
variantD {
dimension "version"
versionNameSuffix ".d"
}
variantE {
dimension "version"
versionNameSuffix ".e"
}
}
you can use gradle task. in your root build.gradle define task like bellow:
task assembleFlavorBuilds(type: GradleBuild) {
description = 'Description of task.'
tasks = ['assembleFlavorName1Debug', 'assembleFlavorName1Release']
}
each of item in tasks are like this: "assemble"+FlavorName(first of name must be capital)+build type(Debug or Release).
for instance if you have flavors with names "free" and "paid", your task must be like this:
task assembleFlavorBuilds(type: GradleBuild) {
description = 'Description of task.'
tasks = ['assembleFreeRelease', 'assemblePaidRelease']
}
after sync project you can see this task in gradle area in idea or just click on run icon next of this task.

Gradle task for Sentry not compiling

I have to add the Analytics tool Sentry to our Android project. In order to make it work, one needs to create mappings for the obfuscated code (from Proguard/R8) and upload it later to Sentry.
On the website https://docs.sentry.io/platforms/android/ it is even described how to do that.
There it is written that one needs to create a gradle task looking like this:
gradle.projectsEvaluated {
android.applicationVariants.each { variant ->
def variantName = variant.name.capitalize();
def proguardTask = project.tasks.findByName(
"transformClassesAndResourcesWithProguardFor${variantName}")
def dexTask = project.tasks.findByName(
"transformClassesWithDexFor${variantName}")
def task = project.tasks.create(
name: "processSentryProguardFor${variantName}",
type: Exec) {
workingDir project.rootDir
commandLine *[
"sentry-cli",
"upload-proguard",
"--write-properties",
"${project.rootDir.toPath()}/app/build/intermediates/assets" +
"/${variant.dirName}/sentry-debug-meta.properties",
variant.getMappingFile(),
"--no-upload"
]
}
dexTask.dependsOn task
task.dependsOn proguardTask
}
}
This shall wait until Proguard is finished, than copy this properties file to the assets. However, when I add this to my Android gradle script I get the error:
Could not create task
':app:processSentryProguardForPlayStoreStagingDebug'.
No signature of method: java.util.ArrayList.multiply() is applicable for argument types: (ArrayList) values: [[sentry-cli, upload-proguard,
--write-properties, {Application-Path}/app/build/intermediates/assets/playStoreStaging/debug/sentry-debug-meta.properties,
...]] Possible solutions: multiply(java.lang.Number),
multiply(java.lang.Number)
I assume there is something wrong with the multiplication symbol * before the commandLine array. But when I remove it I get the error
Could not create task
':app:processSentryProguardForPlayStoreStagingDebug'.
Cannot cast object 'sentry-cli' with class 'java.lang.String' to class 'int'
So I tried to test this with only that line
commandLine "sentry-cli", ...
Which gave me another error
What went wrong: Cannot invoke method dependsOn() on null object
Thus I assume something went really wrong with that gradle script since it seems the dependend task can't be found. Does anyone have any idea how to fix this (or optionally have any other idea how to copy that sentry-debug-meta.properties file to my assets in another way, once Proguard/R8 is finished)?
Thanks!
-------- EDIT --------
I noticed something important.
The gradle tasks are defined in a different name than what was defined in the manual. Looking at my tasks I have them named
transformClassesAndResourcesWithR8For...
and
transformClassesWithDexBuilderFor...
However, I print the variantName then for checking but it seems my tasks are incomplete.
In my tasks list there exist
transformClassesAndResourcesWithR8ForPlayStoreStagingDebug
but not
transformClassesAndResourcesWithR8ForPlayStoreStagingRelease
and thus the task can't be found. I think that is the real problem here. So where are these gradle tasks defined?
------- EDIT 2 --------
Okay I noticed something strange here. Some variants don't have tasks. It makes sense that DEBUG tasks don't have R8 tasks but I found this here:
Variant: PlayStoreStagingRelease DexTask is null
Variant: PlayStorePreviewRelease DexTask is null
Variant: HockeyAppRelease DexTask is null
Variant: LocalServerRelease DexTask is null
Variant: PlayStoreProductionRelease DexTask is null
So how can this be?
I'd recommend using the Sentry Gradle integration (Gradle plugin) which is described here https://docs.sentry.io/platforms/android/#gradle-integration
The official Android Gradle plugin changed its task names over versions, Gradle version also affects those code snippets.
Google also replaced Proguard with R8 and it also affected those code snippets.
Is there a reason why not using the Sentry Gradle integration? if so, We'll be looking into updating them.
Thanks.
java.util.ArrayList.multiply() hints for that * in front of the [ ] list, which looks strange to me. Try removing the *[ ], only keeping List<String> (there's no ArrayList expected, to begin with):
commandLine "sentry-cli", "upload-proguard", "--write-properties", "${project.rootDir.toPath()}/app/build/intermediates/assets/${variant.dirName}/sentry-debug-meta.properties", variant.getMappingFile(), "--no-upload"
You'd have to look up how your tasks are actually being called, but it should be something alike:
def r8Task = project.tasks.findByName("transformClassesAndResourcesWithR8For${variantName}")
def d8Task = project.tasks.findByName("transformClassesWithDexBuilderFor${variantName}")
With a null check, because not every variant might have minifyEnabled true set:
if(r8Task != null) {
d8Task.dependsOn task
task.dependsOn r8Task
}
Maybe even a previous null check is required, because variant.getMappingFile() needs R8.
And that some flavors have no D8 task might be based upon the absence of code (nothing to do).
Here's a summary of the steps that I followed for integrating Sentry with my Android app. These steps are to ensure the sentry gradle plugin works as expected and automatically uploads the proguard mapping files, without you having to worry about uploading using cli. I assume you would have setup the Sentry SDK as described here:
https://docs.sentry.io/platforms/android/#integrating-the-sdk
Ensure you have Android Studio gradle plugin 3.5.0 (Not 3.6.x, that seems to break the sentry plugin. I observed that the sentry proguard or native symbol upload tasks are not configured or executed at all). This value should be in your root project's build.gradle in dependencies block
Provide a sentry.properties file the root folder of your project. The sentry.properties file should have the following values at minimum:
defaults.project=your_sentry_project_name
defaults.org=your_sentry_org_name
auth.token=sentry_project_auth_token
You can get info about generating auth tokens here: https://sentry.io/settings/account/api/auth-tokens/
(Optional: If you have build flavors) In my case, I have different flavors for my app. So, I had to put the sentry.properties inside my flavor specific folder in /app/src/ folder. Then, I wrote a gradle task to copy the flavor specific sentry.properties file into the project's root folder during gradle's configuration phase. Example:
task copySentryPropertiesTask {
if (getBuildFlavor() != null && !getBuildFlavor().isEmpty()) {
println("Copying Sentry properties file: ${getBuildFlavor()}")
copy {
from "src/${getBuildFlavor()}/"
include "sentry.properties"
into "../"
}
}
}
def getBuildFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern;
if (tskReqStr.contains("assemble"))
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher(tskReqStr)
if (matcher.find())
return matcher.group(1)
else {
println "NO MATCH FOUND"
return ""
}
}
Note 1: You can place this task in your app/build.gradle anywhere (I had placed it at the end).
Note 2: If you followed step 3 for build flavors, you can also add the root folder's sentry.properties in .gitignore. Since, it will be copied everytime you create a build.
Sentry should now be able to upload the proguard files for any release builds (or if you set minifyEnabled=true for any buildType).

How to add conditional afterEvaluate on buildType in Gradle

I have this code at the end of my build.gradle file:
project.afterEvaluate {
// If you add/change build types, you have to add to/change
// these task names.
mergeDebugAssets.dependsOn project.tasks.getByName('downloadLicenses')
mergeReleaseAssets.dependsOn project.tasks.getByName('downloadLicenses')
}
See: Copy generated third party licenses to assets for the full code
This used to work fine (regardless of which buildType was set) but if updating my dependencies to the latest version this triggers an exception (when building with buildType='debug'):
Could not get unknown property 'mergeReleaseAssets' for project ':application'
My thought was that maybe split this block in two and put them under the buildTypes configuration. This doesn't work though, as it tries to evaluate the code anyway and crashes.
Any ideas?
Update 1: Root cause?
https://code.google.com/p/android/issues/detail?id=219732
Update 2: A horrible workaround:
try {
mergeDebugAssets.dependsOn project.tasks.getByName('downloadLicenses')
} catch (Exception e) {
// Expected when building variant Release
}
try {
mergeReleaseAssets.dependsOn project.tasks.getByName('downloadLicenses')
} catch (Exception e) {
// Expected when building variant Debug
}
The reason that you get an error referencing a release task in afterEvaluate is probably because Android Studio's Instant Run feature uses a special Android Gradle plugin feature to only build the debug application variant. This means that only the debug tasks are created, hence why the release tasks can't be found when you reference them.
There are several ways to deal with this:
Search for the dependent task by name using a string. If the Android build system changes the task name in the future, your additional build rules won't run, but you might not even notice.
To determine if release tasks exist, check for the existence of a task whose name is unlikely to change in the future. Maybe assembleRelease fits this bill.
To determine if release tasks exist, check if the release application variant is built with something like:
project.afterEvaluate {
if (!android.applicationVariants.matching { it.buildType.name == 'release' }.isEmpty()) {
mergeReleaseAssets.dependsOn project.tasks.getByName('downloadLicenses')
}
}
The last option looks kind of gross, but hopefully it will fail-fast if the Android build system task names change in the future.
The reason you get exception in case of debug buildType is because mergeReleaseAssets task is not created. You can do the following instead:
project.tasks.findByName('mergeReleaseAssets')?.dependsOn project.tasks.getByName('downloadLicenses')
or even omit project:
tasks.findByName('mergeReleaseAssets')?.dependsOn tasks.getByName('downloadLicenses')
This uses safe navigation operator on nullable return type, so it's clearer than try/catch workaround.

How do I get gradle to run a task before building?

I am working with an ndk project using gradle-experimental. I have a "prebuilt" library that is really a library I generate with a shell call in a gradle task. I am trying to make it very easy for this project to run out of the box, so I need to be able to run this buildTask before gradle builds. I have looked into preBuild.dependsOn ..., but unfortunately, that is not supported in experimental. Any ideas? I currently have a .sh file that runs it, but I'm trying to get away from it.
Edit: Not duplicate because gradle-experimental doesn't support preBuild as I already stated.
Assumptions:
NDK moduleName is foo
Supported ABI is armeabi
Variants are debug and release
You'll have to create a combination for every variant and ABI you added to the abiFilters.
Create a RuleSource class in build.gradle
class FooRuleSource extends RuleSource {
#Mutate
void validatePreCompileFooDebugEnvironmentArmeabi(
#Path('tasks.compileFooArmeabiDebugSharedLibraryFooMainCpp') Task compileTask) {
validateEnvironment(compileTask)
}
#Mutate
void validatePreCompileFooReleaseEnvironmentArmeabi(
#Path('tasks.compileFooArmeabiReleaseSharedLibraryFooMainCpp') Task compileTask) {
validateEnvironment(compileTask)
}
private void validateEnvironment(compileTask) {
compileTask.dependsOn("desiredTask")
}
}
Create the task in your build.gradle:
task desiredTask(type:Exec){
commandLine 'echo', 'hi'
}

Gradle: How do I define build type for specific flavors only?

I have been using gradle for creating different build variants for different companies for an Android app.
For example I have build flavors:
Company1
Company2
And then I have build types:
Production
Preview
Development
So this will create 6 build variants:
Company1Production
Company1Preview
Company1Development
Company2Production
Company2Preview
Company2Development
So the question is:
Actually I don't need the development build type for company 2, I only need it for company 1.
Is there a way I can specify only company 1 have the development build type?
I have a lot of companies in my projects, some of the build type just don't make sense for those companies, and it confuses people who want to build the app.
To answer my own question, I have found the documentation on the Gradle Plugin User Guide
Filtering Variants
When you add dimensions and flavors, you can end up with variants that don't make sense. For example you may define a flavor that uses your Web API and a flavor that uses hard-coded fake data, for faster testing. The second flavor is only useful for development, but not in release builds. You can remove this variant using the variantFilter closure, like this:
android {
productFlavors {
realData
fakeData
}
variantFilter { variant ->
def names = variant.flavors*.name
if (names.contains("fakeData") && variant.buildType.name == "release") {
variant.ignore = true
}
}
}
With the configuration above, your project will have only three variants:
realDataDebug
realDataRelease
fakeDataDebug
You can't stop the Android plugin from creating the matrix of all builds, but you can cause the build to fail if it's not valid. For example, if you don't want Flavor2 Debug builds to work, you can stop them like this:
afterEvaluate {
tasks['prepareFlavor2DebugDependencies'].doFirst {
throw new IllegalArgumentException("This project is not valid")
}
}

Categories

Resources