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:禁用模块支持,项目编译时会从 GOPATHvendor 文件夹中查找包。
  • GO111MODULE=on:开启模块支持,项目编译时会忽略 GOPATHvendor 文件夹,只根据 go.mod 下载所需要的依赖,同时该依赖会被下载到 $GOPATH/pkg/mod 目录下。
  • GO111MODULE=auto:当项目在 $GOPATH/src 外且项目的根目录中有 go.mod 文件时,使用模块支持模式,src 目录下禁用模块支持。

使用 go module 管理依赖后会在项目根目录下生成两个文件 go.modgo.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 模块版本,其工作机制如图所示:

Go Checksum Database 工作机制

GONOPROXY/GONOSUMDB/GOPRIVATE

GONOPROXY, GONOSUMDB, GOPRIVATE 这三个环境变量都是用在当前项目依赖了私有模块的场景,也就是依赖了由 GOPROXY 指定的 Go module proxy 或由 GOSUMDB 指定 Go checksum database 无法访问到的模块时的场景。

  • 它们三个的值都是一个以英文逗号 , 分割的模块路径前缀,匹配规则同 path.Match
  • 其中 GOPRIVATE 较为特殊,它的值将作为 GONOPROXYGONOSUMDB 的默认值,所以最佳实践是只是用 GOPRIVATE

在使用上来讲,比如 GOPRIVATE=*.corp.example.com 表示所有模块路径以 corp.example.com 的下一级域名 (如 team1.corp.example.com) 为前缀的模块版本都将不经过 Go module proxyGo 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

  1. 修改 GOBIN 路径(可选):go env -w GOBIN=$HOME/bin

  2. 打开 Go modules:go env -w GO111MODULE=on

  3. 设置 GOPROXY:go env -w GOPROXY=https://goproxy.cn,direct (可选,对于无法访问外网的同学来说是必须的,因为它的默认值被墙了)。

  4. 在项目的根目录下执行 go mod init <OPTIONAL_MODULE_PATH> 以生成 go.mod 文件。

  5. 执行 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.orggoproxy.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/barexample.com/foo/bar/v2 视为完全不同的两个模块来对待。

一个项目中可能同时存在多个拥有不同主版本号的相同模块,但是互不影响。

go get -u 不会更新主版本号,如果需要更新,需要手动修改代码中对应的导入路径。

另外,使用 Go modules 时,不可以同时依赖同一个模块的不同的两个或者多个小版本(修订版本号不同)。Go modules 只可以同时依赖一个模块的不同的两个或者多个大版本(主版本号不同)。比如可以同时依赖 example.com/foobar@v1.2.3example.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 版本。

参考文章

干货满满的 Go Modules 和 goproxy.cn

go module 使用

Go Modules 详解使用

Go module的介绍及使用