Go的包管理工具(三):Go Modules

在前面的文章,咱們先是介紹了Go 的幾種包管理方式,而後具體介紹了一種包管理的工具: glide。隨着 Go 1.11 的發佈,官方的包管理工具 Go Modules 變得流行起來。在發佈不久的 Go 1.12 版本中,加強了對 Go Modules 的支持。本文將會介紹如何在項目中安裝和使用 Go Moduleshtml

安裝和激活 Modules 的支持

前置條件

如本文開頭所說,從 Go 1.11 版本才支持 Go Modules。因此,默認 Go 的版本爲 >= 1.11。git

$ go version
go version go1.12 darwin/amd64
複製代碼

筆者安裝了最新的 1.12 版本。github

激活使用

安裝後,咱們能夠經過如下兩種方式之一激活模塊支持:golang

  • $GOPATH/src 以外的目錄中調用 go 命令,且當前目錄或其任何父目錄中使用有效的 go.mod 文件,而且環境變量 GO111MODULE 未設置(或顯式設置爲auto)。
  • 在環境變量集上設置 GO111MODULE = on 後,調用go命令。

如何定義模塊

爲當前的項目建立一個 go.mod 文件。緩存

當項目不在 GOPATH 中,直接執行:bash

go mod init
複製代碼

不然,會出現以下的錯誤:微信

go: modules disabled inside GOPATH/src by GO111MODULE=auto; see 'go help modules'
複製代碼

所以,咱們須要手動激活 Modules:ide

$ export GO111MODULE=on 
複製代碼

而後才能執行 go mod init。這會將任何現有的dep Gopkg.lock 文件或其餘九種支持的依賴關係轉換,添加 require 語句以匹配現有配置。工具

go mod init一般可以使用輔助數據(例如VCS元數據)來自動肯定相應的模塊路徑,可是若是 go mod init 代表它不能自動肯定模塊路徑,或者若是你須要以其餘方式覆蓋 path,你能夠提供模塊路徑做爲 go mod init 的可選參數,例如:測試

$ go mod init modtest
複製代碼

構建模塊

從模塊的根目錄執行時,./... 模式匹配當前模塊中的全部包。 go build 將根據須要自動添加缺失或未轉換的依賴項,以知足此特定構建調用的導入:

$ go build ./...
複製代碼

測試模塊

$ go test ./...
複製代碼

按配置測試模塊,以確保它適用於所選版本。還能夠運行模塊的測試以及全部直接和間接依賴項的測試以檢查不兼容性:

$ go test all
複製代碼

實戰

建立項目

建立項目並進入根目錄:

$ mkdir src/hello
$ cd src/hello
複製代碼

初始化

$ go mod init github.com/keets2012/hello
go: creating new go.mod: module github.com/keets2012/hello
複製代碼

go mod 初始化,並命名包名爲 github.com/keets2012/hello。能夠看到,一塊兒建立了 go.mod 文件。

實現一個簡單的方法

$ cat <<EOF > hello.go
package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
}
EOF
複製代碼

咱們建立了一個 hello.go 的文件,並輸出調用的方法結果。

構建執行

$ go build   # 構建可執行文件
$ ./hello  # 執行

Hello, world.  # 輸出結果
複製代碼

執行構建以後,獲得可執行文件,咱們執行了獲得結果。 go.mod 文件已更新爲包含依賴項的顯式版本,其中 v1.5.2semver 標記:

$ cat go.mod

module github.com/keets2012/hello

require rsc.io/quote v1.5.2
複製代碼

升級和降級依賴

應該使用 go get 來完成平常升級和降級依賴項,這將自動更新 go.mod 文件。 或者能夠直接編輯 go.mod

此外,像 go buildgo test 或甚至 go list 這樣的命令會根據須要自動添加新的依賴項以知足導入。

要查看全部直接和間接依賴項的可用 minor 和 patch 程序升級:

go list -u -m all
複製代碼

要升級到當前模塊的全部直接和間接依賴關係的最新版本:

  • 運行 go get -u 以使用最新的次要版本或補丁版本
  • go -u = patch使用最新的補丁版本

要升級或降級到更具體的版本,go get 容許經過在 package 參數中添加@version 後綴或「模塊查詢」來覆蓋版本選擇,例如 go get foo@v1.6.2go get foo @ e3702bed2,或者 go foo @'<v1.6.2'

semver

在上一小節,咱們提到了 semver。golang 官方推薦的最佳實踐叫作 semver,這是一個簡稱,寫全了就是Semantic Versioning,也就是語義化版本。

語義化的定義

通俗地說,就是一種清晰可讀的,明確反應版本信息的版本格式,更具體的規範在這裏。

如規範所言,形如 vX.Y.Z 的形式顯然比一串 hash 更直觀,因此 golang 的開發者纔會把目光集中於此。

爲什麼使用語義化版本

semver 簡化版本指定的做用是顯而易見的,然而僅此一條理由顯然有點缺少說服力,經過semver對版本進行嚴格的約束,能夠最大程度地保證向後兼容以及避免 「breaking changes」,而這些都是 golang 所追求的。二者一拍即合,因此 go modules 提供了語義化版本的支持。

若是你使用和發佈的包沒有版本 tag 或者處於 1.x 版本,那麼你可能體會不到什麼區別,由於 go mod 所支持的格式從始至終是遵循 semver 的,主要的區別體如今 v2.0.0 以及更高版本的包上。

「若是舊軟件包和新軟件包具備相同的導入路徑,則新軟件包必須向後兼容舊軟件包。」 - go modules wiki

相同名字的對象應該向後兼容,然而按照語義化版本的約定,當出現 v2.0.0 的時候必定表示發生了重大變化,極可能沒法保證向後兼容,這時候應該如何處理呢?

答案很簡單,咱們爲包的導入路徑的末尾附加版本信息便可,例如:

module my-module/v2

require (
  some/pkg/v2 v2.0.0
  some/pkg/v2/mod1 v2.0.0
  my/pkg/v3 v3.0.1
)
複製代碼

格式總結爲 pkgpath/vN,其中 N 是大於 1 的主要版本號。在代碼裏導入時也須要附帶上這個版本信息,如import "some/pkg/v2"。這樣包的導入路徑發生了變化,也不用擔憂名稱相同的對象須要向後兼容的限制了,由於 golang 認爲不一樣的導入路徑意味着不一樣的包。固然還有意外的狀況:

  • 當使用gopkg.in格式時可使用等價的require gopkg.in/some/pkg.v2 v2.0.0
  • 在版本信息後加上 +incompatible 就能夠不須要指定 /vN ,例如:require some/pkg v2.0.0+incompatible

除此之外的狀況若是直接使用 v2+ 版本將會致使 go mod 報錯。

v2+ 版本的包容許和其餘不一樣大版本的包同時存在(前提是添加了/vN),它們將被當作不一樣的包來處理。

另外 /vN 並不會影響你的倉庫,不須要建立一個v2對應的倉庫,這只是 go modules 添加的一種附加信息而已。

固然若是你不想遵循這一規範或者須要兼容現有代碼,那麼指定 +incompatible 會是一個合理的選擇。不過 go modules 不推薦這種行爲。

使用 vendor 目錄

若是你不喜歡 go mod 的緩存方式,你可使用 go mod vendor 回到 godep 或 govendor 使用的 vendor 目錄進行包管理的方式。

固然這個命令並不能讓你從godep之類的工具遷移到 go modules,它只是單純地把 go.sum 中的全部依賴下載到 vendor 目錄裏,若是你用它遷移 godep 你會發現 vendor 目錄裏的包會和 godep 指定的產生至關大的差別,因此請務必不要這樣作。

使用 go build -mod=vendor 來構建項目,由於在 go modules 模式下 go build 是屏蔽 vendor 機制的,因此須要特定參數從新開啓 vendor 機制:

go build -mod=vendor
./hello
hello world!
複製代碼

構建成功。當發佈時也只須要和使用 godep 同樣將 vendor 目錄帶上便可。

總結

本文主要介紹了 go modules的一些特性和使用方法, go modules 是官方的包管理工具,Go 語言經過引入 module 的概念進而引入了 Go tool 的另一種工做模式 module-aware mode 。在新的工做模式下,module 支持包依賴的版本化管理。

新的工做模式也帶來了一些問題,在大陸地區咱們沒法直接經過 go get 命令獲取到一些第三方包,這其中最多見的就是 golang.org/x 下面的各類優秀的包。一旦工做在模塊下,go build 將再也不關心 GOPATH 或是 vendor 下的包,而是到 GOPATH/pkg/mod 查詢是否有cache,若是沒有,則會去下載某個版本的 module,而對於某些包的 module,在大陸地區每每會失敗。咱們將在下篇文章介紹 go module 的 proxy 配置實現。

推薦閱讀

Go的包管理工具

訂閱最新文章,歡迎關注個人公衆號

微信公衆號

參考

  1. Modules docs
  2. 再探go modules:使用與細節
相關文章
相關標籤/搜索