Go Wiki:Go Modules
本 Wiki 页面用作使用和故障排除指南。
- 对于教程博客文章,请参阅使用 Go Modules。
- 对于技术参考,请参阅Go Modules 参考(开发中)。
自 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 上出现的“Access is denied”(访问被拒绝)错误提供了解决方法,此错误由外部程序同时扫描文件系统引起(参见问题 #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
变体,它们支持不太常见的用例。详细信息请参阅文档。
- 如果您有私有代码,您很可能应该配置
- 如果找到任何 go.mod 文件,即使在 GOPATH 内,
GO111MODULE=auto
也会启用模块模式。(在 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
)。
目录
对于刚开始使用模块的人来说,“快速入门”和“新概念”部分尤为重要。“操作方法…”部分涵盖了更多机制细节。本页的大部分内容都在回答更具体问题的常见问题解答(FAQ)中;至少浏览一下此处列出的常见问题解答单行摘要也是值得的。
- 快速入门
- 新概念
- 如何使用模块
- 迁移到模块
- 附加资源
- 自最初 Vgo 提案以来的更改
- GitHub 问题
- 常见问题解答 (FAQ)
- 常见问题解答 — 附加控制
- 常见问题解答 — 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
文件的隐式修改。
您典型的日常工作流程可以是
- 根据需要将 import 语句添加到您的
.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
中删除所有不再需要的依赖项,并添加其他 OS、架构和构建标签组合所需的任何依赖项(详细信息)replace
指令或gohack
— 使用依赖项的 fork、本地副本或特定版本(详细信息)go mod vendor
— 创建vendor
目录的可选步骤(详细信息)
阅读完接下来的关于“新概念”的四个部分后,您将获得足够的知识来开始在大多数项目中使用模块。回顾上面的目录(包括其中的常见问题解答单行摘要)以熟悉更详细的主题列表也很有用。
新概念
这些部分提供了主要新概念的高级介绍。有关更多详细信息和原理,请参阅 Russ Cox 介绍设计理念的 40 分钟介绍视频、官方提案文档,或更详细的最初 vgo 博客系列。
模块
模块(module)是相关 Go 包的集合,它们作为一个单元进行版本控制。
模块记录精确的依赖项要求并创建可复现的构建。
通常情况下,版本控制仓库仅在仓库根目录中定义一个模块。(单个仓库支持多个模块,但通常这会比每个仓库一个模块带来更多持续工作)。
总结仓库、模块和包之间的关系
- 一个仓库包含一个或多个 Go 模块。
- 每个模块包含一个或多个 Go 包。
- 每个包由单个目录中的一个或多个 Go 源文件组成。
模块必须根据semver进行语义版本控制,通常采用 v(major).(minor).(patch)
的形式,例如 v0.1.0
、v1.2.3
或 v1.5.0-rc.1
。前导的 v
是必需的。如果使用 Git,请使用版本标记已发布的提交。公共和私有模块仓库和代理正在变得可用(参见下文常见问题解答 below)。
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
指令的讨论,请参阅下文常见问题解答 below)。
版本选择
如果您在源代码中添加了新的导入,但 go.mod
中尚未包含相应的 require
,则像 ‘go build’ 和 ‘go test’ 这样的大多数 go 命令会自动查找适当的模块,并将该新直接依赖项的*最高*版本作为 require
指令添加到您的模块的 go.mod
中。例如,如果您的新导入对应于依赖项 M,其最新标记的发布版本是 v1.2.3
,那么您的模块的 go.mod
最终将包含 require M v1.2.3
,这表明模块 M 是一个依赖项,允许的版本 >= v1.2.3(并且 < v2,因为 v2 被认为与 v1 不兼容)。
*最小版本选择*算法用于选择构建中使用的所有模块的版本。对于构建中的每个模块,最小版本选择所选择的版本始终是主模块或其某个依赖项中的 require
指令明确列出的版本中语义上的*最高*版本。
例如,如果您的模块依赖于包含 require D v1.0.0
的模块 A,并且您的模块还依赖于包含 require D v1.1.1
的模块 B,那么最小版本选择将选择 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 的结果称为*语义导入版本控制*(Semantic Import Versioning),其中主要版本包含在导入路径中——这确保了只要主要版本由于兼容性破坏而增加,导入路径就会改变。
作为语义导入版本控制的结果,选择使用 Go 模块的代码必须遵守以下规则
- 遵循semver。(VCS 标签示例是
v1.2.3
)。 - 如果模块版本为 v2 或更高版本,则模块的主要版本必须作为
/vN
包含在go.mod
文件中使用的模块路径末尾(例如,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/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
)。 - 调用设置了
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
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 -t ./...
类似,但也会升级测试依赖项。
go get foo
更新到 foo
的最新版本。go get foo
等效于 go get foo@latest
— 换句话说,如果未指定 @
版本,则默认使用 @latest
。
在本节中,“latest”是指带有 semver 标签的最新版本,如果没有任何 semver 标签,则指最新的已知提交。预发布标签不会被选为“latest”,除非仓库上没有其他 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
允许通过向包参数添加 @版本 后缀或“模块查询”来覆盖版本选择,例如 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
文件中的伪版本 (pseudo-versions)。
有关此处主题的更多信息,请参阅 go
命令文档中的“模块感知的 go get”和“模块查询”部分。
模块能够消费尚未选择加入模块的包,包括将任何可用的 semver 标签记录在 go.mod
中,并使用这些 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
文件一起提交。有关更多详细信息和理由,请参阅下方的常见问题解答 (FAQ)。
发布模块 (v2 或更高版本)
如果您要发布 v2 或更高版本的模块,请先回顾上面“语义导入版本控制”部分中的讨论,其中解释了为什么主版本包含在 v2+ 模块的模块路径和导入路径中,以及 Go 1.9.7+ 和 1.10.3+ 版本如何更新以简化这一转变。
请注意,如果您是首次为一个在采用模块之前已经标记为 v2.0.0
或更高版本的现有仓库或包集合采用模块,那么推荐的最佳实践是在首次采用模块时增加主版本。例如,如果您是 foo
的作者,且 foo
仓库的最新标签是 v2.2.2
,并且 foo
尚未采用模块,那么最佳实践是为 foo
首次采用模块的发布(也因此是 foo
首次包含 go.mod
文件的发布)使用 v3.0.0
。在这种情况下增加主版本为 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
发布为例,两个选项是
-
主分支方法 (Major branch):更新
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
工具不了解 semver,或者在 Go 1.11+ 中模块模式未启用时)。 - 现有的依赖管理解决方案,例如
dep
,目前在消费以此方式创建的 v2+ 模块时可能遇到问题。例如参阅 dep#1962。
-
主子目录方法 (Major subdirectory):创建一个新的
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 中引入)以及位于不同子目录中的主版本之间的转发 shim。这可以提供额外的兼容性,并允许一个主版本基于另一个主版本实现,但这会给模块作者带来更多工作。一个正在开发中的自动化工具是
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+ 模块,请确保转换后的
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
不包含在初始安装说明中,则“高保真构建 (High Fidelity Builds)” 会带来更多好处。有关更多详细信息,请参阅“如何升级和降级依赖项”。 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
,这会是您当前消费者的一个破坏性更改。一种方法是,如果您稍后迁移到 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
函数。 - 这种方法有助于解决模块系统的多个方面问题,包括帮助解决菱形依赖问题,在大型代码库中逐步迁移到新版本,并允许将一个主版本作为另一个主版本的 shim 来实现。
- 有关一些相关讨论,请参阅https://research.swtch.com/vgo-import 中的“避免单例问题”部分或#27514。
模块消费非模块代码
- 模块能够消费尚未选择加入模块的包,并将适当的包版本信息记录在导入模块的
go.mod
中。模块可以消费尚未拥有任何合适 semver 标签的包。有关更多详细信息,请参阅下方的常见问题解答。 - 模块也可以导入尚未选择加入模块的 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+ 版本.
该方法使用“主分支”方法,并依赖于回port到 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(尽管如果您最终为了支持像 Go 1.8 这样的旧版本而选择上面的策略 2,那么您现在就可以这样做)。
附加资源
文档和提案
- 官方文档
- 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 Modules 提案(最后更新于 2018 年 3 月 20 日)
入门资料
- Russ Cox 在 GopherCon Singapore 上的 40 分钟入门视频“Go 版本控制的原则”(2018 年 5 月 2 日)
- 简洁地介绍了带版本控制的 Go 模块设计的哲学,包括“兼容性”、“可重复性”和“协作”三个核心原则。
- 基于示例的 35 分钟入门视频“什么是 Go 模块以及如何使用它们?” (Paul Jolly 的幻灯片)(2018 年 8 月 15 日)
- Dave Cheney 的入门博客文章“初探 Go Modules”(2018 年 7 月 14 日)
- Chris Hines 关于模块的入门Go Meetup 幻灯片(2018 年 7 月 16 日)
- Francesc Campoy 的 30 分钟入门视频“Go Modules 和 SemVer 简介”(2018 年 11 月 15 日)
补充资料
- Fatih Arslan 的博客文章“在 Travis CI 上使用支持 vendor 的 Go 模块”(2018 年 8 月 26 日)
- Todd Keech 的博客文章“Go Modules 与 CircleCI”(2018 年 7 月 30 日)
- Russ Cox 的博客文章“vgo 提案被接受了。然后呢?”(2018 年 5 月 29 日)
- 包含对带版本控制的模块目前是实验性可选功能意味着什么的总结
- Carolyn Van Slyck 关于如何从 tip 构建 Go 并开始使用 Go 模块的博客文章(2018 年 7 月 16 日)
自最初 Vgo 提案以来的更改
作为提案、原型和 Beta 测试过程的一部分,整个社区已经创建了 400 多个问题。请继续提供反馈意见。
以下是一些主要更改和改进的部分列表,其中几乎所有都主要基于社区的反馈意见
- 保留了顶级 vendor 支持,而不是让基于 vgo 的构建完全忽略 vendor 目录(讨论,变更列表 (CL))
- 回port了最小模块感知功能,以便 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) 是最近的更改)
- 添加了更灵活的 replace 指令(变更列表 (CL))
- 增加了更多查询模块的方法(供人工查看,以及更好地集成到编辑器/IDE 中)
- go CLI 的用户体验一直在根据目前的经验进行改进(例如,#26581,变更列表 (CL))
- 增加了预热缓存的支持,用于 CI 或 Docker 构建等用例,通过
go mod download
命令实现(#26610) - 很可能:更好地支持将特定版本的程序安装到 GOBIN(#24250)
GitHub 问题
- 当前开放的模块问题
- 已关闭的模块问题
- 已关闭的 vgo 问题
- 提交一个新的模块问题,使用 ‘cmd/go:’ 作为前缀
常见问题解答 (FAQ)
版本如何被标记为不兼容?
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
文件,然后丢弃。这已经由 一个简单的 shell 脚本 由 @rogpeppe 自动化完成。该脚本允许通过vgoget example.com/cmd[@version]
选择性地提供版本信息。(这可以解决避免错误cannot use path@version syntax in GOPATH mode
的问题)。 -
gobin
是一个支持模块的命令,用于安装和运行 main 包。默认情况下,gobin
安装/运行 main 包时不需要先手动创建模块,但使用-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 及更高版本中,您可以将 tool
指令添加到您的 go.mod 中
go 1.24
...
tool golang.org/x/tools/cmd/stringer
在 Go 1.24 之前,推荐的方法是向模块添加一个 tools.go
文件,其中包含感兴趣的工具的 import 语句(例如 import _ "golang.org/x/tools/cmd/stringer"
),以及一个 //go:build tools
构建约束。import 语句允许 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
会自动克隆相应的仓库并将必要的replace
指令添加到您的go.mod
中 - 使用
gohack undo
删除所有 gohack replace 语句 - 该项目正在继续扩展,以使其他与模块相关的工作流程更容易
- 一个新的社区工具,用于自动化和极大地简化
- github.com/marwan-at-work/mod
- 命令行工具,自动升级/降级模块的主要版本
- 自动调整
go.mod
文件和 Go 源代码中相关的 import 语句 - 有助于升级,或者在使用 v2+ 包时首次选择加入模块
- github.com/akyoto/mgit
- 让您查看和控制所有本地项目的语义版本标签
- 显示未标记的提交,并允许您一次性标记它们(
mgit -tag +0.0.1
)
- github.com/goware/modvendor
- 帮助将额外文件(如 shell 脚本、.cpp 文件、.proto 文件等)复制到
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
博客系列确实提议完全放弃 Vendoring,但社区的 反馈 导致保留了对 Vendoring 的支持。
简而言之,要将 Vendoring 与模块一起使用
go mod vendor
重置主模块的 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 系统。
然而,考虑到您的一些用户尚未选择加入模块,在 CI 中同时启用和禁用模块的 Go 1.11 上运行测试可能很有价值。Vendoring 也是一个需要考虑的话题。
以下两篇博客文章更具体地介绍了这些主题
- Fatih Arslan 的 “在 Travis CI 上使用 Go 模块并支持 Vendoring”
- Todd Keech 的 “Go Modules 和 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
反映所有可能的操作系统、架构和构建标签组合的依赖需求(如 此处 所述)。相比之下,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 文档中的 “Module downloading and verification” 部分。请参阅可能正在讨论的未来扩展,例如在 #24117 和 #25530 中。
如果我没有任何依赖项,我是否仍应添加 ‘go.mod’ 文件?
是的。这支持在 GOPATH 之外工作,有助于向生态系统传达您正在选择加入模块,此外,您 go.mod
文件中的 module
指令是您的代码身份的明确声明(这也是 import comments 最终可能被弃用的原因之一)。当然,在 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 工具还将在其他工作流程中(例如 go list -u=patch
,它将模块的依赖项升级到可用的补丁版本,或者 go list -u -m all
,它显示可用的升级等)使用非模块包的可用 semver 标签。
请参阅下一个常见问题解答,了解与尚未选择加入模块的 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
有一个 语义化版本(semver) 标签 v3.0.1
,但 oldpackage
并未被授予 语义化导入版本控制 的权利和责任(例如在导入路径中使用 /vN
),因为它尚未声明这样做。
+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
。 - 基于模块的消费者(即已选择加入模块模式的代码)在导入 v2+ 模块时必须在导入路径中包含
/vN
。
然而,预计模块和语义化导入版本控制的生态系统将以不同的速度推进其采用进程。
正如 “如何发布 v2+ 模块” 部分更详细描述的那样,在“主版本子目录”方法中,v2+ 模块的作者创建诸如 mymodule/v2
或 mymodule/v3
之类的子目录,并将相应的包移动或复制到这些子目录下方。这意味着传统的导入路径逻辑(即使在 Go 1.8 或 1.7 等较旧的版本中)在看到诸如 import "mymodule/v2/mypkg"
的导入语句时会找到相应的包。因此,即使模块支持未启用,驻留在“主版本子目录”v2+ 模块中的包也会被找到和使用(无论是您正在运行 Go 1.11 但未启用模块,还是您正在运行 Go 1.7、1.8、1.9 或 1.10 等没有完整模块支持的旧版本)。有关“主版本子目录”方法的更多详细信息,请参阅 “如何发布 v2+ 模块” 部分。
本 FAQ 的其余部分侧重于 “如何发布 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+ 模块,而无需旧的消费者代码在使用 v2+ 模块时立即更改为使用新的
/vN
导入路径。 -
这样做而无需依赖模块作者创建
/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"
)在 1.9.7+、1.10.3+ 和 1.11 的 GOPATH 模式下仍然可以正确编译,并且会解析为如同写着import "foo"
(没有/v2
)一样,这意味着它将使用位于您的 GOPATH 中的foo
版本,而不会被额外的/v2
混淆。 - “最低限度模块兼容性”不影响其他任何事物,包括不影响
go
命令行中使用的路径(例如go get
或go list
的参数)。
- 其全部逻辑是——当在 GOPATH 模式下运行时,如果导入语句位于已选择加入模块模式的代码中(即,在包含有效
- 这种过渡性的“最低限度模块感知”机制有意打破了“具有不同导入路径的包被视为不同的包”的规则,以追求一个非常具体的向后兼容性目标——允许旧代码在使用 v2+ 模块时无需修改即可编译。更详细地说
- 如果旧代码使用 v2+ 模块的唯一方法是先修改旧代码,那将对整个生态系统造成更大的负担。
- 如果我们不修改旧代码,那么旧代码必须使用 v2+ 模块的非模块化导入路径。
- 另一方面,选择加入模块模式的新代码或更新代码必须使用 v2+ 模块的新的
/vN
导入路径。 - 新的导入路径与旧的导入路径不等同,但两者都允许在单个构建中工作,因此我们有两种不同的功能性导入路径解析到同一个包。
- 例如,在 GOPATH 模式下运行时,出现在基于模块的代码中的
import "foo/v2"
会解析到与import "foo"
一样位于您 GOPATH 中的相同代码,并且构建最终会得到一个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) 是模块系统的基础。为了给消费者提供最佳体验,鼓励模块作者应用语义化版本 VCS 标签(例如 v0.1.0
或 v1.2.3-rc.1
),但语义化版本 VCS 标签不是严格要求的
-
模块必须遵循语义化版本规范,才能使
go
命令按文档所述运行。这包括遵循关于何时允许破坏性更改的语义化版本规范。 -
没有语义化版本 VCS 标签的模块会被消费者以 伪版本 的形式记录为一个语义化版本。通常这将是一个 v0 主版本,除非模块作者遵循 “主版本子目录” 方法构建了 v2+ 模块。
-
因此,未应用语义化版本 VCS 标签且未创建“主版本子目录”的模块实际上是在声明自己属于语义化版本 v0 主版本系列,基于模块的消费者会将其视为具有语义化版本 v0 主版本。
模块可以依赖于它自己的不同版本吗?
一个模块可以依赖于自身的另一个主版本:这大致上与依赖于另一个模块相当。这在多种情况下都很有用,包括允许一个模块的主版本作为另一个主版本的垫片(shim)实现。
此外,一个模块可以在循环中依赖于自身的另一个主版本,就像两个完全不同的模块可以在循环中相互依赖一样。
然而,如果您不期望一个模块依赖于自身的另一个版本,这可能是一个错误的迹象。例如,旨在从 v3 模块导入包的 .go 代码可能在导入语句中缺少必需的 /v3
。这个错误可能表现为一个 v3 模块依赖于自身的 v1 版本。
如果您惊讶地看到一个模块依赖于自身的另一个版本,那么回顾上面的 “语义化导入版本控制” 部分以及 FAQ “如果我看不到依赖项的预期版本,可以检查什么?” 可能是有益的。
两者之间仍然存在一个限制,即两个 包 不能在循环中相互依赖。
常见问题解答 — 多模块仓库
什么是多模块仓库?
多模块仓库是一个包含多个模块的仓库,每个模块都有自己的 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
图。顶层模块的路径是另一个模块路径的前缀。
这个仓库包含两个模块。然而,模块 “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” 将在编译时导致“模糊导入”错误。
解决这个问题的方法是让新添加的模块依赖于它被“拆分”出来的模块,依赖的版本是拆分发生之后的版本。
让我们以上述仓库为例逐步进行,假设“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
,因为这个特性通常被称为“modules”)。
- 注意:您从不将
- 如果预期使用 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 beta 版本中常见的问题来源,但在 Go 1.11 GA 版本中已不那么常见。
- 较旧的 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
的输出。go mod graph
打印模块需求图(包括考虑了 replace 指令)。输出中的每一行都有两个字段:第一列是消费模块,第二列是该模块的一个需求(包括该消费模块要求的版本)。这是一种快速查看哪些模块需要特定依赖项的方法,包括当您的构建中的一个依赖项有来自不同消费者的不同所需版本时(如果是这种情况,熟悉上面 “版本选择” 部分描述的行为非常重要)。
go mod why -m
在这里也可能有用,尽管它通常更常用于查看为什么包含某个依赖项(而不是该依赖项为何最终为特定版本)。
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
,以匹配您手动克隆的版本。
在此 #28489 评论中有一个针对 docker 遵循此技术的具体示例,它说明了如何获取一组一致的 docker 依赖项版本,以避免 github.com/sirupsen/logrus
与 github.com/Sirupsen/logrus
之间的区分大小写问题。
如何解决由于导入路径与声明的模块标识不匹配导致的“parsing go.mod: unexpected module path”和“error loading module requirements”错误?
为什么会出现此错误?
通常,模块通过其 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
),或者由于虚荣导入路径(vanity import path)的原因,模块在模块化之前有时通过两个不同的名称使用(例如,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: parsing go.mod: unexpected module path “github.com/quasilyte/go-consistent” go get: error loading module requirements
解决办法
此错误最常见的形式是:
go: example.com/some/OLD/name@vX.Y.Z: 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,并在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 中有所记录)。
- 如果上述步骤未能解决问题,可能是因为您一个或多个依赖项的最新版本仍在 P使用有问题的旧导入路径。在这种情况下,重要的是找出谁仍在 P使用有问题的旧导入路径,并查找或提出问题,要求有问题的导入方更改为使用当前的规范导入路径。在上述步骤 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` 文件所在的目录之外,您仍需小心谨慎,确保您的 Go 构建总体上得到正确处理(无论是否使用 vendoring)。
请参阅 #26366 中的更多讨论。
传统 vendoring 的另一种方法是将模块缓存签入版本控制。它最终可以获得与传统 vendoring 类似的益处,并且在某些方面会得到更高保真度的副本。这种方法在“Go Modules by Example”教程中有所解释。
此内容是 Go Wiki 的一部分。