Mobile Zone is brought to you in partnership with:

Henry Lee is founder of NewAgeSolution.Net and is passionate about the technology. He is also the author of the book Beginning Windows Phone 7 Development 2nd edition from Apress (http://bit.ly/wp7apress2nd). He works with various Fortune 500 companies delivering mobile applications and rich internet applications. He recently formed start-up company called ToeTapz.com focusing his energy on delivering mobile applications to the consumers. In his spare time, he dedicates his effort to help his .NET community by delivering sessions at the technology events. He enjoys talking with other technologist about current trends in the technology and sharing business insights with fellow colleagues. Often you will find Henry at local cigar bar enjoying a cigar and a drink trying to come up with next big mobile application. Henry is a DZone MVB and is not an employee of DZone and has posted 9 posts at DZone. You can read more from them at their website. View Full User Profile

Custom Versioning Strategy on TFS 2010 for Windows Phone

10.28.2011
| 4171 views |
  • submit to reddit

Read this tutorial that shows you how to implement the major.minor.build.revision format in Team Foundation Server while developing Windows Phone apps.

Recently, I started to build many Windows Phone 7 applications and I decided to take the next step and start implementing build automation for Windows Phone 7 projects. One thing I struggled with is versioning. I wanted to be able to use a major.minor.build.revision format and I wanted this to be used on labels and also on drop location folder names.

I find it extremely hard to believe that there are almost no blogs on this particular topic of custom versioning.; although there are a couple of blogs that do this, they still fail to mention the important part of using BuildNumber to create DropLocation and Set LabelName using buildNumber which was my main motivation for this blog. Every organization I work with to help migrate or implement TFS 2008 and TFS 2010 seems to always ask the question of using custom versioning.

Custom versioning is nothing more then being able to control the versioning using the format of {major}.{minor}.{build}.{revision}. The roblem with TFS 2008 and TFS 2010 is that by default it uses $(Date:yyyyMMdd)$(Rev:.r) which almost nobody wants to use at least from my experience working with my clients.

In TFS 2008, it was easy enough to simply override BuildNumberOverrideTarget using a custom MSBuild task. But in TFS 2010 if you decide to use the Workflow approach it is not so obvious and needs little more work. It took me a while to come up with something that was a good solution.

Your final goal after this blog is to create something similar to the below Workflow diagram. You will be adding custom activities after Get Workspace activity as shown below.

image

So here are your expected final results.

1) Custom build number using major.minor.build.revision.

2) Set LabelName using custom version.

3) Create DropLocation using custom version.

4) Windows Phone 7 Xap file.

It seems like a lot but there will be a reason for all these things. In the following sections, you will learn to 1) customize your build number using custom Workflow Activity, 2) integrate it to Workflow, and 3) build Windows Phone 7 bits.

Preparing the Build Server

Make sure to read my blog on this if you are planning to set up a build server specific to Windows Phone 7.

Assumes

I will assume following for the sake of making this blog more simpler.

1) At least created build definition once before.

2) Has general idea about TFS 2010 and Workflow or glimpsed through following helpful blogs that I enjoy reading:

- Jim Lamb’s blog on TFS 2010

- Martin Woodward’s TFS 2010 related blogs

- Ewald Hofman’s TFS 2010 Workflow related tutorials

Creating Custom Activities

You can learn about preparing to create custom activities by reading blogs above they cover various best practices on how to do this. You will need four custom activities. And following section will describe what they do and their short snippets of codes.

Checkout

I don’t remember where I got this code originally from but I made some modification of adding search patters to allow to checkout files that matches specific search pattern.

using System.Activities;

using System.IO;

using Microsoft.TeamFoundation.Build.Client;

using Microsoft.TeamFoundation.VersionControl.Client;

 

namespace TfsBuildActivitiesLib.Activities

{

    [BuildActivity(HostEnvironmentOption.Agent)]

    public sealed class Checkout : CodeActivity

    {

        // The file mask of all files for which the buildnumber of the

        // AssemblyVersion must be increased

        [RequiredArgument]

        public InArgument<string> SearchPatterns { get; set; }

 

        // The workspace that is used by the build

        [RequiredArgument]

        public InArgument<Workspace> Workspace { get; set; }

 

        protected override void Execute(CodeActivityContext context)

        {

            // Obtain the runtime value of the input arguments

            string[] searchPatterns = context.GetValue(this.SearchPatterns).Split(‘;’);

 

            Workspace workspace = context.GetValue(this.Workspace);

 

            // Checks all files out in the workspace that apply to the file mask

            // For every workspace folder (mapping)

            foreach (var folder in workspace.Folders)

            {

                foreach (var searchPattern in searchPatterns)

                {

                    // Get the files that apply to the mask on the local system

                    foreach (var file in Directory.GetFiles(folder.LocalItem,

                                                    searchPattern, SearchOption.AllDirectories))

                    {

                        // Check all those file out

                        workspace.PendEdit(file);

                    }

                }

            }

        }

    }

}

 

SetCustomVersion

In order to use your own custom version, you would need to have file where you can store version that you know it will be there so that you can use it to increment or control the version by changing major or minor version. I noticed from experience that the companies like to control either major or minor or both of the numbers.

I typically put the version.txt at the root of the folder where the solution file is where I want that product to have specific version and I track these version using version.txt as shown below.

image

To manipulate version.txt and get new version custom activity you will use an OutArgument called NewVersion as shown in below code. NewVersion will be accessible to the Workflow and you will be using it to change the BuildNumber and DropLocation found in BuildDetail using SetBuildPropties TFS activity.

using System;

using System.Activities;

using System.IO;

using Microsoft.TeamFoundation.Build.Client;

 

namespace TfsBuildActivitiesLib.Activities

{

    [BuildActivity(HostEnvironmentOption.Agent)]

    public sealed class SetCustomVersion : CodeActivity

    {

 

        [RequiredArgument]

        public InArgument<IBuildDetail> BuildDetail { get; set; }

 

        [RequiredArgument]

        public InArgument<string> VersionText { get; set; } // i.e. wp7\duckcaller\version.txt

 

        [RequiredArgument]

        public InArgument<string> SourceDir { get; set; } // i.e. wp7\duckcaller\version.txt

 

        public OutArgument<string> NewVersion { get; set; } // i.e. wp7\duckcaller\version.txt

 

        protected override void Execute(CodeActivityContext context)

        {

            var srcDir = SourceDir.Get(context);

            var versionText = VersionText.Get(context);

 

            var versionFile = Path.Combine(srcDir, versionText);

 

            if (File.Exists(versionFile))

            {

                Version version =  new Version(File.ReadAllText(versionFile));

 

                Version newVersion = new Version(version.Major, version.Minor, version.Build + 1, 0);

                var buildDetail = BuildDetail.Get(context);

 

                File.WriteAllText(versionFile, newVersion.ToString());

 

                this.NewVersion.Set(context, newVersion.ToString());

            }

            else

            {

                throw new ArgumentException(versionFile + “Does not exist!”);

            }

        }

 

    }

}

 

SetAssemblyVersion

This custom activity will search through DirectoryList (contains semicolon delimited list of directories relative to the source directory) and stamp AssemblyInfo.cs files with the version as show in below code.

using System.Activities;

using Microsoft.TeamFoundation.Build.Client;

using Microsoft.TeamFoundation.Build.Workflow.Activities;

using System.ComponentModel;

using System.IO;

using System.Text.RegularExpressions;

 

namespace TfsBuildActivitiesLib.Activities

{

    [BuildActivity(HostEnvironmentOption.All)]

    [DisplayName("Set Assembly Version Task")]

    public sealed class SetAssemblyVersion : CodeActivity

    {

        [RequiredArgument]

        public InArgument<string> BuildNumber { get; set; }

        [RequiredArgument]

        public InArgument<string> DirectoryList { get; set; } // ; delimited (i.e root\dir3;c:\root\dir4)

        [RequiredArgument]

        public InArgument<string> SearchPatterns { get; set; } // ; delimited (i.e assemblyinfo.*;assembly.cs)

        [RequiredArgument]

        public InArgument<string> SourcesDirector { get; set; }

 

        protected override void Execute(CodeActivityContext context)

        {

            // Build Number

            string buildNumber = context.GetValue<string>(BuildNumber);

            context.TrackBuildMessage(“Preparing For Setting Assembly Version: “ + buildNumber, BuildMessageImportance.High);

 

            string sourceDir = SourcesDirector.Get(context);

 

            // Get directories to search

            string dirList = DirectoryList.Get(context);

            string[] directories = string.IsNullOrEmpty(dirList) ? null : dirList.Split(‘;’);

            context.TrackBuildMessage(“Searching Directories: “ + dirList, BuildMessageImportance.High);

 

            // Get Assembly File Names

            string assemblyInfoFileMasks = SearchPatterns.Get(context);

            if (!string.IsNullOrEmpty(assemblyInfoFileMasks))

            {

                context.TrackBuildMessage(“Setting Assemblies: “ + assemblyInfoFileMasks, BuildMessageImportance.High);

                foreach (string dir in directories)

                {

                    DirectoryInfo dirInfo = new DirectoryInfo(Path.Combine(sourceDir, dir));

                    foreach (string assemblyInfoFileMask in assemblyInfoFileMasks.Split(‘;’))

                    {

                        foreach (FileInfo file in dirInfo.GetFiles(assemblyInfoFileMask, SearchOption.AllDirectories))

                        {

                            context.TrackBuildMessage(string.Format(“Setting version on {0}”, file.FullName), BuildMessageImportance.High);

                            ChangeAssemblyVersion(file, buildNumber);

                        }

                    }

                }

            }

        }

 

        private void ChangeAssemblyVersion(FileInfo assemblyFile, string buildNumber)

        {

            string contents = string.Empty;

 

            using (StreamReader reader = assemblyFile.OpenText())

            {

                contents = reader.ReadToEnd();

                reader.Close();

            }

 

            string newAssemblyVersion;

            string newAssemblyFileVersion;

 

            if (assemblyFile.Extension.ToLower().Equals(“.cs”))

            {

                // c#

                newAssemblyVersion = “[assembly: AssemblyVersion(\"" + buildNumber + "\")]“;

                newAssemblyFileVersion = “[assembly: AssemblyFileVersion(\"" + buildNumber + "\")]“;

 

                contents = Regex.Replace(contents, @”\[assembly: AssemblyVersion\("".*""\)\]“, newAssemblyVersion);

                contents = Regex.Replace(contents, @”\[assembly: AssemblyFileVersion\("".*""\)\]“, newAssemblyFileVersion);

            }

            else

            {

                // vb

                newAssemblyVersion = “<Assembly: AssemblyVersion(\”" + buildNumber + “\”)>”;

                newAssemblyFileVersion = “<Assembly: AssemblyFileVersion(\”" + buildNumber + “\”)>”;

 

                contents = Regex.Replace(contents, @”\<Assembly: AssemblyVersion\(“”.*”"\)\>”, newAssemblyVersion);

                contents = Regex.Replace(contents, @”\<Assembly: AssemblyFileVersion\(“”.*”"\)\>”, newAssemblyFileVersion);

            }

 

            using (StreamWriter writer = new StreamWriter(assemblyFile.FullName, false))

            {

                writer.Write(contents);

                writer.Close();

            }

        }

    }

}

 

Checkin

I also got this code from somewhere I don’t remember where I got them so please write a comment below to give credit for this code. This task will check in all the files that are check out in current Workspace and code is shown below. Always keep in mind to put “***NO_CI***” comment if you are doing continuous integration.

using System.Activities;

using Microsoft.TeamFoundation.Build.Client;

using Microsoft.TeamFoundation.VersionControl.Client;

using Microsoft.TeamFoundation.Build.Workflow.Activities;

 

namespace TfsBuildActivitiesLib.Activities

{

    [BuildActivity(HostEnvironmentOption.Agent)]

    public sealed class Checkin : CodeActivity

    {

        // The workspace that is used by the build

        [RequiredArgument]

        public InArgument<Workspace> Workspace { get; set; }

 

        protected override void Execute(CodeActivityContext context)

        {

            // Obtain the runtime value of the input arguments

            Workspace workspace = context.GetValue(this.Workspace);

 

            context.TrackBuildMessage(workspace.Name, BuildMessageImportance.High);

 

            // Checks all files in in the workspace that have pending changes

            // The ***NO_CI*** comment ensures that the CI build is not triggered (and that

            // you end in an endless loop)

            workspace.CheckIn(workspace.GetPendingChanges(), “Build Agent”, “***NO_CI***”,

                null, null, new PolicyOverrideInfo(“Auto checkin”, null),

                CheckinOptions.SuppressEvent);

        }

 

    }

}

 

Using BuildNumber to Set DropLocation

There are blogs that will show you how to customize the build but not with specific case of using major, minor, build and revision. And most importantly how to make sure DropLocation uses the BuildNumber.

If you look at the default build template creation of DropLocation happens after Update Build Number task as shown in below figure.

image

Here is the problem. You have to read version.txt (whatever mechanism you use to read previous version so you can increment the version number) has to  happen before you can Set Drop Location and Create the Drop Location activities as shown in above.

To over come this problem you need to do two things.

1. First move three activities: Set Drop Location, Create Drop Location and If Drop Build and Build Reason is validatShelvset activities to Custom Version sequence shown in very first figure shown at the top.

2. After you moved the shapes down you need to modify Set Drop Location and Set Drop Location Private to include BuildNumber as shown in below diagram.

image

And then Set the BuildNumber using NewVersion that is the output of the SetAssemblyVersion activity and change DropLocation to following.

BuildDetail.DropLocationRoot + “\” + BuildDetail.BuildDefinition.Name + “\” + NewVersion

Define Workflow Arguments and Create Custom Build Properties and Build Definition

In above custom activities you coded you will notice that the public properties with RequiredArgument attributes. These properties are expected to pass into the custom activities from the Workflow and you must define the arguments and then create Process Parameter Metadata so that it can be set when you are creating build definition. See below for process of creating arguments to be passed into the activities and creating metadata to be passed in from the build definition.

image

Not metadata is exposed to the build definition level where you can customize with greater flexibility as show below.

image

Final Result

So here is the final result as shown in figure below.

1) Custom build number using major.minor.build.revision.

2) Set LabelName using custom version.

3) Create DropLocation using custom version.

4) Windows Phone 7 Xap file. if you properly prepared Windows Phone 7 build server you will see Xap file in DropLocation when you build the solution that contains Windows Phone 7 project.

image

Download Code

TFS XAML

Conclusion

In this blog you built a Windows Phone 7 project in TFS 2010 using Workflow. As part of the process you learned to create custom version (major.minor.build.revision), set custom version as label, and use custom version to create drop location.

Source: http://blog.toetapz.com/2010/12/02/custom-versioning-strategy-on-tfs-2010-using-workflow-for-windows-phone-7-application/

Published at DZone with permission of Henry Lee, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Timo Lihtinen replied on Wed, 2012/03/14 - 2:10pm

Great Post,

I just have some issues setting up the workflow. Would you mind attaching the corresponding .xaml file to your download because the one currently attached is empty.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.