65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 VS 2010 和 MSBUILD 任务进行自定义程序集版本控制

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (13投票s)

2011年10月23日

CPOL

4分钟阅读

viewsIcon

47993

downloadIcon

1085

使用 Visual Studio 2010 和 MSBUILD 的 MSBUILD 任务自动递增和自定义程序集版本控制。

目录

引言和背景

有大量资源涉及自动递增的构建号,以及大量的插件和其他管理程序集版本控制的工具。那么为什么还要再写一篇文章呢?事实是,这些选项都无法解决我的问题。我需要一些简单、易于维护,最重要的是具有本地 .NET 风格的灵活解决方案。所以,很自然地,我必须写我自己的一个简单解决方案。

我们在部署程序集时经常面临的一项艰巨任务是管理程序集和产品版本。在任何“体面”的 .NET 解决方案中,都需要在每次成功构建时自动递增版本。例如,在默认方案中递增程序集版本的构建部分

Major.Minor.Build.Revision

有时,版本控制要求更加复杂和苛刻,我们可能希望附加构建日期,同时递增构建号,版本方案可能看起来像

Major.Minor.Build.YYMMDD

为此,我更喜欢使用 AssemblyFileVersion 而不是 AssemblyVersion 。前者格式开放,几乎可以容纳任何附加信息,而后者 AssemblyVersion 则供 .NET 框架使用,并强制执行严格的编号方案,如果违反该方案,则会导致编译器错误。

[assembly: AssemblyVersion("4.0.*")] //Strict Format, for framework use
[assembly: AssemblyFileVersion("4.0.20.110708")] 	//Flexible Format more suitable 
						//for product versions

本文介绍如何利用 MSBUILD 来自动化此过程并将其包含在持续集成管道中。

管理多个 assembly.cs 文件的技巧

在具有多个项目和相应多个 assembly.cs 文件(其中包含 AssemblyVersion AssemblyFileVersion )的解决方案中,最好有一个这样的文件由所有程序集共享。为了实现这一点,我们使用一个非常有用的 Visual Studio 功能,即**添加现有项为链接**。

Add_as_Link_in_VS2010.png

我们使用此技术创建单个程序集信息文件,以供解决方案中的所有项目共享。此共享文件将包含 AssemblyVersion AssemblyFileVersion 程序集属性以及任何解决方案范围的值,而各个项目级别的 assembly.cs 将包含项目特定的信息,例如

using System.Reflection;
using System.Runtime.InteropServices;
 
[assembly: AssemblyTitle("POC.AssemblyVersioning.Common")]
[assembly: AssemblyCulture("")]
 
[assembly: Guid("bdf2e8b4-41aa-4569-b093-987439090dea")]

以下是我们使用建议方案实现后解决方案结构的示例……所有这些都是为了方便自动修改程序集版本。

solution_view.png

现在,让我们进入更有趣的部分。

构建自定义 MSBUILD 任务

我更喜欢在构建自动化中使用自定义任务而不是其他脚本解决方案的原因在于,构建任务是纯 C# 类,您可以使用 .NET 框架提供的强大功能。要创建构建 Task ,您只需要

  • 添加对所需 .NET 程序集的引用
  • references.png

  • 创建一个扩展 Microsoft.Build.Utilities.Task 的类

    public class MyTask : Task
    {
        // The only behaviour that need be overridden
        public override bool Execute()
        {
            throw new NotImplementedException();
        }
    }
  • 在符合 msbuild 的文件中添加对自定义任务的引用。任何引用以下命名空间 http://schemas.microsoft.com/developer/msbuild/2003 的 XML 文件都是有效的 msbuild 文件。以下是一个引用自定义任务的示例文件

    <?xml version="1.0" encoding="utf-8" ?>
    <Project ToolsVersion="4.0" DefaultTargets="IncrementBuild" 
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <UsingTask AssemblyFile="bin\debug\POC.AssemblyVersioning.BuildTask.dll" 
    	TaskName="AutoIncrementTask" />
        <Target Name="IncrementBuild">
        <AutoIncrementTask AssemblyInfoPath="..\SharedAssemblyInfo.cs" />
      </Target>
    </Project>

