Showing posts with label MSBuild. Show all posts
Showing posts with label MSBuild. Show all posts

Tuesday, 29 November 2011

Creating Zip Files with IronPython

I’ve been working on automating some of the parts of the release process for NAudio, as I always seem to forget something or make a mistake. One of the tasks was to create a “demo” zip file containing the demo applications together with their supporting files.

I initially attempted to do this with MSBuild and the Zip task from MSBuild Community Extensions, but I ended up double-adding a number of files, as well as struggling to get exactly the right folder structure.

This is exactly the sort of task that Python excels at, and Python comes with the zipfile module built in, meaning that the script I wrote is not IronPython specific. Here’s what I came up with:

import zipfile
import os

folders = ['AudioFileInspector','NAudioDemo','NAudioWpfDemo']
files = {}

def exclude(filename):
    return filename.endswith('.pdb') or ('nunit' in filename)

for folder in folders:
    fullpath = folder + "\\bin\\debug\\"
    for filename in os.listdir(fullpath):
        if not exclude(filename):
            files[filename] = fullpath + filename

zip = zipfile.ZipFile("BuildArtefacts\\test.zip", "w")

for filename, fullpath in files.iteritems():
    if os.path.isdir(fullpath):
        for subfile in os.listdir(fullpath):
            zip.write(fullpath + "\\" + subfile, filename + "\\" + subfile)
    else:
        zip.write(fullpath, filename)

zip.close()

There's not a lot to it really. I first build up a dictionary containing the files I want in my zip, using the filename to exclude duplicates. Then I use the write method on zipfile to specify the file I want to add, and the folder it belongs in.

My Python skills are a bit rusty, so the code above would probably benefit from being refactored a little, but as you can see, it is very easy, and much simpler than fighting MSBuild to make it do what I want.

Saturday, 11 June 2011

MSBuild task to update AssemblyVersion and AssemblyFileVersion in C# files

A while ago I decided I needed an MSBuild task that could update the version numbers in my AssemblyInfo.cs file. I had a search around the web, but all the custom tasks I found seemed to work in a different way to what I wanted. So I created my own.

What I wanted was a simple way to say “increment the revision number but leave other numbers the same”, or “set the version to exactly this”. So I came up with a simple syntax to let me do just that. For example:

<SetVersion FileName="AssemblyInfo.cs" AssemblyVersion="1.2.+.=" />

This means, set the major version to 1, set the minor version to 2, increment the revision number and leave the build number at whatever it was before. You can also specify a rule for AssemblyFileVersion if you want to keep them both in sync.

SetVersionTask is open source, and hosted on BitBucket. You can download the build of the latest version there, and read more detailed instructions on how to set it up in your MSBuild script.

I had originally planned to make it work for nuspec files too, but I don’t have a pressing need for that just yet. Shouldn’t be too hard to add though. Feel free to make use of it if it does what you need.

Wednesday, 14 July 2010

Running NUnit tests on .NET 4 assemblies with MSBuild Part 2

Yesterday, I posted a way to run NUnit tests on .NET 4 assembles with MSBuild. Although it worked, I was not 100% happy with the way of going about it. Thanks to a comment from Travis Laborde, I can now present a simpler way, making use of the exec task in MSBuild and the /framework switch on nunit-console.exe. Here’s the project file syntax:

<Target Name="Test2" DependsOnTargets="Build">
  <exec 
     command="&quot;C:\Program Files\NUnit 2.5.5\bin\net-2.0\nunit-console.exe&quot; /framework=4.0.30319 @(TestAssembly)">
     WorkingDirectory="."
  </exec>
</Target>

Tuesday, 13 July 2010

Running NUnit tests on .NET 4 assemblies with MSBuild

If you want to run NUnit tests with MSBuild, then you need to download and install the MSBuild Community tasks. This allows you to create a unit test target like this:

<ItemGroup>
  <TestAssembly Include="ClientBin\*Tests.dll" />
</ItemGroup>
<Target Name="Test" DependsOnTargets="Build">
  <NUnit Assemblies="@(TestAssembly)"
         WorkingDirectory="."
         ToolPath="C:\Program Files\NUnit 2.5.5\bin\net-2.0"
         />
</Target>

(Most examples don’t show the need to specify a ToolPath, but I have found I need it, perhaps because it isn’t in my Path?)

The trouble is, if the assemblies containing the unit tests have been build with the .NET 4 framework, you will get the following error:

C:\Program Files\NUnit 2.5.5\bin\net-2.0\nunit-console.exe /nologo ClientBin\
Nice.Inform.Client.Tests.dll
ProcessModel: Default    DomainUsage: Single
Execution Runtime: net-2.0
Unhandled Exception:
System.BadImageFormatException: Could not load file or assembly 'D:\TFS\Trial
\Inform5\ClientBin\Client.Tests.dll' or one of its dependencies.
This assembly is built by a runtime newer than the currently loaded runtime a
nd cannot be loaded.
File name: 'D:\TFS\Trial\Inform5\ClientBin\Client.Tests.dll'

