Go Wiki:Go 模块

此 Wiki 页面用作使用和故障排除指南。

Go 从 1.11 版本开始包含对版本化模块的支持,如 此处 所述。初始原型 vgo 于 2018 年 2 月 宣布。2018 年 7 月,版本化模块在 Go 主仓库中 落地

Go 1.14 版本开始,模块支持被认为已准备好用于生产环境,鼓励所有用户从其他依赖管理系统迁移到模块。如果您由于 Go 工具链中的问题而无法迁移,请确保已针对该问题提交了 开放性问题。(如果该问题不在 Go1.16 里程碑中,请说明其阻止您迁移的原因,以便对其优先级进行适当调整)。您也可以提供 体验报告以获取更详细的反馈。

最近的更改

Go 1.16

有关详细信息,请参阅 Go 1.16 发行说明

Go 1.15

有关详细信息,请参阅 Go 1.15 发行说明

Go 1.14

有关详细信息,请参阅 Go 1.14 发行说明

Go 1.13

有关详细信息,请参阅 Go 1.13 发行说明

目录

“快速入门”和“新概念”部分对于刚开始使用模块的人来说尤其重要。“如何…”部分涵盖了有关机制的更多详细信息。此页面上大部分内容都在 FAQ 中,回答了更多具体的问题;至少浏览一下此处列出的 FAQ 简短答案是有意义的。

快速入门

示例

本页的剩余部分涵盖了这些细节,但这里有一个从头开始创建模块的简单示例。

在你的 GOPATH 之外创建一个目录,并可选择初始化 VCS。

$ mkdir -p /tmp/scratchpad/repo
$ cd /tmp/scratchpad/repo
$ git init -q
$ git remote add origin https://github.com/my/repo

初始化一个新的模块。

$ go mod init github.com/my/repo

go: creating new go.mod: module github.com/my/repo

编写你的代码。

$ cat <<EOF > hello.go
package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
}
EOF

构建并运行。

$ go mod tidy
go: finding module for package rsc.io/quote
go: found rsc.io/quote in rsc.io/quote v1.5.2
$ go build -o hello
$ ./hello
Hello, world.

go.mod 文件已更新,包含对你的依赖项的显式版本,其中 v1.5.2 是一个 semver 标签。

$ cat go.mod
module github.com/my/repo

go 1.16

require rsc.io/quote v1.5.2

日常工作流程

在 1.16 之前,在运行 go build -o hello 之前不需要 go getgo mod tidy。在 1.16 中,默认情况下,禁用对 go.modgo.sum 文件的隐式修改。

你日常工作流程通常可以是

简要介绍你可能使用的其他常见功能

阅读完接下来的四部分内容“新概念”后,你将拥有足够的知识来为大多数项目开始使用模块。查看上面的 目录(包括其中的 FAQ 一句话)也有助于你熟悉更详细主题的列表。

新概念

这些部分提供了对主要新概念的概述。有关更多详细信息和基本原理,请参阅 Russ Cox 在 这个 40 分钟的介绍性视频中描述的设计理念官方提案文档或更详细的初始 vgo 博客系列

模块

模块 是相关 Go 包的集合,这些包作为一个单元一起版本化。

模块记录精确的依赖项需求并创建可重复的构建。

大多数情况下,版本控制库中恰好包含一个模块,该模块在库根目录中定义。(单个库中支持多个模块,但通常这会导致比每个库一个模块更多持续的工作量)。

总结库、模块和包之间的关系

模块必须按照 semver 进行语义版本化,通常采用 v(major).(minor).(patch) 的形式,例如 v0.1.0v1.2.3v1.5.0-rc.1。需要在前面加上 v。如果使用 Git,请使用 标记 来标记已发布的提交及其版本。公共和私有模块库和代理正在变得可用(请参阅下面的 FAQ 部分)。

go.mod

模块由一棵 Go 源文件树定义,该树的根目录中包含一个 go.mod 文件。模块源代码可以位于 GOPATH 之外。有四个指令:modulerequirereplaceexclude

以下是一个定义模块 github.com/my/thinggo.mod 文件示例

module github.com/my/thing

require (
    github.com/some/dependency v1.2.3
    github.com/another/dependency/v4 v4.0.0
)

模块通过其 go.mod 中的 module 指令声明其标识,该指令提供模块路径。模块中所有包的导入路径共享模块路径作为共同前缀。模块路径和从 go.mod 到包目录的相对路径共同决定包的导入路径。

例如,如果你要为一个库 github.com/user/mymod 创建一个模块,该模块将包含两个包,其导入路径分别为 github.com/user/mymod/foogithub.com/user/mymod/bar,那么你的 go.mod 文件的第一行通常会将你的模块路径声明为 module github.com/user/mymod,相应的磁盘结构可以是

