Hans Dockter wrote:
On May 10, 2009, at 5:04 AM, Steve Appling wrote:
Adam Murdoch wrote:
Steve Appling wrote:
Tom Eyckmans wrote:
2009/5/6 Adam Murdoch <[email protected]
<mailto:[email protected]>>
<clip>
I'm curious, how we will make this available to tasks?
You'd implement a ChangeProcessor that gets notified of what has
changed, depending how the task works you can just keep a list of
the files / directories that have changed and execute the function
of the task on the list or execute the function of the task in the
ChangeProcessor methods.
Currently the change detection always scans for all files /
directories that have changed, this is not always needed so I'll
add mulitple old / new state comparison strategies. So you can
have only one event if the directory has changed in some way or
for every file / directory that has changed.
When I was experimenting with Tom's change detection, I added a
dependsOnDir method to Task that used his ChangeProcessor. If
nothing in the dir changed, it would throw a
StopExecutionException. This seemed to work well for customizing
some simple user defined tasks.
This is a good idea - it's simple, but is probably sufficient for
90% of custom tasks.
I'm not sure about the method name - do we want to reuse 'dependsOn'
or use a different term?
Adam
That is a good point, Adam. This is very different from the other
dependencies, in some ways this is the opposite (stopping tasks
instead of adding more to the DAG). I think there may be a range of
these user specified optimizations. How about this instead:
Task gets a new method, onlyIf that takes a closure. The closure is
run before any actions and is expected to return a boolean. If the
boolean is true, then the task continues executing normally, if it
returns false, the task is stops execution, but logs an appropriate
lifecycle message "skipped (optimized)".
The onlyIf closure runs with a delegate of a new class, an
OptimizationHelper, that has some of the helper methods for various
checks like Tom's change detection. This could look like:
myTask.onlyIf {
dirChanged(file 'src/main/java')
}
or
myTask.onlyIf { !file('build/someresult').exists() }
This seems very simple. If anyone likes this, I can make a git
branch that would support this.
I like the idea. It might be implemented in the context of the
skipping refactoring planned for 0.7:
Right now we have enabled true/false, we have skip properties and we
have StopExecutionException to deal with skipping.
For 0.7 we want to have (a) smart skipping, (b) a new command line
syntax and (c) an improved api for skipping.
(a): If a task gets skipped, we will automatically skip all tasks
where the skipped task is the only one that depends on them.
(b): With the new command line syntax (e.g. -test instead of
-Dskip.test) we might get rid of the notion of skip properties
altogether. In particular as (a) will also solve a subset of the
problems where you currently use skip properties.
I think we should. You can very easily add them back in if you like:
tasks.each { task ->
task.onlyIf { !System.getProperty("skip.$task.name") }
}
(c): I like the onlyIf idea. I'm wondering if onlyIf can even used to
replace enabled/disabled. (onlyIf { false }).
I think it should
Together with (b) that might simplify the API in a very nice way.
Another thing I'm wondering is, whether onlyIf should follow the
specification pattern
(http://en.wikipedia.org/wiki/Specification_pattern).
Yes. You should be able to call onlyIf() with either a closure or a
Spec<Task>. You should be able to make multiple calls to onlyIf(), and
the task is enabled only if all such predicates are true.
Right now skipping a task means not executing its actions. I think a
nicer solution might be to exclude the task from the DAG. In this case
we might also change the terminology from 'skipping a task' to
'excluding a task'. Excluding would make it easier to follow and work
with the execution of a build. For example when looking at the console
output or when working with the dependency graph.
This works for tasks skipped from the command-line. I'm not sure it
works when tasks need to be skipped because their input and output
artifacts are up to date.
For the up-to-date case, you can exclude a task from the DAG only if, at
the point in time when it would normally execute, its input artifacts
haven't changed since last time it was run, and its output artifacts
still exist unmodified. The problem is that you can't know this in a
general way when the DAG is assembled. You can know it sometimes. For
example, you know the answer when all the dependencies of the task are
also excluded, or when they declare that they won't touch the input or
output files.
What this mean, in practise, is that every task has to do up-to-date
checking. A task deep in the DAG which does not do any checking
effectively means that nothing will ever be treated as up-to-date. And
so, every task defined in a build file would have to have an onlyIf
predicate which checks that its input and output files are up to date.
Which is really a big step backwards as far as usability goes: Firstly I
have to do the work of declaring all this stuff for every task
(including, somehow, tasks which simply configure the project in some
way), and secondly, it is extremely easy to break the up to date
checking of the build as a whole, by forgetting to do this for a single
task.
So, I think we want a solution which provides some way for a task to
declare that it shouldn't be executed, at 2 different points in time -
when the DAG is being assembled, and when the task is about to be executed.
Some options:
- Evaluate onlyIf twice: once when the DAG is assembled, and again just
before executing the task. False means 'do not execute' and true means
'maybe - keep going and I'll let you know later'
- Have 2 different properties, say, skipWhen and excludeWhen (bad names,
I know, but you get the idea)
Were we to have a more artifact-centric way of declaring task
dependencies, then Gradle can take care of figuring this stuff out on
behalf of the tasks.
As an alternative a StopExecutionException would still be available
(no smart skipping, no exclusion from the graph).
Would it makes sense to provide a skipping API also to actions?
I don't think we should. Tasks should be our atomic units of scheduling.
Actions should simply be chunks of code that get executed as part of a
task. If you need to skip some actions and not others, you can use the
if statement, or throw a StopExecutionException, or split the task up,
or implement a custom task which does this.
Adam
---------------------------------------------------------------------
To unsubscribe from this list, please visit:
http://xircles.codehaus.org/manage_email