Can multiple extensions be used to configure additional instance of a task?


(crotwell) #1

I have written a couple of plugins, and based on looking at other plugins, each has a corresponding extension to allow configuration. This seems to work well for cases where the tasks and plugin are used once in a build. But seems to break down if you want to effectively have the plugin/task run twice with different configurations. An example might be reusing the startScripts task from the Application multiple times if the project has multiple main classes. But because the startScripts task uses project.mainClassName, you can’t have more than one startScript.

Another example, take the “greeting” plugin from the Custom Plugin section of the manual, example 58.2 in the 1.10 user guide.

Normal use would be:

apply plugin: GreetingPlugin
greeting.message = 'Hi from Gradle'

and if I do ‘gradle hello’ and get the message.

But now suppose in the same build I want to reuse the GreetingPlugin by making a new task “replyHello” that does the same thing as hello, but with a different message from a different extension, with 'gradle hello replyHello" outputing ‘Hi from Gradle’ followed by ‘Howdy Gradle’.

apply plugin: GreetingPlugin
greeting.message = 'Hi from Gradle'
  project.extensions.create("greetingReply", GreetingPluginExtension)
greetingReply.message = "Howdy Gradle"
  task replyHello(Type: hello) << {
    //how do we use greetingReply.message?
}

There doesn’t seem to be a way to tell the new task to use the new extension, since the Greeting plugin uses a hard coded project.greeting.message in the hello task.

It feels like some reuse is prevented because of the connection between the plugin and its extension/configuration is only via a well know and usually hard coded name.

Is there a better way to write plugins and/or tasks so that the extension can be set dynamically to enable reuse? Is this a misuse of the notion of task? It seems to me that there are two parts to a plugin/task, the code from the plugin author and the configuration info from the user. But because there is no explicit mechanism of associating an extension with a task/plugin, it is hard for the “code” part of the task/plugin to be reused with different configurations in the same build.

thanks Philip


(Peter Niederwieser) #2

Often plugins target the 80% case where a behavior is just needed once. For the remaining 20%, users can drop down to the task level and configure another task manually. The alternative is to write a more sophisticated plugin with a more elaborate extension (perhaps involving ‘project.container()’) that allows to express multiple “things” that get mapped down to multiple tasks.

The greeting sample is unfortunate in that the task is tightly coupled to the extension. Usually, the task has its own model, which is configured by the plugin based on the extension.


(crotwell) #3

So the preferred way to write a plugin is to have a single extension, but then have each task in the plug have its own model, which defaults via closures to get the value from the plugin’s extension? So, a modification the greeting example for easier reuse could be something like this. Any changes you would suggest?

apply plugin: GreetingPlugin
  greeting.message = 'Hi from Gradle'
  class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        // Add the 'greeting' extension object
        project.extensions.create("greeting", GreetingPluginExtension)
        // Add a task that uses the configuration
        project.tasks.create('hello', GreetingTask)
    }
}
  class GreetingTask extends DefaultTask {
    String message = "${-> project.greeting.message}"
      @TaskAction
    def greet() {
        println message
    }
}
  class GreetingPluginExtension {
    def String message = 'Hello from GreetingPlugin'
}
  project.hello.message = "Howdy"
  task reply(type:GreetingTask) {
   message = "Howdy back"
}

(Peter Niederwieser) #4

So the preferred way to write a plugin is to have a single extension

Not necessarily the preferred way, but a common way. Depending on expected needs of users, the extension could describe a single thing or multiple things. For example, it could hold a single string message, or a list of string messages, or a ‘project.container()’ of more complicated message objects.

then have each task in the plug have its own model, which defaults via closures to get the value from the plugin’s extension?

Usually it’s the plugin that configures the task’s defaults (sometimes just for the task added by the plugin, sometimes for all tasks of that type), in one way or another. (The task should be usable without applying the plugin, and hence shouldn’t be coupled to the extension.) There is no easy, generally applicable way to do this just with public APIs, but I hope this will change soon enough. In simple cases like this one, you could check in a callback such as ‘project.afterEvaluate’ or ‘gradle.projectsEvaluated’ whether a message was set for the task, and set it to the extension’s message otherwise.