Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
211 lines (141 sloc) 15.2 KB
layout title
default
Quick Start

This quick start guide is meant to be a very simple introduction to Buildr and its most basic concepts. However, despite its basic level, we will still cover most of the concepts you will ever need to be productive with Buildr. We will leave out some important things (like sub-projects), and we will over-simplify some other concepts (such as artifacts). Nevertheless, most Buildr projects never need to go beyond the techniques contained within these pages.

No knowledge of Ruby is assumed. Buildr is designed to be a very intuitive, very easy-to-use tool. You can create buildfiles which describe incredibly intricate projects, write custom tasks which do things far beyond Ant, and still never need to pick up more than a smattering of Ruby syntax. With that said, if you do know Ruby, Buildr’s DSL will seem very natural and welcoming. We do assume that you have already downloaded and installed Buildr and are ready to put the tool to good use.

Your First Project

Much like Maven, Buildr is oriented around projects and tasks. You define your project in a concise, declarative fashion and most common tasks (such as compilation and testing) will be made available to you “at no extra charge”. Most of the project definition is contained within the buildfile — or Buildfile, if you’re really in love with the Make convention — a single file sitting at the root of your project. A project definition does not need to be any more complicated than the following:

{% highlight ruby }
define ‘killer-app’
{ endhighlight %}

Compiling

Of course, this isn’t really giving Buildr much information. What it can’t learn from the buildfile, Buildr will figure out by inspecting your directory structure. Java sources are expected to exist within the src/main/java/ directory. If Buildr finds these sources, it will automatically configure the compilation to source that directory, depositing the results in the target/classes/ directory (all under the project directory of course). We can run this compilation using the following command:

{% highlight sh }
$ buildr compile
{ endhighlight %}

Information about the classpath and dependencies is described later on.

By default, Buildr projects assume the Java language and the src/main/java/ source directory. You can also have projects in the Scala or Groovy language (both languages support joint compilation with Java). To use Scala, place your .scala files in the src/main/scala/ directory and include the following invocation at the head of your buildfile: require 'buildr/scala' Similarly, Groovy expects sources in the src/main/groovy/ directory and necessitates require 'buildr/groovy' (see languages for more information).

The compile task will also detect any files which exist under the src/main/resources/ directory. These resources are copied verbatim to the target/resources/ directory as part of the compilation task. Buildr also performs some basic change detection to minimize compilation. If your source files haven’t changed since the last compile, then they will not be recompiled.

Packaging

At some point, we’re going to want to wrap up our classes and produce a single JAR file for distribution. Buildr can certainly help us with this, but we are going to need to provide it with a little bit more information. Specifically, we need to say the type of package to produce (e.g. :jar, :war, etc) as well as the current version of our project. This information is placed within the buildfile:

{% highlight ruby }
define ‘killer-app’ do
project.version = ‘0.1.0’
package :jar
end
{ endhighlight %}

The project.version attribute can be any value you like. Even non-numeric versions are perfectly acceptable (e.g. 'ikj-0.3.1-E'). This version — coupled with the packaging information — will be used to generate a JAR file: killer-app-0.1.0.jar. As would be expected, this file is placed within the target/ directory when the following command is run:

{% highlight sh }
$ buildr package
{ endhighlight %}

The package task depends upon the compile task, so if a rebuild is necessary prior to the creation of the JAR, Buildr will see to it.

We can also chain tasks together in a single invocation. For example, we may want to clean all of the old compilation results prior to recompiling and generating a packaged result:

{% highlight sh }
$ buildr clean package
{ endhighlight %}

The clean task simply removes the target/ directory, effectively wiping out any compilation results like class files or resources.

Directory Structure

As you may have noticed, Buildr does have some default notions about what a project should look like and how it should be organized. We think that these defaults are quite nice and make for a more navigable project. However, we do understand that not all projects are exactly alike. Buildr’s layouts make it possible for any project to easily change these defaults. For example, this would allow you to easily migrate a project that had been based on a different directory structure, such as the src/ and bin/ convention often used by Ant.

Dependencies

