如果你是一個 Golang 的用戶,那麼你大概率會遇到管理和維護 Golang 版本的訴求,如果你恰好同時需要開發調試兩個不同版本的項目,在不考慮強制跳版本的情況下,你或許就需要使用「Golang 版本管理工具」來幫助你減輕負擔了。
本篇文章將介紹最近幾個月,我在使用的工具,它們的優勢和不足。希望能夠幫助到有類似需求的同學。
寫在前面在本地新舊項目並行開發的過程中,你大概率會遇到一個令人頭疼的問題,如何同時使用兩個不同版本的 Golang Runtime 進行開發呢?
在容器和 CI 流行的當前時代下,我們似乎已經習慣了用 docker run 來切換各種語言的版本,來完成不同項目的開發,基礎類型項目的兼容性測試。配合一些支持遠程調試的工具,體驗似乎也還行。
但是在運行效率和複雜度上,相比本地環境而言,總歸是高了那麼一丟丟。那麼有沒有更節能環保的方式呢?
基於 Golang 的版本管理工具:voidint/g最初安裝 gvm 後,總覺得工具不夠「簡潔」,所以我基於 https://github.com/voidint/g/ 調整了一些細節,重新編譯了一個版本自用。
如果你不希望自己編譯安裝,也可以用作者推薦的方式進行安裝:
curl -sSL https://raw.githubusercontent.com/voidint/g/master/install.sh | bash這裡如果你是 oh-my-zsh 的用戶,那麼你還需要做一件事,就是解決全局的 g 命令的衝突,解決的方式有兩種,第一種是在你的 .zshrc 文件末尾添加 unalias :
echo "unalias g" >> ~/.zshrc # 可選。若其他程序(如'git')使用了'g'作為別名。
# 記得重啟 shell ,或者重新 source 配置第二種,則是調整 ~/.oh-my-zsh/plugins/git/git.plugin.zsh 中關於 g 的註冊,將其注釋或刪除掉:
# alias g='git'我的 .zshrc 中的完整配置:
# 我的 g 的bin目錄調整到了 .gvm ,所以你可能需要一些額外的調整
export PATH="${HOME}/.gvm/bin:$PATH"
export GOROOT="${HOME}/.g/go"
export PATH="${HOME}/.g/go/bin:$PATH"
export G_MIRROR=https://gomirrors.org/但是隨著使用過程中,我發現在同時使用兩個版本的 Golang 的時候,會有一些問題。翻看源碼實現,看到了 https://github.com/voidint/g/blob/master/cli/install.go 中的安裝定義:
fmt.Println("Checksums matched")
// 刪除可能存在的歷史垃圾文件
_ = os.RemoveAll(filepath.Join(versionsDir, "go"))
// 解壓安裝包
if err = archiver.Unarchive(filename, versionsDir); err != nil {
return cli.NewExitError(errstring(err), 1)
}
// 目錄重命名
if err = os.Rename(filepath.Join(versionsDir, "go"), targetV); err != nil {
return cli.NewExitError(errstring(err), 1)
}
// 重新建立軟連結
_ = os.Remove(goroot)
if err := mkSymlink(targetV, goroot); err != nil {
return cli.NewExitError(errstring(err), 1)
}
fmt.Printf("Now using go%s\n", v.Name)
return nil發現其實每次版本切換,都將重新建立軟鏈映射。官方項目的 Issue 區,有一個類似的反饋:#44,作者當時給出了一個 g 這個程序之外的解決方案。
所以,如果你的需求比較簡單,期望使用一個工具,能夠從網上快速的下載 Golang 的預編譯版本的 Runtime,並且不需要同時運行多個版本,那麼使用 voidint/g 就可以滿足你的需求了,但是如果你的需求是需要多個版本同時運行,那麼你可以接著往下看。
基於 BASH 的版本管理工具:gvm因為出現了上面的問題,所以我開始考慮調整方案。首先是考慮切換回 https://github.com/moovweb/gvm,說起 gvm,熟悉 Node.js 生態的同學,其實可以很容易聯想起 nvm。沒錯,他們的理念是一致的,通過語言生態無關的 Bash 來編寫語言管理工具。
在 Node.js 中,因為維護版本下載、更新、刪除、切換這些功能和語言無關(比如另外一款工具n基於 Node.js),所以其實更健壯一些,不會出現因為 Node.js 配置出現問題, 語言版本管理工具無法運行,出現無法管理語言版本的問題。(雞生蛋、蛋生雞的哲學問題)但在 Golang 中,其實預編譯的二進位已經和語言無關了,相比之下,使用 Bash 來編寫程序,會顯得比較「囉嗦」。
這也是我最初沒有堅持 gvm 的原因之一。除此之外,gvm 雖然用戶者眾,但是很長一段時間作者已經不活躍了,所以在 Issue 和 PR 區都堆積了一堆待辦事項。官方的文檔中也存在不少錯誤或者缺失的地方。
不過,這些都是可解決的。
gvm 之於用戶,一般存在三類常見問題:
先來解決第一個問題,如何正確安裝 gvm,官方 ReadMe 中的安裝方式在 ZSH 環境中會遇到問題,推薦切換為下面的方式安裝:
curl -sSL https://github.com/moovweb/gvm/raw/master/binscripts/gvm-installer | bash執行過後,我們就可以看到正確的日誌輸出了:
Cloning from https://github.com/moovweb/gvm.git to /home/ubuntu/.gvm
No existing Go versions detected
Installed GVM v1.0.22
Please restart your terminal session or to get started right away run
`source /home/ubuntu/.gvm/scripts/gvm`接著我們來看第二個問題,首次安裝 Golang 某個版本的時候,因為我們沒有配置下載鏡像地址,所以可能你的下載會遇到「中斷」,獲得一個不完全的程序壓縮包。程序會判斷我們是否已經下載過程序,會嘗試優先使用下載過的緩存內容,而不管它是否是完整的,這就導致了一部分用戶反覆執行 gvm install go1.17.3 -B ,但是發現一切正常,就是無法完成版本下載或者切換。
解決這個問題其實也很簡單,就是清除掉這個緩存內容:
rm -rf ~/.gvm/archive/go1.17.3.darwin-amd64.tar.gz
# or
rm -rf ~/.gvm/archive/接著我們來看第三個問題,如何使用鏡像地址進行下載,加速我們切換 Golang 版本的效率。在官方文檔中,有一段使用介紹:
Usage: gvm install [version] [options]
-s, --source=SOURCE Install Go from specified source.
...但是,這個其實並不是我們要的內容,因為它解決的是「指定Golang原始碼」的在線地址,而不是預構建的二進位包的地址,在 https://github.com/moovweb/gvm/blob/master/scripts/install 中我們可以看到默認使用的是 GitHub 倉庫代碼,所以如果你希望從零開始源碼編譯,這個參數可以幫助到你,但是如果你想下載二進位,那麼這個參數毫無用處。
...
GO_SOURCE_URL=https://github.com/golang/go
for i in "$@"; do
case $i in
-s=*|--source=*)
GO_SOURCE_URL=$(echo "$i" | sed 's/[-a-zA-Z0-9]*=//')
;;
...在相同文件的比較靠下的位置,我麼可以看到一個名為 download_binary() 的函數:
# `GO_BINARY_BASE_URL` env allow user setting base URL for binaries
# download, e.g. "https://dl.google.com/go".
GO_BINARY_BASE_URL=${GO_BINARY_BASE_URL:-"https://storage.googleapis.com/golang"}
GO_BINARY_URL="${GO_BINARY_BASE_URL}/${GO_BINARY_FILE}"
GO_BINARY_PATH=${GVM_ROOT}/archive/${GO_BINARY_FILE}
if [ ! -f $GO_BINARY_PATH ]; then
curl -s -f -L $GO_BINARY_URL > ${GO_BINARY_PATH}
if [[ $? -ne 0 ]]; then
display_error "Failed to download binary go"
rm -rf $GO_INSTALL_ROOT
rm -f $GO_BINARY_PATH
exit 1
fi
fi這裡有一個 GO_BINARY_BASE_URL 變量,針對它進行調整,就可以達到我們的目的啦。可惜的是,這個參數自2019年末合併進來之後,並沒有更新文檔,如果你不閱讀代碼,基本不會知道還可以從鏡像進行資源下載。
這裡給出我目前使用的配置,在將下面的配置添加到你的 SHELL 的 rc 後,你就可以正常的使用 gvm 對 Golang 進行快速的版本切換啦。
export GO111MODULE=on
export GOPROXY=https://goproxy.io,direct
# or
# exort GOPROXY="https://goproxy.cn"
export GOPATH="$HOME/go"
PATH="$GOPATH/bin:$PATH"
export GO_BINARY_BASE_URL=https://golang.google.cn/dl/
[[ -s "$HOME/.gvm/scripts/gvm" ]] && source "$HOME/.gvm/scripts/gvm"
export GOROOT_BOOTSTRAP=$GOROOT至於切換不同版本 Golang ,也很簡單,只需要兩條條命令:
gvm install go1.17.3 -B
gvm use go1.17.3倘若你期望不藉助 Golang 團隊官方鏡像,完全定製一個 Golang Base 的 Docker 的鏡像,相比較其他工具,gvm 會是一個簡單的選擇,不需要預構建、也不挑系統。
來自官方的解決方案:golang/dl如果你不喜歡來自三方的解決方案,那麼或許可以試試來自官方的方案。(前提是,你不需要同時運行多個版本的 Golang)
相比較社區方案,官方的方案就更有趣了:https://github.com/golang/dl。官方維護了自 1.5 以來到 1.17 的所有版本的更新軟體包。
我們可以通過安裝普通軟體包的方式來獲取具體版本的安裝工具,以及進行「覆蓋安裝」:
go get golang.org/dl/go1.17.3
go1.17.3 download不過和上面不同的是,https://github.com/golang/dl/blob/master/internal/version/version.go中的寫死的邏輯會讓你安裝的目錄在用戶目錄的 sdk 文件夾中,所以如果你使用這種方式,export 的路徑需要做一個調整:
func goroot(version string) (string, error) {
home, err := homedir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %v", err)
}
return filepath.Join(home, "sdk", version), nil
}
其他此外,還有兩個有趣的項目,借鑑自 Rustup 的 :https://github.com/owenthereal/goup;以及借鑑 rbenv和pyenv的:https://github.com/syndbg/goenv。
最後最近在持續做筆記內容整理的事情,恰好看到這篇筆記草稿,順手整理成文。
本篇就先寫到這裡啦,希望能夠幫你節約一些時間,避過小坑。
--EOF
如果你覺得內容還算實用,歡迎點讚分享給你的朋友,在此謝過。
本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或重新修改使用,但需要註明來源。署名 4.0 國際 (CC BY 4.0)
本文作者: 蘇洋
創建時間: 2021年12月15日統計字數: 5707字閱讀時間: 12分鐘閱讀本文連結: