Go Modules 的使用

Go 語言中一直被人詬病的一個問題就是沒有一個比較好用的依賴管理系統,GOPATH 的設計讓開發者一直有不少怨言,在 Go 語言快速發展的過程當中也出現了一些比較優秀的依賴管理工具,好比 govendor、dep、glide 等,有一些差很少成了半官方的工具了,可是這些工具都仍是須要依賴於 GOPATH,爲了完全解決這個「禍水」,隨着 Go1.11 的發佈,Golang 官方給咱們帶來了依賴管理的全新特性 GoModules,這是 Golang 全新的一套依賴管理系統。下面咱們就來看下 Go Modules 是如何使用的。git

新建 Module

要使用 Go Modules 首先須要保證你環境中 Golang 版本大於1.11:github

$ go version

go version go1.11.4 darwin/amd64

咱們說 Go Modules 主要就是爲了消除 GOPATH 的,因此咱們新建的項目能夠徹底不用放在 $GOPATH/src目錄下面,任何地方均可以:golang

$ echo $GOPATH

/Users/ych/devs/projects/go/

咱們在目錄 /Users/ych/devs/projects下面建立一個用於測試的工做目錄:docker

$ pwd

/Users/ych/devs/projects

$ mkdir stardust && cd stardust

在 stardust 目錄下面建立一個用於字符串操做的 stringsx 的包:json

$ mkdir stringsx && cd stringsx

在包 stringsx 下面建立一個 string.go 的文件:bash

package stringsx

import (

    "fmt"

)

func Hello(name string) string{

    return fmt.Sprintf("Hello, %s", name), nil

}

如今咱們的包裏面的代碼準備完成了,但還不是一個模塊,咱們須要使用 Go Modules 來作一些工做:微信

$ export GO111MODULE=on  # 開啓GoModule特性

而後在項目根目錄下面初始化 Go Module:markdown

$ pwd

/Users/ych/devs/projects/stardust

$ go mod init github.com/cnych/stardust

go: creating new go.mod: module github.com/cnych/stardust

咱們這裏使用了一個命令:go mod init模塊名,該命令會在當前目錄下面生成一個 go.mod的文件,生成的內容就是包含一個模塊名稱的聲明:併發

module github.com/cnych/stardust

要注意 模塊名很是重要,就這至關於聲明瞭咱們的模塊名稱,之後要想使用該模塊就須要使用這個名稱來獲取模塊。ide

這一步操做很是簡單,可是將咱們當前的包變成了一個 Module 了,如今咱們能夠將這個代碼推送到代碼倉庫上,我這裏使用的是 Github,倉庫地址:https://github.com/cnych/stardust

$ git init

$ git add .

$ git commit -am "add stringsx package content"

$ git remote add origin git@github.com:cnych/stardust.git

$ git push -u origin master

到這裏咱們就完成了一個最簡單的 Go Module 的編寫,其餘任何開發者想要使用咱們這個模塊的用戶均可以經過 goget命令來獲取了:

$ go get github.com/cnych/stardust

不過上面的命令是獲取 master 分支的最新代碼,這樣固然沒有問題,但不是一個最佳實踐的方式,由於頗有可能咱們這個模塊會有新的內容更新,或者有一些 BUG 須要修復,若是咱們都放在 master 分支上面的話,勢必會形成使用者的混亂,由於頗有可能使用者的代碼在咱們模塊更新後就不兼容了,不過也不用擔憂,Go Moduels 就能夠很好的來解決這個版本的問題。

Module 版本管理

Go Modules 是須要進行版本化管理的,就相似於咱們平時寫代碼同樣用不一樣的版原本進行區分。對於 Go Modules 的版本強烈推薦使用語義化的版本控制,對於語義化的版本控制咱們查看響應的文檔說明:https://semver.org,最主要的一個版本規則以下

版本格式:主版本號.次版本號.修訂號,版本號遞增規則以下:

  • 主版本號:當你作了不兼容的 API 修改,

  • 次版本號:當你作了向下兼容的功能性新增,

  • 修訂號:當你作了向下兼容的問題修正。