并且要从命令行简单地调用此任务,我们使用这个熟悉的语法,假设 msbuild 文件已保存为 sample.proj

msbuild sample.proj /t:AutoIncrement 

整合所有内容!

在熟悉了 MSBUILD 任务并考虑使用一个共享的程序集文件信息来托管 AssemblyVersion AssemblyFileVersion 属性后,我在这里介绍一种通过这个简单的自定义任务来更新这些属性的简单方法。

该任务基于文件 I/O,解析共享程序集信息的内容,更新(或转换为所需格式)属性,然后将内容写回文件。

这是将执行文件操作的简单构建任务的源代码。**这仅用于说明目的,仅作为创建自己的构建任务的指南**。

我鼓励您。

public class AutoIncrementTask : Task
{
    private const string VersionPattern = 
	@"\[assembly: AssemblyFileVersion\(\""(\d{1}).(\d{1}).(\d{1,}).(\d{6})""\)\]";
    public string AssemblyInfoPath { get; set; }
        
    public override bool Execute()
    {
        try
        {
            if (String.IsNullOrEmpty(AssemblyInfoPath))
                throw new ArgumentException("AssemblyInfoPath must have a value");

            string[] content = File.ReadAllLines(AssemblyInfoPath, Encoding.Default);
            var rx = new Regex(VersionPattern);

            var newContent = new List<string>();
            content.ToList().ForEach(line =>
                                            {
                                                if (rx.IsMatch(line))
                                                    line = VersionMatcher(rx.Match(line));
                                                newContent.Add(line);
                                            });

            File.WriteAllLines(AssemblyInfoPath, newContent);

        }
        catch(Exception ex)
        {
            Console.Out.WriteLine(ex);
            return false;
        }

        return true;
    }

    private string VersionMatcher(Match match)
    {            
        int major = int.Parse(match.Groups[1].Value);
        int minor = int.Parse(match.Groups[2].Value);
        int build = int.Parse(match.Groups[3].Value);
        string revision = match.Groups[4].Value;

        Console.WriteLine("AutoIncrement Assembly {0}", 
			Path.GetFileName(AssemblyInfoPath));
        Console.WriteLine("Current matched version: {0}.{1}.{2}.{3}", 
			major, minor, build, revision);

        ++build;
        revision = String.Format("{0}{1:d2}{2:d2}", 
            DateTime.Now.Year.ToString().Substring(2), 
            DateTime.Today.Month, 
            DateTime.Today.Day);
        Console.WriteLine("Incremented to version: {0}.{1}.{2}.{3}", 
			major, minor, build, revision);

        string result = match.Result
			("[assembly: AssemblyFileVersion(\"$1.$2.{0}.{1}\")]");
        return String.Format(result, build, revision);
    }
}

这是相应的 XML MSBUILD 文件

<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" DefaultTargets="IncrementBuild" 
	xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask AssemblyFile="PATH_TO_TASK_DLL" TaskName="AutoIncrementTask" />
  <Target Name="IncrementBuild">
    <AutoIncrementTask AssemblyInfoPath="..\SharedAssemblyInfo.cs" />
  </Target>
</Project>

如您所见,我选择了 AssemblyFileVersion 作为自定义产品和程序集版本控制的目标,因为其版本格式没有编译时检查,并且产品版本比 .NET 框架内部使用的自动构建号更受最终用户和业务利益相关者的欢迎。

您可以将前面的构建任务包含在构建过程中,以利用 MSBUILD 的命令行功能。

示例代码

我包含了一个示例 Visual Studio 2010 解决方案(.NET 4.0),其中包含自定义构建任务的代码,以及用于演示实现共享程序集文件时的解决方案结构的虚拟项目

历史记录

  • 2011 年 8 月 7 日,最初发布在我的博客 C# | B# | Stay#
  • 2011 年 10 月 22 日,为 CodeProject 修订
© . All rights reserved.