So far, we have seen how Buildr can automatically infer what amounts to dozens of lines of build.xml contents, all based on a buildfile and a directory structure. However, the best is yet to come. Buildr also provides Maven-style dependency management (but without the long loading times!). In other words, you specify each dependent library using a string descriptor and Buildr figures out how to download and configure your classpath (the library descriptors are just a Ruby array, therefore they are separated by commas (,)). You must specify at least one remote repository so that Buildr knows from where to download these libraries. For example, we can configure our project to reference the Apache Commons CLI library and download libraries from the Ibiblio repository:

{% highlight ruby %}
repositories.remote << ‘http://www.ibiblio.org/maven2’

define ‘killer-app’ do
project.version = ‘0.1.0’
compile.with ‘commons-cli:commons-cli:jar:1.2’
package :jar
end
{% endhighlight %}

This sort of dependency declaration should look quite familiar if you are at all familiar with Maven. The general format for an artifact descriptor is groupId:artifactId:packageType:version. Any Maven artifacts included in this fashion will be retrieved from the list of remote repositories (in this case, Ibiblio) and installed in your local repository at ~/.m2/repository/.

You can search the global repository of artifacts at sites like MvnBrowser. Simply enter the name of the library you are looking for, and the search should pull up the groupId, artifactId and a list of available versions.

Unfortunately, not all libraries are quite as simple as Commons CLI. Many libraries (such as Apache Wicket) have dependencies of their own. While we may be able to compile against Apache Wicket without these extra libraries on our classpath, we cannot actually run our application without its transitive dependencies. To avoid tracking down each of these dependencies and adding them manually, we can simply use the transitive directive (this is how Maven behaves by default):

{% highlight ruby %}
repositories.remote << ‘http://www.ibiblio.org/maven2’

define ‘killer-app’ do
project.version = ‘0.1.0’
compile.with transitive(‘org.apache.wicket:wicket:jar:1.4-rc6’)
package :jar
end
{% endhighlight %}

The compile.with property accepts a full array of comma-separated artifacts, making it possible to specify any number of dependencies as necessary. Of course, such a long list of verbose string descriptors could get very tiresome and messy. For this reason, it is conventional to assign each dependency to a constant (e.g. WICKET) which is declared just above the project in the buildfile and passed to compile.with in a clean, easy-to-read style:

{% highlight ruby %}
repositories.remote << ‘http://www.ibiblio.org/maven2’

WICKET = transitive(‘org.apache.wicket:wicket:jar:1.4-rc6’)
SLF4J = ‘org.slf4j:slf4j-jdk14:jar:1.5.8’

define ‘killer-app’ do
project.version = ‘0.1.0’
compile.with WICKET, SLF4J
package :jar
end
{% endhighlight %}

Unfortunate as it may seem, not all libraries are available in Maven repositories. While most of the major libraries (e.g. Hibernate, Spring, etc) are kept updated by intrepid volunteers, some of the more obscure frameworks are left out in the cold. An example of one such framework is DBPool, a very fast connection pool designed to integrate with JDBC. However, like most Java libraries, DBPool does provide a zip archive which contains the JAR file, as well as some documentation and perhaps a license or two.

Almost magically, we can instruct Buildr to get the DBPool artifact from this URL. Buildr will treat this download just like any other artifact, retrieving it when requried by the compile task. However, unlike a normal Maven artifact, Buildr will do some extra processing once the download is complete. It will actually dig into the downloaded archive, detect and extract the JAR file, installing it into the local repository just like any other artifact:

{% highlight ruby %}
DBPOOL = ‘net.snaq:dbpool:jar:4.8.3’
download artifact(DBPOOL) => ‘http://www.snaq.net/java/DBPool/DBPool_v4.8.3.zip’

define ‘killer-app’ do
project.version ‘0.1.0’
compile.with DBPool
package :jar
end
{% endhighlight %}

This is one area where Buildr’s dependency management vastly excedes Maven’s. With Maven, you would have to install the DBPool dependency manually. Buildr’s auto-magical download and extraction keeps the dependency definitions centralized within the buildfile, available to your entire team and automatically resolved as needed by the compilation tasks.

Testing

