Again on assembly numbering during TFS Build

If you read this post, you can see how to customize a tfs build to modify versioning of the assembly. During that process to find the latest changeset of the repository, to use as “revision Number” , I used a direct call to tf.exe tool and a custom Regex msbuild task to parse the result to find desired number. This approach have some weak points, first of all you need to know the location of tf.exe tool, moreover we are bound to the format output of the tool, now I want to show you a different way to obtain the same result.

To access functionality of Team Foundation Server you can also use API, and I must admit that I really like this approach instead of relying on calling an external exe tool and parse its output. Here is the full code of a custom task that accomplish the same operation with the use of tfs API.

namespace DotNetMarche.MsBuildExtensions.TfsBuild
{
    public class SourceControlTask : Task
    {
        [Required]
        public String Operation { get; set; }

        [Required]
        public String TfsUrl { get; set; }

        [Output]
        public Int32 LatestChangeset { get; set; }

        public ICredentials Credentials { get; set; }

        public String TeamProject { get; set; }

        public override bool Execute()
        {
            SourceControlTaskOperation operation;
            try
            {
                operation = (SourceControlTaskOperation)Enum.Parse(typeof(SourceControlTaskOperation), Operation);
            }
            catch (FormatException fex)
            {
                Log.LogError("The operation {0} is not supported;", Operation);
                return false;
            }

            Log.LogMessage("SourceControlTask: Operation{0} requested;");
            TeamFoundationServer tfs;
            if (Credentials == null)
                tfs = new TeamFoundationServer(TfsUrl);
            else
                tfs = new TeamFoundationServer(TfsUrl, Credentials);

            switch (operation)
            {
                case SourceControlTaskOperation.GetLatestChangeset:
                    VersionControlServer vcs = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));

                    LatestChangeset = vcs.GetLatestChangesetId();
                    break;
                default:
                    Log.LogError("The operation {0} is not supported;", Operation);
                    return false;
            }
            return true;
        }
    }

    public enum SourceControlTaskOperation
    {
        GetLatestChangeset = 0,
    }
}

The object that permits to access TFS is the TeamFoundationServer one, that you can find in the C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\Microsoft.TeamFoundation.Client.dll assembly. Since we need also to use version control API we need also a reference to the Microsoft.TeamFoundation.VersionControl.Client located in the same directory. The task has a property called Credentials of type ICredentials used to specify the credentials used to access tfs. Since during a build the thread runs with the credential of the Build Agent there is no need to specify any credentials, but if you want to create a unit test you absolutely need them.

[TestMethod]
public void TestSmokeConnectToServer()
{
    SourceControlTask sut = new SourceControlTask();
    sut.BuildEngine = MockRepository.GenerateStub<IBuildEngine>();
    sut.Operation = "GetLatestChangeset";
    sut.Credentials = new NetworkCredential("alkampfer", "thisisnotmypassword:)");
    sut.TfsUrl = "http://tfsalkampfer:8080/";
    sut.Execute();
    Assert.IsTrue(sut.LatestChangeset > 0);
}

With this test I verify that the task GetLatestChangeset works and returns a value greater than 0, but since this test can be run with an account that have no permission to tfs I need to specify credential in the test. If you  do not like to store credentials in tests, you need to be sure to run this test with a user that have access right to tfs.

After you create a valid TeamFoundationServer object you need to call its GetService method to obtain a reference to desired service, in this example the VersionControlServer service. Once you have reference to it, you can simply call his GetLatestChangesetId() method to retrieve the latest changeset number. Once you have this task you can use in a tfs build project in this way.

<SourceControlTask
    TfsUrl="$(TeamFoundationServerUrl)"
    Operation="GetLatestChangeset">
    <Output    TaskParameter="LatestChangeset" PropertyName="lastChangeset"/>
    
</SourceControlTask>

As you can see you can simply specify the url of Team Foundation Server, the operation GetLatestChangeset and finally grab the output value with <output> tag.With the old method you need to call two tasks and if for some reason the output of the tf.exe tool changes (maybe for a new version), you need also to change the regex used to parse the output. Thanks to the api you can create a stronger method to obtain latest changeset number in your build file.

Now you can fire a build, goes to the drop location and verify the File Version attribute of the file

image

As you can see the version number is 135, my actual latest number for version control system of my test TFS server.

alk.

Tags:

Take control of assembly numbering during a tfs build

One of the most important stuff in a project build, is the ability to mark the assemblies with unique numbers that permits us to reproduce the build. Tfs does not have a standard way of doing this, but with a couple of MsBuild actions it is really simple to overcome this limitation. This is a good example that shows how you can extend build script to do complex task.

