Go module
Go module 是 Go1.11 版本之后推出的模块管理工具,从1.13开始为默认的依赖管理工具。在没有 Go module 之前,对于导入依赖的管理都是通过 GOPATH 来指定在工程中使用哪些源文件和模块。项目的代码只能放到 GOPATH/src
目录下,依赖的各种源文件也都只能加入到 src
目录下才可以运行使用。
Go module 相关属性
GO111MODULE
要想使用 go module,首先需要设置环境变量 GO111MODULE
,通过它可以开启或关闭模块支持。它有三个可选的值:off
,on
,auto
。最新版本(1.16)中默认值是 on
。
GO111MODULE=off
:禁用模块支持,项目编译时会从GOPATH
和vendor
文件夹中查找包。GO111MODULE=on
:开启模块支持,项目编译时会忽略GOPATH
和vendor
文件夹,只根据go.mod
下载所需要的依赖,同时该依赖会被下载到$GOPATH/pkg/mod
目录下。GO111MODULE=auto
:当项目在$GOPATH/src
外且项目的根目录中有go.mod
文件时,使用模块支持模式,src
目录下禁用模块支持。
使用 go module 管理依赖后会在项目根目录下生成两个文件 go.mod
和 go.sum
。
go.mod
go.mod
描述了当前项目(也就是当前模块)的元信息,文件的内容如下:
module example.com/foobar
go 1.13
require (
example.com/apple v0.1.2
example.com/banana v1.2.3
example.com/banana/v2 v2.3.4
example.com/pineapple v0.0.0-20190924185754-1b0db40df49a
)
exclude example.com/banana v1.2.4
replace example.com/apple v0.1.2 => example.com/rda v0.1.0
replace example.com/banana => example.com/hugebanana
文件中有以下几个关键词:
- module:用于定义当前项目的模块路径
- go:用于设置预期的 Go 版本
- require:用于添加依赖,设置一个特定的模块版本
- exclude:用于从使用中排除一个特定的模块版本
- replace:用于将一个模块版本替换成另外一个模块版本
go.sum
go.sum
详细罗列了当前项目直接或者间接依赖的所有模块版本,并写明了这些模块版本的SHA-256 哈希值,用于在今后的操作中保证项目所依赖的那些模块版本不会被篡改。
zexample.com/apple v0.1.2 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
example.com/apple v0.1.2/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= example.com/banana v1.2.3 h1:qHgHjyoNFV7jgucU8QZUuU4gcdhfs8QW1kw68OD2Lag=
example.com/banana v1.2.3/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= example.com/banana/v2 v2.3.4 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= example.com/banana/v2 v2.3.4/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
...
从上面的例子中可以看到,一个模块路径下可能有两种哈希值:
example.com/apple v0.1.2 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
example.com/apple v0.1.2/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
第一种为 Go modules 打包整个模块包文件zip后再进行hash,后者为针对 go.mod 的 hash 值。两者要么同时存在,要么就只存在 go.mod hash
,此时 Go 认为肯定用不到某个模块版本,会自动忽略它的 zip hash。
GOPROXY
GOPROXY
环境变量用于设置 Go 模块代理,它是以一个英文逗号,
分割的 Go module proxy 列表。其作用是使 Go 在后续拉取版本模块时能够脱离传统的 VCS(版本控制系统)方式,从镜像站点快速拉取,默认值为 https://proxy.golang.org,direct
,但是该站点在中国无法访问,可以使用 goproxy.cn
替代:
go env -w GOPROXY=https://goproxy.cn,direct
direct
是一个特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取实现代码(如GitHUb),当值列表中上一个 Go module proxy 返回 404 或者 410 错误的时候,Go 自动尝试列表中的下一个,遇到 direct
时回源,遇到 EOF
时终止并抛出类似 “invalid version: unknown revision…” 的错误。
当 GOPROXY
的值为 off
时,禁止 Go 在后续操作中使用任何 go module proxy
。
GOSUMDB
GOSUMDB
的值是一个 Go checksum database,用于使 Go 在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经篡改,也可以是 off
即禁止 Go 在后续操作中校验模块版本。
- 格式 1:
<SUMDB_NAME>+<PUBLIC_KEY>
。 - 格式 2:
<SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>
。 - 拥有默认值:
sum.golang.org
(之所以没有按照上面的格式是因为 Go 对默认值做了特殊处理)。 - 可被 Go module proxy 代理。
sum.golang.org
在中国无法访问,可以将 GOPROXY 设置为goproxy.cn
,因为goproxy.cn
支持代理sum.golang.org
。
Go Checksum Database
Go checksum database 主要用于保护 Go 不会从任何源头(包括 Go module proxy)拉到被篡改过的非法 Go 模块版本,其工作机制如图所示:
GONOPROXY/GONOSUMDB/GOPRIVATE
GONOPROXY
, GONOSUMDB
, GOPRIVATE
这三个环境变量都是用在当前项目依赖了私有模块的场景,也就是依赖了由 GOPROXY
指定的 Go module proxy
或由 GOSUMDB
指定 Go checksum database
无法访问到的模块时的场景。
- 它们三个的值都是一个以英文逗号
,
分割的模块路径前缀,匹配规则同path.Match
。 - 其中
GOPRIVATE
较为特殊,它的值将作为GONOPROXY
和GONOSUMDB
的默认值,所以最佳实践是只是用GOPRIVATE
。
在使用上来讲,比如 GOPRIVATE=*.corp.example.com
表示所有模块路径以 corp.example.com
的下一级域名 (如 team1.corp.example.com
) 为前缀的模块版本都将不经过 Go module proxy
和 Go checksum database
,需要注意的是不包括 corp.example.com
本身。
Global Caching
这个主要是针对 Go modules 的全局缓存数据说明,如下:
- 同一个模块版本的数据只缓存一份,所有其他模块共享使用。
- 目前所有模块版本数据均缓存在
$GOPATH/pkg/mod
和$GOPATH/pkg/sum
下,未来或将移至$GOCACHE/mod
和$GOCACHE/sum
下( 可能会在当$GOPATH
被淘汰后)。 - 可以使用
go clean -modcache
清理所有已缓存的模块版本数据。
另外在 Go1.11 之后 GOCACHE 已经不允许设置为 off 了,可能这也是为了模块数据缓存移动位置做准备。
Go module 相关命令
go mod
常用的 go mod
命令如下:
go mod init:初始化当前文件夹,创建 go.mod
文件。
go mod tidy:整理现有的依赖,它会增加缺少的 module,删除无用的 module,-v
参数可以显示执行的信息
go mod vendor:将依赖复制到 vendor 下
go mod download:下载依赖的 module 到本地
go mod verify:校验模块是否被篡改过
go mod edit:编辑 go.mod
文件,-fmt
参数用于格式化当前文件;-require=package path
参数用于添加依赖项
go mod why:解释为什么需要依赖
go mod graph:打印模块依赖图
go get
go get
命令用于拉取依赖,常用示例如下:
go get package path@latest:拉取最新的版本(优先择取 tag)
go get package path@master:拉取 master
分支的最新 commit
go get package path@v0.3.2:拉取 tag 为 v0.3.2 的 commit
go get package path@342b2e:拉取 hash 为 342b231 的 commit,最终会被转换为 v0.3.2,这是因为虽然设置了拉取 @342b2e commit,但是因为 Go modules 会与 tag 进行对比,若发现对应的 commit 与 tag 有关联,则进行转换
go get -u:更新现有的主要模块依赖(忽略单元测试),该指令会将模块升级到最新的次要版本或者修订版本(x.y.z,z是修订版本号,y是次要版本号)
go get -u=patch:升级到最新的修订版本
**go get -u ./..**:递归更新所有子目录的所有模块,忽略单元测试
go get -u -t:同样只会更新主要模块,但是考虑了单元测试
**go get -u -t ./..**:同样是递归更新所有子目录的所有模块,但是考虑了单元测试
go get -u all:更新所有模块,推荐使用
将项目迁移至 Go Modules
修改 GOBIN 路径(可选):
go env -w GOBIN=$HOME/bin
。打开 Go modules:
go env -w GO111MODULE=on
。设置 GOPROXY:
go env -w GOPROXY=https://goproxy.cn,direct
(可选,对于无法访问外网的同学来说是必须的,因为它的默认值被墙了)。在项目的根目录下执行
go mod init <OPTIONAL_MODULE_PATH>
以生成 go.mod 文件。执行
go get
Go module 常见问题
管理 Go 的环境变量
在 Go 1.13 中管理环境变量会变得有些混乱,因此 Go 1.13建议将所有与 Go 相关的环境变量都交由新推出的 go env -w
来管理,比如:go env -w GO111MODULE=on
就会在 $HOME/.config/go/env
文件中追加一行“GO111MODULE=on”。
但是 go env -w
不会覆盖系统环境变量,所以建议在Go 1.13之后删除系统中所有跟 Go 相关的环境变量,改有 go env -w
进行管理
拉取私有模块
常见的公共 Go module proxy,例如 proxy.golang.org
和 goproxy.cn
都是无权访问任何人的私有模块的,即使使用 direct
也无法抓去私有模块,解决办法是修改VCS的拉取行为,例如使用 Git 时就在 $HOME/.gitconfig
文件中追加:
[url "ssh://git@github.com/"]
insteadOf = "https://github.com"
另外需要在 GOPROXY
加上 direct
,这样才能在 Go module proxy 拉取不到时回源到模块所在地拉取。
这里主要想涉及两块知识点,如下:
GOPROXY
是无权访问到任何人的私有模块的,所以你放心,安全性没问题。GOPROXY
除了设置模块代理的地址以外,还需要增加 “direct” 特殊标识才可以成功拉取私有库。
主版本号
除了 v0 和 v1外,主版本号必须显示地出现在模块路径的尾部。
Go 会将 example.com/foo/bar
和 example.com/foo/bar/v2
视为完全不同的两个模块来对待。
一个项目中可能同时存在多个拥有不同主版本号的相同模块,但是互不影响。
go get -u
不会更新主版本号,如果需要更新,需要手动修改代码中对应的导入路径。
另外,使用 Go modules 时,不可以同时依赖同一个模块的不同的两个或者多个小版本(修订版本号不同)。Go modules 只可以同时依赖一个模块的不同的两个或者多个大版本(主版本号不同)。比如可以同时依赖 example.com/foobar@v1.2.3
和 example.com/foobar/v2@v2.3.4
,因为他们的模块路径(module path)不同,Go modules 规定主版本号不是 v0 或者 v1 时,那么主版本号必须显式地出现在模块路径的尾部。但是,同时依赖两个或者多个小版本是不支持的。
比如如果模块 A 同时直接依赖了模块 B 和模块 C,且模块 A 直接依赖的是模块 C 的 v1.0.0 版本,然后模块 B 直接依赖的是模块 C 的 v1.0.1 版本,那么最终 Go modules 会为模块 A 选用模块 C 的 v1.0.1 版本而不是模块 A 的 go.mod 文件中指明的 v1.0.0 版本。这是因为 Go modules 认为只要主版本号不变,那么剩下的都可以直接升级采用最新的。但是如果采用了最新的结果导致项目 Break 掉了,那么 Go modules 就会 Fallback 到上一个老的版本,比如在该例子中就会 Fallback 到 v1.0.0 版本。
参考文章: