Gradle task doLast if task fails - gradle

I want a clean build, where you can see exactly what happened, but all information is preserved - so essentially for every task, I want it to write the output to a file, and only display it if the task fails.
I've tried to achieve this in gradle - but am being defeated because doLast doesn't run if the task fails. Here's my "almost" working pattern:
task fakeTask( type: Exec ) {
standardOutput = new ByteArrayOutputStream()
commandLine 'cmd', '/c', 'silly'
doLast {
new File("build.log") << standardOutput.toString()
if (execResult.exitValue != 0) println standardOutput.toString()
}
}
is there any alternative to doLast that will run any time? Any other way of doing this? - especially as I want to do this for every single task I have?

this is my final solution:
tasks.withType( Exec ) {
standardOutput = new ByteArrayOutputStream()
ignoreExitValue = true
doLast {
new File("gradle.log") << standardOutput.toString()
if (execResult.exitValue != 0)
{
println standardOutput.toString()
throw new GradleException(commandLine.join(" ") + " returned exitcode of " + execResult.exitValue )
}
}
}

Add a ignoreExitValue true to your task definition to suppress throwing of an exception when the exit value is non-zero.

You can use the task graph as documented at https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:task_execution to execute code after a task has run.
gradle.taskGraph.afterTask { task, state ->
if (task instanceof ExecTask && state.failure) {
file('gradle.log') << standardOutput.toString()
}
}

Related

Stop execution of finalizedBy task, or only execute follow-up task on a condition

I'm using the com.google.cloud.tools.appengine gradle plugin, which has a task appengineDeploy.
I have two tasks that configure the appengineDeploy task before executing it. My current solution looks something like that:
task deployTest() {
doFirst {
appengine.deploy {
version = 'test'
...
}
}
finalizedBy appengineDeploy
}
task deployProduction() {
doFirst {
appengine.deploy {
version = '7'
...
}
}
finalizedBy appengineDeploy
}
Now I wanted to add a security question before the deployProduction task is executed, like this:
println "Are you sure? (y/n)"
def response = System.in.newReader().readLine()
if (response != 'y') {
throw new GradleException('Task execution stopped')
}
The problem is, by defintion the finalizedBy task is executed even if my task throws an exception, and that is exactly the opposite of what I want.
I can't change it to appengineDeploy dependsOn deployTest and call appengineDeploy as I have two tasks with different configuration.
And I can't change the appengineDeploy task as it comes from a plugin.
Is there any other way I can either stop the execution of appengineDeploy, or use something other than finalizedBy to execute that task after my deploy task?
One option is to leverage onlyIf to decide whether to execute the task, for example by examining a project property.
Here's a litte demo, given that task appengineDeploy is a task contributed by a plugin (see comment for details):
plugins {
id 'base'
}
ext {
set('doDeploy', false)
}
appengineDeploy.configure {
onlyIf {
project.ext.doDeploy
}
}
task deployTest() {
doFirst {
println 'deployTest()'
project.ext.doDeploy = true
}
finalizedBy appengineDeploy
}
task deployProduction() {
doFirst {
println 'deployProduction()'
println "Are you sure? (y/n)"
def response = System.in.newReader().readLine()
if (response == 'y') {
project.ext.doDeploy = true
}
}
finalizedBy appengineDeploy
}
Another option is to disable the task, which goes like this:
task deployProduction() {
doFirst {
println 'deployProduction()'
println "Are you sure? (y/n)"
def response = System.in.newReader().readLine()
if (response != 'y') {
project.tasks.appengineDeploy.enabled = false
}
}
finalizedBy appengineDeploy
}
I modified the answer from thokuest a bit (thanks for the help!), to prevent executing any tasks inbetween.
I created it as extension method since I needed it more than once:
ext.securityQuestion = { project, task ->
println "Are you sure you want to execute ${project.name}:${task.name}? (y/n)"
def response = System.in.newReader().readLine()
if (response != 'y') {
project.tasks.each {
if (it != task)
it.enabled = false
}
throw new GradleException("Execution of ${project.name}:${task.name} aborted")
}
}
and my task now looks like this:
task deployProduction() { task ->
doFirst {
securityQuestion(this, task)
}
finalizedBy appengineDeploy
}

Gradle returns exit code as 0 though one of its tasks failed

