On 29/01/2013, at 10:45 AM, Marcin Erdmann wrote:
> On 01/16/2013 07:05 AM, Adam Murdoch wrote:
>> I wonder if we should change the plan a little. We've just added a
>> `TestReport` task type that can generate the HTML report. The goal is that
>> at some point we remove the reporting from the `Test` task type and use this
>> task instead. To make this work well, we need to make some improvements to
>> the task graph. These are the same improvements that are already in the spec.
>>
>> So, if you're interested, you could do something like this:
>>
>> 1. Make the task graph changes. There are two parts to this: schedule the
>> dashboard task to run after the reports have been generated, without adding
>> a hard dependency, and to automatically add the dashboard task to the graph
>> whenever any reporting task is added to the task graph.
>> 2. Change the `TestReport` task to implement `Reporting` so that it is
>> included in the dashboard.
>> 3. When the dashboard plugin is applied, add in a `TestReport` task and
>> disable the test report in the `Test` task.
>> 4. At some point later, once the above is stable, move #3 to the Java plugin
>> and deprecate the test report in the `Test` task.
>
> I had a first look at how we could implement that 'always runs after'
> dependency between tasks.
Thanks for looking into this.
>
> From what I can tell the reordering logic should go into
> DefaultTestExecutionPlan.addToTaskGraph(). My first idea is to check every
> time a task is added if it has to run before a task that already is in the
> executionPlan map. That means that even though the API should probably look
> like
>
> project.tasks.withType(Reporting).all {
> buildDashboard.runsAfter(it) // or maybe alwaysRunsAfter()? method name
> should show in a more explicit way that only a 'soft' dependency is defined
> hereā¦
> }
Something we eventually should be able to do with this feature is declare
things like:
* `clean` and all its dependencies must run before anything else.
* Configuration tasks should run before Validation tasks, and Validation tasks
should run before Verification tasks, and Verification tasks should run before
Publish tasks, and Publish tasks should run after everything else. For example,
validate that the user's repository credentials are ok before running the unit
and integration tests, before uploading the jar.
* A resource clean up task must run after the tasks that use the resource. For
example, stop jetty after the integ tests have executed (if it executes at all).
So, there are a few things here:
* Both sides of the predicate can be a collection of tasks.
* The collection of tasks is a subset of the tasks in the task graph.
* The predicate can be 'should run after' or 'must run after'.
So, it feels like this is a constraint that should be attached to the task
graph, rather than to individual tasks, and the above Task.runsAfter() method
might simply be a convenience method for adding the constraint to the task
graph.
For this first story, we only need to be able to declare: task `n` must run
after all tasks in collection `m`. We can add all the other stuff later. Which
means we could just go with the Task.runsAfter() for now. I'd call it
'mustRunAfter()' or something like that.
>
> we would need to store the information about the soft dependency on both
> tasks - the task that should run before, as we need to act if a task that
> should run before is added to executionPlan after the task it should run
> after has already been added to it, as well as on the task that should run
> after(will explain that in a while). When that happens we should probably
> take the task that should run before and that is currently added, and
> together with all the tasks it depends on (also transitively) put it before
> (move it in front of) the task that should run after and is already in the
> executionPlan. If the task added depends on the task (also transitively)
> which should be executed after it then we shall throw and exception
> (CircularReferenceException?). When moving the task and the tasks it depends
> on we should also make sure that we're not moving any task that runsAfter()
> in front of a task that it should run after - that's why I believe that soft
> dependencies should be stored also on the task that runs after. If that
> happens we should probably throw an exception (CircularReferenceException?).
I think the implementation depends on how 'must run after' affects the
transitive dependencies. It would make a lot of sense if the semantics were the
same as for the command-line, so that:
gradle n m
implies:
1. All tasks with name `m` must run after all tasks with name 'n'.
2. Add all tasks with name 'n' to the task graph.
3. Add all tasks with name 'm' to the task graph.
When `m` must run after `n`, then Gradle should run `n` and all its
dependencies before `m` and all its dependencies. Any dependencies in common
are executed before `n`, and if `m` is in the dependencies of `n`, then fail
(where 'dependencies' == all hard and soft dependencies and all their
dependencies).
It also depends on how failures affect these dependencies. There are two
options. Given `n` must run after `m`:
* A failure in `m` prevents `n` from being executed.
* A failure in `m` does not affect whether `n` is executed or not.
To keep with the command-line semantics, we would use the second option.
Implementation-wise, I would think about busting up building the task graph
into 2 steps:
1. Build the task graph proper, with a node for each task in the graph and
edges to represent the various types of dependencies.
2. Once the graph is built, calculate the execution plan:
- Take each node that has no incoming edges, sort them and then
traverse each in turn.
- To traverse a node
- Take each soft dependency, sort them and traverse each in
turn.
- Take each hard dependency, sort them and traverse each in
turn.
--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com