Gradle: custom message on task failure? - gradle

I'd like to hook into the compileJava target and spit out an additional custom message on failure. We've got a really common case setup case that a lot of folks overlook and it'd be useful, only on failure, to be able to do something like:
compileJava.onFailure {
println "Did you configure your wampfrankle to talk to the slackometer?"
}
My Google skills have not yet led to an answer.

The error is a dependency error and as Rene points out that needs to be detected after the build has executed, not after the project has been evaluated.
Here I've added a call to buildFinished with a closure that detects if a failure has happened and prints out an error message.
project.gradle.buildFinished { buildResult ->
if (buildResult.getFailure() != null) {
println "Did you configure your wampfrankle to talk to the slackometer?"
}
}
To test this I force a dependency resolution failure with this bogus dependency:
dependencies {
compile 'foo:bar:baz'
}

Use finalizedBy on a task that executes onlyIf the first task fails. For example:
tasks.compileJava.finalizedBy('compileJavaOnFailure')
task compileJavaOnFailure {
doLast {
println 'Did you configure your wampfrankle to talk to the slackometer?'
}
onlyIf {
tasks.compileJava.state.failure != null
}
}

Related

Error using dependsOn to call task from another gradle file

I am working with a build.gradle file that has multiple ways to specify executions for a task - setup. To call a task from another gradle file - runtests.gradle, I created a task - testTask and added task dependency using dependsOn, but this implementation does not seem to work and giving out an error like :
Could not find property 'testTask' on root project 'GradleFile
My build file looks like this :
build.gradle
task setup(dependsOn: testTask) <<
{
println "In main execution"
}
// new task
task testTask(type: GradleBuild) {
if (getEnvironmentVariable('RUN_TEST').equalsIgnoreCase("true")) {
buildFile = "../Behave/runtests.gradle"
tasks = ['mainTask']
}
else {
println "Exiting runTests Task"
}
}
setup.doFirst {
println "In first execution"
}
setup.doLast {
println "In last execution"
}
D:\>gradle -q GradleFile/build.gradle setup
I am not looking to make much changes to existing tasks, so is there any other workaround I should try?
I have been through many links but could not find anything that suits this scenario. Looking for suggestions please.
Gradle is sensitive to the ordering of tasks in the build script if a task instance is given in the dependsOn. The task setup depends on task (instance) testTask which, at the moment the build script is compiled, doesn't exist yet. The most common options to solve the issue are:
Define task setup below testTask:
task testTask(type: GradleBuild) {
}
task setup(dependsOn: testTask) {
}
Use a relative path to the task, i.e. the task's name, in the dependsOn
task setup(dependsOn: 'testTask') {
}
task testTask(type: GradleBuild) {
}
Please find more details in Javadoc of Task.

How to get Gradle to fail tests on certain console output

I am trying to configure Gradle to fail a test upon detecting a particular console output, but only if that test did not already fail.
For better context, I am trying to fail a test whenever the word "LEAK" is logged to the console, which Netty will automatically log.
My current solution looks like this:
ext {
testsWithLeak = [:]
}
test {
useJUnitPlatform()
onOutput { test, output ->
if (output.message.contains("LEAK")) {
testsWithLeak[test] = output.message
}
}
afterTest { test, result ->
if (result.getResultType().equals(TestResult.ResultType.FAILURE)) {
testsWithLeak.remove(test);
}
}
finalizedBy 'checkLeaks'
}
task checkLeaks {
dependsOn 'test'
doLast {
testsWithLeak.each{entry ->
throw new GradleException("ERROR: $entry.key produced leak: $entry.value")
}
}
}
This mostly works, and will correctly fail a build upon detecting a "LEAK" message. The problem is that since the test itself is considered a SUCCESS, it appears to be cached and not run again of subsequent builds. I tried calling cleanTest as part of throwing a GradleException, but that did not help.
Note that I use the testsWithLeak variable because I only want to throw this exception if the test did not already fail. A test may fail due to other assertions in which case I do not want to check for leaks (as the GradleException may mask the underlying assertion failure).
Also note that throwing the GradleException as part of the afterTest closure does not appear to fail the build.
How can I:
Fail a build when a test logs this message
Only throw this exception when the test did not otherwise fail
Ensure the build will correctly fail on subsequent attempts (without relying on commandline options)
The test is considered up to date because the test inputs (the class files) and the test outputs (the xml report) have not changed since the previous run. You can do the following so that tests are never up to date
test {
outputs.upToDateWhen {false}
...
}
See
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskOutputs.html
Your testsWithLeak map will not persist between gradle invocations. I suggest you write to a file under $buildDir and add a TaskOutputs.upToDateWhen {...} predicate to check the file exists. Your checkLeaks tasks should also be driven by the file and not a project variable.
Eg:
test {
ext {
testsWithLeak = new java.util.Properties()
leakFile = file("$buildDir/testsWithLeak.props")
}
outputs.upToDateWhen {!leakFile.exists()}
onOutput {...}
afterTest {...}
doFirst {
delete leakFile
}
doLast {
if (testsWithLeak) {
leakFile.withWriter { testsWithLeak.store(it)}
}
}
}

Creating a task that runs before all other tasks in gradle

