語義版本控制(簡稱為SemVer)是一種軟件版本控制方案,規定版本號碼為三部分的形式<主要>.<次要>.<修訂>,例如1.0.2,並且可以選擇性地添加預釋出版本後綴,形式為-<預釋>,如1.0.2-beta

SemVer也許是當今最廣泛使用的版本控制方案。例如,Nugetnpm都推薦並支持它,而VS Code也使用它

在大多數使用GitHub Releases功能發布版本的GitHub倉庫中,你會在首頁的最新發布徽章上看到SemVer版本號碼,如下面的截圖所示:

我在構建ASP.NET Core API項目時經常需要設定SemVer版本號碼,然後在運行時讀取或報告這個號碼。

例如,如果我建立了一個最小API,其版本設置為1.0.2-beta,這將通過API暴露的/version端點報告,如下面來自Hoppscotch的截圖所示(這是一個類似Postman的工具,方便之處在於它可以在瀏覽器中運行):

檢查從部署的服務(如Web應用和API)報告的版本是否正確,是我持續交付管道的重要部分,也是我用來確定部署是否成功的煙霧測試之一。

在.NET程序集中設置SemVer版本號時的一個小麻煩是,.NET最初使用四部分版本號,如1.0.3.212,程序集仍然有這些(程序集是.NET術語,指編譯為.NET字節碼的代碼單元,最典型的就是dll和exe)。

另一個是.NET不僅有一個,而是有許多略有不同的版本號,這些版本號存在於同一程序集中。

在本文中,我將向您展示如何在構建過程中在.NET程序集上打上SemVer版本號。也就是說,在編譯的.exe.dll上,以及如何在運行時讀取它。

目錄

結構化SemVer版本號

考慮一個SemVer版本號,如 1.0.21.0.2-beta。它具有 <主要>.<次要>.<修补><預發布> 的形式

以下各個組成的含義:

版本號的<major>部分僅在新的發布會破壞現有(最新)發布時增加。

在UI應用程序的情況下,客戶可能會被視為人類客戶。因此,如果新發布會破壞用戶現有的資產,如工作流定義,則需要增加主要版本號。在這種情況下,如果前一個發布版本是1.0.2,則新發布應為2.0.0(版本號的所有低級部分將重置)。

在庫的情況下,例如Nuget或NPM上的庫包,客戶將是其他代碼。因此,如果新發布會破壞現有的客戶代碼,即與其自己的前一個版本不向后兼容,則同樣<major>部分會增加。

<minor>會在添加了新功能但新版本仍然向后兼容的情況下增加。因此,從1.0.2增加到1.1.0

<patch>會在即使沒有重大變更且沒有添加新功能也需要發布新版本的情況下增加。例如,如果需要發布一個修復bug的版本,則可能會發生這種情況。

-<prerelease>後綴是可選的。當軟件需要在預發布測試階段(如alpha和beta)提供時,通常會將其附加到三部分版本號上。例如,在普遍發布您的軟件的1.0.2版本之前,您可以將其作為1.0.2-beta提供給您的測試人員。

<prerelease> 元件可以基本上是您選擇的任何字串,唯一的要求是它必須是一個 字母數字標識符,例如 beta12alpha2(除了數字或字母之外沒有其他字符),或者是由多個字母數字標識符以點(.)分隔的組合,例如 development.version

.NET 組件的眾多版本號

正如 Andrew Lock 的 .NET 版本控制文章 所解釋的,一個 .NET 組件並不是只有一個版本號,而是有幾個不同的版本號:

  • AssemblyVersion: 這是一個四部分的版本號,例如 1.0.2.0。它用於運行時加載相關的組件。

  • FileVersion: 這是在 Windows 檔案總管中右鍵單擊組件並選擇屬性時,報告的 .dll 檔案的版本號。

  • 資訊版本: 還是一個版本號碼,與FileVersion一樣,可以在屬性對話框中看到,如果您在Windows中對程序集右鍵單擊並選擇屬性。這可以包含字符串,而不僅僅是AssemblyVersion和FileVersion所限制的整數和點。

  • 包版本: 如果項目是Nuget包,這將是程序集所屬包的版本號碼。

