Hans Dockter wrote:
On Sep 12, 2008, at 12:28 AM, Adam Murdoch wrote:
Hans Dockter wrote:
The question is how to fit this into a directed acyclic graph. My
rough idea is to create dependsOn relations from the postRequisite
tasks to the source task. If the build is started with: 'gradle
sourceTask' we need to translate this into an execution of the
postRequisite tasks. If a task has postRequisite its name is
rather an alias for the postRequisite tasks.
I think all of the above stuff boils down to 3 primitive
dependencies between tasks:
1) taskA should execute if taskB successfully executes
2) taskA should execute if taskB executes and fails
3) taskA should execute if taskB executes at all, regardless of
success or failure
Building the DAG would involve converting from TaskDependency
objects to the above primitives (somehow).
May be I'm missing something. But I think the domain is more
complicated then that (leaving aside conditional tasks).
I didn't explain this too well. It feels to me like there are 2
levels of dependency relationships here. The first level is made up
of high-level, domain specific dependencies. Dependencies such as,
taskA depends on all Report tasks. Or taskB must be executed if taskA
fails. Or 'clean' and all its dependencies must be executed before
'assemble' and all its dependencies. And so on.
These would be represented as TaskDependency implementations attached
to Tasks.
First of all, I'm definitely in favor of a TaskDependency object. And
I also share the notion of high level domain specific dependencies and
low level DAG dependencies.
Domain wise I see another two categories. There are lifecycle
dependencies and execution dependencies. For example the jar task has
a lifecycle dependency on the test task. The jar task does not care if
the test task is skipped. The jar task uses the dependency only to
establish its point of execution in the build. The test task on the
other hand has an execution dependency on testCompile. I know that
this view is a bit fuzzy and has a couple of border cases. Right now
we can only skip a particular task. I think there are use case where
you want to skip a subset of the graph if a certain task is skipped.
Right now we simulate this by adding for example the skip.test
property as a skip property to testResources. But this is not a very
nice solution.
I really like the idea of using the dependency type to exclude and
include things from a build. I'm not sure about the categories, or how
you might declare this. One option would be to allow a condition to be
attached to a dependency, something like:
taskA.dependsOn('taskA', 'taskB').when { buildType == 'full' }
or
taskA.dependsOn { buildType == 'full' ? 'otherTask' : null }
Another option (not necessarily mutually exclusive) would be to allow a
type to be specified for a dependency, and allow the dependency type to
be skipped using something from the command-line.
Another option would be to introduce the concept of a build type (or
build), which would be declared in the build file, and which would
progammatically modify the task graph in some way, such as
including/excluding tasks and dependencies, and executing tasks in a
particular sequence (eg run clean, then run assemble). Some examples
from gradle's build would be the developerBuild, or a quickInstallBuild
which would run clean and install but skip docs and tests (and
checkstyle when we add it, etc).
The second level is made up of a small number of types of low level,
general purpose dependencies. Namely the 3 I described earlier.
I'm still not sure if I understand the way you describe the low level
dependencies.
You say: taskA should execute if taskB executes
This is taskA dependsOn taskB, expressed from the point of view of the
thing that executes the tasks. I should have used dependsOn to express
this, as it's clearer.
The point is really that we take the high-level graph, where a task can
have incoming and outgoing polymorphic dependencies, and compile it down
to a low-level graph, containing simple dependencies which point in 1
direction only.
So, with a project declaring that taskA dependsOn taskB and taskB
doFinally taskC, we get:
gradle taskB -> execute {taskB, taskC} with taskC dependsOn taskB
gradle taskA -> execute {taskA, taskB, taskC} with taskA dependsOn taskB
and taskC dependsOnTaskB
gradle taskC -> execute {taskC}
There would be represented as edges in the DAG.
The process of assembling the DAG would involve evaluating each of
the appropriate TaskDependency instances and adding edges to the DAG.
Build execution would then proceed as it currently does, traversing
the edges, except it would traverse some edges conditionally.
I don't think the way we currently execute the DAG will be enough for
what we want to achieve. I've tried to explain this is my earlier
email. Before I get into this again, I would like to get on the same
page with you regarding the low level dependencies (see above).
I think we're both saying the same thing. I think a soluation is to add
a state enum (on success, on failure, always) to the low level
dependencies we execute, and change the execution algorithm to traverse
a dependency A dependsOn B only when B has executed with the required
state. Currently we have an implicit on 'success state' for all our
dependencies.
Adam
---------------------------------------------------------------------
To unsubscribe from this list, please visit:
http://xircles.codehaus.org/manage_email