Using Gradle to augment your legacy Ant build
Many of us would love to use the latest and greatest tools in our development, but that’s not always easy to do. When dealing with a legacy application, it doesn’t make always make sense to shift technologies, even if that technology provides a lot of benefits.
Gradle is one technology that would be a great addition to most projects. It is a build system that is at once more flexible than Maven and easier to set up than Ant. The builds are written in Groovy code, which makes it easy to read and understand the build. It includes all of the functionality of Ant and interacts with dependency repositories much like Maven or Ivy. However, the biggest benefit may be its easy integration with your existing build system. Gradle integrates nicely with both Ant and Maven, but here we will focus on Ant.
Often times, as an application grows, the Ant build slowly becomes large and unwieldy. Even on projects where developers pay attention to keeping their code up-to-date, tested and refactored properly, the build often goes ignored. If this is the case, just like with old, out-of-date code, developers avoid making changes out of fear for breaking things.
Gradle has an easy mechanism for importing your existing ant build. All you have to do is add the following line to your build.gradle.
ant.importBuild 'build.xml'
This command will allow you to use all of your ant targets as if they were Gradle tasks. At this point you can interact with your ant build as if it were a native Gradle build. Try executing:
gradle <your ant task>
Now that we have all of our targets available in Gradle, let’s see how it makes our lives easier.
Excluding tasks on the fly
Suppose you have a target you want to execute called “test”. Also suppose that somewhere in test’s dependencies is “reallySlowTarget”. It would be nice to be able to exclude reallySlowTarget when we know it’s not needed and still run test. With Gradle, you can do this easily.
gradle -x reallySlowTarget test
This will execute test as normal, but skip reallySlowTarget.
Abbreviated task names
While we’re at it, that last line seemed like a lot to type. Luckily, Gradle also allows us to use camel case to shorten our task names
gradle -x rST test
You can also shorten your target names using a unique starting string. Assuming we don’t have any other tasks that start with ‘t’, we can run
gradle -x rST t
Modifying an existing target
Gradle contains many mechanisms for manipulating a task. Now that our Ant targets are Gradle tasks, we can take advantage of that. In your build.gradle, you can do the following:
reallySlowTarget.doFirst {
println "This is going to take awhile"
}
reallySlowTarget.doLast {
println “Whew, it’s finally done”
}
Or you can totally override a target using
tasks.replace("test") << {
println "This is my new implementation of the 'test' task"
}
This can be a nice way to slowly migrate from your Ant build to Gradle.
You can add conditions to your tasks. For example if you have a “deploy” task:
deploy.onlyIf {new File("mywar.war").exists()}
You can also disable a task by using
deploy.enabled = false
Managing tasks by name
Gradle gives you a great mechanism for working with all of your tasks at once. For example, if you want to add some performance logging, this will log a statement for every task.
tasks.each {
it.doFirst {println "Task ${it.name} executing at ${new Date()}"}
}
The tasks collection has many of the same methods available on Groovy collections. For example, you can find tasks with a certain name prefix. Here, we find all tasks that start with clean and add them to the dependency list of the “fullClean” task.
tasks.findAll{it.name.startsWith("clean")}.each {
fullClean.dependsOn it
}
Only execute tasks when necessary
The final item I’m going to cover is determining when a task is up-to-date. Many times you may know that a certain module hasn’t change and thus doesn’t need to be rebuilt. However, depending on the setup of your ant build, you may have to run the whole build anyway. Gradle offers an easy way to define when a task needs to be executed.
Let’s say that we have an ant task called generateStuff that takes input from the input/foo directory and generates output in the fooOutput directory. Within our Gradle build, we can add the following code.
generateStuff {
inputs.dir file('input/foo')
outputs.dir file('fooOutput')
}
This tells Gradle where to look for input and output when determining whether or not to execute the task. The first time you run this target, it will execute as normal. However, if nothing changes in those directories before you run in the next time, you will see output for that task similar to:
:generateStuff UP-TO-DATE
This can be a huge time saver for those ant tasks that can’t determine their state and take up a lot of time.
There are many more features that Gradle offers. Regardless of your current build system, you are bound to find features that allow you to simplify your build process. For more information visit the online documentation. All examples here were tested with Gradle 0.9.
for those poor souls, still fighting with ant and its xml hell, this article is a real eye opener. running your ant build via gradle is a very good pattern to hit the road to a better way to build.
regards,
René
Excellent information. Gives more incentive to take the Gradle plunge. Languages do matter, and when one builds upon another, synergy.