Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to define a Gradle plugin property extension with Java?

Tags:

java

gradle

I'm trying to create a Gradle plugin in Java that has property extensions (not conventions, as this is apparently the old, wrong way). For the record, I'm working with Gradle 1.6 on a Linux machine (Ubuntu 12.04).

I've gotten as far as figuring out that the this should be done in the Plugin class definition. Here is A way of adding an extension. Create an extension class that contains your properties:

public class MyPluginExtensions {

    File sourceDir;
    File outputDir;

    public MyPluginExtensions(Project project) {
        this.project = project;
        sourceDir = new File(project.getProjectDir(), "src");
        outputDir = new File(project.getBuildDir(), "out");
    }

}

Now add these extensions to the project in the main plugin class:

public class MyPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {

        Map<String,Object> taskInfo = new HashMap<String,Object>();
        taskInfo.put("type", MyPluginTask.class);
        taskInfo.put("description", "Generates blah from blah.");
        taskInfo.put("group", "Blah");
        Task myPluginTask = project.task(taskInfo, "myPluginTask");

        // Define conventions and attach them to tasks
        MyPluginExtensions extensions = new MyPluginExtensions(project);
        myPluginTask.getExtensions().add(
                "sourceDir", 
                extensions.sourceDir);
        myPluginTask.getExtensions().add(
                "outputDir", 
                extensions.outputDir);
    }
}

This approach, however, doesn't seem to be correct. A new project property is shows up in the project.ext namespace. I expect to be able to address the plugin extensions as:

in my build.gradle:
myPluginTask.sourceDir = file('the/main/src')
myPluginTask.outputDir = file('the/output')

However, when I put such things in a gradle script that uses my plugin and try to set this property, gradle tells me I can't set it:

* What went wrong:
A problem occurred evaluating script.
> There's an extension registered with name 'sourceDir'. You should not reassign it via a property setter.

So, what's the right way to add property extensions for a task in a Java-based Gradle plugin?

EDIT:

Based on some other SO posts, I tried just adding my extensions object in one shot:

// first attempt:
//myPluginTask.getExtensions().add("sourceDir", extensions.sourceDir);
//myPluginTask.getExtensions().add("outputDir",extensions.outputDir);

// second attempt
myPluginTask.getExtensions().add("myPluginTask", extensions);

This appears to work. However, Gradle is now complaining that I've added a dynamic property:

Deprecated dynamic property: "sourceDir" on "task ':myPluginTask'", value: "/Users/jfer...".

So, again, what's the right way to add a plugin extension property?

EDIT 2

So, taking yet another shot at this, I'm adding the extension to the project object and using the create method instead:

// first attempt:
//myPluginTask.getExtensions().add("sourceDir", extensions.sourceDir);
//myPluginTask.getExtensions().add("outputDir",extensions.outputDir);

// second attempt
// myPluginTask.getExtensions().add("myPluginTask", extensions);

// third attempt
project.getExtensions().create("myPluginTask", MyPluginExtensions.class, project);

However, this fails for a couple of reasons:

  1. Creating a properties extension with the same name ("myPluginTask") as the task creates a collision between the task name and extension name, causing the task to disappear from gradle's perspective (and throw oblique errors, such as "No such property: dependsOn for class ...MyPluginExtensions").
  2. If I provide a name that does not collide with a task name (e.g., "myPluginPropExt"), the create() method works, but DOES NOT add the extension in its own namespace as expected (e.g., project.myPluginPropExt.propertyName and instead adds it in the project namespace (e.g., project.propertyName) which is not correct and causes Gradle to throw a bunch of "deprecated dynamic property" warnings.
like image 417
Joe Fernandez Avatar asked Oct 04 '22 03:10

Joe Fernandez


1 Answers

So here is a solution to my problem:

public class MyPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {

        Map<String,Object> taskInfo = new HashMap<String,Object>();
        taskInfo.put("type", MyPluginTask.class);
        taskInfo.put("description", "Generates blah from blah.");
        taskInfo.put("group", "Blah");
        Task myPluginTask = project.task(taskInfo, "myPluginTask");

        // Define conventions and attach them to tasks
        MyPluginExtensions extensions = new MyPluginExtensions(project);

        // the magic extension code:
        project.getExtensions().add("myPluginName", extensions);
    }
}

Now I can set a value for one of the extension properties in my gradle.build file like so (and I don't get a warning about adding deprecated dynamic properties):

 myPluginName.sourceDir = file('the/main/src')

The final trick is to get this value in my Plugin's task:

public class MyPluginTask extends DefaultTask {
    @TaskAction
    public void action() {
        MyPluginExtensions extensions = (MyPluginExtensions) getProject()
                .getExtensions().findByName("myPluginName");

        System.out.println("sourceDir value: " + extensions.sourceDir);
    }
}

This works, but what annoys me about this solution is that I want to be able to put the extension properties in the same namespace as the task (e.g., myPluginTask.sourceDir) which I have seen in groovy-based plugins, but this apparently is not supported or just doesn't work.

In the meantime, hope this helps someone else.

like image 64
Joe Fernandez Avatar answered Oct 07 '22 17:10

Joe Fernandez