h1. More Stuff h2. Using Gems The purpose of the buildfile is to define your projects, and the various tasks and functions used for building them. Some of these are specific to your projects, others are more general in nature, and you may want to share them across projects. There are several mechanisms for developing extensions and build features across projects which we cover in more details in the section "Extending Buildr":extending.html. Here we will talk about using extensions that are distributed in the form of RubyGems. "RubyGems":http://rubygems.rubyforge.org provides the @gem@ command line tool that you can use to search, install, upgrade, package and distribute gems. It installs all gems into a local repository that is shared across your builds and all other Ruby applications you may have running. You can install a gem from a local file, or download and install it from any number of remote repositories. RubyGems is preconfigured to use the "RubyForge":http://rubyforge.org repository. You'll find a large number of open source Ruby libraries there, including Buildr itself and all its dependencies. RubyForge provides a free account that you can use to host your projects and distribute your gems (you can use RubyForge strictly for distribution, as we do with Buildr). You can also set up your own private repository and use it instead or in addition to RubyForge. Use the @gem sources@ command to add repositories, and the @gem server@ command to run a remote repository. You can see all available options by running @gem help@. If your build depends on other gems, you will want to specify these dependencies as part of your build and check that configuration into source control. That way you can have a specific environment that will guarantee repeatable builds, whether you're building a particular version, moving between branches, or joining an existing project. Buildr will take care of installing all the necessary dependencies, which you can then manage with the @gem@ command. Use the @build.yaml@ file to specify these dependencies (see "Build Settings":settings_profiles.html#build_settings for more information), for example: {{{!yaml # This project requires the following gems gems: # Suppose we want to notify developers when testcases fail. - buildr-twitter-notifier-addon >=1 # we test with ruby mock objects - mocha - ci_reporter }}} Gems contain executable code, and for that reason Buildr will not install gems without your permission. When you run a build that includes any dependencies that are not already installed on your machine, Buildr will ask for permission before installing them. On Unix-based operating systems, you will also need sudo privileges and will be asked for your password before proceeding. Since this step requires your input, it will only happen when running Buildr interactively from the command line. In all other cases, Buildr will fail and report the missing dependencies. If you have an automated build environment, make sure to run the build once manually to install all the necessary dependencies. When installing a gem for the first time, Buildr will automatically look for the latest available version. You can specify a particular version number, or a set of version numbers known to work with that build. You can use equality operations to specify a range of versions, for example, @1.2.3@ to install only version 1.2.3, and @=> 1.2.3@ to install version 1.2.3 or later. You can also specify a range up to one version bump, for example, @~> 1.2.3@ is the same as @>= 1.2.3 < 1.3.0@, and @~> 1.2@ is the same as @>= 1.2.0 < 2.0.0@. If necessary, you can exclude a particular version number, for example, @~> 1.2.3 != 1.2.7@. Buildr will install the latest version that matches the version requirement. To keep up with newer versions, execute the @gem update@ command periodically. You can also use @gem outdated@ to determine which new versions are available. Most gems include documentation that you can access in several forms. You can use the @ri@ command line tool to find out more about a class, module or specific method. For example: {{{!sh $ ri Buildr::Jetty $ ri Buildr::Jetty.start }}} You can also access documentation from a Web browser by running @gem server@ and pointing your browser to "http://localhost:8808":http://localhost:8808. Note that after installing a new gem, you will need to restart the gem server to see its documentation. h2. Using Java Libraries Buildr runs along side a JVM, using either RJB or JRuby. The Java module allows you to access Java classes and create Java objects. Java classes are accessed as static methods on the @Java@ module, for example: {{{!ruby str = Java.java.lang.String.new('hai!') str.toUpperCase => 'HAI!' Java.java.lang.String.isInstance(str) => true Java.com.sun.tools.javac.Main.compile(args) }}} The @classpath@ attribute allows Buildr to add JARs and directories to the classpath, for example, we use it to load Ant and various Ant tasks, code generators, test frameworks, and so forth. When using an artifact specification, Buildr will automatically download and install the artifact before adding it to the classpath. For example, Ant is loaded as follows: {{{!ruby Java.classpath << 'org.apache.ant:ant:jar:1.7.0' }}} Artifacts can only be downloaded after the Buildfile has loaded, giving it a chance to specify which remote repositories to use, so adding to classpath does not by itself load any libraries. You must call @Java.load@ before accessing any Java classes to give Buildr a chance to load the libraries specified in the classpath. When building an extension, make sure to follow these rules: # Add to the @classpath@ when the extension is loaded (i.e. in module or class definition), so the first call to @Java.load@ anywhere in the code will include the libraries you specify. # Call @Java.load@ once before accessing any Java classes, allowing Buildr to set up the classpath. # Only call @Java.load@ when invoked, otherwise you may end up loading the JVM with a partial classpath, or before all remote repositories are listed. # Check on a clean build with empty local repository. h2. Nailgun "Nailgun":http://www.martiansoftware.com/nailgun/index.html is a client, protocol, and server for running Java programs from the command line without incurring the JVM startup overhead. Nailgun integration is available only when running Buildr within JRuby. Buildr provides a custom nailgun server, allowing you to start a single JVM and let buildr create a queue of runtimes. These JRuby runtimes can be cached (indexed by buildfile path) and are automatically reloaded when the buildfile has been modified. Runtime caching allows you to execute tasks without spending time creating the buildr environment. Start the BuildrServer by executing {{{!ruby $ jruby -S buildr -rbuildr/nailgun nailgun:start }}} Server output will display a message when it becomes ready, you will also see messages when the JRuby runtimes are being created, or when a new buildr environment is being loaded on them. After the runtime queues have been populated, you can start calling buildr as you normally do, by invoking the $NAILGUN_HOME/ng binary: {{{!sh # on another terminal, change directory to a project. # if this project is the same nailgun:start was invoked on, it's # runtime has been cached, so no loading is performed unless # the buildfile has been modified. otherwise the buildfile # will be loaded on a previously loaded fresh-buildr runtime # and it will be cached. cd /some/buildr/project ng nailgun:help # display nailgun help ng nailgun:tasks # display overview of ng tasks ng clean compile # just invoke those two tasks }}} Some nailgun tasks have been provided to manage the cached runtimes, to get an overview of them execute the @nailgun:tasks@ task. Be sure to read the nailgun help by executing the @nailgun:help@ task. h2. Eclipse, IDEA If you're using Eclipse, you can generate @.classpath@ and @.project@ from your Buildfile and use them to create a project in your workspace: {{{!sh $ buildr eclipse }}} The @eclipse@ task will generate a @.classpath@ and @.project@ file for each of projects (and sub-project) that compiles source code. It will not generate files for other projects, for examples, projects you use strictly for packaging a distribution, or creating command line scripts, etc. If you add a new project, change the dependencies, or make any other change to your Buildfile, just run the @eclipse@ task again to re-generate the Eclipse project files. If you prefer IntelliJ IDEA, you can always: {{{!sh $ buildr idea }}} It will generate a @.iml@ file for every project (or subproject) and a @.ipr@ that you can directly open for the root project. To allow IntelliJ Idea to resolve external dependencies properly, you will need to add a @M2_REPO@ variable pointing to your Maven2 repository directory (@Settings / Path Variables@). If you're using IDEA 7 or later, use the @buildr idea7x@ task instead. This task creates the proper @.ipr@ and @.iml@ files for IDEA version 7. It includes the @-7x@ suffix in the generated files, so you can use the @idea@ and @idea7x@ tasks side by side on the same project. Also, check out the "Buildr plugin for IDEA":http://www.digitalsanctum.com/buildr-plug-in/ (IDEA 7 and later). Once installed, open your project with IDEA. If IDEA finds that you have Buildr installed and finds a buildfile in the project's directory, it will show all the tasks available for that project. To run a task, double-click it. When the task completes, IDEA will show the results in the Buildr Output window. h2. Cobertura, JDepend You can use "Cobertura":http://cobertura.sourceforge.net/ to instrument your code, run the tests and create a test coverage report in either HTML or XML format. There are two tasks, both of which generate a test coverage report in the @reports/cobertura@ directory. For example: {{{!sh $ buildr test cobertura:html }}} As you can guess, the other task is @cobertura:xml@. If you want to generate cobertura reports only for a specific project, you can do so by using the project name as prefix to cobertura tasks. {{{!sh $ buildr subModule:cobertura:html }}} Each project can specify which classes to include or exclude from cobertura instrumentation by giving a class-name regexp to the @cobertura.include@ or @cobertura.exclude@ methods: {{{!ruby define 'someModule' do cobertura.include 'some.package.*' cobertura.include /some.(foo|bar).*/ cobertura.exclude 'some.foo.util.SimpleUtil' cobertura.exclude /*.Const(ants)?/i end }}} You can use "JDepend":http://clarkware.com/software/JDepend.html on to generate design quality metrics. There are three tasks this time, the eye candy one: {{{!sh $ buildr jdepend:swing }}} The other two tasks are @jdepend:text@ and @jdepend:xml@. We want Buildr to load fast, and not everyone cares for these tasks, so we don't include them by default. If you want to use either one, you need to require it explicitly. The proper way to do it in Ruby: {{{!ruby require 'buildr/cobertura' require 'buildr/jdepend' }}} You may want to add those to the Buildfile. Alternatively, you can use these tasks for all your projects without modifying the Buildfile. One convenient method is to add these two likes to the @buildr.rb@ file in your home directory. Another option is to require it from the command line (@--require@ or @-r@), for example: {{{!sh $ buildr --require buildr/jdepend jdepend:swing $ buildr -rbuildr/cobertura cobertura:html }}} h2. Anything Ruby Can Do Buildr is Ruby code. That's an implementation detail for some, but a useful features for others. You can use Ruby to keep your build scripts simple and DRY, tackle ad hoc tasks and write reusable features without the complexity of "plugins". We already showed you one example where Ruby could help. You can use Ruby to manage dependency by setting constants and reusing them, grouping related dependencies into arrays and structures. You can use Ruby to perform ad hoc tasks. For example, Buildr doesn't have any pre-canned task for setting file permissions. But Ruby has a method for that, so it's just a matter of writing a task: {{{!ruby bins = file('target/bin'=>FileList[_('src/main/dist/bin/*')]) do |task| mkpath task.name cp task.prerequisites, task.name chmod 0755, FileList[task.name + '/*.sh'], :verbose=>false end }}} You can use functions to keep your code simple. For example, in the ODE project we create two binary distributions, both of which contain a common set of files, and one additional file unique to each distribution. We use a method to define the common distribution: {{{!ruby def distro(project, id) project.package(:zip, :id=>id).path("#{id}-#{version}").tap do |zip| zip.include meta_inf + ['RELEASE_NOTES', 'README'].map { |f| path_to(f) } zip.path('examples').include project.path_to(:source, :examples), :as=>'.' zip.merge project('ode:tools-bin').package(:zip) zip.path('lib').include artifacts(COMMONS.logging, COMMONS.codec, COMMONS.httpclient, COMMONS.pool, COMMONS.collections, JAXEN, SAXON, LOG4J, WSDL4J, XALAN, XERCES) project('ode').projects('utils', 'tools', 'bpel-compiler', 'bpel-api', 'bpel-obj', 'bpel-schemas').map(&:packages).flatten.each do |pkg| zip.include(pkg.to_s, :as=>"#{pkg.id}.#{pkg.type}", :path=>'lib') end yield zip end end }}} And then use it in the project definition: {{{!ruby define 'distro-axis2' do parent.distro(self, "#{parent.id}-war") { |zip| zip.include project('ode:axis2-war').package(:war), :as=>'ode.war' } end }}} Ruby's functional style and blocks make some task extremely easy. For example, let's say we wanted to count how many source files we have, and total number of lines: {{{!ruby sources = projects.map { |prj| prj.compile.sources. map { |src| FileList["#{src}/**/*.java"] } }.flatten puts "There are #{source.size} source files" lines = sources.inject(0) { |lines, src| lines += File.readlines(src).size } puts "That contain #{lines} lines" }}}