Continuous Delivery with TFS: Automatically Versioning Assemblies as Part of The Build
In a previous post in this series on implementing continuous delivery with TFS we looked at how some simple tweaks to the build process can help with the goal of baking quality in. This post continues in the vein of making improvements to the pipeline by addressing the issue of assembly versioning. What issue is that, I hear some of you asking? It's the situation where your Visual Studio solution contains many projects (maybe dozens) and you want all the projects to have same assembly versioning, ie the details you would traditionally set in AssemblyInfo.cs. A Google search will reveal several ways to accomplish this but most techniques involve some maintenance when a new project is added. In this post I explain how to make a publicly available low maintenance solution work with the Release Management build process template. I should point out that this issue won't affect everyone, and if you or your business don't care about this issue then do feel free to ignore. It is quite interesting though as it involves editing a build process template.
TFSVersioning on CodePlex
If assembly versioning is important to you and you use TFS there is a good chance you've seen the TFSVersioning solution available on CodePlex. It's a very nice piece of work that versions all of your solution's assemblies as part of the build process. If a new project is added it automatically gets included, so it's a low maintenance solution. There are essentially two ways to use TFSVersioning -- with the build process template that the project provides or with your own process template. This latter technique is a little involved as it requires editing your build template, but it's the technique we need to use here since we would like to use the ReleaseTfvcTemplate.12.xml build process template that ships with Release Management 2013.4. It turns out that editing this template is quite a job and I'm indebted to my good friend, colleague and TFS guru Bharath Sundaresan for figuring out all of the complicated details. An additional required hurdle is that the project hasn't been updated for TFS 2013 but fortunately it's not a lot of work to remedy this. The TFSVersioning deployment pack is available from the Downloads page and it has some great documentation which I recommend reading before you begin.
Update TFSVersioning for TFS2013
The core component that we need to update for TFS 2013 is TfsBuild.Versioning.Activities.dll. To accomplish this follow these steps:
- Download the latest source code for TFSVersioning from the Source Code page and unzip to somewhere convenient.
- Navigate to the latest version under Prod and open BuildVersioning.sln. Remove the TfsBuild.Versioning.Activities.Tests and TfsBuild.Versioning.Activities.Tests.Stubs projects as we don't need to amend them for what we are doing here.
- Expand the References node of the TfsBuild.Versioning.Activities project and notice that the Microsoft.TeamFoundation.* references are marked as missing:
- Remove these references and replace them with the 2013 versions from C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ReferenceAssemblies\v2.0.
- You shoud now be able to build a Release version of TfsBuild.Versioning.Activities.dll.
Once you have succesfully updated the project for TFS 2013 it's probably a good idea to make sure that a basic installation of TFSVersioning works. Follow these steps to verify this:
- Download and unzip the latest TFSVersioning deployment pack -- currently 2.0.1. Copy VersioningBuildTemplate20.xaml from the pack to the ContosoUniversity BuildProcessTemplates folder and check in to version control.
- Under ContosoUniversity create a new folder called CustomActivityStorage and copy over the new version of TfsBuild.Versioning.Activities.dll. Check in to version control.
- From Team Explorer in Visual Studio navigate to Builds > Actions > Manage Build Controllers.
- In Manage Build Controllers dialog choose Properties and in Manage Controller Properties set Version control path to custom assemblies to $/ContosoUniversity/CustomActivityStorage.
- Now create a test build definition, replacing the standard release template with VersioningBuildTemplate20.xaml and setting all required properties including the drop folder.
- Whilst editing the build definition set any properties under the Build Versioning and Build Versioning (Optional) sections as you wish. Refer to the documentation for TFSVersioning for details.
- Queue a manual build of the test build definition. Observe in the drops folder that the ContosoUniversity.* binaries all have the same File version.
Update the ReleaseTfvcTemplate.12 Release Template with the TFSVersioning Custom Activity
This process broadly follows the Harder Installation but More Instructive section of the TfsVersioning User and Development Guide however modifying ReleaseTfvcTemplate.12.xaml requires several more steps. Partly this is because TfsBuild.Versioning.Activities.dll contains more functionality that isn't referred to in the documentation and partly because ReleaseTfvcTemplate.12.xaml is missing some activities that (reading between the lines) were present in the template that was used by the TFSVersioning project. In the instructions below I assume a degree of familiarity with editing release templates. If you need guidance take a look here for just one example of how to get started. You should be aware that there are two ways to edit templates: through the XAML designer and through notepad. The former is less prone to error but slow and the latter is much faster but with the distinct possibility of a copy and paste error. The technique I describe below also sets you up for relatively easy debugging of the process template since there is a good chance of not getting everything right first time.
- Install the updated TfsBuild.Versioning.Activities.dll to the Global Assembly Cache by opening a Visual Studio command prompt (from C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts if your shortcuts are missing in Windows 8.1) and issuing a command similar to gacutil -i "C:\Users\Graham\Downloads\tfsversioning-103318\Prod\V 2.0.1.0\Source\TfsBuild.Versioning.Activities\bin\Release\TfsBuild.Versioning.Activities.dll".
- Copy C:\Program Files (x86)\Microsoft Visual Studio 12.0\Release Management\Client\bin\ReleaseTfvcTemplate.12.xaml to the ContosoUniversity BuildProcessTemplates folder and check in to version control. Chances are you already have a template with the same name so you'll probably want to change the name to ReleaseTfvcTemplate.12.Versioning.xaml or similar. Once checked in open this template in Visual Studio so it displays in the XAML editor.
- Set up the Visual Studio Toolbox to work with TfsBuild.Versioning.Activities.dll in a custom tab. You can reference the version in CustomActivityStorage. Note that you only need to add the VersionAssemblyInfoFiles item.
- Drag the VersionAssemblyInfoFiles activity from the toolbox to the workflow as the first item under Compile, Test and Publish. Feel free to give the activity a custom name. If you examine the properties of the activity you will see all the InArguments that need to be married up with either Variables or Arguments that do not yet exist in the process template:
- The arguments can be created as per the TfsVersioning User and Development Guide instructions using the Arguments editor but a faster way is to open the template in Notepad, copy the following values and append them to the <x:Members> section.
123456789101112131415161718<x:Property Name="AssemblyCompanyPattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyConfigurationPattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyCopyrightPattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyCulturePattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyDescriptionPattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyFileVersionPattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyInfoFilePattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyInformationalVersionPattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyProductPattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyTitlePattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyTrademarkPattern" Type="InArgument(x:String)" /><x:Property Name="AssemblyVersionPattern" Type="InArgument(x:String)" /><x:Property Name="BuildNumberPrefix" Type="InArgument(x:Int32)" /><x:Property Name="BuildSettings" Type="InArgument(mtbwa:BuildSettings)" /><x:Property Name="DoCheckinAssemblyInfoFiles" Type="InArgument(x:Boolean)" /><x:Property Name="ForceCreateVersion" Type="InArgument(x:Boolean)" /><x:Property Name="UseVersionSeedFile" Type="InArgument(x:Boolean)" /><x:Property Name="VersionSeedFilePath" Type="InArgument(x:String)" /> - The next step is to add the metatdata items that allow each of the above arguments to be set. Again, it's possible to use the Metadata editor but the faster Notepad way is to copy the following values and append them to the <mtbw:ProcessParameterMetadataCollection> section.
1234567891011121314151617<mtbw:ProcessParameterMetadata Category="Build Versioning" Description="This is the pattern used to replace the AssemblyFileVersion value." DisplayName="Assembly File Version Pattern" ParameterName="AssemblyFileVersionPattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning" Description="This is the pattern used to replace the AssemblyVersion value." DisplayName="Assembly Version Pattern" ParameterName="AssemblyVersionPattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning" Description="This is the pattern used to find the AssemblyInfo files. Generally, you shouldn't need to change this value." DisplayName="AssemblyInfo File Pattern" ParameterName="AssemblyInfoFilePattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning" Description="Indicated whether the AssemblyInfo files should be checked back into source control after they are modified." DisplayName="Perform Check-in of the AssemblyInfo Files" ParameterName="DoCheckinAssemblyInfoFiles" /><mtbw:ProcessParameterMetadata Category="Build Versioning" Description="If true, the versioning process will create AssemblyVersion or AssemblyFileVersion values even if they do not already exist." DisplayName="Force Create Version" ParameterName="ForceCreateVersion" /><mtbw:ProcessParameterMetadata Category="Build Versioning" Description="Indicate which values to use as the versioning patterns. If set to True, the "seedfile.xml" file must exist in the location described by the "Version Seed File Path" setting. Otherwise, the "Assembly Version Pattern" and "Assembly File Version Pattern" values will be used." DisplayName="Use Version Seed File" ParameterName="UseVersionSeedFile" /><mtbw:ProcessParameterMetadata Category="Build Versioning" Description="Relative path location for the seed (xml) file containing the Assembly Version and Assembly File Version values." DisplayName="Version Seed File Path" ParameterName="VersionSeedFilePath" /><mtbw:ProcessParameterMetadata Category="Build Versioning" Description="Number added to the version component that uses the "B" symbol pattern (Build Number). This helps create a unique version for a build definition." DisplayName="Build Number Prefix" ParameterName="BuildNumberPrefix" /><mtbw:ProcessParameterMetadata Category="Build Versioning (Optional)" Description="Assembly Title Attribute: String value specifying a friendly name for the assembly. For example, an assembly named comdlg might have the title Microsoft Common Dialog Control." DisplayName="Assembly Title Pattern" ParameterName="AssemblyTitlePattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning (Optional)" Description="Assembly Description Attribute: String value specifying a short description that summarizes the nature and purpose of the assembly." DisplayName="Assembly Description Pattern" ParameterName="AssemblyDescriptionPattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning (Optional)" Description="Assembly Configuration Attribute: String value indicating the configuration of the assembly, such as Retail or Debug. The runtime does not use this value." DisplayName="Assembly Configuration Pattern" ParameterName="AssemblyConfigurationPattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning (Optional)" Description="Assembly Company Attribute: String value specifying a company name." DisplayName="Assembly Company Pattern" ParameterName="AssemblyCompanyPattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning (Optional)" Description="Assembly Product Attribute: String value specifying product information." DisplayName="Assembly Product Pattern" ParameterName="AssemblyProductPattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning (Optional)" Description="Assembly Copyright Attribute: String value specifying copyright information." DisplayName="Assembly Copyright Pattern" ParameterName="AssemblyCopyrightPattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning (Optional)" Description="Assembly Trademark Attribute: String value specifying trademark information.
" DisplayName="Assembly Trademark Pattern" ParameterName="AssemblyTrademarkPattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning (Optional)" Description="Assembly Culture Attribute: Enumerated field indicating the culture that the assembly supports. An assembly can also specify culture independence, indicating that it contains the resources for the default culture." DisplayName="Assembly Culture Pattern" ParameterName="AssemblyCulturePattern" /><mtbw:ProcessParameterMetadata Category="Build Versioning (Optional)" Description="Assembly Informational Version Attribute: String value specifying version information that is not used by the common language runtime, such as a full product version number. " DisplayName="Assembly Informational Version Pattern" ParameterName="AssemblyInformationalVersionPattern" /> - Back in the XAML editor navigate to the Arguments editor and supply default values for some arguments as follows:
- AssemblyFileVersionPattern = "1.0.J.B"
- AssemblyInfoFilePattern = "AssemblyInfo.*"
- AssemblyVersionPattern = "1.0.0.0"
- BuildNumberPrefix = 0
- DoCheckinAssemblyInfoFiles = False
- ForceCreateVersion = False
- UseVersionSeedFile = False
- VersionSeedFilePath= "TfsVersion\VersionSeed.xml"
- Navigate to the Variables editor and create the following variables (you may need to Browse for Types to get some of the variable types):
- Name = BuildAgent; Variable Type = Microsoft.TeamFoundation.Build.Client.iBuildAgent; Scope = Compile, Test and Publish
- Name = BuildDetail; Variable Type = Microsoft.TeamFoundation.Build.Client.iBuildDetail; Scope = Compile, Test and Publish
- Name = BuildDirectory; Variable Type = String; Scope = Compile, Test and Publish
- Name = Workspace, Variable Type = Microsoft.TeamFoundation.VersionControl.Client.Workspace; Scope = Compile, Test and Publish
- From Toolbox > Team Foundation Build Activities add the following activities to the top of Compile, Test and Publish so they appear in the order listed:
- Activity = GetBuildAgent; Result = BuildAgent
- Activity = GetBuildDetail; Result = BuildDetail
- Activity = GetWorkspace; Name = String.Format("{0}_{1}_{2}", BuildDetail.BuildDefinition.Id, Microsoft.TeamFoundation.LinkingUtilities.DecodeUri(BuildAgent.Uri.AbsoluteUri).ToolSpecificId, BuildAgent.ServiceHost.Name); Result = Workspace
- Return to the properties of the VersionAssemblyInfoFiles activity and use the ellipsis at the end of each row to replace Enter a VB expression with the correct value. The result should be as follows:
- As a final step in this section save all the changes and check them in to version control.
Testing the Updated ReleaseTfvcTemplate.12 Release Template
At long last we are in a position to test the new template. The easiest way is to edit the test build definition created above and replace VersioningBuildTemplate20.xaml with our updated ReleaseTfvcTemplate.12.xaml version. Set any properties as required and queue a new build. With luck you will have a successful build and a set of uniformly versioned assemblies!
If you are having difficulty in implementing the steps above the debugging process is reasonably straightforward. Once the build template has been added to the test build definition you can make changes to the template, save them and then check them in to version control. Simply queue a new build to check your changes.
The final piece of the jigsaw when everything is working is to edit ContosoUniversity_Main_Nightly to use the new version of the template. And to enjoy a well-deserved drink.
Cheers -- Graham