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
}
> 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.
> 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?
--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com