How to re-configure tasks from plugin applied to root project before task graph is populated but after tasks are configured?

Hi guys,

I’m having difficulties re-configuring tasks in a multi-project module because I would like to access them after they are configured, but the whenTaskAdded/withType deliver task objects after they are created but before they are configured (e.g. when a configure closure is specified when adding the task).

Here is my scenario, maybe somebody could point out a different mechanism to achieve what I’m trying to:

  • I have a “container install” plugin which provides task for installing a container. For the purposes of this example let’s assume it is a web container, so the plugin provides an “install” task rule which would create an instance of the web container somewhere on the file system - the instance will be named according to the rule and there cannot be 2 instances with same name

  • I have a separate “container deploy” plugin which allows deploying built artifacts to a container. The “container deploy” plugin adds a “deploy” task which needs to find the container installation directory using its “container name” and deploy the artifacts However, since I wanted the “deploy” tasks to be independent from the “container” plugin, so that they could also be used to deploy into a container which is already installed instead of being created as part of the current build, the “deploy” tasks do NOT have an explicit dependency to the install task.

Thus this covers two scenarios: - have an already installed container, apply just “container deploy” and configure “deploy” task specifying its location - apply “container install” to root project, apply “container deploy” to each subproject - the root project installs the container and each subproject deploys its artifacts into it

For the latter scenario I need to re-configure the “deploy” tasks with the location of the container, and I’m trying to do this in the “container install” plugin:

“container deploy” # apply

// add deploy task rule “deploy” for installing into specified container

String container = … // extract container name from task name

project.tasks.add([name: "deploy$container ", type: Deploy.class], {

containerName = container

})

“container install” # apply project.gradle.projectsEvaluated {

project.rootProject.subprojects { Project proj->

proj.tasks.withType(Deploy.class) { Deploy deploy ->

String containerName = deploy.getContainerName() // returns NULL since Deploy task is NOT configured yet

deploy.conventionMapping.containerLocation

= { containerConvention.baseInstallDir + File.separator + containerName }

deploy.dependsOn(project.tasks.getByName(“install$containerName”))

}

} }

However, since the withType {} closure is called after adding the Deploy task to the TaskContainer but BEFORE it is configured, trying to access ‘containerName’ in the closure returns null :frowning:

I know that the container name can be extracted by parsing the task name, but in the real example I have more configuration settings that I would like to access in the withType {} closure. I have been thinking of adding this re-configuration in the “container deploy” plugin - e.g. instead of “container install” to confiure “deploy” tasks, deploy tasks might search for “container install” and re-configure themselves, but this does not seem very elegant.

Does somebody sees anything wrong in this approach? Are there any other suggestions for how this could be accomplished?

Regards,

Detelin

Hi Detelin, do you mind editing your post and wrapping your code blocks in <code></code> tags so they are easier to read?

I just saw there is some HTML support and tried to but re-sending the new post opened up this same page without doing any changes and now the “edit” button is missing. And I’m still signed in …

Re-logging does not seem to help, still no “edit” button, if you could go ahead and delete the post and I will re-post it with proper formatting.

Do you not see an edit link like in the picture below?

Nope :frowning:

It seems that you can’t edit a post once replies have been added, so don’t worry about the formatting.

Whenever you need laziness, closures are your friend.

tasks.withType(Deploy).all { deployTask ->
    deployTask.dependsOn { tasks.getByName("install$deployTask.containerName") }
 }

The closure given to dependsOn will be evaluate just before assembling the task graph, which is after evaluation.

Also note the use of the all() method which applies the closure to all current and future items (in this case Deploy tasks).

As for the other configuration, assuming you want to wire property values of the install task onto the deploy task, you can do a similar thing

tasks.withType(Deploy).all { deployTask ->
    def getInstallTask = { tasks.getByName("install$deployTask.containerName") }
    deployTask.dependsOn { getInstallTask() }
    deployTask.conventionMapping.someConfig = { getInstallTask().somePropertyValue }
}

The getInstallTask().somePropertyValue statement will only be evaluated when something tries to read deployTask.someConfig. I’m not sure if that’s the kind of thing you mean though.

Thanks Luke, that’s exactly what I wanted. Closures are simply great!

In the original code, the closure is passed directly to the TaskContainer.add method. Shouldn’t callbacks fire only after the configuration has been applied in this case? It seems unnecessary to force the user into laziness here.

We could also add a TaskContainer.add(name, type, closure) overload to make it easier to atomically add and configure a typed task.

Right, it would be great if TaskContainer first creates and configures the task and then adds it to internal store. Hopefully this won’t cause any issues.