First of all I want to change only AssemblyFileVersion and not the AssemblyVersion, in this way all builds are compatible until someone manually changes AssemblyVersion. A standard technique I like very much is letting the programmers to manage major and minor number manually, and letting my builds generates build and revision ones. For build number I want to be able to generate a unique number each build, a sequential generator will be fine; but for revision number I want to use the changeset used to generate the build. To accomplish this we need essentially four macro steps.

In the first step I need to generate unique integer build number, most of the time sequential generator is ok, then I need also to find a way to correlate this generated number with the build label of the TFS. Step two is used to find latest changeset, then in step three we need to check-in modified files (the one used by the generator), being sure that this check-in does not trigger another build, finally we need to modify a file named ProjectVersion.cs that is used by all projects.

To modify AssemblyFileVersion for a project I love this technique: I remove AssemblyFileVersion and AssemblyVersion attributes from assemblyinfo.cs, put them in a single ProjectVersion.cs file stored in the root of the team project. Here is a typical content for the ProjectVersion.cs .

using System.Reflection;
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]

Next I import this file as link in every project that belong to this team project

image

With this little trick to change AssemblyFileVersion attribute for every project I need only to change one file. Now it is time to build some custom tasks that will help us to manage the whole process, first of all the task that generates unique numbers and correlate them with build label.

public class BuildVersionNumberManagerTask : Task
{
    /// <summary>
    /// Filename that will store the versions
    /// </summary>
    [Required]
    public String VersionFileName { get; set; }

    /// <summary>
    /// label of the current build.
    /// </summary>
    [Required]
    public String BuildLabel { get; set; }

    /// <summary>
    /// new incremental version numer.
    /// </summary>
    [Output]
    public Int32 NewVersionNumber { get; set; }

    public override bool Execute()
    {
        if (!File.Exists(VersionFileName))
        {
            Log.LogError("The file {0}[{1}] with version number does not exists", VersionFileName, Path.GetFullPath(VersionFileName));
            return false;
        }
        String[] allLines = File.ReadAllLines(VersionFileName);
        String startChar = "";
        if (allLines.Length > 0 && !String.IsNullOrEmpty(allLines[allLines.Length - 1]))
        {
            startChar = "\n";
            String lastline = allLines[allLines.Length - 1];
            String lastNum = lastline.Substring(0, lastline.IndexOf("|"));
            Int32 lastNumber;
            if (!Int32.TryParse(lastNum, out lastNumber))
            {
                Log.LogError("There are errors in the version file, the last line does not contains lastnum|lastlabel valid format");
                return false;
            }
            NewVersionNumber = lastNumber + 1;
        } else
        {
            //There is no lines, or the last line is empty.
            NewVersionNumber = 1;
        }
        String newLastLine = String.Format("{0}{1}|{2}",startChar,  NewVersionNumber, BuildLabel);
        File.AppendAllText(VersionFileName, newLastLine);
        return true;
    }
}

The BuildVersionNumberManagerTask is responsible of the generation of a sequential number, as well as storing in a file the relationship between autogenerated numbers and build labels. It use a simple text file and write a line for each generated number. Here is an example of file content.

...
3|Standard build for CI_20090820.20
4|Standard build for CI_20090820.21
5|Standard build for CI_20090820.24
...

With such a technique, we can immediately find the build label associated with each auto generated number. Now if you have problem with an assembly that have 4 as the build number, I immediately find in the file that it was build by “Standard build for CI_20090820.21” Here is how I call this task into TFSBuild.proj file

<PropertyGroup>
        <tfTool>"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\tf.exe"</tfTool>
        ...
<Target Name="BeforeCompile" Condition=" '$(IsDesktopBuild)' != 'true' ">
        <Message Text="Beginning generation of new AssemblyFileVersionAttribute" />
        <Exec Command="$(tfTool) checkout ..\sources\src\StandardBuildForCIVersion.txt"  />
        <BuildVersionNumberManagerTask
            VersionFileName="..\sources\src\StandardBuildForCIVersion.txt"
            BuildLabel="$(BuildNumber)">
            <Output TaskParameter="NewVersionNumber" PropertyName="NewVersionNumber" />
        </BuildVersionNumberManagerTask>

I override the BeforeCompile task, since I need to change version number before the compile phase. As you can see I use the Exex command to call the tf.exe tool to checkout the file StandardBuildForCIVersion.txt, the one used to store autogenerated numbers. In this way I can use a different file for each build definition or I can use same file for different builds, I have great flexibility. The checkout is needed because I want to check in modified file at the end of the process, in this way the autogenerated number can be accessed from other build agents. Then I need another custom task capable to modify the ProjectVersion.cs file.

public class AssemblyInfoVersionManagerTask : Task
{
    [Required]
    public String AssemblyInfoFileName { get; set; }

    [Required]
    public Int32 NewBuildNumber { get; set; }

