DevOps Digest 306: Building Scripts, aka Automating the Debug Build (Part Two)

Dec 20, 2016

DevOps Digest Hero Image

Now that we have a fully automated debug build executing for every new commit, it’s time we add more powerful features to our continuous integration process, and key to doing so is abstracting those features through build scripts.

Where Should You Store Your Knowledge?

Our Jenkins project isn’t particularly complicated so far, but it isn’t hard to imagine how much more complicated the pipeline script would grow to accommodate unit tests, code coverage analysis, static analysis, and so forth. It’s better to embed this kind of knowledge in the product for several reasons:

  • Build scripts make it trivially simple to exercise such functionality, keeping your Jenkins projects simple.
  • Simple projects in Jenkins lower the burden on DevOps staff and make it easier to switch CI systems if needed.
  • Build scripts make it easier for less experienced developers to use, learn, and assist with build automation.
  • It’s a good extension of time-honored, object-oriented principles of encapsulation and information hiding.

What Tools Should You Use to Capture It?

The lowest-common denominator for our sample project, an ASP.NET MVC web application written in C#, is the Microsoft Visual Studio project system. Visual Studio project files contain XML-structured information that describe overall project properties, the items that comprise the project, build targets, and so forth.

Those project files are “understood” by the MSBuild utility, and their contents are expected to conform to a particular schema. As long as you stick to it, you can extend project files however you like. By defining new targets, new properties, and supplying your own commands to execute, you can leverage the Microsoft build system to do a great deal.

It’s a good idea to keep your tool set as small as possible, and on this point it’s worth adding another tool to our kit. More specifically , we’re going to add NAnt, a free, open-source build tool for the .NET platform. NAnt is no longer actively developed, but here are a few reasons why preferring it to the MSBuild system (or other tools) still makes sense:

  • NAnt is effectively the .NET version of Apache Ant, one of the more popular build systems available.
  • NAnt offers a more powerful library of built-in tasks, types, functions, and filters than the MSBuild system.
  • NAnt offers greater flexibility and ease-of-use than the MSBuild system through its extension mechanisms.

In short, NAnt remains a great choice today, and we’ve never found a DevOps-pipeline task with which it couldn’t assist. It provides a relatively low burden of information and learning curve, unlike mastering all the intricacies of the MSBuild system, making it relatively easy to find staff who know its ways. Further, its extension mechanism requires nothing more than writing new classes in C#, so the same developers who work our sample project could easily build custom NAnt extensions.

Creating a Build Script

Having made our choice, let’s get NAnt installed and ready.

  1. Download the latest version of NAnt (v0.92 available here) and install it by unzipping the folder contained in the archive to your 32-bit program files directory (“C:\Program Files (x86)” by default).
  2. Update your system path to include the resulting “bin” folder location (“C:\Program Files (x86)\nant-0.92\bin” by default), so that the utility will be available from any command line, under any account.
  3. Download the corresponding version of the NAntContrib tools (v0.92 available here) and put its folder into the 32-bit program directory as well. No need to update the path for this; it’s a set of useful extensions for NAnt.

 

That gets the basic NAnt tool and the NAntContrib extension in place for our use. The latter isn’t strictly necessary, but it does make it easier to execute the Microsoft build tools and do a variety of other nifty things. Its freely available source code also serves as an example for those wanting to learn to create their own NAnt extensions.

For the actual script, all we currently need are clean and build targets. We don’t have the luxury of explaining the full details of NAnt here, but anyone familiar with Ant (or build systems and the XML format in general) should be able to get the gist of the following.

We’ve defined a set of properties near the top, which are then used in the build targets below. You’ll find a total of three targets: the aforementioned clean and build as well as the default target for the file. We find it’s good practice to specify a default so that the intent is always clear when the user doesn’t specify targets on the command line. Notice also how NAntContrib’s “msbuild” task is leveraged to make our lives easier.

<?xml version="1.0"?>

