Increment Version for Changed Assemblies only – Second Part

In the previous post we have seen how to hook into the MSBuild process, common to both the Visual Studio and the Team Foundation Server build processes. Now we examine how to leverage the previous achievement in the context of TFS Build.

We will examine the solution in a top-down fashion: first the build definition, than the build template, finally the MSBuild target file. The only drawback, is the lack of correlation between the version number appearing in the output assemblies with the Team Build Identifier; this will be correct with the next post.

Please note that this solution works only on-premises, i.e. it is not applicable to Visual Studio Online.

Incremental build

The default Team Build process template remove all files from the workspace — i.e. the local folder on the server running the Build Agent used for a Build Definition — and get the latest version from version control. This means that all source files get a new timestamp and the binaries are deleted also: everything will be re-compiled again.

This behavior is controlled by some properties: Clean workspace (TFVC), Clean repository (Git) and Clean build. The first two, when the value is False, only the changed files get refreshed from version control. The latter, Clean build, when the value is False, you get the same behavior as Visual Studio, also called incremental building: only the changed projects will recompile.

image

Sadly the Clean workspace (TFVC), Clean repository (Git) setting has no effect if you use the Hosted Build Controller, that is Visual Studio Online. In this case, you get a new working directory with each build (cf. Get your code).

In TFS 2010/2 the behavior is controlled by a single property, Clean Workspace, with three possible values: All, Outputs and None. This latter means: don’t delete anything and get only the changes from Version Control (see Incremental Builds with TFS 2010 for more details).

Hooking into MSBuild from the Build Template

To extend the standard MSBuild process we must pass it the full path to a file containing our custom code. It happens that this path is determined by three factors: the Build Agent selected, the Agent Working Directory and the mapping imposed by the Build Definition. Shortly, the path is dynamic and must be determined while building.

But… TFS 2013 has a nice new feature, some useful environment variables, described at Team Foundation Build environment variables. Stay assured that these variables are available throughout the whole process and not just when lunching a script. In particular you can use them in composing the MSBuild arguments, as in this snippet.

/p:CustomAfterMicrosoftCommonTargets="$(TF_BUILD_SOURCESDIRECTORY)\BuildSample.AutoIncr\BuildTemplate\autoIncrTfs2013.targets"

The TF_BUILD_SOURCESDIRECTORY represents the directory where the source code is downloaded from version control to the Build Agent local disk.

So the overall parameters to set in the Build Definitions are, at least, four. We will use the fifth, Build number format, later on.

2014-04-12_182207-b

The path used in the MSBuild arguments must match the relative path in version control and the mapping used in the Build Definition.

image

image

Note that the autoIncrTfs2013.targets file sits in the same version control folder as the MSBuild Extension Pack DLLs.

At last, we can take a look at the content of autoIncrTfs2013.targets. It replace the BeforeCompile target, conveniently triggered by input and output dependencies.

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

    <!-- assume the DLL is in the same folder as this Targets file -->
    <UsingTask
    AssemblyFile="$(MSBuildThisFileDirectory)\MSBuild.ExtensionPack.dll"
    TaskName="MSBuild.ExtensionPack.Framework.AssemblyInfo"/>
    
    <Target
        Name="BeforeCompile"
        Inputs="$(MSBuildAllProjects);
                @(Compile); 
                @(_CoreCompileResourceInputs);
                $(ApplicationIcon);
                $(AssemblyOriginatorKeyFile);
                @(ReferencePath);
                @(CompiledLicenseFile);
                @(LinkResource);
                @(EmbeddedDocumentation); 
                $(Win32Resource);
                $(Win32Manifest);
                @(CustomAdditionalCompileInputs)"
                Outputs="@(DocFileItem);
                @(IntermediateAssembly);
                @(_DebugSymbolsIntermediatePath); 
                $(NonExistentFile);
                @(CustomAdditionalCompileOutputs)"
        DependsOnTargets="$(BeforeCompileDependsOn)">
    
    <ItemGroup>
        <AssemblyInfoFiles Include="$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs"/>
    </ItemGroup>
    
    <MSBuild.ExtensionPack.Framework.AssemblyInfo
        AssemblyInfoFiles="@(AssemblyInfoFiles)"
        AssemblyFileBuildNumberType="YearWeekDay"
        AssemblyFileRevisionType="AutoIncrement"
        FirstDayOfWeek="Sunday"
    />
    
    </Target>
</Project>

The AssemblyInfo task set the File version using the current date for the build and incrementing the revision number. Note the MSBuildThisFileDirectory property that refers to the same folder where is located the autoIncrTfs2013.targets file.

Next time we will see how to synchronize the version number appearing in the assemblies with the Build Identifier: for this we have to customize the Build Template.