On 5/04/10 1:17 PM, Geronimo M. H. wrote:
Am Sunday 04 April 2010 23:44:10 schrieb Adam Murdoch:
On 3/04/10 3:55 AM, Spencer Allain wrote:
The "<<" form is confusing enough. Having the first hello world
example state that it's equivalent to doFirst will only lead to later
confusion once a user learns about doFirst and doLast.
I think we should look at deprecating the '<<' form after the Gradle 0.9
release. And possibly doFirst() and doLast() too, so that a task has
only 1 action.
I vote against reducing the number of (possible) task-actions and against the
removal of doFirst() and doLast().
I didn't phrase that very well. I meant that we should replace these
things with alternatives which are more expressive or more powerful or
both. So, this time, here's too much detail, rather than too little detail:
The problem with the << form is that these 2 constructs:
task mytask {
.. do stuff at configuration time ..
}
task mytask << {
.. do stuff at task execution time ..
}
are very similar in syntax but very different in semantics. The only
syntactic difference is the '<<'.
The '<<' form is only a shortcut for
task mytask {
doLast {
.. do stuff at task execution time
}
}
To me, the 9 or so characters we save with the '<<' form really aren't
worth the confusion that the 2 forms cause. I'd rather we replaced it
with something more explicit, like, for example:
task mytask {
execute {
.. do stuff at task execution time
}
}
This way, you only ever configure a task. It just so happens that for
some tasks, you configure the action it will perform at execution time.
I used to think that we should optimise the DSL for imperative task
definitions like the above. Now I think that's the wrong way to go. As
we add more typed tasks and more plugins, the need for imperative tasks
lessens. And I think that we should optimise the DSL for declarative
task definitions, such as you get with typed tasks or plugins, to
encourage the use of declarative over imperative task definitions:
For example, this kind of task definition is declarative
task copy(type: Copy) {
from 'src/java'
into 'build'
}
and is much preferable to this imperative task definition:
task copy << {
ant.copy(dir: 'src/java', todir: 'build')
}
A big benefit of declarative task definitions is that Gradle can reason
about them. For example, Gradle can automatically add task dependencies
for the input files. It can apply up-to-date checking. It can validate
the task's configuration. It can extract parallelism between tasks or
even reason about how to distribute task execution across a bunch of
machines. The same is true for plugins, user interfaces, and reports:
They can all reason about declarative tasks. Whereas, imperative tasks
are black boxes to them.
As far as doFirst() and doLast() goes, they have a bunch of (similar)
limitations:
* They're imperative, not declarative. When you add an action to a task,
Gradle can no longer reason about the task. For example, Gradle can't
figure out whether an action is up-to-date or not, and so, whether the
task is up-to-date or not. Instead, it has to always execute all the
actions of the task.
* There's an implicit ordering between the actions, rather than explicit
dependencies between actions. We don't know, for example, which actions
we can run in parallel. Or which actions we can skip.
* There's no general way to conditionally execute an action. For
example, the Test task has an ad hoc testReport property to
enable/disable the report action.
* There's no general way to configure an existing action. For example,
the configuration for the test report action lives on the Test task.
This means you can't swap in a different report implementation with a
different set of configuration properties.
* There's no way to run a particular action once before multiple tasks,
or once after multiple tasks. For example, to tear down a web container
after a bunch of integration test suites.
* There's no way to run an action on the failure of a task.
Compare this with tasks. For each of these problems above, we have
already solved it for declarative tasks, or need to solve it for
declarative tasks. This suggests to me that we ultimately should replace
the concept of task actions with tasks, because tasks will already do
all the stuff we need actions to do.
How this would look in the DSL is still something we need to figure
out. One option is to introduce 2 new types of task dependencies:
* An initializer: When taskA is an initializer for taskB then,
taskB.dependsOn taskA and taskA onlyIf { taskB is not up-to-date }. This
would replace doFirst()
* A finalizer: When taskA isFinalizerFor taskB then, when taskB is added
to the DAG, taskA dependsOn taskB and taskA onlyIf { taskB didWork }.
This would replace doLast()
We'd also add an option for a finalizer to execute when the target task
fails - for example, post-test reporting or cleanup.
--
Adam Murdoch
Gradle Developer
http://www.gradle.org
---------------------------------------------------------------------
To unsubscribe from this list, please visit:
http://xircles.codehaus.org/manage_email