Buildr – The build system that doesn’t suck

If you are a Java guy like me, you are probably doing a lot of ugly tasks in your everday work. One of these tasks is build management.

The Java community tries to tackle builds with standard tools like “Ant”:http://ant.apache.org/ and “all-in-one” solutions like “Maven”:http://maven.apache.org/. Both are based on huge “XML”:http://xml.iscrapbecause.it/ configuration files that are hard to read, painful to maintain and impossible to test. Even though these tools are mainstream, they are quite buggy. Just have a look at all the “rants about maven”:http://lmgtfy.com/?q=maven+sucks you find on the net.

Java builds are known to become very complex over time. You have to compile things found here, include stuff from there, resolve dependencies in a remote repository, add something to the version control, […even more crazy stuff…]. Addressing such complex and dynamic tasks with static structures is just a stupid idea. What you really want to do is scripting!

h2. scripting builds

Using scripting languages is a natural way to address build tasks. If you are close to the operating system you can do most things easily. Trust your admin, the shell is your friend!

The simplest way to get scripting power is to use bash scripts, which has the drawback that it’s not interoperable and you will always have to show consideration for WINDOW$ users…

There are portable frameworks that try to integrate the flexibilty of scripting languages like Ruby with the power of Ant or Maven. Have a look at “Gradle”:http://www.gradle.org/, “GMaven”:http://docs.codehaus.org/display/GMAVEN/Home, “Sbt”:http://code.google.com/p/simple-build-tool/, “Raven”:http://raven.rubyforge.org/, “Gant”:http://gant.codehaus.org/ or “Antwrap”:http://antwrap.rubyforge.org/ as examples.
About one year ago I stumbled over another build tool that claims the slogan “The build system that doesn’t suck”:http://buildr.apache.org/ which I found quite promising!

h2. dynamic builds with Buildr

Buildr is an expressive Ruby DSL for managing builds based on “Rake”:http://rake.rubyforge.org/ and Ant. Defining a Java project that follows “the default Apache directory structure”:http://buildr.apache.org/projects.html#dir_structure is dead simple:

# buildfile.rb
desc 'define a project called simple'
define 'simple' do
  info 'create a simple.jar from this project'
  package :jar
end

After installing the Buildr Gem you can call built-in or custom Buildr tasks on your project. This is done in the same way Rake would do it:

# install the Buildr gem
gem install buildr

# check buildr version
buildr -V
=> Buildr 1.3.5 (JRuby 1.4.0)

# build your Java project: compile, test, package
buildr package

Since Buildr is based on sensible defaults, it knows where to look for your class files, resources and tests. Before creating the jar, it compiles everything, executes the tests and then zips up the bytecode into a distributable.

Buildr does not support every build feature by default, but it’s easy to extend. Buildr at its core is just a bunch of tasks, chained in a predefined order. You can easily write your own tasks and plug them into the build to fit your needs. Buildr provides method hooks that allow you to integrate your tasks into the build cycle.

If this small example makes you say “Awesome!”, you should have a look at “the best documentation of an open source framework I have ever seen”:http://buildr.apache.org/buildr.pdf. There are some sophisticated “examples of how to use Buildr”:http://github.com/phoet/buildr-examples hosted on github as well.

Build, don’t goof up XML!

(X)Ruby on the Mac

=====================================================================
=====================================================================

_Update Aug. 2010_

The “RVM installer”:http://rvm.beginrescueend.com/rvm/install/ should be prefered over installation via gem:

