大家好,我是 polarisxu。
專案中,特別是開源專案,會特別重視專案的版本號。有些專案,會把版本號寫入原始碼中,每次升級都修改原始碼號。不過這不是特別好的方式。本文透過學習 Go 語言原始碼的處理方式來掌握它,並應用於自己的專案中。
本文基於 Go1。17,不同版本的實現細節可能有所不同
01 如何獲取版本號
在 Go 語言專案中,如果要獲取當前 Go 語言版本,只需要呼叫
runtime。Version
:
package mainimport ( “fmt” “runtime”)func main() { fmt。Println(“Go Version:”, runtime。Version())}
02 如何實現的
檢視
runtime。Version
的原始碼:
// buildVersion is the Go tree‘s version string at build time。//// If any GOEXPERIMENTs are set to non-default values, it will include// “X:
根據註釋提示,在 Go 倉庫原始碼中,找到 src/cmd/link,這是 Go 連結器的實現。在其中的 internal/ld/main。go 檔案找到了如下程式碼:
buildVersion := buildcfg。Versionif goexperiment := buildcfg。GOEXPERIMENT(); goexperiment != “” { buildVersion += “ X:” + goexperiment}addstrdata1(ctxt, “runtime。buildVersion=”+buildVersion)
buildVersion 值從 buildcfg。Version 獲取,如果設定了 GOEXPERIMENT(環境變數值),則用該值。
著重看 buildcfg。Version 如何得到的:
var ( defaultGOROOT string // set by linker GOROOT = envOr(“GOROOT”, defaultGOROOT) GOARCH = envOr(“GOARCH”, defaultGOARCH) GOOS = envOr(“GOOS”, defaultGOOS) GO386 = envOr(“GO386”, defaultGO386) GOARM = goarm() GOMIPS = gomips() GOMIPS64 = gomips64() GOPPC64 = goppc64() GOWASM = gowasm() GO_LDSO = defaultGO_LDSO Version = version)
很奇怪,Version 的值,直接用 version 賦值的。但 version 的值是什麼?在 src/cmd/dist/buildruntime。go 檔案中,有一個函式 mkbuildcfg,用於生成 buildcfg:
// mkbuildcfg writes internal/buildcfg/zbootstrap。go://// package buildcfg//// const defaultGOROOT =
其中 version 的值是透過 findgoversion() 得到,該函式定義在 src/cmd/dist/build。go 中:(省略了部分細節)
// findgoversion determines the Go version to use in the version string。func findgoversion() string { // The $GOROOT/VERSION file takes priority, for distributions // without the source repo。 path := pathf(“%s/VERSION”, goroot) if isfile(path) { 。。。 } // The $GOROOT/VERSION。cache file is a cache to avoid invoking // git every time we run this command。 Unlike VERSION, it gets // deleted by the clean command。 path = pathf(“%s/VERSION。cache”, goroot) if isfile(path) { return chomp(readfile(path)) } // Show a nicer error message if this isn‘t a Git repo。 if !isGitRepo() { fatalf(“FAILED: not a Git repo; must put a VERSION file in $GOROOT”) } // Otherwise, use Git。 // What is the current branch? branch := chomp(run(goroot, CheckExit, “git”, “rev-parse”, “——abbrev-ref”, “HEAD”)) 。。。 // Cache version。 writefile(tag, path, 0) return tag}
按一下順序獲得 Version(如果前面的獲取不到,則依次執行後續獲取步驟)
從
$GOROOT/VERSION
獲取,在這個檔案中放了版本資訊,你可以看看你的 Go 安裝目錄下這個檔案的資訊
從
$GOROOT/VERSION。cache
獲取
根據 Git 倉庫生成版本資訊,並且生成快取,這樣後續直接讀取
$GOROOT/VERSION。cache
獲取
03 自己專案
透過前文分析,總結下 Go 版本是如何寫入 Go 原始碼的:
正式版本,透過專案根目錄的一個檔案得到,比如
$GOROOT/VERSION
;
非正式版本,透過 Git 獲得版本資訊;為了避免編譯時重複執行 Git 相關操作,可以生成快取;
透過環境變數控制版本資訊;
最後,可以透過一個 API 把版本資訊公開給使用者。
對於我們自己的 Go 專案,透過 Git 獲得版本資訊,可以透過 shell 指令碼實現,最後編譯 Go 專案時,將版本資訊透過
-X
傳遞進去。
現在我們透過指令碼來實現這個功能。
專案程式碼如下:
// main。gopackage mainimport ( “fmt”)var Version stringfunc main() { fmt。Println(“Version:”, Version)}
現在寫一個 shell 指令碼,透過該指令碼對以上程式碼進行編譯:
#!/bin/shversion=“”if [ -f “VERSION” ]; then version=`cat VERSION`fiif [[ -z $version ]]; then if [ -d “。git” ]; then version=`git symbolic-ref HEAD | cut -b 12-`-`git rev-parse HEAD` else version=“unknown” fifigo build -ldflags “-X main。Version=$version” main。go
如果有 VERSION 檔案,讀取該檔案的值作為版本資訊;
如果 version 的值是空,判斷當前專案是否是 Git 專案。是,則獲取版本資訊,格式:master-commithash;否則,版本資訊設定為 unknown;
透過 go build 的 ldflags 傳遞版本資訊給 main。Version;
這樣專案中的 Version 就設定上正確的值了。
04 總結
本文透過對 Go 原始碼中版本資訊的學習研究,掌握了優秀開源專案設定版本資訊的做法。最後,演示瞭如何在自己的專案中用上該技能。
本文沒有演示環境變數,一般用的比較少。