Go Module 入門與使用介紹

Posted by Kubeguts on 2020-11-07

Go Module 為目前最主流的依賴解決的方案

發佈於 Go 11.1版,於Go 1.14推薦在Production環境上使用

Go Modules的出現解決了以下幾點爭議:

  • 依賴問題
  • 淘汰GOPATH的機制
  • 統一社群中其他依賴管理工具

為何 GOPATH 不在被推薦使用

簡單介紹

GOPATH原先是透過定義好的目錄結構如下:

1
2
3
4
5
6
7
go
|--bin
|--pkg
|--src
|--github.com
|--golang.org
...

各目錄分別為:

  • bin: 儲存所編譯產生的二進位檔案
  • pkg: 儲存預先編譯的目的檔案,以加快程式的後續編譯速度
  • src: 儲存所有 .go 檔案或原始程式碼
    • 一般會以 $GOPATH/src/github.com/ 儲存 Go的應用程式和函式庫

棄用原因: 無法明確定義與參照依賴(Dependency)的版本

因為.go 專案都必須要儲存在 $GOPATH/src 底下,但該依賴管理方式並沒有提供版本控制的概念,會造成以下問題:

  1. 執行go get 取得遠端的依賴包時,會無法得知目前所載的版本
  2. 無法處理 v1,v2,v3等不同版本的參考問題,假設有的Library叫做 /foo/bar 但不管幾版,都會在GOPATH底下,其路徑都會是一樣的 (都在github.com/foo/bar)

Go modules的基本使用方式

指令

  • go mod init: 產生 go.mod 檔案
  • go mod download: 下載go.mod檔案中指明的所有依賴
  • go mod tidy: 整理所有依賴
  • go mod graph: 檢視現有的依賴結構
  • go mod edit: 編輯 go.mod 檔案
  • go mod vendor: 會出專案所有的依賴到vendor目錄
  • go mod verify: 驗證一個模組是否被竄改過
  • go mod why: 檢視為何需要依賴該模組

環境變數

可透過 go env 來檢視常用的環境變數,以下為與Go Module相關的環境變數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# GOMODULES新增的環境變數
GO111MODULE="on"
GOPROXY="https://proxy.golang.org,direct"
GONOPROXY=""
GOSUMDB="sum.golang.org"
GONOSUMDB=""
GOPRIVATE=""

# 以下為原本GOPATH就有的環境變數
GOARCH="amd64"
GOBIN="/Users/user/go/bin"
GOCACHE="/Users/user/Library/Caches/go-build"
GOENV="/Users/user/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/user/go/pkg/mod"
GOOS="darwin"
GOPATH="/Users/user/go"
GOROOT="/usr/local/Cellar/go/1.15/libexec"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.15/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/3t/8jsg2cz52yn2glq8rk_9y7x40000gn/T/go-build233814295=/tmp/go-build -gno-record-gcc-switches -fno-common"

GO111MODULE

透過GO111MODULE作為該專案是否使用 Go modules的開關,可設定以下內容

  • auto: 只要專案包含 go.mod 檔案,就使用Go modules (在Go 1.11~1.14,為預設)
  • on: 啟用Go modules (推薦使用)
  • off: 禁用Go modules
為何叫做 GO111MODULE?

因為go modules是在 go1.11版本所提出的

GOPROXY

設定GO模組的proxy, 使Go在後續拉取模組的版本時,直接透過映像檔網站快速拉取,而非到Github這種Version control service平台

GOPROXY預設值為 https://proxy.golang.org,direct,透過逗號將代理的地方給隔開

若不想使用GOPROXY, 可設置為 GOPROXY=“off”

其中最後有 direct 一詞的用意是,若前面的proxy值都沒辦法載到
模組,例如在 https://proxy.golang.org抓不到,那麼就會回到原位置(例如Github)去下載

GOSUMDB

GOSUMDB為Go checksum database,驗證拉取的模組版本資料未經過竄改

預設值為 sum.golang.org,也可設置成 off,禁止Go在後續操作中做驗證版本的動作

GONOPROXY/GONOSUMDB/GOPRIVATE

設定私有模組的環境變數,一般來說建議直接設定 GONOPROXY,會作為 GONOPROXY與GONOSUMDB的預設值

值可設定多組,例如

1
$ go env -w GOPRIVATE="git.xxx.com,github.com/eddycjy/mquote"

設定之後字首為 git.xxx.com 以及 github.com/eddycjy/mquote 的模組會被認為是私有模組

也可以直接用設定 *,以下表示來自.example.com 的子域名的都表示來自私有倉庫

1
$ go env -w GOPRIVATE="*.example.com"

啟用方式

Go modules預設不是開啟的,透過GO111MODULE來做設定,可用auto, on, off做設定

透過env進行設定 go env -w

1
$ go env -w GO111MODULE=on

初始化一個專案

在一個資料夾內,透過 go mod init 初始化一個專案

