Towards an Oject-Oriented build
For the umptieth time, I am creating a build environment. And for the umptieth time, I am frustrated with the possibilities that Ant, or–in this case–NAnt–provide. To quote Alef: we’ve got ourselves a pretty decent multi-project build system, continuously integrating and all.
Right now, I’m trying to “port” this system to NAnt for our upcoming .NET-based projects. The system consists mostly of generic build files, which are imported into the project build.xml. Thus, all targets are defined in one place: a common build-common.xml file, and we don’t have to copy and paste anything. Using imports or includes is about the best you can do when you want to create a multi-project build system.
However, sometimes you want to override a target in a specific project. For instance, in some projects, I want to do a code-generation step before compiliation. In other projects I want to do some post-processing after compilation. You can’t do this generically in Ant, unless you use something like the following:
<project name=“sample” > <import file=“build-common.xml” /> <target name=“compile” depends=“pre-compile, common.compile, post-compile” /> <target name=“pre-compile”> <!– Do code-generation step –> </target> <target name=“post-compile”> <!– Do post-processing step here –> </target> </project>
Note that you cannot have two compile targets, since all targets and all properties live in a global namespace, though Ant 1.6 gave imported targets a separate namespace. Also note that if you use the code generation steps in any other project, I will want to move its definition to a global import/include file too.
Now let’s say that I also want to delete the generated code when I call the clean target. So, in addition to the above, we also have to add the following:
<target name=“clean” depends=“generated-clean, common.clean”> <target name=“generated-clean”> <!– Clean generated code here –> </target>
Now imagine that I want to use three different code-generation tasks in my project (which isn’t that weird: I have had projects where I did wsdl, commons attribute, and xdoclet generation). Before you know it, you will end up with targets that are called pre-compile-custom, and post-clean-common, and whatnot. Eeek.
Imagine…
Now imagine this: an object-oriented build, where you can use the object-oriented principles you know to solve these issues. Imagine that there are common classes and interfaces that perform build tasks. For instance, there is an interface that is implemented by all tasks that can be cleaned:
public interface Cleanable { void clean; }
For a project, you will probably want implement a basic Project interface and perhaps extend a DefaultProject. But in the project, you can decide where and how to override the default behavior, and use common build components to build you project. For instance, project will definitely also implements Cleanable, and its implementation will look something like:
public void clean() { for (int i=0; i < buildComponents.length; i++) { if (buildComponents[i] instanceof Cleanable) { Cleanable cleanable = (Cleanable)buildComponents[i]; cleanable.clean(); } } }
Of course, too make it easier to change your project build, you will probably write in a language like Groovy or IronPython. That way, you can write the real meat of the build in Java or C#, and the rest in a scripting language. Also, there will probably be some kind of (N)Ant-wrapper, so that we can its handy tasks.
Now I know that greater gods have toyed with this idea. I read of people that deserted and returned to Ant. I even looked at Maven, Ruby and Rake. However, none of these solutions give me the satisfaction of the solution I propose above.
To be continued, I am sure…