Task created by plugin always up-to-date


(Patrik Schalin) #1

Hi All,

I’m trying to write a base plugin for our Gradle based projects. Idea is to reduce boilerplate build code.

Currently I’m fighting an “always up-to-date” problem with one of my provided base tasks.

Given

class BasePlugin implements Plugin<Project> {

void apply(Project project) {
	...
    project.task('unpackBuildTools', type: Copy) {
        group 'Build'
        description 'Unpacks resolved build related tools into the ${project.buildDir} directory.'
        doLast {
            project.configurations.tools.resolvedConfiguration.resolvedArtifacts.each { toolDependency ->
                copy {
                    from zipTree(toolDependency.file)
                    into "${project.buildDir}/${toolDependency.name}"
                }
            }
        }
    }
	...

When I run a build that uses this plugin the unpackBuildTools task is always UP-TO-DATE and I’m running out of ideas…

Any insights would be highly appreciated.


(Lance Java) #2

You’re not actually using the copy task, you’re explicitly calling project.copy in a doLast. Net effect is that there are no task inputs and hence no up-to-date checking.

Try this:

project.task('unpackBuildTools', type: Copy) {
   group 'Build'
   description 'Unpacks resolved build related tools into the ${project.buildDir} directory.'
   project.configurations.tools.resolvedConfiguration.resolvedArtifacts.each { toolDependency ->
      with project.copySpec({
         from zipTree(toolDependency.file)
         into "${project.buildDir}/${toolDependency.name}"
      })
   }
}

(Patrik Schalin) #3

Hi and sorry for my delay.

I didn’t realize that I was actually doing a project.copy, thanks for pointing that out…

I’m having trouble running your suggestion due to;
Cannot change dependencies of configuration ':tools' after it has been resolved.

I’ve tried several different ideas but seem to come back to constructs like my original one. Partly because I do want the lazy eval of the configuration and I have no hard requirement on task type.

Since normal tasks are run every time if they do not specify intputs/outputs I have the following running now;

project.task('unpackBuildTools') {
        group 'Build'
        description 'Unpacks resolved build related tools into the ${project.buildDir} directory.'
        doLast {
            project.configurations.tools.resolvedConfiguration.resolvedArtifacts.each { toolDependency ->
                project.copy {
                    from project.zipTree(toolDependency.file)
                    into "${project.buildDir}/${toolDependency.name}"
                }
            }
        }
    }

(Lance Java) #4

If you put it all under a single root directory you could use the configuration as an input and the root dir as an output

project.task('unpackBuildTools') {
   inputs.files project.configurations.tools
   outputs.dir "${project.buildDir}/unpack"
   ...
   doLast {
      ...
      copy {
         ...
         into "${project.buildDir}/unpack/${toolDependency.name}"
      }
   }
}

@see TaskInputs.files(…)
@see TaskOutputs.dir(…)

Another option is to delay the creation of the task so that the configuration is resolved later

project.afterEvaluate {
   project.task('unpackBuildTools', type: Copy) {
      ...
   }
}

(Patrik Schalin) #5

Works like a charm.

Creating the task in afterEvaluate…why didn’t I think of that!? Maybe too obvious… :wink: :smiley:

Thank you very much!
Patrik