mymod
|-- bar
|   `-- bar.go
|-- foo
|   `-- foo.go
`-- go.mod

在 Go 源代码中,使用包含模块路径的完整路径导入包。例如,如果在我们上面的示例中,我们在 go.mod 中将模块标识声明为 module github.com/user/mymod,那么消费者可以执行以下操作

import "github.com/user/mymod/bar"

这会从模块 github.com/user/mymod 导入包 bar

excludereplace 指令仅对当前(“主”)模块起作用。除了主模块以外的模块中的 excludereplace 指令在构建主模块时会被忽略。因此,replaceexclude 语句允许主模块完全控制自己的构建,而不会受到依赖项的完全控制。(有关何时使用 replace 指令的讨论,请参阅下面的 FAQ 部分)。

版本选择

如果向你的源代码添加新的导入,而该导入尚未包含在 go.mod 中的 require 中,大多数 go 命令(如“go build”和“go test”)将自动查找正确的模块,并将该新直接依赖项的最高版本添加到你的模块的 go.mod 中作为 require 指令。例如,如果你的新导入对应于依赖项 M,其最新的标记发布版本为 v1.2.3,那么你的模块的 go.mod 将最终包含 require M v1.2.3,这表示模块 M 是一个依赖项,其允许版本 >= v1.2.3(且 < v2,因为 v2 被认为与 v1 不兼容)。

最小版本选择 算法用于选择构建中使用的所有模块的版本。对于构建中的每个模块,由最小版本选择选择的版本始终是在主模块或其依赖项之一的 require 指令中显式列出的版本的语义最高版本。

例如,如果你的模块依赖于模块 A,而模块 A 具有 require D v1.0.0,并且你的模块还依赖于模块 B,而模块 B 具有 require D v1.1.1,那么最小版本选择将选择 D 的 v1.1.1 包含在构建中(因为它是列出的 require 版本中最高的)。即使后来 D 的 v1.2.0 变为可用,对 v1.1.1 的选择仍然保持一致。这展示了模块系统如何提供 100% 可重复的构建。准备就绪后,模块作者或用户可以选择升级到 D 的最新可用版本,或为 D 选择一个显式版本。

有关最小版本选择算法的简要基本原理和概述,请参阅官方提案的 “高保真构建”部分,或参阅 更详细的 vgo 博客系列

要查看所选模块版本的列表(包括间接依赖项),请使用 go list -m all

另请参阅下面的 “如何升级和降级依赖项” 部分和下面的 “如何将版本标记为不兼容?” 常见问题解答。

语义导入版本控制

多年来,官方 Go 常见问题解答一直包含有关包版本控制的以下建议

“用于公共使用的包在不断发展时应尝试保持向后兼容性。Go 1 兼容性指南在这里是一个很好的参考:不要删除导出的名称,鼓励使用带标签的复合文字,等等。如果需要不同的功能,请添加一个新名称,而不是更改旧名称。如果需要完全中断,请使用新的导入路径创建一个新包。”

最后一句话尤为重要——如果破坏了兼容性,则应更改包的导入路径。有了 Go 1.11 模块,该建议被形式化为导入兼容性规则

“如果旧包和新包具有相同的导入路径,则新包必须向后兼容旧包。”

回想一下,semver 要求在 v1 或更高版本的包进行向后不兼容的更改时进行主版本更改。遵循导入兼容性规则和 semver 的结果被称为语义导入版本控制,其中主版本包含在导入路径中——这确保了在主版本由于兼容性中断而递增时导入路径会发生更改。

作为语义导入版本控制的结果,选择加入 Go 模块的代码必须遵守这些规则

通常,具有不同导入路径的包是不同的包。例如,math/randcrypto/rand 是不同的包。即使不同的导入路径是由于导入路径中出现了不同的主版本而造成的,情况也是如此。因此,example.com/my/mod/mypkgexample.com/my/mod/v2/mypkg 是不同的包,并且两者都可以在单个构建中被导入,这除了其他好处之外还有助于解决钻石依赖问题,还允许 v1 模块根据其 v2 替代方案来实现,反之亦然。

有关语义导入版本控制的更多详细信息,请参阅 go 命令文档中的 “模块兼容性和语义版本控制” 部分,有关语义版本控制的更多信息,请参阅 https://semver.org

到目前为止,本节重点介绍了已选择加入模块并导入其他模块的代码。但是,在 v2+ 模块的导入路径中放置主版本可能会导致与 Go 的旧版本或尚未选择加入模块的代码不兼容。为了帮助解决此问题,有三个重要的过渡性特殊情况或对上述行为和规则的例外。随着越来越多的包选择加入模块,这些过渡性例外将变得不那么重要。

三个过渡性例外

  1. gopkg.in

    使用以 gopkg.in 开头的导入路径(例如 gopkg.in/yaml.v1gopkg.in/yaml.v2)的现有代码,即使在选择加入模块后,也可以继续使用这些形式作为其模块路径和导入路径。

  2. 导入非模块 v2+ 包时为“+incompatible”

    模块可以导入尚未选择加入模块本身的 v2+ 包。具有有效 v2+ semver 标签的非模块 v2+ 包将在导入模块的 go.mod 文件中记录带有 +incompatible 后缀。+incompatible 后缀表示,即使 v2+ 包具有有效的 v2+ semver 标签(例如 v2.0.0),v2+ 包也尚未主动选择加入模块,因此假设该 v2+ 包 *未* 了解语义导入版本控制的含义以及如何在导入路径中使用主版本。因此,在 模块模式 下运行时,go 工具会将非模块 v2+ 包视为包的 v1 版本系列的(不兼容)扩展,并假设该包没有意识语义导入版本控制,而 +incompatible 后缀表示 go 工具正在这样做。

  3. 未启用模块模式时的“最小模块兼容性”

    为了帮助实现向后兼容性,Go 版本 1.9.7+、1.10.3+ 和 1.11 已经更新,以便更容易让使用这些版本的代码能够正确使用 v2+ 模块,*无需* 修改现有代码。这种行为被称为“最小模块兼容性”,它只在为 go 工具禁用完全 模块模式 时才生效,例如,如果您在 Go 1.11 中设置了 GO111MODULE=off,或者正在使用 Go 版本 1.9.7+ 或 1.10.3+。当在 Go 1.9.7+、1.10.3+ 和 1.11 中依赖这种“最小模块兼容性”机制时,*尚未* 选择加入模块的包 *不会* 在导入路径中包含任何导入的 v2+ 模块的主版本。相反,*已* 选择加入模块的包 *必须* 在导入路径中包含主版本才能导入任何 v2+ 模块(以便在 go 工具以完全模块模式运行并完全了解语义导入版本控制时正确导入 v2+ 模块)。

有关发布 v2+ 模块所需的确切机制,请参阅下面的 “发布模块(v2 或更高版本)” 部分。

如何使用模块

如何安装和激活模块支持

要使用模块,有两个安装选项

安装完成后,您可以通过以下两种方式之一激活模块支持

如何定义模块

要为现有项目创建 go.mod

  1. 导航到 GOPATH 之外的模块源代码树的根目录

    $ cd <project path outside $GOPATH/src>         # e.g., cd ~/projects/hello
    

    请注意,在 GOPATH 之外,您无需设置 GO111MODULE 即可激活模块模式。

    或者,如果您想在您的 GOPATH 中工作

    $ export GO111MODULE=on                         # manually active module mode
    $ cd $GOPATH/src/<project path>                 # e.g., cd $GOPATH/src/you/hello
    
  2. 创建初始模块定义并将其写入 go.mod 文件

    $ go mod init
    

    此步骤会从任何现有的 dep Gopkg.lock 文件或任何其他 九种支持的依赖项格式 中进行转换,添加与现有配置匹配的 require 语句。

    go mod init 通常能够使用辅助数据(例如 VCS 元数据)来自动确定适当的模块路径,但如果 go mod init 指出它无法自动确定模块路径,或者您需要以其他方式覆盖该路径,则可以将 模块路径 作为 go mod init 的可选参数提供,例如

    $ go mod init github.com/my/repo
    

    请注意,如果您的依赖项包含 v2+ 模块,或者您正在初始化 v2+ 模块,那么在运行 go mod init 后,您可能还需要编辑您的 go.mod.go 代码,以根据上面 “语义导入版本控制” 部分的说明,将 /vN 添加到导入路径和模块路径中。即使 go mod init 自动将您的依赖项信息从 dep 或其他依赖项管理器转换过来,情况也是如此。(因此,在运行 go mod init 后,您通常不应运行 go mod tidy,直到成功运行 go build ./... 或类似操作,这是本节中显示的顺序)。

  3. 构建模块。当从模块的根目录执行时,./... 模式与当前模块中的所有包匹配。go build 会自动添加必要的缺失或未转换的依赖项,以满足此特定构建调用的导入

    $ go build ./...
    
  4. 测试配置后的模块,以确保它与所选版本一起使用

    $ go test ./...
    
  5. (可选)运行模块的测试以及所有直接和间接依赖项的测试,以检查是否存在不兼容性

    $ go test all
    

在标记发布之前,请参阅下面的 “如何为发布做准备” 部分。

有关所有这些主题的更多信息,模块官方文档的主要入口点是 golang.org 上的可用文档

如何升级和降级依赖项

日常依赖项的升级和降级应使用“go get”完成,它会自动更新 go.mod 文件。或者,您可以直接编辑 go.mod

此外,像“go build”、“go test”甚至“go list”这样的 go 命令会自动添加必要的新的依赖项以满足导入(更新 go.mod 并下载新的依赖项)。

要将依赖项升级到最新版本

go get example.com/package

要将依赖项*及其所有依赖项*升级到最新版本

go get -u example.com/package

要查看所有直接和间接依赖项的可用次要和修补程序升级

go list -u -m all

要查看*仅*直接依赖项的可用次要和修补程序升级,请运行

go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}' -m all 2> /dev/null

要将当前模块的所有直接和间接依赖项升级到最新版本,可以在模块根目录中运行以下命令

go get foo 更新到 foo 的最新版本。go get foo 等效于 go get foo@latest — 换句话说,如果未指定 @ 版本,则 @latest 为默认值。

在本节中,“latest”是指具有 semver 标签的最新版本,或者如果没有 semver 标签,则为最新已知提交。除非存储库中没有其他 semver 标签,否则不会选择预发布标签作为“latest”(详细信息)。

一个常见的错误是认为 go get -u foo 仅仅获取 foo 的最新版本。实际上,go get -u foogo get -u foo@latest 中的 -u 表示要*同时*获取 foo 的所有直接和间接依赖项的最新版本。升级 foo 时的一个常见起点是改为执行 go get foogo get foo@latest,不带 -u(并且在一切正常后,考虑 go get -u=patch foogo get -u=patchgo get -u foogo get -u)。

要升级或降级到更具体的版本,“go get”允许通过向包参数添加 @version 后缀或 “模块查询” 来覆盖版本选择,例如 go get [email protected]go get foo@e3702bed2go get foo@'<v1.6.2'

使用分支名称(例如 go get foo@master(在 mercurial 中为 foo@default))是获取最新提交的一种方式,无论它是否具有 semver 标签。

通常,无法解析为 semver 标签的模块查询将被记录为 go.mod 文件中的 伪版本

有关此处主题的更多信息,请参阅 go 命令文档中的 “模块感知 go get”“模块查询” 部分。

模块能够使用尚未选择加入模块的包,包括在 go.mod 中记录任何可用的 semver 标签并使用这些 semver 标签进行升级或降级。模块还可以使用尚未有任何适当的 semver 标签的包(在这种情况下,它们将在 go.mod 中使用伪版本记录)。

在升级或降级任何依赖项后,您可能希望再次运行构建中所有包(包括直接和间接依赖项)的测试,以检查是否存在不兼容性

$ go test all

如何为发布做准备

发布模块(所有版本)

创建模块发布的最佳实践预计将在初始模块实验中出现。其中许多最终可能会由 未来的“go release”工具 自动执行。

在标记发布之前要考虑的一些当前建议的最佳实践

发布模块(v2 或更高版本)

如果你要发布 v2 或更高版本的模块,请首先查看上面“语义导入版本控制” 部分中的讨论,其中包括为什么 v2+ 模块的模块路径和导入路径中包含主版本,以及 Go 版本 1.9.7+ 和 1.10.3+ 如何更新以简化该过渡。

请注意,如果你第一次为一个预先存在的存储库或一组包采用模块,而这些包在采用模块之前已经标记为v2.0.0或更高版本,那么建议的最佳做法是在第一次采用模块时增加主版本。例如,如果你foo的作者,foo存储库的最新标签是v2.2.2,而foo还没有采用模块,那么最佳做法是使用v3.0.0作为foo采用模块的第一个版本(也是foo包含go.mod文件的第一个版本)。在这种情况下,增加主版本可以为foo的使用者提供更大的清晰度,允许在foo的 v2 系列上进行额外的非模块补丁或次要版本发布(如果需要),并为基于模块的foo使用者提供一个强烈的信号,表明如果你执行import "foo"和相应的require foo v2.2.2+incompatible,会得到不同的主版本,而如果你执行import "foo/v3"和相应的require foo/v3 v3.0.0,则会得到不同的主版本。(请注意,此关于在第一次采用模块时增加主版本的建议*不*适用于最新版本为 v0.x.x 或 v1.x.x 的预先存在的存储库或包)。

有两种替代机制可以发布 v2 或更高版本的模块。请注意,使用这两种技术,当模块作者推送新标签时,新的模块版本就会对使用者可用。使用创建v3.0.0版本的示例,两种选择是

  1. 主分支:更新go.mod文件,在module指令中在模块路径的末尾包含/v3(例如,module github.com/my/module/v3)。更新模块内的导入语句,也使用/v3(例如,import "github.com/my/module/v3/mypkg")。使用v3.0.0标记版本。

    • Go 版本 1.9.7+、1.10.3+ 和 1.11 能够正确地使用和构建使用这种方法创建的 v2+ 模块,而无需更新还没有选择模块的使用者代码(如上面“语义导入版本控制”部分中所述)。
    • 一个社区工具github.com/marwan-at-work/mod可以帮助自动执行此过程。请查看存储库或下面社区工具常见问题解答以获取概述。
    • 为了避免使用这种方法造成混淆,可以将模块的v3.*.*提交放在一个单独的 v3 分支上。
    • 注意:创建新分支*不是*必需的。如果你之前一直在 master 分支上发布,并且更愿意在 master 分支上标记v3.0.0,这也是一种可行的选择。(但是,请注意,在master中引入不兼容的 API 更改可能会导致非模块用户在执行go get -u时出现问题,因为在 Go 1.11 之前,或者在 Go 1.11+中模块模式未启用时,go工具不知道semver)。
    • dep等现有的依赖管理解决方案目前可能在使用这种方式创建的 v2+ 模块时遇到问题。例如,请参阅dep#1962
  2. 主子目录:创建一个新的v3子目录(例如,my/module/v3),并将一个新的go.mod文件放在该子目录中。模块路径必须以/v3结尾。将代码复制或移动到v3子目录中。更新模块内的导入语句,也使用/v3(例如,import "github.com/my/module/v3/mypkg")。使用v3.0.0标记版本。

    • 这提供了更大的向后兼容性。特别是,低于 1.9.7 和 1.10.3 的 Go 版本也能够正确地使用和构建使用这种方法创建的 v2+ 模块。
    • 这里更复杂的方法可以利用类型别名(在 Go 1.9 中引入)和位于不同子目录中的主版本之间的转发垫片。这可以提供额外的兼容性,并允许一个主版本根据另一个主版本实现,但需要模块作者做更多工作。一个正在开发的工具goforward可以自动执行此操作。请查看这里以获取更多详细信息和原理,以及一个正在运行的goforward初始版本。
    • dep等现有的依赖管理解决方案应该能够使用这种方式创建的 v2+ 模块。

请参阅https://research.swtch.com/vgo-module以更深入地讨论这些替代方案。

发布版本

可以通过将标签推送到包含模块源代码的存储库来发布新的模块版本。标签由连接两个字符串形成:一个前缀和一个版本

版本是发布的语义导入版本。它应该按照语义导入版本控制的规则进行选择。

前缀指示模块在存储库中定义的位置。如果模块在存储库的根目录中定义,则前缀为空,标签只是版本。但是,在多模块存储库中,前缀用于区分不同模块的版本。前缀是存储库中定义模块的目录。如果存储库遵循上面描述的主子目录模式,则前缀不包含主版本后缀。

例如,假设我们有一个模块example.com/repo/sub/v2,我们想发布版本v2.1.6。存储库根目录对应于example.com/repo,模块在存储库内的sub/v2/go.mod中定义。此模块的前缀是sub/。此版本的完整标签应该是sub/v2.1.6

迁移到模块

本节试图简要列举迁移到模块时需要做出的一些主要决定,以及列出其他与迁移相关的主题。一般情况下,会提供其他部分的参考,以便更详细地介绍。

这些内容主要基于社区在模块实验中出现的最佳实践;因此,这是一个仍在不断改进的部分,随着社区积累更多经验,它将会变得更好。

摘要

迁移主题

从先前的依赖管理工具自动迁移

向旧版本的 Go 和非模块使用者提供依赖信息

更新预先存在的安装说明

避免破坏现有的导入路径

模块在 `go.mod` 中通过 `module` 指令声明其身份,例如 `module github.com/my/module`。模块内的所有包都必须由任何支持模块的消费者使用与模块声明的模块路径匹配的导入路径导入(对于根包,路径完全匹配,或者模块路径作为导入路径的前缀)。如果导入路径与相应模块的声明的模块路径不匹配,则 `go` 命令会报告 `unexpected module path` 错误。

在为一组预先存在的包采用模块时,应注意避免破坏现有消费者使用的现有导入路径,除非您在采用模块时正在递增主版本号。

例如,如果您的预先存在的 README 一直在告诉消费者使用 `import "gopkg.in/foo.v1"`,并且您随后使用 v1 版本采用模块,则您的初始 `go.mod` 几乎肯定应该读取 `module gopkg.in/foo.v1`。如果您想要不再使用 `gopkg.in`,那么对于您的当前消费者来说,这是一个重大变化。一种方法是更改为类似 `module github.com/repo/foo/v2` 的东西,如果您稍后迁移到 v2 的话。

请注意,模块路径和导入路径区分大小写。例如,将模块从 `github.com/Sirupsen/logrus` 更改为 `github.com/sirupsen/logrus` 对于消费者来说是一个重大变化,即使 GitHub 会自动从一个存储库名称转发到新的存储库名称。

在您采用模块之后,在 `go.mod` 中更改模块路径是一个重大变化。

总的来说,这类似于模块出现之前通过 “导入路径注释” 对规范导入路径的强制执行,这些注释有时也被称为“导入编译指令”或“导入路径强制”。例如,`go.uber.org/zap` 包当前托管在 `github.com/uber-go/zap` 中,但使用了一个导入路径注释 在包声明旁边,它会对任何使用错误的基于 github 的导入路径的预模块消费者触发错误。

package zap // import "go.uber.org/zap"

导入路径注释被 `go.mod` 文件的模块语句所取代。

在首次使用 v2+ 包采用模块时递增主版本号

v2+ 模块允许在单个构建中使用多个主版本

使用非模块代码的模块

使用模块的非模块代码

预先存在的 v2+ 包作者的策略

对于正在考虑加入模块的预先存在的 v2+ 包的作者来说,总结替代方法的一种方法是选择三种顶级策略之一。每个选择都将有后续的决定和变体(如上面所述)。这些替代的顶级策略是

  1. 要求客户端使用 Go 版本 1.9.7+、1.10.3+ 或 1.11+.

    这种方法使用“主分支”方法,并依赖于回溯到 1.9.7 和 1.10.3 的“最小模块感知”。有关更多详细信息,请参阅上面 “语义导入版本控制”“发布模块(v2 或更高版本)” 部分。

  2. 允许客户端使用更旧的 Go 版本,例如 Go 1.8.

    这种方法使用“主子目录”方法,并涉及创建子目录,例如 `v2` 或 `v3`。有关更多详细信息,请参阅上面 “语义导入版本控制”“发布模块(v2 或更高版本)” 部分。

  3. 等待加入模块.

    在这种策略中,无论客户端代码是否加入模块,它们都能够正常工作。随着时间的推移,Go 版本 1.9.7+、1.10.3+ 和 1.11+ 将会存在更长的时间,在将来的某个时间点,要求使用 Go 版本 1.9.7+/1.10.3+/1.11+ 会变得更加自然或对客户端更友好,在那个时间点,您可以实施上面策略 1(要求使用 Go 版本 1.9.7+、1.10.3+ 或 1.11+),甚至可以实施上面策略 2(但如果您最终要使用上面策略 2 来支持更旧的 Go 版本,例如 1.8,那么现在就可以进行)。

其他资源

文档和提案

入门材料

补充资料

自最初的 Vgo 提案以来的更改

在提案、原型和测试阶段,整个社区创建了 400 多个问题。请继续提供反馈。

以下是一些较大更改和改进的摘要,其中几乎所有更改和改进都主要基于社区反馈

GitHub 问题

常见问题解答

如何将版本标记为不兼容?

require 指令允许任何模块声明它应该使用依赖项 D 的版本 >= x.y.z 构建(由于与模块 D 的版本 < x.y.z 不兼容,因此可能会指定该版本)。经验数据表明 这是 depcargo 中使用的约束的主要形式。此外,构建中的顶层模块可以 exclude 依赖项的特定版本,或将其他模块 replace 为不同的代码。有关 更多详细信息和基本原理,请参阅完整提案。

版本化模块提案的关键目标之一是为工具和开发人员添加有关 Go 代码版本的通用词汇和语义。这为将来声明其他形式的不兼容性奠定了基础,例如可能

何时获得旧行为与新的基于模块的行为?

通常,模块对于 Go 1.11 是选择加入的,因此根据设计,默认情况下会保留旧行为。

总结您何时获得旧的 1.10 状态 quo 行为,何时获得新的选择加入模块行为

为什么使用 go get 安装工具会失败并出现错误 cannot find main module

当您设置了 GO111MODULE=on,但在运行 go get 时,不在包含 go.mod 的文件树内时,就会发生这种情况。

最简单的解决方案是将 GO111MODULE 保持未设置状态(或等效地显式设置为 GO111MODULE=auto),这样可以避免此错误。

请记住,模块存在的首要原因之一是记录精确的依赖项信息。此依赖项信息将写入您当前的 go.mod。如果您不在包含 go.mod 的文件树内,但已通过设置 GO111MODULE=on 告知 go get 命令在模块模式下运行,那么运行 go get 将导致错误 cannot find main module,因为没有可用的 go.mod 来记录依赖项信息。

解决方案替代方案包括

  1. GO111MODULE 保持未设置状态(默认值,或显式设置为 GO111MODULE=auto),这将导致更友好的行为。这将为您提供 Go 1.10 行为,当您不在模块内时,因此将避免 go get 报告 cannot find main module

  2. GO111MODULE=on 保持不变,但根据需要临时禁用模块并在 go get 期间启用 Go 1.10 行为,例如通过 GO111MODULE=off go get example.com/cmd。这可以变成一个简单的脚本或 shell 别名,例如 alias oldget='GO111MODULE=off go get'

  3. 创建一个临时 go.mod 文件,然后将其丢弃。这已被 @rogpeppe 创建的简单 shell 脚本 自动化。此脚本允许通过 vgoget example.com/cmd[@version] 可选地提供版本信息。(这可以作为避免错误 cannot use path@version syntax in GOPATH mode 的解决方案)。

  4. gobin 是一个模块感知命令,用于安装和运行主包。默认情况下,gobin 会安装/运行主包,而无需先手动创建模块,但使用 -m 标志可以告诉它使用现有模块来解析依赖项。有关详细信息和更多用例,请参阅 gobin READMEFAQ

  5. 创建一个 go.mod 用于跟踪您全局安装的工具,例如在 ~/global-tools/go.mod 中,并在运行任何全局安装的工具的 go getgo install 之前 cd 到该目录。

  6. 为每个工具创建 go.mod,分别放在不同的目录中,例如 ~/tools/gorename/go.mod~/tools/goimports/go.mod,并在运行工具的 go getgo install 之前 cd 到相应的目录。

此当前限制将得到解决。但是,主要问题是模块目前是选择加入的,完整解决方案可能要等到 GO111MODULE=on 成为默认行为。有关更多讨论,请参阅 #24250,包括此评论

这最终必须起作用。我不确定的是这在版本方面具体做了什么:它是否会创建一个临时模块根目录和 go.mod,执行安装,然后将其丢弃?很可能。但我还不完全确定,目前,我不想通过让 vgo 在 go.mod 树之外执行操作来混淆人们。当然,最终的 go 命令集成必须支持这一点。

此 FAQ 一直在讨论跟踪全局安装的工具。

如果您想跟踪特定模块所需的工具,请参阅下一个 FAQ。

如何跟踪模块的工具依赖项?

如果您

那么目前推荐的方法之一是向您的模块添加一个 tools.go 文件,其中包括对感兴趣工具的导入语句(例如 import _ "golang.org/x/tools/cmd/stringer"),以及 //go:build tools 构建约束。导入语句允许 go 命令在您的模块的 go.mod 中精确记录您的工具的版本信息,而 //go:build tools 构建约束可以防止您的正常构建实际导入您的工具。

有关如何执行此操作的具体示例,请参阅此 “Go 模块示例”演练

关于该方法的讨论以及一个更早的关于如何执行此操作的具体示例,请参见#25922 中的此评论

简要的理由(同样来自#25922

我认为 tools.go 文件实际上是工具依赖项的最佳实践,至少对于 Go 1.11 而言是如此。

我喜欢它,因为它没有引入新的机制。

它只是重用了现有的机制。

您还可以(从 go 1.16 开始)使用 go install tool@version 安装特定版本,或者(从 go 1.17 开始)使用 go run tool@version 在不安装的情况下运行工具,如#42088#40276 中实现的那样,这可以消除对 tools.go 的需求。

IDE、编辑器和像 goimports、gorename 等标准工具中模块支持的现状如何?

对模块的支持开始出现在编辑器和 IDE 中。

例如

像 goimports、guru、gorename 和类似工具等其他工具的状态正在一个总括问题#24661 中跟踪。请参阅该总括问题以了解最新状态。

某些特定工具的跟踪问题包括

一般来说,即使您的编辑器、IDE 或其他工具尚未实现模块感知,如果使用 GOPATH 内部的模块并执行 go mod vendor(因为这样会通过 GOPATH 获取正确的依赖项),那么它们的许多功能都应该可以使用模块。

完整的修复方法是将加载包的程序从 go/build 移动到 golang.org/x/tools/go/packages,后者知道如何在模块感知的方式下定位包。这最终可能会变成 go/packages

常见问题解答 - 附加控制

有哪些与模块一起使用的社区工具?

社区正在开始构建基于模块的工具。例如

我什么时候应该使用 replace 指令?

如上面的‘go.mod’ 概念部分 中所述,replace 指令在顶层 go.mod 中提供了额外的控制,用于实际使用什么来满足 Go 源代码或 go.mod 文件中找到的依赖项,而主模块以外的模块中的 replace 指令在构建主模块时会被忽略。

replace 指令允许您提供另一个导入路径,该路径可能是位于 VCS(GitHub 或其他地方)或在您的本地文件系统上的另一个模块,带有相对或绝对文件路径。来自 replace 指令的新导入路径在不需要更新实际源代码中的导入路径的情况下使用。

replace 允许顶层模块控制用于依赖项的精确版本,例如

replace 还允许使用分叉的依赖项,例如

您还可以引用分支,例如

一个示例用例是,如果您需要修复或调查依赖项中的某些内容,您可以拥有一个本地分叉并添加以下内容到您的顶层 go.mod

replace 还可用于将多模块项目中模块的相对或绝对磁盘位置告知 go 工具,例如

注意:如果 replace 指令右侧是文件系统路径,则目标必须在该位置具有 go.mod 文件。如果 go.mod 文件不存在,您可以使用 go mod init 创建一个。

通常,您可以选择在 replace 指令中 => 的左侧指定一个版本,但如果您省略它,通常对更改的敏感度较低(例如,如上面所有 replace 示例中所做的那样)。

每个直接依赖项的 replace 指令都需要一个 require 指令。当从文件系统路径替换依赖项时,相应 require 指令的版本实际上会被忽略;在这种情况下,伪版本 v0.0.0 是一个不错的选择,可以明确这一点,例如 require example.com/module v0.0.0

您可以通过运行 go list -m all 来确认您获得了预期的版本,这将向您显示构建中将使用的实际最终版本,包括考虑 replace 语句。

有关更多详细信息,请参阅‘go mod edit’ 文档

github.com/rogpeppe/gohack 使这些类型的工作流程变得更加容易,尤其是在您的目标是对模块的依赖项进行可变检出时。请参阅存储库 或前面的常见问题解答以获取概述。

请参阅下一个常见问题解答,以了解使用 replace 完全在 VCS 外部工作的详细信息。

我可以在本地文件系统上完全在 VCS 之外工作吗?

是的。VCS 不是必需的。

如果您只有一个模块需要在 VCS 外部进行编辑(并且您要么只有一个模块,要么其他模块位于 VCS 中),这非常简单。在这种情况下,您可以将包含单个 go.mod 的文件树放在一个方便的位置。即使您的单个模块在 VCS 外部,您的 go buildgo test 和类似命令也能正常工作(无需在您的 go.mod 中使用 replace)。

如果您想在本地磁盘上拥有多个相互关联的模块,并且您希望同时编辑它们,那么 replace 指令是一种方法。以下是一个示例 go.mod,它使用 replace 和一个相对路径来将 hello 模块指向 goodbye 模块的磁盘位置(不依赖于任何 VCS)

module example.com/me/hello

require (
  example.com/me/goodbye v0.0.0
)

replace example.com/me/goodbye => ../goodbye

主题 中展示了一个小的可运行示例。

如何在模块中使用 vendoring?vendoring 会消失吗?

最初的 vgo 博客文章系列确实建议完全放弃供应商,但是来自社区的反馈 导致保留了对供应商的支持。

简而言之,要将供应商与模块一起使用

Go 的旧版本(如 1.10)了解如何使用 go mod vendor 创建的供应商目录,Go 1.11 和 1.12+ 在模块模式 禁用时也是如此。因此,供应商是模块为不支持模块的 Go 旧版本以及尚未启用模块本身的使用者提供依赖项的一种方式。

如果您正在考虑使用供应商,值得阅读尖端文档中的“模块和供应商”“创建依赖项的供应商副本” 部分。

是否有“始终开启”的模块存储库和企业代理?

公开托管的“始终在线”不可变模块存储库以及可选的私有托管代理和存储库正在变得可用。

例如

请注意,您不需要运行代理。相反,1.11 中的 go 工具通过 GOPROXY 添加了可选代理支持,以支持更多企业用例(例如更多控制),以及更好地处理诸如“GitHub 宕机”或人们删除 GitHub 存储库等情况。

我可以控制何时更新 go.mod 以及何时 go 工具使用网络来满足依赖项?

默认情况下,诸如 go build 之类的命令将根据需要连接网络以满足导入需求。

某些团队可能希望在某些情况下阻止 go 工具连接网络,或者希望更好地控制 go 工具何时更新 go.mod、如何获取依赖项以及如何使用供应商。

go 工具提供了相当大的灵活性来调整或禁用这些默认行为,包括通过 -mod=readonly-mod=vendorGOFLAGSGOPROXY=offGOPROXY=file:///filesystem/pathgo mod vendorgo mod download

有关这些选项的详细信息散布在官方文档中。社区尝试对这些行为相关旋钮进行集中概述的一个尝试是 这里,其中包含指向官方文档的链接,以获取更多信息。

如何在 Travis 或 CircleCI 等 CI 系统中使用模块?

最简单的方法可能是设置环境变量 GO111MODULE=on,它应该与大多数 CI 系统配合使用。

但是,在 CI 中运行启用和禁用模块的 Go 1.11 测试可能很有价值,因为您的一些用户可能还没有选择加入模块。供应商也是需要考虑的一个主题。

以下两篇博文更具体地介绍了这些主题

如何下载构建特定包或测试所需的模块?

go mod download 命令(或等效的 go mod download all)下载构建列表中的所有模块(如 go list -m all 所报告)。许多这些模块不需要构建主模块中的包,因为完整的构建列表包含诸如测试依赖项和用于其他模块的工具依赖项之类的东西。因此,使用 go mod download 准备的 Docker 镜像可能比必要的大。

相反,考虑使用 go list。例如,go list ./... 将下载构建包 ./...(从模块根目录运行时,主模块中的包集)所需的模块。

要下载测试依赖项,请使用 go list -test ./...

默认情况下,go list 将仅考虑当前平台所需的依赖项。您可以设置 GOOSGOARCH 使 go list 考虑另一个平台,例如,GOOS=linux GOARCH=amd64 go list ./...-tags 标志也可以用来选择具有特定构建标签的包。

这种技术在未来可能不太必要,因为在实现延迟模块加载时(请参阅 #36460),all 模块模式将包含更少的模块。

常见问题解答 - go.mod 和 go.sum

为什么“go mod tidy”在我的“go.mod”中记录了间接和测试依赖项?

模块系统在您的 go.mod 中记录了精确的依赖项要求。(有关更多详细信息,请参阅上面的 go.mod 概念 部分或 go.mod 提示文档)。

go mod tidy 更新您当前的 go.mod 以包含模块测试所需的依赖项 - 如果测试失败,我们必须知道哪些依赖项已用于重现故障。

go mod tidy 还会确保您当前的 go.mod 反映了所有可能的 OS、体系结构和构建标签组合的依赖项要求(如 此处 所述)。相反,其他命令(如 go buildgo test)只会更新 go.mod 以提供在当前 GOOSGOARCH 和构建标签下由请求的包导入的包(这是 go mod tidy 可能添加 go build 或类似命令未添加的要求的原因之一)。

如果模块的依赖项本身没有 go.mod(例如,因为依赖项尚未选择加入模块本身),或者如果它的 go.mod 文件缺少一个或多个依赖项(例如,因为模块作者没有运行 go mod tidy),那么缺少的传递依赖项将被添加到您的模块的要求中,以及一个 // indirect 注释以指示该依赖项不是来自您的模块中的直接导入。

请注意,这也意味着您直接或间接依赖项的任何缺少的测试依赖项也将记录在您的 go.mod 中。(这是一个重要的例子:go test all 运行您模块的所有直接和间接依赖项的测试,这是一种验证您当前的版本组合是否协同工作的方法。如果您在运行 go test all 时在其中一个依赖项中遇到测试失败,重要的是要记录一组完整的测试依赖项信息,以便您可以获得可重复的 go test all 行为)。

您在 go.mod 文件中可能具有 // indirect 依赖项的另一个原因是,如果您已将其中一个间接依赖项升级(或降级)到超出直接依赖项所需的范围,例如,如果您运行了 go get -ugo get [email protected]。go 工具需要一个地方来记录这些新版本,它会在您的 go.mod 文件中这样做(并且它不会向下进入您的依赖项以修改它们go.mod 文件)。

通常,上面描述的行为是模块通过记录精确的依赖项信息来提供 100% 可重复构建和测试的方式的一部分。

如果您想知道为什么某个特定模块出现在您的 go.mod 中,您可以运行 go mod why -m <module>回答 这个问题。其他用于检查要求和版本的工具包括 go mod graphgo list -m all

“go.sum”是一个锁定文件吗?为什么“go.sum”包含我不再使用的模块版本的信息?

不,go.sum 不是一个锁定文件。构建中的 go.mod 文件提供了足够的信息来实现 100% 可重复的构建。

出于验证目的,go.sum 包含特定模块版本的预期内容的加密校验和。有关 go.sum 的更多详细信息(包括为什么通常应该签入 go.sum),请参阅下面的 常见问题解答 以及提示文档中的 “模块下载和验证” 部分。

此外,您的模块的 go.sum 记录了构建中使用过的所有直接和间接依赖项的校验和(因此,您的 go.sum 中列出的模块通常比您的 go.mod 多)。

我是否应该提交“go.sum”文件以及“go.mod”文件?

通常,您的模块的 go.sum 文件应与您的 go.mod 文件一起提交。

如果我没有依赖项,是否仍然应该添加“go.mod”文件?

是的。这支持在 GOPATH 之外工作,帮助向生态系统传达您正在选择加入模块,此外,您 go.mod 中的 module 指令是您代码身份的明确声明(这就是导入注释最终可能会被弃用的原因之一)。当然,模块在 Go 1.11 中纯粹是选择加入的功能。

常见问题解答 - 语义导入版本控制

为什么主版本号必须出现在导入路径中?

请参阅上面 “语义导入版本控制” 概念部分中关于语义导入版本控制和导入兼容性规则的讨论。另请参见 宣布该提案的博文,其中详细介绍了导入兼容性规则的动机和理由。

为什么主要版本 v0、v1 从导入路径中省略?

请参阅早期 官方提案讨论中的常见问题解答 中的“为什么主要版本 v0、v1 从导入路径中省略?”这个问题。

使用主版本 v0、v1 标记我的项目或使用 v2+ 进行重大更改会产生哪些影响?

针对“k8s 进行次要版本发布,但每次次要版本发布都会更改 Go API”这一评论,Russ Cox 做出了以下 回复,其中重点介绍了在项目中选择 v0、v1 以及频繁地使用 v2、v3、v4 等进行重大更改的一些影响。

我不完全理解 k8s 的开发周期等等,但我认为一般来说,k8s 团队需要决定/确认他们打算向用户保证什么稳定性,然后相应地应用版本号来表达这一点。

  • 要做出关于 API 兼容性的承诺(这似乎是最好的用户体验!),那么就开始这样做并使用 1.X.Y。
  • 为了能够在每次发布中进行向后不兼容的更改,但允许大型程序的不同部分根据不同的时间表升级其代码,这意味着不同部分可以在一个程序中使用 API 的不同主要版本,那么请使用 X.Y.0,以及诸如 k8s.io/client/vX/foo 之类的导入路径。
  • 不要对 API 兼容性做出任何承诺,并且还要求每次构建都只有一个 k8s 库的副本,无论是什么,这意味着所有构建部分都必须使用相同的版本,即使并非所有部分都已为此做好准备,那么请使用 0.X.Y。

相关地,Kubernetes 有一些非典型的构建方法(目前包括在 godep 之上的自定义包装脚本),因此 Kubernetes 对许多其他项目来说是一个不完美的例子,但随着 Kubernetes 向采用 Go 1.11 模块迈进,它很可能成为一个有趣的例子。

模块可以消耗尚未加入模块的包吗?

是的。

如果某个仓库尚未选择加入模块,但已使用有效的 semver 标签(包括所需的开头 v)进行标记,则这些 semver 标签可以在 go get 中使用,并且相应的 semver 版本将记录在导入模块的 go.mod 文件中。如果仓库没有任何有效的 semver 标签,则仓库的版本将使用 “伪版本”(例如 v0.0.0-20171006230638-a6e239ea1c69)进行记录(其中包括时间戳和提交哈希,这些哈希旨在允许跨 go.mod 中记录的版本进行总排序,并使其更容易推断哪个记录的版本比另一个记录的版本“更晚”)。

例如,如果包 foo 的最新版本标记为 v1.2.3,但 foo 本身尚未选择加入模块,则从模块 M 内部运行 go get foogo get [email protected] 将在模块 M 的 go.mod 文件中记录为

require  foo  v1.2.3

go 工具还将在其他工作流中(例如 go list -u=patch,它将模块的依赖项升级到可用的补丁版本,或者 go list -u -m all,它显示可用的升级等等)中为非模块包使用可用的 semver 标签。

请参阅下一个常见问题解答,以获取有关尚未选择加入模块的 v2+ 包的其他详细信息。

模块可以消耗尚未加入模块的 v2+ 包吗?“+incompatible”是什么意思?

是的,模块可以导入尚未选择加入模块的 v2+ 包,如果导入的 v2+ 包具有有效的 semver 标签,它将使用 +incompatible 后缀进行记录。

其他详细信息

请熟悉上面 “语义导入版本控制” 部分中的内容。

首先回顾一些通常很有用但在考虑此常见问题解答中描述的行为时尤其重要的核心原则会很有帮助。

以下核心原则在 go 工具处于模块模式(例如 GO111MODULE=on)时始终为真。

  1. 包的导入路径定义了包的身份。
    • 具有不同导入路径的包被视为不同的包。
    • 具有相同导入路径的包被视为相同的包(即使 VCS 标签表明这些包具有不同的主要版本)。
  2. 没有 /vN 的导入路径被视为 v1 或 v0 模块(即使导入的包尚未选择加入模块,并且具有表明主要版本大于 1 的 VCS 标签)。
  3. 模块路径(例如 module foo/v2)在模块 go.mod 文件的开头声明,它既是
    • 对该模块身份的明确声明
    • 对该模块如何被使用代码导入的明确声明

正如我们在下一个常见问题解答中看到的那样,这些原则在 go 工具处于模块模式时并不总是为真,但这些原则在 go 工具处于模块模式时始终为真。

简而言之,+incompatible 后缀表明在以下情况为真时,原则 2 生效。

go 工具处于模块模式时,它将假设非模块 v2+ 包不了解语义导入版本控制,并将将其视为包的 v1 版本系列的(不兼容)扩展(+incompatible 后缀表明 go 工具正在这样做)。

示例

假设

在这种情况下,从模块 M 内部运行例如 go get oldpackage@latest 将在模块 M 的 go.mod 文件中记录以下内容。

require  oldpackage  v3.0.1+incompatible

请注意,在上面的 go get 命令中或在记录的 require 指令中,oldpackage 的末尾没有使用 /v3——在模块路径和导入路径中使用 /vN语义导入版本控制 的一项功能,并且由于 oldpackage 本身没有在 oldpackage 内具有 go.mod 文件,因此 oldpackage 没有发出其接受和理解语义导入版本控制的信号。换句话说,即使 oldpackage 具有 semver 标签 v3.0.1,但 oldpackage 也不被授予 语义导入版本控制 的权利和责任(例如在导入路径中使用 /vN),因为 oldpackage 尚未声明其希望这样做。

+incompatible 后缀表明 oldpackagev3.0.1 版本没有积极选择加入模块,因此,假设 oldpackagev3.0.1 版本了解语义导入版本控制或如何在导入路径中使用主要版本。因此,在 模块模式 下运行时,go 工具将把 oldpackage 的非模块 v3.0.1 版本视为 oldpackage 的 v1 版本系列的(不兼容)扩展,并假设 oldpackagev3.0.1 版本不了解语义导入版本控制,+incompatible 后缀表明 go 工具正在这样做。

根据语义导入版本控制,oldpackagev3.0.1 版本被认为是 v1 发布系列的一部分这一事实意味着,例如,版本 v1.0.0v2.0.0v3.0.1 将始终使用相同的导入路径进行导入。

import  "oldpackage"

再次注意,oldpackage 的末尾没有使用 /v3

一般来说,具有不同导入路径的包是不同的包。在本例中,鉴于 oldpackage 的版本 v1.0.0v2.0.0v3.0.1 将使用相同的导入路径进行导入,因此它们在构建中被视为相同的包(同样是因为 oldpackage 尚未选择加入语义导入版本控制),并且最终在任何给定构建中只有一个 oldpackage 副本。(使用的版本将是任何 require 指令中列出的版本中语义上最高的版本;请参阅 “版本选择”)。

如果我们假设稍后创建了 oldpackage 的一个新的 v4.0.0 版本,该版本采用了模块,因此包含一个 go.mod 文件,那么这就是 oldpackage 现在了解语义导入版本控制的权利和责任的信号,因此基于模块的使用者现在将使用导入路径中的 /v4 进行导入。

import  "oldpackage/v4"

并且版本将被记录为

require  oldpackage/v4  v4.0.0

oldpackage/v4 现在是一个与 oldpackage 不同的导入路径,因此是一个不同的包。如果构建中的一些使用者具有 import "oldpackage/v4",而同一构建中的其他使用者具有 import "oldpackage",则在模块感知构建中最终将存在两个副本(每个导入路径一个)。这是允许逐渐采用模块的策略的一部分。此外,即使在模块摆脱其当前的过渡阶段之后,这种行为也是可取的,因为它允许随着时间的推移逐渐进行代码演变,不同的使用者以不同的速度升级到更新版本(例如,允许大型构建中的不同使用者选择以不同的速度从 oldpackage/v4 升级到某个未来的 oldpackage/v5)。

如果模块支持未启用,v2+ 模块在构建中如何处理?“最小模块兼容性”在 1.9.7+、1.10.3+ 和 1.11 中如何工作?

在考虑较旧的 Go 版本或尚未选择加入模块的 Go 代码时,语义导入版本控制对 v2+ 模块具有重要的向后兼容性影响。

如上面 “语义导入版本控制” 部分所述

但是,预计生态系统将以不同的速度进行模块和语义导入版本控制的采用。

“如何发布 v2+ 模块” 部分中更详细地描述的那样,在“主要子目录”方法中,v2+ 模块的作者创建了诸如 mymodule/v2mymodule/v3 之类的子目录,并将相应的包移动或复制到这些子目录下。这意味着传统的导入路径逻辑(即使在较旧的 Go 版本中,例如 Go 1.8 或 1.7)在看到诸如 import "mymodule/v2/mypkg" 之类的导入语句时将找到相应的包。因此,即使未启用模块支持(无论是因为您正在运行 Go 1.11 且尚未启用模块,还是因为您正在运行不支持模块的旧版本,例如 Go 1.7、1.8、1.9 或 1.10),位于“主要子目录”v2+ 模块中的包也将被找到并使用。请参阅 “如何发布 v2+ 模块” 部分,以详细了解“主要子目录”方法。

本常见问题解答的其余部分重点介绍了“主分支”方法,该方法在 “如何发布 v2+ 模块” 部分进行了描述。在“主分支”方法中,不会创建 /vN 子目录,而是通过 go.mod 文件和对提交应用语义化版本控制标签(通常在 master 分支上,但也可以在其他分支上)来传达模块版本信息。

为了帮助解决当前的过渡期,“最小模块兼容性”已 引入 到 Go 1.11 中,为尚未采用模块的 Go 代码提供更高的兼容性,并且“最小模块兼容性”也被移植回 Go 1.9.7 和 1.10.3(这些版本实际上始终禁用完全模块模式,因为这些较旧的 Go 版本没有完全的模块支持)。

“最小模块兼容性”的主要目标是

  1. 允许较旧的 Go 版本 1.9.7+ 和 1.10.3+ 更轻松地编译使用语义化导入版本控制(在导入路径中使用 /vN)的模块,并在 模块模式 在 Go 1.11 中被禁用时提供相同的行为。

  2. 允许旧代码能够使用 v2+ 模块,而无需立即更改旧的使用者代码以使用新的 /vN 导入路径来使用 v2+ 模块。

  3. 这样做无需依赖于模块作者创建 /vN 子目录。

其他细节 - “最小模块兼容性”

“最小模块兼容性”仅在 go 工具的完全 模块模式 被禁用时生效,例如,如果在 Go 1.11 中设置了 GO111MODULE=off,或者正在使用 Go 版本 1.9.7+ 或 1.10.3+。

当 v2+ 模块作者创建 /v2/vN 子目录,并且您依赖于 Go 1.9.7+、1.10.3+ 和 1.11 中的“最小模块兼容性”机制时

如果我创建了 go.mod 但没有在我的存储库中应用 semver 标签会发生什么?

语义化版本控制 是模块系统的一个基础。为了为使用者提供最佳体验,鼓励模块作者应用语义化版本控制 VCS 标签(例如,v0.1.0v1.2.3-rc.1),但语义化版本控制 VCS 标签不是严格要求的。

  1. 模块必须遵循语义化版本控制规范,以便 go 命令能够按文档的方式执行。这包括遵循语义化版本控制规范,该规范涉及如何以及何时允许进行重大更改。

  2. 没有语义化版本控制VCS 标签的模块由使用者使用语义化版本控制版本(以 伪版本 的形式)进行记录。通常这将是 v0 主版本,除非模块作者遵循 “主子目录” 方法构建了 v2+ 模块。

  3. 因此,没有应用语义化版本控制 VCS 标签并且没有创建“主子目录”的模块实际上是在声明自己处于语义化版本控制 v0 主版本系列中,并且基于模块的使用者将认为它们具有语义化版本控制 v0 主版本。

模块可以依赖于其自身的不同版本吗?

模块可以依赖于其自己的不同主版本:总的来说,这类似于依赖于不同的模块。这出于不同的原因可能有用,包括允许模块的主版本作为围绕不同主版本的垫片来实现。

此外,模块可以循环依赖于其自己的不同主版本,就像两个完全不同的模块可以循环依赖于彼此一样。

但是,如果您不希望模块依赖于其自己的不同版本,则这可能表明存在错误。例如,打算从 v3 模块导入包的 .go 代码可能缺少导入语句中所需的 /v3。该错误可能会表现为 v3 模块依赖于其自身的 v1 版本。

如果您对看到模块依赖于其自身的不同版本感到惊讶,则值得回顾一下上面关于 “语义化导入版本控制” 的部分以及常见问题解答中关于 “如果我没有看到依赖项的预期版本,我可以检查什么?” 的部分。

仍然存在一个限制,即两个不能循环依赖于彼此。

常见问题解答 - 多模块存储库

什么是多模块存储库?

多模块存储库是一个包含多个模块的存储库,每个模块都有自己的 go.mod 文件。每个模块从包含其 go.mod 文件的目录开始,并包含该目录及其所有子目录中的所有包,递归地包含,排除包含另一个 go.mod 文件的任何子树。

每个模块都有自己的版本信息。存储库根目录以下模块的版本标签必须包含相对目录作为前缀。例如,考虑以下存储库

my-repo
`-- foo
    `-- rop
        `-- go.mod

