Go 1.11 和 Go 1.12 包含了初步的 Go Modules 支持,且计划在 2019 年 8 月发布的 Go 1.13 会在所有开发过程中默认使用 Go Modules。
Go Modules 是为了提升使用其他开发者代码,即添加 依赖项(模块、包) 时的体验,也是为了让代码的正确性、安全性得到保障。并且 Go Modules 可以使用 GOPROXY 环境变量来解决中国大陆无法使用 go get 的问题。
所以学习跟 Go Modules 有关的知识是很有必要的。
模式
Go Modules 在 Go 1.11 及 Go 1.12 中有三个模式,根据环境变量 GO111MODULE 定义:
GO111MODULE=autoGO111MODULE=offGO111MODULE=onGo 1.13 默认使用 Go Modules 模式,所以以上内容在 Go 1.13 发布并在生产环境中使用后都可以忽略。
核心文件:go.mod
以下就是 go.mod 文件的一个最全面的示例:
1
2
3
4
5
6
7
8
9
10
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 这样的命令都会根据需要自动添加新的依赖项以满足导入。
但现在我们还是来详细了解 go.mod 文件的组成:
go.mod 文件是面向行的, 当前模块(主模块)通常位于第一行,接下来是根据路径排序的依赖项。
每行包含一个指令,由一个前导动词后跟参数组成。
所有前导动词的作用如下:
modulegorequireexcludereplace前导动词还可以按块的方式使用,用括号创建一个块(第 5-8 行),就像在 Go 语言中的导入一样:
1
2
3
4
5
import (
"errors"
"fmt"
"log"
)
// 这是注释/* 这是注释 */indirectgo mod initimportv0.0.0-20190603091049-60506f45cf65前面部分为语义化版本号,用于标记版本;中间部分为 UTC 的提交时间,用于比较两个伪版本以其确定先后顺序;后面部分是 commit 哈希的前缀,用于标记该版本位于哪个 commit。
版本管理文件:go.sum
示例如下:
1
2
3
4
5
6
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 Modules 选择的是最小版本选择策略(默认使用构建中涉及的每个模块的最旧允许版本,使得新版本的发布对构建没有影响)就可以实现可重现的构建(在重复构建时产生相同的结果)。
语义化版本
什么是语义化版本?语义化版本是一套由 Gravatars 创办者兼 GitHub 共同创办者 Tom Preston-Werner 所建立的约定。在这套约定下,语义化版本号及其更新方式包含了很多有用的信息。
X.Y.Z- 进行不向下兼容的修改时,递增主版本号。
- API 保持向下兼容的新增及修改时,递增次版本号。
- 修复问题但不影响 API 时,递增修订号。
v0.1.2v所以在使用 Go 命令行工具或 go.mod 文件时,就可以使用语义化版本号来进行模块查询,具体规则如下:
@latest@v1.2.3@v1@v1.2@=v1.5.6<>go get 'github.com/gin-gonic/gin@ 如上图所示,为了能让 Go Modules 的使用者能够从旧版本更方便地升级至新版本,Go 语言官方提出了两个重要的规则:
v1v2rsc.io/quotersc.io/quote/v2rsc.io/quote/v3而与 Git 分支的集成如下:
vendor 目录
以前使用 vendor 目录有两个目的:
- 可以使用依赖项的确切版本用来构建。
- 即使原始副本消失,也能保证这些依赖项是可用的。
而模块现在有了更好的机制来实现这两个目的:
$GOPROXY而且 vendor 目录也很难管理这些依赖项,久而久之就会陷入与 node_modules 黑洞一样的窘境。
go mod vendor-mod=vendorgo test -mod=vendor ./...-mod=vendor环境变量 GOPROXY
设置环境变量 GOPROXY 可以解决中国大陆无法使用 go get 的问题:
export GOPROXY=https://goproxy.io常用命令
go mod initgo mod init github.com/linehk/examplego getgo buildgo testgo test ./...go list -m allgo mod tidygo list -m -versions github.com/gin-gonic/gingo mod verify与 GoLand 集成
在 GoLand 2019.1.3 中使用 Go Modules 需要进行两个设置:
- Preferences -> Go -> Go Modules (vgo),勾选 Enable Go Modules (vgo) integration 以启用 Go Modules,并在 Proxy 输入框中输入 https://goproxy.io。如图所示:
- Preferences -> Go -> GOPATH,勾选上 Index entire GOPATH 以索引整个 GOPATH,不然无法导入包。如图所示:
进行如上设置后,就可以在导入不在缓存中的包时,点击 Sync packages of… 下载该包了: