Go Modules 踩坑總結

微信公衆號「後端進階」,專注後端技術分享:Java、Golang、WEB框架、分佈式中間件、服務治理等等。

在 Java 的項目中,有 Maven 和 Gradle 這些很好用的依賴版本管理工具,簡直不要太方便了,但是在 Golang 的項目中,之前的 Golang 官方並沒有提供版本管理工具,我們以前用 go get 獲取依賴其實是有潛在危險的,因爲我們不確定最新版依賴是否會破壞掉我們項目對依賴包的使用方式,即當前項目可能會出現不兼容最新依賴包的問題。之後官方出了一個 vendor 機制,將項目依賴的包都放在該目錄中,但這也並沒有很好地管理依賴的版本。之後官方出了一個準官方版本管理工具 go dep,這也算是 go modules 的前身了吧。隨着 Go1.11 的發佈,Golang 給我們帶來了 module 全新特性,這是 Golang 新的一套依賴管理系統。現在 Go1.12 已經發布了,go modules 進一步穩定,但官方還是沒有將其設爲默認機制,所以踩坑之路是必須的,本篇文章除了詳細說明 go modules 的特性以及使用之外,還總結了我在這個過程中遇到的一些“坑”。

創建 module

  • 創建項目

在默認情況下,$GOPATH 默認情況下是不支持 go mudules 的,我們需要在項目目錄下手動執行以下命令:

$ export GO111MODULE=on

這也表明了 go 要利用 modules 機制消滅 $GOPATH 的決心啊!

爲了配合 go modules 機制,我們 $GOPATH 以外的目錄創建一個 testmod 的包:

$ mkdir testmod
$ cd testmod

$ echo 'package testmod
        import "fmt"
    func SayHello(name string) string {
   return fmt.Sprintf("Hello, %s", name)
}' >> testmod.go
  • 初始化 module:
$ go mod init github.com/objcoding/testmod
go: creating new go.mod: module github.com/objcoding/testmod

以上命令會在項目中創建一個 go.mod 文件,初始化內容如下:

module github.com/objcoding/testmod

這時,我們的項目已經成爲了一個 module 了。

  • 推送到 github 倉庫
$ git init
$ git add *
$ git commit -am "First commit"
$ git push -u origin master

在這裏我也着重說下關於項目依賴包引用地址的問題,這個問題雖小,但也確實很困擾人,所以必須得說一下:

go mudules 出現之前,在一個項目中有很多個包,在項目內,有些包需要依賴項目內其它包,假設項目有個包,相對於 gopath 的地址是 objcoding/mypackage,在項目內其它包引用這個包時,就可以通過以下引用:

import myproject/mypackage

但你有沒有想過,當別的項目需要引用你的項目中的某些包,那麼就需要遠程下載依賴包了,這時就需要項目的倉庫地址引用,比如下面這樣:

import github.com/objcoding/myproject/mypackage

go modules 發佈之後,就完全統一了包引用的地址,如上面我們說的創建 go.mod 文件後,初始化內容的第一行就是我們說的項目依賴路徑,通常來說該地址就是項目的倉庫地址,所有需要引用項目包的地址都填寫這個地址,無論是內部之間引用還是外部引用,舉個例子,goim 的內部包引用:

go.mod module:

內部包引用:

也即是說,在項目 啓用了 go modules 之後,引用包必須跟 go mod 文件第一行包名一樣,

依賴的包都會保存在 ${GOPATH}/pkg/mod 文件夾中了,我們也可以在項目底部那裏查看依賴包:

但也有可能會出現依賴包地址正確但會報紅的情況,這時極有可能是你在 Goland 編輯器中沒有將項目設置爲 go modules 項目,具體設置如下:

勾選了該選項之後,就會在 External Libraries 中出現 Go Modules 目錄。

版本規則

go modules 是一個版本化依賴管理系統,版本需要遵循一些規則,比如版本號需要遵循以下格式:

vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef
vX.0.0-yyyymmddhhmmss-abcdefabcdef
vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef
vX.Y.Z

vX.Y.Z 是我們倉庫打的標籤版本,也就是 go modules 是根據倉庫標籤來確定版本號的,因此我們發佈版本時,需要給我們的倉庫打上一個標籤版本號。

也就是版本號 + 時間戳 +hash,我們自己指定版本時只需要制定版本號即可,沒有版本 tag 的則需要找到對應 commit 的時間和 hash 值。

還有一個重要的規則是,版本 0 和 1,最好需要有不同的依賴路徑,如:v1.0.0 和 v2.0.0 是有不同的依賴路徑,下面會詳細介紹一下這個版本規則。

發佈版本

瞭解了 go modules 的版本規則後,現在我們發佈一下該項目的版本:

$ git tag v1.0.0
$ git push --tags

這時我們最好還需要創建一條 v1 分支,以便我們在其它分支寫代碼不會影響到 v1.0.0 版本:

