What form apply() lets me apply a plugin with a configuration parameter?

Continuing the discussion from Establishing task dependencies in a plugin:

The solution finally arrived at in the linked task did cause something my earlier version did successfully to break. That was a way of configuring my plugin.

I defined the following java enum type:

public enum WarContainerType {
    TOMCAT, WEBLOGIC;
    public String getDfltDeplDir() {
        switch (this) {
        case TOMCAT:
            return "webapps";
        case WEBLOGIC:
            return "web-apps";
        default:
            //gotta keep compiler happy even if this can't happen.
            throw new IllegalArgumentException();
        }    
    }
}

And a little groovy “extension” class:

    class ContainerExtension {
        @Input @Optional
        WarContainerType container = WarContainerType.TOMCAT
    }

Then in the plugin apply method, we had

    void apply(Project project) {
        project.plugins.apply('war') 
        project.plugins.apply(RpmPlugin.class)
        project.extensions.create("dest", ContainerExtension)

Then in the build script, I had

apply plugin: 'vt.content.rpm'
defaultTasks 'rpm'
dest {
    container WEBLOGIC
}
dependencies {
    providedCompile "javax.servlet:servlet-api:2.2"
}
task rpm(type: RpmContent) {
    packageDescription 'gvp008 app - content file installer.'
}

And my little configuration worked, with a WEBLOGIC rather than the default TOMCAT being assumed.

However, the previous solution was to instantiate the tasks in the plugin as the plugin was being applied, rather than in the build script as here.So now we have just

apply plugin: 'vt.content.rpm'
defaultTasks 'rpm'
dest {
    container WEBLOGIC
}
dependencies {
    providedCompile "javax.servlet:servlet-api:2.2"
    compile "com.att.vtone:ivsslib:12.0:jdk1.5"
    compile "apache-log4j:log4j:1.2.14"
}

in our buildscript, and our configuration is ignored, with the default TOMCAT being assumed. This is because the task is already created at this point.

What I really want to do is configure this value as the plugin is being applied.

What is the right way to do this? An example of plugin configuration would be appreciated. In looking over the documentation, what looks like it might be helpful is an example of calling the apply method with a map, with that map having both “plugin” and “to” parameters.

@Optional and @Input are to be used in task classes, not extensions. Remove them from your extension and make sure your task class has them.

You can create the task and then have a project.afterEvaluate block where you set the container property of your task to be equal to the container property on the extension.

If you will only ever have one such task, it would be conceptually simpler to let the user configure the container property on the task directly.

Let’s see. In the first place, I was following the pattern (anti-pattern?) of the OS Nebula Plugin where properties in the extension are given the @Input and @Optional annotations. And this actually worked, as long as I was creating the task in the build script rather than the plugin.

But I believe you are saying that it would be better to have the container as a property of the task, not mess with extensions? I may walk down this path and see where it leads.

The reason here is the extension is in turn passed as an input to tasks. In conjunction with the @Nested annotation this allows you to specify complex objects as task inputs.

Thanks to you both.
The key thing was doing all this configuration inside afterEvaluate.
And I missed the @Delegate and @Nested annotations.

“Extension” is a bad name in this case, because it is never added as an extension to the project. It would be clearer if it was called “Options”.

When I think of an “Extension”, I think of something that is added as an extension to the project. Such an object should not be directly assigned to a field in a task. Otherwise all tasks of that type would share the same input object. Then an unsuspecting user changes it - thinking he is configuring one specific task - and ends up changing all tasks of that type.

If there is only going to be one task of that kind per project, then having an extension on the project is usually overkill. The user can just configure the task directly.