A blog about software development and other software related matters

Blog Archive

Friday, August 3, 2007

Building made easy..


Well this post is devoted to Buildr a Maven like build tool intended for Java projects which aims to make the whole building process much simpler to write and maintain.
It Actually uses similar Maven idioms & concepts like:
  • Usage of remote Repositories.
  • Artifacts.
  • Different Life cycle stages.
Still it differs in its infer structure which is based upon Rake instead of Ant (no more XML and thank god for that!).
One of the positive things that i feel that Ruby can bring into the Java sphere is simplicity and Buildr is no exception, its really easy to start get going in a matter of hours not days!.
With that last statement in mind iv had some troubles with the installation of Buildr under Ubuntu, which lead me to the following conclusions:
  • download and install gem manually (don't use synaptic).
  • sudo apt-get install ruby1.8-dev, build-essential.
  • choose ruby versions when ever gem asks you.

Now lets go ahead and create our simple build example with the following structure (looks familiar?):


We want our build to supply us with the following simple services:
  • Compile our project.
  • Create an Intellij project (with all the dependencies already defined).
  • Package our project into a jar.
  • Deploy our project into a folder ready to run.
  • To clean up our acts.
  • Run some basic tests.

Buildr requires a single file (named buildfile) to be placed at the base folder of our project, this file will contain the project's tasks.
Our first step will be to define the artifacts that we depend upon, Buildr uses Ruby data structures (arrays, structs and even hashes!) to define them.
Its up to us to if we want to place these definitions in a separate file or not (usually we do), here is our artifacts.rb file:

#group:id:type:version
DUMMY_ARTIFACTS=['commons-collections:commons-collections:jar:3.1']

Now will go ahead and define our project and its repositories:

require 'artifacts'
repositories.remote << "http://www.ibiblio.org/maven2/"
repositories.local = "home/myuser/.m2/repository/"
desc "our dummy project"
define "Dummy" do
project.version = '1.0'
project.group = "Dummy"
manifest["Main-Class"] = "main.Main"# defining the jar's main class

desc "dummy single module"
define "dummy-module" do
# our tasks ..
end
end# the end of the dummy single module end
end

Buildr provides us with all the expected predefined tasks that you might expect:

desc 'compiling'
compile.with DUMMY_ARTIFACTS

desc 'testing'
test.with DUMMY_ARTIFACTS

desc 'packing and including classpath in the manifest'
package(:jar).with(:manifest=>
manifest.merge("Class-Path" =>
compile.classpath.collect{|dep|
" "+File.basename(dep.inspect)}.join("\n").strip))

Don't be alarmed by the package task code it's quite simple (remember its all Ruby code!), each and every task can be referenced by using task(:name) (compile is equivalent to task(:compile)).
We are also referencing the manifest property which is a predefined data member of our project and setting the Class-Path property with all the artifacts that our project depends upon (the spacing is required due to weird manifest requirements), in order to run one of these tasks (at the project's base dir):

bla@bla-desktop:~/workspaces/Dummy$ buildr taskname
#in order to build an Intellij project simply use the idea task.


Now will head on to deploying our application into a folder in with the following structure:


This is a custom task that iv cooked up:

desc 'deploying the application'
task :deploy_app => [:package,:build_app_folders,:build_bin] do
  FileUtils.cp(path_to('target')+'/'+File.basename(project.packages[0].inspect),lib_path)
  compile.classpath.each{|dep|FileUtils.cp(dep.inspect.gsub('Buildr::Artifact:','').strip,lib_path)}
 end

This task depends upon the package, build_app_folders and build_bin tasks, it basicly copies the project jar and all the artifacts the project depends upon into the application folder.
Take special notice to the usage of Buildr path_to method which returns the path to our module's target folder, since its a custom task will need to run it by specifying its full name:

bla@bla-desktop:~/workspaces/Dummy$
buildr Dummy:dummy-module:deploy_app

Here are build_app_folders and build_bin tasks on which deploy_app depends upon:

desc 'building the application folders'
task :build_app_folders do
unless File.exists?(app_path)
FileUtils.mkdir app_path ,:mode=>0777
end
...
end

desc 'building the application launcher'
task :build_bin do
launch_cmd='java -Dfile.encoding=UTF-8 -jar ./lib/'+File.basename(project.packages[0].inspect)
unless File.exists?(bin_path+'/'+'run.sh')
f = File.new(bin_path+'/'+'run.sh',"w+")
f.puts '#!/bin/bash'
f.puts 'cd ..'
f.puts launch_cmd
FileUtils.chmod 0755,bin_path+'/'+'run.sh'
end
end

Now all that is left is to extend the clean task so that it will remove the application folder, in order to extend an existing buildr task will use the enhance method:

 desc 'cleaning up our act'
  clean.enhance {
   if File.exists?(app_path)
   FileUtils.remove_dir app_path
  end
 }

Thats all, happy building to us all :)

No comments: