Go 博客

2019 年的 Go 模块

Russ Cox
2018 年 12 月 19 日

真是丰收的一年!

2018 年是 Go 生态系统非常棒的一年,其中包管理是我们主要的关注点之一。2 月份,我们开始了一场关于如何在 Go 工具链中直接集成包管理的社区讨论,8 月份,我们发布了该功能的第一个粗略实现,称为 Go 模块,在 Go 1.11 中。向 Go 模块的迁移将是自 Go 1 以来对 Go 生态系统影响最深远的改变。将整个生态系统 - 代码、用户、工具等 - 从 GOPATH 转换为模块将需要在许多不同的领域进行工作。模块系统反过来将帮助我们为 Go 生态系统提供更好的身份验证和构建速度。

这篇文章是 Go 团队计划在 2019 年对模块进行的预告。

版本

2018 年 8 月发布的 Go 1.11 引入了 对模块的初步支持。目前,模块支持与传统的基于 GOPATH 的机制并存。当在 GOPATH/src 之外的目录树中运行并且在它们的根目录中标记了 go.mod 文件时,go 命令默认使用模块模式。可以通过将过渡环境变量 $GO111MODULE 设置为 onoff 来覆盖此设置;默认行为是 auto 模式。我们已经看到 Go 社区中模块的显著采用,以及许多有益的建议和错误报告来帮助我们改进模块。

计划于 2019 年 2 月发布的 Go 1.12 将改进模块支持,但仍将其默认保留在 auto 模式。除了许多错误修复和其他小改进之外,Go 1.12 中最显著的变化可能是像 go run x.gogo get rsc.io/[email protected] 这样的命令现在可以在没有明确 go.mod 文件的情况下在 GO111MODULE=on 模式下运行。

我们的目标是 Go 1.13(计划于 2019 年 8 月发布)默认启用模块模式(即,将默认值从 auto 更改为 on)并弃用 GOPATH 模式。为了做到这一点,我们一直在努力改进工具支持以及对开源模块生态系统的支持。

工具和 IDE 集成

在使用 GOPATH 的八年时间里,已经创建了大量工具,它们假设 Go 源代码存储在 GOPATH 中。迁移到模块需要更新所有做出此假设的代码。我们设计了一个新包,golang.org/x/tools/go/packages,它抽象了查找和加载有关给定目标的 Go 源代码信息的运行。此新包会自动适应 GOPATH 和模块模式,并且还可以扩展到特定于工具的代码布局,例如 Bazel 使用的布局。我们一直在与 Go 社区中的工具作者合作,帮助他们在他们的工具中采用 golang.org/x/tools/go/packages。

作为这项工作的一部分,我们还一直在努力将各种源代码查询工具(如 gocode、godef 和 go-outline)统一到一个单一工具中,该工具可以从命令行使用,并且还支持现代 IDE 使用的 语言服务器协议

向模块的过渡以及包加载的变化也促使 Go 程序分析发生了重大变化。作为重新设计 go vet 以支持模块的一部分,我们引入了一个通用的 Go 程序增量分析框架,在这个框架中,分析器一次为一个包调用。在这个框架中,一个包的分析可以写出可供分析其他导入第一个包的包的事实。例如,go vetlog 包 的分析确定并记录了 log.Printffmt.Printf 包装器的事实。然后 go vet 可以检查调用 log.Printf 的其他包中的 printf 样式格式字符串。此框架应该能够支持许多新的、复杂的程序分析工具,帮助开发人员尽早发现错误并更好地理解代码。

模块索引

go get 的原始设计中最重要的部分之一是它是分散的:我们当时认为 - 并且我们今天仍然认为 - 任何人都应该能够在任何服务器上发布他们的代码,这与像 Perl 的 CPAN、Java 的 Maven 或 Node 的 NPM 这样的中央注册中心形成对比。将域名放在 go get 导入空间的开头重用了现有的分散系统,避免了重新解决决定谁可以使用哪些名称的问题。它还允许公司在私有服务器上导入代码,以及从公共服务器上导入代码。在我们转向 Go 模块时,保持这种分散性至关重要。

Go 依赖项的分散化带来了许多好处,但也带来了几个显著的缺点。第一个是很难找到所有公开可用的 Go 包。每个想要提供有关包的信息的网站都必须进行自己的抓取,或者等待用户询问特定包,然后才获取它。

我们正在开发一项名为 Go 模块索引的新服务,它将提供进入 Go 生态系统的包的公共日志。像 godoc.org 和 goreportcard.com 这样的网站将能够观察此日志以获取新条目,而不是各自独立地实现代码来查找新包。我们还希望该服务允许使用简单的查询查找包,以允许 goimports 添加尚未下载到本地系统的包的导入。

模块身份验证

如今,go get 依赖于连接级身份验证(HTTPS 或 SSH)来检查它是否正在与正确的服务器通话以下载代码。没有对代码本身进行额外的检查,如果 HTTPS 或 SSH 机制以某种方式遭到破坏,可能会导致中间人攻击。分散化意味着构建的代码是从许多不同的服务器获取的,这意味着构建依赖于许多系统才能提供正确的代码。

Go 模块设计通过在每个模块中存储一个 go.sum 文件来改进代码身份验证;该文件列出了模块每个依赖项的预期文件树的加密哈希。在使用模块时,go 命令使用 go.sum 来验证依赖项在用于构建之前是否与预期版本完全一致。但是,go.sum 文件只列出了该模块使用的特定依赖项的哈希。如果您正在添加新的依赖项或使用 go get -u 更新依赖项,则 go.sum 中没有相应的条目,因此没有对下载的位进行直接身份验证。

对于公开可用的模块,我们打算运行一项我们称之为公证机构的服务,它跟踪模块索引日志,下载新模块,并对“版本 V 的模块 M 具有文件树哈希 H”形式的语句进行加密签名。公证机构服务将在可查询的、证书透明度 样式的 防篡改日志 中发布所有这些经过公证的哈希,以便任何人都可以验证公证机构的行为是否正确。此日志将作为公共的、全局的 go.sum 文件,go get 可以使用它在添加或更新依赖项时对模块进行身份验证。

我们的目标是从 Go 1.13 开始,让 go 命令检查尚未在 go.sum 中的公开可用模块的经过公证的哈希。

模块镜像

由于分散的 go get 从多个源服务器获取代码,因此获取代码的速度和可靠性仅与最慢、最不可靠的服务器一样快和可靠。在模块之前,唯一可用的防御措施是将依赖项推销到您自己的存储库中。虽然推销将继续得到支持,但我们更喜欢一个对所有模块都适用的解决方案 - 不仅仅是您正在使用的模块 - 并且不需要将依赖项复制到使用它的每个存储库中。

Go 模块设计引入了模块代理的概念,模块代理是 go 命令请求模块的服务器,而不是源服务器。一种重要的代理是模块镜像,它通过从源服务器获取模块并将其缓存以供将来请求使用来响应模块请求。运行良好的镜像应该很快且可靠,即使一些源服务器已经关闭。我们计划在 2019 年启动一个针对公开可用模块的镜像服务。其他项目,如 GoCenter 和 Athens,也计划镜像服务。(我们预计公司将有多种选择来运行他们自己的内部镜像,但这篇文章重点介绍公共镜像。)

镜像的一个潜在问题是它们恰恰是中间人服务器,使它们成为攻击的自然目标。Go 开发人员需要一些保证,即镜像提供了与源服务器相同的位。我们在上一节中描述的公证流程正是解决了这个问题,它将适用于使用镜像的下载以及使用源服务器的下载。镜像本身不需要被信任。

我们的目标是让 Google 运行的模块镜像从 Go 1.13 开始可以默认在 go 命令中使用。使用备用镜像或根本不使用镜像将很容易配置。

模块发现

最后,我们之前提到模块索引将使构建像 godoc.org 这样的网站变得更容易。我们在 2019 年的工作将包括对 godoc.org 的重大改造,使其对需要发现可用模块然后决定是否依赖于给定模块的开发人员更有用。

大图

此图显示了模块源代码在本篇文章的设计中是如何移动的。

以前,所有 Go 源代码的使用者 - go 命令和任何像 godoc.org 这样的网站 - 都直接从每个代码主机获取代码。现在,他们可以从快速、可靠的镜像中获取缓存的代码,同时仍然验证下载的位是否正确。索引服务使镜像、godoc.org 以及任何其他类似网站能够轻松地跟上每天添加到 Go 生态系统中的所有新代码。

我们对 2019 年 Go 模块的未来感到兴奋,我们希望您也是。新年快乐!

下一篇文章: Go 1.12 发布
上一篇文章: Go 2,我们来了!
博客索引