Assume I run gradle tasks as below:
./gradlew cleanTest task1 task2 --info --rerun-task
Here if task1 fails and task2 passes, the exit code for this run is still 0. I want it to return non zero if any of the tasks fails.
These are the task added to my build.gradle file
task task1(type: Test) {
maxParallelForks = 1
ignoreFailures true
testLogging {
events 'started', 'passed', 'skipped', 'failed'
exceptionFormat "full"
showStandardStreams = true
afterSuite { desc, result ->
if (!desc.parent) { // will match the outermost suite
println "RESULT: ${result.resultType} \n" +
"TOTAL TESTS: ${result.testCount}, " +
"SUCCESS: ${result.successfulTestCount}, " +
"FAILED: ${result.failedTestCount}, " +
"SKIPPED: ${result.skippedTestCount}"
finalResult = "${result.resultType}"
failedTest = "${result.failedTestCount}"
}
}
}
include "org/company/proj/test/Task1/Task1Test.class"
}
task task2(type: Test) {
maxParallelForks = 1
ignoreFailures true
testLogging {
events 'started', 'passed', 'skipped', 'failed'
exceptionFormat "full"
showStandardStreams = true
afterSuite { desc, result ->
if (!desc.parent) { // will match the outermost suite
println "RESULT: ${result.resultType} \n" +
"TOTAL TESTS: ${result.testCount}, " +
"SUCCESS: ${result.successfulTestCount}, " +
"FAILED: ${result.failedTestCount}, " +
"SKIPPED: ${result.skippedTestCount}"
finalResult = "${result.resultType}"
failedTest = "${result.failedTestCount}"
}
}
}
include "org/company/proj/test/Task2/Task2Test.class"
}```
From the discussion I gather that ignoreFailures and failFast on the test task do not meet your requirements, right? I could see a use-case where you want to make sure that both test tasks are actually executed, even if the first task has failed tests, but at the same time you want the build to fail in the end when there are failed test cases in any test task.
First of all you might want to think about this. Usually, the intention is to save execution time on the server (or locally) by failing and aborting the build as soon as possible.
However, if you really want to build something that meets your requirements, you gave all the details for that already: You could add a project variable that stores the result of the tests and create an additional task that checks that property and fails if there were failed tests.
ext {
failedTests = false
}
task test1(type: Test) {
ignoreFailures true
afterSuite { desc, result ->
if (result.failedTestCount > 0) {
failedTests = true
}
}
}
task test2(type: Test) {
ignoreFailures true
afterSuite { desc, result ->
if (result.failedTestCount > 0) {
failedTests = true
}
}
}
task verifyTestResults() {
//dependsOn("test1", "test2") // static naming of test tasks
dependsOn tasks.withType(Test) // dynamically depend on all test tasks
doLast {
if(failedTests) {
throw new GradleException("There were failing tests!")
}
}
}
Now, you only need to call gradlew verifyTestResults.

Gradle - How to execute command line in doLast and get exit code?

task executeScript() {
doFirst {
exec {
ignoreExitValue true
commandLine "sh", "process.sh"
}
}
doLast {
if (execResult.exitValue == 0) {
print "Success"
} else {
print "Fail"
}
}
}
I'm getting the following error
> Could not get unknown property 'execResult' for task ':core:executeScript' of type org.gradle.api.DefaultTask.
If I move the commandLine to configuration part everything works fine. But I want commandLine to be in action block so it wont run every time we execute some other gradle tasks.
Use gradle type for your task
task executeScript(type : Exec) {
commandLine 'sh', 'process.sh'
ignoreExitValue true
doLast {
if(execResult.exitValue == 0) {
println "Success"
} else {
println "Fail"
}
}
}
This will work for you...
You can read more about the Exec task here
Alternative syntax to execute external command and get its return code:
doLast {
def process = "my command line".execute()
process.waitFor()
println "Exit code: " + process.exitValue()
}

Change global ext property in gradle

I'm trying to write a build script for different environments. But the global property is not updated for specific tasks.
Here is the script:
ext {
springProfilesActive = 'development'
angularAppBasePath = '/test/'
}
task setActiveProfiles {
doLast {
if (project.hasProperty('activeProfiles')) {
springProfilesActive = project.property('activeProfiles')
}
}
}
task setProperties(dependsOn: ':setActiveProfiles') {
doLast {
if (springProfilesActive != 'development') {
angularAppBasePath = '/'
}
println springProfilesActive
println angularAppBasePath
}
}
task buildAngular(type: Exec, dependsOn: ':setProperties') {
workingDir angularPath
commandLine 'cmd', '/c', "npm run build.prod -- --base ${angularAppBasePath}"
}
If I run buildAngular -PactiveProfiles=integration the properies are correctly set. But the angularAppBasePath is still the old /test/ value in the npm command. Output:
Executing external task 'buildAngular -PactiveProfiles=integration'...
:setActiveProfiles
:setProperties
integration
/
:buildAngular
> angular-seed#0.0.0 build.prod C:\myapp\src\main\angular
> gulp build.prod --color --env-config prod --build-type prod "--base" "/test/"
Why the propery are changed in the setProperties task but remains the old value in the buildAngular task?
Try to rewrite your setActiveProfiles and setProperties tasks as follows:
task setActiveProfiles {
if (project.hasProperty('activeProfiles')) {
springProfilesActive = project.property('activeProfiles')
}
}
task setProperties(dependsOn: ':setActiveProfiles') {
if (springProfilesActive != 'development') {
angularAppBasePath = '/'
}
println springProfilesActive
println angularAppBasePath
}
This behavior is caused by different build lifecycles. You modify your variable during execution phase (within doLast closure), but use it at the configuration phase, which happens just before the execution.You can read about build lifecycle in the Gradle official user guide.

Task running unnecessarily

I have written a task to run my project using a main class chosen via user input only it is prompting me to choose a main class when I run gradle tasks. Why is this and how do I prevent it?
task run(dependsOn: "classes", type: JavaExec) {
description "Executes the project using the selected main class"
def selection = null
def mainClasses = []
// Select the java files with main classes in
sourceSets.main.allJava.each {
if(it.text.contains("public static void main")) {
def pkg = relativePath(it) - 'src/main/java/' - '.java'
pkg = pkg.tr "/", "."
println "${mainClasses.size()}. $pkg"
mainClasses << pkg
}
}
// Now prompt the user to choose a main class to use
while(selection == null) {
def input = System.console().readLine "#? "
if(input?.isInteger()) {
selection = input as int
if(selection >= 0 && selection < mainClasses.size()) {
break
} else {
selection = null
}
} else if(input?.toLowerCase() == "quit") {
return
}
if(selection == null) {
println "Unknown option."
}
}
main = mainClasses[selection]
classpath = sourceSets.main.runtimeClasspath
}
Gradle has a configuration phase and an execution phase.
The fact that your build logic is actually run when calling "gradle tasks" is because your build logic is in the tasks configuration section. If you want to move it to the execution phase, you should introduce a doFirst or doLast closure
See gradle build script basics for more details or this post

Resources