What tool do you use to build your project and why? In general there are two main answers to this questions in the Java world:
I use Maven. Thanks to fantastic plugins I need 3 lines to achieve effects, that would require a lot of XML pseudo-coding, if I used Ant. Sometimes Maven is not flexible enough, but then I use AntRun plugin and that solves my problems.
-- Maven user
I use Ant, and I can do everything, because Ant offers me the flexibility I need. Maven gained popularity by dependency management, but I have Apache Ivy, which can do even more.
-- Ant user
It is not my intention to repeat the arguments, that Ant and Maven users have used so many times in blogs, articles and discussion forums. First of all, because there is nothing new to say in favor of Ant or Maven. Secondly (and this reason is more important to me) because the new competitor - Gradle - has joined the tournament. And Gradle is so powerful that will redefine the Java build tools scene - moving all Ant/Maven antagonism to the history of computer flame wars.
Before presenting Gradle, let us have a closer look at some of the shortcomings of both Ant and Maven:
it is hard to implement any algorithm in the build file; even simple if
or for
constructs are hard to achieve, and very unnatural to express,
it is hard to do tasks (even presumably simple ones), that were not foreseen by Ant/Maven developers, or are not available in form of tasks/plugins,
you need to know these tools very well to understand a complex build,
"build by convention" is not supported (Ant), or ties your hands because the configuration is hard (Maven),
the support for multi-module builds is limited, and rarely fits your project needs,
the boilerplate of XML is annoying, and makes build files much bigger and harder to read, than they should be.
To fully benefit from this article, you should install Gradle. Consult the installation instructions on Gradle's website. You will find all source code attached, so you can easily follow the examples. All the code presented in the article is compatible with Gradle 0.8 (the latest at the time of writing).
The aim of this article is to introduce Gradle. Gradle has comprehensive documentation and many examples that will show you all its secrets. This article is not meant to replace these sources of information but rather to whet your appetite.
I'd like to present Gradle by showing some real-life examples of its usage. But before this, let me underline few main features of Gradle, so you know what to expect.
Gradle is a very flexible general purpose build tool, especially suitable for Java developers. The build scripts are written in Groovy based DSL, making it very easy to express algorithms. Gradle allows to create dependencies between tasks, and follows the "convention over configuration" paradigm (but the configuration is always possible and easy to achieve). It goes beyond what today's build tools can achieve, at the same time sticking to good “standards”, that are well established among Java developers, thus lowering the cost of switching.
Gradle is still under rapid development (but at the same time ready for production use) and gains a lot of interest from the Java community. The list of already implemented features is impressive (including very advanced support for multi-module projects), and so are the features planned for the oncoming releases (e.g. DSLs in other languages than Groovy - Scala, JRuby, etc.).
I promised to present Gradle by examples, so let us create some code. First, we will write a very simple web application. Then we will enhance it to present many features of Gradle - especially the dynamic nature of build scripts and Gradle's support for multi-module builds.
The project layout looks like this:
myWebApp `-- src `-- main |-- java | `-- org | `-- gradle | `-- sample | `-- Greeter.java `-- webapp `-- index.jsp
The project is painfully simple. It has only one class (Greeter.java
) which depends on a 3rd party library (Commons Lang from Apache Commons), and a single JSP page (index.jsp) that makes use of it.
Now we will add a Gradle build script - by default named build.gradle
- to myWebApp project. It should perform the following tasks:
download the required dependencies,
compile Java classes,
create WAR file.
So here it goes - your first Gradle build script.
usePlugin 'war' version = 0.1 repositories { mavenCentral() } dependencies { compile "commons-lang:commons-lang:2.4" }
If you place this script in myWebApp main folder, and run it by executing gradle war a resulting myWebApp-0.1.war file will be created in build/libs folder. Copy it to any web container (e.g. Tomcat) and enjoy by visiting http://YourHostName/myWebApp-0.1.
With Gradle you can easily gather many important information regarding your build. Execute gradle --tasks to learn about tasks (written by you and those offered by plugins). Execute gradle --properties to learn about properties and gradle --dependencies to learn about dependencies (try also gradle -? to learn about all command line switches).
The first things that you probably spotted about this file are:
Gradle build scripts can be very concise,
it uses DSL instead of XML,
it can "talk" to Maven repositories.
Yes, it is all true, but this is only a beginning. Now, let us take a closer look at Gradle by examining this build script.
First thing you probably noticed, is that the build.gradle script is very concise. This is thanks to the two facts:
Would you like to use War plugin in your build ? Would you like to set version to 0.1 ? Say it straight.
usePlugin 'war' version = 0.1
Would you like to print a message if some condition occurred ? Say it straight.
if (someCondition) { println "some message" }
If you think about this two simple examples, you won't notice anything unusual here. "That's the normal way the things should work". Yes, true. But unfortunately, it is very hard to express such simple things using Ant or Maven build files. Gradle uses mix of Groovy and DSL that gives you definitely more expressive power than XML based solutions.
Gradle uses DSL which is much more expressive than XML (it is also easier to read and understand),
Gradle follows the "convention over configuration" paradigm, allowing to cut the length of build file to the minimum, if default values (directory layout, file naming conventions) are used.
For example, Gradle follows the well-established convention of project layout (set by Maven), and by default expects to find the source code and resources in the following folders:[1]
`-- src |-- main | |-- groovy // Groovy source code | |-- java // Java source code | |-- resources // application resources | `-- webapp // web application sources `-- test |-- groovy // tests written in Groovy |-- java // tests written in Java `-- resources // test resources
Another convention makes Gradle produce WAR file using projectName-version.war pattern (project folder is used as projectName
if not specified otherwise). Also by default, Java 1.5 source compatibility is assumed.
All of these conventions can be easily configured if required. For example if created artifacts (JAR and WAR files) should be created in build/artifacts instead of default build/libs, it is enough to set value of libsDirName variable to "artifacts":
libsDirName = "artifacts"
To change the name of the generated WAR or JAR file (by default it uses name of the project) set archivesBaseName
property:
archivesBaseName = "i-dont-like-the-default-name"
Because our simple myWebApp follows the default layout and conventions, there is no need to set any of these values. This way, the build scripts is very short.
Gradle is based on plugins. Each plugin can enhance the core functionality offered by Gradle in three ways. For example, usage of Java plugin results in:
almost 20 new tasks available (e.g. clean
, compileJava
, test
, jar
),
dependencies between these tasks, which forces the reasonable execution order (e.g. test
task depends on compileJava
and compileJavaTests
tasks),
a so called "convention object" is added to your project configuration, which results in many convention properties defined, for example:
javadoc.destinationDir= file('build/docs/javadoc') archivesBaseName = projectName
In the first build script, War plugin is used, which has two important consequences:
it imports Java plugin (as War plugin depends on it), thus importing all its tasks and configurations,
it adds a war
archive task, which does what war
task is expected to do:
copies src/main/webapp to the root of archive,
copies compiled classes to WEB-INF/classes,
puts all required runtime dependencies into WEB-INF/lib,
creates archive in build/libs directory
Gradle right now offers 9 plugins (Java, Groovy, War, OSGi, Eclipse, Jetty, Maven, Project-reports, Code-quality) and if you miss some features, you can always write your own (which is very simple, but outside the scope of this article - please refer to the userguide).
I have a good news for you - after you switch to Gradle, you'll be still able to use your current artifacts repository. Gradle uses Ivy underneath, and is compatible with any kind of repository you can think of - from "standard" Maven layouts, through old-school flat directory layouts (like libs folder in Ant builds), to basically any custom layout of repository.
As you can see in the first example, achieving simple things is very elegant when using Gradle. To make Gradle download artifacts from the central Maven repository, it is enough to add these lines to your build script:
repositories { mavenCentral() }
Declarations of dependencies are also much more concise than those used by Maven[2] or Ivy. The snippet presented below puts Commons Lang JAR on the compilation classpath:
dependencies { compile "commons-lang:commons-lang:2.4" }
Gradle provide excellent integration with Ant. It is trivial to import your Ant build.xml directly into a Gradle project. You can then use the targets of your Ant build, as if they were Gradle tasks. The other type of integration is achieved thanks to fantastic AntBuilder
provided by Groovy. The ant
property, which is available in every Gradle build script, allows to reuse a wealth of Ant tasks and types - from simple ones like javac
, copy
, jar
to more sophisticated like selenese
(for running Selenium tests) or findbugs
(for execution of Findbugs static code analysis tool).
This all makes switching from Ant to Gradle rather painless... and I'm pretty sure you will never want to go back. :)
Till now we relied on functionality provided by Gradle. It is time to go beyond of what is prepared for us. While doing this, we will learn many interesting things about Gradle.
Let us write a task, that will calculate a checksum for each file in build/libs directory (this is a default directory where all created JAR and WAR files ends up). Because we strongly believe in code reuse, we will use Ant checksum task for this. Add the task presented below to the build.gradle file and execute gradle clean checksum from the command line.
task checksum (dependsOn: assemble) << { def files = file(libsDir).listFiles() files.each { File file -> ant.checksum(file: file, property: file.name) println "$file.name Checksum: ${ant.properties[file.name]}" } }
There is plenty of things happening in this short code snippet. It presents usage of Groovy scripting, dependencies between tasks, and cooperation with Ant.
Firstly, a new checksum task is created. It depends on on assemble
task provided by Java plugin (which creates JAR and WAR files). Then, all files from libsDir
(this is one of many properties set by Java plugin, which by default points to build/libs directory) are stored in a list using Groovy. Next, ant.checksum
method is invoked on each of these files, and the result is printed to the standard output.
It is quite common in the Java world, that the distinction between snapshot and release version is reflected by the names of artifacts. The snapshots receive a "-SNAPSHOT" suffix, which makes them easily recognizable. Let us have a look at implementation of such naming schema in Gradle.
The fragment of build.gradle file presented below shows one very interesting features of Gradle. The execution of Gradle build scripts is divided into phases. First, a DAG (dependency acyclic graph) of tasks is created. Then, the appropriate tasks are executed. The simple example below shows, that it is possible to jump "in between" these two phases, and manipulate the graph of tasks. In the example below, this introspection of tasks graph is used to decide, if archive should receive and additional "-SNAPSHOT" suffix or not.
task release(dependsOn: assemble) << { println 'We release now' } build.taskGraph.whenReady { taskGraph -> if (taskGraph.hasTask(':release')) { version = '1.0' } else { version = '1.0-SNAPSHOT' } }
Execute this build script once with gradle clean assemble and then second time with gradle clean release, and compare the resulting JAR file in the build/libs dir.
Imagine you use Gradle to execute TestNG integration tests for a OSGi based application. This application consists of more than 10 OSGi bundles, and each of them changes often (it is an early stage of development). You would like to have a report that would contain:
information on the build environment (Java version, OS version etc.),
list of bundles (with versions),
results of tests.
It is possible to put all this report functionality into the build script, but then it would grow significantly. Instead, you can easily create your own custom task and then configure it and execute from the build script. The build.gradle would look (roughly) like this:
import org.gradle.sample.report.ReportTask ... dependencies { compile 'commons-lang:commons-lang:2.4' testRuntime 'org.easymock:easymock:2.5.2' // much more dependencies here } ... task report(type: ReportTask, dependsOn: itest) { reportDir = 'build/report' testNgResultsDir = 'build/itest/test-result' serverLogDir= 'build/itest/server-log' jars = configurations.testRuntime }
And the task class (some details omitted):
public class ReportTask extends DefaultTask { def String reportDir def String testNgResultsDir def String serverLogDir def FileCollection jars @TaskAction def createReport() { def text = new StringBuilder() ["os.name", "os.version", "java.version", "java.vm.version", "java.runtime.version", "user.language", "user.name"].each { text.append(it + ":\t${System.getProperty(it)}\n") } text.append("gradle -v".execute().text) jars.each { text.append("$it.name\n") } ... ant.zip(destfile: zipReportFile, basedir: tmpDir) } ... }
These two snippets of code show you few things. First of all, combined power of Groovy and AntBuilder makes it trivial to create text files and perform all kind of operations on them (copy, zip, delete etc.). Second, it is easy to create reusable tasks, and configure them - here, task ReportTask
can take four parameters, which are supplied from the build script (this also helps to keep the real logic out of the build script). Third, groups of dependencies in Gradle can be processed in many ways - here you see a simple example of printing them.
The important thing is, that you can achieve same effects regardless of which approach you use. For example, if you want to add dependency on Commons Lang to core project, you could add this snippet of code to build.gradle placed in the root of the project:
project (':core') { dependencies { compile "commons-lang:commons-lang:2.4" } }
or put this lines into the build.gradle file placed in core directory:
dependencies { compile "commons-lang:commons-lang:2.4" }
As you can see, it is really just a matter of taste.
Our application needs another user interface - this time in Swing. It seems natural at this point to divide the project into three parts - which will also give us a splendid opportunity to have a closer look at how Gradle supports multi-module builds:
core project contains business logic. It is written in Java and uses Commons Lang library. Both UI projects makes use of the core project classes.
swing project creates UI for desktop. It is written in Groovy.
web project creates UI for the web browsers. It is written in Java and uses JSP. The resulting WAR archive must contain all required libraries - not only core JAR, but also transitive dependencies of core project (that is Commons Lang library).
Gradle offers a lot of flexibility when it comes to the layout of mutli-project builds. For the sake of this article, I will use a hierarchical layout (content of src directories not shown):
. |-- build.gradle |-- settings.gradle |-- core | `-- src `-- ui |-- swing | `-- src `-- web `-- src
In the main directory there are two subfolders - one for core project, and the other one called ui. The ui subdirectory is a container for all possible UI implementations. Right now it contains swing and web subdirectories.
As you can see there is only one build.gradle file. You might wonder if it is a good idea to keep all the build logic in one place. If you don't like it, Gradle allows you to have separate build.gradle file for each subproject.[3]
First of all, let us have a look at the new new file - settings.gradle - that we haven't met yet. This file describes the layout of a multi-module build[4]. As you can see below, it tells Gradle where the subprojects are located:
include "core", "ui:swing", "ui:web"
Before we see the build script, let us write down what it should do. Here are the tasks for the full build of this multi-module project:
core project - compile Java classes and create JAR archive,
web project - compile Java classes and JSP files, and create WAR archive,
swing project - compile Groovy classes and create JAR archive,
put all created artefacts (JARs and WARs files) into common repository (repo subdirectory of root project folder).
The build script - build.gradle - is presented below.
subprojects { usePlugin 'java' group = 'org.gradle.sample' version = '1.0' repositories { mavenCentral() flatDir(name: 'fileRepo', dirs: "./repo") } uploadArchives { repositories { add project.repositories.fileRepo } } } project (':core') { dependencies { compile "commons-lang:commons-lang:2.4" } } project (':ui') { subprojects { dependencies { compile project (':core') } } } project (':ui:web') { usePlugin 'war' } project (':ui:swing') { usePlugin 'groovy' dependencies { groovy "org.codehaus.groovy:groovy-all:1.6.5" } }
Even if it is the first time, you looked at multi-module build.gradle file, you should have no problems with understanding it. First it uses subprojects property to set common settings for all subprojects (core, swing and web). This way all of them will:
use Java plugin,
have group property set to org.gradle.sample and version to 1.0,
look for and download dependencies from Maven central repository and local repo directory,
store created archives in repo directory.
After the common settings for all the subprojects are set, each of them is configured independently. A dependency on Commons Lang library is added to core project. Both UI projects are configured to depend on the core project. Then, project web is enhanced with War plugin. Finally project swing is set to use Groovy plugin, and a dependency on Groovy (required by this plugin) is also set.
For a multi-module build, there is always a question of "what part of it would you like to build?". So, would you like to build both UIs, or maybe only one of them ?
To build all projects execute gradle build from the root directory. You will notice, that all projects were build, and three artifacts were created: core-0.1.jar, swing-0.1.jar and web-0.1.war.
If you want to build only one of the UI projects, the buildNeeded
[5] task is what you are looking for. For example, if you execute gradle buildNeeded from ui/swing directory, you will notice, that only core project and swing project were built resulting in the creation of two archives: core-0.1.jar and swing-0.1.jar.
The moral is, that partial builds are possible in Gradle (and even advisable, as they significantly shorten build time).
...ah, another silver bullet ? Another tool that promises to solve all your build-related problems ? Well, Gradle doesn't promise it all. But it strives at
making the impossible possible, the possible easy, and the easy elegant
-- Moshé Feldenkrais
And surely, even now, before 1.0 release, it offers plenty of interesting and useful features. I have presented only some of them - a tip of a (still growing) iceberg. :)
I'd like to encourage you to put some effort into knowing Gradle better. At first you might feel uneasy (especially if you are experienced Maven user) with the flexibility and freedom that it offers. I can tell you from my experience, that even after few month with Gradle, I feel like I haven't made the full mental switch, I'm am still surprised by the impact of Gradle features on my build scripts. Just give yourself some time, and you will be delighted with the power that Gradle offers. The switching cost is not so bad, as many solutions that you are accustomed to (e.g. project layout, Ant tasks, Maven repos) are still there, after you switch to Gradle.
Gradle comes with an impressive userguide, and a lot of examples that will help you to begin (see also CookBook). You can also count on the members of the community (check the mailing lists and the wiki). And if you need professional support, you might be interested in the services offered by Gradle Inc.
http://gradle.org – Gradle home
http://maven.apache.org/plugins/maven-antrun-plugin/ - Maven AntRun Plugin
http://ant.apache.org - Ant
http://maven.apache.org - Maven
http://groovy.codehaus.org/Using+Ant+from+Groovy - Using Ant from Groovy
[1] To be more precise it is Java and War plugins which make Gradle use this project layout as default.
[2] Maven 3 will offer similar syntax, but as you will see later, Gradle's approach to dependencies is much more than just a syntactic sugar.
[3] You'll find both versions in code attached to this article - see for yourself which one seems more natural to you.
[4] Let us mention, that settings.gradle is also used for many other purposes beside multi-module builds.
[5] Please consult the userguide to learn about other, similar tasks.
Source: http://www.javaexpress.pl/article/show/Gradle__Mocarne_narzedzie_do_budowy_projektow