Go Wiki:Go 模块
本 Wiki 页面旨在提供使用和故障排除指南。
- 有关教程博文,请参阅《使用 Go 模块》。
- 有关技术参考,请参阅Go 模块参考(开发中)。
自 1.11 版起,Go 已包含对版本化模块的支持,如此处所提议。最初的 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
,即此更改之前模块缓存的位置。 - 现在有了一个解决方案,用于解决 Go 命令访问模块缓存时,外部程序并发扫描文件系统导致的 Windows “访问被拒绝”错误(参见问题 #36568)。此解决方案默认不启用,因为它在 Go 1.14.2 以下和 1.13.10 以下版本与同一模块缓存并发运行时不安全。可以通过显式设置环境变量
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
工具现在默认从公共 Go 模块镜像 https://proxy.golang.org 下载模块,并且默认针对公共 Go 校验和数据库 https://sum.golang.org 验证下载的模块(无论来源如何)。- 如果您有私有代码,您很可能应该配置
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 -d foo
替换go get -m foo
)。
目录
“快速入门”和“新概念”部分对于刚开始使用模块的人来说特别重要。“如何…”部分涵盖了更多机制细节。本页内容最多的部分是回答更具体问题的常见问题解答;至少浏览一下此处列出的常见问题解答的简短描述是值得的。
- 快速入门
- 新概念
- 如何使用模块
- 迁移到模块
- 其他资源
- 自最初 vgo 提案以来的变更
- GitHub 问题
- 常见问题解答
- 常见问题解答 — 附加控制
- 常见问题解答 — go.mod 和 go.sum
- 常见问题解答 — 语义导入版本控制
- 常见问题解答 — 多模块仓库
- 常见问题解答 — 最小版本选择
- 常见问题解答 — 可能的问题
- 如果我发现问题,我可以检查哪些一般情况?
- 如果我没有看到预期的依赖项版本,我可以检查什么?
- 为什么我收到错误“cannot find module providing package foo”?
- 为什么“go mod init”会报错“cannot determine module path for source directory”?
- 我有一个复杂的依赖项问题,该依赖项尚未选择模块。我可以使用其当前依赖项管理器中的信息吗?
- 如何解决导入路径与声明的模块身份不匹配导致的“parsing go.mod: unexpected module path”和“error loading module requirements”错误?
- 为什么“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 foo@v1.2.3
、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
目录的可选步骤(详情)。
阅读完接下来关于“新概念”的四个部分后,您将掌握足够的信息,可以开始使用模块进行大多数项目。查看上面的目录(包括其中的常见问题解答简短描述)也很有用,以熟悉更详细的主题列表。
新概念
这些部分对主要新概念进行了高级介绍。有关更多详细信息和原理,请参阅 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,请用其版本标记已发布的提交。公共和私有模块仓库和代理正在变得可用(请参阅下面的常见问题解答)。
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
)
模块通过 module
指令在 go.mod
中声明其身份,该指令提供了“模块路径”。模块中所有包的导入路径都以模块路径作为公共前缀。模块路径和从 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
指令的讨论,请参阅下面的常见问题解答)。
版本选择
如果您向源代码添加了新导入,但尚未被 go.mod
中的 require
覆盖,大多数 go 命令(如“go build”和“go test”)将自动查找正确的模块,并将该新直接依赖项的“最高”版本作为 require
指令添加到您的模块的 go.mod
中。例如,如果您的新导入对应于依赖项 M,其最新标记发布版本为 v1.2.3
,您的模块的 go.mod
最终将包含 require M v1.2.3
,这表示模块 M 是一个依赖项,允许版本 >= v1.2.3(且 < v2,因为 v2 被认为与 v1 不兼容)。
“最小版本选择”算法用于选择构建中使用的所有模块的版本。对于构建中的每个模块,通过最小版本选择选择的版本始终是主模块或其依赖项中 require
指令明确列出的版本中语义“最高”的版本。
举例来说,如果您的模块依赖于模块 A,该模块包含 require D v1.0.0
,并且您的模块还依赖于模块 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
文件中使用的模块路径(例如,module github.com/my/mod/v2
,require github.com/my/mod/v2 v2.0.1
)和包导入路径(例如,import "github.com/my/mod/v2/mypkg"
)的末尾,作为/vN
。这包括在go get
命令中使用的路径(例如,go get github.com/my/mod/v2@v2.0.1
。请注意,此示例中既有/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 版本.
- 从
master
分支从源代码安装 Go 工具链。
安装后,您可以选择以下两种方式之一激活模块支持
- 在
$GOPATH/src
树之外的目录中调用go
命令,当前目录或其任何父目录中包含有效的go.mod
文件,并且环境变量GO111MODULE
未设置(或显式设置为auto
)。 - 调用
go
命令时,设置GO111MODULE=on
环境变量。
如何定义模块
为现有项目创建 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
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 1.21.13 或更早版本一起使用,此步骤还会从任何现有的 dep
Gopkg.lock
文件或所有九种受支持的依赖项格式中的任何一种进行转换,添加 require
语句以匹配现有配置。
-
构建模块。当从模块的根目录执行时,
./...
模式匹配当前模块中的所有包。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
是默认值。
在本节中,“最新”是指带有 semver 标签的最新版本,如果没有任何 semver 标签,则为最新已知提交。除非仓库上没有其他 semver 标签,否则预发布标签不会被选为“最新”(详情)。
一个常见的误解是认为 go get -u foo
只获取 foo
的最新版本。实际上,go get -u foo
或 go get -u foo@latest
中的 -u
意味着“也”获取 foo
的所有直接和间接依赖项的最新版本。升级 foo
时常见的起点是执行不带 -u
的 go get foo
或 go get foo@latest
(在一切正常后,考虑 go get -u=patch foo
、go get -u=patch
、go get -u foo
或 go get -u
)。
要升级或降级到更具体的版本,“go get”允许通过在包参数中添加 @version 后缀或“模块查询”来覆盖版本选择,例如 go get foo@v1.6.2
、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
以可能修剪任何多余的需求(如此处所述),并确保当前的 go.mod 反映了所有可能的构建标签/操作系统/架构组合(如此处所述)。- 相比之下,
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
工具在 Go 1.11 之前或在 Go 1.11+ 中模块模式未启用时,不了解 semver)。 - 现有依赖项管理解决方案(如
dep
)目前在消费以这种方式创建的 v2+ 模块时可能会遇到问题。例如,请参见 dep#1962。
-
主子目录:创建一个新的
v3
子目录(例如,my/module/v3
),并在该子目录中放置一个新的go.mod
文件。模块路径必须以/v3
结尾。将代码复制或移动到v3
子目录中。更新模块内的导入语句,使其也使用/v3
(例如,import "github.com/my/module/v3/mypkg"
)。使用v3.0.0
标记发布。- 这提供了更大的向后兼容性。特别是,Go 1.9.7 和 1.10.3 之前的版本也能够正确使用和构建通过此方法创建的 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 1.21.13 或更早版本中,
go mod init
会自动将 dep、glide、govendor、godep 和其他 5 个现有依赖管理器所需的信息转换为go.mod
文件,生成等效的构建。 - 如果您正在创建 v2+ 模块,请确保您的
module
指令在转换后的go.mod
中包含适当的/vN
(例如,module foo/v3
)。 - 请注意,如果您正在导入 v2+ 模块,那么在初始转换后,您可能需要进行一些手动调整,以便将
/vN
添加到go mod init
在从以前的依赖项管理器转换后生成的require
语句中。有关更多详细信息,请参阅上面的“如何定义模块”部分。 - 此外,
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 中的讨论)。
避免破坏现有导入路径
模块通过 module
指令在 go.mod
中声明其身份,例如 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
,那将是对您当前消费者的一个重大更改。一种方法是,如果您以后迁移到 v2,则更改为类似 module github.com/repo/foo/v2
。
请注意,模块路径和导入路径是区分大小写的。例如,将模块从 github.com/Sirupsen/logrus
更改为 github.com/sirupsen/logrus
对消费者来说是一个破坏性更改,即使 GitHub 自动从一个仓库名称转发到新仓库名称。
在您采用模块后,更改 go.mod
中的模块路径是一个破坏性更改。
总的来说,这类似于模块之前通过“导入路径注释”强制执行规范导入路径,有时也称为“导入 pragma”或“导入路径强制”。例如,包 go.uber.org/zap
目前托管在 github.com/uber-go/zap
,但使用包声明旁边的导入路径注释,这会为使用错误基于 GitHub 的导入路径的任何模块前消费者触发错误
package zap // import "go.uber.org/zap"
导入路径注释已被 go.mod 文件的 module 语句废弃。
首次采用包含 v2+ 包的模块时增加主版本
- 如果您在采用模块之前已经标记了 v2.0.0 或更高版本的包,那么推荐的最佳实践是在首次采用模块时增加主版本。例如,如果您当前版本为
v2.0.1
且尚未采用模块,那么您将在首次采用模块时使用v3.0.0
。有关更多详细信息,请参阅上面的“发布模块(v2 或更高版本)”部分。
v2+ 模块允许在单个构建中包含多个主版本
- 如果一个模块是 v2 或更高版本,这意味着单个构建中可以包含多个主版本(例如,
foo
和foo/v3
可能最终出现在同一个构建中)。- 这自然源于“具有不同导入路径的包是不同的包”的规则。
- 当这种情况发生时,将会有多份包级状态(例如,
foo
的包级状态和foo/v3
的包级状态),并且每个主版本都将运行其自己的init
函数。 - 这种方法有助于模块系统的多个方面,包括帮助解决菱形依赖问题,在大型代码库中逐步迁移到新版本,以及允许将一个主版本实现为另一个主版本的垫片。
- 请参阅 https://research.swtch.com/vgo-import 的“避免单例问题”部分或 #27514 以获取一些相关讨论。
模块消费非模块代码
- 模块能够消费尚未选择加入模块的包,并在导入模块的
go.mod
中记录相应的包版本信息。模块可以消费尚未具有任何正确语义版本标签的包。有关更多详细信息,请参阅下面的常见问题解答。 - 模块还可以导入一个尚未选择加入模块的 v2+ 包。如果导入的 v2+ 包具有有效的 semver 标签,则它将以
+incompatible
后缀记录。有关更多详细信息,请参阅下面的常见问题解答。
非模块代码消费模块
-
非模块代码消费 v0 和 v1 模块:
- 尚未选择加入模块的代码可以消费和构建 v0 和 v1 模块(不要求与使用的 Go 版本相关)。
-
非模块代码消费 v2+ 模块:
-
Go 1.9.7+、1.10.3+ 和 1.11 版本已经更新,以便使用这些版本构建的代码能够正确使用 v2+ 模块,而无需修改现有代码,如上面“语义导入版本控制”和“发布模块(v2 或更高版本)”部分所述。
-
Go 1.9.7 和 1.10.3 之前的版本可以使用 v2+ 模块,如果 v2+ 模块是按照“发布模块(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 关于
vgo
的初始“Go 与版本控制”系列博文(首次发布于 2018 年 2 月 20 日) - 官方 golang.org 博客文章介绍该提案 (2018 年 3 月 26 日)
- 这提供了比完整
vgo
博客系列更简洁的提案概述,以及一些提案背后的历史和过程
- 这提供了比完整
- 官方版本化 Go 模块提案(上次更新日期为 2018 年 3 月 20 日)
入门材料
- GopherCon Singapore 上 Russ Cox 的 40 分钟入门视频“Go 中版本的原则”(2018 年 5 月 2 日)
- 简洁地涵盖了版本化 Go 模块设计背后的理念,包括“兼容性”、“可重复性”和“协作”这三个核心原则
- Paul Jolly 的基于示例的 35 分钟入门视频 “什么是 Go 模块以及如何使用它们?”(幻灯片)(2018 年 8 月 15 日)
- Dave Cheney 的入门博文“试用 Go 模块”(2018 年 7 月 14 日)
- Chris Hines 关于模块的入门Go Meetup 幻灯片(2018 年 7 月 16 日)
- Francesc Campoy 的 30 分钟入门视频“Go 模块和 SemVer 简介”(2018 年 11 月 15 日)
额外资料
- Fatih Arslan 的博客文章 “在 Travis CI 上使用带有供应商支持的 Go 模块”(2018 年 8 月 26 日)
- Todd Keech 的博客文章 “Go Modules and CircleCI”(2018 年 7 月 30 日)
- Russ Cox 的博客文章 “vgo 提案已接受。接下来呢?”(2018 年 5 月 29 日)
- 包括对版本化模块目前作为实验性选择加入功能的含义的总结
- Carolyn Van Slyck 关于如何从 tip 构建 go 并开始使用 go 模块的博客文章(2018 年 7 月 16 日)
自最初 vgo 提案以来的变更
作为提案、原型和测试版流程的一部分,社区总共创建了 400 多个问题。请继续提供反馈。
以下是一些主要变更和改进的部分列表,其中几乎所有都主要基于社区反馈
- 保留了顶级供应商支持,而不是 vgo 基于的构建完全忽略供应商目录(讨论,CL)
- 回溯了最小模块感知,以允许旧版 Go 1.9.7+ 和 1.10.3+ 更轻松地使用 v2+ 项目的模块(讨论,CL)
- 默认允许 vgo 对尚未拥有 go.mod 的现有包使用 v2+ 标签(相关行为的最新更新此处描述)
- 通过命令
go get -u=patch
添加了支持,以将所有传递性依赖项更新到同一次要版本上可用的最新补丁级别版本(讨论,文档) - 通过环境变量进行额外控制(例如,#26585 中的 GOFLAGS,CL)
- 对是否允许更新 go.mod、如何使用 vendor 目录以及是否允许网络访问进行更细粒度的控制(例如,-mod=readonly、-mod=vendor、GOPROXY=off;相关CL 用于最近的更改)
- 添加了更灵活的替换指令(CL)
- 增加了更多查询模块的方式(供人阅读,也为了更好地集成编辑器/IDE)
- Go CLI 的用户体验一直在根据目前的经验进行改进(例如,#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 现状行为与新的选择加入模块化行为
- 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 命令集成必须支持这一点。
本常见问题解答一直在讨论跟踪全局安装的工具。
如果您想跟踪特定模块所需的工具,请参阅下一个常见问题解答。
如何跟踪模块的工具依赖项?
如果您
- 在处理模块时希望使用基于 go 的工具(例如
stringer
),并且 - 希望确保每个人都使用该工具的相同版本,同时在模块的
go.mod
文件中跟踪该工具的版本
那么在 Go 1.24 及更高版本中,您可以向 go.mod 添加一个 tool
指令。
go 1.24
...
tool golang.org/x/tools/cmd/stringer
在 Go 1.24 之前,推荐的方法是在模块中添加一个 tools.go
文件,其中包含所需工具的导入语句(例如 import _ "golang.org/x/tools/cmd/stringer"
),以及一个 //go:build tools
构建约束。导入语句允许 go
命令在模块的 go.mod
中精确记录工具的版本信息,而 //go:build tools
构建约束可以防止您的正常构建实际导入您的工具。
有关如何执行此操作的具体示例,请参阅此 “Go Modules by Example” 演练。
您还可以(自 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 模块存储库 中找到。
- Atom with go-plus:跟踪问题是 #761。
- vim with vim-go:对
go.mod
的语法高亮和格式化的初始支持已经 落地。更广泛的支持在 #1906 中跟踪。 - emacs with go-mode.el:跟踪问题在 #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
自动克隆相应的存储库并向您的go.mod
添加必要的replace
指令 - 使用
gohack undo
删除所有 gohack 替换语句 - 该项目正在继续扩展,以简化其他模块相关工作流
- 一个新的社区工具,用于自动化并极大简化
- github.com/marwan-at-work/mod
- 用于自动升级/降级模块主要版本的命令行工具
- 自动调整
go.mod
文件和 Go 源代码中相关的导入语句 - 有助于升级,或首次使用 v2+ 包选择模块时
- github.com/akyoto/mgit
- 让您查看和控制所有本地项目的 semver 标签
- 显示未标记的提交,并允许您一次性标记它们(
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 在本地文件系统上工作?
是的。不需要版本控制系统。
如果您想一次编辑一个模块而不在版本控制系统之外(并且您总共只有一个模块,或者其他模块位于版本控制系统中),这非常简单。在这种情况下,您可以将包含单个 go.mod
的文件树放置在方便的位置。您的 go build
、go test
和类似命令将起作用,即使您的单个模块在版本控制系统之外(无需在 go.mod
中使用任何 replace
)。
如果您希望在本地磁盘上同时编辑多个相互关联的模块,那么 replace
指令是一种方法。这是一个示例 go.mod
,它使用带有相对路径的 replace
将 hello
模块指向 goodbye
模块的磁盘位置(不依赖任何版本控制系统)
module example.com/me/hello
require (
example.com/me/goodbye v0.0.0
)
replace example.com/me/goodbye => ../goodbye
此 帖子 中显示了一个可运行的小示例。
如何在模块中使用 vendoring?vendoring 会消失吗?
最初的 vgo
博客文章确实提出了完全放弃 vendoring,但来自社区的 反馈 导致保留了对 vendoring 的支持。
简而言之,要将 vendoring 与模块一起使用
go mod vendor
根据 go.mod 文件和 Go 源代码的状态,重置主模块的供应商目录,以包含构建和测试模块所有包所需的所有包。- 默认情况下,当处于模块模式时,
go build
等 go 命令会忽略 vendor 目录。 -mod=vendor
标志(例如,go build -mod=vendor
)指示 go 命令使用主模块的顶级 vendor 目录来满足依赖项。在此模式下,go 命令因此会忽略 go.mod 中的依赖项描述,并假定 vendor 目录包含正确的依赖项副本。请注意,仅使用主模块的顶级 vendor 目录;其他位置的 vendor 目录仍将被忽略。- 有些人会希望通过设置
GOFLAGS=-mod=vendor
环境变量来定期选择 vendoring。
Go 的旧版本(如 1.10)知道如何使用 go mod vendor
创建的 vendor 目录,Go 1.11 和 1.12+ 在禁用 模块模式 时也是如此。因此,vendoring 是一种模块向不完全理解模块的旧版本 Go 以及未启用模块本身的消费者提供依赖项的方式。
如果您正在考虑使用 vendoring,值得阅读 tip 文档的 “Modules and vendoring” 和 “Make vendored copy of dependencies” 部分。
是否有“始终在线”的模块仓库和企业代理?
公共托管的“始终在线”不可变模块存储库以及可选的私有托管代理和存储库正在变得可用。
例如
- proxy.golang.org - 官方项目 - 由 Google 运行 - Go 团队构建的默认 Go 模块代理。
- proxy.golang.com.cn - 中国代理项目 - 由 中国 Golang 贡献者俱乐部 运行 - 中国 Go 模块代理。
- mirrors.tencent.com/go - 商业项目 - 由 腾讯云 运行 - 一个 Go 模块代理替代。
- mirrors.aliyun.com/goproxy - 商业项目 - 由 阿里云 运行 - 一个 Go 模块代理替代。
- goproxy.cn - 开源项目 - 由 七牛云 运行 - 中国最受信任的 Go 模块代理。
- goproxy.io - 开源项目 - 由 中国 Golang 贡献者俱乐部 运行 - 全球 Go 模块代理。
- Athens - 开源项目 - 自托管 - 一个 Go 模块数据存储和代理。
- Goproxy - 开源项目 - 自托管 - 一个极简的 Go 模块代理处理程序。
- THUMBAI - 开源项目 - 自托管 - Go 模块代理服务器和 Go vanity import path 服务器。
请注意,您不需要运行代理。相反,Go 1.11 中的 go 工具通过 GOPROXY 添加了可选的代理支持,以实现更多的企业用例(例如更大的控制权),并且更好地处理“GitHub 宕机”或人们删除 GitHub 存储库等情况。
我能否控制何时更新 go.mod 以及何时 go 工具使用网络来满足依赖项?
默认情况下,go build
等命令会根据需要访问网络以满足导入。
一些团队希望在某些时候禁止 go 工具访问网络,或者希望对 go 工具何时更新 go.mod
、如何获取依赖项以及如何使用 vendoring 有更大的控制权。
go 工具提供了相当大的灵活性来调整或禁用这些默认行为,包括通过 -mod=readonly
、-mod=vendor
、GOFLAGS
、GOPROXY=off
、GOPROXY=file:///filesystem/path
、go mod vendor
和 go mod download
。
这些选项的详细信息分散在官方文档中。社区尝试对与这些行为相关的旋钮进行综合概述,请参阅 此处,其中包含指向官方文档的链接以获取更多信息。
如何在 Travis 或 CircleCI 等 CI 系统中使用模块?
最简单的方法可能是设置环境变量 GO111MODULE=on
,这应该适用于大多数 CI 系统。
但是,在 Go 1.11 中,同时启用和禁用模块在 CI 中运行测试可能很有价值,因为您的一些用户可能尚未选择模块。vendoring 也是一个需要考虑的话题。
以下两篇博客文章更具体地涵盖了这些主题
- Fatih Arslan 的 “在 Travis CI 上使用 Go 模块和供应商支持”
- Todd Keech 的 “Go 模块和 CircleCI”
如何下载构建特定包或测试所需的模块?
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 tip 文档)。
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 foo@1.2.3
。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
),请参阅下面的常见问题解答,以及 tip 文档中的 “Module downloading and verification” 部分。
此外,您的模块的 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
的简要理由。有关更多详细信息,请参阅 tip 文档的 “模块下载和验证” 部分。有关可能讨论的未来扩展,请参阅 #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 foo@v1.2.3
将在模块 M 的 go.mod
文件中记录如下
require foo v1.2.3
go
工具还将在其他工作流中使用非模块包的可用 semver 标签(例如 go list -u=patch
,它将模块的依赖项升级到可用的补丁版本,或 go list -u -m all
,它显示可用的升级等)。
有关尚未选择模块的 v2+ 包的其他详细信息,请参阅下一个常见问题解答。
模块能否使用尚未选择模块的 v2+ 包?“+incompatible”是什么意思?
是的,一个模块可以导入一个尚未选择模块的 v2+ 包,如果导入的 v2+ 包具有有效的 semver 标签,它将以 +incompatible
后缀记录。
附加详细信息
请熟悉上面 “语义导入版本控制” 部分中的内容。
首先回顾一些通常有用但在此常见问题解答中思考行为时特别重要的核心原则。
当 go
工具在模块模式下运行时(例如,GO111MODULE=on
),以下核心原则始终成立
- 包的导入路径定义了包的身份。
- 导入路径不同的包被视为不同的包。
- 导入路径相同的包被视为相同的包(即使 VCS 标签表明这些包具有不同的主版本,这也是正确的)。
- 不带
/vN
的导入路径被视为 v1 或 v0 模块(即使导入的包尚未选择模块并且 VCS 标签表明主要版本大于 1,这也是正确的)。 - 在模块的
go.mod
文件开头声明的模块路径(例如module foo/v2
)既是- 该模块身份的明确声明
- 该模块必须如何由消费代码导入的明确声明
正如我们将在下一个常见问题解答中看到的,当 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
具有 v3.0.1
的 semver 标签,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 1.8 或 1.7 等较旧的 Go 版本中)在看到诸如 import "mymodule/v2/mypkg"
的导入语句时也会找到适当的包。因此,即使未启用模块支持(无论是由于您正在运行 Go 1.11 但未启用模块,还是由于您正在运行 Go 1.7、1.8、1.9 或 1.10 等没有完整模块支持的旧版本),位于“主子目录”v2+ 模块中的包也将被找到并使用。有关“主子目录”方法的更多详细信息,请参阅 “如何发布 v2+ 模块” 部分。
本 FAQ 的其余部分重点关注 “如何发布 v2+ 模块” 部分中描述的“主分支”方法。在“主分支”方法中,不创建 /vN
子目录,而是通过 go.mod
文件和对提交应用 semver 标签(通常在 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 模式下操作时,如果包含
/vN
的不可解析导入语句位于已选择模块的代码中(即,有效go.mod
文件树中的.go
文件中的导入语句),则在删除/vN
后会再次尝试该导入语句。 - 最终效果是,在 1.9.7+、1.10.3+ 和 1.11 的 GOPATH 模式下,模块代码中的
import "foo/v2"
等导入语句仍将正确编译,并且它将解析为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 磁盘上的任何版本。这允许包含import "foo/v2"
的模块代码甚至在 1.9.7+、1.10.3+ 和 1.11 的 GOPATH 模式下也能编译。
- 相反,当
go
工具在完整的模块模式下运行时- “具有不同导入路径的包是不同包”的规则没有例外(包括 vendoring 在完整的模块模式下也已完善以遵守此规则)。
- 例如,如果
go
工具处于完整的模块模式,并且foo
是一个 v2+ 模块,那么import "foo"
是请求foo
的 v1 版本,而import "foo/v2"
是请求foo
的 v2 版本。
如果我创建了 go.mod 但没有对我的仓库应用 semver 标签,会发生什么?
semver 是模块系统的基础。为了给消费者提供最佳体验,鼓励模块作者应用 semver VCS 标签(例如 v0.1.0
或 v1.2.3-rc.1
),但 semver VCS 标签并非严格要求
-
模块必须遵循semver 规范,以便
go
命令按文档所述运行。这包括遵循 semver 规范中关于何时以及如何允许破坏性更改的规定。 -
没有 semver VCS 标签的模块将由消费者使用 伪版本 形式的 semver 版本进行记录。通常这将是一个 v0 主要版本,除非模块作者按照 “主要子目录” 方法构建了 v2+ 模块。
-
因此,未应用 semver VCS 标签且未创建“主子目录”的模块实际上是在声明自己属于 semver v0 主要版本系列,并且基于模块的消费者将将其视为具有 semver 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 的包的示例)。在这种情况下,您的存储库有一个带自己的
go.mod
的example
或_example
目录可能很有意义,例如 此处 所示。 -
如果您有一个具有复杂依赖关系集的存储库,但您的客户端 API 具有较小的依赖关系集。在某些情况下,拥有一个带自己的
go.mod
的api
或clientapi
或类似目录可能很有意义,或者将该clientapi
分离到自己的存储库中。
然而,对于这两种情况,如果您正在考虑为了大量间接依赖项的性能或下载大小而创建多模块存储库,强烈建议您首先尝试使用 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”将在编译时导致“ambiguous import”错误。
解决这个问题的方法是,让新添加的模块依赖于它“切分”出来的模块,且版本在该模块被切分之后。
让我们用上面的存储库来逐步说明,假设“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/myrepo@v1.3
-
git commit
-
git tag v1.3.0
-
git tag mig/v1.0.0
-
接下来,让我们测试这些。我们不能直接
go build
或go test
,因为 go 命令会尝试从模块缓存中获取每个依赖模块。因此,我们需要使用 replace 规则来使go
命令使用本地副本cd path-to/github.com/my-repo/mig go mod edit -replace github.com/my-repo@v1.3.0=../ go test ./... go mod edit -dropreplace github.com/my-repo@v1.3.0
-
git push origin master v1.3.0 mig/v1.0.0
推送提交和两个标签
另请注意,将来 golang.org/issue/28835 应该会使测试步骤更加直接。
还要注意,代码已从模块“github.com/my-repo”中删除了次要版本。这可能看起来奇怪,不将其视为一个重大更改,但在这种情况下,传递依赖项继续在其原始导入路径提供已删除包的兼容实现。
是否可以从多模块仓库中删除模块?
是的,与上述两个案例和类似步骤相同。
模块能否依赖另一个模块的 internal/?
是的。一个模块中的包可以导入另一个模块中的内部包,只要它们共享相同的路径前缀,直到 internal/ 路径组件。例如,考虑以下存储库
my-repo
|-- foo
| `-- go.mod
|-- go.mod
`-- internal
在这里,只要模块“my-repo/foo”依赖于模块“my-repo”,包 foo 就可以导入 /my-repo/internal。同样,在以下存储库中
my-repo
|-- foo
| `-- go.mod
`-- internal
`-- go.mod
在这里,只要模块“my-repo/foo”依赖于模块“my-repo/internal”,包 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
。- 除非您要求
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 beta 版常见问题来源,但在 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 Modules by Example” 演练 中可以看到更详细的查询模块命令和示例。
版本意外的原因之一可能是有人创建了意想不到的或无效的 go.mod
文件,或相关错误(例如:模块的 v2.0.1
版本可能错误地在 go.mod
中声明自己为 module foo
,而没有必需的 /v2
;.go
代码中意图导入 v3 模块的导入语句可能缺少必需的 /v3
;v4 模块的 go.mod
中的 require
语句可能缺少必需的 /v4
)。因此,如果某个特定问题的原因不明显,值得首先重新阅读上面 “go.mod” 和 “语义导入版本控制” 部分中的内容(因为这些内容包含模块必须遵循的重要规则),然后花几分钟时间检查最相关的 go.mod
文件和导入语句。
为什么我收到错误“cannot find module providing package 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”会报错“cannot determine module path for source directory”?
不带任何参数的 go mod init
将尝试根据 VCS 元数据等不同提示猜测正确的模块路径。但是,不期望 go mod init
始终能够猜测正确的模块路径。
如果 go mod init
给了您这个错误,则这些启发式方法无法猜测,您必须自己提供模块路径(例如 go mod init github.com/you/hello
)。
我有一个复杂的依赖项问题,该依赖项尚未选择模块。我可以使用其当前依赖项管理器中的信息吗?
是的。这需要一些手动步骤,但在某些更复杂的情况下可能会有所帮助。
Go 1.21 及更早版本尝试将之前的依赖项管理器格式转换为 go.mod
格式。因此,以下说明需要 1.21.13 或更早版本;您需要使用 GOTOOLCHAIN=go1.21.13
运行以下命令,或者手动安装更早版本的 go。
当您初始化自己的模块时运行 go mod init
,它将通过将 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,这是一个针对此用例的社区工具。此外,您需要向您的实际 go.mod
添加 require github.com/some/nonmodule v1.2.3
,以匹配您手动克隆的版本。
在 docker 中遵循此技术的具体示例见此 #28489 评论,其中说明了如何获取一致的 docker 依赖项版本集,以避免 github.com/sirupsen/logrus
与 github.com/Sirupsen/logrus
之间的区分大小写问题。
如何解决导入路径与声明的模块身份不匹配导致的“parsing go.mod: unexpected module path”和“error loading module requirements”错误?
为什么会出现这个错误?
通常,模块通过 module
指令在 go.mod
中声明其身份,例如 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/go-consistent@v0.0.0-20190521200055-c6f3937de18c: 解析 go.mod: 意外的模块路径 “github.com/quasilyte/go-consistent” go get: 错误加载模块要求
解决
错误的最常见形式是
go: example.com/some/OLD/name@vX.Y.Z: 解析 go.mod: 意外的模块路径 “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。该仓库没有使用语义版本标签,所以我们将使用紧接之前的提交 00dd7fb039e,并在replace
语句的两侧都使用旧的 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)。如果不清楚是谁在使用有问题的旧导入路径进行导入,通常可以通过创建干净的模块缓存,执行触发错误的操作,然后在使用模块缓存中 grep 旧的有问题的导入路径来找出。例如
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
文件的目录不会被 go mod vendor
复制到 vendor
目录中。这是设计使然。
简而言之,抛开任何特定的 vendoring 行为不谈,Go 构建的总体模型是构建包所需的文件应该与 .go
文件在同一个目录中。
以 cgo 为例,修改其他目录中的 C 源代码不会触发重新构建,而是会使用过时的缓存条目。cgo 文档现在 包含
请注意,对其他目录中文件的更改不会导致包重新编译,因此包的所有非 Go 源代码都应存储在包目录中,而不是子目录中。
社区工具 https://github.com/goware/modvendor 允许您轻松地将一组完整的 .c、.h、.s、.proto 或其他文件从模块复制到 vendor
目录中。尽管这可能很有用,但如果您需要构建包的文件位于 .go
文件所在的目录之外,则需要谨慎确保您的 Go 构建总体上(无论是否使用 vendoring)得到正确处理。
请参阅 #26366 中的其他讨论。
传统供应商模式的另一种方法是检入模块缓存。它最终可以达到与传统供应商模式相似的优势,并且在某些方面最终可以实现更高保真度的复制。此方法在“Go Modules by Example” 演练中有所解释。
此内容是 Go Wiki 的一部分。