1. Go Modules介绍
Go Modules是Go管理包的依赖的工具,自Go 1.11加入,以解决Go的依赖管理问题,淘汰了旧的GOPATH模式。
旧的GOPATH模式将代码存放在GOPATH/src目录下,通过go get来下载外部依赖包。但是GOPATH模式没有版本控制的概念,无法确保下载的依赖包是期望的版本。因此引入了Go Modules解决这些问题。
2. 使用Go Modules
首先要确保Go升级到了1.11或以上版本。
然后是设置GO111MODULE,GO111MODULE有三个值:off、on和auto(默认值)。
- GO111MODULE=off:不支持modules功能,通过GOPATH来查找依赖包。
- GO111MODULE=on:使用modules,完全不会通过GOPATH来查找依赖包。
- GO111MODULE=auto:根据当前目录决定是否启用modules,当前目录在GOPATH/src之外,且包含go.mod文件或在包含go.mod文件的目录下面,就会启用modules。
设置GO111MODULE:
$ go env -w GO111MODULE=on # 设置GO111MODULE
当启用modules功能时,依赖包存放位置在GOPATH/pkg/mod,并且允许同一个package的多个版本并存,供项目指定引用。
3. go mod
go mod带有多个命令,且会在项目中生成和维护go.mod、go.sum两个文件。
3.1 go.mod
Go提供了go mod命令来管理包。
新建目录并初始化生成go.mod文件:
$ mkdir hehe
$ cd hehe
$ go mod init hehe # 初始化go.mod文件
新建go.mod后,当其他源文件引用了任何包时,go get、go build、go mod等命令都会更新go.mod文件,并会自动下载用到的依赖包。
生成的go.mod内容示例如下:
module hehe
go 1.15
require (
github.com/labstack/echo v3.3.10+incompatible // indirect
github.com/mattn/go-colorable v0.1.1
github.com/mattn/go-isatty v0.0.7
)
exclude (
go.etcd.io/etcd/client/v2 v2.305.0-rc.0
go.etcd.io/etcd/client/v3 v3.5.0-rc.0
)
replace github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.3
go.mod中可以包含以下几个语句:
- module:定义当前项目的模块路径。
- go:当前模块的Go语言版本,目前只是标识作用。
- require:指定依赖的包以及特定版本。
- exclude:指定忽略某个依赖包的某版本,Go在版本选择时就会主动跳过这些版本。
- replace:替换依赖包,用来解决错误的包引用,或是用来调试以将依赖包替换成本地的版本。
- retract:是Go 1.16新增的,在某个包的自身中定义,用于声明本包前面某个版本被撤回,提示大家不要用了。
3.1.1 require
require中的包后面带有版本号,就可以指定Go下载使用对应的包版本,若没有指定版本号则使用最新的版本。
出现在源文件import中的包是直接依赖的包,而间接依赖的包则会加上indirect注释,且不是所有间接依赖都会出现在go.mod文件中。当module A引用的包B再引用别的包C时,如果B包还不支持Go Modules或go.mod没有带有包C,则会在module A的go.mod文件中以indirect注释引用包C。出现间接依赖可能意味着正在使用过时的包,推荐尽快消除间接依赖,使用依赖的新版本或替换间接依赖。
3.1.2 replace
当项目代码依赖一个第三方包,而又想做本地的修改和调试时,可以将这个包下载到本地,然后在go.mod中通过replace来使用它。
首先将依赖的第三方包项目下载到本地。
$ cd /User/moondo/git
$ git clone github.com/coreos/bbolt
然后在go.mod中已经require这个包之后,通过replace将依赖的包从使用GOPATH下的本地缓存目录,改为使用指定目录下的项目。这里指向的路径要用绝对路径。
require (
replace github.com/coreos/bbolt
)
replace github.com/coreos/bbolt => /User/moondo/git/bbolt
然后我们的项目引用这个包的时候,实际上就会引用replace后的路径下的包。我们可以修改它的代码,来测试和观察程序执行的结果了。
3.2 go.sum
Go还会生成一个go.sum来记录依赖信息,其中的每一行格式如下:
<module> <version>[/go.mod] <hash>
module是依赖的路径,version是版本号,hash是h1:开头的字符串。具体内容示例:
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
每个依赖包版本会包含两条记录,第一条是该依赖包整体的哈希值,第二条是依赖包中go.mod文件的哈希值,如果该依赖包没有go.mod则没有第二条记录。go.sum文件记录的依赖包往往比go.mod文件要多,因为go.mod只需要记录直接依赖的包,而go.sum要记录构建用到的所有包。
Go会将依赖包下载到本地缓存目录GOPATH/pkg/mod/cache/download,并计算哈希值,写入go.sum记录。构建项目时会对本地缓存中go.mod记录的依赖包计算哈希值,若与go.sum中的记录不一致,则表示依赖包不是期望的版本,构建失败。
3.3 go mod命令
3.3.1 init
初始化创建go.mod文件。
$ go mod init hehe # 指定模块路径
$ go mod init # 根据.go文件中import的来确认模块路径
3.3.2 download
下载需要用到的依赖包到缓存目录。
$ go mod download
$ go mod download golang.org/x/mod@v0.2.0 # 下载指定包
3.3.3 tidy
检查当前项目代码,加入缺少的包,并移除不需要的包。
$ go mod tidy
3.3.4 edit
修改go.mod文件的内容。
$ go mod edit --require=rsc.io/quote@v3.1.0 # 修改require
$ go mod edit --droprequire=golang.org/x/crypto # 移除require
$ go mod edit -fmt # 格式化go.mod
3.3.5 why
显示包含依赖包的import路径。
$ go mod why golang.org/x/text/language
3.3.6 verify
检查依赖是否正确。
$ go mod verify
3.3.7 vendor
在当前项目新建目录vendor,并将所有依赖包复制到vendor目录里。
$ go mod vendor
3.3.8 graph
打印模块依赖图。
$ go mod graph