Best way to extend and reconfigure built-in tasks?


(Eugene Janusov) #1

It often seems convenient to extend one of the built-in tasks with some custom configuration logic in order to avoid copy-pasting of that logic. For instance, I have a number of tasks, which generate configuration files from templates. This is obviously done with the ‘Copy’ task. To make it more convenient I’ve implemented tiny ‘GenerateTemplate’ task that extends ‘Copy’.

class GenerateTemplate extends Copy {
      String template
    File target
      def GenerateTemplate() {
        from(project.templatesDir)
    }
      @Override
    protected void configureRootSpec() {
        into(target.getParent())
        include(template)
        rename(template, target.getName())
          super.configureRootSpec()
    }
  }

This works exactly as I need, but overriding ‘configureRootSpec()’ isn’t a good solution, apparently. And in any way this is not universal. ‘Exec’ task, for example, is implemented as a proxy to ‘ExecAction’, and there’s no point where I could put my code, so I had to override a number of setters to achieve the desired behavior.

I would expect to have something like ‘postConfiguration()’ standard hook. I’ve also found a thread with a similar request.

Is there any better way?


(Peter Niederwieser) #2

Most tasks aren’t designed to be subclassed. In general, configuration is best left to plugins, and so you could write your own plugin that adds and configures some ‘Copy’ tasks. In the case of copying, you could also write a task that internally delegates to the ‘project.copy’ method.


(Eugene Janusov) #3

Do you suggest something like

myPlugin.createGenerateTemplateTask(templateName, targetFile)

?


(Peter Niederwieser) #4

Usually, plugins offer a higher-level configuration model. Something like ‘templates { myTemplate { target = … } }’.


(Peter Niederwieser) #5

Yet another potential solution is to subclass ‘Copy’ and preconfigure it in the constructor.


(Eugene Janusov) #6

As far as I understand I cannot preconfigure in the constructor, as I still need ‘template’ and ‘target’ values to be provided.


(Peter Niederwieser) #7

Right. This leaves the plugin approach, or the ‘project.copy’ approach.


(Eugene Janusov) #8

Could you please explain your example, I’m not sure I understand how it should work.

Previously I had a number of tasks like this:

task (generateConfigA, type: Copy) {
    dependsOn ...
      ext.template = ... // name of the source template
    ext.target = ... // destination file
      from templatesDir
    into target.getParent()
    include template
    rename template, target.getName()
    filter(...)
  }

Now it shortened to this:

task (generateConfigA, type: GenerateTemplate) {
    dependsOn ...
      template ...
    target ...
}

How would it work with the plugin?


(Peter Niederwieser) #9

You would write some model classes and a plugin that configures tasks with that information. You can find some information on how to write plugins in the Gradle User Guide.


(Eugene Janusov) #10

Yeah, a plugin with some model for a trivial task, where extending the built-in functionality should be a natural solution.

Just in case if someone else will need this, here’s a working example of ‘GenerateTemplate’ task. It uses ‘expand()’ instead of ‘filter()’, but you can rework it easily.