Chapter 3. Ant and Gradle

When considering leveraging an existing investment in Apache Ant, or perhaps using the broad set of tasks that the Ant community has created, Gradle has a great story to tell. Gradle offers a complete superset of Ant. In fact, it makes Gradle’s use of Ant simpler than directly using Ant, partly by leveraging Groovy’s AntBuilder functionality. Gradle brings in everything from the Ant namespace into the Gradle namespace such that using a core Ant task is as easy as calling ant.<taskname>.

Discussing the usage of Ant from Gradle provides a great bridge in terms of progressive migration to a pure Gradle strategy. Over time though, we believe you’ll desire to standardize on the more powerful Gradle feature set by using native Gradle components or by wraping any Ant behavior in a Gradle plug-in. The latter maintains the value of the functioning Ant behavior while fully enabling build-by-convention throughout the updated Gradle build ecosystem. With full awareness of the quantity of existing Ant infrastructure and value of leveraging it via the new build tool, Gradle ships with a full copy of Ant, thus making Ant’s default tasks available to every Gradle build. To aid with this mind-set remapping, we’ll discuss how Gradle compares to Ant, providing parallels in the approaches to writing builds and to each tool’s unique terminology.

The Vocabulary

Gradle is occasionally described as a Groovy-based Ant. That would be the role that Gant fills, but Gradle has much more ambitious aims. Gradle offers the flexibility of Ant, which many teams still cherish, but with the dependency management style of Ivy, the intelligent defaults of Maven, the speed and hashing of Git, and the meta-programming power of Groovy. That potent best-of-breed blend is an intrinsic motivator for joining the Gradle movement.

Gradle, like Ant, has a Directed Acyclic Graph (DAG) of tasks in its execution plan. However, Gradle takes better advantage of this graph, and will, in the future, run tasks in parallel that are on discrete paths on the DAG. Gradle also plans to leverage the value of holding this build DAG in memory to run certain graph paths of tasks in parallel across a distributed set of build machines. A prototype visualization tool has even been produced that writes the DAG into a DOT format, as shown in Figure 3-1.

DAG of Gradle Tasks
Figure 3-1. DAG of Gradle Tasks

Caution

Both Ant and Gradle use the word task, but with different meanings in each namespace.

Ant uses the word task to indicate one of its executable components (close to what many would call a plug-in in other systems), such as echo. An Ant task can be executed within the context of a target. Gradle defines task to refer to any step of the directed acyclic graph. task in Gradle most closely compares to a target in Ant, but shares some of the benefits of a task in Ant in that a Gradle plug-in can bring several automatic Gradle tasks into the sequence of build steps.

A property in Ant can be compared to a variable in Gradle. In the more powerful Groovy language that Gradle provides, variables can be typeless (as in Ant) or typed, if desired. Variables are available for reference in the parent Gradle project or any subprojects.

Example 3-1. Ant build script defining a property
<project>
    <property name="buildoutput" location="output"/>
    <property name="appversion" value="2.1"/>
</project>
Example 3-2. Gradle build script defining variables
def helloName = "Fird Birfle"
int personAge = 43

task hello << {
    println "Hello ${helloName}. I believe you are ${personAge} years of age."
}

Hello Ant

Example 3-3. Ant calling the Ant Echo plug-in with an attribute
<project>
    <target name="helloViaAttribute">
        <echo message="hello from Ant"/>
    </target>
</project>
Example 3-4. Gradle calling the Ant Echo plug-in with an attribute
task hello << {
    String greeting = "hello from Ant"
    ant.echo(message: greeting)
}

In Example 3-4, we took a simple string and supplied it to the message parameter of the echo Ant task. It is useful to reiterate that no import-like plug-in statement was needed to make ant (a Groovy AntBuilder instance) available for invocation. Further, no command was needed to make the echo task available for calls. To fully discover which defined fields can be supplied with values, simply refer to the actual Ant Echo task documentation since Gradle provides a lightweight wrapper over the Ant implementation.

Making just a minor change to this build, we will additionally use the Echo task’s documented ability to accept a message as the sole input as ant.echo(message: greeting). In Ant’s XML form (Example 3-5), this means that the greeting is put between the <echo> task tags, not in a message attribute of the <echo message=""> tag. In Gradle (Example 3-6), the equivalent syntax is passing the greeting string directly into the echo() call without naming the field to which the value should be assigned.

Example 3-5. Ant calling the Ant Echo plug-in with a tag
<project>
    <target name="helloViaTag">
        <echo>hello from Ant</echo>
    </target>
</project>
Example 3-6. Gradle calling the Ant Echo plug-in with a parameter
task hello << {
    String greeting = "hello from Ant"
    ant.echo(greeting)
}

This encompasses the basics of using Ant with Gradle, but hardly is the full breadth of such interoperation. Let’s go a step further with the importing of custom Ant Tasks, configuration of complex nested Ant structures, and weaving of dependence between Gradle tasks and Ant targets.

Importing Custom Ant Tasks

Using the core Ant tasks from Gradle was easy, but using a custom Ant task is nearly as simple. Just use the taskdef method on the AntBuilder instance according to the same recipes that would be used for its Ant invocations.

For the first example, the Checkstyle Ant task will be loaded from a combination of a properties file and a set of local directory JARs.

Example 3-7. Gradle loading the Checkstyle Ant task via a resource
task checkTheStyle << {
    //Load the custom task
    ant.taskdef(resource: 'checkstyletask.properties') {
        classpath {
            fileset(dir: 'libs/checkstyle', includes: '*.jar')
        }
    }

    //Use the custom task
    ant.checkstyle(config: 'src/tools/sun_checks.xml') {
        fileset(dir: 'src')
    }
}

In Example 3-7, the entire loading and execution of the custom Ant task happens within a single Gradle task. Using standard Ant capabilities, we fetch the parameters needed to load the Ant task from a properties file named checkstyletask.properties that is contained within one of the jars in the libs/checkstyle directory. It happens to be contained in checkstyle-5.3-all.jar and the properties file’s contents are minimal, as shown in Example 3-8. There is simply a mapping from the desired task name checkstyle, to bring into the Ant namespace and the fully qualfied class that implements it, com.puppycrawl.tools.checkstyle.CheckStyleTask.

Example 3-8. The checkstyletask.properties file from the Checkstyle JAR
checkstyle=com.puppycrawl.tools.checkstyle.CheckStyleTask

Lastly, in Example 3-7, we invoke the checkstyle Ant task via a traditional-style method call to ant.checkstyle(...). The nested configurations via config and fileset directly correspond to the Checkstyle Ant equivalents as if this was being configured with Ant’s XML syntax.

As a contrasting example, a custom Ant Task can be loaded using a combination of a Gradle configuration, a declared dependency, and Maven Central repository connectivity.

Example 3-9. Gradle loading the PMD Ant task via a Maven repository and Gradle configuration
configurations {
    myPmd
}

dependencies {
    myPmd group: 'pmd', name: 'pmd', version: '4.2.5'
}

repositories {
    mavenCentral()
}

task checkThePMD << {
    ant.taskdef(name: 'myPmdTask', classname: 'net.sourceforge.pmd.ant.PMDTask', 
        classpath: configurations.myPmd.asPath)
    ant.myPmdTask(shortFilenames: 'true', failonruleviolation: 'true',
      rulesetfiles: file('src/tools/pmd-basic-rules.xml').toURI().toString()) {
        formatter(type: 'text', toConsole: 'true')
        fileset(dir: 'src/main/java')
    }
}

