Real World builds in .NET

How do you build your Visual Studio solution, verify your coding guidelines and execute tests?
What steps do you take when adding a new project to your Visual Studio solution?

Living in the past

Let me summarize my past experience. I have tried several different approaches, all of them involved build scripts, and Visual Studio Project Templates or manual editing of *.csproj files. I don’t like any of the approaches. Why? I will show you some drawbacks of this kind of build definitions.

Build scripts:

  • you have to learn a scripting language
  • you try to solve problems which you would solve in your preferred .NET language with less effort

Visual Studio Project Templates:

  • making up-to-date versions available to all team members is a PITA (pain in the ass butt)
  • update your templates and you still have to update all previously existing *.csproj files manually
  • if you change your build process (e.g. enable StyleCop) you have to release and distribute a new version of your templates

Manual editing:

  • enough said

Imagine the unimaginable

How much would you like the following scenarios?

Build your solution (including coding guidelines and tests)

Start Windows PowerShell in your source directory and type

PS> msbuild YourSolution.sln /p:Configuration=Release /target:Rebuild

Hit <Enter>. Built!
Well, you could even put that one-liner in an integrate.ps1 and execute that script instead.

Add a project to your solution

Add a new project of the desired type to your solution.
Open the NuGet Package Manager Console and type

PM> Install-Package Development

Hit <Enter>. Done!

Change your build process

Open the NuGet Package Manager Console and type

PM> Update-Package Development

Hit <Enter>. You’re up-to-date!

What do we need?

Before getting started we should give some thought about our initial build process.

We have three different types of projects (csproj): production code, facts (unit tests) and specifications (BDD-style tests). I like the strict separation into these three types: It’s easy to know where a piece of code should go to, and it allows to treat the build for them differently with ease.

First, let’s see what’s common for all three project types. We want to have all the compiler warnings and the StyleCop warnings (StyleCop.MSBuild) checked. We also use the Appccelerate.CheckHintPathTask to ensure the csproj references our 3rd party libraries from the correct locations (e.g. packages folder) so that Visual Studio takes the ones we want. These checks should be warnings in Debug mode and errors in Release mode.
To have automatic versioning we use the Appccelerate.VersionTask (only works for git repositories).

To summarize, we have the following requirements for all our project types:

Compiler StyleCop CheckHintPath Versioning
Debug Warn Warn Warn Yes
Release Error Error Error Yes

Table 1: Requirements for all project types

Next, let’s see what’s specific for each project type. Every project type has its own Settings.stylecop file. The facts and specifications project types should run their respective tests in Release builds. That’s it from the requirements.

How do we do it?

My good friend Urs Enzler and I came up with the idea of having our build defined in a NuGet package. This way we have all the benefits of NuGet to manage the package including installing, updating, dependency management, and availability for the whole team on all supported types of feeds.
We put everything that’s valid for all project types into a base package which we call Development. The specifics go into the Development.ProductionCode, Development.Facts and Development.Specs packages respectively.

Image 1: Development packages dependencies
Image 1: Development packages dependencies

Creating the packages

Let’s start with the Development package. I will show you the folder and files structure and then explain and show every file in turn.

Image 2: Folder structure of Development package
Image 2: Folder structure of Development package

The Development.nuspec file is the specification of your NuGet package (see official reference for details). It defines metadata, dependencies, etc.

<?xml version="1.0"?>
<package xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
    <id>Development</id>
    <version>0.0.0</version>
    <authors>Philipp Dolder</authors>
    <owners>Philipp Dolder</owners>
    <copyright>Copyright 2014</copyright>
    <developmentDependency>true</developmentDependency>
    <dependencies>
      <dependency id="StyleCop.MSBuild" version="[4.7.48.2,5)" />
      <dependency id="Appccelerate.VersionTask" />
      <dependency id="Appccelerate.CheckHintPathTask" />
    </dependencies>	
  </metadata>
</package>

Most of it is self-explanatory. there are 2 important parts to the nuspec file though.

    <developmentDependency>true</developmentDependency>

This prevents NuGet from adding the Development package as a dependency if the project depending on it goes into a NuGet package itself.

    <dependencies>
      <dependency id="StyleCop.MSBuild" version="[4.7.48.2,5)" />
      <dependency id="Appccelerate.VersionTask" />
      <dependency id="Appccelerate.CheckHintPathTask" />
    </dependencies>

Add the dependencies mentioned in “What do we need?”

Development.targets defines the build process that is referenced from the csproj the package is installed to.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings>
  </PropertyGroup>
</Project>

In Release builds we want compiler warnings and StyleCop warnings to be treated as errors. The Appccelerate.CheckHintPathTask also uses the value of TreatWarningsAsErrors to determine the severity of hint path errors.

We define the general assembly attributes including copyright, company and product in GlobalAssemblyInfo.cs. Include all attributes you want to share in all your assemblies.

using System.Reflection;
using System.Runtime.InteropServices;

