Friday, January 13, 2012

Learning MSBuild

Google didn't seem to be quite as helpful as it usually is this time round. I was in the position where I needed to learn MSBuild as fast as possible however most of the information I was finding was pointing to the Microsoft site. Now don't get me wrong, there is a heap of information here, but it was a bit of an overload of info.

Given a solution or project file that you've already created it is a simple matter to call MSBuild against that particular file, but what I really wanted to do is have a separate build file that would do the lot.

So how did I go about picking it up?

I started with a REALLY simple app (can't stress enough how simple it was) and added an XML file to  the solution that would act as my build file. On a side note, I later changed the file extension to .proj and the file maintained intelisense.

I then copy/pasted the following lines into the file:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>

The following is an overview of my observations of how I got my app to build and execute tests.

The first thing I played with was indicating which solution to build. This is what I ended up with:
<PropertyGroup>
    <SolutionDir>..</SolutionDir>
    <SpecificSolutionToBuild></SpecificSolutionToBuild>
</PropertyGroup>
<Choose>
    <When Condition="'$(SpecificSolutionToBuild)' == ''">
        <ItemGroup>
            <SolutionsToBuild Include="$(SolutionDir)\TeamCityTestApp1.sln" />
            <SolutionsToBuild Include="$(SolutionDir)\TeamCityTestApp1Alternate.sln" />
        </ItemGroup>
    </When>
    <Otherwise>
        <ItemGroup>
            <SolutionsToBuild Include="$(SolutionDir)\$(SpecificSolutionToBuild)" />
        </ItemGroup>
    </Otherwise>
</Choose>
<Target Name="Compile">
    <MSBuild Projects="@(SolutionsToBuild)" Properties="OutputPath=$(OutputDir);Configuration=Release;DebugType=none;" />
</Target>
The above code introduces the Choose element and the Properties attribute in the MSBuild element.
Next I wanted to set the build output directory. Here's what I came up with:
<ItemGroup>
    <FilesInOutputDir Include="$(outputDir)\*"></FilesInOutputDir>
</ItemGroup>
<Target Name="CleanOutputPath">
    <Delete Files="@(FilesInOutputDir)" />
</Target>
 
<Target Name="Compile" DependsOnTargets="CleanOutputPath">
    <MSBuild Projects="@(SolutionsToBuild)" Properties="OutputPath=$(outputDir);Configuration=Release;DebugType=none;" />
</Target>
It was while I was figuring this out that it dawned on me that if I need to refer to multiple items then I always need to create an ItemGroup.

The next thing that was essential to figured out was getting unit tests executed. This was my first introduction to the use of custom build tasks.
For this I downloaded MSBuild.Community.Tasks.msi from msbuildtasks.tigris.org.
To get this working all you need to do is import the MSBuild.Community.Tasks.Targets at the top of your build file like so:
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
 and to call:

<Target Name="RunUnitTests" DependsOnTargets="Compile">
    <ItemGroup>
        <AssembliesToTest Include="$(OutputDir)\TeamCityTestApp1.Tests.dll" />
    </ItemGroup>
    <NUnit ToolPath="$(NUnitToolsDir)"
            DisableShadowCopy="true"
            Assemblies="@(AssembliesToTest)"
            OutputXmlFile="$(BuildDir)\UnitTestResults.xml"/>
</Target>
In this example I've created an item group to encompass which assemblies to test. In this situation this isn't really required as I'm only testing one dll, however if I were testing multiple dll's then this would be the approach to use.


I know a lot of this information was fairly basic, but thought it might be useful for those who haven't worked with msbuild before.

Enjoy!