    [Required]
    public Int32 NewRevisionNumber { get; set; }


    public override bool Execute()
    {
        if (!File.Exists(AssemblyInfoFileName))
        {
            Log.LogError("The file {0} does not exists", AssemblyInfoFileName);
            return false;
        }
        String filecontent = File.ReadAllText(AssemblyInfoFileName);
        Match curVersionMatch =
            Regex.Match(filecontent, "AssemblyVersion\\(\"(?<curversion>.*?)\"\\)");
        if (!curVersionMatch.Success)
        {
            Log.LogError("The content of file {0} does not contains valid Assemblyversion attribute");
            return false;
        }
        String curversion = curVersionMatch.Groups["curversion"].Value;
        String[] versionParts = curversion.Split('.');
        String newVersion = String.Format("{0}.{1}.{2}.{3}", versionParts[0], versionParts[1], NewBuildNumber, NewRevisionNumber);
        String newFileContent = Regex.Replace(
            filecontent,
            "AssemblyFileVersion\\(\"(?<curversion>.*?)\"\\)",
            String.Format(@"AssemblyFileVersion(""{0}"")", newVersion));
        FileAttributes currentFileAttributes = File.GetAttributes(AssemblyInfoFileName);
        File.SetAttributes(AssemblyInfoFileName, FileAttributes.Normal);
        File.WriteAllText(AssemblyInfoFileName, newFileContent);
        File.SetAttributes(AssemblyInfoFileName, currentFileAttributes);
        return true;
    }
}

This is another simple task, it uses a little bit of regular expression to find actual version number stored in the ProjectVersion.cs, then it creates another version number with simple composition. Major and minor number are taken from the original content, while build and revision are passed by the caller. Remember also to remove the Readonly attribute from the file, because it is usually readonly since it is under version control. Now I need the last piece, a simple TfTask that permits me to grab the changeset number associated with the current project.

public class TfTask : ToolTask
{
    public TfTask()
    {
        ToolPath = @"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\";
    }

    [Required]
    public String Operation { get; set; }

    [Output]
    public String TfOutput { get; set; }

    public String TfsUrl { get; set; }

    public String TeamProject { get; set; }

    protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
    {
        TfTaskOperation operation = (TfTaskOperation)Enum.Parse(typeof(TfTaskOperation), Operation);
        String commandline;
        switch (operation)
        {
            case TfTaskOperation.GetLatestChangeset:
                commandline = String.Format(
                    "history /s:{0} /stopafter:1 /noprompt /recursive /version:T $/{1}", TfsUrl, TeamProject);
                break;
            default:
                throw new NotSupportedException();
        }
        using (System.Diagnostics.Process process = new System.Diagnostics.Process())
        {
            process.StartInfo.FileName = Path.GetFullPath(pathToTool);
            process.StartInfo.Arguments = commandline;
            process.StartInfo.WorkingDirectory = Path.GetDirectoryName(pathToTool);
            process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
            process.StartInfo.UseShellExecute = false;
            //process.StartInfo.ErrorDialog = false;
            //process.StartInfo.CreateNoWindow = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.Start();
            process.WaitForExit();
            TfOutput = process.StandardOutput.ReadToEnd();
        }
        return 0;
    }

    protected override string GenerateFullPathToTool()
    {
        throw new NotImplementedException();
    }

    protected override string ToolName
    {
        get { return "tf.exe"; }
    }
}

public enum TfTaskOperation
{
    GetLatestChangeset = 0,
}

This is a simple wrapper to the tf.exe tool, and since I invoke it with the System.Diagnostic.Process class, I’m able to intercept the output. There are a lot of possibilities on how to get latest changeset, but the simplest is to invoke tf.exe with a command line like this: tf.exe history /s:http://tfsalkampfer:8080 /stopafter:1 /noprompt /recursive /version:T $/MsBuildExtension

This This command gives a result like this one.

Changeset User          Date       Comment
--------- ------------- ---------- ---------------------------------
129       TfsService    8/21/2009  ***NO_CI***

That can be parsed with a simple regular expression, here is the remaining of the build script.

<TfTask 
    TfsUrl="$(TeamFoundationServerUrl)" 
    TeamProject="$(TeamProject)" 
    Operation="GetLatestChangeset">
    <Output    TaskParameter="TfOutput" PropertyName="historyOutput"/>
</TfTask>

<Message Text="historyOutput is $(historyOutput)" />

<RegexTask TextToMatch="$(historyOutput)" Regex="\n(?&lt;changeset&gt;\d+)" FailIfNoMatch="true" GroupName="changeset">
    <Output
         TaskParameter="Result"
         PropertyName="lastChangeset"/>
</RegexTask>

