go modules 初體驗(yàn)

引言

通過上一篇文章《go modules 基礎(chǔ)》的學(xué)習(xí),很多讀者已經(jīng)掌握了 go 原生的包依賴管理方案的基本知識(shí),于是在實(shí)踐中嘗試 go modules 機(jī)制的想法已箭在弦上,不得不發(fā)。

本文開啟 go modules 的實(shí)戰(zhàn)部分,將帶領(lǐng)讀者通過一段輕松的旅途,初步體驗(yàn)一下 go modules(簡(jiǎn)稱 go mod) 的強(qiáng)大。

go mod 工程的搭建

我們考慮在本地搭建一個(gè) go mod 工程,工程名為 hello-gomod,而工程所在的位置應(yīng)該是任意的,且不受原有 GOPATH mode 的影響。

本地 GOPATH 的路徑:

$ echo $GOPATH
/Users/zhangxiaolong/Desktop/D/go-workspace

我們先在 D 盤建立一個(gè)新目錄 gomod(與 GOPATH 的路徑無關(guān)),然后在 gomod 目錄下建立一個(gè)目錄 hello-gomod(工程名)。

打開 shell 終端,在 hello-gomod 目錄下運(yùn)行 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 目錄下生成了一個(gè)文件 go.mod,該文件中的內(nèi)容為:

  • 第一行為該工程的模塊名 github.com/agiledragon/hello-gomod。一般模塊命名為該工程所在代碼倉(cāng)庫(kù)的路徑,比如作者一般將代碼工程放在 github 上的個(gè)人目錄下
  • 第二行為本地 go 語言的版本。作者本地的 go 語言版本為 1.14,go mod 機(jī)制已成熟

在該工程下新建一個(gè)包 calculator,并實(shí)現(xiàn)一個(gè)簡(jiǎ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)
}

可見,在本工程中導(dǎo)入工程內(nèi)部的包時(shí)的路徑為:"模塊名" + "/" + "內(nèi)部包的相對(duì)路徑"。

至此,hello-gomod 工程下的目錄結(jié)構(gòu)為:

$ find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'
.
|____go.mod
|____calculator
| |____add.go
|____main.go

注:Mac 下默認(rèn)沒有 tree 命令,我們通過 find 命令來模擬。

在 hello-gomod 目錄下運(yùn)行工程:

$ go run main.go 
3

注:該模塊還沒有依賴任意的第三方包,所以在工程目錄下沒有生成 go.sum 文件,同時(shí) go.mod 文件也沒有任何變化。

go mod 工程的測(cè)試

為了提高代碼質(zhì)量,我們計(jì)劃為 calculator 包添加單元測(cè)試,于是新建了在 calculator 包下新建了測(cè)試文件 add_test.go,并寫了一個(gè)簡(jiǎn)單的測(cè)試用例:

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))
        })
    })
}

我們直接運(yùn)行該測(cè)試:

$ 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)測(cè)試運(yùn)行成功,并且 go test 命令在正式測(cè)試前下載了所有的依賴項(xiàng)。

我們?cè)龠\(yùn)行一下測(cè)試:

$ 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 運(yùn)行前不再下載依賴項(xiàng),那說明依賴項(xiàng)用的是本地的(上次測(cè)試下載的)。

這些依賴項(xiàng)是從哪下載的?讀者可能也想到了環(huán)境變量 GOPROXY,我們一起看一下:

$ echo $GOPROXY
https://goproxy.cn,direct

注:goproxy.cn 是七牛云維護(hù)的一個(gè)非營(yíng)利性項(xiàng)目,目標(biāo)是為中國(guó)及世界上其他地方的 gophers 們提供一個(gè)免費(fèi)的、可靠的、持續(xù)在線的且經(jīng)過 CDN 加速的模塊代理;“direct” 為特殊指示符,用于指示 go 回源到模塊版本的源地址去抓取 (比如 github 等),當(dāng)值列表中上一個(gè) go module proxy 返回 404 或 410 錯(cuò)誤時(shí),go 會(huì)自動(dòng)嘗試列表中的下一個(gè),遇見 “direct” 時(shí)回源,遇見 EOF 時(shí)終止并拋出類似 “invalid version: unknown revision...” 的錯(cuò)誤。

這些依賴項(xiàng)都下載到哪去了?依賴項(xiàng)是如何管理的?我們雖然對(duì)整個(gè)過程不是很清楚,但可以斷定這些問題肯定與 go.mod 文件有關(guān)。

我們先查看 go.mod 文件,發(fā)現(xiàn)多了一行:

module github.com/agiledragon/hello-gomod

go 1.14

require github.com/smartystreets/goconvey v1.6.4

注:最后一行是新增的,require 是關(guān)鍵字,指明 hello-gomod 工程的依賴項(xiàng),此處只有 goconvey 模塊(代碼工程)及其版本。

我們接著查看 hello-gomod 工程的目錄,發(fā)現(xiàn)模塊根目錄下(與 go.mod 同級(jí))增加了一個(gè)文件 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 文件是運(yùn)行 go test 命令自動(dòng)生成的,里面有直接依賴和間接依賴的所有模塊的導(dǎo)入路徑、版本號(hào)和哈希值,可以確保下次獲取的第三方依賴及其版本與本次完全相同,即保證了可重復(fù)構(gòu)建。

我們最后進(jìn)入 $GOPATH/pkg 目錄,發(fā)現(xiàn)多了一個(gè) mod 子目錄,同時(shí) mod 下有兩個(gè)子目錄:

