Imagine a programming language with one namespace. One where all variables (including parameters) are one big happy ball of interacting goo and functions are only usable by side-effect. Add a cumbersome verbose syntax, make it relatively slow, and provide only one data type. Remove any sort of debugger or trace mechanism and make what errors you do generate cryptic and misleading. Finally, top it all off with a standard programming idiom that encourages long dependency chains and reusing functions with different side-effects.
The result? Writing large build scripts in NAnt.
As Martin talks about, at some point build scripts for a code base of any size shift from being primarily declarative descriptions of what to build to ever larger procedural descriptions of how to build it.
Its not the NAnt guys fault -- Ant suffers from similar limitations -- Its just that XML is truly awful for this sort of procedural work. In fact, as both projects have added more and more features, they've continued to push back the limit after which the complexity just becomes too much. Unfortunately, all this does in the end is make the resultant mess that much more difficult to clean up.
Good standards, obsessive removal of duplication (tip: keep paths that aren't prefixed with a root property to a bare minimum) and an even more obsessive (and often conflicting) attention to reduction of cross-file calls and overly-clever reuse of workflows can extend the boundary, but in the end I think there is just a limit to what you can do with these XML-based MAKE tools. Sooner or later, you get wedged in tight, and there's nowhere to go (but into the next hour of debugging).
I have been up against this limit on more than one occasion -- including right now (I am writing this on a couple hours of sleep after working through a never-ending thread of dependency woes), and I am convinced that while NAnt is a great tool, its not necessarily the right tool for large build projects.
But I am going to use some of this pain to my advantage. I am going to take this big hairy code base (8300 files, 3300 tests and approximately 15 build scripts -- some over 700 lines long) and see if building it using RAKE is easier to maintain.
I suspect it will be, because in the end build scripts are just code -- with all of the design and architecture issues faced by other systems. Building systems in a language that has the limitations listed above just doesn't scale. Such considerations are why (among other things) OO languages have classes, and functional languages eschew side-effects in functions.
Seeing as how RAKE is just Ruby (which has these and other modern PL constructs), I fully expect to be able to push the wedge point back even further -- perhaps far enough to handle the larger, interdependant build scenarios that our clients seem to increasingly find themselves trying to manage.
If I get the chance, I'll try to write it up as an article (which I want to do more of now that I have some time).