模块“my-repo/foo/rop”的版本 1.2.3 的标签是“foo/rop/v1.2.3”。

通常,存储库中一个模块的路径将是其他模块路径的前缀。例如,考虑此存储库

my-repo
|-- bar
|-- foo
|   |-- rop
|   `-- yut
|-- go.mod
`-- mig
    |-- go.mod
    `-- vub

Fig. A top-level module’s path is a prefix of another module’s path.

图 A 顶层模块的路径是另一个模块路径的前缀。

此存储库包含两个模块。但是,模块“my-repo”是模块“my-repo/mig”路径的前缀。

我应该在一个存储库中拥有多个模块吗?

在此配置中添加模块、删除模块以及对模块进行版本控制需要仔细考虑和权衡,因此,与在现有存储库中管理多个模块相比,管理单个模块存储库几乎总是更容易和更简单。

Russ Cox 在 #26664 中评论道

除了高级用户之外,您可能希望采用通常的约定,即一个存储库 = 一个模块。对于代码存储选项的长期演变来说,一个存储库可以包含多个模块很重要,但几乎可以肯定这不是您默认情况下想要做的。

多模块可能更复杂的一些例子

但是,除了这两个例子之外,还有更多细微之处。如果您正在考虑在单个存储库中使用多个模块,请仔细阅读本 小节 中的常见问题解答。

在单个存储库中使用多个 go.mod 文件可能合理的两项示例场景

  1. 如果你有使用示例,而这些示例本身具有复杂的依赖关系(例如,你可能有一个小型包,但包含一个使用你的包与 Kubernetes 的示例)。在这种情况下,你的仓库可以包含一个名为 example_example 的目录,并具有自己的 go.mod 文件,例如 这里 所示。

  2. 如果你有一个具有复杂依赖关系的仓库,但你有一个客户端 API,它具有较小的依赖关系集。在某些情况下,可能需要在仓库中创建一个名为 apiclientapi 类似的目录,并为其提供自己的 go.mod 文件,或者将 clientapi 分离到单独的仓库中。

但是,对于这两种情况,如果你正在考虑为大量间接依赖关系创建多模块仓库以提高性能或减小下载大小,我们强烈建议你首先尝试使用 GOPROXY,GOPROXY 在 Go 1.13 中默认启用。使用 GOPROXY 可以获得与创建多模块仓库相同的性能优势或依赖关系下载大小优势。

是否可以在多模块存储库中添加模块?

是的。但是,这个问题有两类。

第一类:要添加模块的包尚未纳入版本控制(新的包)。这种情况很简单:在同一个提交中添加包和 go.mod,标记提交,并推送。

第二类:添加模块的路径已纳入版本控制,并且包含一个或多个现有包。这种情况需要非常小心。为了说明这一点,我们再次考虑以下仓库(现在位于 github.com 位置,以便更好地模拟真实情况)。

github.com/my-repo
|-- bar
|-- foo
|   |-- rop
|   `-- yut
|-- go.mod
`-- mig
    `-- vub

考虑添加模块 “github.com/my-repo/mig”。如果按照上述方法,包 /my-repo/mig 可以由两个不同的模块提供:旧版本的 “github.com/my-repo” 和新的独立模块 “github.com/my-repo/mig”。如果这两个模块都处于活动状态,导入 “github.com/my-repo/mig” 将在编译时导致 “不明确的导入” 错误。

解决此问题的方法是使新添加的模块依赖于其“分离”的模块,并且依赖于其“分离”后的版本。

让我们以上面的仓库为例,假设 “github.com/my-repo” 当前版本为 v1.2.3。

  1. 添加 github.com/my-repo/mig/go.mod

    cd path-to/github.com/my-repo/mig
    go mod init github.com/my-repo/mig
    
    # Note: if "my-repo/mig" does not actually depend on "my-repo", add a blank
    # import.
    # Note: version must be at or after the carve-out.
    go mod edit -require github.com/[email protected]
    
  2. git commit

  3. git tag v1.3.0

  4. git tag mig/v1.0.0

  5. 接下来,让我们测试这些。我们不能简单地 go buildgo test,因为 go 命令会尝试从模块缓存中获取每个依赖模块。因此,我们需要使用替换规则,以使 go 命令使用本地副本。

    cd path-to/github.com/my-repo/mig
    go mod edit -replace github.com/[email protected]=../
    go test ./...
    go mod edit -dropreplace github.com/[email protected]
    
  6. git push origin master v1.3.0 mig/v1.0.0 推送提交和两个标签。

