If you are coming from an Ant or Maven background, the first question that comes to mind is: why Gradle? We have already discussed this topic in the initial chapters. Then, another important question comes up. We already have lots of build code written in Ant or Maven; now, if a new script needs to be written into Gradle, wouldn't it be tough to manage two build tools? In this chapter, we will explain different techniques to migrate existing Ant or Maven script to Gradle build script. In the first section of this chapter, we will discuss different strategies that can be applied to migrate from Ant to Gradle and later sections will cover strategies from Maven to Gradle migration.
Ant is one of the initial build tools that became very popular among developers because they can control each and every step of the build process. But writing each and every step means a lot of boilerplate code in the build file. Another feature which was lacking in the initial Ant releases was complexity in dependency management, which was later simplified by introducing an Ivy dependency manager. For Ant users, it is very simple and easy to switch to using Gradle as their build tool. Gradle provides direct integration with Ant using Groovy's AntBuilder
. Ant tasks are treated as first class citizens in the Gradle world. In the next sections, we will discuss three different strategies: importing Ant file into Gradle, script use of AntBuilder
class and rewriting to Gradle. These strategies can be followed to migrate from Ant to Gradle.
This is one of the simplest ways of integrating the Ant script with Gradle script. It is very useful as the first step of migration where you have lots of build scripts, all written in Ant, and you want to start with Gradle without making any big changes to the current build structure initially. We will start with a sample Ant file. Consider we have the build.xml
Ant file for a Java project and we perform the following tasks:
The following is the build.xml
file to perform all three preceding mentioned operations:
<project name="sampleProject" default="makeJar" basedir="."> <property name="src" location="src/main/java"/> <property name="build" location="build"/> <property name="classes" location="build/classes"/> <property name="libs" location="build/libs"/> <property name="distributions" location="build/distributions"/> <property name="version" value="1.0"/> <target name="setup" depends="clean"> <mkdir dir="${classes}"/> <mkdir dir="${distributions}"/> </target> <target name="compile" depends="setup" description="compile the source"> <javac srcdir="${src}" destdir="${build}/classes" includeantruntime="false"/> </target> <target name="makeJar" depends="compile" description="generate the distributions"> <jar jarfile="${libs}/sampleproject-${version}.jar" basedir="${classes}"/> </target> <target name="clean" description="clean up"> <delete dir="${build}"/> </target> <target name="zip" description="zip the jar and checksum" depends="makeJar,checksum"> <zip destfile="${distributions}/sampleproject.zip" filesonly="true" basedir="${libs}" includes="*.checksum,*.jar" /> </target> <target name="checksum" description="generate checksum and store in file" depends="makeJar"> <checksum file="${libs}/sampleproject-${version}.jar" property="sampleMD5"/> <echo file="${libs}/sampleproject.checksum" message="checksum=${sampleMD5}"/> </target> <target name="GradleProperties"> <echo message="Gradle comments are:: ${comments}"/> </target> </project>
To build the project, you need to run the following target (in Ant, we execute a target that can be compared to a Gradle task):
SampleProject$ ant makeJar Buildfile: <path>/Chapter8/SampleProject/build.xml clean: [delete] Deleting directory <path>/Chapter8/SampleProject/build setup: [mkdir] Created dir: <path>/Chapter8/SampleProject/build/classes [mkdir] Created dir: <path>/Chapter8/SampleProject/build/distributions compile: [javac] Compiling 2 source files to <path>/Chapter8/SampleProject/build/classes makeJar: [jar] Building jar: <path>/Chapter8/SampleProject/build/libs/sampleproject-1.0.jar BUILD SUCCESSFUL Total time: 0 seconds
The target will execute other required targets such as compile, clean, setup, and so on. This will generate the sampleproject-1.0.jar
file in the build/libs
directory. Now, to generate checksum for JAR and to bundle it along with the JAR file, we can run the following target:
$ ant zip
This target will run the makeJar
target and all the other dependent targets to create the JAR file and then it will execute the checksum target to generate the md5
checksum for the JAR file. Finally, ZIP task will bundle the checksum file and the JAR file, and creates a ZIP file inside the build/distributions
directory.
This is a sample build file for a Java project; you can have additional targets for customized requirements. We can simply import this Ant
build file in a Gradle build file and will be able to execute Ant targets as Gradle tasks. The content of the build file will look as follows:
ant.importBuild 'build.xml'
This one line is sufficient to import the Ant build file and go ahead with Gradle. Now try to execute the following:
$ gradle -b build_import.gradle zip ::clean :setup :compile :makeJar :checksum :zip BUILD SUCCESSFUL
Here we have named the build file as build_import.gradle
. The preceding command executed all Ant tasks, one after another. You can find the ZIP file created in the build/distributions
directory.
This is one of the first steps to migrate from Ant to Gradle. This will help initially, if you do not want to play with the existing build script and want to use Gradle. Just importing the Ant file in the Gradle build help you to get started.
Gradle also enable you to access existing Ant properties and add new properties. To access existing Ant properties, you can use ant.properties
, as shown here:
ant.importBuild 'build.xml' def antVersion = ant.properties['version'] def src = ant.properties['src'] task showAntProperties << { println "Ant Version is "+ antVersion println "Source location is "+ src }
$ gradle -b build_import.gradle sAP :showAntProperties Ant Version is 1.0 Source location is D:Chapter8SampleProjectsrcmainjava BUILD SUCCESSFUL
Here, Gradle script has fetched properties value from the Ant file and we are printing the value in the Gradle task. In a similar fashion, we can set Ant properties in Gradle and access these properties in the Ant build file.
Update the build file with the following statement:
ant.properties['comments'] = "This comment added in Gradle"
This property will be read by the GradleProperties
target in the Ant file, as follows:
<target name="GradleProperties"> <echo message="Gradle comments are ${comments}"/> </target>
Now, on executing the GradleProperties
target, we can find the comments property printed in the console as shown in this code snippet:
$ gradle -b build_import.gradle GradleProperties Starting Build ... Executing task ':GradleProperties' (up-to-date check took 0.015 secs) due to: Task has not declared any outputs. [ant:echo] Gradle comments are:: This comments added in Gradle :GradleProperties (Thread[main,5,main]) completed. Took 0.047 secs. BUILD SUCCESSFUL
Gradle also enables you to enhance an existing Ant task. In the same way, we enhance any existing Gradle tasks using the doFirst
or doLast
closures; Ant tasks can be extended in a similar fashion. Add the following statements in the build file (file: build_import.gradle
) to add the doFirst
and doLast
closures to the GradleProperties
task:
GradleProperties.doFirst { println "Adding additional behavior before Ant task operations" } GradleProperties.doLast { println "Adding additional behavior after Ant Task operations" }
Now, the GradleProperties
target executes the doFirst
and doLast
closures, and the console output is displayed as follows:
$ gradle -b build_import.gradle GP Starting Build …… :GradleProperties (Thread[main,5,main]) started. :GradleProperties Executing task ':GradleProperties' (up-to-date check took 0.003 secs) due to: Task has not declared any outputs. Adding additional behavior before Ant task operations [ant:echo] Gradle comments are:: This comments added in Gradle Adding additional behavior after Ant Task operations :GradleProperties (Thread[main,5,main]) completed. Took 0.158 secs. BUILD SUCCESSFUL
We have seen how easy it is to just import the Ant build.xml
into Gradle and use Ant targets as Gradle tasks. Another approach is to use the AntBuilder
class. With AntBuilder
you can call Ant tasks from the Gradle script. An instance of AntBuilder
class called 'ant' is available in the Gradle build file. Using this instance, when we call a method, it actually executes an Ant task.
In the following examples, we will use the same build.xml
file and will explain how to rewrite the tasks to Gradle using AntBuilder
:
Ant way |
With AntBuilder |
---|---|
<project name="qualitycheck" default="makeJar" basedir="."> <property name="src" location="src/main/java"/> <property name="build" location="build"/> <property name="lib" location="lib"/> <property name="dist" location="dist"/> <property name="version" value="1.0"/>
|
defaultTasks "makeJar" def src = "src/main/java" def build = "build" def libs = "build/libs" def classes = "build/classes" def distributions = "build/distributions" def version = 1.0
|
Ant way |
With AntBuilder |
---|---|
<delete dir="${build}"/>
|
ant.delete(dir:"${build}")
|
Ant way |
With AntBuilder |
---|---|
<mkdir dir="${classes}"/> <mkdir dir="${distributions}"/>
|
ant.mkdir(dir:"${libs}") ant.mkdir(dir:"${classes}")
|
Ant way |
With AntBuilder |
---|---|
<javac srcdir="${src}" destdir="${build}/classes" includeantruntime="false"/>
|
ant.javac(srcdir:"${src}", destdir:"${classes}", includeantruntime:"false")
|
Ant way |
With AntBuilder |
---|---|
<jar jarfile ="${libs}/sampleproject-${version}.jar" basedir="${classes}" />
|
ant.jar( destfile: "${libs}/sampleproject-${version}.jar", basedir:"${classes}")
|
Ant way |
With AntBuilder |
---|---|
<checksum file="${libs}/sampleproject-${version}.jar" property="sampleMD5"/> <echo file ="${libs}/sampleproject.checksum" message="checksum=${sampleMD5}" />
|
ant.checksum( file:"${libs}/sampleproject-${version}.jar", property:"sampleMD5" ) ant.echo(file:"${libs}/sampleproject.checksum", message:"checksum=${ant.sampleMD5}" )
|
Ant way |
With AntBuilder |
---|---|
<zip destfile ="${distributions}/sampleproject.zip" filesonly="true" basedir="${libs}" includes="*.checksum,*.jar" />
|
ant.zip(destfile: "${dist}/sampleproject.zip", basedir:"dist")
|
So the complete build file will look as follows:
defaultTasks "makeJar" def src="src/main/java" def build="build" def libs="build/libs" def classes = "build/classes" def distributions="build/distributions" def version=1.0 task setup(dependsOn:'clean') << { ant.mkdir(dir:"${libs}") ant.mkdir(dir:"${classes}") } task clean << { ant.delete(dir:"${build}") } task compileProject(dependsOn:'setup') << { ant.javac(srcdir:"${src}",destdir:"${classes}", includeantruntime:"false") } task makeJar << { ant.jar(destfile: "${libs}/sampleproject-${version}.jar", basedir:"${classes}") } task zip(dependsOn:'checksum') << { ant.zip(destfile: "${distributions}/sampleproject.zip", basedir:"${libs}") } task checksum(dependsOn:'makeJar') << { ant.checksum(file:"${libs}/sampleproject-${version}.jar", property:"sampleMD5") ant.echo(file:"${libs}/sampleproject.checksum", message:"checksum=${ant.sampleMD5}") } makeJar.dependsOn compileProject
Now, execute the ZIP task and check the distributions directory. You will find the sampleproject.zip
file, created as follows:
$ gradle -b build_ant.gradle zip :clean :setup :compileProject :makeJar :checksum :zip BUILD SUCCESSFUL
Note here that AntBuilder
is most useful for the custom Ant taskdef
tasks that have not been ported over to Gradle.
Until now, we have seen how easy it is to import an Ant file to a Gradle script. We also looked into a different approach, where we used AntBuilder
instance to replicate the same behavior while migrating from Ant to Gradle. Now, in the third approach, we will rewrite the Ant script in Groovy.
We will continue with the same Ant build.xml
file and we will convert this to a Gradle build script. In this example, we are building a Java project. As we know, to build a Java project Gradle already provides us with a Java plugin; you just need to apply the Java plugin in the build file and that is all. The Java plugin will take care of all the standard conventions and configurations.
The following are some of the conventions of the Java plugin that we have already discussed in Chapter 4, Plugin Management:
Convention used |
Description |
---|---|
|
Default build directory name |
|
Default jar location |
|
Java source files location |
Project name |
Archive filename |
If the project also follows these conventions, we do not need to write any additional configurations for the project. The only configuration needed is to define the version property; otherwise the JAR will be created without the version information.
So, our new build script will look as follows:
apply plugin :'java' version = 1.0
Now, we are done. No need to write any script to create and delete directories, compile files, create JAR tasks, and so on. You can find <projectname>-<version>.jar
in the build/libs
directory after executing the build command:
$ gradle build :clean :compileJava :processResources UP-TO-DATE :classes :jar :assemble :compileTestJava UP-TO-DATE :processTestResources UP-TO-DATE :testClasses UP-TO-DATE :test UP-TO-DATE :check UP-TO-DATE :build BUILD SUCCESSFUL
It is so easy to trim around 30 lines of Ant code to two lines of Gradle code. It allowed us to escape all the boilerplate code and concentrate on the main logic. However, all projects can't be simply converted just by applying a plugin or following some convention. You might need to configure sourceSets
and other configurations if the project does not follow Gradle or Maven conventions.
Coming back to the example, we have only created the JAR file; two more tasks are pending. We have to generate a file to store the checksum and we need to bundle the checksum file and JAR file in to a ZIP file. We can define two additional tasks to accomplish this, as follows:
apply plugin:'java' version = 1.0 task zip(type: Zip) { from "${libsDir}" destinationDir project.distsDir } task checksum << { ant.checksum(file:"${libsDir}/${project.name}-${version}.jar",property:"sampleMD5") ant.echo(file:"${libsDir}/${project.name}.checksum",message:"checksum=${ant.sampleMD5}") } zip.dependsOn checksum checksum.dependsOn build
In the preceding build script, checksum task will create the checksum for the jar file. Here we are again using Ant. The checksum task creates checksum, as this is the simplest way in Gradle. We have configured the ZIP task (of type ZIP) to create a ZIP file. Gradle already provides a convention for the build/distributions directory as project.distsDir
:
$ gradle clean zip :clean :compileJava :processResources UP-TO-DATE :classes .... :check UP-TO-DATE :build :checksum :zip BUILD SUCCESSFUL
If you do not want to follow the convention, Gradle provides an easy way to configure projects as per requirement. We will show how to configure previously created Ant tasks in Gradle:
Ant way |
Gradle way |
---|---|
<target name="clean" description="clean up"> <delete dir = "${build}"/> <delete dir = "${dist}"/> </target>
|
task cleanDir(type: Delete) { delete "${build}" }
|
Ant way |
Gradle way |
---|---|
<target name="setup" depends="clean"> <mkdir dir = "${build}"/> </target>
|
task setup(dependsOn:'cleanDir') << { def classesDir = file("${classes}") def distDir = file("${distributions}") classesDir.mkdirs() distDir.mkdirs() }
|
Ant way |
Gradle way |
---|---|
<target name="compile" depends="setup" description="compile the source"> <javac srcdir="${src}" destdir="${build}" /> </target>
|
compileJava { File classesDir = file("${classes}") FileTree srcDir = fileTree(dir: "${src}") source srcDir destinationDir classesDir }
|
Ant way |
Gradle way |
---|---|
<target name="dist" depends="compile" description="generate the distribution"> <mkdir dir="${dist}"/> <jar jarfile="${dist}/sampleproject-${version}.jar" basedir="${build}"/> </target>
|
task myJar(type: Jar) { manifest { attributes 'Implementation-Title': 'Sample Project', 'Implementation-Version': version, 'Main-Class': 'com.test.SampleTask' } baseName = project.name +"-" +version from "build/classes" into project.libsDir }
|
So the final build file (build_conf.gradle
) with configuration will look as follows:
apply plugin:'java' def src="src/main/java" def build="$buildDir" def libs="$buildDir/libs" def classes = "$buildDir/classes" def distributions="$buildDir/distributions" def version=1.0 task setup(dependsOn:'cleanDir') << { def classesDir = file("${classes}") def distDir = file("${distributions}") classesDir.mkdirs() distDir.mkdirs() } task cleanDir(type: Delete) { delete "${build}" } compileJava { File classesDir = file("${classes}") FileTree srcDir = fileTree(dir: "${src}") source srcDir destinationDir classesDir } task myJar(type: Jar) { manifest { attributes 'Implementation-Title': 'Sample Project', 'Implementation-Version': version, 'Main-Class': 'com.test.SampleTask' } baseName = project.name +"-" +version from "build/classes" into project.libsDir } task zip(type: Zip) { from "${libsDir}" destinationDir project.distsDir } task checksum << { ant.checksum(file:"${libsDir}/${project.name}-${version}.jar", property:"sampleMD5") ant.echo(file:"${libsDir}/${project.name}.checksum", message:"checksum=${ant.sampleMD5}") } myJar.dependsOn setup compileJava.dependsOn myJar checksum.dependsOn compileJava zip.dependsOn checksum
Now, try to execute the ZIP command. You can find the JAR file and checksum file created in the build/libs
directory, and the ZIP file inside the build/distributions directory:
$ gradle -b build_conf.gradle zip :cleanDir :setup :myJar :compileJava :checksum :zip BUILD SUCCESSFUL
3.133.128.145