Module 功能默认开启

在 Go 1.16 版本中,Go module-aware 模式成为了默认模式 (另一种则是传统的 gopath 模式)。module-aware 模式成为默认意味着什么呢?意味着 GO111MODULE 的值默认为 on 了。

这里将 Go 1.13 版本之前、Go 1.13 版本以及 Go 1.16 版本在 GO111MODULE 为不同值的情况下的行为做一下对比,这样我们可以更好的理解 go 1.16 中 module-aware 模式下的行为特性,下面我们就来做一下比对:

GO111MODULE < Go 1.13 Go 1.13 Go 1.16
on 任何路径下都开启 module-aware 模式 任何路径下都开启 module-aware 模式 【默认值】:任何路径下都开启 module-aware 模式
auto 【默认值】:使用 GOPATH mode 还是 module-aware mode,取决于要构建的源码目录所在位置以及是否包含 go.mod 文件。如果要构建的源码目录不在以 GOPATH/src 为根的目录体系下,且包含 go.mod 文件 (两个条件缺一不可),那么使用 module-aware mode;否则使用传统的 GOPATH mode。 【默认值】:只要当前目录或父目录下有 go.mod 文件时,就开启 module-aware 模式,无论源码目录是否在 GOPATH 外面 只要当前目录或父目录下有 go.mod 文件时,就开启 module-aware 模式,无论源码目录是否在 GOPATH 外面
off gopath 模式 gopath 模式 gopath 模式

我们看到在 Go 1.16 模式下,依然可以回归到 gopath 模式。但 Go 核心团队已经决定拒绝 “继续保留 GOPATH mode” 的提案,并计划在 Go 1.17 版本中彻底取消 gopath mode,仅保留 go module-aware mode。

不会自动更改 go.mod 和 go.sum

在之前的版本中,当 go 命令发现 go.mod 或 go.sum 有问题时,比如缺少 require 指令或缺少 sum,它会尝试自动修复问题。我们收到了很多反馈,认为这种行为是出乎大家意料的,尤其是对于像 go list 这样通常不会产生副作用的命令。自动修复并不总是可取的:如果一个导入的包没有被任何需要的 Module 提供,go 命令会添加一个新的依赖关系,可能会触发普通依赖关系的升级。即使是拼写错误的导入路径也会导致(失败的)网络查找。

在 Go 1.16 中,module-aware 命令在发现 go.mod 或 go.sum 中的问题后会报告一个错误,而不是尝试自动修复问题。在大多数情况下,错误信息建议使用命令来修复问题。

1
2
3
4
5
$ go build
example.go:3:8: no required module provides package golang.org/x/net/html; to add it:
    go get golang.org/x/net/html
$ go get golang.org/x/net/html
$ go build

和之前一样,如果存在 vendor 目录,go 命令可能会使用该目录(详见 Vendoring)。像 go get 和 go mod tidy 这样的命令仍然会修改 go.mod 和 go.sum,因为它们的主要目的是管理依赖关系。

在特定版本上安装可执行文件

go install 命令现在可以通过指定 @version 后缀来安装特定版本的可执行文件。

1
go install golang.org/x/tools/gopls@v0.6.5

当使用这种语法时,go install 命令会从该 Module 的制定版本安装,而忽略当前目录和父目录中的任何 go.mod 文件。(如果没有 @version 后缀,go install 会像往常一样继续运行,使用当前 Module 的 go.mod 中列出的版本要求和替换来构建程序。)

并且后续,Go 团队会让 go get 将专注于分析依赖,并获取 go 包 /module,更新 go.mod/go.sum,而不再具有安装可执行 Go 程序的行为能力,这样 go get 和 go install 就会各司其职,Gopher 们也不会再被两者的重叠行为所迷惑了。现在如果不想 go get 编译安装,可使用 go get -d

Module 撤回

Module 作者现在可以使用 go.mod 中的 retract 指令撤回 Module 版本。撤回的版本仍然存在,并且可以被下载(所以依赖它的构建不会中断),但在解析 @latest 这样的版本时,go 命令不会自动选择它,go get 和 go list -m -u 会打印关于现有使用版本的警告。

例如,假设一个流行库 example.com/lib 的作者发布了 v1.0.5,然后发现了一个新的安全问题。他们可以在他们的 go.mod 文件中添加如下指令。

1
2
// Remote-triggered crash in package foo. See CVE-2021-01234.
retract v1.0.5

接下来,作者可以标记并推送 v1.0.6 版本,即新的最高版本。在这之后,已经依赖 v1.0.5 的用户在检查更新或升级依赖的软件包时,就会被通知版本撤回。通知信息可能会包含来自 retract 指令上方注释的文字。

1
2
3
4
5
6
7
$ go list -m -u all
example.com/lib v1.0.0 (retracted)
$ go get .
go: warning: example.com/lib@v1.0.5: retracted by module author:
    Remote-triggered crash in package foo. See CVE-2021-01234.
go: to switch to the latest unretracted version, run:
    go get example.com/lib@latest

于是按照提示执行 go get example.com/lib@latest 即可。

用 GOVCS 控制版本管理工具

Go 1.16 引入了一个新的配置变量 GOVCS,让用户可以指定哪些 Module 可以使用特定的版本控制工具。GOVCS 接受一个以逗号分隔的 pattern:vcslist 规则列表。pattern 是一个 path.Match 模式,匹配一个 Module 路径的一个或多个前缀元素。特殊模式 public 和 private 匹配公共和私有 Module(private 被定义为由 GOPRIVATE 中的模式匹配的 Module;public 是其他所有 Module)。vcslist 是一个以管道符分隔的允许的版本控制命令列表,或关键字 all 或 off。

GOVCS 的默认值为 public:git|hg,private:all,即对所有公共 module 允许采用 git 或 hg 获取源码,而对私有 module 则不限制版本控制工具的使用。

如果要允许使用所有工具,可像下面这样设置 GOVCS:

1
GOVCS=*:all

如果要禁止使用任何版本控制工具去直接获取源码(不通过 go proxy),那么可以像下面这样设置 GOVCS:

1
GOVCS=*:off

下一步?

据 Go 官方博客介绍,Go 1.17 将开发新的 Module 功能,特别是懒惰Module加载,这将使 Module 加载过程更快、更稳定。

参考

Go 1.16 中值得关注的几个变化

Go 官方:New module changes in Go 1.16