如何通过我们的CI平台(Hudson)自动增加C#程序集版本?
我自己和我的团队在增加程序集版本号方面是可怕的,我们经常以1.0.0.0版本发布程序集。 显然,这导致了很多头痛。
通过我们的CI平台,我们的实践变得越来越好,我真的希望将其设置为在assemblyinfo.cs
文件中自动增加值,以便我们的程序集版本自动更新那个集会。
我以前设置(之前我们发现Hudson )通过msbuild
或命令行(不记得)增加值,但与哈德森,这将更新SVN存储库,并触发另一个版本。 Hudson每小时轮询一次SVN会导致一个缓慢的无限循环。
有哈德森增加版本号一个坏主意? 什么是替代方法呢?
理想情况下,我的解决scheme标准是:
- 在构build之前增加buildinfo.cs中的内部版本号
- 只增加已更改的程序集中的内部版本号。 这可能是不可能的,因为Hudson每次构build时都会擦除项目文件夹
- 将更改的assemblyinfo.cs提交到代码库(当前为VisualSVN )
- 不会导致Hudson在下次扫描更改时触发新的构build
在我的脑海里解决这个问题,我可以很容易地通过batch file/命令想出一个解决scheme,但是我所有的想法都会让Hudson在下一次扫描时触发一个新的构build。 我不是在找人为我做所有事情,只是指出我的方向是正确的,也许是让哈德森忽略某些SVN提交等技巧。
到目前为止,我发现的所有内容都只是一篇文章,解释如何自动增加版本号,没有考虑到可能陷入无限循环的CI平台。
一个简单的替代方法是让C#环境通过将version属性设置为major.minor.*
(如AssemblyInfo文件模板中所述)为您递增程序集版本。
不过,您可能正在寻找更全面的解决scheme。
编辑 (对评论中的问题的回应):
来自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.*")]
这是我所做的,用于盖印AssemblyFileVersion属性。
从AssemblyInfo.cs中删除了AssemblyFileVersion
将一个名为AssemblyFileInfo.cs的新文件添加到项目中。
在哈德森构build机器上安装MSBuild社区任务工具集,或作为项目中的NuGet依赖项 。
编辑项目(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提供了在项目build立在hudson上时看到的那些envvariables(假设它是从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,以在构build项目之前包含AssemblyFileVersion属性。 你可以为任何/所有的版本属性做到这一点,如果你想。
结果是,无论何时发出哈德森版本,生成的程序集都会得到一个1.0.HUDSON_BUILD_NR.SVN_REVISION的AssemblyFileVersion,例如1.0.6.2632,这意味着哈德森版本中的第6个版本#是从版本2632开始生成的。
这是一个优雅的解决scheme,在添加一个新项目时需要做一些工作,但是很容易处理这个过程。
这个想法是,每个项目链接到一个只包含程序集版本信息的解决scheme文件。 所以你的构build过程只需要更新一个文件,编译时从一个文件中取出所有的程序集版本。
脚步:
- 添加一个类给你的解决scheme文件* .cs文件,我命名为min SharedAssemblyProperties.cs
- 从新文件中删除所有的cs信息
- 从AssemblyInfo文件中剪切程序集信息:[assembly:AssemblyVersion(“1.0.0.0”)] [assembly:AssemblyFileVersion(“1.0.0.0”)]
- 添加语句“using System.Reflection;” 然后将数据粘贴到新的cs文件(例如SharedAssemblyProperties.cs)
- 添加一个现有的项目给你项目(等待…读取添加文件之前)
- select文件,然后单击添加,单击添加button旁边的下拉列表并select“添加为链接”。
- 对解决scheme中的所有现有和新项目重复步骤5和6
将文件作为链接添加时,它将数据存储在项目文件中,编译时从该文件中提取程序集版本信息。
在你的源码控制中,你添加一个bat文件或者脚本文件,这个文件只需增加SharedAssemblyProperties.cs文件,所有的项目都会从那个文件中更新它们的程序集信息。
哈德森可以configuration为忽略对某些path和文件的更改,以便它不会提示新build立。
在“作业configuration”页面的“ 源代码pipe理”下 ,单击“ 高级”button。 在“ 排除的区域”框中,input一个或多个正则expression式以匹配排除项。
例如,要忽略对version.properties文件的更改,您可以使用:
/MyProject/trunk/version.properties
这将适用于C#以外的其他语言,并允许您将版本信息存储在subversion中。
.NET为你做这个。 在您的AssemblyInfo.cs文件中,将您的程序集版本设置为major.minor。*(例如:1.0。*)。
当你build立你的项目时,版本是自动生成的。
我相信,构build和修订号是基于date生成的,使用unix时代。 构build基于当天,修订基于自午夜以来的秒数。
我从来没有真正看到1.0。*function在VS2005或VS2008工作。 有什么需要做的设置VS增加值?
如果AssemblyInfo.cs使用1.0。*进行硬编码,那么真正的build / revision存储在哪里?
在AssemblyInfo中放入1.0。*之后,我们不能使用以下语句,因为ProductVersion现在有一个无效值 – 它使用1.0。*而不是由VS分配的值:
Version version = new Version(Application.ProductVersion);
叹息 – 这似乎是每个人都问的事情之一,但不知何故,这个问题从来没有一个确切的答案。 几年前,我看到了一些解决scheme,用于生成修订版本号并将其保存到AssemblyInfo中作为后期构build过程的一部分。 我希望VS2008不需要那种舞蹈。 也许VS2010?
我假设你也可以用一个文本模板来做到这一点,你可以从AssemblyVersion.tt这样的环境中快速创build有问题的程序集属性。
<#@ 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#>")]
作为MikeS答案的延续,我想补充一点,需要安装VS + Visual Studio Visualization and Modeling SDK才能正常工作,并且还需要修改项目文件。 也应该提到我使用Jenkins作为构build服务器与版本模块在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" />
在我的构build服务器上,我有以下脚本在实际构build之前运行文本转换,以获取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>
这样,我可以跟踪构build和变更集,所以如果自上次构build之后我没有检查任何内容,最后一个数字不应该改变,但是我可能已经对构build过程进行了更改,因此需要倒数第二个。 当然,如果您在构build之前进行多个签入,您只能获得版本中反映的最后一个更改。 我想你可以连接的是必需的。
我敢肯定,你可以做一些更有趣的事情,并直接从tt模板中调用TFS,但这对我很有用。
然后我可以像这样在运行时获得我的版本
Assembly assembly = Assembly.GetExecutingAssembly(); FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); return fvi.FileVersion;
我的解决scheme不需要添加外部工具或脚本语言 – 它几乎保证在您的生成机器上工作。 我分几部分来解决这个问题。 首先,我创build了一个BUILD.BAT文件,将Jenkins BUILD_NUMBER参数转换为一个环境variables。 我使用Jenkins的“执行Windows批处理命令”function通过inputJenkins版本的以下信息来运行构buildbatch file:
./build.bat --build_id %BUILD_ID% -build_number %BUILD_NUMBER%
在构build环境中,我有一个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的解决scheme资源pipe理器窗格中生成的项目,并select属性,select生成事件,并input以下信息作为预生成事件命令行,它会自动创build一个.cs文件包含基于当前环境variables设置的内部版本号信息:
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%
您可能需要适应您的品味。 我手动构build项目以在主项目的Properties目录中生成一个初始的Version.cs文件。 最后,我手动将Version.cs文件拖放到Visual Studio解决scheme中,方法是将其拖到解决scheme资源pipe理器窗格中,在该项目的“属性”选项卡下。 在将来的构build中,Visual Studio会在Jenkins构build时读取.cs文件,并从中获取正确的构build编号信息。
所以,我们有一个解决scheme,其中包含几个具有不同版本号的程序集的项目。
在研究了上述几个方法之后,我只实现了一个构build步骤来运行一个Powershell脚本,该脚本在AssemblyInfo.cs文件中查找并replace。 我仍然使用源码控制中的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文件视为二进制文件,如果我没有。 当然,这并不重要,因为我从来没有真正的承诺过。 它只是在我testing的时候出现的。
我们的CI环境已经有了将Jenkins构build与特定的git commit(感谢Stash插件!)关联的工具,所以我不担心没有git提交附加的版本号。
这是一个更简单的机制。 它只需在MSBuild步骤之前添加Windows Batch命令任务构build步骤,并使用简单的查找和replace程序(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以外的源代码控制,请为您的scm环境更改适当的–svn选项。
下载放屁
我决定使用一些使用预build立的Powershell脚本( https://gist.github.com/bradjolicoeur/e77c508089aea6614af3 )的方法来增加每个成功的构build,然后在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,然后到Jenkin的没有太多的附加工具。