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


Reply via email to