On 13/05/10 6:56 AM, Kurt Harriger wrote:
I'm having a slight problem with the execution order.
I like how goals in maven are attached to a lifecycle. I thought this
would be trivial to replicate in gradle using tasks and dependsOn but
things aren't working quite the way I had hoped.
Here is an example:
-------------- build.gradle --------------
// default lifecycle (based on maven lifecycle)
task validate
task initialize( dependsOn: validate )
task generateSources( dependsOn: initialize )
task processSources( dependsOn: generateSources )
task generateResources( dependsOn: processSources )
task processResources( dependsOn: generateResources )
task compile( dependsOn: processResources )
task processClasses( dependsOn: compile )
task generateTestSources( dependsOn: processClasses )
task processTestSources( dependsOn: generateTestSources )
task generateTestResources( dependsOn: processTestSources )
task processTestResources( dependsOn: generateTestResources )
task testCompile(dependsOn: processTestResources )
task processTestClasses(dependsOn: testCompile )
task test(dependsOn: processTestClasses )
task preparePackage(dependsOn: test )
task createPackage( dependsOn: preparePackage )
task preIntegrationTest( dependsOn: createPackage )
task integrationTest( dependsOn: preIntegrationTest )
task postIntegrationTest( dependsOn: integrationTest )
task verify( dependsOn: postIntegrationTest )
task install( dependsOn: verify )
task deploy( dependsOn: install )
task beforeMsBuild << {
println "Executing beforeMsBuild"
}
task msbuild(dependsOn: beforeMsBuild) << {
println "Executing MSBuild"
}
task generateSolutionVersionInfo << {
println "Creating solution version info"
}
generateSources.dependsOn generateSolutionVersionInfo
compile.dependsOn msbuild
-------------- output --------------
:msbuild
Executing MSBuild
:generateSolutionVersionInfo
Creating solution version info
:validate UP-TO-DATE
:initialize UP-TO-DATE
:generateSources
:processSources
:generateResources
:processResources
:compile
BUILD SUCCESSFUL
----
My intention here was to execute these tasks as part of that lifecycle
phase, and I expected that the task dependencies would be executed
in-order defined. Howerver, in this case, msbuild is being executed
before generateSolutionVersionInfo.
Technically, speaking msbuild does not depend on any tasks, so from a
DAG perspective this one correct solution to the stated dependencies,
but not the one I'm looking for.
I could change msbuild to dependsOn the processResources task, but I
wanted the reason I did not do this is because I wanted to keep the
lifecycle and implementation separate as maven does. In some cases I
may want to run "gradle msbuild" directly without executing any other
tasks, but adding a dependsOn processResources would force execution
of the lifecycle up to processResources which is not what I want.
Another way to approach this is perhaps to use execute() instead of
dependsOn.
generateSources << { generateSolutionVersionInfo.execute() }
compile << { msbuild.execute() }
However if I use execute the dependsOn is not processed at all and the
tasks are never added to the taskGraph and beforeMsBuild is not
executed which prevents me from creating mini-lifecycles.
------- output -------
W:\build-sandbox\test>gradle compile
:validate UP-TO-DATE
:initialize UP-TO-DATE
:generateSources
Creating solution version info
:processSources
:generateResources
:processResources
:compile
Executing msbuild
BUILD SUCCESSFUL
Is there another way to go about this?
Yes, you might think about not trying to replicate the phases and goals
model. It's a weak model for describing a build. For example, it does
not describe inter-phase dependencies. Integration tests almost never
actually depend on unit tests. Sometimes they do, but they usually
don't. From the model, there's no way for the build tool to determine
one way or the other. And so, it must be conservative and always run the
unit tests to completion before it starts the integration tests. Which
is almost always unnecessary and almost always undesired. Even when
there is some relationship between the unit and integration tests, a
weak model doesn't give the build tool any opportunity to extract
parallel work, such as running the unit and integration tests
concurrently. Similarly, install almost never actually depends on unit
or integration tests, but the weakness of the model means that the build
tool cannot determine whether or not it can perform an install without
running the unit or integration tests, and so it must assume that they
must be run. And that's almost never what the user actually wants.
So, instead, you should think about going the Gradle way, and describing
only the actual dependencies of each task, and let Gradle do the rest.
You might add in some high-level lifecycle tasks, similar to how the
Java plugin adds in 'check', 'assemble' and 'build'. Using this approach
means Gradle can execute the fewest set of tasks to perform the work you
actually want to have happen. It also means that Gradle will (in later
releases) be able to extract the maximum concurrency, and have the most
freedom to distribute the build across multiple machines.
If you really want to go with the phases-goals model, you should be able
to code it up. I'd use the dependsOn approach applied by some general
purpose code. Something like this:
// Add a task for each phase
phases = ['phase1', 'phase2']
phases.each {
task "$it"
}
// Add the goals and attach to phases
task someGoal
phase1.dependsOn someGoal
// Wire up the dependencies
afterEvaluate { project ->
for (int i = 1; i < phases.size(); i++ ) {
def phaseTask = project.tasks[phases[i]]
def previousPhase = project.tasks[phases[i-1]
// All the dependencies of current phase (ie goals) depend on
the previous phase
phaseTasks.dependsOn*.previousPhase
}
}
Of course, this drags in too many dependencies. You're pretty much stuck
with that for now. There are a couple of new features which we might add
after the 0.9 release, which would make this work better.
One is to introduce the concept of a weak, or optional, dependency. A
weak dependency, such as task A weakly dependsOn task B, only applies
when both task A and B are already in the DAG and simply applies an
ordering, so that task A must execute before task B. Using this approach
to implement the phases model, you could have the goals weakly depend on
the previous phase, and each phase strongly depend on the previous phase.
Another option is to introduce the concepts of builds (for want of a
better term). A build is basically an ordered sequence of tasks or
builds. When the build is executed, the tasks are executed to completion
in the order specified. Using this approach to implement the phases
model, you could declare each phase as a build and have it sequence the
previous phase first.
--
Adam Murdoch
Gradle Developer
http://www.gradle.org
---------------------------------------------------------------------
To unsubscribe from this list, please visit:
http://xircles.codehaus.org/manage_email