The solution is design around MSBuild: leveraging some extension points, you trigger the custom code that increments some version number in AssemblyInfo.cs, subject to the same conditions that triggers code compiling. Being MSBuild based it will work both on a local (desktop) build, and a Team Foundation Build; you must be careful selecting the correct options for your Build Definition, otherwise it will not function. In this post we will look at the basics of inject code in the standard MSBuild process, leaving the Team Build customization for the next post. A good discussion about what version numbers are available and the best practices to manage them can be found in Team Foundation Build Customization Guide. This solution will not touch any Visual Studio / MSBuild project file (
*.*proj) nor requires any change to a build server.
Let’s start analyzing how to intercept the standard compile process and inject a custom Task in it. The standard .Net compile process is designed with some extension points ready to use and since .Net 2; furthermore there are some properties to easily hook the extension points in a clean way (see How to: Extend the Visual Studio Build Process and Common MSBuild Project Properties for details). Strangely, the most powerful property is
CustomAfterMicrosoftCommonTargets: supplying the path of an MSBuild file, all targets defined in your file redefine the default ones and add more.
Let’s see an example. Suppose you created a new Console Application project in the
C:\Sample folder; running from the command line
MSBuild /property:CustomAfterMicrosoftCommonTargets=c:\Sample\custom.targets ConsoleApplication1.csproj
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="AfterCompile"> <Message Text="*TRACE* AfterCompile intercepted!" Importance="high" /> </Target> </Project>
the output for the first run of the command is
you see that
AfterCompile is redefined, and is executed after compiling the project.
Rerunning the command without touching any file gives a different output, as MSBuild is smart to not recompile when source is unchanged.
Note that our custom message appears in both scenarios.
Now that we have seen the basics of intercepting the standard compile process, let’s see how to leverage this technique to control the versioning.
In order to see an updated version number on the executable, we have to select a target running before the target which compiles the project; also it must be run only if the project will be compiled. For this to happen, use the
BeforeCompile target and specify the same Inputs and outputs that the
CoreCompile target uses, as in the following MSBuild snippet.
<Target Name="BeforeCompile" Inputs="$(MSBuildAllProjects); @(Compile); @(_CoreCompileResourceInputs); $(ApplicationIcon); $(AssemblyOriginatorKeyFile); @(ReferencePath); @(CompiledLicenseFile); @(LinkResource); @(EmbeddedDocumentation); $(Win32Resource); $(Win32Manifest); @(CustomAdditionalCompileInputs)" Outputs="@(DocFileItem); @(IntermediateAssembly); @(_DebugSymbolsIntermediatePath); $(NonExistentFile); @(CustomAdditionalCompileOutputs)"> <!-- increment task here --> </Target>
The standard process computes the project inputs — source files, resources, referenced projects and DLLs — the project outputs — executables, PDBs, XML documentation files, etc — and compare the timestamps to see if outputs are out-of-date with respect to inputs. It is important to list the same set of files as CoreCompile, so MSBuild will trigger our target on the same condition used to compile the source. This code can be found in the
%Program Files (x86)%\MSBuild\12.0\Bin\Microsoft.CSharp.CurrentVersion.targets file for MSBuild 12 (Visual Studio 2013 or later) or
%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\Microsoft.CSharp.targets (up to Visual Studio 2012 included).
Updating the Assembly version is easy thanks to the AssemblyInfo task of MSBuild Extension Pack.
<ItemGroup> <AssemblyInfoFilesToUpdate Include=”$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs”/> </ItemGroup> <MSBuild.ExtensionPack.Framework.AssemblyInfo AssemblyInfoFiles=”@(AssemblyInfoFilesToUpdate)” AssemblyFileBuildNumberType=”YearWeekDay” AssemblyFileRevisionType=”AutoIncrement” FirstDayOfWeek=”Sunday” />
In this snippet we collect the AssemblyInfo file contained in the Project’s Properties folder and feed it to the Task asking to set a build number using current date and to automatically increment the revision number; major and minor are left untouched. See the Task documentation for many more options. Only two DLLs are required:
The code does not change the
AssemblyVersion attribute: this is the most common option and in the Team Foundation Build Customization Guide you will find in depth information on the reasons.
In the next post we will see how to integrate these techniques in the Team Foundation Build process.