On 21/06/2012, at 1:00 AM, Adam Murdoch wrote:

> 
> On 15/06/2012, at 5:54 PM, Luke Daley wrote:
> 
>> 
>> 
>> 
>> 
>> On 14/06/2012, at 11:15 PM, Adam Murdoch <[email protected]> wrote:
>> 
>>> 
>>> On 14/06/2012, at 11:03 PM, Luke Daley wrote:
>>> 
>>>> Howdy,
>>>> 
>>>> On a project I am working on, I'm orchestrating a release process. Part of 
>>>> this is checking out the project from a tag and running a build against 
>>>> the checkout. It's awkward to use a task in this case as it's one step in 
>>>> a sequential workflow. I can make a GradleBuild task work, but it is 
>>>> awkward.
>>>> 
>>>> Is there any good reason not to offer a gradleBuild {} “imperative” method 
>>>> like javaexec {}, copy {} etc.? I think it makes sense to offer it.
>>>> 
>>>> 
>>>> Speaking more generally, I'm seeing this kind of issue more and more. It 
>>>> can be inconvenient to lock functionality up behind the task mechanism. I 
>>>> think the core issue here is that tasks don't really compose into larger 
>>>> “atomic” pieces.
>>> 
>>> So we should fix this, rather than work around it.
>>> 
>>> 
>>>> 
>>>> I think as a general philosophy and approach, we should be careful about 
>>>> locking functionality up behind tasks. Tasks should just adapt the 
>>>> functionality into our execution mechanism. This leads to some 
>>>> duplication, but it makes the most sense to me. We already do this 
>>>> sometimes, but I wouldn't consider it a consistent approach. What are 
>>>> others thoughts on this? I've been saying this kind of thing to plugin 
>>>> developers etc. and in trainings for a while.
>>>> 
>>>> Longer term, a richer execution model will alleviate some of this stuff 
>>>> but I'm not suggesting pursuing this now.
>>> 
>>> Don't assume this stuff is longer term. We should add the stuff we need 
>>> rather than work around it with imperative stuff. It's not one big bang 
>>> feature - it's a bunch of smaller ones.
>>> 
>>> The plan is to use declarative tasks and not imperative actions for all 
>>> work that Gradle does, and push this more and more. Actions do not scale. 
>>> Tasks do.
>>> 
>>> Having said that, there are 2 things we might do to bridge the task and 
>>> action world:
>>> * Support pojo task types, combined with dependency injection. This way, 
>>> you don't need to implement Task, and we'll take care of filling in the 
>>> missing pieces when we happen to need a Task instance.
>>> * Allow arbitrary Runnable instances to be instantiated from such pogo 
>>> types.
>>> 
>>> This way, there is no difference between an action implementation and a 
>>> task implementation - they're the same thing. It's how the instances are 
>>> used that determines whether the logic is an action or a task.
>> 
>> Good stuff, but it wouldn't help here.
> 
> Sure it would, if you want to script up a sequence of actions:
> 
> Say, for example, we add something like this:
> 
> project.actions.run(Class<?> type, Closure configClosure)
> 
> where 'type' is any type that is accepted by TaskContainer.add(). Which 
> happens to be anything that implements Task at the moment, but that may 
> change to allow more types.
> 
> So, given this, you'd be able to do:
> 
> task myMegaTask << {
>     // do the first step
> 
>    actions.run(GradleBuild) { dir = 'checkout-dir'; tasks = 'build' }
> 
>    // do the third step
> }

This is an interesting idea.

>> Granted it could reduce the cost of supporting declarative & imperative in 
>> the long term.
>> 
>> I don't think we can lay task semantics over any action POJO. There is going 
>> to always be the need to do some adapting. I think we should focus on making 
>> task adapters cheap somehow. Take something like JavaExec, a large part (but 
>> not all, importantly) of it is just boilerplate delegation. When moving 
>> functionality out of the task (as I think needs to happen for JettyRun for 
>> e.g) you pay this boilerplate cost. 
> 
> We wouldn't need an adapter for JavaExec. The JavaExec type would simply 
> become an action type instead. And all that means is that it would no longer 
> extend DefaultTask. That's it. It would still have a method marked up to 
> indicate how to 'execute' it. It would still have properties marked up to 
> indicate its inputs and output. We may or may not also change how you mark 
> those things up - e.g. we may understand Runnable or Callable as well as 
> @TaskAction.
> 
> When you create an action given one of these types, you do so with a factory. 
> This would give you:
> * Declarative validation
> * The DSL would be mixed in.
> * Things we later add, such as declarative progress reporting and profiling, 
> running in an isolated class loader, forking, distribution and so on.
> 
> When you create a task given one of these types, you'd get:
> * All of the above, plus:
> * Up-to-date checking.
> * Dependencies and auto-wiring.
> * Logging.
> * All of the goodness that having a name give you:
>     * Configuration from the command-line
>     * Visibility in the IDE, and the profiling report and analytics, and 
> project reports, and so on.
>     * DSL reference generation for the task instance, and so on.
> 
> Given this, I don't think we need adapters at all.

I think I like this general idea, for a couple of reasons. 

1. The registration aspect could remove the need to use Class objects when 
creating tasks (if we want to do this)
2. We could change the Task API to be interface based and separate the Spec 
from the impl
3. A model for composition and linear workflows emerges from this
4. Gives us the opportunity to move to a composition vs. inheritance model

So we end up with tasks being one more sequenced actions (and maybe some kind 
of finaliser(s)). People no longer write Task implementations, but actions that 
we can either create a Task of or create a kind of aggregate task of multiple.  

> 
>> This tempts people into fat tasks, which is inflexible.
>> 
>> I see three options:
>> 
>> 1. making it more convenient to write thin task adapters
>> 2. Fat tasks, but make using tasks much more flexible (composites, 
>> cloning/retry etc.)
>> 3. A new model.
>> 
>> We probably will need both 1 & 2 I guess.
>> 
>> 
>> Returning to the concrete…
>> 
>> If we want to avoid exposing more action impls, to solve the use case I had 
>> in mind we'd need the ability to create composite tasks.
> 
> What is the use case, exactly? Why does 'dependsOn' not work for it?

There were 3 steps; do something, run a GradleBuild, do something else. It 
makes no sense to do any of those bits in isolation, and the build master was 
unhappy with that being possible at all for users to do run any of these bits 
in isolation. For example, it would never be valid to just run the 1 and 2 
steps, which could happen if the user just invoked the task for the second 
step. 

-- 
Luke Daley
Principal Engineer, Gradleware 
http://gradleware.com

Reply via email to