版本控制 .NET 建構
只是想知道 .NET 版本的最佳版本控制方法是什麼?
我用:
- 用於版本控制的 TFS 2013
- TFS 門控簽到
- Wix 3.8 將程式碼打包到 MSI 文件
我想設置版本:
- 程序集(AssemblyInfo.cs,或所有項目中引用的共享程序集)
- MSI 包(在 Wix 程式碼中)
- 文件(例如在建構的最終輸出中的 readme.txt 文件中)
- ETC
理想的版本號將允許將已安裝的軟體追溯到確切的原始碼。
就像是:
<Major>.<Minor>.<TFS_changeset_number>我想將版本的前 2 部分儲存在解決方案附近的版本控制中的一些簡單文本 \ XML 文件中,因為我相信它們應該一起存在。開發人員將手動更新此文件(例如遵循語義版本控制方法)。每個建構都將讀取此版本文件,從呼叫 CI 工具獲取版本的 3d 部分,並使用該版本更新所有必要的文件。
實現這一點的最佳方法是什麼?
我過去使用過幾種方法:
- 執行此版本工作的 NAnt \ MsBuild 包裝器,然後為解決方案呼叫 MsBuild。它可以從 CI 工具(Jenkins \ TeamCity \ 等)中呼叫。
問題- 與 TFS 門控簽入的集成很難看,因為我建構了兩次解決方案。
2)自定義TFS建構過程模板
問題- 它不是那麼簡單,並且會導致 TFS 升級的一些合併工作。此外,在門控簽入中還不存在變更集編號,因此我們只能使用以前的變更集 ID。
- 解決方案中一個單獨的MsBuild項目,它只做這個版本控制任務,並且被配置為在VS解決方案的Project Build Order中首先執行。
問題- 需要在所有其他感覺難看的項目(包括所有未來的項目)中引用這個元項目
我知道可以簡化更新的不同 MsBuild 和 TFS 擴展包。這個話題不是關於哪一個是最好的。這個問題更多的是方法論而不是技術問題。
我還認為,如果 Microsoft 在其標準 TFS 建構模板中包含一些用於版本控制的內容,那將是理想的。其他 CI 工具已經具有此功能(AssemblyInfo 修補程序)。
2014 年 11 月 9 日更新
我決定明確表達符合敏捷\持續傳遞最佳實踐的版本控制原則:
能夠複製任何歷史建築
作為 1) 的結果,並且根據 CD 原則,所有內容(原始碼、測試、應用程序配置、環境配置、建構\包\部署腳本等)都儲存在版本控制之下,因此分配了一個版本
版本號與其適用的原始碼緊密儲存在一起
人們可以根據自己的業務\行銷邏輯更新版本
5)版本只有1個主副本,用於自動化建構\打包過程的所有部分
6)您可以輕鬆說出目標系統上目前安裝的軟體版本
已安裝軟體的版本必須明確標識用於建構它的原始碼
比較版本非常簡單,可以說哪個更低,哪個更高 - 控制允許哪些升級\降級場景及其實現細節
2014 年 15 月 9 日更新
請參閱下面我自己的答案。
我很幸運找到了滿足我所有要求的解決方案!
我想出了一個滿足我所有要求的解決方案,而且非常簡單!
主意
將所有自定義版本控制工作放入自定義Version.proj MsBuild 腳本中,並在****.sln之前的 TFS 建構定義中呼叫它。該腳本將 Version 注入原始碼(SharedAssemblyInfo.cs、Wix 程式碼、readme.txt),然後解決方案建構建構該原始碼。
版本由儲存在 TFS 中的Version.xml文件中的主要和次要數字以及原始碼組成;並從父 TFS 建構過程作為TF_BUILD_SOURCEGETVERSION env var 提供的變更集編號
感謝微軟:
- TFS 2013 - 將TF_BUILD 環境變數傳遞給建構過程,這就是我獲取正在建構的目前程式碼的變更集編號的方式
- MsBuild 允許C# 中的內聯任務- 使用 Regex C# 類替換源文件中的版本
所以不需要使用任何 MsBuild 或 TFS community\extension packs\addons\whatever。並且無需修改標準 TFS 建構過程模板。簡單的解決方案帶來高可維護性!
執行
版本.proj
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- Run this script for every build in CI tool before building the main solution If you build in TFS, simply add script as the first item in a list of projects under Process tab > Build > Projects --> <PropertyGroup> <VersionFile>..\Version.xml</VersionFile> <MainProjectDir>... set this to main solution directory ...</MainProjectDir> </PropertyGroup> <Import Project="$(VersionFile)"/> <Import Project="Common.proj"/> <Target Name="GetMajorMinorNumbers"> <Error Text="ERROR: MajorVersion is not set in $(VersionFile)" Condition="'$(MajorVersion)' == ''" /> <Message Text="MajorVersion: $(MajorVersion)" /> <Error Text="ERROR: MinorVersion is not set in $(VersionFile)" Condition="'$(MinorVersion)' == ''" /> <Message Text="MinorVersion: $(MinorVersion)" /> </Target> <Target Name="GetChangesetNumber"> <Error Text="ERROR: env var TF_BUILD_SOURCEGETVERSION is not set, see http://msdn.microsoft.com/en-us/library/hh850448.aspx" Condition="'$(TF_BUILD_SOURCEGETVERSION)' == ''" /> <Message Text="TF_BUILD_SOURCEGETVERSION: $(TF_BUILD_SOURCEGETVERSION)" /> </Target> <Target Name="FormFullVersion"> <PropertyGroup> <FullVersion>$(MajorVersion).$(MinorVersion).$(TF_BUILD_SOURCEGETVERSION.Substring(1))</FullVersion> </PropertyGroup> <Message Text="FullVersion: $(FullVersion)" /> </Target> <Target Name="UpdateVersionInFilesByRegex"> <ItemGroup> <!-- could have simplified regex as Assembly(File)?Version to include both items, but this can update only one of them if another is not found and operation will still finish successfully which is bad --> <FilesToUpdate Include="$(MainProjectDir)\**\AssemblyInfo.cs"> <Regex>(?<=\[assembly:\s*Assembly?Version\(["'])(\d+\.){2,3}\d+(?=["']\)\])</Regex> <Replacement>$(FullVersion)</Replacement> </FilesToUpdate> <FilesToUpdate Include="$(MainProjectDir)\**\AssemblyInfo.cs"> <Regex>(?<=\[assembly:\s*AssemblyFileVersion\(["'])(\d+\.){2,3}\d+(?=["']\)\])</Regex> <Replacement>$(FullVersion)</Replacement> </FilesToUpdate> <FilesToUpdate Include="CommonProperties.wxi"> <Regex>(?<=<\?define\s+ProductVersion\s*=\s*['"])(\d+\.){2,3}\d+(?=["']\s*\?>)</Regex> <Replacement>$(FullVersion)</Replacement> </FilesToUpdate> </ItemGroup> <Exec Command="attrib -r %(FilesToUpdate.Identity)" /> <Message Text="Updating version in %(FilesToUpdate.Identity)" /> <RegexReplace Path="%(FilesToUpdate.Identity)" Regex="%(Regex)" Replacement="%(Replacement)"/> </Target> <Target Name="WriteReadmeFile"> <Error Text="ERROR: env var TF_BUILD_BINARIESDIRECTORY is not set, see http://msdn.microsoft.com/en-us/library/hh850448.aspx" Condition="'$(TF_BUILD_BINARIESDIRECTORY)' == ''" /> <WriteLinesToFile File="$(TF_BUILD_BINARIESDIRECTORY)\readme.txt" Lines="This is version $(FullVersion)" Overwrite="true" Encoding="Unicode"/> </Target> <Target Name="Build"> <CallTarget Targets="GetMajorMinorNumbers" /> <CallTarget Targets="GetChangesetNumber" /> <CallTarget Targets="FormFullVersion" /> <CallTarget Targets="UpdateVersionInFilesByRegex" /> <CallTarget Targets="WriteReadmeFile" /> </Target> </Project>Common.proj
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="12.0"> <!-- based on example from http://msdn.microsoft.com/en-us/library/dd722601.aspx --> <UsingTask TaskName="RegexReplace" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll"> <ParameterGroup> <Path ParameterType="System.String" Required="true" /> <Regex ParameterType="System.String" Required="true" /> <Replacement ParameterType="System.String" Required="true" /> </ParameterGroup> <Task> <Reference Include="System.Core" /> <Using Namespace="System" /> <Using Namespace="System.IO" /> <Using Namespace="System.Text.RegularExpressions" /> <Code Type="Fragment" Language="cs"><![CDATA[ string content = File.ReadAllText(Path); if (! System.Text.RegularExpressions.Regex.IsMatch(content, Regex)) { Log.LogError("ERROR: file does not match pattern"); } content = System.Text.RegularExpressions.Regex.Replace(content, Regex, Replacement); File.WriteAllText(Path, content); return !Log.HasLoggedErrors; ]]></Code> </Task> </UsingTask> <Target Name='Demo' > <RegexReplace Path="C:\Project\Target.config" Regex="$MyRegex$" Replacement="MyValue"/> </Target> </Project>版本.xml
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <MajorVersion>1</MajorVersion> <MinorVersion>1</MinorVersion> </PropertyGroup> </Project>
天哪,所有這些複雜的答案。
在 TFS 2013 中,這很簡單。Community TFS Build Extensions 站點提供了一個簡單的 PowerShell,用於從TFS 分配的內部版本名稱中提取內部版本號。
您將內部版本號格式配置為“ $ (BuildDefinitionName)_6.0.0 $ (Rev:.r)" 這將導致類似於 “6.0.0.1” 的結果,其中 “1” 為每個建構增加。
然後,您將 PowerShell 版本控制腳本添加到您的建構中,它會自動抓取上面的版本號並將其應用於建構文件夾中的所有 AssemblyInfo.* 文件。您可以通過更新腳本來添加其他文件類型。

