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

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'
}

Related

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

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.

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).

Android Build failure : Could not get unknown property 'assembleDebug' for project

I need to create Jar and copy to lib folder, which is done in following task :
task copyJarToLib(type: Copy, dependsOn: 'createJar') {
from "build/libs/lib1.jar"
from "build/libs/lib2.jar"
into "../App/libs/"
}
I have to execute this after apk generation. So, I am calling following instruction at the end of the module-app build.gradle :
assembleDebug.finalizedBy(copyJarToLib)
Issue is observed after upgrading the gradle plugin to 3.1.0 and gradle to 4.4.
Same implementation is working fine with gradle 2.3.
If you want to execute something at the end of build, you can do it as follows:
gradle.buildFinished {
copy {
from "build/libs/lib1.jar"
from "build/libs/lib2.jar"
into "../App/libs/"
}
}
If you want to execute task before apk is built the you can:
afterEvaluate {
project.tasks.findByName('preDebugBuild').dependsOn(':<module>:copyJarToLib')
}
base on Android Studio 2020.3.1, you can use follow codes
afterEvaluate {
project.tasks.findByName('preDebugBuild').dependsOn(copyJarToLib)
}

Declaring the inputs of a gradle task as multiple files

We have a gradle task that will automatically generate codes for us before building. See the following as an example,
task djinniTask(type: org.gradle.api.tasks.Exec) {
commandLine 'sh', './Djinni/run_djinni.sh'
}
assembleDebug.dependsOn djinniTask
Basically, the above run_djinni.sh is using a library djinni to generate JNI codes. The above works fine except that it will run this script every time we build even if we didn't update the script file, which is obviously not very efficient. We did a bit of research and found 17.9. Skipping tasks that are up-to-date. And as a result, the following works fine. It will skip this task if we didn't modify run_djinni.sh.
task transform {
ext.srcFile = file('./Djinni/run_djinni.sh')
ext.destDir = new File(buildDir, 'generated')
doLast {
commandLine 'sh', './Djinni/run_djinni.sh'
}
}
Now the problem is, the run_djinni.sh is not the only script file that we have. The project is big and we multiple scripts files like: run_foo_djinni.sh, run_bar_djinni.sh and etc. run_djinni.sh will call each of the other scripts. So is there a way to declare the inputs of a gradle task as multiple files, for example, in our case, every files that is under the Djinni folder?
Ok, according to gradle DSL you can define multiple inputs:
task transform {
inputs.files('file path', 'another file path')
}

Gradle: How to run custom task after an Android Library is built?

I have an Android Library, it's generating a debug.aar and a release.aar, I need to copy the release.aar to another folder as a reference to other part of the project.
What I've done now is in this Android Library build.gradle I defined a task:
task copyAARToCommonLibs(type: Copy) {
from('../build/outputs/aar') {
include '*-release.arr'
}
into '../SomeSampleApps/libs'
}
I'm trying to run this task after the arr is generated, which I assume is assembleRelease stage, so I tried do this in this build.gradle
assembleRelease.doLast{
copyAARToCommonLibs
}
I build the overall project using
gradle build
But this task is running at the very beginning of the whole process.
I also tried this:
applicationVariants.all { variant ->
variant.assemble.doLast {
copyAARToCommonLibs
}
}
inside android{} property(I guess that's what it's called?)
Running gradle build, got this error: Could not find property 'applicationVariants'
I then came across this snippet:
tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn copyAARToCommonLibs }
But it seems this makes the task to run after compiling, I don't know exactly how to modify this to run after assemble.
Could someone please correct me where I did wrong and how can I get this copy task work after the .arr file is generated?
It seems that finalizedBy might be helpful.
assembleRelease.finalizedBy(copyAARToCommonLibs)
Mind the fact that in the following way you won't define a dependency:
assembleRelease.doLast {
copyAARToCommonLibs
}
actually.. it does exactly nothing. You need to execute the task:
assembleRelease.doLast {
copyAARToCommonLibs.execute()
}
but running task in the following way is discouraged and very bad practice.
You can also try:
assembleRelease.doLast {
copy {
from('../build/outputs/aar') {
include '*-release.aar'
}
into '../AscendonSDKSamples/libs'
}
}
I went with finalizedBy() but had to include it within an afterEvaluate...
afterEvaluate {
if (gradle.startParameter.taskNames.contains(":app:assembleFatReleaseInternal")) {
play.enabled = true
play.commit = true
play.track = "internal"
play.releaseStatus = "completed"
play.releaseName = versionName
generateFatReleaseInternalBuildConfig.dependsOn set_build_date
assembleFatReleaseInternal.finalizedBy(uploadCrashlyticsSymbolFileFatReleaseInternal)
uploadCrashlyticsSymbolFileFatReleaseInternal.finalizedBy(publishFatReleaseInternal)
}
}
This worked well for automating the upload of native symbols to Fabric / Crashlytics and other things such as automated play store publishing.
Because android studio add task by dynamic,so assembleRelease will not be recognized.
Just add hook after task added event happens.
tasks.whenTaskAdded {
theTask ->
if (theTask.name.contains('externalNativeBuild')) {
theTask.doLast{
println "[*] begin to copy file."
}
}
// println theTask.name
}

Categories

Resources