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.
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.
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.
<project>
<target
name=
"helloViaAttribute"
>
<echo
message=
"hello from Ant"
/>
</target>
</project>
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.
<project>
<target
name=
"helloViaTag"
>
<echo>
hello from Ant</echo>
</target>
</project>
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.
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.
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
.
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.
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.
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.
<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.
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.
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.
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.
$ 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.
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.
<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>
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"
}
$ 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
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.
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
.
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.
<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.
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.
<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>
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"
}
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.