I am extremely happy we're finally attacking this feature :)))))) To me, "soft dependency" is a special case of "ordering rules" (nice term, Daz). Soft dependency means "should run after" and it can be overridden by a regular dependency (just like ant does it). Ordering rules will allow "must run after", "should run after" (following Adam's suggestions), maybe even more stuff.
I have a feeling that "must run after" rule is far more interesting and useful than "should run after". Current command line semantics is "should run after" (e.g. not "must"). Do we intend to change this or I've misinterpreted Adam's post? Still rejoicing, On Tue, Jan 29, 2013 at 4:52 PM, Daz DeBoer <[email protected]> wrote: > On 29 January 2013 00:44, Adam Murdoch <[email protected]> wrote: >> >> >> 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. > > > Perhaps instead of "soft dependencies" we should refer to these as "task > ordering rules", or something like that? > > - A "dependency" is "Task A cannot run without Task B running first" > - An "ordering" is "Task B must run after Task A" (making no statement about > whether TaskA will be run or not) > > -- > Darrell (Daz) DeBoer > Principal Engineer, Gradleware > http://www.gradleware.com -- Szczepan Faber Principal engineer@gradleware Lead@mockito --------------------------------------------------------------------- To unsubscribe from this list, please visit: http://xircles.codehaus.org/manage_email
