Extracting the annotated inputs and outputs

Hey Peeps!

I’m working on an awesome plugin to help other devs instrument their gradle builds ( https://github.com/jakeouellette/inspector/ ). I’m interested in pulling out the inputs and outputs from tasks (e.g., when you set their @Output and @Input annotation), but I was struggling to figure out where this state is exposed.

The output files are accessible from the TaskOutputs object, but the inputs and outputs don’t seem to be in that object. I imagine that when you annotate a parameter with @Input, it’s adding it to a list of parameters in some caching service, but the codebase seemed totally opaque to me.

Where is that listing service layered?


1 Like

Not sure what other kind of output you are referring to other than the files. As for inputs, you can get input properties from Task.getInputs().getProperties() and input files from Task.getInputs().getFiles().

Ah, this makes sense, I somehow missed the getProperties.

I blame it on the fact that getProperties is also a groovy idiom… Out of curiosity, any idea of this violates Liskov Substitution principle anywhere? (e.g., getProperties() returning the annotated @Input objects, not the TaskInputs’ getter properties?)

Anyway, for some reason, I was imagining that tasks could have non-file outputs, so that other tasks could depend on those outputs explicitly, but I realize now the API doesn’t have this facility.

I’m not quite certain what you mean by “TaskInputs’ getter properties”. Quite simply the annotation handler for @Input simply calls task.inputs.property() and getProperties() returns the Map (with some type coercion going on).


I’m more of a Java / Scala dev, historically, so feel free to correct my assumptions here:

My understanding is that all Groovy objects have been provided additional properties via DefaultGroovyMethods and GroovyObjectSupport. They all have a getProperties method and setProperties method which both have semantic meaning.

Yet, inside TaskInputs, getProperties is overridden with a different implementation which maintains a Map<String, Object> in memory. The implementation follows pretty closely with the internal implementation inside groovy’s libraries, except when you get to getProperties(). GetProperties actually checks each property for whether or not it’s a closure, and evaluates it if it is. Assuming the closure is side-effect free, the only difference is it’s returning the return value of the closure / callable, which, I think, means the subclass has different return value semantics from the base class. (E.g., if some Groovy library depended on checking a property it just set, it’d break on this code). This is probably super minor, because it’s internal, but I thought I’d throw out the observation.

FWIW, if the property is a closure that is NOT side effect free, every time you request access to the inputs, it has the potential to do weird things. You have to maintain in your head knowledge about how the collaborator will use this class, which increases code complexity :slight_smile:

We aren’t overloading anything in this case. TaskInputs defines a method getProperties(), whereas the GroovyObject interface defines the a method getProperty(String s).

Perhaps this will help: https://github.com/MinecraftForge/ForgeGradle/tree/master/src/main/java/net/minecraftforge/gradle/util/caching
The entry point is CacheContainer.getCache()
I used it for caching the outputs of tasks in a central location thats project agnostic, as long as the inputs are the same. I gather the inputs and outputs with a bunch of reflection… hope its helpful.