Custom Task with Extensions

Ok, this time, I’m most likely just being a gradle noob.

I have a lot of almost identical repetitive tasks, that I naturally want to create a custom task (perhaps even a plugin) for.

That means, I want to define a plugin, an extension, and a task, and I want these to correctly resolve my input files (depending on extension/task configuration), and output files (depending on the configuration).

So, in simple terms, this is what I have:

class GenbeansPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create("genbeans", GenbeansExtension)
        project.task('genbeans', type: GenbeansTask)
        project.compileJava.dependsOn += 'genbeans'
    }
}
class GenbeansExtension {
    def generatedSrc = "src/generated/java"
    String projectName = "MyProject"
    String projectPackage = "/com/core/component"
    String dataSrc = "src/resources/ejbeans"
    String templateSrc = "src/resources/templates"
}
class GenbeansTask extends DefaultTask {
    def generatedSrc
    String dataSrc
    String templateSrc
    String projectName
    String projectPackage

    GenbeansTask() {
        description = 'Generates java beans, servlets and services based on the beans from src/resources/beans'
        project.afterEvaluate {
            def ext = project.getExtensions().findByName("genbeans")
            if (!ext) {
                ext = new GenbeansExtension()
            }
            generatedSrc = generatedSrc ?: ext.getGeneratedSrc()
            projectName = projectName ?: ext.getProjectName()
            projectPackage = projectPackage ?: ext.getProjectPackage()
            dataSrc = dataSrc ?: ext.getDataSrc()
            templateSrc = templateSrc ?: ext.getTemplateSrc()
            inputs.dir templateSrc
            inputs.sourceDir project.file(dataSrc)
            generatedSrc = project.file("src/generated/java")
            inputs.getSourceFiles().each { File f ->
                def bname = baseName(f)
                outputs.file project.file("$generatedSrc/$projectPackage/ejb/generated/beans/Abstract${bname}Bean.java")
            }
        }
    }

    @TaskAction
    void genbeans(IncrementalTaskInputs inputs) {
        // Do logic
    }
}

Now, this is annoying, esp. the need for afterEvaluate clause.

But, I cannot fathom how else I can initialize the task with the values from the extension (if available), and still support overruling the values when using the task directly, and still get a chance to setup the input and output files when the task is NOT run (i.e. the TaskAction is not executed, when the input-files have not changed).

So what am I missing here?

Is there a “doAfterInitializationButBeforeLastAndAlwaysDoThisBeforeUpToDateCheckAnnotation”?

EDIT: Thunderstruck, am I simply ignoring doFirst?
EDIT2: Nope, doFirst do not accomplish what I need. Any help?

Ok.

I did some more digging, and I think I have a better grasp at the way extensions is supposed to be used now.

But, I’m still missing a place to put code into my custom task, that get’s evaluated at the right time (after configuration but before execution).

I.e. this example:

class MyTask extends DefaultTask {
    def myProp

    MyTask() {
        println "MyTask(name: $name)"
    }

    @OutputFile
    def getMyPropFile() {
        def value = myProp ? project.file(myProp) : project.file(project.mytask.myProp)
        println "${name}.getMyPropFile: $value"
        return value;
    }

    @InputFiles
    def getMyPropFiles() {
        def value = myProp ? myProp : project.mytask.myProp
        println "${name}.getMyPropFiles: $value"
        return "/tmp/" + value;
    }

    @TaskAction
    def action() {
        println "${name}.action: out " + getMyPropFile()
        println "${name}.action: in " + getMyPropFiles()
    }
}
class MyTaskExtension {
    def myProp = "default prop value"
}

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create("mytask", MyTaskExtension)
        project.task('mytask', type: MyTask)
    }
}

task testMyTask(type: MyTask) {
    myProp = "from testMyTask"
    dependsOn += 'mytask'
}
apply plugin: MyPlugin
mytask {
    myProp = "my prop set in closure"
}

Allows me to re-use the task, and define the inputs and outputs.
But, if I want to add those outputs to a specific configuration, or something similar, mark them as artifacts or what-ever, I cannot figure out how to do this “the plugin/task way”.

I know I could make functions, or do this explicitly in the project-gradle file, but it feels like this should be able to be accomplished by “the plugin”.

There’s a few options available to you. Convention mappings are an internal feature and should be superseded by the upcoming rule based model configuration features which are incubating and will be a part of the public API.

Here’s an example of using a convention mapping that may be helpful if you decide not to try the rule based model configuration:

class SomeExtension {
    String hello
}

class SomeTask extends DefaultTask {
    String whatToSay

    @TaskAction
    def sayIt() {
        /*
         * In order for the conventionMapping to work correctly
         * you need to use the getter.
         */
        println getWhatToSay()
    }
}

class SomePlugin implements Plugin<Project> {
    @Override
    void apply( Project project )
    {
        project.extensions.create( 'greeting', SomeExtension )
        project.task( 'sayIt', type: SomeTask ) {
            conventionMapping.whatToSay = { project.greeting.hello }
        }
    }
}

apply plugin: SomePlugin

greeting {
    hello 'world'
}

that will produce:

$ ./gradlew sayIt
:sayIt
world

If the task is later configured explicitly

sayIt {
    whatToSay 'something else'
}

you get:

$ ./gradlew sayIt
:sayIt
something else

Note: if you have both the extension and the task configured, the order doesn’t matter and the task configuration always wins.

Thanks. That is more or less what I found out my self.

The problem is, when I want to do something with the outputs of the task.

Say, I want to register the output artifacts in a specific configuration.
If I do this in the TaskAction, it won’t be done when the task is UP-TO-DATE.

If I do it in the constructor, it won’t use the “right values” (the ones from the extension/task).

I can try to do it in the getters (or setters), but these get’s called many times, with different values.

The only thing I’ve been able to think up, is to implement an upToDateWhen closure on the task, and do the initialization of state there, and then delegate the actual implementation to the super-class.

But that is just very clumsy.

What is it that you are actually trying to do by updating a configuration with task outputs?

I have several tasks that create jar-files, these jar-files I want to depend on in other projects.
I want to use configuration to “mark” the artifacts as specific traits of the project.

So, basically, all I need to do, is to add the output-files to the projects artifacts, under a specific configuration.

This needs to be done AFTER resolving the actual input and output files (i.e. the mix of extensions, closures and default task-state), but BEFORE running the TaskAction - since the action won’t run, if the files hasn’t changed.

If I do this in the TaskAction - it works as long as the task is not UP-TO-DATE, but when it is, the jars are not registered in the project-artifacts.

I have another use-case, where this seems to be needed.

We have some resources gathered in 4 different locations, but all adhering to a convention with regard to naming etc.

These resources are usually put through the following steps (tasks):

  • copy resources to a temp location (from multiple sources)
  • compile resources into servlets using JspC
  • gather compiled outputs into a war file, with a partly generated web.xml

So, I can either make this ONE task, use functions to create multiple tasks and chain them together - but to create a task, that gets configured using closure, and then create multiple “sub-tasks” I cannot do, because I have no way to invoke the “create depending tasks” logic.

I’d like to have:

task makeJspWarA(type: JspWarTask)  {
  subdir = 'adm'
  descriptor = 'adm-web.xml'
}

But instead have:

Task t = createJspWarTask('adm', 'adm-web.xml')
ear.dependsOn += t

Where createJspWarTask among other stuff, creates 2 “sub-tasks”.

If only a task had an AfterInitialization method, or annotation, where you could put in stuff that has an impact on the task-hierarchy or the evaluation of it (apart from Input/Output files that is).