Custom task: input and output dependencies

I’m going to be writing a custom plugin/task with particular features that I’m not sure how to implement yet. There is a Maven plugin implementation of it, which I’m using to understand the issues required for a Gradle plugin for the same. I’ve read the custom task/plugin info in “Gradle in Action” and the User Guide, but neither of those resources really go into enough detail.

For background, the principal task in the plugin is going to generate java code from source files in a particular configuration language. The plugin has one property that specifies the folder where input files are to be found. The generation process will use as input all the files with a particular extent in that folder.

The other principal property is a “generators” element, which has a list of “generator” elements, which has two main properties, being a Java FQCN and an output directory path.

When the task executes, it will execute all of the generators, and each generator will read all of the input files and generate classes into its designated output directory.

I have several concerns about how this will work, but right now I’m thinking about how I properly register the inputs and outputs to Gradle, so it knows when the task needs to be executed or skipped.

The task should be run if any of the following are true: * Anything in the input source folder has changed * Anything in any of the generator’s output folders has changed * Any of the generator classes have changed

I have a feeling the first two will be straightforward, although I’m not sure exactly how that is done yet. The last might be more complicated, and I’m not sure how that could be done.

I’m also considering whether this could be an incremental task. It seems reasonable, but I’ll consider that in a second version.

Your inputs (a collection of ‘Generators’) are complex, which usually means using input/output annotations on getters which produce a derived value. Perhaps something like this would work for your purposes:

task foo(type: MyTask) {

sourceDir = file(‘src/foo’)

generator {

packageName = “org.bar”

outputDir = file("$buildDir/foo")

}

}

class MyTask extends DefaultTask {

@InputDirectory

File sourceDir

Collection generators = new ArrayList<>()

@TaskAction

void action() {

// do stuff

}

void generator(Closure config) {

generators.add(project.configure(new Generator(), config))

}

@Input

def getPackageNames() {

generators.collect { it.packageName }

}

@OutputDirectories

def getOutputDirectories() {

generators.collect { it.outputDir }

}

static class Generator {

String packageName

File outputDir

}

}

Ok. A couple of things. The generator actually has a class name, not a package name. More importantly, I need to have Gradle run the task if the CLASS changes, not just the class name.

Yeah, I realized later that should have been a class name rather than a package name. My question then is where is this class defined? Is it in a jar somewhere, buildSrc, etc? What we would probably want to specify as an input is the classpath that class lives on.

Yes, that would be reasonable. The Maven plugin that I’m basing this on doesn’t allow specifying that. I’ve noted that this is something that the Gradle plugin should have, but I assume a first version could use a “default” classloader, correct?

Just to clarify, I mean classpath, not classloader, since a classpath is really just a file collection, which makes specifying it as an input very easy. If this jar is going to be on the buildscript classpath (as a dependency?), than perhaps something like this would work.

@InputFiles

def generatorsClasspath = project.buildscript.configurations.classpath

Ok, so this would result in Gradle detecting that one or more classes in that classpath have changed, forcing the task to run?

I assume this allows for the script setting the generatorsClasspath from a custom configuration, allowing these generator classes to be isolated from the classes deployed to a server.

It would seem to make sense to allow for an optional configuration name (I wish there was a better name for that thing, too general) in the extension object, as a shortcut for a couple of dotted properties to get to the classpath of a configuration. Would that be reasonable?

Basically all that Gradle is checking is that a file collection has changed. This could simply be a build output directory (a bunch of .class files) or a classpath (collection of jars), or even both.

A common convention is for a plugin to define a custom configuration for users to add dependencies too that are used by tasks of that plugin. Perhaps you could have your plugin define a ‘generators’ configuration to which the user would add those dependencies?

Concerning the “generators” configuration, that’s what I just asked. Is that what you’re talking about?

Yes. Sorry if it wasn’t clear that was a response to your question.