给.Net项目编译的程序集加入版本号的方式有许多种,包括:
1. 默认的方式,在每个项目的AssemblyInfo.cs文件中指定版本号:
1 // Version information for an assembly consists of the following four values: 2 // 3 // Major Version 4 // Minor Version 5 // Build Number 6 // Revision 7 // 8 // You can specify all the values or you can default the Build and Revision Numbers 9 // by using the ‘*‘ as shown below: 10 // [assembly: AssemblyVersion("1.0.*")] 11 [assembly: AssemblyVersion("1.0.0.0")] 12 [assembly: AssemblyFileVersion("1.0.0.0")]
2. 使用IDE的插件方式定制版本号增加的方式:
从上图中可以看到,这个插件可以通过根据时间信息来生产每一位的版本号
3. 通过T4模板在每次编译解决方案时更新项目的版本号
T4模板是一种在设计阶段帮助开发人员按照一定规则生产代码的机制,在IDE中可以添加:
具体的介绍可以参考MSDN上面的:
http://msdn.microsoft.com/en-us/library/bb126445(v=VS.100).aspx
我们在这里使用T4模板的目的就是让它根据当前的subversion上的revision number生产版本号
using System.Reflection; [assembly: AssemblyVersion("1.0.0.7162")] [assembly: AssemblyFileVersion("1.0.0.7162")]
其中最后一位是当前的SvnRevisionNumber
具体操作方法如下:
(1)为了使用T4模板,需要安装Visual Studio 2010 SP1 SDK和Visual Studio 2010 SP1 Visualization and Modeling SDK
Visual Studio 2010 SP1 SDK的下载地址:
http://visualstudiogallery.msdn.microsoft.com/25622469-19d8-4959-8e5c-4025d1c9183d
Visual Studio 2010 SP1 Visualization and Modeling SDK的下载地址:
http://www.microsoft.com/en-us/download/details.aspx?id=23025
(2)在需要共享同一个版本号的解决方案中新增一个新的项目
(3)编辑这个项目文件,加入如下内容:
(4)在这个新项目中添加一个T4模板文件
(5)编辑这个扩展名为tt的模板, 内容如下:
1 <#@ template hostspecific="true" language="C#" #> 2 <#@ output extension=".cs" #> 3 <#@ Assembly name="E:\trunk\src\BuildVersion\SSvnEx-1.7002.1998\SharpSvn\SharpSvn.dll" #> 4 <#@ import namespace="System.IO" #> 5 <#@ import namespace="SharpSvn" #> 6 <# 7 string RevisionNumber = String.Empty; 8 SvnInfoEventArgs args; 9 SvnPathTarget target = SvnPathTarget.FromString(@"E:\trunk\src"); 10 try 11 { 12 new SvnClient().GetInfo(target, out args); 13 RevisionNumber = args.LastChangeRevision.ToString(); 14 } 15 catch (SvnWorkingCopyPathNotFoundException) 16 { 17 RevisionNumber = "0"; 18 } 19 #> 20 // 21 // This code was generated by a tool. Any changes made manually will be lost 22 // the next time this code is regenerated. 23 // 24 25 using System.Reflection; 26 27 [assembly: AssemblyVersion("1.0.0.<#= RevisionNumber #>")] 28 [assembly: AssemblyFileVersion("1.0.0.<#= RevisionNumber #>")]
上面的代码使用SharpSvn类库来读取指定目录下的svn信息,并将LastChangeRevision作为最后一位的版本号。
注:
这里的<#@ Assembly name="E:\trunk\src\BuildVersion\SSvnEx-1.7002.1998\SharpSvn\SharpSvn.dll" #>
引用外部dll时,T4模板不能使用相对路径
可以使用如下5中方式指定程序集的引用:
a. 将T4需要引用的模板加入到GAC中,之后在T4中就可以直接写上程序集的名字了:
<#@ Assembly name="SharpSvn.dll" #>
b. 使用绝对路径指定程序集名字:
<#@ Assembly name="E:\trunk\src\BuildVersion\SSvnEx-1.7002.1998\SharpSvn\SharpSvn.dll" #>
c. 将程序集拷贝到Visual Studio的 "Public Assemblies Folder"中,之后在T4中就可以直接写上程序集的名字了:
<#@ Assembly name="SharpSvn.dll" #>
d. 使用一个环境变量来代替绝对路径:
<#@ Assembly name="%mylib%\SharpSvn.dll" #>
e. 使用Visual Studio Macro来构建程序集路径:
<#@ Assembly name="$(SolutionDir)lib\SharpSvn.dll" #>
(6)编译这个包含T4模板的项目,或者保存这个模板文件,都会触发T4生成一个cs文件,内容如下:
(7)在其他项目中添加这个生成的cs文件,以link的方式添加:
(8)注释掉这些项目原有的版本信息:
到此为止,所有引用了这个BuildVersion.cs文件的项目都会根据这个文件的内容生产版本号了,而这个版本号是来自于指定svn路径下的revision number。
4. 通过定制一个MSBuild Task,在每次编译时更新版本号信息。
MSBuild提供对复杂编译任务的可定制,我们通过继承代表一次编译任务的Task类,并重写其Execute方法来定制自己的编译任务。
(1)首先,我们需要建立一个Class Library类型的项目,并引用下面的程序集:
(2)继承Task类,并实现自己的Execute方法:
下面的代码通过读取含有版本号信息的cs文件,使用正则表达式解析出四位版本号,并实现我们自己的版本号增加逻辑,这里的逻辑是根据Jenkins的环境变量,将版本号第三位置为Jenkins的BuildNumber,将第四位置为SVN Revison号。
注:这里在处理来自svn的revision number时,当其大于UInt16.MaxValue时,我们舍弃其最高位,因为Assembly Version的值需要在0~UInt16.MaxValue之间。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Microsoft.Build.Utilities; 6 using System.IO; 7 using System.Text.RegularExpressions; 8 9 namespace VersionInfoBuildTask 10 { 11 public class AutoIncrementTask : Task 12 { 13 private const string AssemlyVersionPattern = 14 @"\[assembly: AssemblyVersion\(\""(\d{1}).(\d{1}).(\d{1,}).(\d{1,})""\)\]"; 15 16 private const string AssemlyFileVersionPattern = 17 @"\[assembly: AssemblyFileVersion\(\""(\d{1}).(\d{1}).(\d{1,}).(\d{1,})""\)\]"; 18 19 private const string AssemblyInformationalVersionPattern = 20 @"\[assembly: AssemblyInformationalVersion\(\""(\d{1}).(\d{1}).(\d{1,}).(\d{1,})""\)\]"; 21 22 public string AssemblyInfoPath { get; set; } 23 24 public override bool Execute() 25 { 26 try 27 { 28 if (String.IsNullOrEmpty(AssemblyInfoPath)) 29 throw new ArgumentException("AssemblyInfoPath must have a value"); 30 31 string[] content = File.ReadAllLines(AssemblyInfoPath, Encoding.Default); 32 var rxForAssemlyVersion = new Regex(AssemlyVersionPattern); 33 var rxForFileVersion = new Regex(AssemlyFileVersionPattern); 34 var rxForInfoVersion = new Regex(AssemblyInformationalVersionPattern); 35 36 var newContent = new List<string>(); 37 content.ToList().ForEach(line => 38 { 39 if (rxForAssemlyVersion.IsMatch(line)) 40 { 41 line = VersionMatcherForAssemlyVersion(rxForAssemlyVersion.Match(line)); 42 Console.Out.WriteLine("Match result for assembly version is: " + line); 43 } 44 else if (rxForFileVersion.IsMatch(line)) 45 { 46 line = VersionMatcherForFileVersion(rxForFileVersion.Match(line)); 47 Console.Out.WriteLine("Match result for file version is: " + line); 48 } 49 else if (rxForInfoVersion.IsMatch(line)) 50 { 51 line = VersionMatcherForInfoVersion(rxForInfoVersion.Match(line)); 52 Console.Out.WriteLine("Match result for info version is: " + line); 53 } 54 else 55 { 56 Console.Out.WriteLine(line + " didn‘t match!!"); 57 } 58 newContent.Add(line); 59 }); 60 61 File.WriteAllLines(AssemblyInfoPath, newContent.ToArray()); 62 } 63 catch (Exception ex) 64 { 65 Console.Out.WriteLine(ex); 66 return false; 67 } 68 69 return true; 70 } 71 72 private string VersionMatcherForFileVersion(Match match) 73 { 74 int major = int.Parse(match.Groups[1].Value); 75 int minor = int.Parse(match.Groups[2].Value); 76 int build = int.Parse(match.Groups[3].Value); 77 string revision = match.Groups[4].Value; 78 79 Console.WriteLine("AutoIncrement Assembly {0}", 80 Path.GetFileName(AssemblyInfoPath)); 81 Console.WriteLine("Current matched version: {0}.{1}.{2}.{3}", 82 major, minor, build, revision); 83 84 string buildNumber = Environment.GetEnvironmentVariable("BUILD_NUMBER"); 85 if (buildNumber == null) buildNumber = "0"; 86 87 string revisionNumber = Environment.GetEnvironmentVariable("SVN_REVISION"); 88 if (revisionNumber == null) revisionNumber = "0"; 89 if (long.Parse(revisionNumber) > UInt16.MaxValue) 90 { 91 Console.WriteLine("The revision number is too big"); 92 long rev = long.Parse(revisionNumber); 93 rev = rev % 10000; 94 revisionNumber = rev.ToString(); 95 } 96 97 Console.WriteLine("Incremented to version: {0}.{1}.{2}.{3}", 98 major, minor, build, revision); 99 100 string result = match.Result 101 ("[assembly: AssemblyFileVersion(\"$1.$2.{0}.{1}\")]"); 102 return String.Format(result, buildNumber, revisionNumber); 103 } 104 } 105 }
(3)准备一个proj文件,这个文件是运行msbuild时需要指定的,主要是告知msbuild实现定制任务的程序集在哪里,和定制任务的输入文件在哪里:
1 <?xml version="1.0" encoding="utf-8" ?> 2 <Project ToolsVersion="4.0" DefaultTargets="IncrementBuild" 3 xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 4 <UsingTask AssemblyFile="VersionInfoBuildTask.dll" TaskName="AutoIncrementTask" /> 5 <Target Name="IncrementBuild"> 6 <AutoIncrementTask AssemblyInfoPath="SharedAssemblyInfo.cs" /> 7 </Target> 8 </Project>
(4)准备一个包含程序集版本信息的文件,这里的SharedAssemblyInfo.cs就是将要传给定制来任务处理的文件,内容如下:
(5)将SharedAssemblyInfo.cs文件加入到解决方案中,其中的所有项目都使用Add as link方式加入这个文件,以达到共享版本信息的目的 :
(6)将编译好的VersionInfoBuildTask.dll文件,与这个proj文件都放到Jenkins的workspace目录中:
并使用下面的命令来运行这个定制的Task:
msbuild BuildTask.proj /t:IncrementBuild
运行之后,查看SharedAssemblyInfo.cs的内容,发现版本号已经发生了改变:
好了,下面就可以使用msbuild来编译整个解决方案了,之后我们查看编译出来的程序集的版本号:
版本号最后两位对应Jenkins上的build number和revision number(舍去最高位),这样做是方便查询每个版本所对应的代码变更信息: