How do I make sure code is executed after ALL instances of a particular task have completed successfully?


(Jamie Cooper) #1

Suppose I have project P, with subprojects S1, S2, S3.

P doesn’t do anything (no source, no builds, nothing to execute) - it is just a container for S1, S2, S3, and a place to put the common build.gradle file with subproject config, etc.

Each subproject may/may not have a build.gradle of it’s own.

Now suppose the ‘maven’ plugin is included, and the gradle command line includes ‘uploadArchives’ task to be called.

If I do a simple ‘uploadArchives.doLast()’, my code is called 3 times - once for each subproject. However, I only want it to be called once total, and only after all 3 uploadArchives instances are successful. I don’t necessarily want it to be called as the very last thing before the entire script is finished… I really want it to happen immediately after the last uploadArchives, just in case there are other steps after that.

Any ideas?


(Lance Java) #2

Something like:

task foo << {
   println "foo" 
} 

subprojects {
   tasks.withType(UploadArchives).each {
      foo.mustRunAfter it
   } 
} 

(René Groeschke) #3

small note on this: you can skip the .each to ensure it is lazy applied to tasks not yet added to subprojects. Otherwise only the tasks already created are considered


(Jamie Cooper) #4

Unfortunately, this has to work with every version of gradle back to 2.4 (at least), and withType must’ve been introduced later.

I tried this instead: tasks.matching { it.getClass().name == … }

and to make sure I caught all the instances, I put that in an afterEvaluate().

Problem is… I tried this in a project with 12 subprojects, and I got “foo” output 12 times, and the came out BEFORE everything else. Even before the ‘Configuring’ phase.

That’s the problem I’m trying to solve - I only want to see “foo” once, regardless of how many times the target tasks is called, and I only want that one time to be AFTER all instances of the target task have succeeded.


(Stefan Oehme) #5

You are executing logic during the configuration phase. Make sure the task’s logic is in a doLast {} block


(Jamie Cooper) #6

Ok, I am now getting the output only once, after all uploadArchives instances have finished.

Only one problem left… my task doesn’t get called unless I explicitly list it on the command line, as in “gradlew foo”.

If possible, I want this to be called whenever 1+ uploadArchives instances are executed, without having to explicitly list it by name.


(Stefan Oehme) #7

Use finalizedBy (which creates a hard dependency) instead of mustRunAfter (which just ensures ordering if the task is called)


(Jamie Cooper) #8

Yes, but finalizedBy executes whether the task succeeds or fails. I only want my code to execute if all instances of the task succeed.


(Stefan Oehme) #9

You could use !task.state.failure in the onlyIf condition of your task.


(Jamie Cooper) #10

Turns out I’m not as close as I thought I was earlier. My code was only being called once because I was explicitly listing it once on the command line.

So, let’s start over.

I tried this:

uploadArchives.finalizedBy{println 'foo'}

I saw ‘foo’ 12 times, before the config phase.

I tried this:

uploadArchives.doLast{println 'foo'}

I saw ‘foo’ immediately after each ‘uploadArchives’… again, 12 times.

I have tried the suggested mustRunAfter, etc., and so far, nothing has worked the way I need it to work.

And of course, we haven’t gotten to the part where I want to do this programmatically in a plugin, rather than explicitly in a build.gradle.

A co-worker here has implemented a somewhat kludgy fix for this problem, but we have both been hoping there’s a better way.

Basically, he has an afterEvaluate block that sets uploadArchives.doLast{} with a call to his own routine (not a task, just some code). Also in the afterEvaluate, he sets a property on the rootProject which has a count of every project from ‘allprojects’ that has an uploadArchives task. Another property is set that is just a counter.

In his routine, he increments the counter property, and checks to see if the current counter is equal to the count of projects with an uploadArchives task. If not, he just saves the new count and exits. If so, he executes his code.

That’s the only way we’ve gotten this to work the way we want it to work, so far, but it is far from elegant… gradle already knows this stuff - it already knows when the last instance of uploadArchives is called, etc. We just haven’t found a way to get it to tell us when it’s there without the counters.


(Stefan Oehme) #11

The logic you want to execute must be in a task. The line above is executing the println statement at configuration time. The reason why finalizedBy also accepts a closure is to allow you to lazily compute the finalization tasks, not to put the actual finalization logic in there :slight_smile:

Did you read the user guide chapter on tasks? If yes, could we improve it to avoid such confusion in the future?

Here’s a short example of what you want to do:

task finalizeStuff {
  doLast {
   // your finalization logic here
  }
}

uploadArchives.finalizedBy(finalizeStuff)

(Jamie Cooper) #12

Yes, I’ve read the chapter on tasks, build cycle, etc. There are obvious conceptual problems here, on my part at least. I can’t think of a way to improve the doc, at least until I have things straight as to how to make this all work.

I tried implementing what you show, and I’m running into something else.

I changed it to this

task foo {
doLast {
println state.failure
}
}

Just to see what I can get out of it… which turned out to always be ‘null’, which makes sense, as task foo always succeeds. However, I need to check the state of all the tasks I’m finalizing, to see if the combined state is success or failure - I couldn’t care less about the state of my finalizer task (at least at the moment), I just want to make sure I don’t attempt my actual code unless they all succeeded.


(Stefan Oehme) #14

If any of the uploadArchives tasks fails, it’s failure will be != null. So you just check whether any of the upload tasks has failed in the onlyIf {} condition of your finalizeTask.


(Jamie Cooper) #15

So first I have to figure out how to add an onlyIf to my custom task code in my plugin.

(This brings up an issue in the docs… there is very little documentation on how to do anything programmatically. Almost all examples I’ve seen show how to do things in a build script, but not in source.)

Then I have to iterate every uploadArchives task to see if any of them failed.

Does that sound like what you’re saying?

I tried this:

task foo {
doLast {
tasks.matching {it.getClass().name == ‘uploadArchives’}.each { ua ->
if (ua.state.failure != null) {
println “Got one.”
}
}
}
}

And on my test build where I force uploadArchives to fail I still get no output.


(Stefan Oehme) #16

That’s because there is no difference. You work against exactly the same APIs as your buildscript. The buildscript is executed against the Project object, just like a plugin takes a Project argument. Have a look at the reference documentation and JavaDoc for lower-level details of each interface.

Yes it does.

  1. The type of the ‘uploadArchives’ task is Upload.
  2. Use withType for a cleaner version of filtering by type

(Jamie Cooper) #17

I changed the line to

    tasks.withType(Upload).each { ua ->

and I still don’t get output, even though I’m forcing an uploadArchives task to fail.

:failure:uploadArchives FAILED
:foo

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ‘:failure:uploadArchives’.

Even if it had worked, I’m not guaranteed that every task of type Upload is also an uploadArchives task.


(Jamie Cooper) #18

I modified my code slightly…

task foo {
doLast {
tasks.withType(Upload) { ua ->
print 'Found a task… that '
if (ua.state.failure == null)
{
println ‘succeeded.’
}
else
{
println ‘failed.’
}
}
}
}

So I can see just how many tasks it it finding, and their state. Only one is being found. :-\

:failure:uploadArchives FAILED
:foo
Found a task… that succeeded.

FAILURE: Build failed with an exception.

  • What went wrong:

(Jamie Cooper) #19

Alright, finally got it to work…

task foo {
doLast {
project.rootProject.allprojects {proj ->
proj.tasks.withType(Upload) { ua ->
print 'Found a task… that '
if (ua.state.failure == null)
{
println ‘succeeded.’
}
else
{
println ‘failed.’
}
}
}
}
}


(Jamie Cooper) #20

I am still trying to get this to work in a plugin.

To recap, I want something to execute after ALL instances of uploadArchives have completed successfully.

So, I have setup code that looks like this:

In the apply() method for the plugin

    project.afterEvaluate {
        addMetadataTask()
    }

The method called

private void addMetadataTask() {
    project.tasks.create(name: 'uploadBuildMetadata', type: UploadBuildMetadata)

    project.rootProject.allprojects { proj ->
        proj.getTasksByName('uploadArchives', false).each {
            it.finalizedBy('uploadBuildMetadata')
        }
    }
}

Then my custorm task:

@TaskAction
def uploadBuildMetadata() {

So… this gets called every time uploadArchives is called. I only want it to be called once, after the very last uploadArchives is called.

Anyone know how to do that in code, rather than in a build.gradle script?


(Stefan Oehme) #21

The plugin looks reasonable under the condition it is only applied to the root project. Otherwise you’ll create several metadata task. You only want one.

To make the plugin foolproof, add a condition that only adds this task if the plugin is applied to the root project.