先行版本號及版本編譯元數據能夠加到「主版本號.次版本號.修訂號」的後面,做爲延伸。

咱們在使用 Go Modules 查找版本的時候會使用倉庫中的 tags,而且某些版本和其餘版本有一些不一樣之處,好比 v2 或者更高的版本要和 v1 的版本模塊的導入路徑要不一樣,這樣才能夠經過模塊來區分使用的是不一樣的版本,固然默認狀況下,Golang 會獲取倉庫中最新的 tag 版本。

因此最重要的一點就是要發佈咱們的模塊,咱們須要使用 git tag 來標記咱們的倉庫版本。

發佈第一個版本

假如如今咱們的包已經準備好了,能夠發佈 release 包了,首先咱們就須要給當前的包打上一個 git tag,要記住使用語義化的版本,好比咱們這裏第一個版本就叫:v1.0.0:

$ git tag v1.0.0

$ git push --tags

Go Modules 的使用

這樣咱們就會在 Github 上面建立了一個名爲 v1.0.0 的 tag,可是一個更好的方式是去建立一個名爲 v1的新分支,這樣能夠方便之後修復當前版本代碼中的 BUG,也不會影響到 master 或者其餘分支的一些代碼:

$ git checkout -b v1

$ git push -u origin v1

如今咱們就徹底不用擔憂會影響到咱們以前的版本了。

模塊使用

如今咱們的模塊已經準備好了,如今咱們來建立一個簡單的程序來使用上面的模塊:

$ pwd

/Users/ych/devs/projects

$ mkdir ch26-gomodules && cd ch26-gomodules

而後在 ch26-gomodules 目錄下面建立一個 main.go 的文件:

package main

import (

    "fmt"

    "github.com/cnych/stardust/stringsx"

)

func main() {

    fmt.Println(stringsx.Hello("cnych"))

}

程序裏面使用了 github.com/cnych/stardust/stringsx這個包,在之前的話咱們直接使用 goget命令將這個包拉到 GOPATH 或者 vendor 目錄下面便可,如今咱們是將這個包當成 modules 來使用,首先一樣的在當前目錄下面初始化模塊:

$ go mod init ch26-gomodules

一樣的,該命令會在目錄下面新建一個 go.mod的文件:

module ch26-gomodules

這個時候咱們來直接運行下咱們當前的程序:

$ go run main.go

go: finding github.com/cnych/stardust v1.0.0

go: downloading github.com/cnych/stardust v1.0.0

Hello, cnych

上面的命令會去自動下載程序中導入的包,下載完成後能夠查看下 go.mod文件內容:

module ch26-gomodules

require github.com/cnych/stardust v1.0.0

而且還在當前目錄下面生成了一個名爲 go.sum的新文件,裏面包含了依賴包的一些 hash 信息,能夠用來確保文件和版本的正確性:

github.com/cnych/stardust v1.0.0 h1:8EcmmpIoIxq2VrzXdkwUYTD4OcMnYlZuLgNntZ+DxUE=

github.com/cnych/stardust v1.0.0/go.mod h1:Qgo0xT9MhtGo0zz48gnmbT9XjO/9kuuWKIOIKVqAv28=

模塊會被下載到 $GOPATH/pkg/mod目錄下面去:

$  ls $GOPATH/pkg/mod/github.com/cnych

stardust@v1.0.0

發佈一個 bugfix 版本

好比如今咱們發現以前模塊中的 Hello 函數有 bug,因此咱們須要修復併發佈一個新的版本:

func Hello(name string) string{

    return fmt.Sprintf("Hello, %s!!!", name), nil

}
這裏咱們添加3個 !來模擬 bugfix。

固然要注意咱們是在 v1這個分支上來進行 fix,fix 完成後再 merge 到 master 分支上面去,而後發佈一個新的版本,聽從語義化的版本規則,咱們這裏是修正一個 bug,因此只須要添加修正版本號便可,好比命名爲 v1.0.1 版本:

