Domain collection rules get applied before initialization

The build below is printing Second before First. I find it weird, as I thought that the onjects are initialized before they are added to the domain collection.

tasks.addRule("...") { taskName ->
    task(taskName, type: JavaExec) {
        ext.foobar='bazqux'
        println "First ${it}(${ext.properties})"
    }
}

p.tasks.withType(JavaExec).all { task ->
    println "Second $task($task.ext.properties)"

    def c = configurations.maybeCreate("baz${task.foobar}")  // fails here - foobar is not set yet
    task.outputs.files.each { f ->
        p.artifacts.add(c.name, [file: f, builtBy: task])
    }
}

Considering this, is there a good pattern to write a rule that depends on a task init state? The use-case is to have a reactive plugin which responds to the settings of another plugin maintained by a separate team.

(Apologies for the contrived example.)

How annoying. It seems that Gradle is doing the following

  1. Creating the task
  2. Adding the task to the TaskContainer (DomainObjectCollection)
  3. Running the configuration closure to configure the task

It seems logical that 3 should occur before 2 but that’s not the case based on your printlns. This seems like a bug to me, but fixing it might cause regressions.

Or more likely, Gradle asks the container to create the object, so the factory can decorate and inject it before we see it. The container decides that while we are at it, why not run the rules too…

We find it counterintuitive only because we perceive the closure at creation to have the semantics of a constructor, while in fact it is more like a convenient init method.

Fwiw I don’t think that strengthening the semantics of the construction initializer should break many things - the ordering is not guaranteed anyway, and I would doubt there are cases where the init closure depends on values set by the rules. If there is a case where it overrides values set by the rules, that is easily fixable by adding a check in the rule.

In other words - a perfect candidate for late addition to 6.0 :slight_smile:

The problem is that tasks.withType(...) adds a listener to the collection. As soon as a task is added to the collection, your listener will fire. Ideally gradle would add the Task to the collection after running the configuration closure. But it seems that it adds it to the collection before (so your listener fires early).

Can you try

tasks.create(taskName, JavaExec) {...} 

Instead of

task(taskName, type: JavaExec) {...} 

Reading the javadoc it says

Creates a Task with the given name and type, configures it with the given action, and adds it to this container

The javadocs seem to say its added to the collection after configuring it

It all leads to here:

We need to move line 66 after line 70.

How annoying! It calls

add(object)

before it calls

configuration.execute(object)

This is why your listener fires early. That seems like a bug to me

I created a bug report

2 Likes