bash < <( curl http://rvm.beginrescueend.com/releases/rvm-install-head )

=====================================================================
=====================================================================

If you are a Mac user and a Ruby developer you probably ran into issues with custom Ruby installations and had troubles with Gems like these:

* "How to fix that Rubygems mess on Leopard":http://blog.carlmercier.com/2007/12/14/how-to-fix-that-rubygems-mess-on-leopard/
* "Installing Ruby, RubyGems, and Rails on Snow Leopard":http://hivelogic.com/articles/compiling-ruby-rubygems-and-rails-on-snow-leopard/
* "How do I install the mysql ruby gem under OS X 10.5.4":http://stackoverflow.com/questions/41134/how-do-i-install-the-mysql-ruby-gem-under-os-x-10-5-4
* [...neverending list of links...]

h2. Using MacPorts

There are a lot of people that recommend using "MacPorts":http://www.macports.org/ for installing a custom Ruby version, but there are a lot of "issues with this approach":http://lmgtfy.com/?q=macports+ruby+error too. If you did manage installing the Ruby version of choice, you might run into "issues using TextMate":http://old.nabble.com/Subversion-Hangs-When-I-Try-to-Commit-td15208705.html or other tools, that depend on the default OS X Ruby (I think that I had ALL the problems one could have, but this might be an exaggeration).

h2. Custom (X)Ruby Versions

The next problem arises if you want to use different versions of Ruby or even *different implementations/runtimes* (EVIL666) like "JRuby":http://jruby.org/, "MacRuby":http://www.macruby.org/ or "Rubinius":http://rubini.us/.

I am currently using:

* Ruby 1.8.6 for integration with "Heroku":http://heroku.com/
* Ruby 1.8.7 for daily usage
* Ruby 1.9.1 for a fast Ruby experience
* MacRuby for playing around and avoid looking at verbose Objective-C code
* JRuby for "Buildr":http://buildr.apache.org/

There are some simple approaches to fix this mess. MacPorts and JRuby provide different binaries to run like _ruby18_, _ruby19_ or _jgem_, but this is error prone and confusing. I was always installing Gems to the wrong Ruby environment and there were always conflicts with scripts and tools looking for the actual Ruby binary.

Since this did not work out quite well I started to wire the path tightly in the _.profile_ file to include just the right Ruby and Rubygems binary. This had the drawback that I had to close terminals for every switch. So I started writing bash scripts manipulating the path directly, which worked well, but was unconvenient.

Most of the Ruby versions are not stable. Ruby 1.9 is under development, JRuby has frequent compatibility and performance releases and MacRuby is kind of an an "Alpha".
As far as I am concerned, I always want to use the *latest stable* release of these distributions! Using MacPorts you often just get *some* release, so you have to compile stuff yourself. Doing so is time consuming and painful...

h2. RVM to the rescue

Some weeks ago I stumbled over "RVM (Ruby Version Manager)":http://rvm.beginrescueend.com/, which can be installed as a Gem to the standard preinstalled OS X Ruby. RVM provides some neat features and does exactly what I want. It provides a simple interface to manage Ruby distributions and does some clever stuff organizing local Gems.

Right now I am using the preinstalled OS X Snow Leopard Ruby distribution with RVM and no ports:

# check that you are running default Ruby
ruby -v
=> ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]

# update your OS X Rubygems
sudo gem update --system

# check Gem version
gem -v
=> 1.3.5

# install RVM as a Gem to the defaut OS X Ruby
sudo gem install rvm

# run RVM installation
rvm-install
# follow the installation instructions (add RVM to the $PATH etc)

