Build File Pattern: Path Prefixes

Problem: You have paths in your NAnt build file that assume a certain file structure, and you want to be able to change these assumptions and have your build files still work.

Context: This happens a lot when trying to relocate the build file, call the build file from another build file, or change the location of a target's input or output.

Solution: Ensure that all of your path strings start with a property value (${someproperty}) and collect all property statements in one location of the file. Now when you need to change assumptions, you can be confident that you have accounted for them all by changing a few properties.

I don't know if this a pattern, or just a good practice, but I think of it as a pattern, because I rarely start out consciously to adhere to this rule, but rather end up at this pattern as I factor out redundant path strings.

Example:

I work with NAnt a lot (and Ant a lot before that) and one thing that is always a PITA is having to maintain any but the most trivial build file when there are lot's of unprefixed paths in the file.

Duplication of any sort in a build file (just as in any code) can be a source of problems, but for paths its particularly nasty because it can be devilishly difficult to get them right if say for example you want to change the directory tree or (god forbid) change the directory the build file lives in.

An unprefixed path is any path that does not have a property as its base. These are problematic whether relative or absolute. For example, say you have 20 csc (c# compiler) tasks that each point into their own subdirectories, and get their references from a common lib directory, and output to a common bin. Each one will look something like this:

<csc target="library"  output="bin">
    <sources>
        <includes name="src/ProjectOne/*.cs" />
    </sources>
    <references>
        <includes name="lib/foo.dll" />
    </references>
</csc>

Now each target will have a different path for the source includes, and each will have a common output value, and each will have a different includes line (although all presumably go to lib somethingorother).

src, bin and lib are all relative to the directory that is the basedir for the script (which is usually, but doesn't have to be the one it lives in).

Compare that target to this one:

<csc target="library"  output="${bin.dir}">
    <sources>
        <includes name="${src.dir}/ProjectOne/*.cs" />
    </sources>
    <references>
        <includes name="${lib.dir}/foo.dll" />
    </references>
</csc>

Now each of our paths are prefixed by a property substitution. The property definitions are then collected at the top of the build file, and one can ensure that everything will work properly by modifying the properties. Because the redundancy is factored out, any changes that must occur in the individual tasks should be specific to that task (and only that task).

I don't generally introduce a property until I have some duplication.

Even really small duplication's can be a source of problems (see NAntContrib's build file and its appending "bin" to the nant.dir property