Chapter 8. Migration

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.

Migration from Ant

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.

Importing Ant file

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:

  1. Build project (compile code and generate a JAR file).
  2. Generate checksum of the JAR file.
  3. Create a ZIP file that contains the JAR file and checksum file.

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.

Accessing properties

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

Update Ant tasks

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

Using AntBuilder API

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:

  1. Setting the properties:

    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

  2. Cleaning the build directories:

    Ant way

    With AntBuilder

    <delete dir="${build}"/>

    ant.delete(dir:"${build}")

  3. Creating new directories:

    Ant way

    With AntBuilder

    <mkdir dir="${classes}"/>
    <mkdir dir="${distributions}"/>

    ant.mkdir(dir:"${libs}")
    ant.mkdir(dir:"${classes}")

  4. Compiling the Java code:

    Ant way

    With AntBuilder

    <javac srcdir="${src}"
    destdir="${build}/classes" 
    includeantruntime="false"/>

    ant.javac(srcdir:"${src}",
    destdir:"${classes}",
    includeantruntime:"false")

  5. Create JAR file from the compiled source code:

    Ant way

    With AntBuilder

    <jar jarfile 
    ="${libs}/sampleproject-${version}.jar" 
    basedir="${classes}"
    />

    ant.jar(
    destfile: "${libs}/sampleproject-${version}.jar",
    basedir:"${classes}") 

  6. Generate the checksum for JAR:

    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}"
    )

  7. Bundle the checksum file and JAR file into a ZIP file:

    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.

Rewriting 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

build

Default build directory name

build/libs

Default jar location

src/main/java; src/test/java

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

Configuration

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:

  1. Cleaning the build directories:

    Ant way

    Gradle way

    <target 
    name="clean" description="clean up">
    
       <delete dir =   "${build}"/>
    
        <delete dir = "${dist}"/>
    </target>

    task cleanDir(type: Delete) {
      delete "${build}"
    }

  2. Creating new directories:

    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()
    }

  3. Compiling the Java code:

    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
    }

  4. JAR the compiled classes:

    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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.128.145