[assembly: AssemblyCompany("Philipp Dolder")]
[assembly: AssemblyProduct("RoadShow Service")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

Keep in mind: All the attributes that are declared in the GlobalAssemblyInfo.cs have to be removed from the AssemblyInfo.cs in the projects.

In the NuGet.config we can add other feeds than the official NuGet feed. This is only necessary if we publish our Development packages or other NuGet packages to another feed source. For private NuGet packages I prefer a MyGet private feed. It’s simple and you get symbolsource.org support as well. If you rely on the official NuGet feed only, you just can omit the NuGet.config file.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="Roadshow on MyGet" value="https://www.myget.org/F/code-to-cd-roadshow/" />
  </packageSources>
</configuration>

That’s it for the Development base package. Let’s quickly go through the other packages.

Development.ProductionCode

Image 3: Folder structure of Development.ProductionCode package
Image 3: Folder structure of Development.ProductionCode package

We don’t need additional build steps.
Define the Settings.Stylecop file as you like. I won’t list the file contents here.
In the Development.ProductionCode.nuspec we only reference the Development package.

<?xml version="1.0"?>
<package xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
    <id>Development.ProductionCode</id>
    <version>0.0.0</version>
    <authors>Philipp Dolder</authors>
    <owners>Philipp Dolder</owners>
    <copyright>Copyright 2014</copyright>
    <developmentDependency>true</developmentDependency>
    <dependencies>
      <dependency id="Development" />
    </dependencies>	
  </metadata>
</package>

Development.Facts

Image 4: Folder structure of Development.Facts package
Image 4: Folder structure of Development.Facts package

In the Development.Facts.targets we reference the xUnit.MSBuild package to execute the facts, but only in Release builds. We are working with JetBrains ReSharper and the xUnit ReSharper runner to run the tests when developing in Visual Studio, so we don’t want to run them on every Debug build.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Condition="'$(Configuration)' == 'Release'">
    <RunXunitTests>true</RunXunitTests>
  </PropertyGroup>
</Project>

In the Development.Facts.nuspec we add the dependencies to the testing framework and other packages we need in all facts assemblies.

<?xml version="1.0"?>
<package xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
    <id>Development.Facts</id>
    <version>0.0.0</version>
    <authors>Philipp Dolder</authors>
    <owners>Philipp Dolder</owners>
    <copyright>Copyright 2014</copyright>
    <developmentDependency>true</developmentDependency>
    <dependencies>
      <dependency id="Development" />
      <dependency id="xUnit.Extensions" />
      <dependency id="xUnit.MSBuild" />
      <dependency id="FakeItEasy" />
      <dependency id="FluentAssertions" />
    </dependencies>	
  </metadata>
</package>

Development.Specs

Image 5: Folder structure of Development.Specs package
Image 5: Folder structure of Development.Specs package

The Development.Specs.targets is identical to the Development.Facts.targets as we use xBehave for our specifications which in turn uses xUnit to execute the tests.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Condition="'$(Configuration)' == 'Release'">
    <RunXunitTests>true</RunXunitTests>
  </PropertyGroup>
</Project>

The Development.Specs.nuspec is listed below. Remember that we don’t need to add indirect dependencies in the nuspec file.

<?xml version="1.0"?>
<package xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
    <id>Development.Specs</id>
    <version>0.0.0</version>
    <authors>Philipp Dolder</authors>
    <owners>Philipp Dolder</owners>
    <copyright>Copyright 2014</copyright>
    <developmentDependency>true</developmentDependency>
    <dependencies>
      <dependency id="Development" />
      <dependency id="FluentAssertions" />
      <dependency id="FakeItEasy" />
      <dependency id="Xbehave" />
      <dependency id="xUnit.MSBuild" />
    </dependencies>	
  </metadata>
</package>

That’s it from the package definitions. We just need to build the packages and publish them to a NuGet feed, which can be the official NuGet, MyGet, your TeamCity server feed, a file location etc.
Make sure nuget.exe is in your path, or prefix the nuget command with its absolute folder.
Open PowerShell in the the parent folder where you have your Development packages and execute the following commands

PS> nuget pack .\Development\Development.nuspec -Version 1.0.0
PS> nuget pack .\Development.ProductionCode\Development.ProductionCode.nuspec -Version 1.0.0
PS> nuget pack .\Development.Facts\Development.Facts.nuspec -Version 1.0.0
PS> nuget pack .\Development.Specs\Development.Specs.nuspec -Version 1.0.0

You can ignore the warnings indicating that you should add the version to the dependencies. We don’t add version restrictions for convenience. Without version restrictions we don’t need to change the Development packages when we update our testing libraries; FluentAssertions, FakeItEasy, xBehave and xUnit in our case.
Now, publish the four packages to your desired feed. Please read the official documentation if you want to learn more about it.

Using the packages

To install the package to, say, a production code assembly go to NuGet Package Manager Console in Visual Studio, select your package source and type:

PM> Install-Package Development.ProductionCode YourProject

Do this for the other project types respectively whenever adding a new project to your solution. Updating your development packages is as simple as

PM> Update-Package Development.ProductionCode

to update it for all the production code projects in your Visual Studio solution.

Building the solution

Now, building the solution is a one-liner in PowerShell which is also very easy to add as a build step in TeamCity (or in your favorite CI server).

PS> msbuild YourSolution.sln /p:Configuration=Release /target:Rebuild

Conclusion

Initially creating the packages is a bit of work, but it pays off when adding projects to your solution or changing the build process. It makes your live easier in the long run and saves headaches.

You can find the sources for my Development packages on bitbucket.

If I can give you some advice:

  • have a separate repository for the Development packages sources (nuspec, targets etc)
  • create a build configuration on your CI server to automatically create the Development packages and publish them to your feed
  • make sure msbuild.exe and nuget.exe are in your path environment variable
  • for convenience, create a ps1 file containing the one-liner to build your solution
  • prefix the development packages with your company or project name
  • if you have more requirements for your build create your own MSBuild task or find a suitable one

About the author

Philipp Dolder

1 comment

By Philipp Dolder

Recent Posts