Chapter 4. Maven and Gradle
Up to this point, you’ve seen the very light footprint of Gradle. You might be convinced it is the way to go for new projects. However, only a very few of us are blessed with the wide-open choices afforded to a greenfield project. The majority of us have an existing build system in place and the mandate to maintain continuity of the build. We might even have downstream clients of our JAR, WAR, and EAR binary artifacts. Gradle would only be a logical successor to Maven if it offered a very thoughtful path to both migration and integration from the world’s largest open source build system. It certainly does. Let’s take a look.
Cue Graven?
First, let’s provide a bearing on the attitude of Gradle. It is not the next incremental step after Maven. It is not just another Domain Specific Language (DSL) on top of the existing build tooling with which we are already familiar. It is, in fact, a progression of selected ideas of our existing build tools. Gradle brings only the best ideas forward and leaves behind the ones that fell short of their intended mark. That is a generic set of claims. Let’s proceed to specifics.
Caution
It would be easy to think of Polyglot Maven as an equivalent to Gradle, but alas, the constraints that apply to Maven apply to Polyglot Maven as well, just sans the angle brackets of XML. Gradle aims to be something more powerful than its predecessor tools in the build space.
Gradle takes the convention over configuration and consistently named artifact concepts and brings them forward from the playbook of Maven. Gradle also implements a strong domain model that is easily programmatically accessible. Though often cited, it is debatable as to whether Maven actually has a strong model or merely a strong authoring language in the form of an XSD-compliant XML file.
Diving to a deeper level of precision, Gradle takes the
coordinates, including groupId
,
artifactId
, and version
, in producing new build artifacts. It
also offers a bridge, in the form of a dependency management definition
graph, to Maven and Ivy artifact repositories without demanding remote
repositories. This provides connectivity to open-source hosted binaries
on sites like Maven Central, Java.net, and
Clojars, just to name a few. But the flexibility of
Gradle also allows for the very valid use case in which dependency
binaries are stored alongside the source in version control, whether for
legal or technical reasons. In the example of the ultimate flexibility
allowed in Gradle, a default mapping is provided to the community-known
dependency (classpath) scopes of Maven, but Gradle users can establish
custom dependency scopes should the need arise.
That is a solid overview of what has been brought forward from the
forge of the past decade of build tool experiments and field testing.
But what was intentionally omitted? Gradle leaves behind the expensive
XML format. It is pure Groovy goodness. We’ll also leave behind the
strict, predefined lifecycle of Maven and the absence of a lifecycle
with Ant. Both are too far toward the ends of the spectrum of control
and chaos, respectively. Gradle will instead replace the strict Maven
lifecycle with task defaults set via common plug-ins such as java
. Even with convenient defaults, Gradle
offers a painless and lightweight way to extend the plug-in-supplied
task sequence with additional steps that fit your build’s unique
needs.
The Maven POM and Gradle Build
With the big picture established, let’s take a piecewise approach to comparing Maven and Gradle. We will start with a reminder of the appearance of a barest bones Maven POM.
<!-- The smallest possible Maven POM.xml -->
<project>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.gradleware.samples</groupId>
<artifactId>
sample01-barestbones</artifactId>
<version>
0.0.1-SNAPSHOT</version>
</project>
While often desirable to have the formal vectors of groupId
, artifactId
, and version
for a build, there are equally times
where it is overkill. No allowance is made for that in Maven. You must
provide all the fields. Adding a final bit of cruft, you also need the
modelVersion
field, just in case
Maven should ever decide to expand its vocabulary of tags (though it
hasn’t since the release of Maven 2.0 in 2004).
Earlier in the book, we showed you just how simple it was to get started with Gradle, so we will leave the simplicity of that first Gradle build file to the earlier example, The Hello World Build File. We will use, as our first comparison with Maven, a Gradle build file in Example 4-2 that defines a few attributes and can produce a JAR, a common final product of a Java build.
That one line Gradle build file in Example 4-2, when executed with a mere gradle build
from the command line, performed
the following actions:
Downloaded any declared dependencies (none) to ~/.gradle/cache
Compiled the code in src/main/java
Wrote the class files into build/classes/main
Attempted to compile and run any unit tests (none)
Wrote unit test results in XML format to build/test-results/
Wrote an HTML-formatted unit test report to build/reports/tests/
Generated a MANIFEST.MF in build/tmp/jar/MANIFEST.MF
Compressed the
.class
files along with the MANIFEST.MF into a JAR in build/libs/maven-gradle-comparison-simple.jar
The described actions taken by Gradle are made evident by
examining the files in the structure of the output
directory.
build
├── classes
│ └── main
│ └── Main.class
├── dependency-cache
├── libs
│ └── maven-gradle-comparison-simple.jar
├── reports
│ └── tests
│ ├── css3-pie-1.0beta3.htc
│ ├── index.html
│ ├── report.js
│ └── style.css
├── test
-results
└── tmp
└── jar
└── MANIFEST.MF
Maven Goals, Gradle Tasks
The aforementioned minimalistic Gradle build also offered many of
the tasks (nee Maven goals) we would have at our disposal with Maven.
Let’s examine the names of these tasks by asking Gradle what it can
offer us by invoking the tasks
task.
$
gradle tasks Build tasks ----------- assemble - Assembles all Jar, War, Zip, and Tar archives. build - Assembles and tests this project. buildDependents - Assembles and tests this project and all projects that depend on it. buildNeeded - Assembles and tests this project and all projects it depends on. classes - Assembles the main classes. clean - Deletes the build directory. jar - Assembles a jar archive containing the main classes. testClasses - Assembles thetest
classes. Documentation tasks ------------------- javadoc - Generates Javadoc API documentationfor
the mainsource
code. Help tasks ---------- dependencies - Displays the dependencies of root project'maven-gradle-comparison-simple'
.help
- Displays ahelp
message projects - Displays the subprojects of root project'maven-gradle-comparison-simple'
. properties - Displays the properties of root project'maven-gradle-comparison-simple'
. tasks - Displays the tasks in root project'maven-gradle-comparison-simple'
. Verification tasks ------------------ check - Runs all checks.test
- Runs the unit tests.
We see that tasks to clean, build, test, and JAR the project’s
code are included via the simple inclusion of the java
plug-in.
The Standard Maven Coordinates, Gradle Properties
That comparison of a one line build.gradle
to the most minimal pom.xml
was valid, but not completely
controlled in the metadata values for groupId
, artifactId
, and version
. In Gradle, the groupId
is known just as group
, the artifactId
is known as name
, and the version
is identically version
. Formally, each of these kind of
fields is known as a property in Gradle since we
are saving state in POJO objects under the hood.
Maven Coordinate | Gradle Property | Gradle Default |
| | |
|
| project’s directory name |
| | |
| | N/A |
| | |
Growing our Gradle example to a more robust Maven POM equivalent,
we’ll add the version field. This is common in
Gradle builds, even in the absence of the other
group and artifact
coordinates. The version property is defined in the
Gradle
DSL reference and is present in every type of build, even
without the java plug-in. The archivesBaseName
value is appended to the end
of the produced JAR.
apply
plugin:
'
java
'
version
=
'
0.0
.
1
-
SNAPSHOT
'
When we run this refined build, take note that the output JAR in
the libs directory is now named maven-gradle-comparison-withattrs-0.0.1-SNAPSHOT.jar
,
whereas it was previously named the less precise, maven-gradle-comparison-simple.jar
in Example 4-2. Recall that the artifact name defaults to the
project’s directory name if no more precisely desired value is supplied
as shown in Table 4-1.
$
tree ├── classes │ └── main │ └── Main.class ├── dependency-cache ├── libs │ └── maven-gradle-comparison-withattrs-0.0.1-SNAPSHOT.jar ├── reports │ └── tests │ ├── css3-pie-1.0beta3.htc │ ├── index.html │ ├── report.js │ └── style.css ├──test
-results └── tmp └── jar └── MANIFEST.MF
Next, using the knowledge gained in Table 4-1, we can control all values of the
coordinates that would typically be set in a Maven-participating
project. Note that the name
field
presented by the very foundation of Gradle is only mutable in the settings.gradle
file, and if not supplied in
settings.gradle, the archivesBaseName
property is assigned a value
instead. The archivesBaseName property is brought
into existence by the java
plug-in.
Note
Gradle 1.0 aims to simplify the field name mapping, doing away with archivesBaseName, and possibly providing a top level closure to configure the project’s Maven-equivalent coordinates.
Up to this point, we’ve only used the java
plug-in. Though many of these features
look Maven-like in their naming and function, these are behaviors
desired by nearly any Java project, even standalone single-JAR Java
applications. The consistent location of Gradle’s source directories
(src/main/java) and the specification of the
artifact’s group
, name
, and version
metadata is useful, even without the
aim of uploading an artifact to a remote repository.
The most visible Gradle win thus far is having the properties at the top level of the build file with slightly simplified property names rather than the XML nesting of Maven. Keep reading. The wins are about to get more significant.
More Gradle Properties
Having introduced one plug-in thus far, you might be wondering
what range of properties are
available to control. Gradle offers an easy always-available task named
properties
to list the fields and
values of the current build. Let’s echo all the Gradle properties to the
console with the -r
or --properties
flag.
$
gradle properties additionalProperties:{}
all:[
task':helloWorld'
]
allprojects:[
root project'minimal-build-file'
]
ant: org.gradle.api.internal.project.DefaultAntBuilder@5057f57f antBuilderFactory: org.gradle.api.internal.project.DefaultAntBuilderFactory@114562c5 artifacts: org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler@23bf8cd5 asDynamicObject: org.gradle.api.internal.DynamicObjectHelper@30c26c8f asMap:{
helloWorld
=
task':helloWorld'
}
buildDir: /minimal-build-file/build buildDirName: build buildFile: /minimal-build-file/build.gradle buildScriptSource: org.gradle.groovy.scripts.UriScriptSource@111edceb buildscript: org.gradle.api.internal.initialization.DefaultScriptHandler@7fb2380e childProjects:{}
class: class org.gradle.api.internal.project.DefaultProject_Decorated classGenerator: org.gradle.api.internal.AsmBackedClassGenerator@709a1411 configurations: org.gradle.api.internal.artifacts.configurations. DefaultConfigurationContainer_Decorated@69d4eeb5 convention: org.gradle.api.internal.plugins.DefaultConvention@71cbd4f7 conventionMapping: org.gradle.api.internal.ConventionAwareHelper@1aa632be defaultTasks:[]
dependencies: org.gradle.api.internal.artifacts.dsl.dependencies. DefaultDependencyHandler@d44752d dependsOnProjects:[]
depth: 0 description: null displayName: task container fileResolver: org.gradle.api.internal.file.BaseDirConverter@56d99277 gradle: build'minimal-build-file'
group: helloWorld: task':helloWorld'
implicitTasks: org.gradle.api.internal.tasks.DefaultTaskContainer_Decorated@384ab40a inheritedScope: org.gradle.api.internal. DynamicObjectHelper$InheritedDynamicObject
@388a2006 logger: org.gradle.api.logging.Logging$LoggerImpl
@521c5cd7 logging: org.gradle.logging.internal.DefaultLoggingManager@3821b42f metaClass: org.codehaus.groovy.runtime.HandleMetaClass@5b76de14[
groovy.lang.MetaClassImpl@5b76de14[
class org.gradle.api.internal.project.DefaultProject_Decorated]]
module: org.gradle.api.internal.artifacts.DefaultModule@59a51312 name: minimal-build-file parent: null parentIdentifier: null path: : plugins: org.gradle.api.internal.plugins.DefaultProjectsPluginContainer@5608a6fc project: root project'minimal-build-file'
projectDir: /minimal-build-file projectEvaluationBroadcaster: ProjectEvaluationListener broadcast projectEvaluator: org.gradle.configuration.DefaultProjectEvaluator@6212f195 projectRegistry: org.gradle.api.internal.project.DefaultProjectRegistry@5e1558dc properties:{
...}
repositories: org.gradle.api.internal.artifacts.dsl. DefaultRepositoryHandler_Decorated@ba3bc8c repositoryHandlerFactory: org.gradle.api.internal.artifacts.dsl. SharedConventionRepositoryHandlerFactory@41ed54a0 rootDir: /minimal-build-file rootProject: root project'minimal-build-file'
rules:[]
services: ProjectInternalServiceRegistry standardOutputCapture: org.gradle.logging.internal.DefaultLoggingManager@3821b42f state: org.gradle.api.internal.project.ProjectStateInternal@594560cf status: release subprojects:[]
tasks: org.gradle.api.internal.tasks.DefaultTaskContainer_Decorated@5e20dcb7type
: interface org.gradle.api.Task typeDisplayName: task version: unspecified
The properties listed here have a wide range of
rare to common in their
frequency of being configured. Many have sensible defaults, such as
status
being set to release
, and are thus only occasionally tuned
to an alternative value.
Adding the java
plug-in as per
the original sample build file
introduces the additional properties shown in Example 4-8.
apiDocTitle: maven-gradle-comparison-simple API archivesBaseName: maven-gradle-comparison-simple assemble: task':assemble'
build: task':build'
buildDependents: task':buildDependents'
buildNeeded: task':buildNeeded'
buildTasks:[
build]
check: task':check'
classes: task':classes'
clean: task':clean'
compileJava: task':compileJava'
compileTestJava: task':compileTestJava'
dependencyCacheDir: /maven-gradle-comparison-simple/build/dependency-cache dependencyCacheDirName: dependency-cache distsDir: /maven-gradle-comparison-simple/build/distributions distsDirName: distributions docsDir: /maven-gradle-comparison-simple/build/docs docsDirName: docs jar: task':jar'
javadoc: task':javadoc'
libsDir: /maven-gradle-comparison-simple/build/libs libsDirName: libs manifest: org.gradle.api.java.archives.internal.DefaultManifest@7a66998f metaInf:[]
processResources: task':processResources'
5 processTestResources: task':processTestResources'
rebuildTasks:[
clean, build]
reportsDir: /maven-gradle-comparison-simple/build/reports reportsDirName: reports rules:[
Rule: Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration., Rule: Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration., Rule: Pattern: clean<TaskName>: Cleans the output files of a task.]
runtimeClasspath: file collection sourceCompatibility: 1.5 sourceSets: org.gradle.api.internal.tasks.DefaultSourceSetContainer_Decorated@2850a492 targetCompatibility: 1.5test
: task':test'
testClasses: task':testClasses'
testReportDir: /maven-gradle-comparison-simple/build/reports/tests testReportDirName: tests testResultsDir: /maven-gradle-comparison-simple/build/test-results testResultsDirName:test
-results
Dependencies
It is rare to see a project stand alone, independent of any third party or in-house library. It almost goes without saying that a Java project will use some open source components such as log4j or commons-collections. Gradle offers both an Ivy and Maven dependency and repository compatibility layer, allowing for very simplistic and flexible specification of dependencies and repositories.
A library is declared as a desired external
dependency by a one-line listing in a closure named dependencies
. The most concise form of
expressing this is a colon-separated list of fields in a single
string.
dependencies
{
compile
'
commons
-
beanutils:
commons
-
beanutils:
1.8
.
3
'
}
A more verbose form preferred by some, but accomplishing the same thing, is a field-by-field list.
dependencies
{
testCompile
group:
'
junit
'
,
name:
'
junit
'
,
version:
'
4.8
.+
'
compile
group:
'
commons
-
beanutils
'
,
name:
'
commons
-
beanutils
'
,
version:
'
1.8
.
3
'
}
What are these testCompile
and
compile
phrases we see in Example 4-10? They are the scopes for which the declared
external dependencies are to apply.
With the java
plug-in
introduced, there are six available scopes (or configurations
to use the precise
Gradle term) to which dependencies can be assigned. Those six are as
follows:
compile
default
testCompile
testRuntime
archives
runtime
However, unlike the fixed scopes of Maven, we have more
flexibility with Gradle, to the objective of providing greater build
flexibility. New scopes, such as groovy
, can be introduced by a plug-in. Here
we see an additional library being added to the groovy
scope.
apply
plugin:
'
java
'
apply
plugin:
'
groovy
'
group
=
'
com
.
gradleware
.
samples
'
version
=
'
0.0
.
1
-
SNAPSHOT
'
repositories
{
mavenCentral
()
}
dependencies
{
testCompile
'
junit:junit:
4.8
.
2
'
compile
'
commons
-
beanutils:
commons
-
beanutils:
1.8
.
3
'
//groovy 'mule:mule-extras-groovy:1.+'
// ^ The "+" flexible definition of a version
// requires Internet access
groovy
'
mule:
mule
-
extras
-
groovy:
1.1
.
1
'
}
The output of asking Gradle to list the dependencies shows that
indeed, the new library lives beneath the groovy
scope, but also scopes that logically
need what was made available to groovy
. That includes compile
, default
, runtime
, testCompile
, and compile
.
$
gradle dependencies archives - Configurationfor
the default artifacts. No dependencies compile - Classpathfor
compiling the sources. +--- mule:mule-extras-groovy:1.1.1[
default]
\-
-- commons-beanutils:commons-beanutils:1.8.3[
default]
\-
-- commons-logging:commons-logging:1.1.1[
compile,master,runtime]
default - Configurationfor
the default artifacts and their dependencies. +--- mule:mule-extras-groovy:1.1.1[
default]
\-
-- commons-beanutils:commons-beanutils:1.8.3[
default]
\-
-- commons-logging:commons-logging:1.1.1[
compile,master,runtime]
groovy - The groovy libraries to be usedfor
this Groovy project.\-
-- mule:mule-extras-groovy:1.1.1[
default]
runtime - Classpathfor
running the compiled sources. +--- mule:mule-extras-groovy:1.1.1[
default]
\-
-- commons-beanutils:commons-beanutils:1.8.3[
default]
\-
-- commons-logging:commons-logging:1.1.1[
compile,master,runtime]
testCompile - Classpathfor
compiling thetest
sources. +--- mule:mule-extras-groovy:1.1.1[
default]
+--- commons-beanutils:commons-beanutils:1.8.3[
default]
|\-
-- commons-logging:commons-logging:1.1.1[
compile,master,runtime]
\-
-- junit:junit:4.8.2[
default]
testRuntime - Classpathfor
running thetest
sources. +--- mule:mule-extras-groovy:1.1.1[
default]
+--- commons-beanutils:commons-beanutils:1.8.3[
default]
|\-
-- commons-logging:commons-logging:1.1.1[
compile,master,runtime]
\-
-- junit:junit:4.8.2[
default]
Gradle offers programmatic access to all elements of the
model that represents the ultimate build plan. A
simple showcase of this access is the printing of the dependencies to
screen in Example 4-13. Iterating through all
dependencies can take simultaneous advantage of the dependencies
object and the Groovy each
method that accepts a closure. The
contents of the closure println
s the
result, but could also perform manipulation or testing of the nodes,
searching for a given pattern and conditionally acting on its
inclusion.
Repositories
The concept of retrieving libraries, source JARs, dependency metadata, and JavaDoc archives is an expected foundation of any modern build tool. Maven has such capabilities at its very core. Ant, though the older of the JVM build tools, has been supplemented by the Ivy subproject to accomplish exactly this. Ivy offers both a namesake format as well as compatibility with Maven repositories. Gradle provides a bridge implementation to equally consume Ivy- or Maven-formatted repositories.
Gradle goes far beyond just offering an API bridge to Ivy and Maven. It provides a Gradle-flavored DSL for both aforementioned dependency tools that make working with repositories quite simple.
First, a demonstration of adding the mother of all repositories,
:16:[Maven Central] in Example 4-14. The
ever-important Maven Central
has its
own predefined method mavenCentral()
.
Since many organizations will need to additionally, or as a
replacement, depend on a company-internal repository, that is equally
easy with an addition of a URL to the mavenRepo
configuration element of repositories
in Example 4-15.
repositories
{
mavenRepo
(
urls:
'
http:
//repo.gradle.org/gradle/libs-releases-local')
}
If your project needs a true Ivy repository with resolution
customization, then Gradle is up to the task with a convenient ivy
closure syntax, as shown in Example 4-16.
repositories
{
ivy
{
name
=
'
ivyRepo
'
artifactPattern
"http://repo.gradleware.org/[organisation]/[module]/
[revision]/[artifact]-[revision].[ext]"
}
}
What if your dependencies aren’t yet in a standard repository, but
rather are stored in a custom
folder alongside your project? Gradle handles that with ease via
the add()
method and a FileSystemResolver()
, as shown in Example 4-17.
repositories
{
add
(
new
FileSystemResolver
())
{
name
=
"repo"
addArtifactPattern
(
"$rootDir/repo/[organization]/[module]-[revision].[ext]"
)
addIvyPattern
(
"$rootDir/repo/[organization]/ivy-[module]-[revision].xml"
)
checkmodified
=
true
}
}
Lastly, what if you haven’t even begun your versioning and
standardization of the names of your project’s artifacts? What if they
are just a series of JARs in a single flat subdirectory? It should be no
surprise by now that Gradle also has a convenient syntax for this
requirement, called flat
, as shown in
Example 4-18.
Unit Testing
Unit testing is enabled as soon as the java
plug-in is included in a Gradle build
script. There’s no additional effort to enable the testCompile
dependency scope and testClasses
task. However, the selection of a
particular testing framework library still needs to be stated via the
dependencies
section of the
build.gradle file. This facilitates compiled tests being able to
reference JUnit or TestNG
annotations or base classes.
apply
plugin:
'
java
'
apply
plugin:
'
maven
'
group
=
'
com
.
gradleware
.
samples
'
version
=
'
0.0
.
1
-
SNAPSHOT
'
repositories
{
mavenCentral
()
}
dependencies
{
testCompile
group:
'
junit
'
,
name:
'
junit
'
,
version:
'
4.8
.+
'
}
The unit tests can be compiled via the testClasses
task.
$
gradle testClasses
:compileJava
:processResources
:classes
:compileTestJava
:processTestResources
:testClasses
BUILD SUCCESSFUL
However, it is far more common to both compile and execute the
tests in one seamless step. Simply invoke the test
task to do this.
$
gradletest
:compileJava :processResources :classes :compileTestJava :processTestResources :testClasses :test BUILD SUCCESSFUL
The raw reports from the execution of the unit tests are written to the build/test-results directory and the distilled HTML output is written to the build/reports/tests directory.
build
├── reports
| └── tests
│ ├── TestMain.html
│ ├── css3-pie-1.0beta3.htc
│ ├── default-package.html
│ ├── index.html
│ ├── report.js
│ └── style.css
└── test
-results
└── TEST-TestMain.xml
A sample HTML report from this unit test is handsomely formatted as shown in Figure 4-1.
Multiple Source Directories
In Maven, it takes hackery and the help of the build-helper-maven-plug-in to add more than one source directory. This is one of those cases where constraints are helpful, right up to the point that you need to break them. Then, Maven causes you great pain to circumvent the limitation for your possibly very valid engineering reason. This is ultimately because the internal model of Maven is limited to just one source folder.
On the other hand, Gradle offers you the convention of a single
source folder, named src/main/java, but lets you
add more directories very easily, should your build’s design call for
it. A sourceSets
entry named main
is registered by default with the
java
plug-in and since that is a
collection, we can simply modify it to give it an additional directory
to scan during compilation, as shown in Example 4-23.
apply
plugin:
'
java
'
sourceSets
.
main
.
java
.
srcDirs
=
[
"src/main/java"
,
"srcAdditional/main/java"
]
// This add (while maintaining the default src/main/java)
// can also be accomplished with a call:
sourceSets
.
main
.
java
.
srcDirs
'
srcAdditionalTwo
/
main
/
java
'
Default Tasks
Default tasks are easy in Gradle. They are more than just a convenience for the build user, but rather a means to communicate which task was intended by the author to be the primary function of this build.
There was no facility for more than one default goal in Maven 1 or Maven 2; however, this was fixed in Maven 3, closing the associated defect after five long years.
To implement a default goal in Maven, include a single <defaultGoal>
tag-wrapped task (Maven 1,
2) or space-separated list of tasks (Maven 3).
Gradle has nearly the same facility, but has included support for multiple default tasks from the get-go.
The Maven Plug-in
It isn’t until the point at which we need to publish a Maven pom.xml file for our project to participate in Maven-compatible dependency resolution that we need to involve Gradle’s Maven plug-in.
The archivesBaseName
property
introduced by the java
plug-in can be
consumed by the newly introduced maven
plug-in in controlling the value written
out as the artifactId
in the
generated pom.xml file. If left empty, the
project’s directory name is used, as per the defaults table.
A Gradle build file need only specify all the Maven coordinates if
it wants precise control over each value. The full set of coordinates
will be generated into the pom.xml
file no matter what, simply with default values if not explicitly
set.
apply
plugin:
'
java
'
apply
plugin:
'
maven
'
group
=
'
com
.
gradleware
.
samples
'
// archivesBaseName is, via the java plugin, then the Maven plugin,
// ultimately setting artifactId for the Maven POM generation
// project.name is used by default, but is immutable
archivesBaseName
=
'
sample01
-
barestbones
'
version
=
'
0.0
.
1
-
SNAPSHOT
'
description
=
"A sample project that uses the Maven plug-in and defines many attributes."
$
gradle install
:compileJava
:processResources
:classes
:jar
:install
BUILD SUCCESSFUL
$
tree
build
├── libs
│ └── sample01-barestbones-0.0.1-SNAPSHOT.jar
├── poms
└── pom-default.xml
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns=
"http://maven.apache.org/POM/4.0.0"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.gradleware.samples</groupId>
<artifactId>
sample01-barestbones</artifactId>
<version>
0.0.1-SNAPSHOT</version>
</project>
Alternatively, a configuration closure can be used to set the same information for the benefit of the generated pom.xml file.
apply
plugin:
'
java
'
apply
plugin:
'
maven
'
defaultTasks
=
[
'
install
'
]
configure
(
install
.
repositories
.
mavenInstaller
)
{
pom
.
project
{
groupId
'
com
.
gradleware
.
samples
'
artifactId
'
sample02
-
barestbones
'
version
'
0.0
.
1
-
SNAPSHOT
'
description
=
'
A
sample
project
that
uses
the
Maven
plug
-
in
and
defines
many
attributes
.
'
}
}
In either the approach of setting Gradle properties or authoring a configuration closure, we’ve simplified the setting of the standard Maven coordinates to their bare essence. Both techniques equally shape the output of the JAR file’s name and the generated pom.xml file contents.
Installing to the Local Maven Repository (Cache)
The Maven plug-in enabled Gradle to not only produce Maven-compatible pom.xml and JAR artifacts, but to install those to the local Maven repository. The standard ~/.m2/repository location is used as the destination, just as it would be from Maven.
$
gradle install
:compileJava
:processResources
:classes
:jar
:install
BUILD SUCCESSFUL
This install process, equivalent to mvn
install
, is guaranteed to be compatible with Maven because it
actually uses the Maven Ant
Tasks produced by the Maven team and hosted at Apache. The
~/.m2/settings.xml is consulted, as it would be
with Maven, for an alternate location of the default local
repository.
Publishing to a Maven Repository
After a successful Gradle build, a binary artifact such as a JAR
should be deployed to a binary repository manager such as Artifactory or Nexus for team-wide
consumption. Gradle again supports this as simply as its Maven
predecessor would with a little configuration of the URL and transport,
and then an equivalent to mvn
deploy
.
As seen in the previous section, the maven
plug-in for Gradle offers
pom.xml and metadata file generation in addition to
local installation to the ~/.m2/repository
directory. This plug-in also offers an uploadArchives
task that is the equivalent to
mvn deploy
.
apply
plugin:
'
java
'
apply
plugin:
'
maven
'
group
=
'
com
.
gradleware
.
samples
'
uploadArchives
{
repositories
.
mavenDeployer
{
repository
(
url:
"file:///Users/mccm06/Documents/Temp/Scratch/mytemprepo/"
)
}
}
$
gradle uploadArchives
:clean
:compileJava
:processResources
:classes
:jar
:uploadArchives
Uploading:
com/gradleware/samples/maven-uploadlocal-unspecified.jar
to repository remote at
file:///Users/mccm06/Documents/Temp/Scratch/mytemprepo/
Transferring 1K from remote
Uploaded 1K
BUILD SUCCESSFUL
Protocols other than file://
require just slightly more
configuration in that the protocol and wagon JAR that supplies
that connectivity must be explicitly called out as shown for webdav
in Example 4-34.
apply
plugin:
'
java
'
apply
plugin:
'
maven
'
group
=
'
com
.
gradleware
.
samples
'
configurations
{
deployerJars
}
repositories
{
mavenCentral
()
}
dependencies
{
deployerJars
"org.apache.maven.wagon:wagon-webdav-jackrabbit:1.0-beta-7"
}
uploadArchives
{
repositories
.
mavenDeployer
{
configuration
=
configurations
.
deployerJars
repository
(
url:
"http://localhost:8081/nexus/content/repositories/matthew"
)
}
}
Maven2Gradle Build Script Converter
There is indeed such a tool called maven2gradle
crafted by Baruch Sadogursky of BMC
Software. It is still under active development on its path to
going 1.0, and though young, is useful even in its current form.
maven2gradle
views the effective-pom
flat view of a project’s POM as
its bridge to export to a Gradle build file. Based on the utility’s
knowledge of the constrained Maven vocabulary, it converts those
instructions to Gradle equivalents.
Note
The maven2gradle conversion tool is rather sensitive to both the
Gradle and Maven tooling versions on the $PATH
. Check the project’s README for
details on tooling version requirements.
The execution of the tool is as simple as cd
ing into a directory containing a standalone
or top level multimodule pom.xml
file, then executing maven2gradle
, as
shown in Example 4-35.
<!-- The smallest possible Maven POM.xml -->
<project>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.gradleware.samples</groupId>
<artifactId>
sample01-barestbones</artifactId>
<version>
0.0.1-SNAPSHOT</version>
</project>
The tool outputs lightweight progress information as it works through the POM, settings, repositories, dependencies, and WAR natures, as shown in Example 4-36.
$
maven2gradle Wait, obtaining effective pom... Done. Wait, obtaining effective settings... Done. This is single module project. Configuring Maven repositories... Done. Configuring Dependencies... Done. Adding tests packaging...Generating settings.gradleif
needed... Done. Generating main build.gradle... Done.
At the conclusion of the tool’s execution, a build.gradle
file and, as needed, a settings.
gradle
file are output to the current working
directory. A sample output is shown in Example 4-37.
apply
plugin:
'
java
'
apply
plugin:
'
maven
'
group
=
'
com
.
gradleware
.
samples
'
version
=
'
0.0
.
1
-
SNAPSHOT
'
description
=
""""""
sourceCompatibility
=
1.5
targetCompatibility
=
1.5
repositories
{
mavenRepo
urls:
[
"http://repo1.maven.org/maven2"
]
}
Taking the file exactly as output by maven2gradle
would be short of the Gradle
build’s full potential as it would miss out on some verbosity reductions
that Gradle has to offer. The resultant
build.gradle file will function correctly, but the
idea with Gradle is not just to have a functional equivalent in the
Groovy language, but a better build than what we
started with in Maven. The output build.gradle
can benefit from hand-tuning to
leverage more Groovy idioms and Gradle plug-ins. After all, Gradle’s
aims are to provide you with great possibilities: a more expressive
vocabulary and a more concise build file due to the ability to conform
to your needs rather than the other way around.
Maven POM Import
A second, and quite different question, is whether Gradle can
directly import a Maven POM, capturing the existing behavior and
converting it to equivalent Gradle behavior at runtime. The short-term
answer is “it’s a work in progress” named Gradle-M2Metadata-Plug-in
. The limited
vocabulary of Maven makes this feasible, but mirroring the
idiosyncrasies of Maven’s behavior for each combination of tags is a
steep challenge. Today, the source compatibility levels, dependencies,
coordinates, and packaging can be brought across via a run-time import
of a Maven pom.xml
file’s
contents.
To use this plug-in, add an apply
and a configuration of the buildScript
task that indicates where the
plug-in’s JAR can be retrieved, as shown in Example 4-38.
apply
plugin:
'
maven
-
metadata
'
//Repo to retrieve the maven-metadata plugin
buildscript
{
repositories
{
mavenRepo
urls:
[
"http://repo.jfrog.org/artifactory/plugins-snapshots"
]
}
dependencies
{
classpath
"org.gradle.plugins:gradle-m2metadata-plugin:1.0-SNAPSHOT"
}
}
On the Maven side, the pom.xml
is as traditional as a POM can be. It defines coordinates and two
dependencies which are used by the source code. This is shown in Example 4-39.
<!--
The
smallest
possible
Maven
POM
.
xml
-->
<
project
>
<
modelVersion
>
4.0
.
0
</
modelVersion
>
<
groupId
>
com
.
gradleware
.
samples
</
groupId
>
<
artifactId
>
sample01
-
m2metadata
</
artifactId
>
<
version
>
0.0
.
2
-
SNAPSHOT
</
version
>
<
dependencies
>
<
dependency
>
<
groupId
>
log4j
</
groupId
>
<
artifactId
>
log4j
</
artifactId
>
<
version
>
1.2
.
16
</
version
>
</
dependency
>
<
dependency
>
<
groupId
>
junit
</
groupId
>
<
artifactId
>
junit
</
artifactId
>
<
version
>
4.8
.
2
</
version
>
<
scope
>
test
</
scope
>
</
dependency
>
</
dependencies
>
</
project
>
Conclusion
Gradle, instead of starting anew from the things that we’ve learned and adopted in the Maven build space, promotes that it is okay to use something that already exists on the way to a better build. Gradle loves scaffolding. Gradle likes convention over configuration. Gradle likes a strong domain model. There’s no shame in those similarities to its predecessors. The important twist is that Gradle is willing to be flexible where predecessors would be rigid. This newfound flexibility exists while still facilitating the partial consumption of existing Maven build files for a gradual transition to a full Gradle build. This is certainly the definition of a win-win situation.
Get Building and Testing with Gradle now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.