$GOROOT, $GOPATH, Go Modules

Gary Liao
7 min readMay 24, 2023

--

$GOROOT

Golang 存放內建 package 的路徑,底下有 src、bin、pkg 以及其他資料夾。src 存放內建 package 的原始碼,bin 存放 go 執行檔,pkg 放些與作業系統相關的檔案。根據作業系統的不同,pkg的內容也會不同。

$GOPATH

Golang 存放第三方 package 的路徑,初始只有 src、bin 兩個資料夾,並且是空的。

$GOPATH 與第三方package

當裝了第三方package之後,就會出現一個 pkg 資料夾,並且直接與間接下載的package都會存放在pkg/mod裡面。我們執行 go install rsc.io/quote@latest 作為舉例:

quote@v1.5.2是我們主要安裝的 package

圖中 rsc.io/quote@v1.5.2 是我們主要安裝的 package,rsc.io/sampler@v1.3.0 則是相依的 package;golang.org 底下亦是相依package。

sumdb 資料夾下存放的是 checksum database 的資訊,用來驗證下載的package沒被竄改。

$GOPATH/src

1.11版 Go Modules 出世以前,$GOPATH/src 就是官方定義的開發目錄。

Go Modules

Go Modules 是目前官方建議的專案開發方式。

以下是在順著官方介紹 Go Modules 的文件練習時所遇到問題的筆記。

要初始化一個專案,需創建一個目錄,並進入該目錄,執行 go mod init [module-path] 初始化專案。關於 [module-path] 強烈建議看官方文件的說明,避免踩坑,例如:雖然[module-path]不是必填,但若開發目錄不在 $GOPATH/src 底下,則go mod init 會要求要給予一個 [module-path]

go mod init 的功能就是產生 go.mod 這個檔案。

go.mod files

go.mod 只能位於 module 的根目錄,官方文件詳細敘述了 go.mod 的細節,這裡簡述裡面6個重要的指令(directive):

  • module directive: 標記了執行 go mod init 時所給予的[module-path]
  • go directive: 開發的 go 版本
  • require directive: 此 module 所相依 package 的最低需求版本
  • exclude directive: 避免被 go 指令載入的 package 版本
  • replace directive: 指名哪個 module 必須要用另一個 module 取代。
go mod edit -replace example.com/greetings=<relative_path>/greetings
  • retract directive: 指名先前發布過的哪個 module 版本有問題必須撤回

go.mod 最開始只會有 module directive 與 go directive,隨著程式碼撰寫 import 的部分後,在 go build 或是 go test 或其他需要編譯的指令,會視程式碼當下 import 的 package 去增加 go.mod 的 require directive,但不會刪減。若要使 go.mod 的內容與程式碼的需求一致,需要下 go mod tidy 指令。

若相依 package 有版本上的不一致時,會採用 Minimal version selection (MVS):

MVS

版本衝突

縱使MVS也不能保證完全沒問題,若測試過程中發現新的 package 版本會出錯,需要降版,雖然可以直接去 go.mod 改內文的版本號,但不建議。這時我們可以使用 go get 來調整。

go get 指令

go get在幾次改版後,變成只能在module mode 底下使用,用來調整module所需 packages 的版本,會改動 go.modgo.sum 兩個檔案。

Go1.16#modules:

Module requirements and sums may be adjusted with go mod tidy or go get.

go get should be used with the -d flag to adjust the current module's dependencies without building packages, and use of go get to build and install packages is deprecated. In a future release, the -d flag will always be enabled.

Go1.17#modules:
… and go get will only be used to change dependencies in go.mod.

go install 取代了 go get 的部分功能,在module mode使用時,可以安裝 go.mod 列舉之外的package,且不會去更動 go.modgo.sum 兩個檔案。在預設的情況下,go install 必須搭配版本號才能安裝 package。

Go1.16#modules:

go install now accepts arguments with version suffixes (for example, go install example.com/cmd@v1.0.0). This causes go install to build and install packages in module-aware mode, ignoring the go.mod file in the current directory or any parent directory, if there is one. This is useful for installing executables without affecting the dependencies of the main module.

Go1.17#modules:
go install cmd@version should be used instead to install a command at a specific version, using a suffix like @latest or @v1.2.3. In Go 1.18, the -d flag will always be enabled, …

go.sum files

go.sum 存有 module 所依賴的 packages 的資訊,防止使用到錯誤的 package。

一行一筆資料,每筆資料有三個欄位,分別是 package 的名稱、版本、以及 cryptographic hashes。

通常每個 package 都會有兩筆資料在 go.sum 之中,一個是針對 package 本身的zip檔,另一個是針對package自己的go.mod;後者會在版本後加上/mod,例如下圖的 v1.5.2/mod:

Cryptographic hashes 是Base64編碼,以SHA-256(h1)加密,故開頭以h1: 標示之;若未來有安全隱患,會升級為h2或更高版本。

$GO111MODULE & VSCode

$GO111MODULE 是一個讓初學者痛苦的環境變數,它控制了 Go import 的行為 (先參考這篇)。

特別把VSCode拉出來講,是因為 VSCode 會調整 $GO111MODULE,我的經驗是即便自己在終端調整了 $GO111MODULE,只要VSCode 重啟,它會依據自己的設定,複寫 $GO111MODULE。強迫 VSCode 不要複寫,請參考這篇

--

--