$ git checkout -b v1
$ git push -u origin v1

使用 module

現在我們把剛剛做好的 module,拿過來用,新建一個 gomodules 項目:

package main

import (
    "fmt"
    "github.com/objcoding/testmod"
)

func main() {
    fmt.Println(testmod.SayHello("張乘輝"))
}

以前我們可以直接通過 go get 命令獲取依賴包,但是對於一個 module 項目來說,就遠遠比這個有趣多了,現將項目初始化成 module 項目:

$ go mod init

這時 go build 等命令就會下載依賴包,並把依賴信息添加到 go.mod 文件中,同時把依賴版本哈希信息存到 go.sum 文件中:

$ go build

go: finding github.com/objcoding/testmod v1.0.0
go: downloading github.com/objcoding/testmod v1.0.0

這時,go.mod 文件內容如下:

module gomodules
require github.com/objcoding/testmod v1.0.0

go.sum 文件內容如下:

github.com/objcoding/testmod v1.0.0 h1:fGa15gBXoqkG0BVkQGP9i5Pg2nt8nayFpHFf+GLiX6A=
github.com/objcoding/testmod v1.0.0/go.mod h1:LGpYEmOLZhLQC3JW88STU2Ja3rsfoGZmsidsHJhDNGU=

這裏還需要注意的是,有時候我們引用 golang.org/x/ 的一些包,但發現在偉大的天朝這個地址是被qian了,但是我們程序員也充分發揮了勤奮好學的優良傳統,在 go modules 中設置了 goproxy 機制,如果 go modules 設置了代理,會優先從代理中下載依賴包,在 /etc/profile 中加入以下內容:

export GOPROXY="https://goproxy.io"

goproxy.io 谷歌官方的代理地址,當然還有很多國內優秀的第三方代理。

你也可以在 Goland編輯器中設置:

升級版本

現在我們來升級一下 testmod 項目:

$ cd gomodules
$ echo 'package testmod
        import "fmt"
    func SayHello(name string) string {
   return fmt.Sprintf("你好, %s", name)
}' >> testmod.go

我把「Hello」改成「你好」,我們把這個修改在 v1 分支中進行:

$ git commit -m "update testmod" testmod.go
$ git tag v1.0.1
$ git push --tags origin v1

現在我們的 項目已經升級到 v1.0.1 版本了,我們可以有多種方式獲取這個版本依賴,go1.11 中,go get 擁有了很多新特性,我們可以直接通過以下命令獲取 v1.01 版本依賴:

$ go get github.com/objcoding/[email protected]

也可以通過 go mod 命令:

$ go mod edit -require="github.com/objcoding/[email protected]"

$ go mod tidy

go mod edit -require 可以主動修改 go.md 文件中依賴的版本號,然後通過 go mod tidy 對版本進行更新,這是一條神一樣的命令,它會自動清理掉不需要的依賴項,同時可以將依賴項更新到當前版本。

主要版本升級

上面版本規則說了,版本 0 和 1,即大版本更新,最好需要有不同的依賴路徑,如:v1.0.0 和 v2.0.0 是有不同的依賴路徑,那麼用 go modules 怎麼實現呢,我們可以通過修改 go.mod 文件第一行添加新路徑:

$ cd testmod
$ echo 'module github.com/objcoding/testmod/v2' >> go.mod

然後我們修改 testmod 函數 Hi():

$ cd testmod

$ echo 'package testmod
import (
    "fmt"
)
func SayHello(name, str string) string {
    return fmt.Sprintf("你好, %s, %s", name, str)
}' >> testmod.go

這時,SayHello() 方法將不兼容 v1 版本,我們需要新建一個 v2.0.0 版本,還是老樣子,我們最好在 v2.0.0 版本新建一條 v2 分分支,將 v2.0.0 版本的代碼寫到這條分支中(這只是一個規範,實際上你將代碼也寫到任何分支中都行,Go並沒有這個規範):

$ git add *
$ git checkout -b v2
$ git commit testmod.go -m "v2.0.0"
$ git tag v2.0.0
$ git push --tags origin v2

這時候 testmod 的版本已經更新到 v2.0.0 版本了,該版本並不兼容以前的版本,但是目前項目依然只能獲取到 v1.0.1 的依賴版本,因爲我們 testmod 的 module 路徑已經加上 v2 了,因此並不會出現衝突的問題,那麼如果我們需要使用 v2.0.0 版本呢?我們只需要在項目中更改一下 import 路徑:

package main

import (
    "fmt"
      "github.com/objcoding/testmod/v2"
)
func main() {
    fmt.Println(testmod.SayHello("張乘輝", "最近過得怎樣"))
}

執行:

go mod tidy

這時我們把 testmod 依賴版本號更新到了 v2.0.0 版本了,雖然是此時的 import 路徑是以 “v2” 結尾,但是 Go 很人性化,我們依然可以使用 testmod 來使用。

公衆號「後端進階」,專注後端技術分享!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章