# open new terminal and check RVM version
rvm --version
=> rvm 0.1.0 by Wayne E. Seguin ([email protected]) [http://rvm.beginrescueend.com/]

# have a look at the manual
rvm --help

# install a (X)Ruby version
rvm install jruby

# see what is installed
rvm list

# see some more information
rvm info

# use a (X)Ruby version
rvm use jruby

# install a gem for the current (X)Ruby
gem install buildr

# install a gem for all Rubies under RVM control
rvm gem install -v=0.6.9 savon

# flip back to default Ruby
rvm system

Cut Rubies with ease...

Savon Handsoap Shootout

This documentation is deprecated, please have a look at “savonrb.com”:http://savonrb.com/!

p. Looking into “The Ruby Toolbox”:http://ruby-toolbox.com/ there are currently two popular “SOAP client libraries”:http://ruby-toolbox.com/categories/soap.html available. In this short article I am going to crunch the candidates “Savon”:http://github.com/rubiii/savon, which is currently the most “popular” library, and “Handsoap”:http://github.com/unwire/handsoap/ which follows short after. Both are open source projects hosted on “github”:http://github.com/.

h2. Scope

p. This article won’t cover all facets of both libraries. I concentrate on the features that are relevant for integrating a Ruby SOAP client into our particular SOA platform. That platform is commonly based on Java SOAP services based on frameworks such as “CXF”:http://cxf.apache.org/ and “Axis”:http://ws.apache.org/axis/ providing interfaces to internal business logic.

p. Since this is not a complete feature list, it should show you at least how to work with the APIs and which client might be the best choice for yourself.

h2. Requirements

p. Having lots of Java Guys around here, there is no great focus on things like beautiful API design or Ruby magic, the client should *just work*! Living in a Java environment, the SOAP client has to integrate smoothly with “JRuby”:http://jruby.org/. Since a lot of Ruby libraries lack support for JRuby, we always have to monkey patch a lot of code to make it run on the JRE.

h2. Examples

p. We refer to free, public SOAP services, so everyone can run the examples by themselves.

p. All the examples seen here can be cloned/downloaded from “github”:http://github.com/phoet/savon_handsoap_shootout.

h2. Chapters

* “Calling a service”:http://blog.nofail.de/2010/01/savon-vs-handsoap-calling-a-service/
* “Accessing a WSDL”:http://blog.nofail.de/2010/01/savon-vs-handsoap-accessing-a-wsdl/
* “Authentication”:http://blog.nofail.de/2010/01/savon-vs-handsoap-authentication/
* “Errors”:http://blog.nofail.de/2010/01/savon-vs-handsoap-errors/
* “Benchmark”:http://blog.nofail.de/2010/01/savon-vs-handsoap-benchmark/
* “JRuby”:http://blog.nofail.de/2010/01/savon-vs-handsoap-jruby/
* “Conclusion”:http://blog.nofail.de/2010/01/savon-vs-handsoap-conclusion/

p. Have fun!

Savon vs. Handsoap: Calling a service

This documentation is deprecated, please have a look at “savonrb.com”:http://savonrb.com/!

p. The two libraries have different approaches on how to get things done. While Handsoap is using an oldschool inheritance style definition:

class HandsoapBankCode < Handsoap::Service
  
  endpoint :uri => "some_wsdl", :version => 2

  def on_create_document(doc)
    doc.alias "tns", "some_namespace"
  end

  def on_response_document(doc)
    doc.add_namespace "ns1", "some_namespace"
  end
  [...]
end

p. Savon clients are just a kind of wrapper or proxy around a WSDL:

client = Savon::Client.new "some_wsdl"

p. While inheritance is a base concept of object oriented programming, it’s usually better to use delegation instead. For not being stuck on the API of the Handsoap::Service class, one would wrap things up into some other class or module, creating more code than necessary.

p. The proxy style client of Savon is less code and provides a flexible API, especially looking at SOAP calls.

p. Using “rspec”:http://rspec.info/ to demonstrate the expected behavior of the clients results in two identical spec for getting a zip code of a concrete client implementation:

describe "Savon" do
  it "should return the corrent zip code for a given bank" do
    zip_code = Shootout::SavonBankCode.zip_code @bank_code
    zip_code.should eql @zip_code
  end
end

describe "Handsoap" do
  it "should return the corrent zip code for a given bank" do
    zip_code = Shootout::HandsoapBankCode.zip_code @bank_code
    zip_code.should eql @zip_code
  end
end

p. Compared to the spec, the code of the two implementations differs a great deal. The task at hand is to call the getBank method of the “SOAP endpoint”:http://www.thomas-bayer.com/axis2/services/BLZService?wsdl providing a blz (bank code) parameter and extracting the plz (zip code) value of the response.

p. Using the Handsoap client class defined above, sending the “invoke()” message to the Handsoap::Service will do the job:

def zip_code(bank_code)
  response = invoke("tns:getBank") do |message|
    message.add "tns:blz", bank_code
  end
  (response/"//ns1:details/ns1:plz").first.to_s
end

p. The bank code parameter is assigned in the block, which yields a “SOAP message object”:http://github.com/unwire/handsoap/blob/master/lib/handsoap/xml_mason.rb. The resulting XML document is wrapped and can be accessed using some predefined XML library. Handsoap enables you to choose between different types of XML parsers like “REXML”:http://www.ruby-doc.org/core/classes/REXML.html, “ruby-libxml”:http://libxml.rubyforge.org/ or “nokogiri”:http://github.com/tenderlove/nokogiri.

p. Savon’s proxy client on the other hand is dynamic and can be accessed directly with the name of the SOAP method and a block:

class SavonBankCode
  def self.zip_code(bank_code)
    client = Savon::Client.new Shootout.endpoints[:bank_code][:uri]
    response = client.get_bank { |soap| soap.body = { "wsdl:blz" => bank_code } }
    response.to_hash[:get_bank_response][:details][:plz]
  end
end

p. The block yields a “SOAP request object”:http://github.com/rubiii/savon/blob/master/lib/savon/soap.rb for setting the payload or tweaking defaults like the SOAP header. Converting the response to a hash is a convenient way to access the desired result. The conversion is done using “crack”:http://github.com/jnunemaker/crack/.

Savon vs. Handsoap: Accessing a WSDL

This documentation is deprecated, please have a look at “savonrb.com”:http://savonrb.com/!

p. Both clients provide an interface to work with a WSDL. While the Handsoap WSDL support is hidden in some helper class, WSDLs are a first class citizen in Savon. The code for printing out the available SOAP actions looks like this:

require "handsoap/parser"
wsdl = Handsoap::Parser::Wsdl.read(@wsdl_uri)
wsdl.bindings.each {|binding| binding.actions.each{|action| p action.name }}
p Savon::Client.new("some_wsdl").wsdl.soap_actions

p. The Handsoap parser class is part of a Rails generator. The generator can be used for creating a Handsoap service class skeleton and tests:

$ script/generate handsoap http://www.thomas-bayer.com/axis2/services/BLZService?wsdl

      exists  app
      exists  app/models
      create  app/models/blz_service.rb
      exists  test
      exists  test/integration
      create  test/integration/blz_service_test.rb
----
Endpoints in WSDL
  You should copy these to the appropriate environment files.
  (Eg. `config/environments/*.rb`)
----
# wsdl: http://www.thomas-bayer.com/axis2/services/BLZService?wsdl
BLZ_SERVICE_ENDPOINT = {
  :uri => 'http://www.thomas-bayer.com:80/axis2/services/BLZService',
  :version => 2
}
----

p. The skeleton provides method stubs for adding request parameters and result parsing:

def get_bank
  soap_action = ''
  response = invoke('tns:getBank', soap_action) do |message|
    raise "TODO"
  end
end