I recently set out to add a few “synonym tasks” to our gradle based build. This is something which is quite easy to do in the ant world, and it seemed like a concept that would be very easy to translate into gradle.
What’s a synonym task? Its a task that just runs a series of other tasks, in a specified order (it could also be called an “alias”). For example, instead of typing this long series of tasks:
gradlew clean assemble runProvisioner stopTomcat installTomcat deployToTomcat startTomcat
Its much more convenient to just type something like this (where “all” is an alias for the above list of tasks) :
gradlew all
Now – if you explicitly tell Gradle which tasks to run via the command line (by typing them out each time), it will respect the ordering you provide. It will execute each of those tasks (along with each of their dependencies) in the order in which you specified via the command line.
Given that – you’d think it would be pretty simple to create an alias task which just calls those things in the expected order.
Not so.
This turned out to be a lot harder than it should have been (which is what drove me to write this, on the chance it might help someone else).
Why is defining a synonym task hard?
Gradle task execution order is typically dependency driven. Left to its own devices, Gradle likes to determine the order in which to run tasks based on a DAG (“Directed Acyclic Graph).
In Gradle, dependencies aren’t necessarily ordered.
Its seems that this has been a point of contention for a while. Many of the discussions I found reference GRADLE-427 (which was opened in 2009).
In recent versions of Gradle, they added a .mustRunAfter method which allows a task to say, “When this other task is going to be run anyway, I should run after it.” That was meant to address the 427 issue, but doesn’t really seem like a great solution for the synonym / alias use case. Here, another Gradle user laments the same problem, and one of the Gradle devs chimes in with a utility function that can provide the equivalent of ordered dependencies:
http://forums.gradle.org/gradle/topics/mustrunafter_not_a_usable_fix_fot_the_dependson_ordering_bug
My first solution to the build aliases problem was to solving it similarly to the way the above post suggests. I wasn’t happy with this solution (its a bit too heavy), but I’ll share it here, just for completeness :
void wireOrderedDependencies( task, others) { task.dependsOn(others) for (int i = 0; i < others.size() - 1; i++) { others[i+1].mustRunAfter(others[i]) } } task all(description: 'an alias for : clean assemble stopTomcat runProvisioner installTomcat deployToTomcat startTomcat') { } //--- set up the "all" target's dependencies IN ORDER in this List def all_dependenciesInOrder = [] subprojects.each () { sp -> all_dependenciesInOrder.add sp.clean } all_dependenciesInOrder += [project(':myapp-provisioner').assemble, project(':myapp-web').assemble, stopTomcat, runProvisioner, installTomcat, deployToTomcat, startTomcat] wireOrderedDependencies(all, all_dependenciesInOrder)
This initial solution seems like something that wouldn’t scale very well. For example, things would get very messy if we had 8-9 different aliases.
As I researched the issue a bit further, I found what seemed like a much cleaner suggestion referenced within one of the (many) comments on GRADLE-427. It was this suggestion.
That seemed like a much cleaner approach, and is the basis for what I ended up doing. Here’s the cleaner solution :
//--- build aliases : define a synonym here if you want a shortcut to run multiple targets def buildAliases = [ 'all' : ['clean', 'assemble', 'runProvisioner', 'stopTomcat', 'installTomcat', 'deployToTomcat', 'startTomcat'], 'rebuild' : ['clean', 'assemble'] ] def expandedTaskList = [] gradle.startParameter.taskNames.each { expandedTaskList << (buildAliases[it] ? buildAliases[it] : it) } gradle.startParameter.taskNames = expandedTaskList.flatten() println "\n\n\texpanded task list: ${gradle.startParameter.taskNames }\n\n"
This solution works off the fact we discussed earlier: If you explicitly invoke Gradle from the command line with a specific list of tasks, it respects the supplied ordering for those tasks. gradle.startParameter.taskNames contains the list of tasks that the user supplied on the command line.
This solution simply iterates the user supplied list of task names, and if it finds one of these task names in the buildAliases map, it “expands” the task name to be the associated list of tasks. Much better.