1
2
$ go mod init github.com/jellyhola/module-repo
go: creating new go.mod: module github.com/jellyhola/module-repo

這時會產生一個 go.mod 檔案,會紀錄目前專案模組為 github.com/jellyhola/module-repo,go版本為1.15

1
2
3
module github.com/jellyhola/module-repo

go 1.15

這時定義一份 main.go 檔案,import github.com/example/hello

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "rsc.io/quote"

func main() {
Hello()
}

//Hello :print quote.Hello()
func Hello() string {
return quote.Hello()
}

這時專案目錄會自動產生 go.sum檔案, 以及go.mod會多紀錄使用了 rsc.io/quote模組以及使用的版本為 1.5.2

go.sum

1
2
3
4
5
6
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

go.mod

1
2
3
4
5
module github.com/jellyhola/module-repo

go 1.15

require rsc.io/quote v1.5.2

建議將 go.sum與go.mod這兩個檔案提交到版本控制

由於本身rsc.io/quote這個模組,下載go時就已經安裝完畢,故不用透過 go get來額外下載

接著可用go list -m all 檢視目前專案所使用的依賴

1
2
3
4
5
$ go list -m all
github.com/jellyhola/module-repo
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0

可看到目前 golang.org/x/text這項模組,為untagged的狀況,接著可以透過 go get來更新此模組到最新的tag

1
2
3
$ go get golang.org/x/text
go: golang.org/x/text upgrade => v0.3.4
go: downloading golang.org/x/text v0.3.4

可看到 go.mod內容改變了

1
2
3
4
5
6
7
8
module github.com/jellyhola/module-repo

go 1.15

require (
golang.org/x/text v0.3.4 // indirect
rsc.io/quote v1.5.2
)

其中 // indirect 表示該模組為間接依賴,表示目前應用程式的import敘述中,並沒有發現這模組的明確參考,有可能是

  • 事先手動go get拉取下來的模組
  • 該專案所依賴模組之所依賴的

試著跑一下專案,驗證模組是否能正常運作

script
1
2
$ go run main.go
你好,世界。%

拉取的模組會存在哪?

go mod會將拉取的模組放置在 $GOPATH/pkg/mod$GOPATH/pkg/sumdb 目錄下!

1
2
$ ls $GOPATH/pkg
cache github.com golang.org rsc.io

若想要清除已經快取的模組版本,使用 go clean -modcache 指令

Go module 進階探討

Go modules的go get探討

使用go get取得模組時,會分別進行3個步驟

  1. finding: 查找模組是否存在
  2. downloading: 進行下載
  3. extracting: 分析下載的模組的雜湊值是否正確

像上一節所拉取的模組 golang.org/x/text 其拉取的資訊如下

script
1
2
版本資訊-commit的時間-雜湊值
v0.0.0-20170915032832-14c0d48ead0c
  • commit的時間會是以UTC時區為準
  • go get拉取的指令若沒有指定版本,會直接拉取到 v0.0.0版

go get行為

  • go get: 直接拉取dependency,只會更新指定的他自己
  • go get -u: 更新現有的dependency,會強制更新該denpendency所依賴的其他模組
  • go get -u -t: 更新所有denpendency,以及包含單元測試使用到的

go get版本指定

Go modules的go run與go build

執行go build時,會根據go.mod自動下載該專案所需的模組,才進行編譯

若是要使用 vendor目錄作為denpendency, 在執行 go mod vendor產生vendor目錄後,需要執行 go build -mod=vendor
才可使用 go build -mod=vendor 使用vendor目錄作為denpendency來編譯

Go modules匯入路徑說明

Go modules在主版本編號為 v0與v1的情況會省略其編號,在v2以上則需要明確指定!

假如要匯入 v2以上的golang.org/example@v2.0.0,則必須要在import底下明確指定v2

1
2
3
import (
"github.com/example/v2"
)

為何省略v0與v1??

因為官方鼓勵開發人員將模組建立到v1版本就不在變動。

所以若開發人員在發佈v2版本時,會被擁有明確的v1版本尾綴,近一步導致v1版本變成雜訊且沒有什麼意義

在匯入路徑忽略v0版本,因為根據語意化版本標準,v0的這些版本大多沒有相容性保證

Go modules 語意化版本控制

一個go modules的版本資訊如下

1
2
//主版本號.次版本號.修訂號
v1.2.3
  • 主版本編號:做了不相容的API修改之類的
  • 次版本編號: 做了向下相容的功能性新增
  • 修訂好: 向下相容的功能除錯

若是先行版本的話,那加上 pre 後綴

1
v1.2.3-pre

發佈新版本時應遵循語意化版本規則,否則無法被go get所拉取

go list的功能

提供該專案所有denpendency的資訊

透過 go list -m -u all 可以檢視所有的dependency資訊

1
2
3
4
5
6
$ go list -m -u all
github.com/jellyhola/module-repo
golang.org/x/text v0.3.4
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
  • -m: 顯示所有依賴的模組
  • -u: 顯示能夠升級的版本