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 发行说明。
- 模块模式 (
GO111MODULE=on
) 在所有情况下都是默认模式 - 命令不再默认修改
go.mod
/go.sum
(-mod=readonly
) go install pkg@version
是全局安装包/可执行文件的推荐方法retract
可用于go.mod
中
Go 1.15
有关详细信息,请参阅 Go 1.15 发行说明。
- 模块缓存的位置现在可以使用
GOMODCACHE
环境变量设置。GOMODCACHE
的默认值为GOPATH[0]/pkg/mod
,即更改前模块缓存的位置。 - 现在可以为 Windows 中访问模块缓存的 go 命令的“访问被拒绝”错误提供解决方法,该错误是由外部程序同时扫描文件系统引起的(请参阅问题 #36568)。该解决方法默认情况下未启用,因为当低于 1.14.2 和 1.13.10 版本的 Go 与同一个模块缓存同时运行时,使用该方法并不安全。可以通过显式设置环境变量
GODEBUG=modcacheunzipinplace=1
来启用该方法。
Go 1.14
有关详细信息,请参阅 Go 1.14 发行说明。
- 当主模块包含一个顶级 vendor 目录,并且其
go.mod 文件
指定了go 1.14
或更高版本时,go 命令现在默认为-mod=vendor
,用于接受该标志的操作。 - 当
go.mod
文件为只读且没有顶级 vendor 目录时,现在默认情况下会设置-mod=readonly
。 -modcacherw
是一个新标志,它指示 go 命令将模块缓存中新创建的目录保留为默认权限,而不是将其设置为只读。-modfile=file
是一个新标志,它指示 go 命令读取(并可能写入)一个备用go.mod
文件,而不是模块根目录中的文件。- 当显式启用模块感知模式(通过设置
GO111MODULE=on
)时,如果不存在go.mod
文件,大多数模块命令的功能将更加有限。 - go 命令现在支持模块模式下的 Subversion 存储库。
Go 1.13
有关详细信息,请参阅 Go 1.13 发行说明。
go
工具现在默认从 https://proxy.golang.org 公共 Go 模块镜像下载模块,并且还默认根据 https://sum.golang.org 公共 Go 校验和数据库验证下载的模块(无论来源)。- 如果您有私有代码,您可能需要配置
GOPRIVATE
设置(例如go env -w GOPRIVATE=*.corp.com,github.com/secret/repo
),或者更细粒度的GONOPROXY
或GONOSUMDB
变体,这些变体支持不太频繁的用例。有关详细信息,请参阅 文档。
- 如果您有私有代码,您可能需要配置
GO111MODULE=auto
如果发现任何 go.mod,即使是在 GOPATH 内部,也会启用模块模式。(在 Go 1.13 之前,GO111MODULE=auto
绝不会在 GOPATH 内部启用模块模式)。go get
参数已更改go get -u
(不带任何参数)现在只升级当前包的直接和间接依赖项,不再检查您的整个模块。- 从模块根目录运行
go get -u ./...
会升级模块的所有直接和间接依赖项,现在不包括测试依赖项。 go get -u -t ./...
类似,但也会升级测试依赖项。go get
不再支持-m
(因为它与go get -d
由于其他更改而大量重叠;您通常可以将go get -m foo
替换为go get -d foo
)。
目录
“快速入门”和“新概念”部分对于刚开始使用模块的人来说尤其重要。“如何…”部分涵盖了有关机制的更多详细信息。此页面上大部分内容都在 FAQ 中,回答了更多具体的问题;至少浏览一下此处列出的 FAQ 简短答案是有意义的。
- 快速入门
- 新概念
- 如何使用模块
- 迁移到模块
- 其他资源
- 自最初的 Vgo 提案以来的更改
- GitHub 问题
- 常见问题解答
- 常见问题解答 - 附加控制
- 常见问题解答 - go.mod 和 go.sum
- 常见问题解答 - 语义导入版本控制
- 常见问题解答 - 多模块存储库
- 常见问题解答 - 最小版本选择
- 常见问题解答 - 可能出现的问题
- 如果我发现问题,我可以检查哪些一般性事项?
- 如果我没有看到预期的依赖版本,我可以检查什么?
- 为什么我收到错误“无法找到提供包 foo 的模块”?
- 为什么“go mod init” 会出现错误“无法确定源目录的模块路径”?
- 我遇到一个复杂依赖的问题,该依赖尚未加入模块。我能否使用其当前依赖管理器的信息?
- 如何解决由导入路径与声明的模块标识不匹配引起的“解析 go.mod:意外的模块路径”和“加载模块需求错误”错误?
- 为什么“go build” 需要 gcc,为什么不使用预构建的包,例如 net/http?
- 模块是否支持像
import "./subdir"
这样的相对导入? - 填充的 vendor 目录中可能缺少一些必要的文件。
快速入门
示例
本页的剩余部分涵盖了这些细节,但这里有一个从头开始创建模块的简单示例。
在你的 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 get
或 go mod tidy
。在 1.16 中,默认情况下,禁用对 go.mod
和 go.sum
文件的隐式修改。
你日常工作流程通常可以是
- 根据需要向你的
.go
代码中添加导入语句。 - 像
go build
或go test
这样的标准命令将自动添加新的依赖项以满足导入要求(更新go.mod
并下载新的依赖项)。 - 在需要时,可以使用
go get [email protected]
、go get foo@master
(对于 mercurial 使用foo@default
)、go get foo@e3702bed2
等命令,或直接编辑go.mod
来选择特定版本的依赖项。
简要介绍你可能使用的其他常见功能
go list -m all
— 查看在构建所有直接和间接依赖项时将使用的最终版本(详细信息)。go list -u -m all
— 查看所有直接和间接依赖项的可用次要和修补程序升级(详细信息)。go get -u ./...
或go get -u=patch ./...
(从模块根目录) — 将所有直接和间接依赖项更新到最新的次要或修补程序升级(忽略预发行版)(详细信息)。go build ./...
或go test ./...
(从模块根目录) — 构建或测试模块中的所有包(详细信息)。go mod tidy
— 从go.mod
中修剪所有不再需要的依赖项,并添加其他操作系统、体系结构和构建标签组合所需的任何依赖项(详细信息)。replace
指令或gohack
— 使用依赖项的分支、本地副本或确切版本(详细信息)。go mod vendor
— 可选步骤,用于创建vendor
目录(详细信息)。
阅读完接下来的四部分内容“新概念”后,你将拥有足够的知识来为大多数项目开始使用模块。查看上面的 目录(包括其中的 FAQ 一句话)也有助于你熟悉更详细主题的列表。
新概念
这些部分提供了对主要新概念的概述。有关更多详细信息和基本原理,请参阅 Russ Cox 在 这个 40 分钟的介绍性视频中描述的设计理念、官方提案文档或更详细的初始 vgo 博客系列。
模块
模块 是相关 Go 包的集合,这些包作为一个单元一起版本化。
模块记录精确的依赖项需求并创建可重复的构建。
大多数情况下,版本控制库中恰好包含一个模块,该模块在库根目录中定义。(单个库中支持多个模块,但通常这会导致比每个库一个模块更多持续的工作量)。
总结库、模块和包之间的关系
- 库包含一个或多个 Go 模块。
- 每个模块包含一个或多个 Go 包。
- 每个包由单个目录中的一个或多个 Go 源文件组成。
模块必须按照 semver 进行语义版本化,通常采用 v(major).(minor).(patch)
的形式,例如 v0.1.0
、v1.2.3
或 v1.5.0-rc.1
。需要在前面加上 v
。如果使用 Git,请使用 标记 来标记已发布的提交及其版本。公共和私有模块库和代理正在变得可用(请参阅下面的 FAQ 部分)。
go.mod
模块由一棵 Go 源文件树定义,该树的根目录中包含一个 go.mod
文件。模块源代码可以位于 GOPATH 之外。有四个指令:module
、require
、replace
和 exclude
。
以下是一个定义模块 github.com/my/thing
的 go.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/foo
和 github.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
。
exclude
和 replace
指令仅对当前(“主”)模块起作用。除了主模块以外的模块中的 exclude
和 replace
指令在构建主模块时会被忽略。因此,replace
和 exclude
语句允许主模块完全控制自己的构建,而不会受到依赖项的完全控制。(有关何时使用 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 模块的代码必须遵守这些规则
- 遵循 semver。(示例 VCS 标签为
v1.2.3
)。 - 如果模块的版本为 v2 或更高版本,则模块的主版本必须包含在
go.mod
文件中使用的模块路径末尾的/vN
中(例如module github.com/my/mod/v2
、require github.com/my/mod/v2 v2.0.1
)以及包的导入路径中(例如import "github.com/my/mod/v2/mypkg"
)。这包括go get
命令中使用的路径(例如go get github.com/my/mod/[email protected]
。请注意,在该示例中,既有/v2
又有@v2.0.1
。可以这样理解,模块名称现在包含/v2
,因此在使用模块名称时,始终包含/v2
)。 - 如果模块版本为 v0 或 v1,则在模块路径或导入路径中 *不要* 包含主版本。
通常,具有不同导入路径的包是不同的包。例如,math/rand
与 crypto/rand
是不同的包。即使不同的导入路径是由于导入路径中出现了不同的主版本而造成的,情况也是如此。因此,example.com/my/mod/mypkg
与 example.com/my/mod/v2/mypkg
是不同的包,并且两者都可以在单个构建中被导入,这除了其他好处之外还有助于解决钻石依赖问题,还允许 v1 模块根据其 v2 替代方案来实现,反之亦然。
有关语义导入版本控制的更多详细信息,请参阅 go
命令文档中的 “模块兼容性和语义版本控制” 部分,有关语义版本控制的更多信息,请参阅 https://semver.org。
到目前为止,本节重点介绍了已选择加入模块并导入其他模块的代码。但是,在 v2+ 模块的导入路径中放置主版本可能会导致与 Go 的旧版本或尚未选择加入模块的代码不兼容。为了帮助解决此问题,有三个重要的过渡性特殊情况或对上述行为和规则的例外。随着越来越多的包选择加入模块,这些过渡性例外将变得不那么重要。
三个过渡性例外
-
gopkg.in
使用以
gopkg.in
开头的导入路径(例如gopkg.in/yaml.v1
和gopkg.in/yaml.v2
)的现有代码,即使在选择加入模块后,也可以继续使用这些形式作为其模块路径和导入路径。 -
导入非模块 v2+ 包时为“+incompatible”
模块可以导入尚未选择加入模块本身的 v2+ 包。具有有效 v2+ semver 标签的非模块 v2+ 包将在导入模块的
go.mod
文件中记录带有+incompatible
后缀。+incompatible
后缀表示,即使 v2+ 包具有有效的 v2+ semver 标签(例如v2.0.0
),v2+ 包也尚未主动选择加入模块,因此假设该 v2+ 包 *未* 了解语义导入版本控制的含义以及如何在导入路径中使用主版本。因此,在 模块模式 下运行时,go
工具会将非模块 v2+ 包视为包的 v1 版本系列的(不兼容)扩展,并假设该包没有意识语义导入版本控制,而+incompatible
后缀表示go
工具正在这样做。 -
未启用模块模式时的“最小模块兼容性”
为了帮助实现向后兼容性,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 1.11 版本.
- 从源代码安装 Go 工具链 到
master
分支上。
安装完成后,您可以通过以下两种方式之一激活模块支持
- 在
$GOPATH/src
树之外的目录中调用go
命令,在当前目录或其任何父目录中具有有效的go.mod
文件,并且环境变量GO111MODULE
未设置(或显式设置为auto
)。 - 使用设置了
GO111MODULE=on
环境变量的go
命令调用。
如何定义模块
要为现有项目创建 go.mod
-
导航到 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
-
创建初始模块定义并将其写入
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 ./...
或类似操作,这是本节中显示的顺序)。 -
构建模块。当从模块的根目录执行时,
./...
模式与当前模块中的所有包匹配。go build
会自动添加必要的缺失或未转换的依赖项,以满足此特定构建调用的导入$ go build ./...
-
测试配置后的模块,以确保它与所选版本一起使用
$ go test ./...
-
(可选)运行模块的测试以及所有直接和间接依赖项的测试,以检查是否存在不兼容性
$ 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 -u ./...
使用最新的*次要或修补程序*版本(并添加-t
以同时升级测试依赖项)go get -u=patch ./...
使用最新的*修补程序*版本(并添加-t
以同时升级测试依赖项)
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 foo
或 go get -u foo@latest
中的 -u
表示要*同时*获取 foo
的所有直接和间接依赖项的最新版本。升级 foo
时的一个常见起点是改为执行 go get foo
或 go get foo@latest
,不带 -u
(并且在一切正常后,考虑 go get -u=patch foo
、go get -u=patch
、go get -u foo
或 go get -u
)。
要升级或降级到更具体的版本,“go get”允许通过向包参数添加 @version
后缀或 “模块查询” 来覆盖版本选择,例如 go get [email protected]
、go get foo@e3702bed2
或 go 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”工具 自动执行。
在标记发布之前要考虑的一些当前建议的最佳实践
-
运行
go mod tidy
以可能修剪任何无关的 requirement(如 此处 所述)并确保您当前的go.mod
反映所有可能的构建标签/OS/架构组合(如 此处 所述)。- 相比之下,其他命令(例如
go build
和go test
)不会从go.mod
中删除不再需要的依赖项,只会根据当前构建调用中的标签/操作系统/架构来更新go.mod
。
- 相比之下,其他命令(例如
-
运行
go test all
来测试你的模块(包括运行你的直接和间接依赖项的测试),作为验证当前选择的包版本是否兼容的一种方式。- 可能的版本组合数量是模块数量的指数级增长,因此,一般情况下,你不能指望你的依赖项已经针对所有可能的依赖项组合进行过测试。
- 作为模块工作的一部分,
go test all
已经重新定义为更有用:包括当前模块中的所有包以及它们通过一个或多个导入所依赖的所有包,同时排除当前模块中无关紧要的包。
-
确保你的
go.sum
文件与你的go.mod
文件一起提交。有关更多详细信息和原理,请参阅下面的常见问题解答。
发布模块(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
版本的示例,两种选择是
-
主分支:更新
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。
-
主子目录:创建一个新的
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 生态系统中的不同包以不同的速度选择加入。
- 已经使用 v2 或更高版本的包需要考虑更多的迁移问题,主要是因为语义导入版本控制的影响。
- 新包和使用 v0 或 v1 的包在采用模块时需要考虑的因素要少得多。
- 使用 Go 1.11 定义的模块可以使用旧版本的 Go(尽管确切的 Go 版本取决于主模块及其依赖项所使用的策略,如下所述)。
迁移主题
从先前的依赖管理工具自动迁移
go mod init
会自动将来自dep、glide、govendor、godep 和其他 5 个预先存在的依赖管理工具的必要信息转换为go.mod
文件,从而产生等效的构建。- 如果你要创建 v2+ 模块,请确保在转换后的
go.mod
中的module
指令包含适当的/vN
(例如,module foo/v3
)。 - 请注意,如果你要导入 v2+ 模块,你可能需要在初始转换后进行一些手动调整,以便在
go mod init
在从先前的依赖管理工具转换后生成require
语句时,为这些语句添加/vN
。有关更多详细信息,请参阅上面“如何定义模块”部分。 - 此外,
go mod init
不会编辑你的.go
代码以在导入语句中添加任何所需的/vN
。请参阅上面“语义导入版本控制”和“发布模块(v2 或更高版本)”部分,以了解所需步骤,包括一些关于社区工具的选项,这些工具可以帮助自动执行转换。
向旧版本的 Go 和非模块使用者提供依赖信息
- Go 的旧版本知道如何使用由 `go mod vendor` 创建的 vendor 目录,当模块模式被禁用时,Go 1.11 和 1.12+ 也知道如何使用。因此,vendoring 是一种方法,可以让模块为不支持模块的 Go 旧版本提供依赖项,以及为未启用模块的消费者提供依赖项。有关更多详细信息,请参阅 vendoring 常见问题解答 和 `go` 命令的 文档。
更新预先存在的安装说明
- 在模块出现之前,安装说明通常会包含 `go get -u foo`。如果您发布了一个模块 `foo`,请考虑在针对基于模块的消费者的说明中删除 `-u`。
- `-u` 会要求 `go` 工具升级 `foo` 的所有直接和间接依赖项。
- 模块消费者可以选择稍后运行 `go get -u foo`,但如果 `-u` 不是初始安装说明的一部分,则 “高保真构建” 具有更多优势。有关更多详细信息,请参阅 “如何升级和降级依赖项”。
- `go get -u foo` 仍然有效,对于安装说明来说仍然是一个有效的选择。
- 此外,对于基于模块的消费者来说,`go get foo` 不是必需的。
- 只需添加一个导入语句 `import "foo"` 就足够了。(随后的命令,如 `go build` 或 `go test` 会自动下载 `foo` 并根据需要更新 `go.mod`)。
- 基于模块的消费者默认情况下不会使用 `vendor` 目录。
- 当 `go` 工具中启用了模块模式时,在使用模块时,`vendor` 不是必需的(因为 `go.mod` 中包含的信息和 `go.sum` 中的加密校验和),但一些预先存在的安装说明假设 `go` 工具默认情况下会使用 `vendor`。有关更多详细信息,请参阅 vendoring 常见问题解答。
- 包含 `go get foo/...` 的安装说明在某些情况下可能会出现问题(请参阅 #27215 中的讨论)。
避免破坏现有的导入路径
模块在 `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.0.0 或更高版本的标记,那么建议的做法是在首次采用模块时递增主版本号。例如,如果您当前处于 `v2.0.1` 版本并且尚未采用模块,那么对于第一个采用模块的版本,您应该使用 `v3.0.0`。有关更多详细信息,请参阅上面的 “发布模块(v2 或更高版本)” 部分。
v2+ 模块允许在单个构建中使用多个主版本
- 如果一个模块处于 v2 或更高版本,则意味着单个构建中可以存在多个主版本(例如,`foo` 和 `foo/v3` 可能最终出现在单个构建中)。
- 这自然地遵循了“具有不同导入路径的包是不同的包”这一规则。
- 当这种情况发生时,将会有多个包级别状态的副本(例如,`foo` 的包级别状态和 `foo/v3` 的包级别状态),并且每个主版本都会运行自己的 `init` 函数。
- 这种方法有助于模块系统的多个方面,包括帮助解决菱形依赖问题,在大型代码库中逐步迁移到新版本,以及允许将主版本实现为围绕不同主版本的 shim。
- 有关一些相关讨论,请参阅 https://research.swtch.com/vgo-import 或 #27514 中的“避免单例问题”部分。
使用非模块代码的模块
- 模块能够使用尚未加入模块的包,并且相应的包版本信息记录在导入模块的 `go.mod` 中。模块可以使用尚未有任何合适的语义版本标记的包。有关更多详细信息,请参阅下面的 FAQ 部分。
- 模块还可以导入尚未加入模块的 v2+ 包。如果导入的 v2+ 包具有有效的语义版本标记,则它将被记录为 `+incompatible` 后缀。有关更多详细信息,请参阅下面的 FAQ 部分。
使用模块的非模块代码
-
使用 v0 和 v1 模块的非模块代码:
- 尚未加入模块的代码可以使用和构建 v0 和 v1 模块(无需与使用的 Go 版本相关联)。
-
使用 v2+ 模块的非模块代码:
-
Go 版本 1.9.7+、1.10.3+ 和 1.11 已更新,因此使用这些版本构建的代码可以正确使用 v2+ 模块,而无需对预先存在的代码进行修改,如上面 “语义导入版本控制” 和 “发布模块(v2 或更高版本)” 部分所述。
-
如果 v2+ 模块是按照上面 “发布模块(v2 或更高版本)” 部分中概述的“主要子目录”方法创建的,则 1.9.7 和 1.10.3 之前的 Go 版本可以使用 v2+ 模块。
-
预先存在的 v2+ 包作者的策略
对于正在考虑加入模块的预先存在的 v2+ 包的作者来说,总结替代方法的一种方法是选择三种顶级策略之一。每个选择都将有后续的决定和变体(如上面所述)。这些替代的顶级策略是
-
要求客户端使用 Go 版本 1.9.7+、1.10.3+ 或 1.11+.
这种方法使用“主分支”方法,并依赖于回溯到 1.9.7 和 1.10.3 的“最小模块感知”。有关更多详细信息,请参阅上面 “语义导入版本控制” 和 “发布模块(v2 或更高版本)” 部分。
-
允许客户端使用更旧的 Go 版本,例如 Go 1.8.
这种方法使用“主子目录”方法,并涉及创建子目录,例如 `v2` 或 `v3`。有关更多详细信息,请参阅上面 “语义导入版本控制” 和 “发布模块(v2 或更高版本)” 部分。
-
等待加入模块.
在这种策略中,无论客户端代码是否加入模块,它们都能够正常工作。随着时间的推移,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,那么现在就可以进行)。
其他资源
文档和提案
- 官方文档
- golang.org 上关于模块的最新 HTML 文档
- 运行 `go help modules` 以了解更多关于模块的信息。(这是通过 `go help` 获取模块主题的主要入口点)
- 运行 `go help mod` 以了解更多关于 `go mod` 命令的信息。
- 运行 `go help module-get` 以了解更多关于模块感知模式下 `go get` 的行为。
- 运行 `go help goproxy` 以了解更多关于模块代理的信息,包括通过 `file:///` URL 实现的纯文件式选项。
- Russ Cox 的初始 “Go & 版本控制” 博客文章系列(首次发布于 2018 年 2 月 20 日)
- golang.org 上介绍该提案的官方 博客文章(2018 年 3 月 26 日)
- 这提供了一个比完整的 `vgo` 博客文章系列更简洁的提案概述,以及一些关于该提案的历史和过程。
- 官方 版本化 Go 模块提案(最后更新于 2018 年 3 月 20 日)
入门材料
- Russ Cox 在 GopherCon Singapore 上的 40 分钟入门视频 “Go 版本控制的原则”(2018 年 5 月 2 日)
- 简洁地涵盖了版本化 Go 模块设计背后的理念,包括“兼容性”、“可重复性”和“协作”三个核心原则。
- 基于示例的 35 分钟入门视频 “什么是 Go 模块,我该如何使用它们?” (幻灯片),由 Paul Jolly 发表(2018 年 8 月 15 日)
- 入门博客文章 “试用 Go 模块”,由 Dave Cheney 发表(2018 年 7 月 14 日)
- 介绍性 Go Meetup 关于模块的幻灯片,由 Chris Hines 撰写(2018 年 7 月 16 日)
- 介绍性 30 分钟视频 “Go 模块和 SemVer 简介”,由 Francesc Campoy 撰写(2018 年 11 月 15 日)
补充资料
- 博客文章 “在 Travis CI 上使用 Go 模块和 vendor 支持”,由 Fatih Arslan 撰写(2018 年 8 月 26 日)
- 博客文章 “Go 模块和 CircleCI”,由 Todd Keech 撰写(2018 年 7 月 30 日)
- 博客文章 “vgo 提案已获批准。接下来呢?”,由 Russ Cox 撰写(2018 年 5 月 29 日)
- 包括关于版本化模块目前是实验性选择加入功能的摘要
- 博客文章,介绍 如何从 tip 构建 Go 并开始使用 Go 模块,由 Carolyn Van Slyck 撰写(2018 年 7 月 16 日)
自最初的 Vgo 提案以来的更改
在提案、原型和测试阶段,整个社区创建了 400 多个问题。请继续提供反馈。
以下是一些较大更改和改进的摘要,其中几乎所有更改和改进都主要基于社区反馈
- 保留了顶层 vendor 支持,而不是让基于 vgo 的构建完全忽略 vendor 目录 (讨论,CL)
- 将最小的模块感知功能移植到旧版 Go 版本 1.9.7+ 和 1.10.3+,让这些版本更容易使用模块,用于 v2+ 项目 (讨论,CL)
- 默认情况下允许 vgo 使用 v2+ 标签,用于尚未拥有 go.mod 的现有包(相关行为的近期更新 在此处 描述)
- 通过命令
go get -u=patch
添加支持,用于将所有传递依赖项更新到同一次要版本的最新可用补丁级别版本 (讨论,文档) - 通过环境变量提供更多控制(例如,#26585 中的 GOFLAGS,CL)
- 更细粒度的控制,用于控制是否允许更新 go.mod、如何使用 vendor 目录以及是否允许网络访问(例如,-mod=readonly、-mod=vendor、GOPROXY=off;最近更改的相关 CL)
- 添加了更灵活的替换指令 (CL)
- 添加了更多用于查询模块的方法(用于人工解读,以及更好地与编辑器/IDE 集成)
- go CLI 的 UX 持续基于目前的体验进行改进(例如,#26581,CL)
- 通过
go mod download
为 CI 或 Docker 构建等用例添加更多支持,用于预热缓存 (#26610) - 最有可能:为将特定版本的程序安装到 GOBIN 提供更好的支持 (#24250)
GitHub 问题
- 当前开放的模块问题
- 已关闭的模块问题
- 已关闭的 vgo 问题
- 使用“cmd/go:” 作为前缀提交 新的模块问题
常见问题解答
如何将版本标记为不兼容?
require
指令允许任何模块声明它应该使用依赖项 D 的版本 >= x.y.z 构建(由于与模块 D 的版本 < x.y.z 不兼容,因此可能会指定该版本)。经验数据表明 这是 dep
和 cargo
中使用的约束的主要形式。此外,构建中的顶层模块可以 exclude
依赖项的特定版本,或将其他模块 replace
为不同的代码。有关 更多详细信息和基本原理,请参阅完整提案。
版本化模块提案的关键目标之一是为工具和开发人员添加有关 Go 代码版本的通用词汇和语义。这为将来声明其他形式的不兼容性奠定了基础,例如可能
- 将已弃用的版本声明为 最初的
vgo
博客系列中所述 - 在外部系统中声明模块之间的成对不兼容性,例如在提案流程期间 此处 所述
- 在发布版本后声明模块的成对不兼容版本或不安全的版本。例如,请参阅 #24031 和 #26829 中正在进行的讨论
何时获得旧行为与新的基于模块的行为?
通常,模块对于 Go 1.11 是选择加入的,因此根据设计,默认情况下会保留旧行为。
总结您何时获得旧的 1.10 状态 quo 行为,何时获得新的选择加入模块行为
- 在 GOPATH 内部 - 默认情况下为旧的 1.10 行为(忽略模块)
- 在 GOPATH 外部,但在包含
go.mod
的文件树内 - 默认情况下为模块行为 - GO111MODULE 环境变量
- 未设置或
auto
- 上述默认行为 on
- 无论目录位置如何,强制启用模块支持off
- 无论目录位置如何,强制禁用模块支持
- 未设置或
为什么使用 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
来记录依赖项信息。
解决方案替代方案包括
-
将
GO111MODULE
保持未设置状态(默认值,或显式设置为GO111MODULE=auto
),这将导致更友好的行为。这将为您提供 Go 1.10 行为,当您不在模块内时,因此将避免go get
报告cannot find main module
。 -
将
GO111MODULE=on
保持不变,但根据需要临时禁用模块并在go get
期间启用 Go 1.10 行为,例如通过GO111MODULE=off go get example.com/cmd
。这可以变成一个简单的脚本或 shell 别名,例如alias oldget='GO111MODULE=off go get'
-
创建一个临时
go.mod
文件,然后将其丢弃。这已被 由 @rogpeppe 创建的简单 shell 脚本 自动化。此脚本允许通过vgoget example.com/cmd[@version]
可选地提供版本信息。(这可以作为避免错误cannot use path@version syntax in GOPATH mode
的解决方案)。 -
gobin
是一个模块感知命令,用于安装和运行主包。默认情况下,gobin
会安装/运行主包,而无需先手动创建模块,但使用-m
标志可以告诉它使用现有模块来解析依赖项。有关详细信息和更多用例,请参阅gobin
README 和 FAQ。 -
创建一个
go.mod
用于跟踪您全局安装的工具,例如在~/global-tools/go.mod
中,并在运行任何全局安装的工具的go get
或go install
之前cd
到该目录。 -
为每个工具创建
go.mod
,分别放在不同的目录中,例如~/tools/gorename/go.mod
和~/tools/goimports/go.mod
,并在运行工具的go get
或go install
之前cd
到相应的目录。
此当前限制将得到解决。但是,主要问题是模块目前是选择加入的,完整解决方案可能要等到 GO111MODULE=on 成为默认行为。有关更多讨论,请参阅 #24250,包括此评论
这最终必须起作用。我不确定的是这在版本方面具体做了什么:它是否会创建一个临时模块根目录和 go.mod,执行安装,然后将其丢弃?很可能。但我还不完全确定,目前,我不想通过让 vgo 在 go.mod 树之外执行操作来混淆人们。当然,最终的 go 命令集成必须支持这一点。
此 FAQ 一直在讨论跟踪全局安装的工具。
如果您想跟踪特定模块所需的工具,请参阅下一个 FAQ。
如何跟踪模块的工具依赖项?
如果您
- 想在处理模块时使用基于 go 的工具(例如
stringer
),并且 - 想确保每个人都在使用该工具的同一版本,同时在您的模块的
go.mod
文件中跟踪该工具的版本
那么目前推荐的方法之一是向您的模块添加一个 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 中。
例如
- GoLand:目前已完全支持 GOPATH 内外模块,包括代码补全、语法分析、重构、导航,如此处 所述。
- VS Code:工作已完成,MS 建议使用模块而不是 GOPATH,以前的跟踪问题(#1532)已关闭。文档可在VS Code 模块存储库 中获得。
- 带有 go-plus 的 Atom:跟踪问题为#761。
- 带有 vim-go 的 vim:对语法高亮和格式化
go.mod
的初始支持已落地。更广泛的支持在#1906 中跟踪。 - 带有 go-mode.el 的 emacs:跟踪问题在#237 中。
像 goimports、guru、gorename 和类似工具等其他工具的状态正在一个总括问题#24661 中跟踪。请参阅该总括问题以了解最新状态。
某些特定工具的跟踪问题包括
- gocode:跟踪问题在mdempsky/gocode/#46 中。请注意,
nsf/gocode
建议人们从nsf/gocode
迁移到mdempsky/gocode
。 - go-tools(dominikh 的工具,如 staticcheck、megacheck、gosimple):示例跟踪问题dominikh/go-tools#328。
一般来说,即使您的编辑器、IDE 或其他工具尚未实现模块感知,如果使用 GOPATH 内部的模块并执行 go mod vendor
(因为这样会通过 GOPATH 获取正确的依赖项),那么它们的许多功能都应该可以使用模块。
完整的修复方法是将加载包的程序从 go/build
移动到 golang.org/x/tools/go/packages
,后者知道如何在模块感知的方式下定位包。这最终可能会变成 go/packages
。
常见问题解答 - 附加控制
有哪些与模块一起使用的社区工具?
社区正在开始构建基于模块的工具。例如
- github.com/rogpeppe/gohack
- 一个新的社区工具,用于自动化和极大地简化
replace
和多模块工作流程,包括允许您轻松修改您的依赖项之一 - 例如,
gohack example.com/some/dependency
会自动克隆相应的存储库并将必要的replace
指令添加到您的go.mod
中 - 使用
gohack undo
删除所有 gohack 替换语句 - 该项目正在不断扩展,以使其他与模块相关的工作流程更轻松
- 一个新的社区工具,用于自动化和极大地简化
- github.com/marwan-at-work/mod
- 命令行工具,用于自动升级/降级模块的主要版本
- 自动调整
go.mod
文件以及 go 源代码中的相关导入语句 - 在升级时或首次选择使用 v2+ 包的模块时有所帮助
- github.com/akyoto/mgit
- 允许您查看和控制所有本地项目的语义版本控制标签
- 显示未标记的提交,并允许您一次性标记所有提交(
mgit -tag +0.0.1
)
- github.com/goware/modvendor
- 帮助将其他文件复制到
vendor/
文件夹中,例如 shell 脚本、.cpp 文件、.proto 文件等。
- 帮助将其他文件复制到
- github.com/psampaz/go-mod-outdated
- 以人性化的方式显示过时的依赖项
- 提供一种方法来过滤间接依赖项和没有更新的依赖项
- 提供一种方法来在过时依赖项的情况下中断 CI 管道
- github.com/oligot/go-mod-upgrade
- 交互式更新过时的 Go 依赖项
我什么时候应该使用 replace 指令?
如上面的‘go.mod’ 概念部分 中所述,replace
指令在顶层 go.mod
中提供了额外的控制,用于实际使用什么来满足 Go 源代码或 go.mod 文件中找到的依赖项,而主模块以外的模块中的 replace
指令在构建主模块时会被忽略。
replace
指令允许您提供另一个导入路径,该路径可能是位于 VCS(GitHub 或其他地方)或在您的本地文件系统上的另一个模块,带有相对或绝对文件路径。来自 replace
指令的新导入路径在不需要更新实际源代码中的导入路径的情况下使用。
replace
允许顶层模块控制用于依赖项的精确版本,例如
replace example.com/some/dependency => example.com/some/dependency v1.2.3
replace
还允许使用分叉的依赖项,例如
replace example.com/some/dependency => example.com/some/dependency-fork v1.2.3
您还可以引用分支,例如
replace example.com/some/dependency => example.com/some/dependency-fork master
一个示例用例是,如果您需要修复或调查依赖项中的某些内容,您可以拥有一个本地分叉并添加以下内容到您的顶层 go.mod
中
replace example.com/original/import/path => /your/forked/import/path
replace
还可用于将多模块项目中模块的相对或绝对磁盘位置告知 go 工具,例如
replace example.com/project/foo => ../foo
注意:如果 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 build
、go 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 mod vendor
会重置主模块的供应商目录,以包括根据 go.mod 文件和 Go 源代码的状态构建和测试所有模块包所需的所有包。- 默认情况下,像
go build
这样的 go 命令在模块模式下会忽略供应商目录。 -mod=vendor
标志(例如go build -mod=vendor
)指示 go 命令使用主模块的顶层供应商目录来满足依赖项。因此,在这种模式下的 go 命令会忽略 go.mod 中的依赖项描述,并假设供应商目录包含依赖项的正确副本。请注意,只有主模块的顶层供应商目录会被使用;其他位置的供应商目录仍然会被忽略。- 有些人可能希望通过设置
GOFLAGS=-mod=vendor
环境变量来定期选择供应商。
Go 的旧版本(如 1.10)了解如何使用 go mod vendor
创建的供应商目录,Go 1.11 和 1.12+ 在模块模式 禁用时也是如此。因此,供应商是模块为不支持模块的 Go 旧版本以及尚未启用模块本身的使用者提供依赖项的一种方式。
如果您正在考虑使用供应商,值得阅读尖端文档中的“模块和供应商” 和“创建依赖项的供应商副本” 部分。
是否有“始终开启”的模块存储库和企业代理?
公开托管的“始终在线”不可变模块存储库以及可选的私有托管代理和存储库正在变得可用。
例如
- proxy.golang.org - 官方项目 - 由 Google 运营 - Go 团队构建的默认 Go 模块代理。
- proxy.golang.com.cn - 中国代理项目 - 由 中国 Go 贡献者俱乐部 运营 - 中国 Go 模块代理。
- mirrors.tencent.com/go - 商业项目 - 由 腾讯云 运营 - Go 模块代理的替代方案。
- mirrors.aliyun.com/goproxy - 商业项目 - 由 阿里云 运营 - Go 模块代理的替代方案。
- goproxy.cn - 开源项目 - 由 七牛云 运营 - 中国最受信赖的 Go 模块代理。
- goproxy.io - 开源项目 - 由 中国 Go 贡献者俱乐部 运营 - 全球 Go 模块代理。
- Athens - 开源项目 - 自托管 - Go 模块数据存储和代理。
- athens.azurefd.net - 开源项目 - 运行 Athens 的托管模块代理。
- Goproxy - 开源项目 - 自托管 - 最简化的 Go 模块代理处理程序。
- THUMBAI - 开源项目 - 自托管 - Go 模块代理服务器和 Go 虚荣导入路径服务器。
请注意,您不需要运行代理。相反,1.11 中的 go 工具通过 GOPROXY 添加了可选代理支持,以支持更多企业用例(例如更多控制),以及更好地处理诸如“GitHub 宕机”或人们删除 GitHub 存储库等情况。
我可以控制何时更新 go.mod 以及何时 go 工具使用网络来满足依赖项?
默认情况下,诸如 go build
之类的命令将根据需要连接网络以满足导入需求。
某些团队可能希望在某些情况下阻止 go 工具连接网络,或者希望更好地控制 go 工具何时更新 go.mod
、如何获取依赖项以及如何使用供应商。
go 工具提供了相当大的灵活性来调整或禁用这些默认行为,包括通过 -mod=readonly
、-mod=vendor
、GOFLAGS
、GOPROXY=off
、GOPROXY=file:///filesystem/path
、go mod vendor
和 go mod download
。
有关这些选项的详细信息散布在官方文档中。社区尝试对这些行为相关旋钮进行集中概述的一个尝试是 这里,其中包含指向官方文档的链接,以获取更多信息。
如何在 Travis 或 CircleCI 等 CI 系统中使用模块?
最简单的方法可能是设置环境变量 GO111MODULE=on
,它应该与大多数 CI 系统配合使用。
但是,在 CI 中运行启用和禁用模块的 Go 1.11 测试可能很有价值,因为您的一些用户可能还没有选择加入模块。供应商也是需要考虑的一个主题。
以下两篇博文更具体地介绍了这些主题
- “在 Travis CI 上使用带有供应商支持的 Go 模块” 作者:Fatih Arslan
- “Go 模块和 CircleCI” 作者:Todd Keech
如何下载构建特定包或测试所需的模块?
go mod download
命令(或等效的 go mod download all
)下载构建列表中的所有模块(如 go list -m all
所报告)。许多这些模块不需要构建主模块中的包,因为完整的构建列表包含诸如测试依赖项和用于其他模块的工具依赖项之类的东西。因此,使用 go mod download
准备的 Docker 镜像可能比必要的大。
相反,考虑使用 go list
。例如,go list ./...
将下载构建包 ./...
(从模块根目录运行时,主模块中的包集)所需的模块。
要下载测试依赖项,请使用 go list -test ./...
。
默认情况下,go list
将仅考虑当前平台所需的依赖项。您可以设置 GOOS
和 GOARCH
使 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 build
和 go test
)只会更新 go.mod
以提供在当前 GOOS
、GOARCH
和构建标签下由请求的包导入的包(这是 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 -u
或 go get [email protected]
。go 工具需要一个地方来记录这些新版本,它会在您的 go.mod
文件中这样做(并且它不会向下进入您的依赖项以修改它们的 go.mod
文件)。
通常,上面描述的行为是模块通过记录精确的依赖项信息来提供 100% 可重复构建和测试的方式的一部分。
如果您想知道为什么某个特定模块出现在您的 go.mod
中,您可以运行 go mod why -m <module>
以 回答 这个问题。其他用于检查要求和版本的工具包括 go mod graph
和 go 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.sum
包含特定模块版本的预期内容的加密校验和。- 如果有人克隆了您的存储库并使用 go 命令下载了您的依赖项,如果他们下载的您的依赖项副本与您
go.sum
中的相应条目之间存在任何不匹配,他们将收到错误。 - 此外,
go mod verify
检查模块下载的磁盘上缓存副本是否仍然与go.sum
中的条目匹配。 - 请注意,
go.sum
不是某些替代依赖项管理系统中使用的锁定文件。(go.mod
提供了足够的信息来实现可重复的构建)。 - 请参阅 Filippo Valsorda 关于为什么要签入
go.sum
的非常简短的 理由。有关更多详细信息,请参阅提示文档的 “模块下载和验证” 部分。请参阅例如在 #24117 和 #25530 中讨论的可能的未来扩展。
如果我没有依赖项,是否仍然应该添加“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 foo
或 go 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
)时始终为真。
- 包的导入路径定义了包的身份。
- 具有不同导入路径的包被视为不同的包。
- 具有相同导入路径的包被视为相同的包(即使 VCS 标签表明这些包具有不同的主要版本)。
- 没有
/vN
的导入路径被视为 v1 或 v0 模块(即使导入的包尚未选择加入模块,并且具有表明主要版本大于 1 的 VCS 标签)。 - 模块路径(例如
module foo/v2
)在模块go.mod
文件的开头声明,它既是- 对该模块身份的明确声明
- 对该模块如何被使用代码导入的明确声明
正如我们在下一个常见问题解答中看到的那样,这些原则在 go
工具不处于模块模式时并不总是为真,但这些原则在 go
工具处于模块模式时始终为真。
简而言之,+incompatible
后缀表明在以下情况为真时,原则 2 生效。
- 导入的包尚未选择加入模块,并且
- 其 VCS 标签表明主要版本大于 1,并且
- 原则 2 覆盖了 VCS 标签——没有
/vN
的导入路径被视为 v1 或 v0 模块(即使 VCS 标签表明并非如此)。
当 go
工具处于模块模式时,它将假设非模块 v2+ 包不了解语义导入版本控制,并将将其视为包的 v1 版本系列的(不兼容)扩展(+incompatible
后缀表明 go
工具正在这样做)。
示例
假设
oldpackage
是一个早于模块引入的包oldpackage
从未选择加入模块(因此本身没有go.mod
)oldpackage
具有有效的 semver 标签v3.0.1
,这是其最新的标签
在这种情况下,从模块 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
后缀表明 oldpackage
的 v3.0.1
版本没有积极选择加入模块,因此,假设 oldpackage
的 v3.0.1
版本不了解语义导入版本控制或如何在导入路径中使用主要版本。因此,在 模块模式 下运行时,go
工具将把 oldpackage
的非模块 v3.0.1
版本视为 oldpackage
的 v1 版本系列的(不兼容)扩展,并假设 oldpackage
的 v3.0.1
版本不了解语义导入版本控制,+incompatible
后缀表明 go
工具正在这样做。
根据语义导入版本控制,oldpackage
的 v3.0.1
版本被认为是 v1 发布系列的一部分这一事实意味着,例如,版本 v1.0.0
、v2.0.0
和 v3.0.1
将始终使用相同的导入路径进行导入。
import "oldpackage"
再次注意,oldpackage
的末尾没有使用 /v3
。
一般来说,具有不同导入路径的包是不同的包。在本例中,鉴于 oldpackage
的版本 v1.0.0
、v2.0.0
和 v3.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 或更高的模块必须在其
go.mod
中声明的自身模块路径中包含/vN
。 - 基于模块的使用者(即已选择加入模块的代码)必须在导入路径中包含
/vN
才能导入 v2+ 模块。
但是,预计生态系统将以不同的速度进行模块和语义导入版本控制的采用。
如 “如何发布 v2+ 模块” 部分中更详细地描述的那样,在“主要子目录”方法中,v2+ 模块的作者创建了诸如 mymodule/v2
或 mymodule/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 版本没有完全的模块支持)。
“最小模块兼容性”的主要目标是
-
允许较旧的 Go 版本 1.9.7+ 和 1.10.3+ 更轻松地编译使用语义化导入版本控制(在导入路径中使用
/vN
)的模块,并在 模块模式 在 Go 1.11 中被禁用时提供相同的行为。 -
允许旧代码能够使用 v2+ 模块,而无需立即更改旧的使用者代码以使用新的
/vN
导入路径来使用 v2+ 模块。 -
这样做无需依赖于模块作者创建
/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 中的“最小模块兼容性”机制时
- 未 采用模块的包不会在任何导入的 v2+ 模块的导入路径中包含主版本。
- 相反,已 采用模块的包必须在导入路径中包含主版本才能导入任何 v2+ 模块。
- 如果一个包已采用模块,但在导入 v2+ 模块时没有在导入路径中包含主版本,则当
go
工具在完全模块模式下运行时,它将不会导入该模块的 v2+ 版本。(已采用模块的包被假定为“使用”语义化导入版本控制。如果foo
是一个具有 v2+ 版本的模块,那么在语义化导入版本控制下,import "foo"
意味着导入foo
的 v1 语义化导入版本控制系列)。
- 如果一个包已采用模块,但在导入 v2+ 模块时没有在导入路径中包含主版本,则当
- 用于实现“最小模块兼容性”的机制有意地非常窄
- 整个逻辑是 - 在 GOPATH 模式下,如果导入语句位于已采用模块的代码中(即,
go.mod
文件有效的树中.go
文件中的导入语句),则将再次尝试包含/vN
的无法解析的导入语句,方法是删除/vN
。 - 最终效果是,位于模块内的代码中的导入语句(例如
import "foo/v2"
)仍将在 GOPATH 模式下在 1.9.7+、1.10.3+ 和 1.11 中正确编译,并且它将解析为好像它说的是import "foo"
(没有/v2
),这意味着它将使用位于 GOPATH 中的foo
的版本,而不会被额外的/v2
所迷惑。 - “最小模块兼容性”不会影响其他任何内容,包括它不会影响
go
命令行中使用的路径(例如,go get
或go list
的参数)。
- 整个逻辑是 - 在 GOPATH 模式下,如果导入语句位于已采用模块的代码中(即,
- 这种过渡性的“最小模块感知”机制有意地打破了“具有不同导入路径的包被视为不同的包”的规则,以实现一个非常具体的向后兼容性目标 - 允许旧代码在使用 v2+ 模块时无需修改即可编译。更详细地解释一下
- 如果让旧代码使用 v2+ 模块的唯一方法是首先更改旧代码,这将给整个生态系统带来更大的负担。
- 如果我们不修改旧代码,那么旧代码必须与 v2+ 模块的模块前导入路径一起工作。
- 另一方面,采用模块的新代码或更新的代码必须为 v2+ 模块使用新的
/vN
导入路径。 - 新的导入路径不等于旧的导入路径,但两者都可以在一次构建中正常工作,因此我们有两个不同的正常工作的导入路径,它们解析到同一个包。
- 例如,在 GOPATH 模式下,出现在基于模块的代码中的
import "foo/v2"
解析为与 GOPATH 中的import "foo"
相同的代码,并且构建最终会得到一个foo
的副本 - 特别是,磁盘上 GOPATH 中的任何版本。这使得基于模块的代码能够在 GOPATH 模式下在 1.9.7+、1.10.3+ 和 1.11 中编译,即使它包含import "foo/v2"
。
- 相反,当
go
工具在完全模块模式下运行时- 没有例外,即“具有不同导入路径的包是不同的包”(包括在完全模块模式下精炼的供应商机制也必须遵守此规则)。
- 例如,如果
go
工具处于完全模块模式,并且foo
是一个 v2+ 模块,那么import "foo"
正在请求foo
的 v1 版本,而import "foo/v2"
正在请求foo
的 v2 版本。
如果我创建了 go.mod 但没有在我的存储库中应用 semver 标签会发生什么?
语义化版本控制 是模块系统的一个基础。为了为使用者提供最佳体验,鼓励模块作者应用语义化版本控制 VCS 标签(例如,v0.1.0
或 v1.2.3-rc.1
),但语义化版本控制 VCS 标签不是严格要求的。
-
模块必须遵循语义化版本控制规范,以便
go
命令能够按文档的方式执行。这包括遵循语义化版本控制规范,该规范涉及如何以及何时允许进行重大更改。 -
没有语义化版本控制VCS 标签的模块由使用者使用语义化版本控制版本(以 伪版本 的形式)进行记录。通常这将是 v0 主版本,除非模块作者遵循 “主子目录” 方法构建了 v2+ 模块。
-
因此,没有应用语义化版本控制 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
图 A 顶层模块的路径是另一个模块路径的前缀。
此存储库包含两个模块。但是,模块“my-repo”是模块“my-repo/mig”路径的前缀。
我应该在一个存储库中拥有多个模块吗?
在此配置中添加模块、删除模块以及对模块进行版本控制需要仔细考虑和权衡,因此,与在现有存储库中管理多个模块相比,管理单个模块存储库几乎总是更容易和更简单。
Russ Cox 在 #26664 中评论道
除了高级用户之外,您可能希望采用通常的约定,即一个存储库 = 一个模块。对于代码存储选项的长期演变来说,一个存储库可以包含多个模块很重要,但几乎可以肯定这不是您默认情况下想要做的。
多模块可能更复杂的一些例子
- 从存储库根目录执行
go test ./...
将不再测试存储库中的所有内容 - 您可能需要通过
replace
指令定期管理模块之间的关系。
但是,除了这两个例子之外,还有更多细微之处。如果您正在考虑在单个存储库中使用多个模块,请仔细阅读本 小节 中的常见问题解答。
在单个存储库中使用多个 go.mod
文件可能合理的两项示例场景
-
如果你有使用示例,而这些示例本身具有复杂的依赖关系(例如,你可能有一个小型包,但包含一个使用你的包与 Kubernetes 的示例)。在这种情况下,你的仓库可以包含一个名为
example
或_example
的目录,并具有自己的go.mod
文件,例如 这里 所示。 -
如果你有一个具有复杂依赖关系的仓库,但你有一个客户端 API,它具有较小的依赖关系集。在某些情况下,可能需要在仓库中创建一个名为
api
或clientapi
类似的目录,并为其提供自己的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。
-
添加 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]
-
git commit
-
git tag v1.3.0
-
git tag mig/v1.0.0
-
接下来,让我们测试这些。我们不能简单地
go build
或go 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]
-
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
文件,请首先仔细查看 此多模块仓库部分 中的其他常见问题解答。
常见问题解答 - 最小版本选择
最小版本选择会阻止开发人员获得重要更新吗?
请参见早期 官方提案讨论中的常见问题解答 中的问题“最小版本选择是否会阻止开发人员获得重要更新?”。
常见问题解答 - 可能出现的问题
如果我发现问题,我可以检查哪些一般性事项?
- 双重检查是否已启用模块,运行
go env
以确认它没有为只读GOMOD
变量显示空值。- 注意:你永远不会将
GOMOD
设置为变量,因为它实际上是go env
输出的只读调试输出。 - 如果你正在设置
GO111MODULE=on
以启用模块,请双重检查它是否不是意外地设置为GO111MODULES=on
。(人们有时会自然地包含S
,因为该功能通常称为“模块”。)
- 注意:你永远不会将
- 如果预计使用 vendoring,请检查
-mod=vendor
标志是否已传递给go build
或类似命令,或者是否已设置GOFLAGS=-mod=vendor
。- 默认情况下,模块会忽略
vendor
目录,除非你要求go
工具使用vendor
。
- 默认情况下,模块会忽略
- 检查
go list -m all
通常有助于查看为你的构建选择的实际版本列表。- 与仅查看
go.mod
文件相比,go list -m all
通常会提供更多详细信息。
- 与仅查看
- 如果运行
go get foo
失败,或者如果go build
在特定包foo
上失败,通常检查go get -v foo
或go get -v -x foo
的输出会很有帮助。- 通常,
go get
提供的错误消息比go build
更详细。 go get
的-v
标志要求打印更详细的信息,但请注意,某些“错误”,例如 404 错误,根据远程仓库的配置方式,可能是预期的。- 如果问题本质仍然不清楚,你还可以尝试更详细的
go get -v -x foo
,它还会显示发出的 git 或其他 VCS 命令。(如果需要,你通常可以在go
工具的上下文中执行相同的 git 命令,以进行故障排除)。
- 通常,
- 你可以检查是否正在使用旧版本的 git。
- 旧版本的 git 曾经是
vgo
原型和 Go 1.11 测试版的常见问题来源,但在 GA 1.11 中这种情况很少见。
- 旧版本的 git 曾经是
- Go 1.11 中的模块缓存有时会导致各种错误,主要是在以前出现网络问题或多个
go
命令并行执行的情况下(参见 #26794,此问题已在 Go 1.12 中解决)。作为故障排除步骤,你可以将 $GOPATH/pkg/mod 复制到备份目录(以防以后需要进一步调查),运行go clean -modcache
,然后查看原始问题是否仍然存在。 - 如果你正在使用 Docker,检查是否可以在 Docker 之外重现该行为(如果该行为仅在 Docker 中发生,则上述要点列表可用作比较 Docker 内部和外部结果的起点)。
你当前正在检查的错误可能是由于构建中没有特定模块或包的预期版本而导致的次要问题。因此,如果特定错误的原因不明确,检查版本(如下一条常见问题解答中所述)可能会有所帮助。
如果我没有看到预期的依赖版本,我可以检查什么?
-
第一步是运行
go mod tidy
。这可能会解决问题,但它也有助于将go.mod
文件与.go
源代码保持一致,这将使后续调查更容易。(如果go mod tidy
本身以你没有预料到的方式更改了依赖项的版本,请首先阅读 关于 ‘go mod tidy’ 的常见问题解答。如果这无法解释,你可以尝试重置go.mod
,然后运行go list -mod=readonly all
,这可能会提供关于需要更改其版本的具体消息)。 -
第二步通常应该是检查
go list -m all
以查看为你的构建选择的实际版本列表。go list -m all
会显示最终选择的版本,包括间接依赖项,以及解决任何共享依赖项的版本后的版本。它还会显示任何replace
和exclude
指令的结果。 -
下一步可能是检查
go mod graph
或go 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 模块,但可能缺少所需的 /v3
;go.mod
中针对 v4 模块的 require
语句可能缺少所需的 /v4
)。因此,如果你看到的特定问题的根源不明确,最好先重新阅读上面的 “go.mod” 和 “语义导入版本控制” 部分中的内容(因为它们包含模块必须遵循的重要规则),然后花几分钟时间检查最相关的 go.mod
文件和导入语句。
为什么我收到错误“无法找到提供包 foo 的模块”?
这是一个常见的错误消息,它可能是由于多个不同的根本原因导致的。
在某些情况下,此错误仅仅是由于路径输入错误导致的,因此第一步应该是根据错误消息中列出的详细信息双重检查路径是否正确。
如果你还没有这样做,下一步通常是尝试 go get -v foo
或 go get -v -x foo
。
- 通常,
go get
提供的错误消息比go build
更详细。 - 有关更多详细信息,请参见本节中的第一个故障排除常见问题解答 上面。
一些其他可能的原因
-
如果您在执行 `go build` 或 `go build .` 时没有在当前目录中找到任何 `.go` 源文件,则可能会看到错误 `cannot find module providing package foo`。如果您遇到这种情况,解决方案可能是使用其他调用方式,例如 `go build ./...`(其中 `./...` 将扩展以匹配当前模块中的所有包)。请参阅 #27122。
-
Go 1.11 中的模块缓存可能会导致此错误,包括在网络问题或多个 `go` 命令并行执行的情况下。此问题在 Go 1.12 中已解决。有关更多详细信息和可能的纠正步骤,请参阅本节中的第一个故障排除常见问题 以上。
为什么“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` 中声明了其规范名称,则会导致问题。此错误随后可能在升级期间触发,此时会发现模块的升级版本声明了不再匹配旧导入路径的规范模块路径。
示例问题场景
- 您间接依赖于 `github.com/Quasilyte/go-consistent`。
- 该项目采用模块,然后将其名称更改为 `github.com/quasilyte/go-consistent`(将 `Q` 更改为小写 `q`),这是一个重大更改。GitHub 将从旧名称转发到新名称。
- 您运行 `go get -u`,这将尝试升级所有直接和间接依赖项。
- 尝试升级 `github.com/Quasilyte/go-consistent`,但现在找到的最新 `go.mod` 读取 `module github.com/quasilyte/go-consistent`。
- 整个升级操作无法完成,并出现错误
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`。如果是,则表明您遇到了“旧模块名称”与“新模块名称”的问题。
本节的其余部分重点介绍如何通过按顺序执行以下步骤来解决此错误的“旧名称”与“新名称”形式
-
检查您自己的代码,以查看您是否正在使用 `example.com/some/OLD/name` 导入。如果是,请将您的代码更新为使用 `example.com/some/NEW/name` 导入。
-
如果您在升级期间收到此错误,您应该尝试使用 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` 问题之外。
-
如果您在执行 `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` 的所有直接和间接依赖项的最新版本;这可能是您想要的,但如果它因深层间接依赖项而失败,则可能不是。)
-
如果上述步骤未能解决错误,则下一种方法稍微复杂一些,但通常应该能够解决此错误的“旧名称”与“新名称”形式。这仅使用来自错误消息本身的信息,以及对一些 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 中进行了跟踪)。
- 如果以上步骤没有解决问题,可能是因为最新的一个或多个依赖项仍在使用有问题的旧导入路径。在这种情况下,重要的是要确定谁仍在使用有问题的旧导入路径,并找到或打开一个问题,要求有问题的导入者更改为使用现在规范的导入路径。使用步骤 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
- 如果这些步骤不足以解决问题,或者如果您是项目的维护者,并且由于循环引用而无法删除对旧的、有问题的导入路径的引用,请参阅关于此问题的更详细的说明,该说明位于单独的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 的一部分。