$ pwd
/Users/zhangxiaolong/Desktop/D/go-workspace/pkg/mod
zhangxiaolongdeMacBook-Pro:mod zhangxiaolong$ ls
cache       github.com

注:細(xì)節(jié)先不用深究,我們僅知道 $GOPATH/pkg/mod 目錄下是 go mod 的緩存就可以了。當(dāng)運(yùn)行 go run,go build,go test 等命令時(shí),如果 go mod 緩存中有依賴項(xiàng),則直接獲取,否則先拉取到緩存,然后再?gòu)木彺嬷蝎@取。

當(dāng)用戶使用了 go mod 機(jī)制管理依賴包后,就棄用了 GOPATH mode,而這時(shí) go mod 的緩存仍然放在 $GOPATH/pkg/mod 目錄下。貌似 go mod 與 環(huán)境變量 GOPATH 仍然有一點(diǎn)點(diǎn)關(guān)系,但這種關(guān)系非常弱,因?yàn)橛脩艨梢栽谌我饽夸浄糯a工程,而不再局限在 $GOPATH/src 目錄下了。

更進(jìn)一步,如果我們不配置環(huán)境變量 GOPATH ,go mod 緩存會(huì)存放在哪里?

我們先取消環(huán)境變量 GOPATH 的設(shè)置:

$ echo $GOPATH
/Users/zhangxiaolong/Desktop/D/go-workspace
$ unset GOPATH
$ echo $GOPATH

$ 

我們?cè)龠\(yùn)行一下測(cè)試,發(fā)現(xiàn)測(cè)試仍然通過,并且 go test 命令在正式測(cè)試前下載了所有的依賴項(xiàng):與我們?cè)O(shè)置了 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 緩存就會(huì)自動(dòng)下載到 $HOME/go/pkg/mod 目錄下。本質(zhì)上,$HOME/go 就是 GOPATH 變量的值(從 go1.8 版本開始,GOPATH 變量有了默認(rèn)值 $HOME/go)。

go mod 工程的 CI

go mod 工程上線 CI 后,代碼中沒有了存放第三方依賴項(xiàng)的 vendor 目錄,編譯時(shí)需要實(shí)時(shí)拉取第三方依賴。

這里有兩個(gè)問題:

  • CI 運(yùn)行過程中不能訪問大網(wǎng)(公網(wǎng))
  • CI 運(yùn)行過程中對(duì)性能和可靠性要求高,從大網(wǎng)下載所有依賴項(xiàng)有風(fēng)險(xiǎn)

針對(duì)這些問題,有兩種解決方案:

  • 使用 go mod vendor 命令生成一個(gè) vendor 目錄,并上傳到代碼庫(kù)
  • 搭建一個(gè)私有的 go proxy

關(guān)于如何搭建一個(gè)私有的 go proxy,本文不進(jìn)行深入討論,在后續(xù)的文章中可能會(huì)涉及。

我們?cè)谀K根目錄下運(yùn)行 go mod vendor 命令:

$ go mod vendor
go: downloading github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1

注:我們運(yùn)行 go test 命令時(shí),沒有下載 gopherjs 模塊,而運(yùn)行 go mod vendor 命令時(shí),自動(dòng)下載了一個(gè)新的依賴項(xiàng)。

我們進(jìn)入到 vendor 目錄,并查看子目錄:

$ cd vendor
$ ls
github.com  modules.txt

一個(gè)代碼工程從 GOPATH mode 遷移到 module-aware(go mod) mode 時(shí),可能的三種狀態(tài):

  • state1:GOPATH mode vendor
  • state2:go.mod + go.sum + module-aware mode vendor
  • state3:go.mod + go.sum + go proxy

對(duì)于一個(gè)大型項(xiàng)目,有很多個(gè)團(tuán)隊(duì),而每個(gè)團(tuán)隊(duì)又有多個(gè) go 工程,作者建議遷移 go mod 時(shí)增加臨時(shí)狀態(tài) state2,即工程狀態(tài)變化為:state1 ==> state2 ==> state3;對(duì)于一個(gè)小型項(xiàng)目,總共只有幾個(gè) go 工程,作者建議遷移 go mod 時(shí)直接到目標(biāo)狀態(tài) state3,即工程狀態(tài)變化為:state1 ==> state3。

小結(jié)

本文通過一個(gè)簡(jiǎn)單的案例,帶領(lǐng)讀者初步體驗(yàn)了 go mod 機(jī)制的強(qiáng)大。讀者在輕松愉悅的旅途中理清了 go mod 的基本原理,掌握了 go mod 的簡(jiǎn)單用法,后續(xù)可以嘗試在學(xué)習(xí)或工作中使用 go mod 來管理工程的依賴項(xiàng),從而真實(shí)感受 go mod 的魅力。

下一篇文章《gomonkey 的 go mod 改造之旅》預(yù)告:
gomonkey 是作者自研的一款 go 語言的打樁框架,深受國(guó)內(nèi)外 gophers 的喜愛,目前已有 336 個(gè) star。gomonkey 在運(yùn)行時(shí)不依賴第三方包,長(zhǎng)期以來一直保持 GOPATH mode 并不會(huì)出現(xiàn)任何問題。gomonkey 提供了詳盡的測(cè)試用例, 雖然運(yùn)行這些用例需要依賴第三方包(測(cè)試框架),但對(duì)于大多數(shù)用戶來說他們并不實(shí)際運(yùn)行這些用例,而是僅僅將這些用例當(dāng)作 API 文檔來學(xué)習(xí)。我們嘗試用 go mod 機(jī)制來改造 gomonkey 工程,希望能給讀者帶來更佳的用戶體驗(yàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容