Before we start…
“I’m about ready to get a new post up”. Heh. That’ll teach me to go on the record with my predictions. That post about arguing is still in with the drafts, but it’s stalled somehow. In the meantime, here’s something else, something exciting, something dramatic, something…nevermind, I can hear my wife snickering at me now.
The Nature of Ant and Builds
I bring this up now because I’m wading back into Ant territory, setting up a nightly build with NAnt. I’ll refer to Ant, because all of what I’m saying applies to both Ant and NAnt, and NAnt really falls under the Ant umbrella anyway. I can’t say I ever liked Ant – it’s better than nothing, but I never worked on a build script and said “whoa, that was cool!”
Why is Ant based on XML? It’s not a neat-o library that lets you whip together some honkin’ XML file to describe a build process. It’s an interpreter for a domain-specific language for builds. The Ant implementors just took the lazy way around writing an interpreter.
Let’s step back a sec. What’s a build process look like? Get the source files. Compile them into binaries. Run some unit tests on those binaries. Package them. Deploy them somewhere. Move around some config files, maybe some page templates. Restore, and maybe update, the database. Run some regression tests. Run some source code profilers. Gather up the results. Tell everyone what happened.
Imperative, or Declarative?
Each of those sentences is a command, an order. Wikipedia tells us it’s called the imperative mood, which “expresses commands, direct requests, and prohibitions.”; We’re telling the build engine, the builder, what to do. Let’s personify the builder, and call him Bob (Bob the Builder, HAHAHA!)—we’re ordering Bob around, like a mouthy foreman. We are not describing to Bob what should happen. You could look at it that way, of course, but that’s not really how we think about it. “Bob, the source files live on this server. The CVS login id and password are ‘build’ and ‘aloha’. There’s a nifty directory called ‘src’ on this build machine over here…”; Is that what runs through your head as you put together an Ant file? I didn’t think so. “Bob! Login to CVS! Copy the files from /src, down to /build/src! Compile into /bin! Zip /bin!”; That’s more like it.
So a build process is mostly imperative, not declarative. Then, shouldn’t we program a build process imperatively, not declaratively? Go ahead an scan the first bits of those wikipedia articles, they’re easy reading. Back? Ok. Now, I’m not saying do builds with shell scripts: I’ve spent enough time dealing with Windows batch scripts, I’ll take declarative XML over that any day. Why? Because Ant makes it easy to modularize your code! Sure, shell scripts can do that, sort of, but it’s a messy pain. There are hard problems, and there are problems that just seem hard…using the wrong tool for the job is category two. Ant makes it easy to wrap up a sequence of steps, and name it something meaningful, and that’s great! Holy cow, Ant has subroutines! Yeah, C has them, too, and while C has a lot of merits, I don’t think ease of maintenance is one of them. Why not use Java, or C#, or Ruby, or Python, or any OO language out there, as your build language?
Imagine this (I’m making this all up on the fly): you include the ant.jar. Make a subclass of
ant package has classes that let you do build-y things, like get source from source control (different subpackages for CVS, Subversion, VSS, PVCS, whatever), get all the files in a directory, call out to the compiler, move files around, FTP, run unit tests, send email, and whatever else you think would be useful. You can recreate your whole build process right in Java!
The Money Shot
And here’s why we really should do it this way. Here’s the raison d’être for this post, the main idea: your local builds just compile, and run unit tests; your QA builds need to do that, plus deploy to QA servers and run regression tests and code profilers; your Production builds need to do all that, minus the profilers, but with different servers…and you don’t run that HTTP Proxy in QA. That build.xml just became insufficient. Use basic OO ideas to customize your builds by environment. It’s trivial, we do this sort of thing all the time. Just not for builds.
Now, the funny part is that Ant is, I believe, already kind of structured this way. You can extend it with your own tasks, Java classes that subclass some
Task class in Ant. Why not just expose the engine itself? I’m sure the classes should be reorganized, since they weren’t designed with this kind of use in mind. But all those Tasks are already built, just sitting there, waiting for us to use them. There’s at least one precedent, too: Ruby has Rake, which I realized is exactly what I’m talking about. In fact, when I googled Rake for a good link, I came across this Martin Fowler article that I’d like to quote from:
All the build languages I've used (make, ant (Nant), and rake) use a dependency based style of computation rather than the usual imperative style. That leads us to think differently about how to program them.
Martin Fowler treats these ideas much better than I did here, and goes way beyond me, into some interesting territory.
A few other thoughts that come out of this:
- Extensions become trivial. Add your own Task subclass. There’s not even a separate “recognize my extension”; mechanism: you just include your class.
- Documentation is easy. What tasks are available? How do I use them? JavaDoc. This also gives us pop-up help, or Intellisense in .Net.
- Design of your build is simplified. How do I organize all these tasks, get those dependencies right, and all that? We’re used to thinking in imperative OO. Let’s bring that to bear on this problem.
A build process is like a lot of other imperative programming we do, so we might as well use an imperative language for it. While we’re at it, let’s pick a language we’re used to using and thinking in, and one that has some decent abstractions built-in. Bringing all that to bear on builds should make automating them a snap.