doLast does not execute when task fails

I am attempting to capture some test/verification output of the check task. I have this configuration (using Kotling script):

tasks.check {
    doLast {
        println("Copying files")
        project.copy {
            from("build/resources/main/reports/index.html")
            into("build/reports/")
        }
        println("Copied files")
    }

Now I would expect this to always copy the specific file but unfortunately, it does nothing if check fails (which kind of defeats the purpose). Very odd semantics that doLast should only execute upon success.

I have seen various posts suggesting to use set ignoreExitValue to true but apparently that isn’t a property of the check task. I’ve also tried creating a separate task to do the job and using finalizedBy to ensure that the custom task executes after check like this:

tasks.check {
    finalizedBy("copyReportRoot")
}

tasks.register("copyReportRoot") {
    println("Running copyReportRoot...")
    doLast {
        println("Copying files")
        project.copy {
            from("build/resources/main/reports/index.html")
            into("build/reports/")
        }
        println("Copied files")
    }
}

This also doesn’t run the doLast block upon failure. If I pull the project.copy block out of doLast, it runs before check which again defeats the purpose.

Any help? Thanks!

doLast is registering an additional action to do as task action.
You can logically consider it being part of the tasks implementation.
So if the task runs and the actions before were successful, this action is run.
If one of this does not hold, it is not run.

finalizedBy would do what you expect, if actually check would be the task that failed.
But check is a lifecycle task without any own actions (unless you add some which you usually shouldn’t).
It only depends on various verification tasks so that you can execute the check task and thus execute all those verifications.
But if one of those verification tasks fails, then the check task is not even started.
And if the check task is not started, then its finalizedBy will also not run.

ignoreExitValue of course also does not work on check. That is for Exec type tasks to ignore the exit value of the externally started process and then eventually check it yourself. But check is not such a task and also is not the one failing.

You should probably do the finalizedBy on the task that actually creates that report you want to copy, not on the check task.

Hmmm, okay. I made it work. Still, the model is very, very confusing and counter-intuitive. Thanks for your help.

Actually, I do not agree and find it pretty intuitive and clear.
But that of course is a very subjective topic. :slight_smile:

It’s often hard for people who understand something well to look at it with fresh eyes. In software engineering, people often talk about proper naming as a critical piece in self documenting code. doLast doesn’t really speak to success or failure. ‘onSuccess’ would be a more meaningful name, or you could add a parameter to doLast like ignorePreviousFailures. And I’m not the only person struggling with this. I went through quite of few stackoverflow articles, etc.

BTW, I’m not criticizing you. Your explanation was quite clear and much appreciated.

But let me ask this, let’s say I wanted to always run some task as the last action of a specific lifecycle task (such as check) regardless of its success, how would I do that? Is it not possible?

It’s often hard for people who understand something well to look at it with fresh eyes.

Definitely.

people often talk about proper naming as a critical piece in self documenting code.

Sure, you just never get it intuitive enough for every person unfortunately. :smiley:

‘onSuccess’ would be a more meaningful name,

Feel free to open a feature request to change that naming, then we’ll see what the Gradle folks think about it. :slight_smile:
Actually, the question also is to what doFirst should be renamed to then.
onSuccessFrist?
Because that action is also only done if previous things, that is other tasks or doFirst actions registered later, were successful. If there actually were things to do before.
The list of actions to which you add with doFirst and doLast is just a list of actions that compose the overall task action and they are only been done if the task as a whole is done.
And if there is no task action at all so far, onSuccess also does not really mean "if other actions in the list of actions were successful, as there are no previous actions, unless somone then uses doFirst.

or you could add a parameter to doLast like ignorePreviousFailures

Also maybe worth a feature request.
But then again, the question is, which failures.
When just ignoring failures in previous actions of the task, then it would not help in your case, as that is not what is happening.
And when ignoring any failures, that is most probably not too helpful anyway in most situations.

But let me ask this, let’s say I wanted to always run some task as the last action of a specific lifecycle task (such as check ) regardless of its success, how would I do that? Is it not possible?

As I said, that sounds like finalizedBy task for check task. But your problem is, that it is not the check task that is failing. It is a task check depends on that is failing. This causes check to not be executed at all as a dependency failed. And due to that neither doFirst, nor doLast, nor finalizedBy will ever be considered as the whole check task is not reached. Also an operation completion listener that is notified about finished tasks no matter whether successful or not would not help, because again, it is not check that is failing and check is never even started.

So you would probably need a task that you register to all tasks check depends on directly and transitively as finalizedBy task and also that would not be trivial to realize if even possible.