Calling DEVENV From NAnt

NAnt includes support for a number of different ways to build .NET code -- which isn't surprising since that is the primary purpose of the tool. Last time I checked, there were at least these options (for C# code):

  • the csc task (which wraps calls to the c# compiler)
  • the solution task (which parses an existing visual studio solution)
  • Slingshot (which generates a nant build file from a VS solution)
  • Calling Visual studio directly via the command line

Apart from Slingshot (which I never found that useful), I've used them all on actual projects. In general, I prefer 'solution' for its simplicity (my first choice when possible), and the 'csc' task for control (e.g. when you need more control over what files are included, what properties are set, or where stuff is put than can easily be done directly via a solution file), but what about calling Visual Studio directly?

Some people say you shouldn't (performance, and having to have Visual Studio on your build machine are the two most common objections) but sometimes its the best option (particularly if those two things are not a problem in your environment).

The most common case, is when you reach the limitations (or bugs) of the solution task. Since it parses the project and solution files, there are some edge cases (depending on what version you're using) that can break it or are simply not supported.

Before rewriting the build script (i.e. using the csc task) and maintaining both a build script and the solution file (which I don't find as onerous as some make it out to be), giving Visual Studio a call is a valid option. But how?

Well, the Visual Studio IDE is housed in an executable called DEVENV.EXE (there is also a DEVENV.COM). The command line interface for this tool allows it to be used for builds without opening up the graphical interface. Using this interface from NAnt is as simple as an exec task call -- the catch is that you either need DEVENV in your path, or you need the path to that file in your build script -- both require additional cofiguration of each computer that runs the script -- this can be a pain, when there are multiple developers (or multiple build machines!) using the same script, and painful things don't get used as much as painless ones. So, for this option to be useful, it should be as easy to add to the build script as possible.

One way is to use the VSVARS32.BAT file included in the Visual Studio installation. It sets some environment variables that -- among other things -- allow you to access DEVENV (it puts it in your path too).

Of course now you need the path to this file -- but that is the value of an existing environment variable (created when you installed Visual Studio) called "VS71COMNTOOLS". Once VSVARS32 is called, it sets an environment variable ("DevEnvDir") that can be used to prefix the path in the exec call to DEVENV. However, to use that environment variable in your nant script, vsvars32 must be called before calling nant (so that you can see it using sysinfo or the environment::get-variable function). This can be accomplished by calling nant from a batch file that includes the following line (before calling nant proper):

 CALL "%VS71COMNTOOLS%"\vsvars32.bat

Kind of a pain really, all those environment variables and batch files. However, there is another (and IMO better) option:

<target name="call_devenv">
    <readregistry property="__ide.dir" 
        key="SOFTWARE\Microsoft\VisualStudio\7.1\InstallDir" 
        hive="LocalMachine" />

    <exec
        program="${__ide.dir}\devenv.exe "
        commandline="${vsi.solution.file}" />

</target>

By using the readregistry task to get the path out of the Visual Studio registry settings, any programmer's workstation (or build machine) that has Visual Studio installed will be able to run the build script (and you needn't worry about coordinatng the installation location. The double underscore prefix ("__ide.dir") is just a convention to make it very unlikely that there will be a property name collision with some other part of your build script (remember nant is one big global namespace). Finally, "vsi.solution.file" is a property that would be set to your solution file name before calling the target (the vsi prefix is a convention I use when the target is in an include file -- in this case from one called "visual_studio.include" -- again to avoid name collisions).

This is as easy to maintain as the solution task-based approach, but can handle any solution file that visual studio can handle (since that's what its using to build it). And while yes, you need VS on the machine to use it, where that's not a problem for you, this can be a real headache reliever.

So, while each NAnt build approach has its uses, if the solution task (which is certainly my preferred choice when feasible) isn't working for you, see if you can't leverage DEVENV for your compilation needs, before turning to csc.