I was working on an automated build for TFS the other day. One of the tasks I was trying to accomplish is automatically increment version number as part of the build. There is no build task I could use, so I wrote a little utility to do so and plugged into the build. I am going to document the process in this blog.
First step is to minimize the effort by employing the following scheme in my solution. By default, assembly version and assembly file version are included in AssemblyInfo.cs in each project in the solution. Whenever I setup a brand new solution, I always move those attributes out of each project and add them to SolutionInfo.cs file. Then, I include this file in every project via add as link option in Add Existing Item dialog. This way I only need to increment this version in a single file in my build. The code I need to modify looks as follows:
[assembly: AssemblyVersion("4.3.2.1")] [assembly: AssemblyFileVersion("4.3.2.1")]
My utility just bumps up the last number (build number in this case). The utility is very simple. It is just a console application with two methods:
class Program { static void Main(string[] args) { if (args.Length == 1) { var fileContent = System.IO.File.ReadAllLines(args[0]); var counter = 0; foreach (var line in fileContent) { var currentLine = line; if (currentLine.Contains("[assembly: AssemblyVersion(")) { fileContent[counter] = UpdateVersion(line); } else if (line.Contains("[assembly: AssemblyFileVersion(")) { fileContent[counter] = UpdateVersion(line); } counter++; } System.IO.File.WriteAllLines(args[0], fileContent); Environment.ExitCode = 0; return; } else { Console.Write("One argument is required"); Environment.ExitCode = -1; return; } } private static string UpdateVersion(string line) { string currentLine = line; int lastPeriodPosition = line.LastIndexOf("."); int lastQuotePosition = line.LastIndexOf("""); int currentVersion = int.Parse(currentLine.Substring(lastPeriodPosition + 1, lastQuotePosition - lastPeriodPosition - 1)); currentLine = line.Substring(0, lastPeriodPosition + 1) + (currentVersion + 1).ToString() + line.Substring(lastQuotePosition); return currentLine; } }
A couple of things to notice. I added Exit Codes everywhere in order to know that an error has occurred as part of the utility execution inside the build process. Now with utility out of the way, it is time to add some tasks to my build.
I am going to use TF.exe utility that is part of TFS / Visual Studio installer. It is a command line utility that allows the user to perform basic TFS functions. In my case, I need checkout and checkin functions. You can read more about TF.exe on MSDN http://msdn.microsoft.com/en-us/library/cc31bk2e(v=vs.80).aspx
Now, let’s look at the TFBuild.proj file. I am setting up properties pointing to my TF.exe first. I have to quote it because the path has spaces in it. I am adding the location to PropertyGroup section
<PropertyGroup> <TF>"C:Program Files (x86)Microsoft Visual Studio 10.0Common7IDETF.exe"</TF> <VersionUtilityExecutable>VersionUtility.exe</VersionUtilityExecutable> <SolutionInfoFile>SolutionInfo.cs</SolutionInfoFile> </PropertyGroup>
As you can see, I also added SolutionInfo property and my Updater executable property. Then I need to call the TF and my updater utility after the latest has been retrieved from TFS. I am using AfterGet target.
<Target Name="AfterGet"> <Exec WorkingDirectory="$(SolutionRoot)MySolution" Command="$(TF) checkout $(SolutionInfoFile)" ContinueOnError="false"/> <Exec WorkingDirectory="$(SolutionRoot)Build" Command=""$(SolutionRoot)Build$(VersionUtilityExecutable)" "$(SolutionRoot)MySolution$(SolutionInfoFile)"" ContinueOnError="false"/> </Target>
SolutionRoot is my TFS project root, and MySolution is the location of the solution under the root TFS project folder. While we are at it, I need to remember that if an error occurs, I need to undo check out. I am doing this in BeforeOnBuildBreak target
<Target Name="BeforeOnBuildBreak"> <Exec WorkingDirectory="$(SolutionRoot)MySolution" Command="$(TF) undo /noprompt $(SolutionInfoFile)" ContinueOnError="false"/> </Target>
Now after the build I need to call my utility and check the SolutionInfo.cs file back in. I am doing it in AfterCompile target.
<Target Name="AfterCompile"> <Exec WorkingDirectory="$(SolutionRoot)MySolution" Command="$(TF) checkin /comment:"Build version update" /noprompt /override:"Build version update" $(SolutionInfoFile)" ContinueOnError="false"/> </Target>
And there is all there is to. You can read more about targets inside TFS build here. http://msdn.microsoft.com/en-us/library/aa337604(v=vs.80).aspx
Also, I recommend you add Message commands to log into the build log. The same applies to utility. All Console.Write commands will end up in your build log as well.
Thanks.