Is it possible to create a task per extension object?

I’m attempting to implement a protostuff plugin that allows a project to have n proto files, configure the protostuff compiler for each independently and have them execute ahead of (java) compilation. Ideally they’d only fire if the proto had changed.

To support the “configure the compiler on a per proto basis” requirement, I was going to create a container and expose that as an extension. For example

// in the plugin
def protoModules = project.container(ProtoModule)
project.extensions.protos = protoModules
//
// in the build.gradle
//
project(':myapp') {
    protos {
        myFirstProto {
            input = 'my-first.proto'
            output = 'java_bean'
        }
        mySecondProto {
            input = 'my-seconds.proto'
            output = 'java_bean'
            options = [compile_imports: true]
        }
    }
}

The problem I have is with the incremental compilation requirement. It seems I’d need to break down the extensions into a task per entry in the container and specify the ‘@Input’ for each one but I don’t know how to do that. Any guidance would be v helpful.

Thanks Matt

If I understand you correctly…

There’s a ‘project.container’ variant that allows you to specify a factory. Use this to inject the ‘Project’ into the ‘ProtoModule’ and create the task in there. So in essence you are making ProtoModule a task wrapper.

A better option though would be to use a configuration rule on the ‘protos’ container.

class ProtoModuleTask extends DefaultTask {
  ProtoModule module
  …
}
  protos.all { ProtoModule protoModule ->
  project.task(protoModule.name, type: ProtoModuleTask) {
    module = protoModule
  }
}

That way you’ve decoupled the tasks from the model.

OK thanks. The rule approach sounds good but I seem to be in a chicken and egg situation, I ahve

def protoModules = project.container(ProtoModule)
        project.extensions.protos = protoModules
        protoModules.addRule('protocRule', {
            configureCompilerTask("protoc$it", project, protoModules.getByName(it))
        })

with the same build.gradle snippet from the earlier post. It seems the rule fires before the named object has been put into the container hence getByName fails so this blows. Am I doing something wrong or do I have to lazily lookup the named domain object?

Thanks Matt

I wouldn’t use ‘addRule’, I’d use ‘all’ which has a different function.

‘addRule’ is less useful for auto-vivifying containers (which this one is). You need to specify that modules should be created on demand, you need to specify that a task should be created for each created module which is what ‘all’ is for.

I hadn’t noticed that add covers all things added in future too, that’s much cleaner.

I’m back at the chicken and egg problem though as it seems that the ProtoModule instance is not fully configured at the point at which it is seen by the add action.

The reason I think I need this is because I need to choose which task to configure based on a certain property on the ProtoModule object.

Cheers Matt

This is a difficult situation because you’re introducing an evaluation ordering constraint. It’s worth exploring if there’s an alternative.

So is there an attribute of ‘ProtoModule’ that determines which task it’s related to? Or is there a 1-to-1 between a module and a task?

What’s the task? Is it something you control?

There is an attribute of ProtoModule that determines the task, I have 1 impl that extends JavaExec because I have to fork to a fresh jvm and another that executes the required code inline. The tasks are my own.

Could you unify the tasks, using ‘project.javaexec{}’ internally in the task action impl?

Yes I can. I hadn’t noticed ‘project.javaexec{}’ (so had been creating/configuring the javaexecspec myself) but that should work.

Thanks Matt