<!--

    This NAnt build file provides the following build targets:


        Build                   Builds the solution

        Clean                   Cleans the solution


    By default the build file will do a basic clean, build, run code coverage

    analysis using unit tests, and generate a code coverage report. To execute a 

    particular target, invoke NAnt on this file with the target as an argument. 

    For example, the following will do a simple build:


        NAnt -buildfile:DevOpsSample.build Build


    More than one target may be specified. To do a complete clean and build, one 

    might use the following command line:


        NAnt -buildfile:DevOpsSample.build Clean Build


    For more information on NAnt, check out the online documentation for the latest

    release at http://nant.sourceforge.net/release/latest/help/

-->

<project name="DevOpsSample" default="DefaultTarget" basedir=".">

    <!-- 

        These variables should be tailored to the particular build machine 


        NOTE WELL: Relative paths are relative to the NAnt build file.

    -->

    <property name="msbuild.configuration" value="Debug" />

    <property name="msbuild.platform" value="Any CPU" />

    <property name="msbuild.target.build" value="Build" />

    <property name="msbuild.target.clean" value="Clean" />

    <property name="nantcontrib.path" value="C:/Program Files (x86)/nantcontrib-0.92" />

    <property name="nuget.packages.path" value="../Solutions/packages" />

    <property name="nuget.command" value="nuget.exe" />

    <property name="main.solution.filename" value="../Solutions/DevOpsSample.sln" />


    <description>Provides build targets for the main DevOpsSample solution</description>


    <!-- Cleaning targets -->


    <target name="Clean" description="Cleans the solution">

        <!-- Includes NAntContrib tasks for NAnt to use, required for the msbuild task -->

        <loadtasks assembly="${nantcontrib.path}/bin/NAnt.Contrib.Tasks.dll" />

        <msbuild project="${main.solution.filename}" target="${msbuild.target.clean}">

            <property name="Configuration" value="${msbuild.configuration}" />

            <property name="Platform" value="${msbuild.platform}" />

        </msbuild>

    </target>


    <!-- Build related targets -->


    <target name="Build" description="Builds the solution">

        <!-- Includes NAntContrib tasks for NAnt to use, required for the msbuild task -->

        <loadtasks assembly="${nantcontrib.path}/bin/NAnt.Contrib.Tasks.dll" />

        <msbuild project="${main.solution.filename}" target="${msbuild.target.build}">

            <property name="Configuration" value="${msbuild.configuration}" />

            <property name="Platform" value="${msbuild.platform}" />

        </msbuild>

    </target>


    <!-- The default target for the build file -->


    <target name="DefaultTarget" description="Builds" depends="Build" />


</project> 

I’ve added that script to the project’s “Build” folder under the name “DevOpsSample.build”. Invoke NAnt subsequently with the following command line:

nant -buildfile:Build\DevOpsSample.build Clean Build

The “buildfile” argument tells the tool which build file to use, while the following arguments specify that both the clean and build targets should be executed, which in our case builds the project successfully. Now, all that remains is to update Jenkins to take advantage of our new script. Doing this is as simple as changing the pipeline script:

DD306_Build Scripts in Helix and Jenkins

Forcing a quick manual build shows that the project now has its own working build script, which we’ll easily be able to extend to add other capabilities going forward. As we progress, the value in embedding these procedures in your project should become clearer.

DevOps Digest is taking a short break until Tuesday, January 10, but stay tuned for Issue 307, where we’ll explore unit tests. Until then, we wish you the happiest of holidays!

You Ask, We Answer

As previously mentioned, this is your roadmap to creating a successful DevOps pipeline. Don’t understand something? Just ask. Need to dive a little deeper? Send an email to info@perforce.com with your questions. Then, stay tuned for a live Q&A webinar at the end of this series.

Get DevOps Digest Sent to Your Inbox

You don’t need to remember to check back with us each week. Instead, get the digest delivered directly to your inbox. Subscribe to our 25-week DevOps Digest and we’ll get you where you need to go, one email at a time.

See Perforce Helix in Action!

Join us for a live demo every other Tuesday and see the best of Perforce Helix in 20 minutes. Save your spot!

Posted In: 
jwilliston's picture
About the author:

John Williston is a Product Marketing Manager at Perforce Software.

See all posts by