I need to create an initialize task that will run before all other task when I execute it.
task A {
println "Task A"
}
task initializer {
println "initialized"
}
If I execute gradle -q A, the output will be:
>initialized
>Task A
Now if i'll add:
task B {
println "Task B"
}
Execute gradle -q B, and I get:
>initialized
>Task B
So it doesn't matter which task I execute, it always get "initialized" first.
You can make every Task who's name is NOT 'initializer' depend on the 'initializer' task. Eg:
task initializer {
doLast { println "initializer" }
}
task task1() {
doLast { println "task1" }
}
// make every other task depend on 'initializer'
// matching() and all() are "live" so any tasks declared after this line will also depend on 'initializer'
tasks.matching { it.name != 'initializer' }.all { Task task ->
task.dependsOn initializer
}
task task2() {
doLast { println "task2" }
}
Or you could add a BuildListener (or use one of the convenience methods eg: Gradle.buildStarted(...))
Seems like you aim execution phase, and you want a task precursing each task or just run as a first task in the execution phase?
If you want a task to always execute in every project before each other task after its being evaluated you can add a closure to he main build.gradle:
allprojects {
afterEvaluate {
for(def task in it.tasks)
if(task != rootProject.tasks.YourTask)
task.dependsOn rootProject.tasks.YourTask
}
}
or
tasks.matching {it != YourTask}.all {it.dependsOn YourTask}
You can also use the Task Execution Graph to define the lifecycle. There are few ways of achieving your goal, depending on your needs and a project structure.
The previously suggested solution with dependsOn works fine, but I don't like about it that it changes and clutters the task dependencies. The first solution coming to my mind is using Gradle Initialization Scripts. They are really cool. But, the usage is a bit tedious: currently there is no way to have a default project-local Gradle init script. You have to either explicitly specify the script(s) on command line, or place them in USER_HOME/GRADLE_HOME.
So another solution (already briefliy mentioned by #lance-java) which can be used to run some initialization, like a init task/script, is "build listeners". Depending on how early/late the initialization code should run, I use one of these two:
gradle.afterProject {
println '=== initialized in afterProject'
}
or
gradle.taskGraph.whenReady {
println '=== initialized in taskGraph.whenReady'
}
Here the docs of the Gradle interface and of BuildListener.
Note that some of the events occur very early, and you probably can't use them because of that, like e.g. beforeProject and buildStarted (explanations here and there).

How to execute a task in gradle only after build failed

I have folowing code in biuld.gradle:
task generateReport(type: Exec){
onlyIf{
project.getState().failure
}
doLast{
executable "generateReport.bat"
}
}
tasks.withType(Test)*.finalizedBy generateReport
I've tried before:
task generateReport(type: Exec){
executable "generateReport.bat"
}
tasks.withType(Test)*.finalizedBy generateReport
gradle.afterProject {project, projectState ->
if (projectState.failure) {
doLast{
generateReport
}
}
}
And others examples, but all was useless..
What I've done incorrectly?
You need to hook into the buildFinished lifecycle event, like this:
gradle.buildFinished { buildResult ->
if (buildResult.failure) {
println "Build Failed!"
} else {
println "Build Succeeded!"
}
}
First of all, you have to use a BuildListener as it was mentioned in other answers already. But one more note, you can't call some task the way you did it, with generateReport. So you have rather to use an exec right in the closure of the listener, as:
gradle.buildFinished { buildResult ->
if (buildResult.failure) {
exec {
executable "generateReport.bat"
}
}
}
Project.state is the evaluation state of the project, not some execution state. If you want to do something when the build failed, you should do gradle.addBuildListener() and in the BuildListener implement the method buildFinished() where you can check result.failure to see whether the build failed. If you want to perform some action for each failed test tast, you should instead use gradle.addListener() and give it an implementation of TestListener where you can act on failed tests or test suites. Alternatively you can also add a test listener only to specific test tasks with testTask.addTestListener(). Or to have it even nicer, you can do testTask.afterTest { ... } or testTask.afterSuite { ... }.

How to create gradle task which always runs?

I'm likely overlooking something pretty core/obvious, but how can I create a task that will always be executed for every task/target?
I can do something like:
task someTask << {
println "I sometimes run"
}
println "I always run"
But it would be much more desirable to have the always running part in a task.
The closest I've come is:
task someTask << {
println "I sometimes run"
}
println "I always run"
void helloThing() {
println "I always run too. Hello?"
}
helloThing()
So, using a method is an 'ok' solution, but I was hoping there'd be a way to specifically designate/re-use a task.
Hopefully somebody has a way to do this. :)
Assuming the goal is to print system information, you could either just always print the information in the configuration phase (outside a task declaration), and have a dummy task systemStatus that does nothing (because the information is printed anyway). Or you could implement it as a regular task, and make sure the task always gets run by adding ":systemStatus" as the first item of gradle.startParameter.taskNames (a list of strings), which simulates someone always typing gradle :systemStatus .... Or you could leverage a hook such as gradle.projectsLoaded { ... } to print the information there.
This attaches a closure to every task in every project in the given build:
def someClosure = { task ->
println "task executed: $task"
}
allprojects {
afterEvaluate {
for(def task in it.tasks)
task << someClosure
}
}
If you need the function/closure to be called only once per build, before all tasks of all projects, use this:
task('MyTask') << {
println 'Pre-build hook!'
}
allprojects {
afterEvaluate {
for(def task in it.tasks)
if(task != rootProject.tasks.MyTask)
task.dependsOn rootProject.tasks.MyTask
}
}
If you need the function/closure to be called only once per build, after all tasks of all projects, use this:
task('MyTask') << {
println 'Post-build hook!'
}
allprojects {
afterEvaluate {
for(def task in it.tasks)
if(task != rootProject.tasks.MyTask)
task.finalizedBy rootProject.tasks.MyTask
}
}
What's wrong with invoking it straight from the root build.gradle?
task init << {
println "I always run"
}
tasks.init.execute()

Resources