How do I intercept failures of a Gradle task? - gradle

'I'm writing build script using net.foragerr.jmeter plug-in, version 1.0.2-2.13.
What my task does is mostly 1) runs plug-in JMeter task and 2) collects application log.
task perfTest(dependsOn: ['jmClean', 'jmRun'],
description:'Runs (cleanly) performance tests on a deployed application and collects the app log if available. ' +
'Use --no-daemon to see progress. Use --info to see all JMeter command-line arguments.') << {
if (logDir.isDirectory()) {
copy {
from "${logDir}"
into "${buildDir}/jmeter-report"
include 'iRePORT.log'
}
}
}
Now, I need to collect the log even if jmRun fails. When I try to implement a solution for this, I am really stuck:
I can't modify jmRun task, because it's a plug-in task
I can't use try/finally and execute another task directly because Gradle is designed not to support this, e.g. see How do I wrap a gradle task in another task?
I can't use --continue flag gradle.startParameter.continueOnFailure = true because it would not continue execution of a dependent task
The only possible workaround that I could think of is to separate log collection, always include it on a command line, e.g. perfTest collectLogs and set gradle.startParameter.continueOnFailure = true in perfTest.
This is far from ideal.
Are there any better solutions? Shouldn't there be a way for Gradle to support scenarios like this?

You could try creating a task like this
task collectPerfTestLogs(type: Copy) {
from "${logDir}"
into "${buildDir}/jmeter-report"
include 'iRePORT.log'
}
and then have perfTest.finalizedBy(collectPerfTestLogs).
To quote Gradle:
Finalizer tasks will be executed even if the finalized task fails.
See https://docs.gradle.org/current/userguide/more_about_tasks.html

Related

How to run a task before build in 2021 gradle Kotlin syntax in an Android project