请注意,将来 golang.org/issue/28835 应该使测试步骤更加直接。

还要注意,模块 “github.com/my-repo” 在次要版本之间已删除代码。在某些情况下,不将其视为重大更改可能很奇怪,但在这种情况下,传递依赖关系继续提供其原始导入路径下已删除包的兼容实现。

是否可以从多模块存储库中删除模块?

是的,有相同的情况和类似的步骤。

模块可以依赖于另一个模块中的内部/吗?

是的。一个模块中的包可以导入另一个模块中的内部包,只要它们在内部/路径组件之前共享相同的路径前缀。例如,考虑以下仓库。

my-repo
|-- foo
|   `-- go.mod
|-- go.mod
`-- internal

在这里,包 foo 可以导入 /my-repo/internal,只要模块 “my-repo/foo” 依赖于模块 “my-repo”。同样,在以下仓库中

my-repo
|-- foo
|   `-- go.mod
`-- internal
    `-- go.mod

在这里,包 foo 可以导入 my-repo/internal,只要模块 “my-repo/foo” 依赖于模块 “my-repo/internal”。语义在两者中相同:由于 my-repo 是 my-repo/internal 和 my-repo/foo 之间的共享路径前缀,因此包 foo 允许导入包 internal。

额外的 go.mod 是否可以排除不必要的内容?模块是否有类似 .gitignore 文件的内容?

在单个仓库中使用多个 go.mod 文件的另一个用例是,如果仓库包含应从模块中修剪的文件。例如,仓库可能包含对 Go 模块不必要的大文件,或者多语言仓库可能包含许多非 Go 文件。

目录中的空 go.mod 文件将导致该目录及其所有子目录从顶层 Go 模块中排除。

如果排除的目录不包含任何 .go 文件,除了放置空 go.mod 文件之外,无需其他步骤。如果排除的目录包含 .go 文件,请首先仔细查看 此多模块仓库部分 中的其他常见问题解答。

常见问题解答 - 最小版本选择

最小版本选择会阻止开发人员获得重要更新吗?

请参见早期 官方提案讨论中的常见问题解答 中的问题“最小版本选择是否会阻止开发人员获得重要更新?”。

常见问题解答 - 可能出现的问题

如果我发现问题,我可以检查哪些一般性事项?

你当前正在检查的错误可能是由于构建中没有特定模块或包的预期版本而导致的次要问题。因此,如果特定错误的原因不明确,检查版本(如下一条常见问题解答中所述)可能会有所帮助。

如果我没有看到预期的依赖版本,我可以检查什么?

  1. 第一步是运行 go mod tidy。这可能会解决问题,但它也有助于将 go.mod 文件与 .go 源代码保持一致,这将使后续调查更容易。(如果 go mod tidy 本身以你没有预料到的方式更改了依赖项的版本,请首先阅读 关于 ‘go mod tidy’ 的常见问题解答。如果这无法解释,你可以尝试重置 go.mod,然后运行 go list -mod=readonly all,这可能会提供关于需要更改其版本的具体消息)。

  2. 第二步通常应该是检查 go list -m all 以查看为你的构建选择的实际版本列表。go list -m all 会显示最终选择的版本,包括间接依赖项,以及解决任何共享依赖项的版本后的版本。它还会显示任何 replaceexclude 指令的结果。

  3. 下一步可能是检查 go mod graphgo mod graph | grep <module-of-interest> 的输出。go mod graph 打印模块需求图(包括考虑替换)。输出中的每行都有两个字段:第一列是消费模块,第二列是该模块的一个需求(包括该消费模块所需版本)。这可以快速了解哪些模块需要特定依赖项,包括在你的构建中,当你的构建具有来自不同消费者的不同所需版本的依赖项时(如果出现这种情况,重要的是要熟悉 “版本选择” 部分中描述的行为)。

go mod why -m <module> 在这里也很有用,尽管它通常更适合了解为什么包含依赖项(而不是为什么依赖项最终使用特定版本)。

go list 提供了许多其他查询变体,如果需要,这些变体可以用来查询模块。例如,以下命令将显示构建中使用的确切版本,不包括仅限测试的依赖项。

go list -deps -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' ./... | sort -u

可以在可运行的“Go 模块示例” 演练 中看到一组更详细的命令和示例,用于查询模块。

意外版本的可能原因之一是,有人创建了不必要的无效或意外的 go.mod 文件,或者发生了相关的错误(例如:模块的 v2.0.1 版本可能错误地将其自身声明为 module foo,而没有所需的 /v2.go 代码中的导入语句打算导入 v3 模块,但可能缺少所需的 /v3go.mod 中针对 v4 模块的 require 语句可能缺少所需的 /v4)。因此,如果你看到的特定问题的根源不明确,最好先重新阅读上面的 “go.mod”“语义导入版本控制” 部分中的内容(因为它们包含模块必须遵循的重要规则),然后花几分钟时间检查最相关的 go.mod 文件和导入语句。

为什么我收到错误“无法找到提供包 foo 的模块”?

这是一个常见的错误消息,它可能是由于多个不同的根本原因导致的。

在某些情况下,此错误仅仅是由于路径输入错误导致的,因此第一步应该是根据错误消息中列出的详细信息双重检查路径是否正确。

如果你还没有这样做,下一步通常是尝试 go get -v foogo get -v -x foo

一些其他可能的原因

为什么“go mod init” 会出现错误“无法确定源目录的模块路径”?

go mod init 不带任何参数将尝试根据不同的提示(例如 VCS 元数据)猜测正确的模块路径。但是,不能保证 `go mod init` 始终能够猜测正确的模块路径。

如果 `go mod init` 给您此错误,则这些启发式方法无法进行猜测,您必须自己提供模块路径(例如 `go mod init github.com/you/hello`)。

我遇到一个复杂依赖的问题,该依赖尚未加入模块。我能否使用其当前依赖管理器的信息?

是的。这需要一些手动步骤,但在某些更复杂的情况下可能会有所帮助。

当您在初始化自己的模块时运行 `go mod init` 时,它将自动从以前的依赖项管理器转换为 `go.mod` 文件,并将配置文件(如 `Gopkg.lock`、`glide.lock` 或 `vendor.json`)转换为包含相应 `require` 指令的 `go.mod` 文件。例如,预先存在的 `Gopkg.lock` 文件中的信息通常描述了所有直接和间接依赖项的版本信息。

但是,如果您要添加一个尚未加入模块的新依赖项,则没有类似的自动转换过程可以从新依赖项可能使用的任何先前的依赖项管理器中进行转换。如果该新依赖项本身具有非模块依赖项,并且这些依赖项已经发生了重大更改,则在某些情况下会导致不兼容性问题。换句话说,不会自动使用新依赖项的先前依赖项管理器,这可能会在某些情况下导致间接依赖项出现问题。

一种方法是在有问题的非模块直接依赖项上运行 `go mod init` 以从其当前依赖项管理器转换,然后使用生成的临时 `go.mod` 中的 `require` 指令来填充或更新模块中的 `go.mod`。

例如,如果 `github.com/some/nonmodule` 是您模块的一个有问题的直接依赖项,它目前正在使用另一个依赖项管理器,您可以执行类似于以下操作:

$ git clone -b v1.2.3 https://github.com/some/nonmodule /tmp/scratchpad/nonmodule
$ cd /tmp/scratchpad/nonmodule
$ go mod init
$ cat go.mod

可以将临时 `go.mod` 中生成的 `require` 信息手动移到您模块的实际 `go.mod` 中,或者您可以考虑使用 https://github.com/rogpeppe/gomodmerge,它是一个针对此用例的社区工具。此外,您需要将 `require github.com/some/nonmodule v1.2.3` 添加到您实际的 `go.mod` 中,以匹配您手动克隆的版本。

此技术在 docker 中的具体示例可以在 #28489 评论 中找到,它说明了如何获取一致的 docker 依赖项版本集,以避免 `github.com/sirupsen/logrus` 与 `github.com/Sirupsen/logrus` 之间的区分大小写问题。

如何解决由导入路径与声明的模块标识不匹配引起的“解析 go.mod:意外的模块路径”和“加载模块需求错误”错误?

为什么会出现此错误?

通常,模块通过 `go.mod` 中的 `module` 指令(例如 `module example.com/m`)声明其标识。这是该模块的“模块路径”,`go` 工具会强制执行该声明的模块路径与任何使用者使用的导入路径之间的一致性。如果模块的 `go.mod` 文件读取 `module example.com/m`,则使用者必须使用以该模块路径开头的导入路径从该模块导入包(例如,`import "example.com/m"` 或 `import "example.com/m/sub/pkg"`)。

如果使用者使用的导入路径与相应的声明的模块路径不匹配,`go` 命令将报告 `parsing go.mod: unexpected module path` 严重错误。此外,在某些情况下,`go` 命令随后将报告更通用的 `error loading module requirements` 错误。

此错误最常见的原因是,如果存在名称更改(例如,`github.com/Sirupsen/logrus` 到 `github.com/sirupsen/logrus`),或者模块在模块之前有时通过两个不同的名称使用(例如,`github.com/golang/sync` 与推荐的 `golang.org/x/sync`)。

如果您的依赖项仍通过旧名称(例如,`github.com/Sirupsen/logrus`)或非规范名称(例如,`github.com/golang/sync`)导入,但该依赖项随后已采用模块,并在其 `go.mod` 中声明了其规范名称,则会导致问题。此错误随后可能在升级期间触发,此时会发现模块的升级版本声明了不再匹配旧导入路径的规范模块路径。

示例问题场景

go: github.com/Quasilyte/[email protected]: parsing go.mod: unexpected module path “github.com/quasilyte/go-consistent” go get: error loading module requirements

解决

此错误最常见的形式是

go: example.com/some/OLD/[email protected]: parsing go.mod: unexpected module path “example.com/some/NEW/name”

如果您访问 `example.com/some/NEW/name`(来自错误的右侧)的存储库,您可以检查最新版本或 `master` 的 `go.mod` 文件,以查看它是否在其 `go.mod` 的第一行声明为 `module example.com/some/NEW/name`。如果是,则表明您遇到了“旧模块名称”与“新模块名称”的问题。

本节的其余部分重点介绍如何通过按顺序执行以下步骤来解决此错误的“旧名称”与“新名称”形式

  1. 检查您自己的代码,以查看您是否正在使用 `example.com/some/OLD/name` 导入。如果是,请将您的代码更新为使用 `example.com/some/NEW/name` 导入。

  2. 如果您在升级期间收到此错误,您应该尝试使用 Go 的 tip 版本进行升级,它具有更针对性的升级逻辑 (#26902),它通常可以避开此问题,并且通常还会为此情况提供更好的错误消息。请注意,tip/1.13 中的 `go get` 参数与 1.12 中的不同。获取 tip 并使用它来升级您的依赖项的示例

go get golang.org/dl/gotip && gotip download
gotip get -u all
gotip mod tidy

由于有问题的旧导入通常位于间接依赖项中,因此使用 tip 进行升级,然后运行 `go mod tidy` 通常可以将您升级到有问题的版本之后,并从您的 `go.mod` 中删除不再需要的有问题的版本,从而使您在返回使用 Go 1.12 或 1.11 进行日常使用时处于正常状态。例如,请参阅 此处,了解如何使用此方法升级到 `github.com/golang/lint` 与 `golang.org/x/lint` 问题之外。

  1. 如果您在执行 `go get -u foo` 或 `go get -u foo@latest` 时收到此错误,请尝试删除 `-u`。这将为您提供 `foo@latest` 使用的依赖项集,而不会将 `foo` 的依赖项升级到 `foo` 作者在发布 `foo` 时可能已验证为可用的版本。这在过渡时期尤其重要,因为 `foo` 的某些直接和间接依赖项可能尚未采用 semver 或模块。(一个常见的错误是认为 `go get -u foo` 仅获取 `foo` 的最新版本。实际上,`go get -u foo` 或 `go get -u foo@latest` 中的 `-u` 表示还要获取 `foo` 的所有直接和间接依赖项的最新版本;这可能是您想要的,但如果它因深层间接依赖项而失败,则可能不是。)

  2. 如果上述步骤未能解决错误,则下一种方法稍微复杂一些,但通常应该能够解决此错误的“旧名称”与“新名称”形式。这仅使用来自错误消息本身的信息,以及对一些 VCS 历史记录的简要查看。

    4.1. 转到 `example.com/some/NEW/name` 存储库

    4.2. 确定何时在其中引入了 `go.mod` 文件(例如,通过查看 `go.mod` 的 blame 或历史记录视图)。

    4.3. 选择正好在 `go.mod` 文件在其中引入之前版本或提交。

    4.4. 在您的 `go.mod` 文件中,添加一个使用旧名称的 `replace` 语句,该语句位于 `replace` 语句的两侧:`replace example.com/some/OLD/name => example.com/some/OLD/name <version-just-before-go.mod>` 使用我们之前的示例,其中 `github.com/Quasilyte/go-consistent` 是旧名称,而 `github.com/quasilyte/go-consistent` 是新名称,我们可以看到,`go.mod` 首次在提交 00c5b0cf371a 中引入。该存储库未使用 semver 标记,因此我们将使用紧邻之前的提交 00dd7fb039e,并将其添加到使用旧的大写 Quasilyte 名称的替换中,该名称位于 `replace` 的两侧

replace github.com/Quasilyte/go-consistent => github.com/Quasilyte/go-consistent 00dd7fb039e

然后,此 `replace` 语句使我们能够通过有效地阻止在存在 `go.mod` 的情况下将旧名称升级到新名称,来升级到有问题的“旧名称”与“新名称”不匹配问题之外。通常,通过 `go get -u` 或类似方式进行升级现在可以避免错误。如果升级完成,您可以检查是否有任何人仍在导入旧名称(例如,`go mod graph | grep github.com/Quasilyte/go-consistent`),如果没有,则可以删除 `replace`。(之所以这样做通常有效,是因为如果使用了有问题的旧导入路径,即使在升级完成时最终结果中可能不会使用该路径,升级本身也可能失败,这在 #30831 中进行了跟踪)。

  1. 如果以上步骤没有解决问题,可能是因为最新的一个或多个依赖项仍在使用有问题的旧导入路径。在这种情况下,重要的是要确定谁仍在使用有问题的旧导入路径,并找到或打开一个问题,要求有问题的导入者更改为使用现在规范的导入路径。使用步骤 2 中的gotip可能会识别出有问题的导入者,但并非在所有情况下都能做到,尤其是在升级时 (#30661)。如果不清楚谁在使用有问题的旧导入路径进行导入,通常可以通过创建一个干净的模块缓存,执行触发错误的操作,然后在模块缓存中搜索有问题的旧导入路径来找出。例如
export GOPATH=$(mktemp -d)
go get -u foo               # perform operation that generates the error of interest
cd $GOPATH/pkg/mod
grep -R --include="*.go" github.com/Quasilyte/go-consistent
  1. 如果这些步骤不足以解决问题,或者如果您是项目的维护者,并且由于循环引用而无法删除对旧的、有问题的导入路径的引用,请参阅关于此问题的更详细的说明,该说明位于单独的wiki 页面上。

最后,以上步骤重点关注如何解决底层的“旧名称”与“新名称”问题。但是,如果go.mod放在了错误的位置,或者仅仅是模块路径错误,也会出现相同的错误消息。如果是这种情况,导入该模块将始终失败。如果您正在导入一个新创建的模块,并且该模块以前从未成功导入过,则应检查go.mod文件是否位于正确的位置,以及它是否具有与该位置相对应的正确模块路径。(最常见的方法是每个存储库一个go.mod,将单个go.mod文件放在存储库根目录中,并在module指令中使用存储库名称作为模块路径)。有关更多详细信息,请参阅“go.mod” 部分。

为什么“go build” 需要 gcc,为什么不使用预构建的包,例如 net/http?

简而言之

因为预构建的包是非模块构建的,无法重复使用。抱歉。现在禁用 cgo 或安装 gcc。

这只有在选择加入模块时才会出现问题(例如,通过GO111MODULE=on)。有关更多讨论,请参阅#26988

模块是否支持像 import "./subdir" 这样的相对导入?

不。请参阅#26645,其中包括

在模块中,子目录终于有了名称。如果父目录是“module m”,那么子目录将被导入为“m/subdir”,不再是“./subdir”。

填充的 vendor 目录中可能缺少一些必要的文件。

go mod vendor不会将没有.go文件的目录复制到vendor目录中。这是设计使然。

简而言之,撇开任何特定的 vendoring 行为不谈,go 构建的总体模型是,构建包所需的 文件应该放在包含.go文件的目录中。

以 cgo 为例,修改其他目录中的 C 源代码不会触发重新构建,而是会使用过时的缓存条目。cgo 文档现在包含

请注意,对其他目录中文件的更改不会导致包重新编译,因此所有包的非 Go 源代码都应存储在包目录中,而不是在子目录中。

社区工具https://github.com/goware/modvendor 允许您轻松地将来自模块的完整.c.h.s.proto 或其他文件集复制到vendor目录。虽然这可能很有用,但必须注意确保您的 go 构建在一般情况下(无论是否使用 vendoring)都能正常处理,如果您有构建包所需的文件位于包含.go文件的目录之外。

请参阅#26366 中的其他讨论。

传统 vendoring 的另一种方法是签入模块缓存。它最终可以实现与传统 vendoring 相似的优势,并且在某些方面最终能够获得更高保真度的副本。这种方法在“Go 模块示例”演练中进行了说明。


此内容是Go Wiki 的一部分。