$ git add .

$ git commit -m "fix Hello function #123"

$ git tag v1.0.1

$ git push --tags origin v1

這樣咱們就發佈了修正版本的模塊了。

更新 modules

默認狀況下,Golang 不會去自動更新模塊的,若是自動更新的話是否是又會形成依賴管理的混亂了,因此咱們須要明確告訴 Golang 咱們須要更新模塊,咱們能夠經過下面幾種方式來進行模塊的更新:

  • 運行 goget-u xxx命令來獲取最新的模塊(好比咱們這裏執行的話就會從v1.0.0更新到v1.0.1版本)

  • 運行 gogetpackage@version命令來更新到指定版本的模塊

  • 直接更新 go.mod文件中的模塊依賴版本,而後執行 go mod tidy命令來進行更新

當咱們這裏用任何一種方式都是能夠的,好比用第二種方式:

$ go get github.com/cnych/stardust@v1.0.1

更新完成後,能夠查看 go.mod文件中的依賴模塊的版本變化:

module ch26-gomodules

require github.com/cnych/stardust v1.0.1

這個時候一樣能夠查看下 mod 文件夾下面的模塊:

$  ls $GOPATH/pkg/mod/github.com/cnych

stardust@v1.0.0 stardust@v1.0.1

看到這裏咱們是否是就明白 Go Modules 是經過怎樣的方式來進行版本控制的了?每一個版本都是獨立的文件夾,這樣是否是就不會出現版本衝突了。

主版本升級

根據語義化版本規則,主版本升級是不向後兼容的,從 Go Modules 的角度來看,主版本是一個徹底不一樣的模塊了,由於咱們認爲兩個大版本之間是互相不兼容的,那麼咱們怎麼設計 Go Modules 來支持兩個大版本呢?好比如今咱們來修改下模塊中的 Hello 函數,增長對語言的一個支持:

func Hello(name, lang string) (string, error) {

    switch lang {

    case "en":

        return fmt.Sprintf("Hi, %s!", name), nil

    case "zh":

        return fmt.Sprintf("你好, %s!", name), nil

    case "fr":

        return fmt.Sprintf("Bonjour, %s!", name), nil

    default:

        return "", fmt.Errorf("unknow language")

    }

}
記住要切換到 master 分支進行修改,由於 v1 分支和咱們如今修改的內容是徹底不一樣的版本了。

這裏咱們的函數須要兩個參數,返回也是兩個參數,直接按照以前 v1 版本中的函數使用確定是會出錯的,因此咱們這裏新版本的模塊就不打算再去兼容以前 1.x 的模塊了,這個時候咱們須要更新版本到 v2.0.0,那麼怎麼去區分這兩個大版本呢?這個時候咱們去更改 v2 版本的模塊路徑就能夠了,好比變成 github.com/cnych/stardust/v2,這樣 v2 版本的模塊和以前 v1 版本的模塊就是兩個徹底不一樣的模塊了,咱們在使用新版本的模塊的時候只須要在模塊名稱後面添加上 v2 就能夠了。

module github.com/cnych/stardust/v2

接下來和前面的操做同樣的,給當前版本添加一個名爲 v2.0.0 的 git tag,固然最好仍是建立一個名爲 v2 的分支,這樣能夠將版本之間的影響降到最低:

$ git add .

$ git commit -m "change Hello function to support lang"

$ git checkout -b v2

$ git tag v2.0.0

$ git push origin v2 --tags

這樣咱們 v2 版本的模塊就發佈成功了,並且以前咱們的程序也不會有任何的影響,由於他仍是能夠繼續使用現有的 v1.0.1 版本,並且使用 goget-u命令也不會拉取最新的 v2.0.0 版本代碼,可是若是對於使用的用戶來講,如今要想使用 v2.0.0 版本的模塊怎麼辦呢?

其實很簡單,只須要單獨引入 v2 版本的模塊便可:

package main

