Just the other day I put a tweet out on Twitter where I exclaimed that I am not a fan of XML based build systems. Yes, I understand their purpose, and yes they were great in their time, and yes I still use them on almost a daily basis, but I really think that we have better ways that we could be doing this. The most common response from my tweet was “but was is the alternative?” And I thought, yeah, one of the most important things that I have learned in my lifetime is that you never complain about something unless you have a solution. And so I am here today to propose to you a solution…
The Problem With XML Based Build Systems
Before we get into this entire discussion, let me ask you a question… what is it that developers do better than anything else? Got your answer? Well, I know what mine is, and that answer is “write code”. And if that is the case, then why are we so intent on shoving things into giant xml files? Why can’t we write code? The usual argument is that XML based applications allow us to more easily make changes without recompiling, they allow for more flexibility, and they make it easier to write tooling since they are based on a format that everyone can parse. But personally, I don’t think that they offer much value, and instead are the project of the XML hysteria that swept through the programming world a few years ago. In fact, those of us who have been working with these XML based build tools for years probably forget just how ridiculous and obtuse they looked when we first saw them.
So while many people are quick to extol the virtues of XML, they often people forget to point out all of the things we lose when we start writing code in XML. For example, how about all of the tools that our industry has spent so many years crafting and refining. We lose our editors, debuggers, libraries, and all of the other tools that we use in order to get a few minor advantages that XML provides us. Those XML files limit us to the degree where we often find ourselves falling back to writing custom tasks in order to get around those limitations. Custom tasks which are not written in XML, but instead have to be written in code, since after all, the XML in these files is really just a hideously complex configuration system for the code which is actually running underneath.
Okay, so what are the advantages that we have with XML again? We can parse it easily? Okay, but how often do you write a parser for your build files? You just write your build file and pass it into NAnt. No parsing needs to be done by you. Doesn’t need to be compiled? Okay, that is a decent argument, since after all we don’t want to have to build our build script before we can build our application. In fact, if the build script is there to build your software, then would you need a build script for your build script? Hmmmm. Chicken, meet egg. But you see, this is merely a limitation of the short list of languages that most of us box ourselves into. Wouldn’t it be nice if we had a language that we didn’t need to compile? And we could execute it in the same way that NAnt or MSBuild works, by just pointing an executable at a file? Well, we have numerous languages like that, and in fact, we have several which run on the .NET platform.
If you have followed my blog for any period of time you will know that I am a fan of Ruby. I don’t program in it much professionally, but I am in constant awe of the simplicity and the beauty of the language. Within the Ruby community, there is just as much of a need for a build process as there is in our statically typed languages, even though they don’t really have a compile step. This is one of the biggest misconception about having a build in the .NET world, it isn’t just about compiling the software people! Phew, I feel better. In languages like C#, sure the build needs to compile the software, but after that you’ve got a lot more work to do. The build often needs to prepare a testing environment, run tests, copy files, package up deployments, and in some cases even push out deployments. The build is the rock upon which a repeatable process is built.
Okay, so we have Ruby, and in fact, IronRuby 1.0 just got release a little bit ago. Surely we can leverage IronRuby and some other Ruby goodness in order to create builds that are both easy and fun! Right? Sure we can!
Very Short Guide To Getting IronRuby On Your Machine
The first step in your journey is to get IronRuby on your system and working. Go to http://ironruby.net/ and click the download link. Then go and download the zip file for the correct version of IronRuby for your version of .NET. At the time of writing, there are two versions, one for .NET 2.0 and the other for .NET 4.0. Right now I am still working with the .NET 2.0 version, but the process should be identical.
Once you have downloaded the zip file, extract it to somewhere on your computer such as c:\tools\ironruby. The next thing that you need to do is to add that path to your IronRuby bin folder to your path. This can be done by going into your System Properties, clicking on the advanced tab and then clicking the “Environment Variables…” button. If you are using Windows 7, just type environment into the start menu and it’ll have an option for editing your system environment variables. Just find your path variable and add “c:\tools\ironruby\bin” to it. Obviously replace “c:\tools\ironruby” with wherever on your machine you saved it to.
Now that you have IronRuby on your machine, and the bin folder in your path, you should be able to open up a command prompt and type in the command “ir” and run the IronRuby interpreter. You’ll get a prompt for the IronRuby REPL (Read Execute Print Loop) and you can type in something like:
puts "CodeThinked.com is awesome!!"
In order to test it. Just type “exit” to quit. Congrats, you now have IronRuby up and running on your system! Wasn’t that easy? Now we are going to take a look at Ruby’s build tool, rake.
Rake is exactly the sort of build tool that we were talking about earlier. It doesn’t use any XML, it is build scripts that are written entirely in Ruby. Honestly, if you are writing in an interpreted language, then there is virtually no advantage to shoving this sort of task into XML when you can just write code to do it. IronRuby comes with its own version of rake, and in order to make sure that we have it up and running correctly, go ahead and type “rake” into the command prompt. After a few seconds you should get an error telling you that it could not find any rake files. So, let’s just start off by creating a project and a simple rake file.
The first thing I am going to do is create a new folder for my project. I’m going to create mine at c:\development\RakeTest, but you can create yours wherever you like. In that folder create a file called Rakefile.rb. Open that file in your favorite text editor, and put in the following source:
require 'rake' task :default => [:congratulate_me] desc "Tells you that you are awesome" task :congratulate_me do puts "Congrats, you have rake running. Wasn't that easy?" end
Now all you have to do is navigate to the folder where this file is located (in your command prompt), and run the “rake” command. It should write out to the screen “Congrats, you have rake running. Wasn’t that easy?”
And it sure was, wasn’t it? You now have a full build tool at your disposal. In the ruby code above, you can see the we are importing the rake library, setting the default task to our congratulate_me task, and then we define the task along with a description of the task. It looks almost like a simple nant script, but *gasp*, we are writing code.
Let’s see now what we would need to do in order to have some additional tasks and then chain those together, or start up our rake script from alternate tasks. So first, let’s go ahead and add two additional tasks to this script:
task :congratulate_team do puts "Congrats to the team!" end task :congratulate_everyone => [:congratulate_me,:congratulate_team] do puts "Phew, that was a lot of congrats" end
You see that things are getting a bit more complex. We have one more task which just writes out to the console, but we have another task with some sort of array next to the name. Well, that is the same functionality we get with NAnt when we tell it that a task depends on other tasks. This allows us to run additional tasks before a give task is run. Now if we update our Rakefile.rb so that the default task is congratulate_everyone, if we run it we will see that the other two tasks are executed before the congratulate_everyone task.
We don’t have to specify the task as the default task though, we can also run specific tasks by name. So if we want to execute the congratulate_team task, we simply need to execute “rake congratulate_team”.
At this point, we have emulated much of what the NAnt or MSBuild frameworks provide us. This is because when it really comes down to it, they just provide us with a set of tasks that we can string together in order to perform specific actions. We have tasks here, along with the ability to string them together and execute them in an organized fashion. We don’t need to anything special around configuration because we can just throw variables in the file or read variables from whatever data source we want. We can also include other ruby files, so we can easily bring in other settings, code, or tasks from different places. It seems that we just get so much of the stuff that XML build tools give us, but for free.
At this point though, there is just one problem. It is the plethora of tasks that both MSBuild and NAnt provide us that really gives us the power, right? If we don’t have tasks that build solutions, run tests, access source control systems, copy files, zip files, push to ftp, etc… then aren’t we losing quite a bit? I’m glad you asked.
Making Rake More .NET Friendly
Now you might be wondering, where are all of these tasks going to come from? Either I am going to have to write all of these tasks (as my colleague Chris Allport did), or there has to be a project out there which is already doing this. And guess what! There is! There is a cool little project out on GitHub, called Albacore (don’t go there to install it, I’ll show you how in a second), and it already has many of these tasks in there just waiting for you to use them. But first, let’s go ahead and get Albacore setup on your machine.
Since you already installed IronRuby, you have IronRuby’s version of gem, called igem. gem (and igem) are a tool which allows you to install packages onto your machine for use within your ruby projects. It is responsible for copying the source down to your machine along with dependencies so that you can be up and running in mere seconds. Since IronRub is already in your path, you should be able to just type “igem install albacore” in console, and away you go! Once it is finished, all you have to do is to add a single line into your rake file and you can immediately being using it:
So let’s take a look at a very simple build on a sample project. In this case I am just going to create a .NET console application called RakeTestApp. Then I can start off by adding a build config to my projects called something like “AutomatedRelease”. Then I can go into the properties on each project in the solution and set the output directory for the build to “../build_output”. This way I can simply invoke the msbuild task that albacore provides on the solution file in order to compile my solution.
The first task I am going to create is one that will invoke all of my other tasks in order:
task :full => [:clean,:build_release,:run_tests]
The second task I am going to create is one that will delete my build_output folder so that I can ensure a clean build:
task :clean do FileUtils.rm_rf 'build_output' end
As you can see here, we don’t need any special tasks to do this because we already have classes within the standard ruby library which do things like delete folders. Next I will create my first task which is based on an Albacore task, and that is the msbuild task which is going to compile the solution:
msbuild :build_release do |msb| msb.properties :configuration => :AutomatedRelease msb.targets :Build msb.solution = "RakeTestApp/RakeTestApp.sln" end
So instead of basing this task off of “task” you see that we are instead using “msbuild” and then we are naming it “build_release”. The msbuild task takes a single parameter which we use to set the configuration for the task. Here we are setting the build configuration to AutomatedRelease and we are setting the path of the solution to build. We also have some tests that we need to run, and so we have included nunit in the project. We can base a task off of the Albacore NUnit task which looks like this:
nunit :run_tests do |nunit| nunit.path_to_command = "tools/nunit/nunit-console.exe" nunit.assemblies "build_output/RakeTestAppTests.dll" end
Again, this task takes just one parameter which lets us set some parameters on the task. Here we simply need to tell the task what executable to use for nunit and a comma separated list of assemblies to run tests within.
All Together Now
Now that we have gone over each individual part, let’s look at it as a whole:
require 'rake' require 'albacore' task :default => [:full] task :full => [:clean,:build_release,:run_tests] task :clean do FileUtils.rm_rf 'build_output' end msbuild :build_release do |msb| msb.properties :configuration => :AutomatedRelease msb.targets :Build msb.solution = "RakeTestApp/RakeTestApp.sln" end nunit :run_tests do |nunit| nunit.path_to_command = "tools/nunit/nunit-console.exe" nunit.assemblies "build_output/RakeTestAppTests.dll" end
That is it! We have created a simple build using IronRuby, Rake, and Albacore. Now this is far from everything that Albacore can do. If I wanted to run ndepend, ncover, sftp, sql commands, zip directories, ssh, unzip, etc… I can easily leverage Albacore tasks in order to perform these actions.
Final Thoughts And Source
If you feel like I do and you are tired of being shackled to your typical XML based builds, then I would encourage you to give IronRuby, Rake, and Albacore a shot. Worse case scenario, you waste a little bit of time. Best case scenario, you are freed from a life of XML configuration stress. Hope you enjoy! Check out the link below to get the source.