Go 博客
2019 年的 Go Modules
不平凡的一年!
2018 年对于 Go 生态系统来说是伟大的一年,其中包管理是我们的主要关注点之一。2 月份,我们发起了一场社区范围的讨论,关于如何将包管理直接集成到 Go 工具链中,并在 8 月份的 Go 1.11 版本中发布了该功能的第一个初步实现,称之为 Go modules。迁移到 Go modules 将是 Go 1 以来 Go 生态系统中最深远的变化。将整个生态系统——代码、用户、工具等——从 GOPATH 转换为 modules 需要在许多不同领域进行工作。反过来,模块系统将帮助我们为 Go 生态系统提供更好的认证和构建速度。
本文预览了 Go 团队在 2019 年针对 modules 的计划。
版本发布
于 2018 年 8 月发布的 Go 1.11 引入了对 modules 的初步支持。目前,module 支持与传统的基于 GOPATH 的机制并行维护。当在 GOPATH/src 之外的目录树中运行,并且根目录由 go.mod
文件标记时,go
命令默认进入 module 模式。此设置可以通过将过渡性环境变量 $GO111MODULE
设置为 on
或 off
来覆盖;默认行为是 auto
模式。我们已经看到 Go 社区广泛采用了 modules,同时也收到了许多有益的建议和错误报告,帮助我们改进 modules。
计划于 2019 年 2 月发布的 Go 1.12 将进一步完善 module 支持,但默认仍保留 auto
模式。除了许多错误修复和其他细微改进之外,Go 1.12 中最显著的变化可能在于,像 go
run
x.go
或 go
get
rsc.io/2fa@v1.1.0
这样的命令现在可以在没有显式 go.mod
文件的情况下以 GO111MODULE=on
模式运行。
我们的目标是让计划于 2019 年 8 月发布的 Go 1.13 默认启用 module 模式(即将默认值从 auto
更改为 on
)并废弃 GOPATH 模式。为此,我们一直在努力改进工具支持,并更好地支持开源模块生态系统。
工具和 IDE 集成
在使用 GOPATH 的八年里,涌现了大量的工具,这些工具都假定 Go 源代码存储在 GOPATH 中。迁移到 modules 需要更新所有基于这一假定的代码。我们设计了一个新包,golang.org/x/tools/go/packages,它抽象了查找和加载特定目标 Go 源代码信息的逻辑。这个新包能自动适应 GOPATH 和 modules 模式,并且还可以扩展以支持特定工具的代码布局,例如 Bazel 使用的那种。我们一直与 Go 社区的工具作者合作,帮助他们在工具中采用 golang.org/x/tools/go/packages。
作为这项工作的一部分,我们还一直在努力将 gocode、godef 和 go-outline 等各种源代码查询工具统一到一个单独的工具中,该工具可以从命令行使用,并且支持现代 IDE 使用的语言服务器协议。
向 modules 的过渡和包加载方式的变化也推动了 Go 程序分析的重大变革。作为重构 go
vet
以支持 modules 的一部分,我们引入了一个用于 Go 程序增量分析的通用框架,其中分析器一次为一个包调用。在这个框架中,对一个包的分析可以输出事实,供导入该包的其他包的分析使用。例如,go
vet
对 log 包的分析确定并记录了 log.Printf
是 fmt.Printf
的包装函数这一事实。然后 go
vet
可以检查调用 log.Printf
的其他包中的 printf 风格格式字符串。这个框架应该能催生许多新的、复杂的程序分析工具,帮助开发者更早地发现 bug 并更好地理解代码。
模块索引
go
get
原始设计中最重要的部分之一是它的去中心化:我们当时相信——今天仍然相信——任何人都可以将他们的代码发布到任何服务器上,这与 Perl 的 CPAN、Java 的 Maven 或 Node 的 NPM 等中央注册中心形成对比。在 go
get
导入路径的开头放置域名重用了现有的去中心化系统,并避免了重新解决谁可以使用哪个名称的问题。这也允许公司从私有服务器导入代码以及从公共服务器导入代码。在我们转向 Go modules 时,保持这种去中心化至关重要。
Go 依赖项的去中心化带来了许多好处,但也带来了一些显著的缺点。首先是太难找到所有公开可用的 Go 软件包。每个希望提供软件包信息的网站都必须自己进行抓取,否则就必须等到用户询问特定软件包时才去获取它。
我们正在开发一项新服务,即 Go 模块索引,它将提供进入 Go 生态系统的软件包的公共日志。像 godoc.org 和 goreportcard.com 这样的网站将能够监视此日志以查找新条目,而无需各自独立实现查找新软件包的代码。我们还希望该服务允许使用简单的查询来查找软件包,以便 goimports
可以为尚未下载到本地系统的软件包添加导入。
模块认证
目前,go
get
依赖于连接级认证(HTTPS 或 SSH)来检查它是否正在与正确的服务器通信以下载代码。对代码本身没有额外的检查,如果 HTTPS 或 SSH 机制以某种方式受到损害,则存在中间人攻击的可能性。去中心化意味着构建所需的代码是从许多不同的服务器获取的,这意味着构建依赖于许多系统来提供正确的代码。
Go modules 设计通过在每个模块中存储一个 go.sum
文件来改进代码认证;该文件列出了模块每个依赖项的预期文件树的加密哈希值。使用 modules 时,go
命令使用 go.sum
来验证依赖项与预期版本完全一致(逐位相同),然后才在构建中使用它们。但 go.sum
文件只列出了该模块使用的特定依赖项的哈希值。如果您正在添加新的依赖项或使用 go
get
-u
更新依赖项,则 go.sum
中没有相应的条目,因此无法直接验证下载的数据。
对于公开可用的模块,我们计划运行一个我们称之为公证服务的服务,它会跟踪模块索引日志,下载新模块,并对形如“模块 M 在版本 V 时具有文件树哈希 H”的声明进行加密签名。公证服务将在一个可查询、证书透明风格的防篡改日志中发布所有这些经过公证的哈希值,这样任何人都可以验证公证服务的行为是否正确。该日志将作为一个公共的、全局的 go.sum
文件,go
get
可以在添加或更新依赖项时使用它来认证模块。
我们的目标是在 Go 1.13 中,让 go
命令开始检查尚未在 go.sum
中的公开可用模块的经过公证的哈希值。
模块镜像
由于去中心化的 go
get
从多个源服务器获取代码,因此获取代码的速度和可靠性取决于最慢、最不可靠的服务器。在 modules 出现之前唯一可用的防御措施是将依赖项 vendor 到自己的仓库中。虽然 vendoring 将继续得到支持,但我们更倾向于一种适用于所有模块(不仅仅是您正在使用的模块),并且不需要将依赖项复制到每个使用它的仓库中的解决方案。
Go module 设计引入了模块代理的概念,这是一个 go
命令请求模块的服务器,而不是直接向源服务器请求。一种重要的代理是模块镜像,它通过从源服务器获取模块并将其缓存起来,供将来请求使用来响应模块请求。一个运行良好的镜像即使在某些源服务器宕机时也应该快速可靠。我们计划在 2019 年推出一个针对公开可用模块的镜像服务。其他项目,如 GoCenter 和 Athens,也在计划提供镜像服务。(我们预计公司也将有多种选项来运行自己的内部镜像,但本文重点关注公共镜像。)
镜像的一个潜在问题是,它们恰好是中间人服务器,这使得它们成为攻击的天然目标。Go 开发者需要一些保证,确保镜像提供的与源服务器提供的数据是相同的。我们在上一节中描述的公证过程恰恰解决了这个问题,并且它将适用于使用镜像下载以及使用源服务器下载的情况。镜像本身不需要被信任。
我们的目标是让由 Google 运营的模块镜像在 Go 1.13 中默认由 go
命令使用。使用替代镜像或根本不使用镜像,配置起来将非常简单。
模块发现
最后,我们之前提到模块索引将使得构建像 godoc.org 这样的网站变得更容易。我们 2019 年工作的一部分将是对 godoc.org 进行重大改造,使其对需要发现可用模块并决定是否依赖某个模块的开发者更加有用。
总体概览
此图显示了本文所述设计中模块源代码的流动方式。

之前,所有 Go 源代码的消费者——go
命令和像 godoc.org 这样的网站——都直接从每个代码托管源获取代码。现在他们可以从一个快速、可靠的镜像获取缓存的代码,同时仍然能认证下载的数据是正确的。索引服务使得镜像、godoc.org 以及任何其他类似网站能够轻松跟上每天添加到 Go 生态系统中的所有优秀新代码。
我们对 2019 年 Go modules 的未来感到兴奋,希望您也一样。新年快乐!
下一篇文章:Go 1.12 发布
上一篇文章:Go 2,我们来了!
博客索引