所有這些版本號碼在編譯過程中都會作為元數據發送到程序集中。如果您使用JetBrains dotPeek(免費)或Red gate Reflector(不免費)或類似的工具檢查程序集,就可以看到它們。

FileVersion和InformationalVersion還可以在您在Windows文件資源管理器中對程序集文件右鍵單擊並選擇屬性時出現的屬性對話框的詳細資訊標籤中看到:

在截图中,“產品版本”是“InformationalVersion”的標題,而“文件版本”則是“FileVersion”的標題。

在上述描述的四種版本號中,只有前三種適用於任何程序集(即程序集是否為Nuget包的一部分)。

在這三種中,當您試圖設置只有三個數字的SemVer版本(加上可選的預發布後綴)時,AssemblyVersion會在第四位添加一個0。例如,如果您在構建過程中試圖設置一個1.0.2-beta的SemVer版本,然後在運行時在程序集中讀取AssemblyVersion值,它會是1.0.2.0

如截圖所示,FileVersion也是這樣做的。

信息版本(InformationalVersion)是唯一一個會被設置為與構建時設置的服務器版本完全相同的版本號,如截圖所示。

因此,InformationalVersion是應在運行時讀取以獲取程序集的SemVer版本的版本。

如何設置SemVer版本號

在構建過程中為程序集設置SemVer版本號需要做兩件事。

首先,在項目csproj文件中的<PropertyGroup>元素中,添加元素<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>

<PropertyGroup>
 ...
 <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> 
</PropertyGroup>

本問題所述,這樣可以確保InformationalVersion恰好設定为我们指定的SemVer版本號,而不会在末尾附加上+<hash code>

第二,將版本號作為Version屬性的值傳遞給dotnet build命令,例如:

dotnet build --configuration Release -p Version=1.0.2-beta

這將會將編譯後的程序集(.exe或.dll文件)中的InformationalVersion設定為1.0.2-beta

順便一提,它還會設定AssemblyVersion和FileVersion(在1.0.2的末尾會添加一個0),但我們對這些不感興趣。

請注意,除了在命令行上傳遞Version參數外,您還可以在csproj文件的<PropertyGroup>元素中設定MS Build屬性<Version>1.0.2-beta</Version>。然而,將Version參數的值傳遞給dotnet build更簡單,因為每次版本號增加時,不需要修改csproj文件。這在CD管道中很有幫助。此外,默認情況下,csproj文件沒有任何與版本控制相關的屬性。

如何在運行時讀取程序集的SemVer版本

在運行時讀取InfromationalVersion的代碼如下:

string? version = Assembly.GetEntryAssembly()?.
  GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.
  InformationalVersion;

在 minimal APIs 中,為了添加一個 /version 端點,如上方的簡介部分所示,我將以上代碼片段放在 Program.cs 中,然後立即在後面添加以下代碼片段。請注意,整個代碼應該出現在 builder.Build() 被調用

//這個匿名類型的對象將
//作為 JSON 序列化在回應正文
//當由處理器返回時
var objVersion = new { Version = version ?? "" };

//其他代碼
//var app = builder.Build()

builder.Build() 被調用之後,我為 /version 端點創建處理器:

app.MapGet("/version", () => objVersion);

現在當我運行 API 專案並調用 /version 端點時,我會在 HTTP 回應正文中以 JSON 對象的形式獲得版本號:

{
  "version": "1.0.2-beta"
}

這就是簡介中 Hoppscotch 截圖所展示的。

結論

本文向您展示了如何在您的 .NET 組配件、庫或應用程序中設定 SemVer 版本號。

它還向您展示了如何在運行時讀取版本號。