Showing posts with label Build. Show all posts
Showing posts with label Build. Show all posts

Injecting Version Numbers Into WiX Projects

I've been meaning to blog about this for a bit. When I responded to someone asking about this very thing on the OzTFS mailing list, I basically crafted the post. So here it is.

I was tasked with making sure the product version number was applied to the deployment package during the release build process. I pored over the problem for a while and found many strange and complex approaches to solving it. Then I had a V8 moment and realized the answer was much simpler than I thought.

I didn't have the burden of persisting the version number in any of my source code because it's date/time driven. I know a lot of people like incrementing revisions that rollover each day, but I gave that up for simplicity. The major and minor values are set statically in the TFSBuild.proj or passed in via command-line arguments. Those values change far less often so this was an acceptable approach. I can even use Error tasks to ensure those values are passed in when doing a release candidate build.

I was already using a regular expression-based FileUpdate task from the MSBuild.Community.Tasks component to update my AssemblyInfo files. I just applied the same technique to update the .wixproj files in the AfterGet target.

<CreateItem Include="$(SolutionRoot)\**\*.wixproj">  
   <Output TaskParameter="Include" ItemName="WixProjectsToVersion" />  
</CreateItem>  
<Attrib Files="@(WixProjectsToVersion)" Normal="true" />  
<FileUpdate Files="@(WixProjectsToVersion)" Regex="SimpleVersion=.*;DetailedVersion=.*&lt;" ReplacementText="$(WixVersions)&lt;" />

This updates preprocessor variables defined in the Property pages of the WiX project as follows.

SimpleVersion=1.0;DetailedVersion=1.0.0.0

The $(WixVersions) property is generated earlier in the build process. As long as it's value ends up looking similar to the following line by the time the FileUpdate task executes, you'll be in good shape.

SimpleVersion=3.5;DetailedVersion=3.5.123.12345

I then used the MSBuild task in the PackageBinaries target to compile the WiX projects. Setting the OutputPath property ensures that the resulting MSI file(s) end up where the rest of the build outputs are.

<MSBuild Projects="$(SolutionRoot)\Setup.wixproj"   
Properties="OutputPath=$(BinariesRoot);Configuration=Release" />

While we're on the subject of build outputs, I should mention a little trick that enables your WiX projects to be built on locally AND on the build server. This next snippet should be self-explanatory:

<!-- Preprocessor directives to conditionally set source paths based on type of build -->  
<?if "$(env.USERNAME)"="s-tfsservice"?>  
   <?define Project1Bin="..\..\..\..\..\..\Binaries\Mixed Platforms\$(var.Configuration)"?>  
<?else?>  
   <?define Project1Bin="..\..\..\some path\bin\$(var.Configuration)"?>  
<?endif?>

A lot of people struggle with the approach of specifying the DefineConstants property override in the <SolutionToBuild> element in their build scripts. The reason this doesn’t work is because those properties and items are all defined when the build is initialized. The dynamic version number hasn't been generated yet.

jb

New MSBuild Extension Pack

Brian Harry posted today about a new MSBuild Extension Pack project on CodePlex. I figured this would be a good opportunity to demonstrate how you can use it to version the assemblies in your builds. Here are the steps for setting up assembly versioning for your builds using the MSBuild Extension Pack.

1. Download and install the MSBuild Extension Pack on your build machine(s).
2. Create a base target file with the following XML:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks" />

<!-- ASSEMBLY VERSIONING -->
<PropertyGroup>
<AssemblyMajor>1</AssemblyMajor>
<AssemblyMinor>0</AssemblyMinor>
</PropertyGroup>

<Target Name="GenerateAssemblyVersion">
<!-- Get a version number based on the elapsed days since a given date -->
<!--TfsVersion TaskAction="GetVersion" BuildName="$(BuildDefinition)" TfsBuildNumber="$(BuildNumber)" VersionFormat="Elapsed" StartDate="17 Nov 1976" PaddingCount="4" PaddingDigit="1" Major="$(AssemblyMajor)" Minor="$(AssemblyMinor)">
<Output TaskParameter="Version" PropertyName="AssemblyVersion" />
</TfsVersion-->
<!-- Get a version number based on the format of a given datetime -->
<TfsVersion TaskAction="GetVersion" BuildName="$(BuildDefinition)" TfsBuildNumber="$(BuildNumber)" VersionFormat="DateTime" DateFormat="MMdd" PaddingCount="5" PaddingDigit="1" Major="$(AssemblyMajor)" Minor="$(AssemblyMinor)">
<Output TaskParameter="Version" PropertyName="AssemblyVersion" />
</TfsVersion>
<Message Text="New Version is $(AssemblyVersion)" />
</Target>

<Target Name="VersionAssemblies">
<!-- Run the CreateItem task to populate the group after the source code has been downloaded. -->
<CreateItem Include="$(SolutionRoot)\**\AssemblyInfo.cs">
<Output TaskParameter="Include" ItemName="FilesToVersion" />
</CreateItem>
<!-- Set the version in a collection of files -->
<TfsVersion TaskAction="SetVersion" SetAssemblyVersion="true" Files="%(FilesToVersion.Identity)" Version="$(AssemblyVersion)" />
</Target>
</Project>

3. Save the file and copy it out to the MSBuild directory under Program Files on your build machine(s).

The GenerateAssemblyVersion target shows a couple of ways to use the GetVersion action of the TfsVersion task to generate different assembly version formats. The task has several attributes that allow you to customize the format to fit your needs. Check out the help file included with the MSBuild Extension Pack installation for more details.

To actually use this functionality in your builds, just add the following XML to your TFSBuild.proj files.

<Import Project="$(MSBuildExtensionsPath)\My.TeamFoundation.Build.targets" />

<PropertyGroup>
<AssemblyMajor>3</AssemblyMajor>
<AssemblyMinor>5</AssemblyMinor>
</PropertyGroup>

<Target Name="AfterGet">
<CallTarget Targets="GenerateAssemblyVersion;VersionAssemblies" />
</Target>

Here, you override the base values of the AssemblyMajor and AssemblyMinor properties from your base target file. In addition, you're overriding the built-in AfterGet target that is part of Team Build to call the custom targets in your base target file. This keeps the amount of duplicated MSBuild script very low.

UPDATE: Mike Fourie has a great post on some additional things you should consider when setting up assembly versioning in your builds. Of note is the separation of the AssemblyVersion and AssemblyFileVersion attributes when working with strong-named assemblies.

jb