I read a lot of answers to multiple questions like this, but they are all very old and use arcane/obsolete syntax in groovy and/or are not suitable for Android projects.
I have a task.
tasks.register("asd") {
doFirst {
exec {
How do I run it when build starts in an Android app/build.gradle.kts file?
I have printed both tasks and project.tasks names and I have tried
tasks.named("build").dependsOn(":asd")
tasks.named("app:build").dependsOn(":asd")
tasks.named(":app:build").dependsOn(":asd")
project.tasks.named("build").dependsOn(":asd")
project.tasks.named("app:build").dependsOn(":asd")
project.tasks.named(":app:build").dependsOn(":asd")
It either fails with Task <name> not found in project or it does nothing.
I tried with doFirst, doLast and neither (directly exec) and still nothing.
So I found the right way to go about it
android {
project.tasks.preBuild.dependsOn("asd")
}
tasks.register("asd") {
doFirst {
exec {
No need for the : when referring to the task.
The build and preBuild tasks are accessible from (project.)tasks.
doFirst was necessary otherwise it would loop endlessly.
tasks.register allowed the task to run when it needed and not immediately, which happens if we use create

Execute more than one command in a task without breaking incremental build

We use gradle incremental builds and want to run two commands (ideally from one task). One of the solutions here worked getting the two commands running... however it breaks incremental builds.. It looks something like:
task myTask() {
inputs.files(inputFiles)
project.exec {
workingDir web
commandLine('mycmd')
}
project.exec {
workingDir web
commandLine('mysecond-cmd')
}
}
if running a single command and incremental builds is working, the task looked similar to this, the thing that seems to make the difference is the workingDir:
task myTask(type: Exec) {
workingDir myDir // this seems to trigger/enable continuos compilation
commandLine ('myCmd')
}
the best alternative so far is create 3 tasks, one for each of the cmdline tasks I want to run and a third one to group them, which seems dirty.
The question is: Is there a way to run two or more commands in one task with incremental builds still working?
I'll try to answer the question from the comments: how can I signal from a task that has no output files that the build should watch certain files. Unfortunately, this is hard to answer without knowing the exact use case.
To start with, Gradle requires some form of declared output that it can check to see whether a task has run or whether it needs to run. Consider that the task may have failed during the previous run, but the input files haven't changed since then. Should Gradle run the task?
If you have a task that doesn't have any outputs, that means you need to think about why that task should run or not in any given build execution. What's it doing if it's not creating or modifying files or interacting with another part of the system?
Assuming that incremental build is the right solution — it may not be — Gradle does allow you to handle unusual scenarios via TaskOutputs.upToDateWhen(Action). Here's an example that has a straightforward task (gen) that generates a file that acts as an input for a task (runIt) with no outputs:
task gen {
ext.outputDir = "$buildDir/stuff"
outputs.dir outputDir
doLast {
new File(outputDir, "test.txt").text = "Hurray!\n"
}
}
task runIt {
inputs.dir gen
outputs.upToDateWhen { !gen.didWork }
doLast {
println "Running it"
}
}
This specifies that runIt will only run if the input files have changed or the task gen actually did something. In this example, those two scenarios equate to the same thing. If gen runs, then the input files for runIt have changed.
The key thing to understand is that the condition you provide to upToDateWhen() is evaluated during the execution phase of the build, not the configuration phase.
Hope that helps, and if you need any clarification, let me know.

Gradle Run Task Even If Build Fails

So I'm trying to print some stuff to the console in Gradle even if the build fails. How can I do this?
I've found build.finalizedBy(taskName) but that only runs if the build finishes normally.
You can let any build continue on task failures by using the --continue parameter on Gradle invocation. If you do not want to type this parameter all the time you can use the following code in your settings.gradle:
startParameter.continueOnFailure = true
Please note that other tasks may fail due to an earlier task that failed. Using this option, tasks connected via finalizedBy will be executed, but you should only use this option if the tasks are related, even for non-failure cases.
Of course, you can also use lifecycle listeners of the Gradle object or its TaskExecutionGraph. You can use
afterTask
a full TaskExecutionListener implementation
buildFinished (for the whole build)
You can use either gradle.buildFinished or a finalizer task (as you mentioned).
In your example, build.finalizedBy(taskName) will only execute taskName if the build task executes. If the build fails before the build task executes, taskName won't be executed.
e.g., this prints a message based on the result of the build:
gradle.buildFinished { result ->
if (result.failure) {
logger.lifecycle("build failed")
} else {
logger.lifecycle("build successful")
}
}

Getting a list of all Gradle tasks from build script

So I'm trying to write a list of all gradle tasks to a file. I of course could use the tasks command for this, but I want cache it to a file every time any other gradle command is called. So whenever I run ./gradlew build for example, I want the available tasks to be written to a file.
This seemed simple enough, and I wrote the below task to try it out:
task cacheTasks() {
doLast {
allprojects { p ->
p.tasks*.each { t ->
println(p.name + ":" + t.name)
}
}
}
}
The problem is, that I only get a sub-set of all the tasks available. When I run the ./gradlew tasks --all command, many more are printed. It seems that none of the built-in tasks (like build, clean or help) are in the tasks* List when I loop over it, but oddly enough I can reference them directly:
tasks.build { t ->
println("DEBUG:" + t.name)
}
It seems so simple, yet I've been searching in vain for a solution. I even tried looking in the gradle source code to see how the tasks Task works, but I couldn't find any clue as to why this doesn't work.
rootProject.getAllTasks(true) looks like it's retrieving more tasks than rootProject.tasks.
I highly doubt there is a task class in container with name build.
This is what I get when I debug your task:
I am not saying gradle build does not run, but it can be in other forms maybe an instance of org.gradle.api.tasks.GradleBuild. (I am not very sure because the gradle source code is very hard for me to compile and run).
When using
tasks.build { t ->
println("DEBUG:" + t.name)
}
You actually call org.gradle.api.tasks.TaskContainer#create(java.util.Map<java.lang.String,?>, groovy.lang.Closure) and create a new task named build.

Why do my Gradle tests run repeatedly?

I have a pretty standard Gradle build that's building a Java project.
When I run it for the first time, it compiles everything and runs the tests. When I run it a second time without changing any files, it runs the tests again.
According to this thread, Gradle is supposed to be lazy by defaut and not bother running tests if nothing has changed. Has the default behaviour here been changed?
EDIT:
If I run gradle test repeatedly, the tests only run the first time and are subsequently skipped. However, if I run gradle build repeatedly, the tests get re-run every time, even though all other tasks are marked as up-to-date.
the gradle uptodate check logs on info level why a task is not considered to be up-to-date. please rerun the "gradle build -i" to run with info logging at check the logging output.
cheers,
René
OK, so I got the answer thanks to Rene prompting me to look at the '-i' output...
I actually have 2 test tasks: the 'test' one from the Java plugin, and my own 'integrationTest' one. I didn't mention this in the question because I didn't think it was relevant.
It turns out that these tasks are writing their output (reports, etc.) to the same directory, so Gradle's task-based input and output tracking was thinking that something had changed, and re-running the tests.
So the next question (which I will ask separately) becomes: how do I cleanly (and with minimal Groovy/Gradle code) completely separate two instances of the test task.
You need to create test tasks in your build.gradle and then call those specific tasks to run a specific set of tests. Here is an example that will filter out classes so that they don't get run twice (such as when running a suite and then re-running its child classes independently):
tasks.withType(Test) {
jvmArgs '-Xms128m', '-Xmx1024m', '-XX:MaxPermSize=128m'
maxParallelForks = 4 // this runs tests parallel if more than one class
testLogging {
exceptionFormat "full"
events "started", "passed", "skipped", "failed", "standardOut", "standardError"
displayGranularity = 0
}
}
task runAllTests(type: Test) {
include '**/AllTests.class'
testReportDir = file("${reporting.baseDir}/AllTests")
testResultsDir = file("${buildDir}/test-results/AllTests")
}
task runSkipSuite(type: Test) {
include '**/Test*.class'
testReportDir = file("${reporting.baseDir}/Tests")
testResultsDir = file("${buildDir}/test-results/Tests")
}
Also, concerning your build question. The "build" task includes a clean step which is cleaning tests from your build directory. Otherwise the execution thinks the tests have already been ran.

Resources