Buildr is a build tool for building Java projects designed to be a drop in replacement for Maven. It uses the same default directory structure as Maven with source files in src/main/java, tests in test/main/java, etc. And it downloads dependencies from maven repositories. So why would you want to learn yet another build tool when the two standard ones (ant and maven) are so dominating? For me it was because I was tired of writing xml files and wanted a regular programming language for specifying our builds. Buildr is built on top of Rake "the ruby make", and is a internal ruby dsl. Rake integrates nicely with ant and can call ant tasks, see this blog for an example of how to translate an existing ant file to Rake. To get a feeling for how buildr works we will walk through how to build a sample Java project with Buildr. We will use an EJB 2 application from
To use Buildr you need a buildfile. Do you want me to create one?: 1. From directory structure 2. Cancel ?Select 1 to let Buildr generate a buildfile for you. This will create a new file called buildfile in your current directory that looks something like this:
# Generated by Buildr 1.4.5, change to your liking # Version number for this release VERSION_NUMBER = "1.0.0" # Group identifier for your projects GROUP = "bank" COPYRIGHT = "" # Specify Maven 2.0 remote repositories here, like this: repositories.remote << "http://www.ibiblio.org/maven2/" desc "The Bank project" define "bank" do project.version = VERSION_NUMBER project.group = GROUP manifest["Implementation-Vendor"] = COPYRIGHT define "AppClient" do end define "BankEjb" do end define "BankWeb" do end endFrom the buildfile we can see that each of our sub-projects gets a 'define' block, except for the 'BankEar' project that does not contain any code, but we will manually add a define block for it later. Following Maven conventions Buildr expects the source code to be located in src/main/java, but the sample application does not follow this convention. So if we run buildr in the top directory, now with the generated buildfile in place, Buildr will not find anything to compile and will just exit saying everything went well. To tell Buildr that we have a non-default project layout we define a Layout before the 'define "bank"' line and specify the location of our source code, like so:
ejb_layout = Layout.new ejb_layout[:source, :main, :java] = 'src' We then tell Buildr to use this layout for the BankEjb project: define "BankeEjb", :layout=>ejb_layout package(:jar) endWe also added a line to tell Buildr to package the BankEjb project as a jar file. If we run buildr again we can see that it now finds our source code and tries to compile it. The problem is just that it is missing some necessary jar dependencies. We can either add these jar files as maven style dependencies with group-id, artifact-id and version, for example to add a compile time dependency to hamcrest-core 1.1 we can add the following line to our BankeEjb project:
compile.with 'org.hamcrest:hamcrest-core:jar:1.1'When Buildr sees this it will try to download the jar from the list of maven repositories specified in our buildfile. The other option is to add a reference to an existing jar file, or directory of jar files, on disk. Which is the approach we will use in this example:
compile.with Dir[path_to('lib/*.jar')]This will compile the BankEjb project with all jar files in the lib/ directory. If we run buildr again it now compiles the code without errors. Similar to maven Buildr has different tasks we can run, the default being 'build': You can also see the available tasks with 'buildr help:tasks', and the projects Buildr knows about with 'buildr help:projects'. So by just running Buildr the build task is run. To also have the BankEjb jar file created we have to run the package task with, 'buildr package'. The generated jar file will be placed in the 'target/main' directory. To continue we add a layout for the BankWeb project, specifying both a source/main/java directory as well as a source/main/webapp directory. We also add a package(:war) directive since we want Buildr to package this as a war file. If we run 'buildr clean package' we get some errors in the output telling us that the web project depends on the ejb project. So lets add it as a dependency. We do this like with did in the ejb project for external jar dependencies.
compile.with project('BankEjb')We can also see that the web project depends on the same jars as the ejb project, to add them we tell Buildr to use the compile dependencies from BankEjb when compiling BankWeb:
compile.with project('BankEjb'), project('BankEjb').compile.dependenciesWith this in place both projects compiles without errors. If we look in the src folder of the BankWeb project we will find three properties files that are not included in the generated war file. (You can check by unziping the generated war file located in BankWeb/target/main). To get the properties files packaged in the war we have to tell Buildr about them.
package(:war).include path_to(:source, :main, :java, '/**/*properties'), :path=>'WEB-INF/classes'The :path argument is the location inside the war file where we want the properties files copied to. path_to is a Buildr function that expands its argument to a full path. The process is similar for the AppClient project and the relevant part of the resulting buildfile looks like this.
define "AppClient", :layout=>ejb_layout do compile.with project('BankEjb'), project('BankEjb').compile.dependencies package(:jar).include path_to(:source, :main, :java, '/**/*properties'), :path=>'appclient' endFinally we want to add a project definition for the ear project and package the three previous projects in it.
define 'BankEar' package(:ear).add :jar=>project('AppLicent') package(:ear).add :ejb=>project('BankEjb') package(:ear).add :war=>project('BankWeb'), :context_root=>'bank' package(:ear).include path_to('conf/*'), :path=>'' endYou can read more about Buildr's ear package here. You can also see that we added a line to include the configuration files in the conf/ directory. The only thing left now is to set the display name for the application and to define some security roles.
package(:ear).display_name = 'JBossDukesBank' package(:ear).security_roles << {:name=>'BankAdmin'} package(:ear).security_roles << {:name=>'BankCustomer'}That's it. The resulting buildfile looks like this.
VERSION_NUMBER = "1.0.0" # Group identifier for your projects GROUP = "bank" COPYRIGHT = "" # Specify Maven 2.0 remote repositories here, like this: repositories.remote << "http://www.ibiblio.org/maven2/" ejb_layout = Layout.new ejb_layout[:source, :main, :java] = 'src' web_layout = Layout.new web_layout[:source, :main, :java] = 'src' web_layout[:source, :main, :webapp] = 'WebContent' desc "The Bank project" define "bank" do project.version = VERSION_NUMBER project.group = GROUP manifest["Implementation-Vendor"] = COPYRIGHT define "BankEar" do package(:ear).add :jar=>project('AppClient'), :path=>'' package(:ear).add :ejb=>project('BankEjb'), :path=>'' package(:ear).add :war=>project('BankWeb'), :path=>'', :context_root=>'bank' package(:ear).include(path_to('conf/*'), :path=>'') package(:ear).display_name = 'JBossDukesBank' package(:ear).security_roles << {:name=>'BankAdmin', :id=>"admin", :description=>"Administrator role"} package(:ear).security_roles << {:name=>'BankCustomer', :id=>"customer", :description=>"Customer role"} end define "AppClient", :layout=>ejb_layout do compile.with project('BankEjb'), project('BankEjb').compile.dependencies package(:jar).include path_to(:source, :main, :java, '/**/*properties'), :path=>'appclient' end define "BankEjb", :layout=>ejb_layout do compile.with Dir[path_to('lib/*.jar')] package(:jar) end define "BankWeb", :layout=>web_layout do compile.with project('BankEjb'), project('BankEjb').compile.dependencies package(:war).include path_to(:source, :main, :java, '/**/*properties'), :path=>'WEB-INF/classes' package(:war).with :libs=>path_to(:source, :main, :webapp, 'WEB-INF/lib/*') end endIf you want to deploy and run the application we first have to create the database schema. We can add a custom task for this in our buildfile. The necessary sql scripts to create the database and populate it with some sample data are located in the sql directory. We begin by adding a task to create the database using the hsql ScriptTool. Buildr tasks are defined with the Rake task keyword.
task :create_db do system "java -cp sql/hsqldb.jar org.hsqldb.util.ScriptTool -url jdbc:hsqldb:hsql: -database //localhost:1701 -script sql/hsql-create-table.sql" endsystem is a ruby function that runs an external program, in this case java. The create_db task creates the database. To populate it with data we need to run the hsql-insert.sql script with the hsql ScriptTool. Lets at the same time extract the system call to a ruby function.
task :create_db do run_hsql_script('sql/hsql-create-table.sql') end task :populate_db do run_hsql_script('sql/hsql-insert.sql') end def run_hsql_script(script) system "java -cp sql/hsqldb.jar org.hsqldb.util.ScriptTool -url jdbc:hsqldb:hsql: -database //localhost:1701 -script #{script}" endFor this to work we need to have a hsql database running on localhost listening on port 1701. JBoss can be configured to start a hsql database upon startup. The sample application we are using is tested and works with JBoss 4.0.5, download it here. To configure hsql modify the server/default/deploy/hsqldb-ds.xml file and make sure the lines where hsql is configured to run in server mode, and can be access over tcp, are uncommented (3 places needs to be changed). If we start JBoss with the bin/run.sh script we can now run the create_db and populate_db tasks:
> buildr bank:create_db bank_populate_dbFinally if we want to deploy the generated ear file we need to copy it to the JBoss deploy dir (
2 comments:
Good post - I fear buildr's days are numbers. I've fought with mvn compliant transitive dependency compatibility for a couple of days.
The is a lot right with buildr, projects can be specified simply and elegantly but without correct and up-to-date maven dependency management I think it is not a long term contender.
Things I like:
* Simple build scripts
* Elegant specification of project settings, including proxies via yaml
* Running on jRuby which means I can check the whole build system gems and all into source control, no more plugin not installed for the team head scratching and needing internet connections to build code.
I hope the build team have enjoyed a good summer rather than have lost momentum.
p
Thank you.
I totally agree with you about the benefits of Buildr, but unfortunately development of Buildr seems to be quite slow.
I guess you know about this thread on the mailing list that discuses transitive dependencies.
Post a Comment