In my project I want to run my custom task "MyTask" to run before uninstall happens. Code for the same is
uninstallDebug.dependsOn "MyTask"
When I run this, it fails with an error
Could not find property 'uninstallDebug' on project
whereas, I see a gradle task listed in the Gradle window with that name.
What may be going wrong?
project.afterEvaluate { p ->
p.tasks.uninstallDebug.doFirst {
println 'before uninstall'
}
}
gradle.taskGraph.whenReady {
// if your MyTask is in the same project
uninstallDebug.dependsOn "MyTask"
// if it is else where
uninstallDebug.dependsOn project("someProject").project("subProject).tasks["MyTask"]
}
uninstallDebug along with several other tasks, especially those that have flavor or buildType name in their name are created and added to the gradle taskGraph quite late in the configuration step by the android plugin. What that means, as you have discovered is that you may not be able to refer to them by name early in the config step since they do not exist yet.
The way around this as the other answers point out is to wait until the tasks in question are added to the taskgraph. You can use your line uninstallDebug.dependsOn "MyTask" in either gradle.taskgraph.whenRead{} or project.afterEvaluate {}
Related
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).
I can refer to connectedCheck task (which came from android plugin) from my build script:
connectedCheck.finalizedBy AndroidShowTestResults
But trying to use connectedDebugAndroidTest (which came from android plugin too)
connectedDebugAndroidTest.finalizedBy AndroidShowTestResults
gives me
Error:(48, 0) Could not find property 'connectedDebugAndroidTest' on project ':app'.
And if I try
task connectedDebugAndroidTest << {print '123'}
it curses me with
Error:Cannot add task ':app:connectedDebugAndroidTest' as a task with that name already exists.
I don't undestand why I cannot refer to connectedDebugAndroidTest?
Available gradle tasks are shown below:
The android plugin defers the addition of several tasks especially those that have buildType or flavor names in them till a very late stage of the configuration phase. Which in turn means if you try to refer to these yet-to-be-added-tasks by name, you're likely to see a "does not exist" type error messages. If you want to add dependencies around deferred-created tasks, you should wait until configuration is complete:
gradle.projectsEvaluated {
connectedDebugAndroidTest.finalizedBy AndroidShowTestResults
}
Alternatively, you can add a listener to task-graph events, so you can do stuff as soon as a certain task is added to task-graph:
tasks.whenTaskAdded { task ->
if (task.name == 'connectedDebugAndroidTest') {
task.finalizedBy AndroidShowTestResults
}
}
Try
task connectedTest(dependsOn: ["connectedDebugAndroidTest"]){
}
connectedTest.finalizedBy "AndroidShowTestResults"
I think you should try to open test and rebuild.
How to set up gradle to run a particular custom copy task, only when running unit tests?
EDIT
I want to run these tasks when i press build, i. e only in the flavor of the build with unit test execution included.
I've finally found the solution, with the help of this documentation which presents all the tasks that run during build, test, release etc. in a very concise manner. So by making tasks clean, preBuild depend on my copyTask I can ensure that the copy task is run every time the project is cleaned or built.
But since I don't want to run this during building or cleaning process but want to run it when I only run tests, I identified a task that compiles release unit test sources called the compileReleaseUnitTestSources but just mentioning it in build.gradle as
compileReleaseUnitTestSources.dependsOn(myCopyTask)
doesn't actually work, because gradle will give an error saying it cannot find the task compileReleaseUnitTestSources as for some reason that task is not available yet. Instead by enclosing it in a afterEvaluate block we can ensure that this block is executed after all tasks are evaluated, that way we have access to that task now, So finally I added this to my build.gradle
afterEvaluate {
compileReleaseUnitTestSources.dependsOn(copyResDirectoryToClasses)
}
All the answers here mention to use the dependsOn keyword to attach my task to another task that is run during general build/test execution, but none of them mentioned how to go around the problem where gradle is not able to find the tasks even though you know for sure that these tasks were available and run during build/test execution.
you have to set up a "customCopyTask" and make the "test-task" which does the unittests depend on the "customCopyTask" like this
task customCopyTask(type: Copy) {
from sourceSets.test.resources
into sourceSets.test.output.classesDir
}
test.dependsOn customCopyTask
You can make some task finalizing another, in that case this task will run only if another one was called, right after it. This could be done as:
task runUnitTest << {
println 'running tests'
}
task copyTestResults << {
println 'copying results'
}
//make copyTestResults finalize runUnitTest
runUnitTest.finalizedBy copyTestResults
You can read about it in the official user guide.
Additionally, if your unit test could be up-to-date and you don't want to run you copy task in that case, you can check the test task status and skip copy-task, as:
task copyTestResults {
doFirst {
//chtck anothe task status and skip this one if it didn't actually work
if (!tasks.getByName("runUnitTest").getState().didWork) {
throw new StopExecutionException();
}
}
doLast{
println 'copying results'
}
}
Or, if you just need to run copy-task before unit tests, make the test task depending on copy-task by setting it's dependsOn property, read about it with a number og examples here
I want to add a custom task to my Gradle build file.
Should this go inside the android configuration closure or not?
I tried both and they seem to work fine and I couldn't find any explicit mention in the docs.
the task definition should usually not go into the android blog and on root level as zeventh suggests. though it sometimes useful to do this in the android block. especially when you create these tasks dynamically.
In the following example I create one task for each build variant:
android{
applicationVariants.all{ variant ->
def variantTask = task("${variant.name}CustomTask", type:CustomTask){
...
}
check.dependsOn variantTask
}
}
You should just put the task at the root level of the file, outside the android closure.
task yourTask << {
println "Hello world"
}
I have 2 gradle tasks that i want to run after assembleRelease task.
task copyRequiredFilesToVersionControl(type:Copy) {
...
}
task ('versionControl') << {
...
}
If I configure order for these tasks as below tasks get never called...
copyRequiredFilesToVersionControl.dependsOn(assembleRelease)
versionControl.dependsOn(copyRequiredFilesToVersionControl)
If i change order like;
assembleRelease.dependsOn(copyRequiredFilesToVersionControl)
versionControl.dependsOn(copyRequiredFilesToVersionControl)
Tasks are run at the beginning of document. So there is no file to copy and add to version control.
What is the best approach?
I have found method that called doLast. So i fixed my problem with it.
assembleRelease {
doLast {
tasks.versionControl.execute()
}
}
The best approach that I've found to date, has been to use the Ordering Tasks feature in Gradle : see http://www.gradle.org/docs/current/userguide/more_about_tasks.html for more documentation, currently section 15.5.
Basically, you have two rules available : MUST run after and SHOULD run after. I like the rule quite a bit, I use this to create zip archives of projects automatically. NOTE : you must still make use of the "dependsOn" to get proper execution if tasks that you need to have run in a particular order.