views:

3139

answers:

3

I have a requirement to install multiple web setup projects (using VS2005 and ASP.Net/C#) into the same virtual folder. The projects share some assembly references (the file systems are all structured to use the same 'bin' folder), making deployment of changes to those assemblies problematic since the MS installer will only overwrite assemblies if the currently installed version is older than the one in the MSI.

I'm not suggesting that the pessimistic installation scheme is wrong - only that it creates a problem in the environment I've been given to work with. Since there are a sizable number of common assemblies and a significant number of developers who might change a common assembly but forget to update its version number, trying to manage versioning manually will eventually lead to massive confusion at install time.

On the flip side of this issue, it's also important not to spontaneously update version numbers and replace all common assemblies with every install, since that could (temporarily at least) obscure cases where actual changes were made.

That said, what I'm looking for is a means to update assembly version information (preferably using MSBuild) only in cases where the assembly constituents (code modules, resources etc) has/have actually changed.

I've found a few references that are at least partially pertinent here (AssemblyInfo task on MSDN) and here (looks similar to what I need, but more than two years old and without a clear solution).

My team also uses TFS version control, so an automated solution should probably include a means by which the AssebmlyInfo can be checked out/in during the build.

Any help would be much appreciated.

Thanks in advance.

+6  A: 

I cannot answer all your questions, as I don't have experience with TFS.

But I can recommend a better approach to use for updating your AssemblyInfo.cs files than using the AssemblyInfo task. That task appears to just recreate a standard AssemblyInfo file from scratch, and loses any custom portions you may have added.

For that reason, I suggest you look into the FileUpdate task, from the MSBuild Community Tasks project. It can look for specific content in a file and replace it, like this:

<FileUpdate 
Files="$(WebDir)\Properties\AssemblyInfo.cs"
Regex="(\d+)\.(\d+)\.(\d+)\.(\d+)"
ReplacementText="$(Major).$(ServicePack).$(Build).$(Revision)" 
Condition="'$(Configuration)' == 'Release'"
/>

There are several ways you can control the incrementing of the build number. Because I only want the build number to increment if the build is completely successful, I use a 2-step method:

  • read a number from a text file (the only thing in the file is the number) and add 1 without changing the file;
  • as a final step in the build process, if everything succeeded, save the incremented number back to the text file.

There are tasks such as ReadLinesFromFile, that can help you with this, but I found it easiest to write a small custom task:

using System;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace CredibleCustomBuildTasks
{
    public class IncrementTask : Task
    {
     [Required]
     public bool SaveChange { get; set; }

     [Required]
     public string IncrementFileName { get; set; }

     [Output]
     public int Increment { get; set; }

     public override bool Execute()
     {
      if (File.Exists(IncrementFileName))
      {
       string lines = File.ReadAllText(IncrementFileName);
       int result;
       if(Int32.TryParse(lines, out result))
       {
        Increment = result + 1;
       }
       else
       {
        Log.LogError("Unable to parse integer in '{0}' (contents of {1})");
        return false;
       }
      }
      else
      {
       Increment = 1;
      }

      if (SaveChange)
      {
       File.Delete(IncrementFileName);
       File.WriteAllText(IncrementFileName, Increment.ToString());
      }
      return true;
     }
    }
}

I use this before the FileUpdateTask to get the next build number:

<IncrementTask 
IncrementFileName="$(BuildNumberFile)" 
SaveChange="false">
  <Output TaskParameter="Increment" PropertyName="Build" />
</IncrementTask>

and as my final step (before notifying others) in the build:

<IncrementTask 
IncrementFileName="$(BuildNumberFile)" 
SaveChange="true"
Condition="'$(Configuration)' == 'Release'" />

Your other question of how to update the version number only when source code has changed is highly dependent on your how your build process interacts with your source control. Normally, checking in source file changes should initiate a Continuous Integration build. That is the one to use to update the relevant version number.

David White
A: 

There's a great article on CodeProject: Versioning Controlled Build

It includes a Visual Studio plugin that will allow you to automagically change all of the version numbers in all of a solution's projects (including c++ and vdproj files). I don't know if it can be executed via MSBUILD, but its worth looking into.

dviljoen
A: 

I have written one custome task you can refer the code below. It will create an utility to which you can pass assemblyinfo path Major,minor and build number. you can modify it to get revision number. Since in my case this task was done by developer i used to search it and again replace whole string.


enter code here

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.IO;

using System.Text.RegularExpressions;

namespace UpdateVersion

{

class SetVersion
{
    static void Main(string[] args)
    {
        String FilePath = args[0];

        String MajVersion=args[1];

        String MinVersion = args[2];

        String BuildNumber = args[3];

        string RevisionNumber = null;

        StreamReader Reader = File.OpenText(FilePath);

        string contents = Reader.ReadToEnd();
        Reader.Close();

        MatchCollection match = Regex.Matches(contents, @"\[assembly: AssemblyVersion\("".*""\)\]", RegexOptions.IgnoreCase);
     // MatchCollection match = Regex.Matches(contents, "(^[//]?)\\[assembly: AssemblyVersion\\(\"\\d*\\.\\d*\\.\\d*\"\\)\\]", RegexOptions.IgnoreCase);
        if (match[0].Value != null)
        {
            string strRevisionNumber = match[0].Value;

// RevisionNumber = strRevisionNumber.Substring((strRevisionNumber.LastIndexOf(".")+1),str) ;

            RevisionNumber = strRevisionNumber.Substring(strRevisionNumber.LastIndexOf(".") + 1, (strRevisionNumber.LastIndexOf("\"")-1) - strRevisionNumber.LastIndexOf("."));
            String replaceWithText = String.Format("[assembly: AssemblyVersion(\"{0}.{1}.{2}.{3}\")]", MajVersion, MinVersion, BuildNumber, RevisionNumber);
            string newText = Regex.Replace(contents, @"\[assembly: AssemblyVersion\("".*""\)\]", replaceWithText);
            StreamWriter writer = new StreamWriter(FilePath, false);
            writer.Write(newText);
            writer.Close();
        }
        else
        {
            Console.WriteLine("No matching values found");
        }
        }
}

}