<AssemblyInfoVersionManagerTask
    AssemblyInfoFileName="..\sources\src\ProjectVersion.cs"
    NewBuildNumber="$(NewVersionNumber)"
    NewRevisionNumber="$(lastChangeset)">
</AssemblyInfoVersionManagerTask>

<Exec Command='$(tfTool) checkin /comment:"***NO_CI***" ..\sources\src\StandardBuildForCIVersion.txt' />

IT is quite simple, I first invoke the TfTask, then I use a simple RegexTask to find the changeset number from tf output. Thanks to TfTask custom task, the script remains simple, I need only to specify tfsurl, team project name and the operation I want to execute (in this situation GetLatestChangeset), and the task will store in the historyOutput property the full output of the tf.exe command.

Finally I use my AssemblyInfoVersionManager task to change the ProjectVersion.cs file and finally I do a check-in of the file that contains the new generated sequential build number with comment ***NO_CI*** to avoid going in loop with continuos integration engine.

After a build you can verify that everything is gone ok.

image

Now if you deploy this assembly into a customer computer, if you have a problem in the future, you can immediately verify that this was compiled with the build called 8, then you check the StandardBuildForCIVersion.txt

7|Standard build for CI_20090820.37
8|Standard build for CI_20090820.39
9|Standard build for CI_20090821.2

You can immediately find the label of the build, but the most important thing in my opinion is the revision number, 128 in the picture above, because it is the changeset that generates this assembly, to replicate and debug any problem, you can simply do a Get Specific Version of that changeset, and you can work with the exact source code and build tools that generates that assembly.

This example shows how simple can be extending build process with the creation of some ad-hoc tasks and a good use of the tf.exe command line tool.

A zip of all the repository can be downloaded here.

Alk.

Tags:

Problem with TFS2010 Beta, error TF31002

I’have a virtual machine with TFS2010 Beta, everything was ok, I’ve not worked with it for the last month, this morning I fire the virtual machine again, open visual studio and found that the TFS is not working. It gave me error TF31002, so I begin to investigate the reason for failure.

When TFS does not work the first thing you should try is to access it from the web interface http://tfs2010/tfs/web when I goes to this page it gave me this error.

Team Foundation services are not available from the server . Technical information (for administrator): TF31002: Unable to connect to this Team Foundation Server: http://tfs2010/tfs. Team Foundation Server Url: http://tfs2010/tfs. Possible reasons for failure include: – The Team Foundation Server name, port number or protocol is incorrect. – The Team Foundation Server is offline. – Password is expired or incorrect. For further information, contact the Team Foundation Server administrator.

It is possible that this is caused by authentication problems, you should verify in IIS that the site is running under the right account (in my situation the TfsServiceAccount user), then you should verify that that user does not have an expired password. I’ve immediately tried to access the system with TfsServiceAccount user, and everything is ok. Then verified that all application pool used by the TFS web site runs under the TfsServiceAccount. Everything was ok so I was a little bit puzzled on the reason why tfs does not work.

After a brief check I verify that the folder C:\Program Files\Microsoft Team Foundation Server 10.0\Microsoft Team Foundation Server 2010 Beta 1 – ENU does not grant right access to the TfsServiceAccount user, even if Administrators group has permissions to read and write to the folder. I added TfsServiceAccount with Full Control permission, and everything started again.

I do not know exactly why it happened, but if you encounter the same problem this post maybe can help you.

Alk.

Tags:

Playing with label in TFS

I just received a question in a old post, the question is if we can generate builds against any applied label on source code in TFS. Tfs build are configured to always retrieve by default the latest code from source control, but you can configure it with no problem.

All you need is to override the BeforeGet target, and set the SkipGet property to true, in this way you can have complete control on the process of getting the source to compile, you can even grab source from other type of repository, like subversion. With this technique you can rebuild a specific label whenever you want.

But the real interesting stuff you can do is getting the source code associated with a label. When a build is triggered it labels the source code with the build number, so you can always get the source code that generates that build. To obtain this you can simply to a Get Specific Version

image

Now you must select to get a specific version by label (step 1), then ask to browse all labels (step2) and finally press the find button (step3) to have a list of every build associated to this source control

image 

Selecting a specific label will revert your local source to the one used during the build, so you can open the solution and do whatever you want. This is especially useful if a tester had signaled a bug in a specific build, and you want to replicate it with the exact source code used for the build.

You can do the same code from command prompt with tf get itemspec command, so you can automate it inside a script, this permits you to grab in any time the exact source code that generate a build, so you will be able to replicate bugs, or inspect the actual code that is running into customer machine, just knowing the build label.

If you want to play nicely with label you can give a look at Team Foundation Sidekick it permits you to search, browse labels and look at what version control each file is respect to the label. Remember always that tfs labels are different from SourceSafe from sidekick you can verify that a single label does not correspond to a point in time.

alk.

Tags: