Go Module 語義化版本規範
Go Module 的設計採用了語義化版本規範,語義化版本規範非常流行且具有指導意義,本文就來聊聊語義化版本規範的設計和在 Go 中的應用。
語義化版本規範
語義化版本規範(SemVer)是由 Gravatars 創辦者兼 GitHub 共同創辦者 Tom Preston-Werner 所建立,旨在解決 依賴地獄 問題。
它清楚明瞭的規定了版本格式、版本號遞增規:
版本格式:採用 X.Y.Z 的格式,X 是主版本號、Y 是次版本號、而 Z 爲修訂號(即:主版本號. 次版本號. 修訂號),其中 X、Y 和 Z 爲非負的整數,且禁止在數字前方補零。
版本號遞增規則:
主版本號:當做了不兼容的 API 修改。
次版本號:當做了向下兼容的功能性新增及修改。
修訂號:當做了向下兼容的問題修正。
另外,先行版本號
及 版本編譯信息
可以加到 主版本號. 次版本號. 修訂號
的後面,作爲延伸。
完整版本格式如下:
其中版本號核心部分 X.Y.Z 是必須的,使用 .
連接,先行版本號和版本編譯信息是可選的,先行版本號通過 -
與核心部分連接,版本編譯信息通過 +
與核心部分或先行版本號連接。
合法的幾種版本號格式如下:
-
主版本號. 次版本號. 修訂號
-
主版本號. 次版本號. 修訂號 - 先行版本號
-
主版本號. 次版本號. 修訂號 + 版本編譯信息
-
主版本號. 次版本號. 修訂號 - 先行版本號 + 版本編譯信息
主版本號必須在有任何不兼容的修改被加入公共 API 時遞增。每當主版本號遞增時,次版本號和修訂號必須歸零。
次版本號必須在有向下兼容的新功能出現或有改進時遞增,或在任何公共 API 的功能被標記爲棄用時也必須遞增。每當次版本號遞增時,修訂號必須歸零。
修訂號必須在只做了向下兼容的修正時才遞增。這裏的修正指的是針對不正確結果而進行的內部修改。
存在先行版本號,意味着當前版本不夠穩定,且可能存在兼容性問題。先行版本號是一連串以 .
分隔的標識符,由 ASCII 字母數字和連接號 [0-9A-Za-z-]
組成,禁止出現空白符,數字類型則禁止在前方補零。合法示例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。
版本編譯信息標誌符規格與先行版本號基本相同,略有差異的是數字類型前方允許補零。合法示例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。
除了上面幾點說明,還需要額外關注以下幾點:
-
標記版本號的軟件發行後,禁止改變該版本軟件的內容。任何修改都必須以新版本發行。
-
主版本號爲零(0.y.z)的軟件處於開發初始階段,一切都可能隨時被改變。這樣的公共 API 不應該被視爲穩定版。
-
1.0.0 的版本號用於界定公共 API 的形成。這一版本之後所有的版本號更新都基於公共 API 及其修改內容。
-
社區中還存在一個不成文的規定,對於次版本號,偶數爲穩定版本,奇數爲開發版本。當然不是所有項目都這樣設計。
使用語義化版本規範可能遇到的問題
在使用語義化版本規範過程中,可能人爲或程序編寫錯誤導致出現如下幾種可預見的問題:
- 萬一不小心把一個不兼容的改版當成了次版本號發行了該怎麼辦?
一旦發現自己破壞了語義化版本控制的規範,就要修正這個問題,併發行一個新的次版本號來更正這個問題並且恢復向下兼容。即使是這種情況,也不能去修改已發行的版本。可以的話,將有問題的版本號記錄到文檔中,告訴使用者問題所在,讓他們能夠意識到這是有問題的版本。
注意:不到萬不得已,不要也不能去修改已發行的版本。
- 如果我變更了公共 API 但無意中未遵循版本號的改動怎麼辦呢?(意即在修訂等級的發佈中,誤將重大且不兼容的改變加到代碼之中)
自行做最佳的判斷。如果你有龐大的使用者羣在依照公共 API 的意圖而變更行爲後會大受影響,那麼最好做一次主版本的發佈,即使嚴格來說這個修復僅是修訂等級的發佈。記住,語義化的版本控制就是透過版本號的改變來傳達意義。若這些改變對你的使用者是重要的,那就透過版本號來向他們說明。
v1.2.3
是一個語義化版本號嗎?
v1.2.3
並不是的一個語義化的版本號。但是,在語義化版本號之前增加前綴 v
是用來表示版本號的常用做法。在版本控制系統中,將 version
縮寫爲 v
是很常見的。比如:git tag v1.2.3 -m "Release version 1.2.3"
中,v1.2.3
表示標籤名稱,而 1.2.3
是語義化版本號。
如何驗證語義化版本規範正確性
官方提供了兩個正則可以檢查語義化版本號的正確性。
- 支持按組名稱提取匹配結果
^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
Go 語言示例:
package main
import (
"encoding/json"
"fmt"
"regexp"
)
func main() {
version := "0.1.2-alpha+001"
pattern := regexp.MustCompile(`^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
r := pattern.FindStringSubmatch(version)
m := make(map[string]string)
for i, name := range pattern.SubexpNames() {
if i == 0 {
m["version"] = r[i]
} else {
m[name] = r[i]
}
}
result, _ := json.MarshalIndent(m, "", " ")
fmt.Printf("%s\n", result)
}
/*
{
"buildmetadata": "001",
"major": "0",
"minor": "1",
"patch": "2",
"prerelease": "alpha",
"version": "0.1.2-alpha+001"
}
*/
- 支持按編號提取匹配結果
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
Go 語言示例:
package main
import (
"fmt"
"regexp"
)
func main() {
version := "0.1.2-alpha+001"
pattern := regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
r := pattern.FindStringSubmatch(version)
for i, s := range r {
fmt.Printf("%d -> %s\n", i, s)
}
}
/*
0 -> 0.1.2-alpha+001
1 -> 0
2 -> 1
3 -> 2
4 -> alpha
5 -> 001
*/
Go Module 版本設計
依賴地獄
我們先來看下早期 Go 依賴包存在的依賴地獄問題:
首先存在兩個包 pkg1
和 pkg2
,分別依賴 pkg3
的 v1.0.0
版本和 v2.0.0
版本,現在我們開發一個 app
包,它依賴 pkg1
和 pkg2
,那麼此時由於 app
包只允許包含一個 pkg3
依賴,所以 Go 構建工具無法抉擇應該使用哪個版本的 pkg3
。這就是所謂的依賴地獄問題。
語義導入版本
爲了解決依賴地獄問題,Go 在 1.11 版本時引入和 Go Module:
Go Module 解決問題的方式是,把 pkg3
的 v1.0.0
版本和 v2.0.0
版本當作兩個不同的包,這樣也就允許了 app
包能夠同時包含多個不同版本的 pkg3
。
在使用時,需要在包的導入路徑上加上包的主版本號。這裏以 go-micro
包使用爲例,展示下 Go Module 語義導入版本的用法:
import "go-micro.dev/v4"
// create a new service
service := micro.NewService(
micro.Name("helloworld"),
)
// initialise flags
service.Init()
// start the service
service.Run()
可以看到導入路徑爲 "go-micro.dev/v4"
,其中 v4
就代表了需要引入 go-micro
的 v4.y.z
版本。
聯繫我
微信:jianghushinian
郵箱:jianghushinian007@outlook.com
博客地址:https://jianghushinian.cn
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/rFT7JTPS7mNr-3xgcu_Ikw