Buildr supports auto-magical integration with a number of mainstream testing frameworks. For Java, this includes the ubiquitus JUnit4, as well as TestNG and a number of others. Scala supports Specs and ScalaTest, while Groovy supports EasyB. Configuration is as simple as placing your test sources in the appropriate directory. In the case of JUnit or TestNG, this would be src/test/java/. Once these tests are in place, we can run them using the test task:

{% highlight sh }
$ buildr test
{ endhighlight %}

When the test task runs, it will ensure that your main sources are compiled, as well as the tests themselves. In the case of JUnit4, test classes are auto-detected based on which base class they extend (TestCase). These tests will be invoked using the special test classpath. This classpath includes all of the dependencies passed to compile.with along with the dependencies required for testing. Thus, Buildr will actually go out and download JUnit 4.5 (if necessary) and place that JAR on the classpath in order to run your tests. It is also possible to add artifacts specifically required for testing. So, if your tests make use of the Commons Collections library, but your main sources do not, you can include that dependency only for the tests by using the test.with property. This functions identically to compile.with:

{% highlight ruby }
define ‘killer-app’ do
project.version = ‘0.1.0’
compile.with ‘commons-cli:commons-cli:jar:1.2’
test.with ‘commons-collections:commons-collections:jar:3.2’
package :jar
end
{ endhighlight %}

Of course, not everyone likes JUnit4. As mentioned previously, Buildr supports a number of test frameworks. It is possible to use TestNG instead of JUnit4 by setting the test.using property to :testng:

{% highlight ruby }
define ‘killer-app’ do
project.version = ‘0.1.0’
compile.with ‘commons-cli:commons-cli:jar:1.2’
test.with ‘commons-collections:commons-collections:jar:3.2’
test.using :testng
package :jar
end
{ endhighlight %}

Note that only one test framework per-project may be used. This may seem like an obvious restriction given that both frameworks introduced so far have used the same directory, but other frameworks such as Specs and EasyB do not follow the same convention. In cases of ambiguity (for example, when tests are present in both src/test/java/ and src/spec/scala/), only one test framework will be chosen, but this choice is not well-defined. When in doubt, explicitly specify the test framework with the test.using property. This overrides Buildr’s auto-detection and ensures sane behavior.

Other test frameworks are documented here and here.

Custom Tasks

If there is one area in which Buildr excels, it is defining custom tasks. This is something which is notoriously difficult in both Ant and Maven, often requiring separate Java plugins and mountains of code simply to perform basic tasks. For example, let’s imagine that we wanted to define a run task which would compile and run our “killer-app” project. This is a simple matter of invoking the java command against our main class:

{% highlight ruby %}
define ‘killer-app’ do
project.version = ‘0.1.0’
package :jar

task :run => :compile do system ‘java -cp target/classes org.apache.killer.Main’ end

end
{% endhighlight %}

This code defines a new task, run, which depends upon the compile task. This task only performs a single operation: it invokes the system method, passing the relevant command as a string. Note that the system method documentation may be found here. Tasks use real Ruby (actually, the entire buildfile is real Ruby too), so if you are familiar with that language, then you should be right at home writing custom tasks in Buildr. We can invoke this task in the following way:

{% highlight sh }
$ buildr killer-app:run
{ endhighlight %}

This works, but it’s clumsy. The reason we had to give the “killer-app:” prefix is because we defined the run task within our project, rather than outside of the define block. However, if we define run outside of the project, then we don’t really have access to the compile task (which is project-specific). The solution here is a bit of magic known as local_task. This is how tasks like compile and test, which are technically project-specific (think: instance methods) can be invoked without the fully-qualified project name:

{% highlight ruby %}
Project.local_task :run

define ‘killer-app’ do
project.version ‘0.1.0’

package :jar task :run => :compile do system ‘java -cp target/classes org.apache.killer.Main’ end

end
{% endhighlight %}

Now, we can invoke run exactly the way we want, with a minimum of wasted characters:

{% highlight sh }
$ buildr run
{ endhighlight %}

Summary

As long as this guide was, we have barely even scratched the surface of Buildr’s true power. This was meant only to get you up and running as quickly as possible, exploiting some of Buildr’s unique features to ease your build process. For more comprehensive documentation, start reading about projects in Buildr and work your way from there.

Something went wrong with that request. Please try again.