import (

    "fmt"

    "github.com/cnych/stardust/stringsx"

    stringsV2 "github.com/cnych/stardust/v2/stringsx"

)

func main() {

    fmt.Println(stringsx.Hello("cnych"))

    if greet, err := stringsV2.Hello("cnych", "zh"); err != nil {

        fmt.Println(err)

    } else {

        fmt.Println(greet)

    }

}

這個時候咱們去執行 go run main.go命令固然一樣的會去自動拉取 github.com/cnych/stardust/v2這個模塊的代碼了:

$ go run main.go

go: finding github.com/cnych/stardust/v2 v2.0.0

go: downloading github.com/cnych/stardust/v2 v1.0.0

Hi, cnych!!!

你好, cnych!

這樣咱們在同一個 go 文件中就使用了兩個不兼容版本的模塊。一樣這個時候再次查看下 go.mod文件的變化:

module ch26-gomodules

require github.com/cnych/stardust v1.0.1

require github.com/cnych/stardust/v2 v2.0.0

默認狀況下,Golang 是不會從 go.mod文件中刪除依賴項的,若是咱們有不使用的一些依賴項須要清理,可使用 tidy 命令:

$ go mod tidy

該命令會清除沒有使用的模塊,也會更新模塊到指定的最新版本。

Vendor

Go Modules 默認會忽略 vendor/這個目錄,可是若是咱們還想將依賴放入 vendor 目錄的話,咱們能夠執行下面的命令:

$ go mod vendor

該命令會在項目根目錄下面建立一個 vendor/的文件夾,裏面會包含全部的依賴模塊代碼,而且會在該目錄下面添加一個名爲 modules.txt的文件,用來記錄依賴包的一些信息,比較相似於 govendor 中的 vendor.json 文件。

不過建議仍是不要使用該命令,儘可能去忘掉 vendor 的存在,若是有一些依賴包下載不下來的,咱們可使用 GOPROXY這個參數來設置模塊代理,好比:

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

阿里雲也提供了 Go Modules 代理倉庫服務:http://mirrors.aliyun.com/goproxy/,使用很簡單就兩步

若是你想上面的配置始終生效,能夠將這兩條命令添加到 .bashrc中去。

除了使用公有的 Go Modules 代理倉庫服務以外,不少時候咱們在公司內部須要搭建私有的代理服務,特別是在使用 CI/CD 的時候,若是有一個私有代理倉庫服務,會大大的提供應用的構建效率。

咱們可使用Athens來搭建私有的代理倉庫服務,搭建很是簡單,直接用 docker 鏡像運行一個服務便可:

export ATHENS_STORAGE=~/athens-storage

mkdir -p $ATHENS_STORAGE

docker run -d -v $ATHENS_STORAGE:/var/lib/athens \

   -e ATHENS_DISK_STORAGE_ROOT=/var/lib/athens \

   -e ATHENS_STORAGE_TYPE=disk \

   --name goproxy \

   --restart always \

   -p 3000:3000 \

   gomods/athens:latest

其中 ATHENS_STORAGE 是用來存放咱們下載下來的模塊的本地路徑,另外 ATHENS 還支持其餘類型的存儲,好比 內存, AWS S3 或 Minio,都是 OK 的。

而後修改 GOPROXY 配置:

export GOPROXY=http://127.0.0.1:3000

總結

一句話:Go Modules 真的用起來很是爽,特別是消除了 GOPATH,這個東西對於 Golang 初學者來講是很是煩人的,很難理解爲何須要進入到特定目錄下面才能夠編寫 Go 代碼,如今不用擔憂了,直接使用 GoModules就行。

參考資料

全新Golang實戰課程上線,課程地址:https://youdianzhishi.com/m/course/1011(點擊查看原文了解詳細信息

掃描下面的二維碼(或微信搜索 k8s技術圈)關注咱們的微信公衆賬號,在微信公衆賬號中回覆 加羣 便可加入到咱們的 kubernetes 討論羣裏面共同窗習。

Go Modules 的使用

相關文章
相關標籤/搜索