Best way to extend and reconfigure built-in tasks?

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?

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.

Do you suggest something like

myPlugin.createGenerateTemplateTask(templateName, targetFile)

?

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

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

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

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

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?

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.

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.