Golang 多版本管理
如果你是一個 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
之於用戶,一般存在三類常見問題:
-
程序安裝過程中遭遇失敗
-
下載 Golang 指定版本失敗後無法繼續安裝
-
用戶不知道如何使用鏡像資源
先來解決第一個問題,如何正確安裝 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。
最後
最近在持續做筆記內容整理的事情,恰好看到這篇筆記草稿,順手整理成文。
本篇就先寫到這裏啦,希望能夠幫你節約一些時間,避過小坑。
本文作者: 蘇洋
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/v_HCQacOFzbuHgNbyVi4oQ