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.

Example 4-1. The smallest possible Maven pom.xml
<!-- 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.

Example 4-2. The simplest Maven equivalent build.gradle file
apply plugin: 'java'

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.

Example 4-3. Listing of the build subdirectory from the Example 4-2 Gradle build project
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.

Example 4-4. Querying Gradle for the available tasks
$ 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 the test classes.

 Documentation tasks
 -------------------
 javadoc - Generates Javadoc API documentation
   for the main source code.

 Help tasks
 ----------
 dependencies - Displays the dependencies of
   root project 'maven-gradle-comparison-simple'.
 help - Displays a help 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.

Table 4-1. Maven-to-Gradle Coordinate Mappings and Defaults
Maven CoordinateGradle PropertyGradle Default

groupId

group

blank

artifactId

name or archivesBaseName

project’s directory name

version

version

unspecified

name

N/A

N/A

description

description

null

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.

Example 4-5. Gradle build that includes a version number for the output artifact
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.

Example 4-6. Directory listing of output from the version-specified Gradle build
$ 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.

Example 4-7. Gradle Properties of a “hello world” build
$ 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@5e20dcb7
type: 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.

Example 4-8. Java plug-in introduced properties
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.5
test: 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.

Example 4-9. Dependency on a library in the most concise syntax
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.

Example 4-10. Test compilation dependency on the latest 4.8.x version of JUnit
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.

Example 4-11. Displaying dependency assignment to scopes
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.

Example 4-12. Displaying dependency assignment to scopes
$ gradle dependencies

archives - Configuration for the default artifacts.
No dependencies

compile - Classpath for 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 - Configuration for 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 used for this Groovy project.
\--- mule:mule-extras-groovy:1.1.1 [default]

runtime - Classpath for 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 - Classpath for compiling the test 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 - Classpath for running the test 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 printlns the result, but could also perform manipulation or testing of the nodes, searching for a given pattern and conditionally acting on its inclusion.

Example 4-13. Print all dependencies
task printDeps(dependsOn: build) << {
    configurations*.dependencies.each { println it }
}

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

Example 4-14. Using Maven Central with Gradle
repositories {
    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.

Example 4-15. A custom URL
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.

Example 4-16. An Ivy repository
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.

Example 4-17. A filesystem-based custom repo
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.

Example 4-18. A flat filesystem repo
repositories {
    // A single directory added to a custom-named repositories grouping.
    flatDir name: 'localDiskRepo', dirs: 'lib'

    // And a more convenient syntax for multiple directories 
    added to the default grouping.
    flatDir dirs: ['graphiclibs', guilibs']
}

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.

Example 4-19. Enabling compilation of unit tests
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.

Example 4-20. Compiling unit tests
$ 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.

Example 4-21. Running unit tests
$ gradle test

: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.

Example 4-22. Unit test execution output
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.

Unit test report
Figure 4-1. Unit test report

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.

Example 4-23. Code example of multiple source directories
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).

Example 4-24. Default goals for Maven 3
<build>
    <defaultGoal>clean install</defaultGoal>
</build>

Gradle has nearly the same facility, but has included support for multiple default tasks from the get-go.

Example 4-25. Default tasks for Gradle
apply plugin: 'java'

//Alternate approach 1
//defaultTasks = ['clean', 'build']

//Alternate approach 2
//defaultTasks 'clean'

//Approach 3
defaultTasks 'clean', 'build'

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.

Example 4-26. Gradle build that specifies all the usual Maven coordinates
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."
Example 4-27. Running the pom.xml generation process
$ gradle install

:compileJava
:processResources
:classes
:jar
:install

BUILD SUCCESSFUL
Example 4-28. Build directory listing from the fully coordinate-specified Gradle build
$ tree

build
├── libs
│   └── sample01-barestbones-0.0.1-SNAPSHOT.jar
├── poms
        └── pom-default.xml
Example 4-29. The produced pom.xml file
<?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.

Example 4-30. Using Configuration to control the generated POM
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.

Example 4-31. Deploying the Maven-generated artifacts locally
$ 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.

Example 4-32. Gradle build file that permits file:// repository uploads
apply plugin: 'java'
apply plugin: 'maven'

group = 'com.gradleware.samples'

uploadArchives {
    repositories.mavenDeployer {
        repository(url: "file:///Users/mccm06/Documents/Temp/Scratch/mytemprepo/")
    }
}
Example 4-33. Invoking the artifact upload
$ 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.

Example 4-34. Specifying a webdav protocol for artifact uploads
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 cding into a directory containing a standalone or top level multimodule pom.xml file, then executing maven2gradle, as shown in Example 4-35.

Example 4-35. A small Maven POM to be converted to Gradle
<!-- 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.

Example 4-36. Converting a Maven POM to a Gradle build file
$ 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.gradle if 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.

Example 4-37. The result of the maven2gradle conversion
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.

Example 4-38. The M2Metadata plug-in’s driving build.gradle
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.

Example 4-39. The M2Metadata plug-in’s consumed pom.xml
<!-- 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.