Can't control configuration order of two plugins

I have a project with two plugins, Plugin A and Plugin B.
Each plugin creates and configures a task. Plugin A creates Task a and Plugin B creates task b.

Each task relies upon some information in extension objects that are configured from the build script. .
Therefore, unless I am missing something, I need to finish the configuration of each task in an afterEvaluate block.

These tasks need to be configured and evaluated in a specific order. b depends on a both for evaluation and execution and I have it defined so that b.dependsOn a.

But no matter what I try the b’s afterExecute occurs before the a’s afterExecute.

How might I fix this?

OK, I got past this but only by implementing a ProjectEvaluationListener inside my plugin. Way too much grief. And I wonder how extensible this solution is. Imagine a situation with three plugins and two ProjectEvaluationListeners with order dependency. Is there any guarantee in what order ProjectEvaluationListeners are notified?

You can use the task name as a string instead of the task object directly.

task a(dependsOn: 'b')
task b

See dependsOn docs for a list of possible values.

Okay, but I see nothing in these documents that goes to my issue of the order in which afterEvaluate blocks defined inside of task definition closures are processed. The way I specified dependsOn did correctly order the EXECUTION of these tasks, but that did not help because the configuration steps (in afterEvaluate closures) were performed in incorrect order.

What does govern the order in which afterEvaluate closures added by various plugins are processed?

I believe the order is undefined and therefore should not be relied upon.

My personal best practice is to avoid afterEvaluate as much as possible and reserve its usage for scenarios where I’m truly stuck (which is rare). I avoid it for the same ordering issue you are seeing (I don’t want to introduce ordering bugs by accident).

Well, I’m truly stuck here, in this use case, which is, taking an oracle JDK tarball and unzipping it into an RPM. The Oracle tarballs have one naming convention and the rpm’s another. The name of the rpm archive is tied up with the version. The version as the rpm sees it depends on the version numbers encoded in the tarball names. I chose to specify this in the build script that will invoke my plugin. Build script provides major version, minor version, and architecture, and the plugin uses these to derive the jdk tarball file name and the output file name. Only after these are read in can the project version be known.

My second plugin does some postprocessing on the rpm, based on the first’s plugin’s task outputs. The names of these output files are not known until afterEvaluate, when the buildScript configuration has been read in.

It would be nice to have some control over evaluation ordering.

Here’s how I might approach these requirements (certainly not the only way to do it).

RpmBuilderPlugin:

  • Create configuration for the jdk tarball dependency
  • Creates task jdkRpmBuild.
    • dependsOn ‘jdkRpmBuildConfig’
    • doLast; build rpm
    • outputs.file …
  • Create task jdkRpmBuildConfig
    • the intent of this task is to delay resolution of the configuration until task exection
    • doLast; configure tasks.jdkRpmBuild
      • IE if using Rpm task type
        • tasks.jdkRpmBuild.from(tarTree(configurations.jdkTarball.files.singleFile)) { into ‘xyz’ }

RpmProcessingPlugin:

  • apply plugin: RpmBuilderPlugin // because it depends on it
  • create task processJdkRpm
    • inputs.files tasks.jdkRpmBuild // creates an implicit task dependency
    • doLast; process inputs.files.

build.gradle

  • apply plugin(s)
  • define version information
  • create dependency in jdkTarball configuration based on version information

Thanks, that looks good, but I’ve basically gotten it working by the method I mentioned above, the ProjectEvaluationListener, which may not be extensible but does work. Your approach looks quite good though.

Gradle Configurations aren’t something I’m familiar with (hole in my learning).

But this looks like the heart of it:

As far as

is concerned, please note bug report I opened on the tarTree method, which I believe would bite me in your scenario as well. Basically, I could only get the rpm to work by executing tar into a temporary directory.

But I do find your remarks quite helpful.

Actually, once the dependency on the tarball is defined, I could use a regular expression to chew that up and derive the project/rpm version since there is a definite relationship between the two.

Having trouble applying your strategy due to the untarring issue. Can’t get my workaround of using Exec to do the untarring to work in your scenario, because the I can’t properly handle the fact that one of the command-line parameters to tar cannot be known until the configuration task has been configured.

The Exec task seems to have some special requirements of its own:

  • If I configure the Exec from the configure task’s doLast{} Gradle complains that the Exec command line is null.
  • If I configure the Exec normally, the task doesn’t know about the input file yet, which is one of the tar parameters…
  • If I try to split this up, defining the config’s command line normally but adding the last argument from the configuration task, I get an error telling me about an unexpected end of file. Evidently the args is not getting processed in the proper sequence.

I still like your idea, but it looks like this bug and the finickiness of the Exec command are preventing me from doing it.

Where is the input file coming from?

I set that up as you suggested, as a configuration dependency, a file dependency in this case,

                    `args '-f', project.configurations.oracleTarBall.singleFile`

You can make that lazy using a GString: "${project.configurations.oracleTarBall.singleFile}"

Thanks, Stefan, but it didn’t help, at least not as I understood your suggestion:

	Task untar    = project.tasks.create("untar", Exec) { 
		workingDir tmpPath
		commandLine 'tar', '-zx'
		args '-f', "${project.configurations.oracleTarBall.singleFile}"					 
	}

This produced the following error, just as the plugin was being applied.

FAILURE: Build failed with an exception.

* Where:
Build file '/home/sc1478/jdkinstaller/build.gradle' line: 18

* What went wrong:
A problem occurred evaluating root project 'jdkinstaller'.
> Failed to apply plugin [id 'vt.jdk.rpm']
   > Expected configuration ':oracleTarBall' to contain exactly one file, however, it contains no files.

Sorry I forgot to actually use the lazy syntax:

"${-> project.configurations.oracleTarBall.singleFile}"

In general I’d lean towards a custom task though. Then all you need to know is that Project.file() can take a callable. Less Groovy magic :slight_smile:

task untar(type: Untar) {
  file { configurations.oracleTarBall.singleFile }
}

class Untar extends Exec {
  Object file
  Untar() {
    commandLine 'tar', '-zx'
  }

  @TaskAction
  void exec() {
    args '-f', getFile()
    super.exec()
  }

  @Input
  def getFile() {
    project.file(file)
  }
}

Oops. didn’t see this until now. In the meantime I’ve developed a pretty reasonable solution just working with the source file name without needing to mess with configurations at all.