Go Modules 詳解

原文連接:Go Modules 詳解node

Go 1.11 和 Go 1.12 包含了初步的 Go Modules 支持,且計劃在 2019 年 8 月發佈的 Go 1.13 會在全部開發過程當中默認使用 Go Modules。git

Go Modules 是爲了提高使用其餘開發者代碼,即添加**依賴項(模塊、包)**時的體驗,也是爲了讓代碼的正確性、安全性獲得保障。而且 Go Modules 可使用 GOPROXY 環境變量來解決中國大陸沒法使用 go get 的問題。github

因此學習跟 Go Modules 有關的知識是頗有必要的。golang

Golang Logo
Golang Logo

模式

Go Modules 在 Go 1.11 及 Go 1.12 中有三個模式,根據環境變量 GO111MODULE 定義:數組

  • 默認模式(未設置該環境變量或 GO111MODULE=auto):Go 命令行工具在同時知足如下兩個條件時使用 Go Modules:
    • 當前目錄不在 GOPATH/src/ 下;
    • 在當前目錄或上層目錄中存在 go.mod 文件。
  • GOPATH 模式(GO111MODULE=off):Go 命令行工具從不使用 Go Modules。相反,它查找 vendor 目錄和 GOPATH 以查找依賴項。
  • Go Modules 模式(GO111MODULE=on):Go 命令行工具只使用 Go Modules,從不諮詢 GOPATH。GOPATH 再也不做爲導入目錄,但它仍然存儲下載的依賴項(GOPATH/pkg/mod/)和已安裝的命令(GOPATH/bin/),只移除了 GOPATH/src/。

Go 1.13 默認使用 Go Modules 模式,因此以上內容在 Go 1.13 發佈並在生產環境中使用後均可以忽略。緩存

核心文件:go.mod

如下就是 go.mod 文件的一個最全面的示例:安全

module my/thing
go 1.12
require other/thing v1.0.2 // 這是註釋
require new/thing/v2 v2.3.4 // indirect
require(
  new/thing v2.3.4
  old/thing v0.0.0-20190603091049-60506f45cf65
)
exclude old/thing v1.2.3
replace bad/thing v1.4.5 => good/thing v1.4.5
複製代碼

很全面,也很複雜。但其實 go.mod 文件在實際項目沒有這麼複雜,並且一旦該文件存在,就不須要額外的步驟:像 go build、go test,甚至 go list 這樣的命令都會根據須要自動添加新的依賴項以知足導入。markdown

但如今咱們仍是來詳細瞭解 go.mod 文件的組成:工具

go.mod 文件是面向行的, 當前模塊(主模塊)一般位於第一行,接下來是根據路徑排序的依賴項。oop

每行包含一個指令,由一個前導動詞後跟參數組成。

全部前導動詞的做用以下:

  • module:定義模塊路徑。
  • go:設置預期的語言版本。
  • require:要求給定版本或更高版本的特定模塊。
  • exclude:排除特定版本模塊的使用,不容許的模塊版本被視爲不可用,而且查詢沒法返回。
  • replace:使用不一樣的模塊版本替換原有模塊版本。

前導動詞還能夠按的方式使用,用括號建立一個塊(第 5-8 行),就像在 Go 語言中的導入同樣:

import (
    "errors"
    "fmt"
    "log"
)
複製代碼

註釋(第 3-4 行)可使用單行 // 這是註釋 註釋,但不能使用多行 /* 這是註釋 */ 註釋。而 indirect 註釋(第 4 行)標記了該模塊不是被當前模塊直接導入的,只是被間接導入。

go.mod 文件只存在於在模塊的根目錄下,子目錄中的導入路徑會使用模塊的導入路徑 + 子目錄路徑的形式。例如:若是建立了一個名叫 world 的子目錄,並不須要在子目錄中使用 go mod init 命令,Go 命令行工具會自動識別它做爲 hello 模塊的一部分,因此它的導入路徑爲 hello/world。

Go 命令行工具會自動處理 go.mod 中指定的模塊版本。當源代碼中 import 指向的模塊不存在於 go.mod 文件中時,Go 命令行工具會自動搜索這個模塊,並將最新版本(最後一個 tag 且非預發佈的穩定版本)添加到 go.mod 文件中。

若是沒有 tag,則使用僞版本(第 7 行),這是一種版本語法,專門用於標記沒有 tag 的提交(一些 golang.org/x/ 下的包就是沒有 tag 的)。如:v0.0.0-20190603091049-60506f45cf65

前面部分爲語義化版本號,用於標記版本;中間部分爲 UTC 的提交時間,用於比較兩個僞版本以其肯定前後順序;後面部分是 commit 哈希的前綴,用於標記該版本位於哪一個 commit。

版本管理文件:go.sum

示例以下:

github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
複製代碼

每行由模塊導入路徑、模塊的特定版本和預期哈希組成。

在每次缺乏模塊時,若是緩存中不存在,則須要下載並計算其哈希添加到 go.sum 中;若是緩存中存在,則須要匹配 go.sum 中的已有條目。

這樣,構建軟件的用戶就可使用哈希驗證其構建是否跟你的構建相同(go mod verify),而不管他們怎樣獲取依賴項,均可以獲得相同的版本。同時也保證了項目依賴不會發生預料以外的惡意修改和其餘問題。這也是爲何要將 go.sum 文件加入版本管理(Git)的緣由。

再加上 Go Modules 選擇的是最小版本選擇策略(默認使用構建中涉及的每一個模塊的最舊容許版本,使得新版本的發佈對構建沒有影響)就能夠實現可重現的構建(在重複構建時產生相同的結果)。

語義化版本

什麼是語義化版本?語義化版本是一套由 Gravatars 創辦者兼 GitHub 共同創辦者 Tom Preston-Werner 所創建的約定。在這套約定下,語義化版本號及其更新方式包含了不少有用的信息。

語義化版本號格式爲:X.Y.Z(主版本號.次版本號.修訂號),使用方法以下:

  • 進行不向下兼容的修改時,遞增主版本號。
  • API 保持向下兼容的新增及修改時,遞增次版本號。
  • 修復問題但不影響 API 時,遞增修訂號。

舉個例子,有一個語義化版本號爲:v0.1.2,則其主版本號爲 0,次版本爲 1,修訂號爲 2。而前面的 v 是 version(版本)的首字母,是 Go 語言慣例使用的,標準的語義化版本沒有這個約定。

因此在使用 Go 命令行工具或 go.mod 文件時,就可使用語義化版本號來進行模塊查詢,具體規則以下:

  • 默認值(@latest):將匹配最新的可用標籤版本或源碼庫的最新未標籤版本。
  • 徹底指定版本(@v1.2.3):將匹配該指定版本。
  • 版本前綴(@v1@v1.2):將匹配具備該前綴的最新可用標籤版本。
  • 版本比較(@<v1.2.3@>=v1.5.6):將匹配最接近比較目標的可用標籤版本。< 則爲小於該版本的最新版本,> 則爲大於該版本的最舊版本。當使用類 Unix 系統時,需用引號將字符串包裹起來以防止大於小於號被解釋爲重定向。如:go get 'github.com/gin-gonic/gin@<v1.2.3'
  • 指定某個 commit(@c856192):將匹配該 commit 時的版本。
  • 指定某個分支(@master):將匹配該分支版本。

語義化導入版本
語義化導入版本

如上圖所示,爲了能讓 Go Modules 的使用者可以從舊版本更方便地升級至新版本,Go 語言官方提出了兩個重要的規則:

  • 導入兼容性規則(import compatibility rule):若是舊包和新包具備相同的導入路徑,則新包必須向後兼容舊包。
  • 語義化導入版本規則(semantic import versioning rule):每一個不一樣主版本(即不兼容的包 v1v2)使用不一樣的導入路徑,以主版本結尾,且每一個主版本中最多一個。如:一個 rsc.io/quote、一個 rsc.io/quote/v2、一個 rsc.io/quote/v3

而與 Git 分支的集成以下:

Go Modules 分支
Go Modules 分支

vendor 目錄

之前使用 vendor 目錄有兩個目的:

  • 可使用依賴項的確切版本用來構建。
  • 即便原始副本消失,也能保證這些依賴項是可用的。

而模塊如今有了更好的機制來實現這兩個目的:

  • 經過在 go.mod 文件中指定依賴項的確切版本。
  • 可用性則由緩存代理($GOPROXY)實現。

並且 vendor 目錄也很難管理這些依賴項,長此以往就會陷入與 node_modules 黑洞同樣的窘境。

node_modules 黑洞
node_modules 黑洞

因此,默認狀況下使用 Go Modules 將徹底忽略 vendor 的依賴項,可是爲了平穩過分,可使用 go mod vendor 命令能夠建立 vendor 目錄。

並在 Go 命令行工具使用 -mod=vendor 參數,如:go test -mod=vendor ./...;或設置環境變量 GOFLAGS 爲 -mod=vendor,這樣會假定 vendor 目錄包含正確的依賴項副本,並忽略 go.mod 文件中的依賴項描述來構建。

環境變量 GOPROXY

設置環境變量 GOPROXY 能夠解決中國大陸沒法使用 go get 的問題:

export GOPROXY=https://goproxy.io 寫入 Shell 的配置文件便可。

經常使用命令

  • go mod init:建立一個新模塊,初始化 go.mod 文件,參數爲該模塊的導入路徑,推薦使用這種形式。如:go mod init github.com/linehk/example
  • go get:更改依賴項版本(或添加新的依賴項)。
  • go buildgo test 等命令:Go 命令行工具會根據須要添加新的依賴項。如:go test ./...,測試當前模塊。
  • go list -m all:打印當前模塊依賴。
  • go mod tidy:移除無用依賴。
  • go list -m -versions github.com/gin-gonic/gin:列出該模塊的全部版本。
  • go mod verify:驗證哈希。

與 GoLand 集成

在 GoLand 2019.1.3 中使用 Go Modules 須要進行兩個設置:

  1. Preferences -> Go -> Go Modules (vgo),勾選 Enable Go Modules (vgo) integration 以啓用 Go Modules,並在 Proxy 輸入框中輸入 https://goproxy.io。如圖所示:
    在 GoLand 中使用 Go Modules 設置 1
    在 GoLand 中使用 Go Modules 設置 1
  2. Preferences -> Go -> GOPATH,勾選上 Index entire GOPATH 以索引整個 GOPATH,否則沒法導入包。如圖所示:
    在 GoLand 中使用 Go Modules 設置 2
    在 GoLand 中使用 Go Modules 設置 2

進行如上設置後,就能夠在導入不在緩存中的包時,點擊 Sync packages of... 下載該包了:

下載包
下載包

參考連接

Command go

Go & Versioning

語義化版本 2.0.0

相關文章
相關標籤/搜索