In this more complex example of Example 3-9, we first establish a configuration for myPmd which is a preprocessing step prior to task executions. Configurations were first introduced as a concept in Task Configuration. The configurations closure declares a grouping named myPmd for our subsequent declaration of JAR requirements via the dependencies closure. In the dependencies closure, we once address the myPmd grouping and indicate that it should download the PMD JAR as needed. The source of the download is declared as the special repository mavenCentral() via the repositories closure. Next, the Gradle task checkThePMD is established. During the loading of the Ant task definition via ant.taskdef(///) call, the JAR downloaded as part of the myPmd configuration is added the myPmdTask classpath. Last, the myPmdTask is invoked via ant.myPmdTask(...) with the parameters passed to the Ant task in the same nested manner as Example 3-7.

Complex Ant Configuration

In more complex Ant configurations, there typically are several nested elements in the build.xml syntax. Though Gradle provides a native class of tasks for archiving, and specifically a task type of Zip, a meaningful and straightforward example of using Ant’s zip task can display the native Ant XML configuration and the same independent configuration of said Ant task from within Gradle using the clearer AntBuilder notation.

Example 3-10. Ant build.xml zipping the source files
<project>
  <target name="zipsourceInAnt">
    <zip destfile='samples-from-ant.zip'>
      <fileset dir= 'samples'>
        <include name='**.txt'/>
      </fileset>
    </zip>
  </target>
</project>

In Example 3-10, a very common configuration for preparing a compressed archive using Ant’s zip task and XML configuration is shown. It includes all files in any subdirectory of samples that ends with the extension .txt and writes it into a file named samples-from-gradle.zip.

Example 3-11. Gradle using Ant to zip the source files
task zipsourceInGradle << {
    ant.zip(destfile: 'samples-from-gradle.zip') {
        fileset(dir: 'samples') {
            include(name: '**.txt')
        }
    }
}

In Example 3-11, the more readable Groovy closure notation is used within Gradle to access this same Ant task, offering a one-to-one comparison with the pure Ant execution of the same behavior in Example 3-10.

Importing an Entire Ant Build File

Up to this point, we’ve dealt with interoperability of Ant and Gradle on a fine-grained level, either calling existing Ant tasks, creating new Ant tasks on the fly, or manipulating the results of Ant task calls with Groovy. But what if you want to bring everything in from an existing Ant build? Gradle handles that without even breaking a sweat.

First, we begin with our traditional Ant project in Example 3-3. Then, inside the Gradle build, we import the Ant targets into a Gradle task.

Example 3-12. Import an entire ant project
ant.importBuild 'build.xml'

The result is an ability to treat the Ant build’s contents as a Gradle task from anywhere else in the Gradle ecosystem. This is an implementation of the Facade pattern; all the utility of the Ant build is maintained, but all the value that Gradle brings to builds is additionally available to the buildmaster.

If we query Gradle for what Gradle tasks it knows about in Example 3-12, the answer will include the helloViaAttribute Ant target.

Example 3-13. Task list from the imported Ant project
$ gradle tasks
:tasks

Help tasks
__________
dependencies - Displays the dependencies of root project 'ant-import-to-gradle'.
help - Displays a help message
projects - Displays the subprojects of root project 'ant-import-to-gradle'.
properties - Displays the properties of root project 'ant-import-to-gradle'.
tasks - Displays the tasks in root project 'ant-import-to-gradle'.

Other tasks
___________
helloViaAttribute

To see all tasks and more detail, run with --all.

If we run the Gradle project requesting that the helloViaAttribute Gradle task be executed, we’ll get the bridged Ant behavior through a pure Gradle interface.

Example 3-14. Sample run of the imported Ant project
$ gradle helloViaAttribute
:helloViaAttribute
[ant:echo] hello from Ant

Ant Target and Gradle Task Codependence

Ant targets can even participate as a dependsOn of Gradle builds, as shown in Example 3-16. Likewise, Ant targets can point to Gradle tasks as depends fields, as shown in Example 3-15. The only requirement of the Ant and Gradle integration is that the more capable tool, Gradle, be the point of execution (see Example 3-17). Gradle must drive the initiation of the build, say from from the command line, IDE, or GUI, but can be either the subordinate or dominate player in the actual XML and Gradle build script flow.

Example 3-15. Define an Ant target and have it depend on a Gradle task
<project>
  <target name="antStandAloneHello">
    <echo message="A standalone hello from an Ant target"/>
  </target>

  <target name="antHello" depends="beforeTheAntTask">
    <echo message="A dependent hello from the Ant target"/>
  </target>
</project>
Example 3-16. Define a task in Gradle that precedes the Ant target
ant.importBuild 'build.xml'

defaultTasks = ['antStandAloneHello', 'afterTheAntTask']

task beforeTheAntTask << {
    println "A Gradle task that precedes the Ant target"
}

task afterTheAntTask(dependsOn: "antHello") << {
    println "A Gradle task that precedes the Ant target"
}
Example 3-17. Driving the build from Gradle
$ gradle

:antStandAloneHello
[ant:echo] A standalone hello from an Ant target

:beforeTheAntTask
A Gradle task that precedes the Ant target

:antHello
[ant:echo] A dependent hello from the Ant target

:afterTheAntTask
A Gradle task that precedes the Ant target

BUILD SUCCESSFUL

Using AntBuilder

The power of using Ant with Gradle isn’t limited to just the prescribed approaches given thus far. Since the ant object is a full Groovy AntBuilder instance, existing paradigms and constructs from the Groovy language can be used here. Though comprehensive knowledge of Groovy is not a prerequisite, any increased understanding of Groovy has a direct benefit to the authoring of Gradle build files since Gradle supports the gamut of Groovy syntax.

For example, to list the contents of a directory using an Ant FileScanner and Ant FileSet, just use the ant instance with Groovy closure nesting for Ant subelement access, as shown in Example 3-18.

Example 3-18. Scan folders with Ant and iterate over the results in Gradle
task echoDirListViaAntBuilder() {
    description = 'Uses the built-in AntBuilder instance to echo and list files'
    //Docs: http://ant.apache.org/manual/Types/fileset.html

    //Echo the Gradle project name via the ant echo plugin
    ant.echo(message: project.name)
    ant.echo(path)
    ant.echo("${projectDir}/samples")

    //Gather list of files in a subdirectory
    ant.fileScanner{
        fileset(dir:"samples")
    }.each{
        //Print each file to screen with the CWD (projectDir) path removed.
        println it.toString() - "${projectDir}"
    }
}

The power of Groovy’s operators can also be leveraged inside AntBuilder calls, such as using the regular expression matcher inside an evaluation of all System properties in Example 3-19. The spread *, elvis ?:, and other Groovy operators can all be applied directly or to subcomponents of the ant instance for a high code-to-effect ratio. With almost no ceremony, the ==~ “firecracker” operator is used in Example 3-19 to query for regular expression matches of a string, charsToFind.

Example 3-19. Use the Groovy regular expression operator in an AntBuilder output
task echoSystemPropertiesWithRegEx() {
    description = "Uses Groovy's regex matching to find a match in System properties"

    def charsToFind = 'sun'

    println "SYSTEM PROPERTIES"
    System.properties.each{
        ant.echo("Does '${it}' contain '${charsToFind}': " + 
        (it ==~ ".*${charsToFind}.*"))
    }
}

In the ultimate combination, a progressive migration from Ant to Gradle can be greatly aided by the ability to bring Ant classpaths into the Gradle space. Example 3-20 demonstrates the establishment of two paths in the Ant realm, and then the consumption of those paths in the Gradle space, including using them as repositories in Example 3-21.

Example 3-20. Ant defining two paths
<project>
    <!-- Classpath created by Ant, then used by Gradle -->
    <path id="antPathToLibs1" location="antlibs"/>
    <path id="antPathToLibs2" location="antlibs"/>
</project>

On the build.gradle side of this example, the use of the * “spread” operator demonstrates how to println repositories*.name all the repositories name fields in a single statement instead of three.

Example 3-21. Use the Ant path references inside the Gradle build
ant.importBuild 'build.xml'

defaultTasks = ['gradleBuild']

////////////////////////////////////////////////////////////////////////
// Gradle paths retrieved from Ant and then built using a Gradle task
repositories {
    flatDir name: 'localRepository1', dirs: ant.references['antPathToLibs1']
    flatDir name: 'localRepository2', dirs: ant.references.antPathToLibs2
    flatDir name: 'localRepository3', dirs: "antlibs"
}

task gradleBuild << {
    //Set classpath to include the JAR
    println "The repositories bridged from Ant to Gradle are:"
    println repositories*.name

    println repositories.localRepository1

    println repositories['localRepository2']

    println "localRepository3 class = " + repositories.localRepository3.class
    println "localRepository3 name = " + repositories.localRepository3.name
    println "localRepository3 latest = " + repositories.localRepository3.latest
    println "localRepository3 ivyPatterns = " + repositories.localRepository3.ivyPatterns
    println "localRepository3 artifactPatterns = 
       " + repositories.localRepository3.artifactPatterns
    println "localRepository3 checkconsistency = 
       " + repositories.localRepository3.checkconsistency
    println "localRepository3 m2compatible = 
       " + repositories.localRepository3.m2compatible
    println "localRepository3 methods = 
       " + repositories.localRepository3.metaClass.methods*.name.sort().unique()
}

The converse use case is also possible and easy. Paths can be established in Gradle, as shown in Example 3-23, and then consumed in an Ant target in Example 3-22 that even depends on the antPathsInjectedByGradle Gradle task.

Example 3-22. Ant build file that consumes the Gradle paths
<project>
    <!-- Classpath created by Gradle, then used by Ant -->
    <path refid="gradlePathToLibs1"/>
    <path refid="gradlePathToLibs2"/>
    <path refid="gradlePathToLibs3"/>

    <!-- Echo one of the gradle paths injected into the Ant build -->
    <target name="antBuild" depends="antPathsInjectedByGradle">
      <property name="gradlePathToLibs1AsProperty" refid="gradlePathToLibs1"/>
      <echo message="gradlePathToLibs1 = ${gradlePathToLibs1AsProperty}"/>

      <property name="gradlePathToLibs2AsProperty" refid="gradlePathToLibs2"/>
      <echo message="gradlePathToLibs2 = ${gradlePathToLibs2AsProperty}"/>

      <property name="gradlePathToLibs3AsProperty" refid="gradlePathToLibs3"/>
      <echo message="gradlePathToLibs3 = ${gradlePathToLibs3AsProperty}"/>

    </target>
</project>
Example 3-23. The Gradle build file that injects the build paths into Ant
ant.importBuild 'build.xml'

defaultTasks = ['antBuild']

////////////////////////////////////////////////////////////////////////
// Ant paths injected by Gradle and then built using an Ant goal
task antPathsInjectedByGradle << {
    ant.path(id: 'gradlePathToLibs1', location: 'gradlelibs')
    // or
    ant.references.gradlePathToLibs2 = ant.path(location: 'gradlelibs')
    // or
    ant.references['gradlePathToLibs3'] = 
       ant.path(location: file('gradlelibs').absolutePath)

    println "This task injects Gradle paths into the AntBuilder"
}

antBuild.doLast {
    // No antBuild Task needs to be explicitly constructed
    // antBuild task is created by the build.xml import
    println "This Ant goal was converted to a Gradle task by the ant.importBuild call"
}

A Harmonic Duo

Over the course of this chapter, the examples have demonstrated the simplicity of integrating Ant with Gradle. The purpose of the cross-tool calls can either be that of a careful stepwise migration to Gradle, or the filling in for an absent Gradle plug-in with a well-established Ant equivalent. Gradle’s build tool compatibility represents an understanding of the real world of software engineering: migrations don’t always happen overnight and bridges to legacy technologies make Gradle an option for many more teams and projects.

..................Content has been hidden....................

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