正在或即將被使用的Go依賴包管理方法:Go Modules,Go 1.13的標准特性


公眾號原文地址:https://mp.weixin.qq.com/s/SGGV3tWEg5AAJ7I_FcK0cg

目錄

  • 目錄

  • 說明

  • 初始化

  • 依賴包的默認導入

  • 依賴包的特定版本導入

  • 查看已添加依賴

  • 依賴包的存放管理

  • 依賴包的版本切換

  • 刪除未使用依賴包

  • 引用項目中的 package

  • 引用不同版本的父子目錄

  • 實例演示

  • 需要注意的坑

  • IDE 與 Go Modules

  • IntelliJ IDEA/Goland

  • vim

  • 參考

說明

Go 的依賴包管理一直是個問題,先后出現了 godep、glide、dep 等一系列工具,vendor 機制使依賴包的管理方便了很多,但依然沒有統一的管理工具,不同的項目各用各的方法。

另外使用 vendor 后,每個項目都完整拷貝一份依賴包,既不方便管理又浪費了本地空間。

此外,Go 項目中的 import 指令后面的 package 路徑與項目代碼的存放路徑相關,項目目錄不能隨意移動,必須安分守己地趴在 $GOPATH/src 中,否則 import 會找不到項目中的 package,雖然可以通過在容器中編譯或者為每個項目准備一套 Go 環境的方式解決,但是麻煩且有額外開銷。

Go1.11 和 Go1.12 引入的 Go Modules 機制,提供了統一的依賴包管理工具 go mod,依賴包統一收集在 $GOPATH/pkg/mod 中進行集中管理,並且將 import 路徑與項目代碼的實際存放路徑解耦,使 package 定義導入更加靈活。

Go Modules 將成為 Go1.13 默認的依賴包管理方法,在 Go1.11 和 Go1.12 中, Go Modules 只能在 $GOPATH 外部使用,Using Go Modules 中有詳細介紹。

很多開源項目已經改用 Go Modules 了,瀏覽代碼的時候會發現,很多項目的 master 分支中增加了 go.mod 和 go.sum 文件。

Go Modules 的主要功能就四個:添加依賴、更新依賴、刪除依賴,以及多版本依賴。

初始化

Go Modules 的初始化命令為 go mod init <ROOTPATH>,ROOTPATH 是項目的 import 路徑。

在 $GOPATH 外部創建一個目錄,然后初始化,項目的路徑設置為 exampe.com/hello

$ mkdir go-modules-example$ cd go-modules-example$ go mod init example.com/hello        # 該項目代碼的引用路徑是 example.com/hellogo: creating new go.mod: module example.com/hello 

引用該項目中的 package 時使用前綴 example.com/hello

項目下生成一個 go.mod 文件,里面記錄了 module 路徑和 go 的版本,剛創建時這個文件中沒有依賴信息:

$ cat go.modmodule example.com/hellogo 1.12 

對於 Go1.11 和 Go1.12,如果在 $GOPATH 中執行 go mod 會遇到下面的錯誤:

$ go mod init example.com/hellogo: modules disabled inside GOPATH/src by GO111MODULE=auto; see 'go help modules' 

依賴包的默認導入

在 go-modules-example 中創建一個 main.go,簡單寫幾行代碼,引入 “github.com/lijiaocn/golib/version”:

// Create: 2019/05/05 16:53:00 Change: 2019/05/05 16:56:53// FileName: main.go// Copyright (C) 2019 lijiaocn <lijiaocn@foxmail.com>//// Distributed under terms of the GPL license.package mainimport ( "github.com/lijiaocn/golib/version")func main() { version.Show()} 

用下面的 Makefile 編譯( Makefile 純粹為了方便,直接用 go build 也可以):

# Makefile# lijiaocn, 2019-05-05 16:56#VERSION=1.0.0COMPILE=$(shell date -u "+%Y-%m-%d/%H:%M:%S")all: buildbuild: go build -ldflags "-X github.com/lijiaocn/golib/version.VERSION=${VERSION} -X github.com/lijiaocn/golib/version.COMPILE=${COMPILE}" 

編譯或者用 go test 運行測試代碼時,默認將 import 引入的 package 的最新版本寫入 go.mod 和 go.sum:

$ makego build -ldflags "-X github.com/lijiaocn/golib/version.VERSION=1.0.0 -X github.com/lijiaocn/golib/version.COMPILE=2019-05-05/09:55:04"go: finding github.com/lijiaocn/golib v0.0.1go: downloading github.com/lijiaocn/golib v0.0.1go: extracting github.com/lijiaocn/golib v0.0.1 

go.mod 中寫入依賴關系:

$ cat go.modmodule example.com/hellogo 1.12require github.com/lijiaocn/golib v0.0.1 

go.sum 中記錄的完整依賴:

$ cat go.sumgithub.com/lijiaocn/golib v0.0.1 h1:bC8xWHei7xTa8x65ShiPBNjVYXoxt6EDmnSUaGgRUW8=github.com/lijiaocn/golib v0.0.1/go.mod h1:BUO0RF2eDlol519GuXLQtlku8pdUim0h+f6wvX/AsNk= 

依賴包的特定版本導入

在使用 go modules 的項目目錄中,用 go get 下載的代碼包自動作為依賴包添加,例如:

$ go get github.com/lijiaocn/codes-go/01-02-hellogo: finding github.com/lijiaocn/codes-go/01-02-hello latestgo: finding github.com/lijiaocn/codes-go latestgo: downloading github.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7ego: extracting github.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7e 

go.mod 中增加了一行記錄,新增的依賴被標注為 indirect,意思是還沒有被使用:

$ cat go.modmodule example.com/hellogo 1.12require ( github.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7e // indirect github.com/lijiaocn/golib v0.0.1) 

在用 go get 添加依賴的時候,可以用 @v1.1 樣式的后綴指定依賴的版本,例如:

$ go get github.com/lijiaocn/glib@v0.0.2 

查看已添加依賴

go list 命令列出當前項目的依賴包以及代碼版本:

$ go list -m allexample.com/hellogithub.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7egithub.com/lijiaocn/golib v0.0.1 

依賴包的存放管理

依賴包既不在 GOPATH/src 目錄中,也不在 vendor 目錄(Go Moduels 不會創建 vendor 目錄),而是在 `GOPATH/pkg/mod` 目錄中:

$ ls $GOPATH/pkg/mod/github.com/lijiaocn/codes-go@v0.0.0-20180220071929-9290fe35de7e golib@v0.0.1$ ls $GOPATH/pkg/mod/github.com/lijiaocn/golib@v0.0.1config container generator terminal version virtio 

如上所示,目錄名中包含版本信息,例如 golib@v0.0.1。

$GOPATH/pkg/mod/cache/download/ 中有原始代碼的緩存,避免重復下載:

$ ls $GOPATH/pkg/mod/cache/download/github.com/lijiaocncodes-go golib$ ls $GOPATH/pkg/mod/cache/download/github.com/lijiaocn/golib/@vlist           list.lock      v0.0.1.info v0.0.1.lock v0.0.1.mod v0.0.1.zip v0.0.1.ziphash 

依賴包的版本切換

依賴代碼的版本更新很簡單,直接用 go get 獲取指定版本的依賴代碼即可,例如將 lijiaocn/glib 更新到 v0.0.2:

$ go get github.com/lijiaocn/glib@v0.0.2go: finding github.com/lijiaocn/golib v0.0.2go: downloading github.com/lijiaocn/golib v0.0.2go: extracting github.com/lijiaocn/golib v0.0.2 

可以看到依賴的代碼版本發生了變化:

$ go list -m allexample.com/hellogithub.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7egithub.com/lijiaocn/golib v0.0.2 

刪除未使用依賴

不需要的依賴必須手動清除,執行 go mod tidy,清除所有未使用的依賴:

$ go mod tidy
$ go list -m allexample.com/hellogithub.com/lijiaocn/golib v0.0.2 

引用項目中的 package

在項目中創建一個名為 display 的 package:

$ tree displaydisplay└── display.go

導入時使用 go mod 初始化時定義的前綴,example.com/hello/display

import ( "example.com/hello/display" "github.com/lijiaocn/golib/version" ) 

引用當前項目中的 package 時,import 使用的路徑和項目所在的路徑徹底解耦,但是要注意,如果提供給外部項目使用,需要確保 go get 能夠從 example.com 獲得 /hello/display。

引用不同版本的父子目錄

Using Go Modules 中有一節是 Adding a dependency on a new major version,示例中引入了 v1.5.2 版本的 rsc.io/quote,和 v3.1.0 版本的 rsc.io/quote/v3,這兩個 package 是父子目錄,版本不相同:

package helloimport ( "rsc.io/quote" quoteV3 "rsc.io/quote/v3")func Hello() string { return quote.Hello()}func Proverb() string { return quoteV3.Concurrency()} 
rsc.io tree quotequote├── LICENSE├── README.md├── buggy│ └── buggy_test.go├── go.mod├── go.sum├── quote.go├── quote_test.go└── v3 ├── go.mod ├── go.sum └── quote.go2 directories, 10 files 

注意,v3 是一個真實存在的子目錄,必須是用 go modules 管理的,rsc.io/quote 和 rsc.io/quote/v3 是父子目錄,但它們是完全獨立的 package。

引用 1.5.2 版本的 rsc.io/quote 和 v3.1.0 版本的 rsc.io/quote/v3 :

$ go get rsc.io/quote@v1.5.2 ...$ go get rsc.io/quote/v3@v3.1.0 ... 

可以看到兩個版本同時存在:

$ go list -m rsc.io/q...rsc.io/quote v1.5.2rsc.io/quote/v3 v3.1.0 

實例演示

實現一個用 go modules 管理的 package: github.com/introclass/go_mod_example_pkg

 
image

在另一個使用 go modules 的項目中引用 v1.0.1 版本:github.com/introclass/go-mod-example

$ go get github.com/introclass/go_mod_example_pkg@v1.0.1go: finding github.com/introclass/go_mod_example_pkg v1.0.1go: downloading github.com/introclass/go_mod_example_pkg v1.0.1go: extracting github.com/introclass/go_mod_example_pkg v1.0.1 

查看依賴的代碼,顯示依賴的是 v1.0.1:

$ go list -m allexample.com/hellogithub.com/introclass/go_mod_example_pkg v1.0.1github.com/lijiaocn/golib v2.0.1+incompatible 

在 main 函數中使用導入的依賴包:

package mainimport ( "example.com/hello/display" pkg "github.com/introclass/go_mod_example_pkg" "github.com/lijiaocn/golib/version")func main() { version.Show() display.Display("display print\n") pkg.Vesrion()} 

編譯執行,輸出的v1.0.1:

$ ./helloversion: compile at: golib v2display printv1.0.1 

將依賴包切換到版本 2.0.1:

$ go get github.com/introclass/go_mod_example_pkg@v2.0.1go: finding github.com/introclass/go_mod_example_pkg v2.0.1 

重新編譯執行,輸出的版本是 v2.0.1:

$ ./helloversion: compile at: golib v2display printv2.0.1 

引用依賴包 v3.0.1 版本的 v3 子目錄(事實上是一個獨立的 pacakge ):

$ go get github.com/introclass/go_mod_example_pkg/v3@v3.0.1go: finding github.com/introclass/go_mod_example_pkg/v3 v3.0.1go: downloading github.com/introclass/go_mod_example_pkg/v3 v3.0.1go: extracting github.com/introclass/go_mod_example_pkg/v3 v3.0.1 

修改 main 函數,引用 v3:

package mainimport ( "example.com/hello/display" pkg "github.com/introclass/go_mod_example_pkg" pkgv3 "github.com/introclass/go_mod_example_pkg/v3" "github.com/lijiaocn/golib/version")func main() { version.Show() display.Display("display print\n") pkg.Vesrion() pkgv3.Vesrion()} 

重新編譯執行,分別輸出 v2.0.1 和 v3.0.1 in v3:

$ ./helloversion: compile at: golib v2display printv2.0.1v3.0.1 in v3 

需要注意的坑

1、引用不同版本的父子目錄,被引用的父子目錄必須是用 go mod 管理的 package,非 go mod 管理的代碼不行;

2、go mod 會在本地緩存代碼,如果被引用的代碼的版本號不變,但是代碼變了(在做實驗或者代碼版本管理比較亂的時候,可能會出現的這種情況),清除本地緩存( GOPATH/pkg/mod/cache 和GOPATH/pkg/mod/ 依賴代碼 )后,才能重新拉取最新的代碼(可能會有其它的更新緩存的方法);

3、如果被外部項目引用,go.mod 中設置的 package 路徑需要與代碼的獲取地址相同,項目內部引用沒有該限制,github.com/introclass/go-mod-example 的 go.mod 中標注的是 example.com/hello,代碼獲取地址 github.com/intraoclass/go-mode-example 與 example.com/hello 不一致,在另一個項目中用 github 地址加載時會失敗:

$ go get github.com/introclass/go-mod-examplego: finding github.com/introclass/go-mod-example latestgo: github.com/introclass/go-mod-example@v0.0.0-20190605063729-4a841a8278e3: parsing go.mod: unexpected module path "example.com/hello"go: error loading module requirements 

IDE 與 Go Modules 項目

IntelliJ IDEA/Goland

在 IntelliJ IDEA 或者 Goland 中(需要是最新的2019.1版本)導入使用 Go Module 的項目的時候,要選擇 Go Module(vgo),否則 IDE 找不到 import 導入的代碼,create-a-project-with-vgo-integration 有更多介紹:

 
image

IntelliJ IDEA/Goland 左側編碼顯示的依賴代碼(帶有版本號或者 commit id):

 
image

vim

vim插件 vim-go 從 v1.19 開始支持 go.mod,但是代碼跳轉等還不支持。

cmd/go: track tools/tooling updates to support modules 列出了一些工具對 go module 的支持情況。

參考

  1. Using Go Modules

  2. Where is the module cache in golang?

  3. create-a-project-with-vgo-integration

  4. cmd/go: track tools/tooling updates to support modules

上一篇:源代碼閱讀方法(附Go語言項目的代碼閱讀技巧)

關注后加作者微信

 
image

公眾號原文地址:https://mp.weixin.qq.com/s/SGGV3tWEg5AAJ7I_FcK0cg


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM