如何在Visual Studio中实现自动递增的版本号?

538

我想在构建时自动递增一组整数并将其存储:

int MajorVersion = 0;
int MinorVersion = 1;
int Revision = 92;
当我编译时,它会自动递增“Revision”。当我构建安装程序时,它会递增“MinorVersion”(我可以手动完成此操作)。 “MajorVersion”只能手动递增。然后我可以在菜单“帮助/关于”中向用户显示版本号,格式为:

  Version: 0.1.92
如何实现这一点?该问题不仅要求如何实现自动递增版本号,还要求如何在代码中使用它,这比其他答案更完整。

3
尽管这个问题已经有了答案,但Noel Kennedy和Matthieu的回答比其他的问题/回答更有用。 - Peter
9个回答

695

如果你在你的项目中添加一个AssemblyInfo类并将AssemblyVersion属性修改为以星号结尾,例如:

[assembly: AssemblyVersion("2.10.*")]

根据这些规则,Visual studio会为您自动递增最终的版本号(感谢galets,我之前完全错了!)

要在代码中引用此版本以便向用户显示,可以使用reflection。例如,

Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
DateTime buildDate = new DateTime(2000, 1, 1)
                        .AddDays(version.Build).AddSeconds(version.Revision * 2);
string displayableVersion = $"{version} ({buildDate})";

你需要了解的三个重要细节

来自 @ashes999:

值得注意的是,如果同时指定了 AssemblyVersionAssemblyFileVersion,则在你的 .exe 文件上将看不到此版本信息。

来自 @BrainSlugs83:

仅将第四位数字设置为 * 可能会导致问题,因为版本号不会始终递增。 第三位数字是自 2000 年以来的天数,而第四位数字是午夜后经过的秒数(除以 2)[它不是随机的]。因此,如果你一天中晚些时候构建了解决方案,第二天早些时候又构建了解决方案,后者的版本号将比前者小。我建议始终使用 X.Y.* 而不是 X.Y.Z.*,因为这样你的版本号将始终递增。

较新版本的 Visual Studio 给出以下错误信息:

(本主题始于 2009 年)

指定的版本字符串包含通配符,与确定性不兼容。请从版本字符串中删除通配符或为此编译禁用确定性。

请参阅此 Stack Overflow 回答,其中说明如何移除确定性https://dev59.com/dVQJ5IYBdhLWcg3welxn#58101474


56
值得注意的是,如果同时指定了"AssemblyVersion"和"AssemblyFileVersion",则在您的".exe"上将看不到这个信息。 - ashes999
170
仅将第四个数字设置为“*”可能会有问题,因为版本号不会始终递增。第三个数字是自2000年以来的天数,第四个数字是午夜以来的秒数(除以2)[它不是随机的]。如果你在一天的晚上建立解决方案,而第二天早上建立了解决方案,后者的版本号会更早。我建议始终使用“X.Y.*”而不是“X.Y.Z.*”,因为这样你的版本号将始终递增(除非你碰巧从TARDIS内部编译代码——那么我可以去吗?)。 - BrainSlugs83
4
我们能否设置*开始的值,而不是使用自2000年以来的天数? - Bruno
3
你该如何将这个变更提交回源代码管理系统? - Joe Phillips
33
顺便说一下,您实际上不需要编辑和添加汇编信息文件。更简单的方法是转到项目属性,应用程序选项卡,单击“程序集信息”并输入所需的主版本号、次版本号,然后在第三个框中输入*,并将第四个框留空。Visual Studio会负责使用这些信息更新.cs文件。 - Dinesh Rajan
显示剩余21条评论

187

您可以使用Visual Studio中的T4模板机制从简单文本文件生成所需的源代码

我想要为一些.NET项目配置版本信息生成。自从我上次调查可用选项以来已经过了很长时间,所以我四处搜索,希望找到一种简单的方法来做到这一点。我发现的东西看起来并不鼓舞人心:人们编写Visual Studio插件和自定义MsBuild任务仅仅为了获得一个整数(好吧,也许是两个)。对于一个小的个人项目来说,这感觉过度设计。

灵感来自StackOverflow讨论之一,在那里有人建议T4模板可以完成这项工作。当然它们能够胜任。解决方案需要最小的工作量和没有Visual Studio或构建过程定制。以下是应该完成的内容:

  1. 创建一个扩展名为".tt"的文件,并在其中放置将生成AssemblyVersion和AssemblyFileVersion属性的T4模板:
<#@ template language="C#" #>
// 
// This code was generated by a tool. Any changes made manually will be lost
// the next time this code is regenerated.
// 

using System.Reflection;

[assembly: AssemblyVersion("1.0.1.<#= this.RevisionNumber #>")]
[assembly: AssemblyFileVersion("1.0.1.<#= this.RevisionNumber #>")]
<#+
    int RevisionNumber = (int)(DateTime.UtcNow - new DateTime(2010,1,1)).TotalDays;
#>

你需要决定版本号生成算法。对我来说,自动生成一个修订号并将其设置为自2010年1月1日以来的天数就足够了。正如你所看到的,版本生成规则是用普通的C#编写的,因此你可以轻松地根据自己的需求进行调整。 第二步:将上述文件放置在其中一个项目中。我创建了一个新项目,只有这个单独的文件,以使版本管理技术清晰明了。当我构建这个项目(实际上我甚至不需要构建它:保存文件就足以触发Visual Studio操作),以下C#代码就会生成:
// 
// This code was generated by a tool. Any changes made manually will be lost
// the next time this code is regenerated.
// 

using System.Reflection;

[assembly: AssemblyVersion("1.0.1.113")]
[assembly: AssemblyFileVersion("1.0.1.113")]

是的,今天距离2010年1月1日已经过去了113天。明天修订号将会改变。 下一步是从所有项目的AssemblyInfo.cs文件中删除AssemblyVersion和AssemblyFileVersion属性,这些项目应该共享相同的自动生成版本信息。而是为每个项目选择“添加现有项”,导航到带有T4模板文件的文件夹,选择相应的“.cs”文件并将其添加为链接。就这样! 我喜欢这种方法的原因是它很轻量级(没有定制的MsBuild任务),自动生成的版本信息不会添加到源代码控制中。当然,使用C#来生成版本算法可以打开任何复杂度的算法。

3
我认为这是一个很好的解决方案,因为它具有插件和自定义可执行文件的灵活性,但又是一个纯粹的开箱即用的Visual Studio解决方案。 - Adam Nofsinger
1
很好地满足了我的需求,使用bzr revno来填充版本信息的一部分。 - CoderTao
12
这也非常适用于生成特定于构建的缓存破坏令牌,以用于JS和CSS引用。 - Kelly Adams
3
我不理解这个解决方案...我们必须调用TransformText()方法才能得到结果文件... - hakan
5
同时,这些模板只有在模板发生更改时才会被渲染。这种方法仅适用于AutoT4 Visual Studio扩展或类似工具。 - Andrey Moiseev
显示剩余8条评论

65
这是我对T4建议的实现...每次构建项目时,无论选择哪种配置(即Debug|Release),都会增加版本号,而每次进行发布构建时,都会增加修订号。您可以通过应用程序 ➤ 程序集信息...继续更新主要和次要版本号。
更详细地解释一下,这将读取现有的AssemblyInfo.cs文件,并使用正则表达式查找AssemblyVersion信息,然后根据来自TextTransform.exe的输入递增修订号和构建号。
  1. Delete your existing AssemblyInfo.cs file.
  2. Create a AssemblyInfo.tt file in its place. Visual Studio should create AssemblyInfo.cs and group it with the T4 file after you save the T4 file.

    <#@ template debug="true" hostspecific="true" language="C#" #>
    <#@ output extension=".cs" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.Text.RegularExpressions" #>
    <#
        string output = File.ReadAllText(this.Host.ResolvePath("AssemblyInfo.cs"));
        Regex pattern = new Regex("AssemblyVersion\\(\"(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<revision>\\d+)\\.(?<build>\\d+)\"\\)");
        MatchCollection matches = pattern.Matches(output);
        if( matches.Count == 1 )
        {
            major = Convert.ToInt32(matches[0].Groups["major"].Value);
            minor = Convert.ToInt32(matches[0].Groups["minor"].Value);
            build = Convert.ToInt32(matches[0].Groups["build"].Value) + 1;
            revision = Convert.ToInt32(matches[0].Groups["revision"].Value);
            if( this.Host.ResolveParameterValue("-","-","BuildConfiguration") == "Release" )
                revision++;
        }
    #>
    
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Resources;
    
    // General Information
    [assembly: AssemblyTitle("Insert title here")]
    [assembly: AssemblyDescription("Insert description here")]
    [assembly: AssemblyConfiguration("")]
    [assembly: AssemblyCompany("Insert company here")]
    [assembly: AssemblyProduct("Insert product here")]
    [assembly: AssemblyCopyright("Insert copyright here")]
    [assembly: AssemblyTrademark("Insert trademark here")]
    [assembly: AssemblyCulture("")]
    
    // Version informationr(
    [assembly: AssemblyVersion("<#= this.major #>.<#= this.minor #>.<#= this.revision #>.<#= this.build #>")]
    [assembly: AssemblyFileVersion("<#= this.major #>.<#= this.minor #>.<#= this.revision #>.<#= this.build #>")]
    [assembly: NeutralResourcesLanguageAttribute( "en-US" )]
    
    <#+
        int major = 1;
        int minor = 0;
        int revision = 0;
        int build = 0;
    #>
    
  3. Add this to your pre-build event:

    "%CommonProgramFiles(x86)%\microsoft shared\TextTemplating\$(VisualStudioVersion)\TextTransform.exe" -a !!BuildConfiguration!$(Configuration) "$(ProjectDir)Properties\AssemblyInfo.tt"
    

3
您可以将数字10替换为当前Visual Studio版本的变量:"%CommonProgramFiles(x86)%\microsoft shared\TextTemplating\$(VisualStudioVersion)\TextTransform.exe" -a !!build!true "$(ProjectDir)Properties\AssemblyInfo.tt"。请注意,此处仅涉及翻译,不包含解释或其他信息。 - BurnsBA
2
@BurnsBA,感谢您的建议!我已经修改了答案以反映这一点。 - Drew Chapin
5
预构建事件非常重要!谢谢!一旦我这样做了,每次我从VS2017或控制台构建项目时,我的版本号都会更新。 - Zam
9
可以使用"$(DevEnvDir)TextTransform.exe"代替"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\TextTransform.exe" - Brains
2
这是一个好的解决方案,但是你把构建和修订位置搞混了。VS明确指出:Major.Minor.Build.Revision,所以你必须在模板中交换Revision和Build。1. new Regex("AssemblyVersion\("(?<major>\d+)\.(?<minor>\d+)\.(?<build>\d+)\.(?<revision>\d+)"\)"); 2. [assembly: AssemblyVersion("<#= this.major #>.<#= this.minor #>.<#= this.revision #>.<#= this.build #>")] 3. [assembly: AssemblyFileVersion("<#= this.major #>.<#= this.minor #>.<#= this.revision #>.<#= this.build #>")] - Mecanik
显示剩余6条评论

32

如果您在生成和修订中使用星号,Visual Studio 将使用自2000年1月1日以来的天数作为生成号,将午夜以来的秒数除以2作为修订号。

一个更好的解决方案是 http://autobuildversion.codeplex.com/

它运行得非常顺畅,而且非常灵活。


1
无法在VS2013中工作。 - hakan
谢谢你的解释 - 我一直在试图弄清楚为什么构建号在同一天没有增加(而不查看修订号)。 这解释了一切,谢谢。 - m1m1k

22

这里是来自MSDN的AssemblyInfo.cs引用:

您可以指定所有值,或者使用星号(* )接受默认生成号和修订号。例如, [assembly:AssemblyVersion("2.3.25.1")] 表示主版本为2,次版本为3,生成号为25,修订号为1。 版本号如[assembly:AssemblyVersion("1.2.")]则表示主版本为1,次版本为2,并接受默认的生成号和修订号。 版本号如[assembly:AssemblyVersion("1.2.15.*")]则表示主版本为1,次版本为2,生成号为15,并接受默认的修订号。 默认生成号每天递增。默认修订号是随机的。

这段话的意思是,如果你在程序集信息中输入1.1.*,只有构建号会自动增加,而且它不会在每次构建后都发生,而是每天发生一次。修订号将在每次构建时更改,但是随机地而不是按递增方式。对于大多数用例来说,这已经足够了。如果这不是你想要的,你就需要编写一个脚本,在预构建步骤中自动增加版本号。

31
它会随机增加?他们开玩笑吧? - Ian Boyd
29
根据在http://msdn.microsoft.com/en-us/library/system.reflection.assemblyversionattribute.aspx上留下的评论,修订号并非随机生成,而是自午夜以来经过的秒数除以2得出的。我认为这不是太糟糕。 - Ronnie Overby
1
SemVer标准。但是微软一如既往地有自己的“独门秘籍”。 - maxkoryukov
默认修订号并非随机生成 - 在您提供的链接中,它指出:“默认修订号是自午夜当地时间起计算的秒数(不考虑夏令时的时区调整)除以2。” - d219

16

使用AssemblyInfo.cs文件。

在App_Code目录下创建该文件,并填写以下内容,或者使用Google搜索其他属性/属性可能性。

AssemblyInfo.cs

using System.Reflection;

[assembly: AssemblyDescription("Very useful stuff here.")]
[assembly: AssemblyCompany("companyname")]
[assembly: AssemblyCopyright("Copyright © me 2009")]
[assembly: AssemblyProduct("NeatProduct")]
[assembly: AssemblyVersion("1.1.*")]

你真正需要的是AssemblyVersion。

如果你正在处理网站,可以在任何aspx页面或控件中添加以下内容到<Page>标签中:

CompilerOptions="<folderpath>\App_Code\AssemblyInfo.cs"

(当然需要用适当的变量替换folderpath)。

我不认为您需要以任何方式添加编译器选项来处理其他类;在编译时,所有App_Code中的类都应该接收到版本信息。

希望这可以帮助您。


13
  • 版本号(例如“2.10.3.*”)- 简单易懂,但数字太大了。

  • AutoBuildVersion - 看起来不错,但它与我的VS2010不兼容。

  • @DrewChapin的脚本可行,但我无法在我的工作室中为Debug预构建事件和Release预构建事件设置不同的模式。

所以我稍微修改了一下脚本...

"%CommonProgramFiles(x86)%\microsoft shared\TextTemplating\10.0\TextTransform.exe" -a !!$(ConfigurationName)!1 "$(ProjectDir)Properties\AssemblyInfo.tt"

并且脚本(这适用于“调试”和“发布”配置):

<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Windows.Forms" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#
    int incRevision = 1;
    int incBuild = 1;

    try { incRevision = Convert.ToInt32(this.Host.ResolveParameterValue("","","Debug"));} catch( Exception ) { incBuild=0; }
    try { incBuild = Convert.ToInt32(this.Host.ResolveParameterValue("","","Release")); } catch( Exception ) { incRevision=0; }
    try {
        string currentDirectory = Path.GetDirectoryName(Host.TemplateFile);
        string assemblyInfo = File.ReadAllText(Path.Combine(currentDirectory,"AssemblyInfo.cs"));
        Regex pattern = new Regex("AssemblyVersion\\(\"\\d+\\.\\d+\\.(?<revision>\\d+)\\.(?<build>\\d+)\"\\)");
        MatchCollection matches = pattern.Matches(assemblyInfo);
        revision = Convert.ToInt32(matches[0].Groups["revision"].Value) + incRevision;
        build = Convert.ToInt32(matches[0].Groups["build"].Value) + incBuild;
    }
    catch( Exception ) { }
#>
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Game engine. Keys: F2 (Debug trace), F4 (Fullscreen), Shift+Arrows (Move view). ")]
[assembly: AssemblyProduct("Game engine")]
[assembly: AssemblyDescription("My engine for game")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyCopyright("Copyright © Name 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type. Only Windows
// assemblies support COM.
[assembly: ComVisible(false)]

// On Windows, the following GUID is for the ID of the typelib if this
// project is exposed to COM. On other platforms, it unique identifies the
// title storage container when deploying this assembly to the device.
[assembly: Guid("00000000-0000-0000-0000-000000000000")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
[assembly: AssemblyVersion("0.1.<#= this.revision #>.<#= this.build #>")]
[assembly: AssemblyFileVersion("0.1.<#= this.revision #>.<#= this.build #>")]

<#+
    int revision = 0;
    int build = 0;
#>

我使用了这种方法,但发现在生成新的assimblyinfo.cs时版权符号变成了问号。有什么解决办法吗? - The King
@TheKing 使用 Unicode 代码点而不是字面字符。 - Sebastian Hofmann
我不理解加号<#+它有什么作用? - jaysonragasa
@jaysonragasa,我刚刚修改了Drew Chapin的版本 https://dev59.com/p3RA5IYBdhLWcg3wyRF6#15050041。所以最好还是问问他。 - Dmi7ry
很棒的东西,只需将其用于TextTransform.exe,它更加简洁: "$(DevEnvDir)TextTransform.exe" - iBobb

9
您可以尝试使用Matt Griffith的UpdateVersion。虽然它已经很老了,但它仍然很有效。要使用它,您只需要设置一个预构建事件,指向您的AssemblyInfo.cs文件,应用程序将根据命令行参数相应地更新版本号。
由于该应用程序是开源的,我还创建了一个版本,使用格式(Major version).(Minor version).([year][dayofyear]).(increment)来增加版本号。我已在GitHub上放置了我修改过的UpdateVersion应用程序的代码:https://github.com/munr/UpdateVersion

2

您可以使用构建脚本(如版本控制)进行更高级的版本控制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接