如何通过我们的 CI 平台 (Hudson) 自动增加 C# 程序集版本?
Posted
技术标签:
【中文标题】如何通过我们的 CI 平台 (Hudson) 自动增加 C# 程序集版本?【英文标题】:How can I auto increment the C# assembly version via our CI platform (Hudson)? 【发布时间】:2010-11-10 17:51:36 【问题描述】:我和我的团队在增加程序集版本号方面非常糟糕,我们经常发布 1.0.0.0 版本的程序集。显然,这会引起很多麻烦。
通过我们的CI 平台,我们的实践变得更好了,我真的很想将它设置为自动增加assemblyinfo.cs
文件中的值,以便我们的程序集版本自动更新与该程序集中的代码更改。
我之前(在我们找到 Hudson 之前)设置了一种通过 msbuild
或命令行(不记得)增加值的方法,但是使用 Hudson,这将更新 SVN 存储库并触发另一个建造。由于 Hudson 每小时轮询 SVN,这将导致缓慢的无限循环。
让 Hudson 增加版本号是个坏主意吗?有什么替代方法可以做到这一点?
理想情况下,我的解决方案标准应该是:
在构建之前增加assemblyinfo.cs
中的内部版本号
仅增加已更改程序集中的内部版本号。这可能是不可能的,因为 Hudson 每次构建时都会清除项目文件夹
将更改后的 assemblyinfo.cs 提交到代码库(当前为 VisualSVN)
不会导致 Hudson 在下次扫描更改时触发新构建
在我的脑海中解决这个问题后,我可以通过批处理文件/命令轻松想出解决大部分问题的方法,但我的所有想法都会导致 Hudson 在下次扫描时触发新构建。我不是在找人为我做所有事情,只是为我指明正确的方向,也许是一种让 Hudson 忽略某些 SVN 提交的技术,等等。
到目前为止,我发现的所有内容都只是一篇解释如何让版本号自动递增的文章,没有考虑到可能陷入无限循环的 CI 平台。
【问题讨论】:
【参考方案1】:一个简单的替代方法是让 C# 环境通过将版本属性设置为 major.minor.*
来为您增加程序集版本(如 AssemblyInfo 文件模板中所述。)
不过,您可能正在寻找更全面的解决方案。
编辑(在评论中回复问题):
来自AssemblyInfo.cs
:
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
【讨论】:
我以前从未遇到过这种情况,您能否详细介绍一下它的作用。它是仅在一个 IDE 中运行,还是在整个开发团队中使用 CI 平台运行? ahhh 我以前见过,这可能是一个可以接受的解决方案,但 #built 不存储在 subversion 等中。我有 Hudson 设置来存档文件,并以这种方式存储它以便可能是可以接受的。我将不得不对该机制的工作原理进行更多研究,谢谢!您不会知道它是如何确定作为值输入的内容,对吗? 请参阅下面我的回答以获取您的问题的答案。这些值是根据构建时间确定的。 哇,我认为这会奏效。不知道我们是如何忽略了这么简单的解决方案 希望如此,很高兴我能提供帮助。当简单、快速的方式也是正确的方式时,为什么要以艰难的方式去做呢? :)【参考方案2】:.NET 会为您完成这项工作。在您的 AssemblyInfo.cs 文件中,将您的程序集版本设置为 major.minor.*(例如:1.0.*)。
当您构建项目时,版本会自动生成。
内部版本号和修订号是根据日期生成的,我相信使用的是 unix 纪元。构建基于当天,修订基于自午夜以来的秒数。
【讨论】:
Hudson 可以配置为忽略对某些路径和文件的更改,这样它就不会提示新的构建。
在作业配置页面的源代码管理下,点击高级按钮。在Excluded Regions框中输入一个或多个正则表达式以匹配排除项。
例如,要忽略对 version.properties 文件的更改,您可以使用:
/MyProject/trunk/version.properties
这适用于 C# 以外的语言,并允许您将版本信息存储在 subversion 中。
【讨论】:
Hudson 还可以忽略来自某些用户的提交,或者根据提交消息不触发构建。这样你就可以忽略来自 Hudson 的所有提交。【参考方案4】:我从未真正看到 1.0.* 功能在 VS2005 或 VS2008 中工作。是否需要做一些事情来设置 VS 以增加值?
如果 AssemblyInfo.cs 是用 1.0.* 硬编码的,那么真正的构建/修订存储在哪里?
将 1.0.* 放入 AssemblyInfo 后,我们不能使用以下语句,因为 ProductVersion 现在有一个无效值 - 它使用的是 1.0.* 而不是 VS 分配的值:
Version version = new Version(Application.ProductVersion);
叹气——这似乎是每个人都在问的问题之一,但不知何故,从来没有一个可靠的答案。多年前,我看到了生成修订号并将其保存到 AssemblyInfo 作为后期构建过程的一部分的解决方案。我希望 VS2008 不需要那种舞蹈。也许是VS2010?
【讨论】:
您必须删除 AssemblyFileVersion。除此之外,它对我们来说很棒,这是公认的答案。 是的,删除 AssemblyFileVersion 允许版本更新,并且版本不再出现错误。好的。注意:两次构建操作只会增加一次修订,但如果您重新构建,则修订会更新。正如 ktrauberman 所说,它看起来像 build.revision = date.time,这解释了为什么数据不会存储在程序集中以外的任何地方。现在我需要获得一个标准的 MSI 设置,以便在主输出项目更新时生成一个新的 ProductCode。设置不允许修改,只能构建。我想在现有安装上安装以进行更新。需要研究。【参考方案5】:这是一个优雅的解决方案,在添加新项目时需要一些前期工作,但处理过程非常容易。
这个想法是每个项目都链接到一个仅包含程序集版本信息的解决方案文件。因此,您的构建过程只需更新一个文件,所有程序集版本都会在编译时从一个文件中提取。
步骤:
-
为您的解决方案文件 *.cs 文件添加一个类,我将其命名为 min SharedAssemblyProperties.cs
从该新文件中删除所有 cs 信息
从 AssemblyInfo 文件中剪切程序集信息:
[组装:组装版本(“1.0.0.0”)]
[程序集:AssemblyFileVersion("1.0.0.0")]
添加语句“使用 System.Reflection;”到文件中,然后将数据粘贴到新的 cs 文件中(例如 SharedAssemblyProperties.cs)
将现有项目添加到您的项目(等待...在添加文件之前继续阅读)
选择文件并在单击添加之前,单击添加按钮旁边的下拉菜单并选择“添加为链接”。
对解决方案中的所有现有项目和新项目重复第 5 步和第 6 步
当您将文件添加为链接时,它会将数据存储在项目文件中,并在编译时从该文件中提取程序集版本信息。
在您的源代码控制中,您添加一个 bat 文件或脚本文件,它只是增加 SharedAssemblyProperties.cs 文件,您的所有项目都将从该文件更新它们的程序集信息。
【讨论】:
谢谢,马克。抱歉死链接,事实证明社区服务器不是那么容易移动。我应该寻求有关该主题的帮助...【参考方案6】:这就是我所做的,用于标记 AssemblyFileVersion 属性。
从 AssemblyInfo.cs 中删除了 AssemblyFileVersion
将名为 AssemblyFileInfo.cs 的新空文件添加到项目中。
在 hudson 构建机器上安装 MSBuild community tasks 工具集,或在您的项目中安装 NuGet dependency。
编辑项目(csproj)文件,它只是一个msbuild文件,并添加以下内容。
某处会有<PropertyGroup>
说明版本。将其更改为例如
<Major>1</Major>
<Minor>0</Minor>
<!--Hudson sets BUILD_NUMBER and SVN_REVISION -->
<Build>$(BUILD_NUMBER)</Build>
<Revision>$(SVN_REVISION)</Revision>
当项目在 hudson 上构建时,Hudson 提供了您在那里看到的那些 env 变量(假设它是从 subversion 获取的)。
在项目文件的底部,添加
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')" />
<Target Name="BeforeBuild" Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')">
<Message Text="Version: $(Major).$(Minor).$(Build).$(Revision)" />
<AssemblyInfo CodeLanguage="CS" OutputFile="AssemblyFileInfo.cs" AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyConfiguration="$(Configuration)" Condition="$(Revision) != '' " />
</Target>
这使用 MSBuildCommunityTasks 生成 AssemblyFileVersion.cs 以在项目构建之前包含 AssemblyFileVersion 属性。如果需要,您可以对任何/所有版本属性执行此操作。
结果是,每当您发出 hudson 构建时,生成的程序集都会获得 1.0.HUDSON_BUILD_NR.SVN_REVISION 的 AssemblyFileVersion,例如1.0.6.2632 ,这意味着在 hudson 中的第 6 个构建 #,但来自 subversion 修订版 2632。
【讨论】:
所以只是更新一下:该方法适用于 C#。我已经使用了一段时间了。但是 C++ 程序集(即 C++/CLI)仍然是一个问题。据我所知,AssemblyInfo 任务不会产生有效的 C++。另外,我认为这种方法有一点缺点,因为它对其他开发人员来说有点不透明,无法理解正在发生的事情。太糟糕了,您不能将版本号作为属性直接推送到 MSBuild... @CJBrew 您可以创建一个小的 .bat 文件,为 AssemblyInfo 生成 C++ 代码,然后让 msbuild 启动该脚本。我不确定您将其作为属性推送是什么意思,您当然可以将版本字符串填充到您喜欢的任何属性中 - 您不需要使用我在这里使用的主要/次要/构建/修订版。跨度> 使用这条路线与仅仅注释掉 AssemblyFileVersion 并让它自动匹配 [assembly: AssemblyVersion("1.0.*")] 有什么收获吗? @ColeChamberlain 如果您在自己的 PC 上从 Visual Studio 构建它,而不是从 Hudson 构建它,它将自动递增 - 并且与版本号和特定构建和源代码修订无关。跨度> 【参考方案7】:这是一个更简单的机制。它只涉及在 MSBuild 步骤之前添加 Windows Batch 命令任务构建步骤以及使用简单的查找和替换程序 (FART)。
批处理步骤
fart --svn -r AssemblyInfo.cs "[assembly: AssemblyVersion(\"1.0.0.0\")]" "[assembly: AssemblyVersion(\"1.0.%BUILD_NUMBER%.%SVN_REVISION%\")]"
if %ERRORLEVEL%==0 exit /b 1
fart --svn -r AssemblyInfo.cs "[assembly: AssemblyFileVersion(\"1.0.0.0\")]" "[assembly: AssemblyFileVersion(\"1.0.%BUILD_NUMBER%.%SVN_REVISION%\")]"
if %ERRORLEVEL%==0 exit /b 1
exit /b 0
如果您使用的是 svn 以外的源代码控制,请将 --svn 选项更改为适合您的 scm 环境的选项。
Download Fart
【讨论】:
【参考方案8】:我假设人们也可以使用text template 来执行此操作,您可以在其中从环境中动态创建有问题的程序集属性,如下面的 AssemblyVersion.tt 所做的那样。
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<#
var build = Environment.GetEnvironmentVariable("BUILD_NUMBER");
build = build == null ? "0" : int.Parse(build).ToString();
var revision = Environment.GetEnvironmentVariable("SVN_REVISION");
revision = revision == null ? "0" : int.Parse(revision).ToString();
#>
using System.Reflection;
[assembly: AssemblyVersion("1.0.<#=build#>.<#=revision#>")]
[assembly: AssemblyFileVersion("1.0.<#=build#>.<#=revision#>")]
【讨论】:
【参考方案9】:作为 MikeS 回答的延续,我想补充一点,需要安装 VS + Visual Studio 可视化和建模 SDK 才能正常工作,并且您还需要修改项目文件。还应该提到的是,我使用 Jenkins 作为在带有版本模块的 windows 2008 R2 服务器盒上运行的构建服务器,我得到了 BUILD_NUMBER。
我的文本模板文件 version.tt 看起来像这样
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<#
var build = Environment.GetEnvironmentVariable("BUILD_NUMBER");
build = build == null ? "0" : int.Parse(build).ToString();
var revision = Environment.GetEnvironmentVariable("_BuildVersion");
revision = revision == null ? "5.0.0.0" : revision;
#>
using System.Reflection;
[assembly: AssemblyVersion("<#=revision#>")]
[assembly: AssemblyFileVersion("<#=revision#>")]
我在属性组中有以下内容
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
<TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>
在导入 Microsoft.CSharp.targets 后,我有了这个(取决于您安装 VS 的位置
<Import Project="C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" />
然后在我的构建服务器上,我有以下脚本在实际构建之前运行文本转换,以获取 TFS 上的最后一个变更集编号
set _Path="C:\Build_Source\foo"
pushd %_Path%
"%ProgramFiles(x86)%\Microsoft Visual Studio 10.0\Common7\IDE\tf.exe" history . /r /noprompt /stopafter:1 /Version:W > bar
FOR /f "tokens=1" %%foo in ('findstr /R "^[0-9][0-9]*" bar') do set _BuildVersion=5.0.%BUILD_NUMBER%.%%foo
del bar
popd
echo %BUILD_NUMBER%
echo %_BuildVersion%
cd C:\Program Files (x86)\Jenkins\jobs\MyJob\workspace\MyProject
MSBuild MyProject.csproj /t:TransformAll
...
<rest of bld script>
这样我可以跟踪构建和变更集,所以如果自上次构建以来我没有检查任何内容,最后一个数字不应该改变,但是我可能已经对构建过程进行了更改,因此需要倒数第二个数字。当然,如果您在构建之前进行多次签入,您只会得到版本中反映的最后一次更改。我想您可以将其连接起来。
我相信您可以做一些更有趣的事情并直接从 tt 模板中调用 TFS,但这对我有用。
然后我可以像这样在运行时获取我的版本
Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
return fvi.FileVersion;
【讨论】:
【参考方案10】:我的解决方案不需要添加外部工具或脚本语言——几乎可以保证在您的构建机器上工作。我分几个部分解决这个问题。首先,我创建了一个 BUILD.BAT 文件,它将 Jenkins BUILD_NUMBER 参数转换为环境变量。我使用 Jenkins 的“执行 Windows 批处理命令”功能通过输入以下 Jenkins 构建信息来运行构建批处理文件:
./build.bat --build_id %BUILD_ID% -build_number %BUILD_NUMBER%
在构建环境中,我有一个 build.bat 文件,开头如下:
rem build.bat
set BUILD_ID=Unknown
set BUILD_NUMBER=0
:parse_command_line
IF NOT "%1"=="" (
IF "%1"=="-build_id" (
SET BUILD_ID=%2
SHIFT
)
IF "%1"=="-build_number" (
SET BUILD_NUMBER=%2
SHIFT
)
SHIFT
GOTO :parse_command_line
)
REM your build continues with the environmental variables set
MSBUILD.EXE YourProject.sln
完成后,我在 Visual Studio 的解决方案资源管理器窗格中右键单击要构建的项目并选择属性,选择构建事件,然后输入以下信息作为预构建事件命令行,它会自动创建一个.cs 文件包含基于当前环境变量设置的内部版本号信息:
set VERSION_FILE=$(ProjectDir)\Properties\VersionInfo.cs
if !%BUILD_NUMBER%==! goto no_buildnumber_set
goto buildnumber_set
:no_buildnumber_set
set BUILD_NUMBER=0
:buildnumber_set
if not exist %VERSION_FILE% goto no_version_file
del /q %VERSION_FILE%
:no_version_file
echo using System.Reflection; >> %VERSION_FILE%
echo using System.Runtime.CompilerServices; >> %VERSION_FILE%
echo using System.Runtime.InteropServices; >> %VERSION_FILE%
echo [assembly: AssemblyVersion("0.0.%BUILD_NUMBER%.1")] >> %VERSION_FILE%
echo [assembly: AssemblyFileVersion("0.0.%BUILD_NUMBER%.1")] >> %VERSION_FILE%
您可能需要根据自己的构建品味进行调整。我手动构建项目一次,以在主项目的 Properties 目录中生成初始 Version.cs 文件。最后,我通过将 Version.cs 文件拖动到该项目的“属性”选项卡下方的“解决方案资源管理器”窗格中,手动将其包含到 Visual Studio 解决方案中。在未来的构建中,Visual Studio 会在 Jenkins 构建时读取该 .cs 文件并从中获取正确的构建号信息。
【讨论】:
【参考方案11】:我决定使用几种方法,使用预构建 Powershell 脚本 (https://gist.github.com/bradjolicoeur/e77c508089aea6614af3) 来增加每次成功构建,然后在 Global.asax 中我会这样做:
// We are using debug configuration, so increment our builds.
if (System.Diagnostics.Debugger.IsAttached)
string version = System.Reflection.Assembly.GetExecutingAssembly()
.GetName()
.Version
.ToString();
var psi = new ProcessStartInfo(@"svn", "commit -m \"Version: " + version + "\n \"");
psi.WorkingDirectory = @"C:\CI\Projects\myproject";
Process.Start(psi);
我仍然认为整个过程过于复杂,我将研究一种更有效的方法来实现相同的结果。我想要这个主要是为了将版本传递给 SVN,然后再传递给 Jenkins,而不需要太多额外的工具。
【讨论】:
【参考方案12】:因此,我们有一个项目,其中一个解决方案包含多个项目,这些项目的程序集具有不同的版本号。
在研究了上述几种方法之后,我刚刚实施了一个构建步骤来运行一个 Powershell 脚本,该脚本对 AssemblyInfo.cs 文件执行查找和替换。我仍然在源代码控制中使用 1.0.* 版本号,Jenkins 只是在 msbuild 运行之前手动更新版本号。
dir **/Properties/AssemblyInfo.cs | % (cat $_) | %$_ -replace '^(\s*)\[assembly: AssemblyVersion\("(.*)\.\*"\)', "`$1[assembly: AssemblyVersion(`"`$2.$build`")" | Out-File $_ -Encoding "UTF8"
dir **/Properties/AssemblyInfo.cs | % (cat $_) | %$_ -replace '^(\s*)\[assembly: AssemblyFileVersion\("(.*)\.\*"\)', "`$1[assembly: AssemblyFileVersion(`"`$2.$build`")" | Out-File $_ -Encoding "UTF8"
我添加了 -Encoding "UTF8" 选项,因为如果我不这样做,git 开始将 .cs 文件视为二进制文件。当然,这并不重要,因为我从未真正提交过结果;它只是在我测试时出现的。
我们的 CI 环境已经有一个工具可以将 Jenkins 构建与特定的 git 提交相关联(感谢 Stash 插件!),所以我不担心没有附加版本号的 git 提交。
【讨论】:
以上是关于如何通过我们的 CI 平台 (Hudson) 自动增加 C# 程序集版本?的主要内容,如果未能解决你的问题,请参考以下文章
在使用像 Hudson/Jenkins 这样的 CI 工具时,我如何成功地将 Maven 发布插件与 GitHub(或 GitHub 企业)一起使用
持续集成(CI)工具------Hudson/Jenkins(Continuous Integration)安装与配置具体解释
使用 CI/Hudson 支持为多个环境 [prod、test、dev] 生成工件的 Maven 最佳实践?
Hudson + SVN + Maven 持续集成实现自动化编译打包部署(over SSH 和 Deploy war/ear to a container 两种部署方式)