While it’s not strictly required by this project, let’s look at what would be required to set up different build targets for different purposes. Currently our Compile.Assembly task just compiles whatever defaults are baked into the project. Let’s change that so we can specify whether we want Release or Debug builds.

One way - perhaps the obvious one - is to use a separate task for each kind of build:

Task Compile.Debug -Depends Requires.MSBuild, Requires.BuildDir { }
Task Compile.Release -Depends Requires.MSBuild, Requires.BuildDir { }

The problem with this approach is that it requires your other tasks to choose which kind of build they require as a prerequisite.

Why is this bad? Consider the Unit.Tests task we wrote last time; while we might normally run those against a debug build, do we want to preclude any possibility of running the tests against a release build? (There’s also the observation that repeating the build steps is a violation of the DRY principle.)

Instead, define a pair of tasks to specify what kind of build we want:

Task Release.Build {
    $script:buildType = "Release"
    Write-Host "Release build configured"
}

Task Debug.Build {
    $script:buildType = "Debug"
    Write-Host "Debug build configured"
}

As before, remember that we need the script: scope on these assignments so that the variable is visible outside the task. It’s easy to forget this wrinkle and then spend far too long trying to troubleshoot a build that’s not behaving. Ask me how I know …

In our Compile.Assembly target, we use $buildType to specify which build configuration is used for the build:

Task Compile.Assembly -Depends Requires.BuildType, Requires.MSBuild, Requires.BuildDir 
{
    exec { 
        & $msbuildExe /p:Configuration=$buildType
            /verbosity:minimal
            /fileLogger
            /flp:verbosity=detailed`;logfile=$buildDir\Niche.CommandLine.txt
            .\Niche.CommandLine.sln
    }
}

Note the new Requires.BuildType dependency - this is how we ensure that one or the other built type has been selected.

Task Require.BuildType {
    if ($buildType -eq $null) {
        throw "No build type specified"
    }

    Write-Host "$buildType build confirmed"
}

By throwing an error if $buildType is undefined, we give a clear diagnostic if something goes wrong.

This is a key point to consider when writing a build script - when it goes wrong, the person fixing it will have been interrupted; fixing the build will be a distraction from what they were trying to do - it’s important to make it easy for them to see the problem and do something about it.

Running a debug build with unit testing now requires this command line:

PS> .\packages\psake.4.6.0\tools\psake.ps1 build.ps1 -Task Debug.Build, Unit.Tests

Similarly, running a release build can be done with this:

PS> .\packages\psake.4.6.0\tools\psake.ps1 build.ps1 -Task Release.Build, Compile.Assembly

Those command lines aren’t terribly complicated, but the chances of remembering them perfectly for more than a few days has to be pretty low. Any other developer looking at the project for the first time would have to work to discover out how to build it - this isn’t really the experience we want to engineer.

After all, that developer who knows nothing about how the project is built might be us in 12 months time.

Next time, we’ll look at creating some convenience scripts to make the builds easier to trigger.

Comments

blog comments powered by Disqus