A blog about software development and other software related matters

Blog Archive

Showing posts with label Build. Show all posts
Showing posts with label Build. Show all posts

Tuesday, December 18, 2007

Buildr and groovyc

Buildr is a build tool that iv mentioned in previous posts, its really flexible and easy to mold custom functionality into it due to its Ruby heritage and its AntWrap utilization.
In this post im going to demonstrate how to use the groovyc ant task in order to intruduce groovy compilation into your build (which is a bit tricky).
Lets dive head first into the code:


SP=File::SEPARATOR

desc "Dummy project"
define "Dummy" do
#..
def
hashToPath(hash)hash[:group].gsub('.',SP)+SP+hash[:id]+SP+hash[:version]+SP+hash[:id]+'-'+hash[:version]+'.jar'
end

def reslove_artifacts_paths(array)
array.flatten.inject([]){|result,arti| result << repositories.local+'/'+hashToPath(artifact(arti).to_hash)}.join(File::PATH_SEPARATOR)
end

desc 'compiling'
compile.enhance([:groovyc])# this actully replaces the standart compile with the groovyc ant task

desc 'groovyc task'
task :groovyc do
classpath=reslove_artifacts_paths(RUNTIME)
ant('groovy') do |ant|
ant.taskdef :name=>'groovyc',:classname=>'org.codehaus.groovy.ant.Groovyc',:classpath=>reslove_artifacts_paths(GROOVY)
ant.groovyc(:srcdir=>path_to('src', 'main', 'java') , :destdir=>path_to('target','classes') , :classpath=>classpath){
ant.javac(:source=>"1.5" ,:target=>"1.5")
}
end
end
#..
end


The code above (if you havent guessed it until now) is the buildfile, lets disassemble it:

  • The groovyc task uses AntWrap in order to define and use the groovyc ant task, notice that we are using joint compilation which in essence suppresses the default compilation (since there wont be anything left to compile when its turn arrives) and makes our life easier (no need to mess with the ordering of the compilation steps).

  • The reslove_artifacts_paths method does what its name suggests and is required in order to make ant happy (i haven't found a nicer way of doing this) or else it wont find the required classes.

  • the GROOVY constant points to the groovy-all jar the RUNTIME points to all that is needed during compilation.



That all about there is to it.

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 :)