What is needed is to ensure that nunit-console.exe runs against the .NET 4 framework. The way I achieved this was to make a copy of the net-2.0 folder that comes with NUnit 2.5.5 and rename it to net-4.0 (n.b. make sure the lib folder comes along too as nunit-console.exe depends on its contents). Then, I edited the nunit-console.exe.config file to have the following contents (the key bit is to add the supportedRuntime setting):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <!-- Set the level for tracing NUnit itself -->
  <!-- 0=Off 1=Error 2=Warning 3=Info 4=Debug -->
  <system.diagnostics>
    <switches>
       <add name="NTrace" value="0" />
    </switches>
  </system.diagnostics>

  <startup>
    <supportedRuntime version="v4.0"/>
  </startup>

  <runtime>
    <!-- We need this so test exceptions don't crash NUnit -->
    <legacyUnhandledExceptionPolicy enabled="1" />

    <!-- Look for addins in the addins directory for now -->
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="lib;addins"/>
   </assemblyBinding>
  </runtime>
</configuration>

Now all that is needed is to update the ToolPath in the MSBuild script to point to the new location

<Target Name="Test" DependsOnTargets="Build">
  <NUnit Assemblies="@(TestAssembly)"
        WorkingDirectory="."
        ToolPath="C:\Program Files\NUnit 2.5.5\bin\net-4.0"
   />
</Target>

I’d be interested in hearing if there is an easier way of solving this problem though, as I don’t really want to have to require all developers to manually perform these steps on their machine.

Tuesday, 30 September 2008

Getting Started With MSBuild

I tried using MSBuild for the first time this week, and took a couple of wrong turnings along the way. Here's a brief summary of how I got started and how to perform a few basic tasks...

What is it?

MSBuild is the build system used by Visual Studio to compile your .NET projects. Check out this nice introduction to MSBuild on CodeProject. The .csproj files you may be familiar with from Visual Studio are in fact nothing more than MSBuild project files.

Why use it?

There are a few reasons why you might want to use MSBuild directly in preference to simply compiling your solution in Visual Studio:

  • You can use it on a dedicated build machine without the need to install Visual Studio.
  • You can use it to batch build several Visual Studio solutions.
  • You can use it to perform additional tasks such as creating installers, archiving, retrieving from source control etc.

It is the third reason that appeals to me, as I wanted to automate the creation of zip files for source code and runtime binaries, as well as run some unit tests.

Creating a project file

MSBuild project files are simply XML files. I created master.proj and put it in the root of my source code folder, alongside my .sln file.

Build Target

The first code I put in to my project file was an example I found on the web that performed a Clean followed by a Build. Here is a slightly modified version of the project file:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build"  
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <DeploymentProject>MyApplication</DeploymentProject>
    <OutputDirectory>$(DeploymentProject)\bin\$(Configuration)</OutputDirectory>
  </PropertyGroup>

  <Target Name="Clean">    
    <RemoveDir Directories="$(OutputDirectory)" 
            Condition="Exists($(OutputDirectory))"></RemoveDir>
  </Target>
  <Target Name="Build">
    <MSBuild 
      Projects="$(DeploymentProject)\MyApplication.csproj"
      Properties="Configuration=$(Configuration)" >      
    </MSBuild>
  </Target>
</Project>

I ran the build script using the following command line: MSBuild master.proj

It deleted all my source code! Aarrghh!! Fortunately I had a recent backup available.

The reason was that I had not put a default value for Configuration in the PropertyGroup, and my OutputDirectory property was missing the bin from its folder path. Note to self: always backup before running any script that can delete files for the first time.

Zip Target

After my unfortunate experience with clean, I was more determined than ever to create a target that would perform a source-code backup for me! To use the Zip task in MSBuild you first need to download and install MSBuild Community Tasks.

Then you create an ItemGroup defining which files you want to be included (and excluded) before using the Zip task to actually create the archive. Here's the two targets I created - one to make a release package, and one to make a source code archive:

<Target Name="Package" DependsOnTargets="Build">
    <ItemGroup>
      <!-- All files from build -->
      <ZipFiles Include="$(DeploymentProject)\bin\$(Configuration)\**\*.*"
         Exclude="**\*.zip;**\*.pdb;**\*.vshost.*" />
    </ItemGroup>
    <Zip Files="@(ZipFiles)"
         WorkingDirectory="$(DeploymentProject)\bin\$(Configuration)\"
         ZipFileName="$(ApplicationName)-$(Configuration)-$(Version).zip"
         Flatten="True" />
    </Target>

    <Target Name="Backup">
    <ItemGroup>
      <!-- All source code -->
      <SourceFiles Include="**\*.*" 
        Exclude="**\bin\**\*.*;**\obj\**\*.*;*.zip" />
    </ItemGroup>
    <Zip Files="@(SourceFiles)"
         WorkingDirectory=""
         ZipFileName="$(ApplicationName)-SourceCode-$(Version).zip" />
</Target>

See Ben Hall's blog for another example.

Test Task

My final task was to get some NUnit tests running. Again the MSBuild Community Tasks are required for this. It took me a while to get this working correctly, particularly because it seemed to have trouble detecting where my copy of NUnit was installed. Here's the XML:

<Target Name="Test" DependsOnTargets="Build">
    <NUnit Assemblies="@(TestAssembly)"
           WorkingDirectory="MyApplication.UnitTests\bin\$(Configuration)"
           ToolPath="C:\\Program Files\\NUnit 2.4.7\\bin"
           />
</Target>

Conclusion

It took me longer than I wanted to work out how to do these basic tasks with MSBuild, but even so, I am sure the time will be very quickly recouped as I will be able to reuse most of these tasks on future projects. The jury is still out on whether it is preferable to use MSBuild to NAnt though.