Dienstag, 14. Oktober 2014

Modular build scripts with NAnt

We are using NAnt for our build process. Since the product is large, this amounts to things like this:
  1. clean up the source tree => clean slate
  2. build all projects (~50 mins) => compiled libraries and executables
  3. run all tests (~2 hours)
  4. run benchmarks
  5. create installer (msi/bootstrapper)
  6. publish to a file share
This all amounts to several thousand lines of nant code. And because we know naught about modularizing things and decoupling things, this all becomes basically one big nant file. That is not maintainable.

We tried to modularize things a bit by putting common code into nant files, like this:
<include buildfile=".\installer.prop" />
which are just included by other nant files. So, we kind of reduced code duplication a bit.

Problems remain:
  • you don't know where your property has been set for the first time
  • if it has been reset to something else
Additionally, we have to deal with the problem that by using nant we have to use a declarative language to do loops, maintain file lists, have few insight in how properties are kept in memory.
It's even difficult to see the workflow of this whole monster.

I currently think, that for such large projects nant is the wrong tool. I'd like to try rake.
However, maybe we're just using nant the wrong way.

There is a way we should use more to modularize all those build scripts and make them independent:
  • separate nant files, each of which does one thing, and one thing well. Kinda like the Single Responsibility Principle. It's code, after all.
  • have integrating nant files which call other nant files. Those integrating nant files should not have logic.
  • when calling other nant files, do not inherit the properties of your current state (to inherit is actually the default).
In practice, this would look like this:
<project name="integrator" basedir=".">
    <property name="baseDir" value="${project::get-base-directory()}" />
    <target name="build">
        <!-- we integrate stupid targets here.
                they do not contain logic, they just call someone else -->
        <call target="cleanBuildfiles" />
        <call target="buildSoftware" />
        <call target="buildInstaller" />
        <call target="publishSoftware" />
    </target>
    <target name="buildSoftware">
        <!-- inheritall="false" prevents the called nant scripts from
                inheriting all what we already might have screwed up.
                It's a fresh start. An error in this script doesn't
                affect those other scripts. Modular :) -->
        <nant 
            buildfile="${baseDir}\myFramework\default.build" 
            target="buildAll"
            inheritall="false">
            <properties>
                <!-- if that nant script depends on a certain global
                        variable, inject it -->
                <property name="CCNetLabel" value="${CCNetLabel}" />
            </properties>
            </nant>
    </target>
    <target name="buildInstaller">
        <nant 
            buildfile="${baseDir}\installer\default.build"
            target="buildInstaller"
            inheritall="false" >
            <properties>
                <!-- or directly give an apt name 
                        to such input variables -->
                <property name="BuildNumber" value="${CCNetLabel}" />
            </properties>
            </nant>
    </target>
</project>
I can't yet say that this will make nant easier to use, but I already feel that I have a better overview of what is done where. Maybe it will help me keep order and find errors in the future.

Keine Kommentare:

Kommentar veröffentlichen