引言
通過上一篇文章《go modules 基礎》的學習穆役,很多讀者已經(jīng)掌握了 go 原生的包依賴管理方案的基本知識娜氏,于是在實踐中嘗試 go modules 機制的想法已箭在弦上或听,不得不發(fā)炕置。
本文開啟 go modules 的實戰(zhàn)部分变过,將帶領讀者通過一段輕松的旅途沐扳,初步體驗一下 go modules(簡稱 go mod) 的強大汤踏。
go mod 工程的搭建
我們考慮在本地搭建一個 go mod 工程织鲸,工程名為 hello-gomod,而工程所在的位置應該是任意的溪胶,且不受原有 GOPATH mode
的影響搂擦。
本地 GOPATH
的路徑:
$ echo $GOPATH
/Users/zhangxiaolong/Desktop/D/go-workspace
我們先在 D 盤建立一個新目錄 gomod(與 GOPATH
的路徑無關),然后在 gomod 目錄下建立一個目錄 hello-gomod(工程名)哗脖。
打開 shell 終端瀑踢,在 hello-gomod 目錄下運行 go mod init 命令:
$ go mod init github.com/agiledragon/hello-gomod
go: creating new go.mod: module github.com/agiledragon/hello-gomod
$ cat go.mod
module github.com/agiledragon/hello-gomod
go 1.14
我們發(fā)現(xiàn)在 hell-gomod 目錄下生成了一個文件 go.mod,該文件中的內(nèi)容為:
- 第一行為該工程的模塊名
github.com/agiledragon/hello-gomod
才避。一般模塊命名為該工程所在代碼倉庫的路徑橱夭,比如作者一般將代碼工程放在 github 上的個人目錄下 - 第二行為本地 go 語言的版本。作者本地的 go 語言版本為 1.14工扎,go mod 機制已成熟
在該工程下新建一個包 calculator徘钥,并實現(xiàn)一個簡單的函數(shù) add:
package calculator
func Add(a, b int) int {
return a + b
}
在 main 包中調(diào)用 math 包的 Add 函數(shù):
package main
import (
"fmt"
"github.com/agiledragon/hello-gomod/calculator"
)
func main() {
r := 1
r = calculator.Add(1, 2)
fmt.Println(r)
}
可見,在本工程中導入工程內(nèi)部的包時的路徑為:"模塊名" + "/" + "內(nèi)部包的相對路徑"
肢娘。
至此呈础,hello-gomod 工程下的目錄結(jié)構(gòu)為:
$ find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'
.
|____go.mod
|____calculator
| |____add.go
|____main.go
注:Mac 下默認沒有 tree 命令,我們通過 find 命令來模擬橱健。
在 hello-gomod 目錄下運行工程:
$ go run main.go
3
注:該模塊還沒有依賴任意的第三方包而钞,所以在工程目錄下沒有生成 go.sum 文件,同時 go.mod 文件也沒有任何變化拘荡。
go mod 工程的測試
為了提高代碼質(zhì)量臼节,我們計劃為 calculator 包添加單元測試,于是新建了在 calculator 包下新建了測試文件 add_test.go珊皿,并寫了一個簡單的測試用例:
package calculator
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestAdd(t *testing.T) {
convey.Convey("TestAdd", t, func() {
convey.Convey("normal case", func() {
convey.So(3, convey.ShouldEqual, Add(1, 2))
})
})
}
我們直接運行該測試:
$ go test -v ./calculator/
go: downloading github.com/smartystreets/goconvey v1.6.4
go: downloading github.com/jtolds/gls v4.20.0+incompatible
go: downloading github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d
=== RUN TestAdd
TestAdd
normal case ?
1 total assertion
--- PASS: TestAdd (0.00s)
PASS
ok github.com/agiledragon/hello-gomod/calculator (cached)
我們發(fā)現(xiàn)測試運行成功网缝,并且 go test 命令在正式測試前下載了所有的依賴項。
我們再運行一下測試:
$ go test -v ./calculator/
=== RUN TestAdd
TestAdd
normal case ?
1 total assertion
--- PASS: TestAdd (0.00s)
PASS
ok github.com/agiledragon/hello-gomod/calculator 0.006s
我們發(fā)現(xiàn)本次 go test 運行前不再下載依賴項蟋定,那說明依賴項用的是本地的(上次測試下載的)粉臊。
這些依賴項是從哪下載的?讀者可能也想到了環(huán)境變量 GOPROXY驶兜,我們一起看一下:
$ echo $GOPROXY
https://goproxy.cn,direct
注:goproxy.cn 是七牛云維護的一個非營利性項目扼仲,目標是為中國及世界上其他地方的 gophers 們提供一個免費的远寸、可靠的、持續(xù)在線的且經(jīng)過 CDN 加速的模塊代理屠凶;“direct” 為特殊指示符驰后,用于指示 go 回源到模塊版本的源地址去抓取 (比如 github 等),當值列表中上一個 go module proxy 返回 404 或 410 錯誤時矗愧,go 會自動嘗試列表中的下一個灶芝,遇見 “direct” 時回源,遇見 EOF 時終止并拋出類似 “invalid version: unknown revision...” 的錯誤唉韭。
這些依賴項都下載到哪去了监署?依賴項是如何管理的?我們雖然對整個過程不是很清楚纽哥,但可以斷定這些問題肯定與 go.mod 文件有關。
我們先查看 go.mod 文件栖秕,發(fā)現(xiàn)多了一行:
module github.com/agiledragon/hello-gomod
go 1.14
require github.com/smartystreets/goconvey v1.6.4
注:最后一行是新增的春塌,require 是關鍵字,指明 hello-gomod 工程的依賴項簇捍,此處只有 goconvey 模塊(代碼工程)及其版本只壳。
我們接著查看 hello-gomod 工程的目錄,發(fā)現(xiàn)模塊根目錄下(與 go.mod 同級)增加了一個文件 go.sum:
$ cat go.sum
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
注:本 go.sum 文件是運行 go test 命令自動生成的暑塑,里面有直接依賴和間接依賴的所有模塊的導入路徑吼句、版本號和哈希值,可以確保下次獲取的第三方依賴及其版本與本次完全相同事格,即保證了可重復構(gòu)建惕艳。
我們最后進入 $GOPATH/pkg
目錄,發(fā)現(xiàn)多了一個 mod 子目錄驹愚,同時 mod 下有兩個子目錄:
$ pwd
/Users/zhangxiaolong/Desktop/D/go-workspace/pkg/mod
zhangxiaolongdeMacBook-Pro:mod zhangxiaolong$ ls
cache github.com
注:細節(jié)先不用深究,我們僅知道 $GOPATH/pkg/mod
目錄下是 go mod 的緩存就可以了。當運行 go run把跨,go build们拙,go test 等命令時,如果 go mod 緩存中有依賴項劫瞳,則直接獲取倘潜,否則先拉取到緩存,然后再從緩存中獲取志于。
當用戶使用了 go mod 機制管理依賴包后涮因,就棄用了 GOPATH mode
,而這時 go mod 的緩存仍然放在 $GOPATH/pkg/mod
目錄下恨憎。貌似 go mod 與 環(huán)境變量 GOPATH
仍然有一點點關系蕊退,但這種關系非常弱郊楣,因為用戶可以在任意目錄放代碼工程,而不再局限在 $GOPATH/src
目錄下了瓤荔。
更進一步净蚤,如果我們不配置環(huán)境變量 GOPATH
,go mod 緩存會存放在哪里输硝?
我們先取消環(huán)境變量 GOPATH
的設置:
$ echo $GOPATH
/Users/zhangxiaolong/Desktop/D/go-workspace
$ unset GOPATH
$ echo $GOPATH
$
我們再運行一下測試今瀑,發(fā)現(xiàn)測試仍然通過,并且 go test 命令在正式測試前下載了所有的依賴項:與我們設置了 GOPATH
環(huán)境變量的情況完全相同
$ go test -v ./calculator/
go: downloading github.com/smartystreets/goconvey v1.6.4
go: downloading github.com/jtolds/gls v4.20.0+incompatible
go: downloading github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d
=== RUN TestAdd
TestAdd
normal case ?
1 total assertion
--- PASS: TestAdd (0.00s)
PASS
ok github.com/agiledragon/hello-gomod/calculator 0.008s
注:我們?nèi)∠?GOPATH
環(huán)境變量后点把,go mod 緩存就會自動下載到 $HOME/go/pkg/mod
目錄下橘荠。本質(zhì)上,$HOME/go
就是 GOPATH
變量的值(從 go1.8 版本開始郎逃,GOPATH
變量有了默認值 $HOME/go
)哥童。
go mod 工程的 CI
go mod 工程上線 CI 后,代碼中沒有了存放第三方依賴項的 vendor 目錄褒翰,編譯時需要實時拉取第三方依賴贮懈。
這里有兩個問題:
- CI 運行過程中不能訪問大網(wǎng)(公網(wǎng))
- CI 運行過程中對性能和可靠性要求高,從大網(wǎng)下載所有依賴項有風險
針對這些問題优训,有兩種解決方案:
- 使用 go mod vendor 命令生成一個 vendor 目錄朵你,并上傳到代碼庫
- 搭建一個私有的 go proxy
關于如何搭建一個私有的 go proxy,本文不進行深入討論揣非,在后續(xù)的文章中可能會涉及抡医。
我們在模塊根目錄下運行 go mod vendor 命令:
$ go mod vendor
go: downloading github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1
注:我們運行 go test 命令時,沒有下載 gopherjs 模塊早敬,而運行 go mod vendor 命令時忌傻,自動下載了一個新的依賴項。
我們進入到 vendor 目錄搁嗓,并查看子目錄:
$ cd vendor
$ ls
github.com modules.txt
一個代碼工程從 GOPATH mode
遷移到 module-aware(go mod) mode
時芯勘,可能的三種狀態(tài):
- state1:
GOPATH mode
vendor - state2:go.mod + go.sum +
module-aware mode
vendor - state3:go.mod + go.sum + go proxy
對于一個大型項目,有很多個團隊腺逛,而每個團隊又有多個 go 工程荷愕,作者建議遷移 go mod 時增加臨時狀態(tài) state2,即工程狀態(tài)變化為:state1 ==> state2 ==> state3棍矛;對于一個小型項目安疗,總共只有幾個 go 工程,作者建議遷移 go mod 時直接到目標狀態(tài) state3够委,即工程狀態(tài)變化為:state1 ==> state3荐类。
小結(jié)
本文通過一個簡單的案例,帶領讀者初步體驗了 go mod 機制的強大茁帽。讀者在輕松愉悅的旅途中理清了 go mod 的基本原理玉罐,掌握了 go mod 的簡單用法屈嗤,后續(xù)可以嘗試在學習或工作中使用 go mod 來管理工程的依賴項,從而真實感受 go mod 的魅力吊输。
下一篇文章《gomonkey 的 go mod 改造之旅》預告:
gomonkey 是作者自研的一款 go 語言的打樁框架饶号,深受國內(nèi)外 gophers 的喜愛,目前已有 336 個 star季蚂。gomonkey 在運行時不依賴第三方包茫船,長期以來一直保持 GOPATH mode 并不會出現(xiàn)任何問題。gomonkey 提供了詳盡的測試用例扭屁, 雖然運行這些用例需要依賴第三方包(測試框架)算谈,但對于大多數(shù)用戶來說他們并不實際運行這些用例,而是僅僅將這些用例當作 API 文檔來學習料滥。我們嘗試用 go mod 機制來改造 gomonkey 工程然眼,希望能給讀者帶來更佳的用戶體驗。