官方原文: https://github.com/golang/go/wiki/Modules
Go 1.11包括此处建议的对版本模块的初步支持。模块是Go 1.11中的实验性加入功能,并计划纳入反馈并最终确定 Go 1.14中的功能。即使某些细节可能会更改,将来的发行版也将支持使用Go 1.11、1.12和1.13定义的模块。
最初的原型vgo
于2018年2月宣布。2018年7月,版本化的模块进入了主Go存储库。
请通过现有问题或新问题以及经验报告提供有关模块的反馈。
近期变动
Go 1.13中对模块进行了重大改进和更改。
如果使用模块,请务必仔细阅读Go 1.13发行说明的模块部分。
三个值得注意的变化:
-
该
go
工具现在默认为从https://proxy.golang.org上的公共Go模块镜像下载模块,并且还默认为针对https://sum.golang.org上的公共Go校验和数据库验证下载的模块(无论源如何)。- 如果您有私人代码,则很可能应该配置
GOPRIVATE
设置(例如go env -w GOPRIVATE=*.corp.com,github.com/secret/repo
),或者配置更细粒度的变体,GONOPROXY
或者GONOSUMDB
支持使用频率较低的用例。有关更多详细信息,请参见文档。
- 如果您有私人代码,则很可能应该配置
-
GO111MODULE=auto
如果找到任何go.mod,即使在GOPATH内部,也将启用模块模式。(在Go 1.13之前,GO111MODULE=auto
永远不会在GOPATH中启用模块模式)。 -
go get
参数已更改:go get -u
(不带任何参数)现在仅升级当前软件包的直接和间接依赖关系,而不再检查整个模块。go get -u ./...
从模块根目录升级模块的所有直接和间接依赖关系,现在不包括测试依赖关系。go get -u -t ./...
相似,但也升级了测试依赖项。go get
不再受支持-m
(因为go get -d
由于其他更改,它会在很大程度上与重叠;您通常可以替换go get -m foo
为go get -d foo
)。
请参阅发行说明,以获取有关这些更改和其他更改的更多详细信息。
快速开始
例
详细信息将在本页面的其余部分中介绍,但这是一个从头开始创建模块的简单示例。
在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 < hello.go
package main
import (
"fmt"
"rsc.io/quote"
)
func main() {
fmt.Println(quote.Hello())
}
EOF
构建并运行:
$ go build -o hello
$ ./hello
Hello, world.
该go.mod
文件已更新为包括依赖项的显式版本,v1.5.2
这里是语义化版本号标签:
$ cat go.mod
module github.com/my/repo
require rsc.io/quote v1.5.2
日常工作流程
请注意,go get
上面的示例中没有要求。
典型的日常工作流程可以是:
.go
根据需要将导入语句添加到您的代码中。- 标准命令(例如
go build
或)go test
会根据需要自动添加新的依赖关系,以实现导入(更新go.mod
和下载新的依赖关系)。 - 需要时,可以使用
go get [email protected]
,go get foo@master
(foo@tip
带有Mercurial)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
—使用派生,本地副本或依赖项的确切版本(详细信息)go mod vendor
—创建vendor
目录的可选步骤(详细信息)
在阅读了有关“新概念”的下四个部分之后,您将获得足够的信息来开始使用大多数项目的模块。查看上面的目录(包括此处的FAQ常见问题解答)以使自己熟悉更详细的主题列表也很有用。
新概念
这些部分对主要的新概念进行了高级介绍。有关更多详细信息和原理,请观看Russ Cox的这段40分钟的介绍性视频,其中介绍了设计背后的理念,正式的建议文档或更为详细的初始vgo博客系列。
模块
一个模块是一些以版本作为单元相关的包的集合。
模块记录精确的依赖要求并创建可复制的构建。
通常,版本控制存储库仅包含在存储库根目录中定义的一个模块。(单个存储库中支持多个模块,但是通常,与每个存储库中的单个模块相比,这将导致正在进行的工作更多)。
总结存储库,模块和软件包之间的关系:
- 一个存储库包含一个或多个Go模块。
- 每个模块包含一个或多个Go软件包。
- 每个软件包都在一个目录中包含一个或多个Go源文件。
模块必须根据在语义版本语义化版本号,通常在形式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
。
这是go.mod
定义模块的示例文件github.com/my/thing
:
module github.com/my/thing
require (
github.com/some/dependency v1.2.3
github.com/another/dependency/v4 v4.0.0
)
模块go.mod
通过module
提供模块path的指令在其声明中声明其身份。模块中所有软件包的导入路径将模块路径共享为公共前缀。模块路径和从go.mod
到软件包目录的相对路径共同确定了软件包的导入路径。
例如,如果要为存储库创建一个模块,该模块github.com/my/repo
将包含两个带有导入路径github.com/my/repo/foo
和的软件包github.com/my/repo/bar
,则go.mod
文件中的第一行通常会将模块路径声明为module github.com/my/repo
,相应的磁盘结构可以是:
repo
|-- bar
| `-- bar.go
|-- foo
| `-- foo.go
`-- go.mod
在Go源代码中,将使用完整路径(包括模块路径)导入软件包。例如,如果一个模块在go.mod
as中声明其身份module example.com/my/module
,则使用者可以执行以下操作:
import "example.com/my/module/mypkg"
这mypkg
将从模块导入包example.com/my/module
。
exclude
而replace
指令仅在当前(“主”)模块上运行。在构建主模块时,将忽略除主模块以外的其他模块中的指令exclude
和replace
指令。该replace
和exclude
语句,因此,允许在自己的构建主要模块的完全控制权,也没有受制于由依赖于完全控制。(有关何时使用指令的讨论,请参见下面的常见问题解答replace
)。
版本选择
如果您在源代码中添加了一个尚未被require
in 覆盖的新导入,则go.mod
大多数go命令(例如“ go build”和“ go test”)将自动查找适当的模块,并将该新直接依赖项的最高版本添加到您的模块go.mod
是require
指令。例如,如果您的新导入对应于依赖项M,其最新标记版本为v1.2.3
,则模块的go.mod
结尾将为require M v1.2.3
,这表示模块M是允许版本> = v1.2.3(且
在最小的版本选择算法用来选择在构建中使用的所有模块的版本。对于构建中的每个模块,通过最小版本选择选择的版本始终是主模块中的指令或其依赖项之一明确列出的版本的语义上最高的版本require
。
例如,如果您的模块依赖于具有A的模块A require D v1.0.0
,而您的模块也依赖于具有A的模块B require D v1.1.1
,则最小的版本选择将选择v1.1.1
D包含在构建中(假设它是列出的最高require
版本)。v1.1.1
即使稍后某个v1.2.0
D可用,对D的选择仍保持一致。这是模块系统如何提供100%可复制构建的示例。准备就绪后,模块作者或用户可以选择升级到D的最新可用版本,或为D选择一个显式版本。
有关最小版本选择算法的简要原理和概述,请参阅官方建议书的“高保真度构建”部分,或查看更详细的vgo
博客系列。
要查看所选模块版本的列表(包括间接依赖关系),请使用go list -m all
。
另请参见下面的“如何升级和降级依赖项”部分和“如何将版本标记为不兼容?” 下面的常见问题解答。
语义导入版本控制
多年以来,官方的Go常见问题解答已在软件包版本管理中包括以下建议:
“面向公共用途的软件包应在发展过程中尝试保持向后兼容性。Go1兼容性指南在此处是很好的参考:请勿删除导出的名称,鼓励带标签的复合文字等等。如果需要不同的功能,请添加一个新名称,而不是更改旧名称。如果需要完全中断,请创建一个具有新导入路径的新软件包。”
最后一句特别重要-如果破坏兼容性,则应更改软件包的导入路径。使用Go 1.11模块,该建议被正式化为导入兼容性规则:
“如果旧软件包和新软件包具有相同的导入路径,则新软件包必须与旧软件包向后兼容。”
当v1或更高版本的软件包进行向后不兼容的更改时,召回语义化版本号需要对主要版本进行更改。遵循导入兼容性规则和语义化版本号的结果称为语义导入版本控制,其中主要版本包含在导入路径中-这可确保在主要版本由于兼容性中断而增加时,导入路径都会更改。
由于语义导入版本控制,选择加入Go模块的代码必须遵守以下规则:
- 跟随语义化版本号。(示例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/[email protected]
请注意,在该示例中同时包含a/v2
和a@v2.0.1
。一种考虑方式是模块名称现在包括/v2
,因此/v2
无论何时使用模块名称,都包括)。 - 如果模块的版本为v0或v1,则在模块路径或导入路径中都不要包含主版本。
通常,具有不同导入路径的软件包是不同的软件包。例如,与math/rand
是不同的软件包crypto/rand
。如果不同的导入路径是由于导入路径中出现的主要版本不同而导致的,则也是如此。因此,与example.com/my/mod/mypkg
包是一个不同的包example.com/my/mod/v2/mypkg
,两者都可以在一个单一版本中导入,这除其他优点外还有助于解决钻石依赖问题,并且还允许在替换v2方面实施v1模块,反之亦然。
有关语义导入版本控制的更多详细信息,请参见命令文档的“模块兼容性和语义版本控制”部分,go
有关语义版本控制的更多信息,请参见https://语义化版本号.org。
到目前为止,本节的重点是已选择加入模块并导入其他模块的代码。但是,将主要版本置于v2 +模块的导入路径中可能会与Go的较早版本或尚未选择加入模块的代码产生不兼容性。为了解决这个问题,上述行为和规则有三种重要的过渡性特殊情况或例外。随着越来越多的程序包加入模块,这些过渡性异常将不再重要。
三个例外:
-
gopkg.in
使用导入路径
gopkg.in
(以gopkg.in/yaml.v1
和开头)的现有代码gopkg.in/yaml.v2
即使选择加入模块,也可以继续将这些格式用于其模块路径和导入路径。 -
导入非模块v2 +软件包时为“ +不兼容”
模块可以导入尚未选择加入模块的v2 +软件包。具有有效v2 + 语义化版本号标签的非模块v2 +软件包将
+incompatible
在导入模块的go.mod
文件中记录后缀。该+incompatible
后缀表示,即使V2 +包有一个有效的V2 + 语义化版本号标签,例如v2.0.0
,使V2 +包没有主动选择的模块和假设,因此该V2 +包都没有被与语义进口版本的含义的理解产生以及如何在导入路径中使用主要版本。因此,当以模块模式运行时,go
该工具会将非模块v2 +软件包视为该软件包的v1版本系列的(不兼容)扩展,并假定该软件包不了解语义导入版本控制,并且+incompatible
后缀表示该go
工具正在这样做。 -
未启用模块模式时的“最小模块兼容性”
为了帮助向后兼容,对Go版本1.9.7 +,1.10.3 +和1.11进行了更新,以使使用这些发行版构建的代码能够更轻松地正确使用v2 +模块,而无需修改现有代码。此行为称为“最小模块兼容性”,并且仅在禁用该工具的完整模块模式时才生效
go
,例如您GO111MODULE=off
在Go 1.11中进行了设置,或者正在使用Go 1.9.7+或1.10.3版本+。当依靠Go 1.9.7 +,1.10.3 +和1.11中的这种“最小模块兼容性”机制时,未选择模块的软件包将不会在任何导入的v2 +模块的导入路径中包含主版本。相比之下,已选择在模块必须包括在导入路径主要版本导入任何V2 +模块(为了正确导入V2 +模块时的go
工具在全模块模式语义进口版本的充分认识工作)。
有关发布v2 +模块所需的确切机制,请参阅下面的“发布模块(v2或更高版本)”部分。
如何使用模块
如何安装和激活模块支持
要使用模块,两个安装选项是:
- 安装最新的Go 1.11版本。
- 从
master
分支上的源代码安装Go工具链。
安装后,您可以通过以下两种方式之一激活模块支持:
go
在$GOPATH/src
树之外的目录中调用命令,并go.mod
在当前目录或其任何父目录中使用有效文件,并且未GO111MODULE
设置(或显式设置为auto
)环境变量。- 调用
go
带有GO111MODULE=on
环境变量设置的命令。
如何定义模块
为go.mod
现有项目创建一个:
-
导航到GOPATH之外的模块源代码树的根目录:
$ cd
# e.g., cd ~/projects/hello 请注意,在GOPATH之外,您无需进行设置
GO111MODULE
即可激活模块模式。或者,如果要在GOPATH中工作:
$ export GO111MODULE=on # manually active module mode $ cd $GOPATH/src/
# e.g., cd $GOPATH/src/you/hello -
创建初始模块定义并将其写入
go.mod
文件:$ go mod init
此步骤从任何现有文件或其他所有9种受支持的依赖项格式中的任何一种转换,添加require语句以匹配现有配置。
dep
Gopkg.lock
go mod init
通常可以使用辅助数据(例如VCS元数据)来自动确定适当的模块路径,但是如果go mod init
状态不能自动确定模块路径,或者如果您需要以其他方式覆盖该路径,则可以提供模块路径作为的可选参数go mod init
,例如:$ go mod init github.com/my/repo
请注意,如果您的依赖项包括v2 +模块,或者正在初始化v2 +模块,则在运行后,
go mod init
您可能还需要编辑go.mod
和.go
代码,以添加/vN
导入路径和模块路径,如上面“语义导入版本控制”部分所述。即使go mod init
自动从dep
或其他依赖项管理器转换了您的依赖项信息,这也适用。(因此,在运行之后go mod init
,通常go mod tidy
只有在成功运行go build ./...
或类似操作后才能运行,这是本节中显示的顺序)。 -
生成模块。从模块的根目录执行时,该
./...
模式将匹配当前模块中的所有软件包。go build
会根据需要自动添加缺少或未转换的依赖项,以满足此特定构建调用的导入需求:$ go build ./...
-
按照配置测试模块,以确保它可以与所选版本一起使用:
$ go test ./...
-
(可选)运行模块的测试以及所有直接和间接依赖项的测试,以检查不兼容性:
$ go test all
在标记发行版之前,请参见下面的“如何准备发行版”部分。
有关所有这些主题的更多信息,可在golang.org上找到官方模块文档的主要入口点。
如何升级和降级依赖项
日常的依赖关系升级和降级应该使用“ go get”完成,它将自动更新go.mod
文件。或者,您可以go.mod
直接编辑。
此外,执行“执行构建”,“执行测试”甚至“执行列表”之类的go命令将根据需要自动添加新的依赖关系,以满足导入要求(更新go.mod
和下载新的依赖关系)。
要查看所有直接和间接依赖项的可用次要和补丁升级,请运行go list -u -m all
。
要将当前模块的所有直接和间接依赖关系升级到最新版本,可以在模块根目录中运行以下命令:
go get -u ./...
使用最新的次要版本或补丁程序版本(并添加-t
以升级测试依赖项)go get -u=patch ./...
使用最新的补丁程序发行版(并添加-t
以升级测试依赖项)
go get foo
更新到的最新版本foo
。go get foo
等效于go get foo@latest
—换句话说,@latest
如果未@
指定版本,则为默认值。
在本节中,“最新”是带有语义化版本号标签的最新版本,或者如果没有语义化版本号标签则是最新的已知提交。除非存储库中没有其他语义化版本号标签,否则不会将预发布标签选择为“最新”标签(details)。
一个普遍的错误是认为go get -u foo
仅获取最新版本的foo
。实际上,-u
in go get -u foo
或的go get -u foo@latest
意思是还获得的所有直接和间接依赖关系的最新版本foo
。升级时,一个共同的起点foo
是不是做go get foo
或go get foo@latest
没有-u
(和后一切正常,可以考虑go get -u=patch foo
,go get -u=patch
,go get -u foo
,或go get -u
)。
要升级或降级到一个更具体的版本,“去把”允许版本选择通过添加一个后缀@version或覆盖“模块查询”到包的说法,比如go get [email protected]
,go get foo@e3702bed2
或者go get foo@'
无论是否具有语义化版本号标记,使用分支名称(例如go get foo@master
(foo@tip
带有mercurial))都是获取最新提交的一种方法。
通常,不能解析为语义化版本号标签的模块查询将作为伪版本记录在go.mod
文件中。
有关此处的主题的更多信息,请参见命令文档的“支持模块的获取”和“模块查询”部分go
。
模块能够使用尚未加入模块的软件包,包括在其中记录任何可用的语义化版本号标签go.mod
并使用这些语义化版本号标签进行升级或降级。模块还可以使用尚没有任何适当语义化版本号标签的软件包(在这种情况下,它们将使用中的伪版本进行记录go.mod
)。
在升级或降级任何依赖项之后,您可能想要对构建中的所有软件包(包括直接和间接依赖项)再次运行测试以检查不兼容性:
$ go test all
如何准备发布
发行模块(所有版本)
创建模块发行版的最佳实践有望作为初始模块实验的一部分出现。其中许多最终可能会由将来的“发布”工具自动化。
在标记版本之前,应考虑一些当前建议的最佳做法:
-
运行
go mod tidy
到可能修剪任何无关的要求(如描述在这里),并确保您的当前go.mod反映了所有可能的堆积标签/ OS /架构的组合(如描述在这里)。- 相反,其他命令(例如,
go build
并且go test
不会从中删除依赖项)go.mod
不再需要,而是仅go.mod
基于当前构建调用的标记/ OS /体系结构进行更新。
- 相反,其他命令(例如,
-
运行
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
to作为)。包含一个go.mod
文件)。在这种情况下,增加主要版本可为的使用者提供更大的清晰度foo
,从而允许在v2系列的其他非模块补丁或次要发行版上使用foo
如果需要,并提供了一个基于模块的消费者一个强烈的信号foo
,不同的主要版本,如果你做的结果import "foo"
和相应的require foo v2.2.2+incompatible
,与import "foo/v3"
和相应require foo/v3 v3.0.0
。(请注意,关于递增主要版本时,首先采用模块不会这个建议并不适用于预先存在的回购或包,其最新版本v0.xx或v1.xx)。
有两种替代机制可以发布v2或更高版本的模块。请注意,使用这两种技术,当模块作者推送新标签时,新模块版本就可以供消费者使用。以创建v3.0.0
发行版为例,两个选项是:
-
Major分支:更新
go.mod
文件以/v3
在module
指令的模块路径末尾包含a (例如module github.com/my/module/v3
)。更新模块中的import语句以也使用/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上发布的,并且希望
v3.0.0
在master上进行标记,那么这是一个可行的选择。(但是,要知道,在引入一个不兼容的API的变化master
可能会导致谁发出非模块用户的问题go get -u
给出的go
工具是不知道的语义化版本号之前去1.11或当模块模式在Go 1.11+未启用)。 - 诸如
dep
当前之类的现有依赖关系管理解决方案在使用这种方式创建的v2 +模块时可能会遇到问题。参见例如dep#1962。
-
主要子目录:创建一个新的
v3
子目录(例如my/module/v3
),然后go.mod
在该子目录中放置一个新文件。模块路径必须以结尾/v3
。将代码复制或移动到v3
子目录中。更新模块中的import语句以也使用/v3
(例如import "github.com/my/module/v3/mypkg"
)。用标记发布v3.0.0
。- 这提供了更大的向后兼容性。特别是,低于1.9.7和1.10.3的Go版本也能够正确使用和构建使用此方法创建的v2 +模块。
- 这里一种更复杂的方法可以利用类型别名(在Go 1.9中引入)并在驻留在不同子目录中的主要版本之间转发填充。这可以提供额外的兼容性,并允许以另一个主要版本的形式实现一个主要版本,但是对于模块作者而言,这将需要更多的工作。正在进行自动化的工具是
goforward
。请在此处查看更多详细信息和基本原理,以及可正常运行的goforward
。 - 预先存在的依赖项管理解决方案,例如
dep
应该能够使用以这种方式创建的v2 +模块。
有关这些替代方案的更深入讨论,请参见https://research.swtch.com/vgo-module。
发布发行
可以通过将标签推送到包含模块源代码的资源库中来发布新的模块版本。标签是通过串联两个字符串形成的:前缀和版本。
该版本是该发行版的语义导入版本。应该按照语义导入版本控制规则进行选择。
所述前缀指示其中模块的存储库中定义的。如果模块是在存储库的根目录中定义的,则前缀为空,而标记仅为版本。但是,在多模块存储库中,前缀区分不同模块的版本。前缀是存储库中定义模块的目录。如果存储库遵循上述主要子目录模式,则前缀不包括主要版本后缀。
例如,假设我们有一个模块example.com/repo/sub/v2
,并且我们要发布version v2.1.6
。存储库根目录与相对应example.com/repo
,并且模块sub/v2/go.mod
在存储库内定义。此模块的前缀是sub/
。此版本的完整标签应为sub/v2.1.6
。
迁移到模块
本节试图简要列举迁移到模块时要做出的主要决定,并列出其他与迁移相关的主题。通常会提供其他部分的参考,以获取更多详细信息。
该材料主要基于模块实验中社区中出现的最佳实践。因此,这是一个进行中的部分,随着社区获得更多的经验,该部分将有所改善。
摘要:
- 该模块系统旨在允许整个Go生态系统中的不同软件包以不同的速率选择加入。
- 主要由于语义导入版本控制的影响,已经在版本v2或更高版本上的软件包具有更多的迁移注意事项。
- 在采用模块时,新软件包和v0或v1上的软件包的考虑要少得多。
- 使用Go 1.11定义的模块可以用于较旧的Go版本(尽管确切的Go版本取决于主模块使用的策略及其依赖项,如下所述)。
迁移主题:
从先前的依赖管理器自动迁移
go mod init
将所需的信息从dep,glid,govendor,godep和其他5个先前存在的依赖项管理器自动转换为go.mod
生成等效构建的文件。- 如果要创建v2 +模块,请确保
module
转换后的指令中go.mod
包含相应的指令/vN
(例如module foo/v3
)。 - 请注意,如果要导入v2 +模块,则可能需要在初始转换后进行一些手动调整,以便添加
/vN
到从先前的依赖项管理器转换后生成的require
语句中go mod init
。有关更多详细信息,请参见上面的“如何定义模块”部分。 - 另外,
go mod init
将不会编辑您的.go
代码以添加任何需要/vN
导入的语句。有关所需步骤,请参见上面的“语义导入版本控制”和“发布模块(v2或更高版本)”部分,包括围绕社区工具进行自动转换的一些选项。
向Go和非模块消费者的较旧版本提供依赖信息
go mod vendor
禁用模块模式时,Go的较早版本了解如何使用由创建的vendor目录,Go 1.11和1.12+也是如此。因此,供应是模块提供依赖的一种方式,该依赖提供了对不能完全理解模块的Go的较旧版本以及未启用模块本身的使用者的依赖。有关更多详细信息,请参见vendor常见问题解答和go
命令文档。
更新现有安装说明
- 在预模块中,通常包含安装说明
go get -u foo
。如果要发布模块foo
,请考虑-u
为基于模块的使用者使用in指令。-u
要求go
工具升级的所有直接和间接依赖foo
。- 模块使用者可以选择
go get -u foo
稍后运行,但是如果它不是初始安装说明的一部分,则“ High Fidelity Builds”还有更多好处-u
。有关更多详细信息,请参见“如何升级和降级依赖项”。 go get -u foo
仍然有效,并且仍然可以作为安装说明的有效选择。
- 另外,
go get foo
对于基于模块的使用者,并非严格需要。- 只需添加import语句
import "foo"
就足够了。(后续命令如go build
或go test
会根据需要自动下载foo
和更新go.mod
)。
- 只需添加import语句
vendor
默认情况下,基于模块的使用者将不使用目录。- 如果在
go
工具中启用了模块模式,vendor
则使用模块时并不需要严格要求(鉴于中包含的信息go.mod
和中的密码校验和go.sum
),但是某些预先存在的安装说明假定该go
工具将vendor
默认使用。有关更多详细信息,请参见vendor常见问题解答。
- 如果在
go get foo/...
在某些情况下,安装说明可能包含问题(请参阅#27215中的讨论)。
避免破坏现有的导入路径
模块go.mod
通过module
指令(例如)在其声明中声明其身份module github.com/my/module
。任何模块支持的使用者都必须使用与模块声明的模块路径匹配的导入路径(确切地说是针对根软件包,或将模块路径作为导入路径的前缀)导入模块内的所有软件包。如果导入路径与相应模块的声明模块路径不匹配,则该go
命令将报告unexpected module path
错误。
在为一组预先存在的软件包采用模块时,应注意避免破坏现有使用者使用的现有导入路径,除非在采用模块时增加主版本。
例如,如果您先前存在的README一直在告诉消费者要使用import "gopkg.in/foo.v1"
,并且随后采用v1版本的模块,则您的首字母go.mod
几乎肯定会读为module gopkg.in/foo.v1
。如果您不想使用gopkg.in
,这对您当前的消费者来说将是一个巨大的变化。一种方法是将其更改为类似的内容(module github.com/repo/foo/v2
如果您稍后转到v2)。
请注意,模块路径和导入路径区分大小写。从更改模块github.com/Sirupsen/logrus
到github.com/sirupsen/logrus
,例如,对消费者来说是一个重大更改,即使GitHub的自动转发从一个存储库名称到新存储库的名称。
采用模块后,更改模块路径go.mod
是一项重大更改。
总体而言,这类似于通过“导入路径注释”对规范的导入路径进行模块前的强制,有时也称为“导入实用程序”或“导入路径强制”。举例来说,该软件包go.uber.org/zap
当前托管在github.com/uber-go/zap
,但在软件包声明旁边使用了导入路径注释,该注释为使用错误的基于github的导入路径的所有前置模块使用者触发了错误:
package zap // import "go.uber.org/zap"
go.mod文件的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 +软件包。
+incompatible
如果导入的v2 +程序包具有有效的语义化版本号标签,它将带有后缀记录。有关更多详细信息,请参见下面的常见问题解答。
非模块代码消费模块
-
非模块代码消耗v0和v1模块:
- 尚未选择使用模块的代码可以使用和构建v0和v1模块(与使用的Go版本无关)。
-
非模块代码消耗v2 +模块:
-
Go版本1.9.7 +,1.10.3 +和1.11已更新,因此使用这些发行版构建的代码可以正确使用v2 +模块,而无需按“语义导入版本控制”和“发布模块( v2或更高版本)”部分。
-
如果按照“发布模块(v2或更高版本)”一节中概述的“主要子目录”方法创建v2 +模块,则1.9.7和1.10.3之前的Go版本可以使用v2 +模块。
-
预先存在的v2 +软件包作者的策略
对于考虑加入模块的预先存在的v2 +软件包的作者,总结替代方法的一种方法是在三种顶级策略之间进行选择。每个选择都有后续的决定和变化(如上所述)。这些替代的顶级策略是:
-
要求客户端使用Go版本1.9.7 +,1.10.3 +或1.11+。
该方法使用“主要分支”方法,并依赖于“最小模块感知”,该模型被反向移植到1.9.7和1.10.3。有关更多详细信息,请参见上面的“语义导入版本控制”和“发布模块(v2或更高版本)”部分。
-
允许客户使用甚至更旧的Go版本,如Go 1.8。
此方法使用“主要子目录”方法,并涉及创建子目录,例如
/v2
或/v3
。有关更多详细信息,请参见上面的“语义导入版本控制”和“发布模块(v2或更高版本)”部分。 -
等待加入模块。
在这种策略下,事情继续与选择了模块的客户端代码以及未选择模块的客户端代码一起工作。随着时间的流逝,Go版本1.9.7 +,1.10.3 +和1.11+的发布时间将越来越长,并且在将来的某个时候,要求Go版本变得更加自然或对客户友好1.9.7 + / 1.10.3 + / 1.11 +,此时,您可以实施以上策略1(需要Go版本1.9.7 +,1.10.3 +或1.11+),甚至可以实施以上策略2(但是如果最终要采用上述策略2来支持1.8等旧版Go,那么您现在就可以这样做。
常见问题
版本如何标记为不兼容?
该require
指令允许任何模块声明其应使用依赖项D的版本> = xyz构建(由于与模块D的版本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.mod
运行时不在文件树内部时,会发生这种情况go get
。
最简单的解决方案是保持未GO111MODULE
设置状态(或等效地显式设置为GO111MODULE=auto
),这样可以避免出现此错误。
回想一下存在的主要原因之一是记录精确的依赖项信息。此依赖项信息将写入您的current go.mod
。如果您不在带有的文件树中,go.mod
但是go get
通过设置告诉命令以模块模式GO111MODULE=on
运行,则运行go get
将导致错误,cannot find main module
因为没有go.mod
可用来记录依赖项信息的信息。
解决方案的替代方案包括:
-
保持未
GO111MODULE
设置状态(默认设置或显式设置GO111MODULE=auto
),这将导致更友好的行为。当您不在模块中时,这将为您提供Go 1.10行为,从而避免了go get
报告cannot find main module
。 -
export GO111MODULE=on
,但根据需要暂时禁用模块,并在过程中启用Go 1.10行为go get
,例如viaGO111MODULE=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
自述文件和常见问题解答以了解详细信息和其他用例。 -
创建一个
go.mod
用于跟踪运行的全局安装工具(例如中的)~/global-tools/go.mod
,然后cd
在运行之前跟踪该目录,go get
或跟踪go install
所有全局安装的工具。 -
go.mod
为每个工具在单独的目录(例如~/tools/gorename/go.mod
和)中创建一个~/tools/goimports/go.mod
,并cd
在运行前为该工具go get
或go install
该工具创建一个相应的目录。
该当前限制将得到解决。但是,主要问题是模块当前处于启用状态,完整的解决方案可能要等到GO111MODULE = on成为默认行为。有关更多讨论,请参见#24250,包括此评论:
显然,这最终必须起作用。就该版本而言,我不确定这到底是做什么的:它会创建一个临时模块root和go.mod,执行安装,然后将其丢弃吗?大概。但是我不太确定,就目前而言,我不想让vgo在go.mod树之外做一些事情来使人们感到困惑。当然,最终的go命令集成必须支持这一点。
该常见问题解答一直在讨论跟踪全局安装的工具。
相反,如果要跟踪特定模块所需的工具,请参阅下一个FAQ。
如何跟踪模块的工具依赖关系?
如果你:
- 想要
stringer
在处理模块时使用基于Go的工具(例如),并且 - 想要确保每个人都在使用该工具的相同版本,同时在模块
go.mod
文件中跟踪该工具的版本
那么当前推荐的一种方法是将一个tools.go
文件添加到您的模块中,该文件包括目标工具(例如import _ "golang.org/x/tools/cmd/stringer"
)的导入语句以及// +build tools
构建约束。import语句使go
命令可以在模块的中精确记录工具的版本信息go.mod
,而// +build tools
构建约束阻止正常的构建实际导入工具。
有关如何执行此操作的具体示例,请参见本“通过示例执行模块”演练。
#25922中的此注释中讨论了该方法以及更早的具体示例。
简要理由(同样来自#25922):
我认为tools.go文件实际上是工具依赖关系的最佳实践,当然对于Go 1.11。
我喜欢它,因为它没有引入新的机制。
它只是简单地重用现有的。
IDE,编辑器和标准工具(例如goimports,gorename等)中模块支持的状态如何?
对模块的支持已开始在编辑器和IDE中获得。
例如:
- Goland:目前拥有内外GOPATH模块,包括完成,语法分析,重构,所描述的导航全面支持这里。
- VS Code:工作正在进行中,正在寻找有助于的人。跟踪问题是#1532。VS Code模块状态Wiki页面中描述了初始beta 。
- 带有加号的原子:跟踪问题是#761。
- 带vim-go的vim:语法高亮显示和格式的最初支持
go.mod
已经到来。#1906年获得了更广泛的支持。 - 带go-mode.el的emacs:#237中的跟踪问题。
在雨伞问题中一直跟踪其他工具(例如goimports,guru,gorename和类似工具)的状态#24661。请查看该伞的最新状态。
特定工具的一些跟踪问题包括:
- gocode:mdempsky / gocode /#46中的跟踪问题。请注意,
nsf/gocode
建议人们从迁移nsf/gocode
到mdempsky/gocode
。 - go-tools(dominikh的工具,例如staticcheck,megacheck,gosimple):示例跟踪问题dominikh / go-tools#328。
通常,即使您的编辑器,IDE或其他工具尚未被模块识别,如果您在GOPATH内使用模块并且可以使用,则它们的大部分功能也应与模块一起使用go mod vendor
(因为应通过GOPATH来选择适当的依赖项) 。
完整的解决方法是把那包加载关闭的程序go/build
和到golang.org/x/tools/go/packages
,其知道如何定位模块感知方式封装。这很可能最终成为事实go/packages
。
常见问题解答-附加控制
存在哪些社区工具来使用模块?
社区开始在模块之上构建工具。例如:
- github.com/rogpeppe/gohack
- 一种新的社区工具,可以自动化并大大简化
replace
和多模块工作流程,其中包括允许您轻松修改其中的一个依赖项 - 例如,
gohack example.com/some/dependency
自动克隆适当的存储库并将必要的replace
指令添加到您的go.mod
- 使用以下命令删除所有gohack替换语句
gohack undo
- 该项目正在继续扩展,以简化与模块相关的其他工作流程
- 一种新的社区工具,可以自动化并大大简化
- github.com/marwan-at-work/mod
- 命令行工具可自动升级/降级模块的主要版本
go.mod
在go源代码中自动调整文件和相关的导入语句- 帮助进行升级,或者在首次选择带有v2 +软件包的模块时提供帮助
- github.com/akyoto/mgit
- 使您可以查看和控制所有本地项目的语义化版本号标签
- 显示未标记的提交,并让您一次标记所有(
mgit -tag +0.0.1
)
- github.com/goware/modvendor
- 帮助将其他文件复制到该
vendor/
文件夹中,例如外壳程序脚本,.cpp文件,.proto文件等。
- 帮助将其他文件复制到该
- github.com/psampaz/go-mod-outdated
- 以人类友好的方式显示过时的依赖关系
- 提供一种过滤间接依赖关系和无需更新的依赖关系的方法
- 提供了一种在依赖项过时的情况下中断CI管道的方法
什么时候应该使用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
一个示例用例是,如果您需要修复或研究依赖项中的某些内容,则可以使用本地派生,并在顶层中添加以下内容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
上述所有示例所示)。
在Go 1.11中,对于直接依赖关系require
,即使执行时也需要一个指令replace
。例如,如果foo
是直接依赖项,那么您不能没有replace foo => ../foo
相应的require
for foo
。如果你不知道在用什么版本的require
指令,你可以经常使用v0.0.0
如require foo v0.0.0
。这在Go 1.12中的#26241中得到了解决。
您可以通过运行确认获得所需的版本go list -m all
,该版本向您显示了将在构建中使用的实际最终版本,包括考虑了replace
语句。
有关更多详细信息,请参见“ go mod edit”文档。
github.com/rogpeppe/gohack使这些类型的工作流变得更加容易,尤其是如果您的目标是对模块依赖项进行可变签出时。有关概述,请参见存储库或之前的常见问题解答。
有关在replace
VCS之外完全使用的详细信息,请参见下一个FAQ 。
我可以在本地文件系统上完全不在VCS上工作吗?
是。不需要VCS。
如果您要一次在VCS之外编辑单个模块,那么这非常简单(并且您总共只有一个模块,或者其他模块位于VCS中)。在这种情况下,可以将包含单个文件的文件树放置go.mod
在方便的位置。你go build
,go test
和类似的命令将工作,即使你的单个模块是VCS之外(无需任何使用replace
你的go.mod
)。
如果要在本地磁盘上同时编辑多个相互关联的模块,则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
如本示例所示,如果在VCS之外,则可以将其v0.0.0
用作require
指令中的版本。请注意,如先前的FAQ中所述,在Go 1.11中require
必须在此处手动添加require
指令,但不再需要在Go 1.12+(#26241)中手动添加该指令。
该线程中显示了一个小的可运行示例。
如何对模块使用vendor?vendor会消失吗?
最初的一系列vgo
博客文章确实建议完全放弃vendor,但是社区的反馈导致保留了对vendor的支持。
简而言之,要对模块使用vendor:
go mod vendor
重置主模块的vendor目录,以包括根据go.mod文件和Go源代码的状态构建和测试所有模块软件包所需的所有软件包。- 默认情况下,
go build
在模块模式下,执行诸如忽略vendor目录之类的命令。 - 该
-mod=vendor
标志(例如,go build -mod=vendor
)指示去命令使用主模块的顶级vendor目录,以满足依赖性。因此,在此模式下,go命令将忽略go.mod中的依赖项描述,并假定vendor目录包含正确的依赖项副本。请注意,仅使用主模块的顶级vendor目录。其他位置的vendor目录仍然被忽略。 - 有些人会希望通过设置
GOFLAGS=-mod=vendor
环境变量来定期选择vendor。
禁用模块模式go mod vendor
时,Go的较旧版本(如1.10)了解如何使用由创建的vendor目录,Go 1.11和1.12+ 也是如此。因此,供应是模块提供依赖的一种方式,该依赖提供了对不能完全理解模块的Go的较旧版本以及未启用模块本身的使用者的依赖。
如果您正在考虑使用vendor,则值得阅读技巧文档中的“模块和vendor”和“提供依赖关系的vendor副本”部分。
是否存在“始终在线”的模块存储库和企业代理?
公共托管的“始终在”不可变模块存储库以及可选的私有托管的代理和存储库正变得可用。
例如:
- proxy.golang.org-官方项目-由Google运行-Go团队构建的默认Go模块代理。
- gocenter.io-商业项目-由JFrog运行-中央Go模块存储库。
- mirrors.aliyun.com/goproxy-商业项目-由阿里云运行-Go模块代理替代。
- goproxy.cn-开源项目-由Qiniu Cloud运行-中国最受信任的Go模块代理。
- goproxy.io-开源项目-由中国Golang贡献者俱乐部运营-Go模块的全球代理。
- 雅典 -开源项目-自托管-Go模块数据存储和代理。
- athens.azurefd.net-开源项目-由Microsoft运行-运行雅典的托管模块代理。
- Goproxy-开源项目-自托管-极简的Go模块代理处理程序。
- THUMBAI-开源项目-自托管-Go mod代理服务器和Go vanity导入路径服务器。
请注意,您不需要运行代理。相反,1.11中的go工具已通过GOPROXY添加了可选的代理支持,以启用更多企业用例(例如,更好的控制),并更好地处理诸如“ GitHub停机”或人们删除GitHub存储库的情况。
我可以控制go.mod何时更新以及go工具何时使用网络满足依赖关系吗?
默认情况下,类似的命令go build
会根据需要到达网络以达到导入要求。
有些团队可能希望禁止go工具在某些时候接触网络,或者想要更好地控制go工具何时更新go.mod
,如何获得依赖关系以及如何使用vendor。
转到工具提供了相当数量的灵活调整或关闭这些默认的行为,包括通过-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中运行测试可能很有价值。vendor也是要考虑的话题。
以下两个博客文章更具体地介绍了这些主题:
- Fatih Arslan的“在Travis CI上使用带有vendor支持的Go模块”
- Todd Keech的“ Go Modules和CircleCI”
常见问题解答— go.mod和go.sum
为什么“ go mod tidy”在我的“ go.mod”中记录间接和测试依赖项?
该模块系统记录您的精确的依赖要求go.mod
。(有关更多详细信息,请参阅上面的go.mod概念部分或go.mod技巧文档)。
go mod tidy
更新您的当前信息,go.mod
以在模块中包括测试所需的依赖关系-如果测试失败,我们必须知道使用了哪些依赖关系来重现失败。
go mod tidy
还可以确保您的当前go.mod
反映了操作系统,架构的所有可能组合的依赖性需求,并建立标签(如描述在这里)。相比之下,其他的命令一样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
行为。
// indirect
您的go.mod
文件中可能具有依赖项的另一个原因是,如果您已经升级(或降级了)一个间接依赖项,超出了直接依赖项所要求的范围(例如,运行go get -u
或)go get [email protected]
。go工具需要一个位置来记录这些新版本,并且它会在您的go.mod
文件中记录(并且不会深入到您的依赖项中来修改其 go.mod
文件)。
通常,上述行为是模块如何通过记录精确的依赖项信息来提供100%可复制的构建和测试的一部分。
如果你是好奇,为什么一个特定的模块,显示在你起来go.mod
,你可以运行go mod why -m
于回答这个问题。用于检查需求和版本的其他有用工具包括go mod graph
和go list -m all
。
'go.sum'是锁定文件吗?为什么“ go.sum”包含有关我不再使用的模块版本的信息?
不,go.sum
不是锁定文件。go.mod
构建中的文件为100%可复制的构建提供了足够的信息。
为了进行验证,go.sum
包含特定模块版本的内容的预期密码校验和。有关详细信息(包括为什么通常需要签入)以及技巧文档中的“模块下载和验证”部分,请参见下面的FAQ。go.sum
go.sum
部分由于go.sum
不是锁文件,因此即使您停止使用模块或特定模块版本,它也会保留模块版本的加密校验和。如果您以后继续使用某些内容,则可以验证校验和,从而提高了安全性。
另外,您的模块go.sum
记录了构建中使用的所有直接和间接依赖项的校验和(因此,go.sum
列出的模块通常比的要多go.mod
)。
我是否应该提交“ go.sum”文件以及“ go.mod”文件?
通常,模块的go.sum
文件应与go.mod
文件一起提交。
go.sum
包含特定模块版本内容的预期密码校验和。- 如果有人克隆您的存储库并使用go命令下载了您的依赖项,那么如果他们下载的依赖项副本和的相应条目之间存在任何不匹配,就会收到错误消息
go.sum
。 - 此外,
go mod verify
检查磁盘下载的模块下载在磁盘上的缓存副本是否仍与中的条目匹配go.sum
。 - 请注意,
go.sum
它不是某些替代性依赖项管理系统中使用的锁定文件。(go.mod
为可复制的构建提供足够的信息)。 - 请参阅Filippo Valsorda的非常简单的理由,了解您为什么要登机
go.sum
。有关更多详细信息,请参见技巧文档的“模块下载和验证”部分。请参阅#24117和#25530中讨论的将来可能的扩展。
如果我没有任何依赖关系,还应该添加一个“ go.mod”文件吗?
是。这支持在GOPATH之外进行工作,帮助与您选择模块的生态系统进行通信,此外module
,您的指令还可以go.mod
用作代码身份的明确声明(这是最终不建议使用导入注释的原因之一) )。当然,模块在Go 1.11中纯粹是可选功能。
常见问题解答—语义导入版本控制
为什么主版本号必须出现在导入路径中?
请参阅上面的“语义导入版本控制”概念部分中有关语义导入版本控制和导入兼容性规则的讨论。另请参阅宣布提案的博客文章,其中更多地讨论了导入兼容性规则的动机和理由。
为什么导入路径中省略了主要版本v0,v1?”
请参阅问题“为什么导入路径中省略了主要版本v0,v1?” 在较早的FAQ中,来自官方提案的讨论。
用主要版本v0,v1标记我的项目,或使用v2 +进行重大更改有什么含义?
在回应有关“ k8发行次要版本,但在每个次要版本中更改Go API”的评论时,Russ Cox做出以下回应,着重强调了选择v0,v1与频繁使用v2,v3,v4进行重大更改的一些含义。 ,等等。
我并不完全了解k8s开发周期等,但是我认为通常k8s团队需要决定/确认他们打算向用户保证稳定性的内容,然后相应地应用版本号来表达这一点。
- 要保证有关API兼容性(这似乎是最佳的用户体验!),然后开始使用1.XY
- 为了灵活地在每个发行版中进行向后不兼容的更改,但允许大型程序的不同部分按不同的时间表升级其代码,这意味着不同的部分可以在一个程序中使用API的不同主要版本,然后使用XY0,以及导入路径,例如k8s.io/client/vX/foo。
- 为了不保证API兼容,并且无论什么情况,每个构建都只需要一个k8s库的副本,这意味着即使不是所有构建都准备好了,构建的所有部分也必须使用相同版本。 ,然后使用0.XY
与此相关的是,Kubernetes具有一些非典型的构建方法(当前在Godep之上包括自定义包装脚本),因此Kubernetes对于许多其他项目来说是不完善的示例,但是随着Kubernetes向采用Go 1.11迈进,这可能是一个有趣的示例。模块。
模块可以使用未选择模块的软件包吗?
是。
如果存储库未选择使用模块,但已使用有效的语义化版本号标签(包括所需的前导v
)进行了标记,则可以在中使用这些语义化版本号标签go get
,并且相应的语义化版本号版本将记录在导入模块的go.mod
文件中。如果存储库没有任何有效的语义化版本号标签,则将使用“伪版本”记录存储库的版本,例如 v0.0.0-20171006230638-a6e239ea1c69
(其中包括时间戳和提交哈希,并且其设计目的是允许对记录在其中的各个版本进行总排序)go.mod
并使其更容易推断出哪个记录版本比另一个记录版本“晚”。
例如,如果foo
标记了软件包的最新版本,v1.2.3
但foo
自身尚未选择加入模块,则在模块M的内部运行go get foo
或go get [email protected]
从模块M记录的内容将记录为模块M的go.mod
文件,如下所示:
require foo v1.2.3
该go
工具还将在其他工作流程中为非模块程序包使用可用的语义化版本号标记(例如go list -u=patch
,将模块的依赖项升级到可用的补丁程序版本,或将go list -u -m all
,显示可用的升级等)。
有关尚未选择模块的v2 +软件包的更多详细信息,请参见下一个常见问题解答。
一个模块可以使用未选择模块的v2 +软件包吗?“ +不兼容”是什么意思?
是的,模块可以导入尚未选择模块的v2 +软件包,并且如果导入的v2 +软件包具有有效的语义化版本号标签,则将记录+incompatible
后缀。
额外细节
请熟悉上面“语义导入版本控制”部分中的材料。
首先回顾一些通常有用但在考虑本FAQ中描述的行为时要记住的特别重要的核心原则会有所帮助。
当工具以模块模式(例如)运行时,以下核心原则始终是正确的:go
GO111MODULE=on
- 软件包的导入路径定义了软件包的标识。
- 具有不同导入路径的软件包被视为不同的软件包。
- 具有相同导入路径的软件包被视为相同的软件包(即使 VCS标签说这些软件包具有不同的主要版本,也是如此)。
- 不带a的导入路径
/vN
被视为v1或v0模块(即使导入的程序包未选择加入模块并且具有表示主要版本大于1的VCS标记,也是如此)。 - 在模块文件
module foo/v2
开头声明的模块路径(例如)go.mod
均为:- 该模块身份的明确声明
- 关于必须如何通过使用代码导入该模块的明确声明
我们将在接下来的FAQ看到,当这些原则并不总是正确的go
工具是不是在模块模式,但是当这些原则是总是正确的go
工具是模块模式。
简而言之,+incompatible
后缀表示当满足以下条件时,上述原则2有效:
- 导入的软件包尚未选择使用模块,并且
- 它的VCS标签说主要版本大于1,并且
- 原理2覆盖了VCS标签-不带a的导入路径
/vN
被视为v1或v0模块(即使VCS标签另有说明)
当go
工具处于模块模式时,它将假定非模块v2 +软件包不了解语义导入版本控制,并将其视为该软件包的v1版本系列的(不兼容)扩展(并且+incompatible
后缀表示go
工具正在这样做)。
例
假设:
oldpackage
是一个在引入模块之前的软件包oldpackage
从未选择使用模块(因此本身没有模块go.mod
)oldpackage
具有有效的语义化版本号标签v3.0.1
,这是它的最新标签
在这种情况下,例如go get oldpackage@latest
从模块M内部运行将在模块M的go.mod
文件中记录以下内容:
require oldpackage v3.0.1+incompatible
请注意,在上面的命令或记录的指令/v3
的末尾没有使用– 在模块路径和导入路径中使用是语义导入版本控制的功能,并且未表示接受并理解了语义导入版本控制(尚未给出)通过在其内部包含文件来选择加入模块。换句话说,即使具有语义化版本号标签,也不会被授予语义导入版本控制的权利和责任(例如,在导入路径中使用),因为尚未声明要这样做。oldpackage
go get
require
/vN
oldpackage
oldpackage
go.mod
oldpackage
oldpackage
v3.0.1
oldpackage
/vN
oldpackage
该+incompatible
后缀表明该v3.0.1
版本oldpackage
并没有主动选择加入的模块,因此v3.0.1
版本oldpackage
被认为不理解语义进口版本或如何使用进口路径主要版本。因此,在模块模式下运行时,该go
工具会将的非模块v3.0.1
版本oldpackage
视为的v1版本系列的(不兼容)扩展,oldpackage
并假定的v3.0.1
版本oldpackage
不了解语义导入版本控制,并且+incompatible
后缀表示该go
工具正在这样做。
该事实v3.0.1
的版本oldpackage
被认为是根据语义进口版本的v1发行版系列的一部分意味着,例如版本v1.0.0
,v2.0.0
以及v3.0.1
使用相同的导入路径都始终输入:
import "oldpackage"
再次注意,/v3
末尾没有用过oldpackage
。
通常,具有不同导入路径的软件包是不同的软件包。在这个例子中,给出的版本v1.0.0
,v2.0.0
和v3.0.1
中oldpackage
会使用相同的导入路径需要进口,因此它们是通过构建视为同一包(也因为oldpackage
在还没有选择到语义进口版本),以单拷贝oldpackage
最终在任何给定的版本中。(使用的版本在任何require
指令中在语义上都是最高的版本;请参见“版本选择”)。
如果我们假设稍后创建一个新的v4.0.0
发行版,oldpackage
该发行版采用模块并因此包含一个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或更高版本的模块必须
/vN
在声明的其自己的模块路径中包含go.mod
。 - 基于模块的使用者(即已选择加入模块的代码)必须
/vN
在导入路径中包含,以导入v2 +模块。
但是,预计生态系统将以不同的采用模块和语义导入版本控制的速度进行。
如“如何释放v2 +模块”部分中更详细描述的那样,在“主要子目录”方法中,v2 +模块的作者创建了诸如mymodule/v2
或的子目录,mymodule/v3
并在这些子目录下移动或复制了相应的软件包。这意味着传统的导入路径逻辑(即使在旧的Go版本中,如Go 1.8或1.7)也会在看到诸如的导入语句时找到合适的软件包import "mymodule/v2/mypkg"
。因此,即使未启用模块支持,也可以找到并使用“主要子目录” v2 +模块中的软件包(这是因为您正在运行Go 1.11且未启用模块,还是因为您正在运行旧版本,如Go)没有完整模块支持的1.7、1.8、1.9或1.10)。请看“如何发布v2 +模块”部分提供了有关“主要子目录”方法的更多详细信息。
该常见问题解答的其余部分集中于“如何发布v2 +模块”部分中介绍的“主要分支”方法。在“主要分支”方法中,不/vN
创建任何子目录,而是通过go.mod
文件并通过将语义化版本号标签应用于提交来传达模块版本信息(通常在上master
,但可以在不同的分支上)。
为了在当前过渡时期提供帮助,Go 1.11 引入了“最小模块兼容性” ,以为尚未加入模块的Go代码提供更大的兼容性,并且“最小模块兼容性”也被反向移植到Go 1.9。 7和1.10.3(鉴于那些旧版Go版本不具有完整模块支持,这些版本始终在禁用完整模块模式的情况下始终有效运行)。
“最小模块兼容性”的主要目标是:
-
允许较早的Go版本1.9.7+和1.10.3+能够更轻松地编译
/vN
在导入路径中使用语义导入版本控制的模块,并在Go 1.11中禁用模块模式时提供相同的行为。 -
允许旧代码能够使用v2 +模块,而无需使用旧的消费者代码在使用
/vN
v2 +模块时立即更改为使用新的导入路径。 -
这样做无需依赖模块作者即可创建
/vN
子目录。
其他详细信息–“最小模块兼容性”
“最小模块兼容性”仅在禁用该工具的完整模块模式时才生效go
,例如,如果您GO111MODULE=off
在Go 1.11中进行了设置,或者正在使用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"
要导入的v1语义导入版本控制系列foo
)。
- 如果软件包已选择加入模块,但在导入v2 +模块时在导入路径中未包含主版本,则当该
- 用于实现“最小模块兼容性”的机制故意非常狭窄:
- 整个逻辑是–当在GOPATH模式下运行时,如果导入语句位于选择加入模块的代码内(即,树中文件中的导入语句),则
/vN
在删除后,将再次尝试包含a的无法解析的导入语句带有有效文件)。/vN
.go
go.mod
- 最终结果是,导入语句(例如
import "foo/v2"
位于模块内部的代码内)仍将在GOPATH模式下(分别为1.9.7 +,1.10.3 +和1.11)正确编译,并且将像说的那样进行解析import "foo"
(不带/v2
) ,这意味着它将使用foo
驻留在您的GOPATH中的版本,而不会被多余的混淆/v2
。 - “最小模块兼容性”不会影响其他任何内容,包括它不会影响
go
命令行中使用的路径(例如go get
或的参数go list
)。
- 整个逻辑是–当在GOPATH模式下运行时,如果导入语句位于选择加入模块的代码内(即,树中文件中的导入语句),则
- 这种过渡的“最小模块感知”机制有意打破了“将具有不同导入路径的软件包视为不同的软件包”的规则,以实现非常具体的向后兼容性目标–允许旧代码在使用v2 +模块时进行编译,而无需修改。稍微详细一点:
- 如果旧代码使用v2 +模块的唯一方法是首先更改旧代码,那么对整个生态系统来说,负担将更大。
- 如果我们不修改旧代码,则该旧代码必须适用于v2 +模块的模块前导入路径。
- 另一方面,选择加入模块的新代码或更新代码必须
/vN
对v2 +模块使用新的导入。 - 新的导入路径不等于旧的导入路径,但是两者都可以在单个版本中使用,因此,我们有两个不同的功能导入路径可以解析为同一程序包。
- 例如,在GOPATH模式下运行时,
import "foo/v2"
出现在基于模块的代码中将解析为与您的GOPATH中驻留的代码相同的代码import "foo"
,并且该构建最终以-的一个副本结尾foo
-特别是无论GOPATH磁盘上的版本如何。这使得基于模块的代码import "foo/v2"
甚至可以在1.9.7 +,1.10.3 +和1.11的GOPATH模式下进行编译。
- 相反,当
go
工具以全模块模式运行时:- “具有不同导入路径的软件包是不同的软件包”规则也没有例外(包括在完全模块模式下对vendor进行了改进,以也遵守此规则)。
- 例如,如果该
go
工具处于完整模块模式并且foo
是v2 +模块,则import "foo"
要求提供foo
vs 的v1版本,import "foo/v2"
要求提供的v2版本foo
。
如果我创建go.mod但不将语义化版本号标记应用于存储库,会发生什么情况?
语义化版本号是模块系统的基础。为了向消费者提供最佳体验,鼓励模块作者使用语义化版本号 VCS标签(例如v0.1.0
或v1.2.3-rc.1
),但严格要求不使用语义化版本号 VCS标签:
-
要求模块遵循语义化版本号规范,以使该
go
命令按照记录的方式运行。这包括遵循关于如何以及何时允许更改的语义化版本号规范。 -
消费者使用伪版本形式的语义化版本号版本记录没有语义化版本号 VCS标签的模块。通常,这将是v0主版本,除非模块作者遵循“主子目录”方法构造了v2 +模块。
-
因此,不应用语义化版本号 VCS标记且未创建“主要子目录”的模块将有效地声明自己属于语义化版本号 v0主版本系列,并且基于模块的使用者将其视为具有语义化版本号 v0主版本。
模块可以依赖于其自身的不同版本吗?
一个模块可以依赖于其自身的不同主要版本:总的来说,这相当于依赖于不同的模块。出于各种原因,这可能很有用,包括允许将模块的主要版本实现为围绕其他主要版本的填充程序。
此外,一个模块可以在一个周期中依赖于其自身的不同主要版本,就像两个完全不同的模块可以在一个周期中彼此依赖一样。
但是,如果您不希望模块依赖于其自身的不同版本,则可能是错误的征兆。例如,打算从v3模块导入软件包的.go代码可能缺少/v3
import语句中所需的内容。根据本身的v1版本,该错误可能表现为v3模块。
如果您惊讶地看到一个模块依赖于其自身的不同版本,那么值得回顾一下上面的“语义导入版本控制”部分以及常见问题解答“如果我没有看到预期的版本,该怎么办?依赖?” 。
两个程序包可能在一个周期中彼此不依赖仍然是一个约束。
FAQS —多模块存储库
什么是多模块存储库?
多模块存储库是一个包含多个模块的存储库,每个模块都有自己的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中评论:
对于除电源用户以外的所有用户,您可能希望采用一种惯例,即一个repo =一个模块。对于代码存储选项的长期发展很重要,一个仓库可以包含多个模块,但是默认情况下您几乎肯定不想这样做。
关于如何使多模块更有效的两个示例:
go test ./...
从存储库根目录开始将不再测试存储库中的所有内容- 您可能需要通过
replace
指令定期管理模块之间的关系。
但是,除了这两个示例之外,还有其他细微差别。如果您考虑在单个存储库中包含多个模块,请仔细阅读本小节中的FAQ 。
有两个示例场景,其中go.mod
一个存储库中可能有多个合理的场景:
-
如果您有一些使用示例,其中这些示例本身具有一组复杂的依赖关系(例如,也许您的软件包很小,但包括一个将软件包与kubernetes结合使用的示例)。在这种情况下,对于您的存储库来说,拥有一个
examples
或一个_examples
自己的目录是很有意义的go.mod
,例如here。 -
如果您的存储库具有一组复杂的依赖关系,但是您的客户端API的依赖关系集较少。在某些情况下,拥有一个
api
或一个或clientapi
多个具有自己的目录go.mod
或将其分隔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/[email protected]
-
git commit
-
git tag v1.3.0
-
git tag mig/v1.0.0
-
接下来,让我们测试一下。我们不能
go build
还是go test
天真地做,因为go命令会尝试从模块缓存中获取每个相关模块。因此,我们需要使用替换规则来使go
命令使用本地副本:cd path-to/github.com/my-repo/mig go mod edit -replace github.com/[email protected]=../ go test ./... go mod edit -dropreplace github.com/[email protected]
-
git push origin master v1.2.4 mig/v1.0.0
推送提交和两个标签
请注意,将来golang.org/issue/28835应该使测试步骤更直接。
还要注意,在次要版本之间,代码已从模块“ github.com/my-repo”中删除。不将其视为主要更改似乎很奇怪,但是在这种情况下,传递性依存关系继续在其原始导入路径中提供已删除软件包的兼容实现。
是否可以从多模块存储库中删除模块?
是的,具有与上述相同的两种情况和类似的步骤。
一个模块可以依赖于内部模块吗?
是。一个模块中的程序包可以从另一个模块中导入内部程序包,只要它们共享与内部/路径组件相同的路径前缀即可。例如,考虑以下存储库:
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包导入内部包。
额外的go.mod可以排除不必要的内容吗?模块是否等效于.gitignore文件?
go.mod
在单个存储库中具有多个文件的另一个用例是,如果存储库中的文件应从模块中删除。例如,存储库可能具有Go模块不需要的非常大的文件,或者多语言存储库可能具有许多非Go文件。
go.mod
目录中的空白将导致该目录及其所有子目录从顶级Go模块中排除。
如果排除的目录不包含任何.go
文件,则除了放置空go.mod
文件之外,不需要其他步骤。如果排除的目录中确实包含.go
文件,请首先仔细阅读此多模块存储库部分中的其他常见问题解答。
常见问题解答–最小版本选择
最少的版本选择是否会使开发人员无法获得重要的更新?
请参阅问题“最小版本选择是否会使开发人员无法获得重要更新?” 在较早的FAQ中,来自官方提案的讨论。
常见问题解答-可能的问题
如果我发现问题,可以进行哪些常规检查?
- 通过运行
go env
来确认是否启用了模块,以确认其未为只读GOMOD
变量显示空值。- 注意:永远不要将其设置
GOMOD
为变量,因为它实际上是输出的只读调试go env
输出。 - 如果
GO111MODULE=on
要启用模块,请仔细检查它是否不是复数形式GO111MODULES=on
。(人们有时自然会包括,S
因为该功能通常称为“模块”)。
- 注意:永远不要将其设置
- 如果期望使用vendor,请检查该
-mod=vendor
标志是否正在传递给go build
或传递给该标志或已GOFLAGS=-mod=vendor
被设置。- 默认情况下,模块会忽略
vendor
目录,除非您要求go
使用该工具vendor
。
- 默认情况下,模块会忽略
- 检查
go list -m all
查看为您的构建选择的实际版本列表 通常会很有帮助go list -m all
与仅查找go.mod
文件相比,通常可以提供更多详细信息。
- 如果运行
go get foo
因某种go build
原因而失败,或者特定软件包失败foo
,则通常可以检查go get -v foo
或的输出go get -v -x foo
:- 通常,
go get
通常会提供比更为详细的错误消息go build
。 - 该
-v
标志go get
要求打印更多详细信息,但请注意,根据配置远程存储库的方式,某些“错误”(例如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内部与外面)。
当前正在检查的错误可能是由于构建中没有特定模块或软件包的预期版本而引起的第二个问题。因此,如果导致特定错误的原因不明显,则可以按照下一个FAQ中的说明对您的版本进行抽查。
如果没有看到期望的依赖版本,该如何检查?
-
一个好的第一步是运行
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
向您显示最终选择的版本,包括用于间接依赖性的版本以及在解决所有共享依赖性的版本之后的版本。它还显示anyreplace
和exclude
指令的结果。 -
下一步是检查
go mod graph
或的输出go mod graph | grep
。go mod graph
打印模块需求图(包括考虑的更换)。输出中的每一行都有两个字段:第一列是使用模块,第二列是该模块的要求之一(包括该使用模块所需的版本)。这是查看哪些模块需要特定依赖项的快速方法,包括当构建的依赖项具有与构建中的不同使用者不同的所需版本时(如果是这种情况,熟悉该模块很重要)。行为在上面的“版本选择”部分中进行了说明)。
go mod why -m
在这里也可以是有用的,尽管通常对于查看为什么完全包含依赖项(而不是为什么依赖项以特定版本结束)更为有用。
go list
提供了更多的查询变体,在需要时可以用于查询模块。以下是一个示例,它将显示构建中使用的确切版本,不包括仅测试依赖项:
go list -deps -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' ./... | sort -u
用于询问你的模块的更详细的命令集和实施例的能够在可运行“转到模块通过实施例”中可以看出walkthough。
导致意外版本的一个原因可能是由于某人创建了一个go.mod
非预期的无效或意外文件,或者是相关的错误(例如:一个v2.0.1
模块版本可能错误地将自身声明为module foo
在其中,go.mod
而没有必要/v2
; import语句)在.go
打算导入v3的模块可能缺少所需的代码/v3
;一require
在一份声明中go.mod
对V4模块可能缺少必要的/v4
)。因此,如果您看不到引起特定问题的原因,那么值得首先阅读“ go.mod”和“语义导入版本控制”中的材料上面的部分(考虑到这些包括模块必须遵循的重要规则),然后花几分钟来检查最相关的go.mod
文件和import语句。
为什么会出现错误“找不到提供软件包foo的模块”?
这是一条常见的错误消息,可能会因几种不同的根本原因而发生。
在某些情况下,此错误仅是由于路径键入错误引起的,因此第一步可能应该是根据错误消息中列出的详细信息再次检查错误的路径。
如果您还没有这样做,那么下一步通常是尝试go get -v foo
以下操作go get -v -x foo
:
- 通常,
go get
通常会提供比更为详细的错误消息go build
。 - 有关更多详细信息,请参见上面本节中的第一个故障排除常见问题解答。
其他一些可能的原因:
-
cannot find module providing package foo
如果您已发出go build
或在当前目录中go build .
没有.go
任何源文件,则可能会看到错误。如果这是您遇到的问题,则解决方案可能是另一种调用,例如go build ./...
(其中./...
展开以匹配当前模块中的所有软件包)。参见#27122。 -
Go 1.11中的模块缓存可能导致此错误,包括面对网络问题或
go
并行执行的多个命令。这在Go 1.12中已解决。有关更多详细信息和可能的纠正步骤,请参阅上面本节中的第一个故障排除常见问题解答。
为什么“ go mod init”给出错误“无法确定源目录的模块路径”?
go mod init
没有任何参数的情况下,将根据不同的提示(例如VCS元数据)尝试猜测正确的模块路径。但是,不能go mod init
总是能够猜测出正确的模块路径。
如果go mod init
给您这个错误,则这些试探法无法猜测,您必须自己提供模块路径(例如go mod init github.com/you/hello
)。
我有一个尚未选择模块的复杂依赖性问题。我可以使用其当前依赖项管理器中的信息吗?
是。这需要一些手动步骤,但在某些更复杂的情况下可能会有所帮助。
在go mod init
初始化自己的模块时运行时,它将通过转换配置文件(如Gopkg.lock
,glide.lock
)或包含相应指令vendor.json
的go.mod
文件自动从先前的依赖项管理器转换require
。Gopkg.lock
例如,现有文件中的信息通常描述所有直接和间接依赖项的版本信息。
但是,如果改为添加尚未选择加入模块本身的新依赖项,则任何先前的依赖项管理器都不会使用类似的自动转换过程,而新的依赖项可能已经在使用该转换过程。如果该新依赖项本身具有发生了重大更改的非模块依赖项,则在某些情况下可能会导致不兼容问题。换句话说,新依赖项的先前依赖项管理器不会自动使用,在某些情况下,这可能会导致间接依赖项出现问题。
一种方法是go mod init
在有问题的非模块直接依赖项上运行,以从其当前的依赖项管理器进行转换,然后使用require
结果临时文件中的指令go.mod
填充或更新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
require
临时的结果信息go.mod
可以手动移至go.mod
模块的实际信息中,或者您可以考虑使用https://github.com/rogpeppe/gomodmerge,这是针对此用例的社区工具。另外,您将需要require github.com/some/nonmodule v1.2.3
在实际中添加一个go.mod
以匹配您手动克隆的版本。
在#28489注释中,针对Docker使用此技术的具体示例说明了如何获取一致的Docker依赖项版本集,从而避免github.com/sirupsen/logrus
vs. 之间区分大小写的问题github.com/Sirupsen/logrus
。
如何解决由于导入路径与声明的模块标识不匹配而导致的“解析go.mod:意外的模块路径”和“错误加载模块要求”错误?
为什么会发生此错误?
通常,模块go.mod
通过module
指令(例如)在其声明中声明其身份module example.com/m
。这是该模块的“模块路径”,并且该go
工具在该声明的模块路径与任何使用者使用的导入路径之间强制保持一致性。如果模块的go.mod
文件为module example.com/m
,则使用者必须使用以该模块路径开头的导入路径(例如import "example.com/m"
或import "example.com/m/sub/pkg"
)从该模块导入软件包。
如果使用者使用的导入路径与相应的声明模块路径不匹配,则该go
命令将报告parsing go.mod: unexpected module path
致命错误。另外,在某些情况下,该go
命令随后将报告更通用的error loading module requirements
错误。
导致此错误的最常见原因是,是否进行了名称更改(例如github.com/Sirupsen/logrus
到github.com/sirupsen/logrus
),或者由于虚荣导入路径而导致模块有时在模块之前通过两个不同的名称使用(例如github.com/golang/sync
vs.建议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
。- 总体升级操作无法完成,出现以下错误:
转到:github.com/Quasilyte/[email protected]:解析go.mod:意外的模块路径“ github.com/quasilyte/go-consistent”转到:错误加载模块要求
解决
错误的最常见形式是:
转到:example.com/some/OLD/[email protected]:解析go.mod:意外的模块路径“ example.com/some/NEW/name”
如果您访问存储库的信息example.com/some/NEW/name
(从错误的右侧开始),则可以检查go.mod
文件的最新版本,或者master
查看它是否在go.mod
as 的第一行中声明了自己module example.com/some/NEW/name
。如果是这样,则暗示您看到的是“旧模块名称”与“新模块名称”的问题。
本节的其余部分重点在于按顺序执行以下步骤来解决此错误的“旧名称”和“新名称”形式:
-
检查您自己的代码,看看是否要使用导入
example.com/some/OLD/name
。如果是这样,请更新您的代码以导入example.com/some/NEW/name
。 -
如果您在升级过程中收到此错误,则应尝试使用Go的尖端版本进行升级,该尖端版本具有针对性更强的升级逻辑(#26902),通常可以回避此问题,并且在这种情况下通常还具有更好的错误消息。请注意,
go get
tip / 1.13中的参数与1.12 中的参数不同。获取提示并使用它升级依赖项的示例:
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
可能尚未采用语义化版本号或模块。(一个常见的错误是认为go get -u foo
仅获取的最新版本foo
。实际上,-u
ingo get -u foo
或go get -u foo@latest
手段也获取的所有直接和间接依赖项的最新版本。foo
; 可能正是您想要的,但如果由于深层间接依赖而导致失败,则可能不会特别如此。 -
如果上述步骤不能解决错误,则下一种方法会稍微复杂一些,但大多数情况下应该可以解决此错误的“旧名称”和“新名称”形式。这仅使用仅来自错误消息本身的信息,并简要介绍了一些VCS历史记录。
4.1。转到
example.com/some/NEW/name
存储库4.2。确定何时将
go.mod
文件引入那里(例如,通过查看的责任或历史视图go.mod
)。4.3。挑选释放或提交之前的
go.mod
介绍有文件。4.4。在您的
go.mod
文件中,在replace
语句的两边添加一个使用旧名称的replace
语句:replace example.com/some/OLD/name => example.com/some/OLD/name
使用我们先前的示例,其中哪里github.com/Quasilyte/go-consistent
是旧名称,又github.com/quasilyte/go-consistent
是新名称,我们可以看到go.mod
最早是在commit 00c5b0cf371a中引入的。该存储库未使用语义化版本号标记,因此我们将紧接之前的提交00dd7fb039e提交,并使用两边的旧大写Quasilyte名称将其添加到替换中replace
:
replace github.com/Quasilyte/go-consistent => github.com/Quasilyte/go-consistent 00dd7fb039e
replace
然后,该语句使我们能够通过有效地防止在存在的情况下将旧名称升级为新名称,从而解决了有问题的“旧名称”与“新名称”不匹配的问题go.mod
。通常,通过go get -u
或类似方式进行升级现在可以避免该错误。如果升级完成,则可以检查是否有人仍在导入旧名称(例如go mod graph | grep github.com/Quasilyte/go-consistent
),如果没有导入,则replace
可以将其删除。(这经常起作用的原因是,如果使用了旧的有问题的导入路径,升级本身可能会失败,即使升级完成后在最终结果中可能不会使用升级路径也是如此(在#30831中进行了跟踪)。
- 如果上述步骤不能解决问题,则可能是因为一个或多个依赖项的最新版本仍在使用有问题的旧导入路径。在这种情况下,重要的是确定谁仍在使用有问题的旧导入路径,并查找或打开一个问题,要求有问题的进口商更改为使用现在的规范导入路径。
gotip
在上面的第2步中使用可能会发现有问题的进口商,但并非在所有情况下都可以,尤其是对于升级(#30661)。如果不清楚是谁在使用有问题的旧导入路径进行导入,通常可以通过创建干净的模块高速缓存,执行触发错误的一个或多个操作,然后在模块高速缓存中查找旧的有问题的导入路径来找出答案。例如:
export GOPATH=$(mktemp -d)
go get -u foo # peform operation that generates the error of interest
cd $GOPATH/pkg/mod
grep -R --include="*.go" github.com/Quasilyte/go-consistent
- 如果这些步骤不足以解决问题,或者您是某个项目的维护者,但由于循环引用而似乎无法删除对较旧的有问题的导入路径的引用,请在上查看有关该问题的详细说明。单独的Wiki页面。
最后,上述步骤集中于如何解决潜在的“旧名称”与“新名称”问题。但是,如果将a go.mod
放置在错误的位置或仅具有错误的模块路径,也会出现相同的错误消息。在这种情况下,导入该模块应始终失败。如果要导入刚刚创建且从未成功导入的新模块,则应检查go.mod
文件是否正确定位,以及文件是否具有与该位置对应的正确模块路径。(最常见的方法是go.mod
每个存储库使用单个go.mod
文件,并将单个文件放置在存储库根目录中,并使用存储库名称作为module
指令中声明的模块路径)。参见“ go.mod”
为什么“开始构建”需要gcc,为什么不使用诸如net / http之类的预构建软件包?
简而言之:
因为预构建的软件包是非模块构建的,所以不能重复使用。抱歉。现在禁用cgo或安装gcc。
仅在选择加入模块时(例如通过GO111MODULE=on
),这才是一个问题。有关其他讨论,请参见#26988。
模块可以与类似的进口商品一起使用import "./subdir"
吗?
否。请参阅#26645,其中包括:
在模块中,最后有一个子目录的名称。如果父目录显示“模块m”,则子目录将导入为“ m / subdir”,而不再是“ ./subdir”。
某些vendor目录中可能没有所需的文件
没有.go
文件的vendor
目录不会通过复制在目录内go mod vendor
。这是设计使然。
简而言之,撇开任何特定的vendor行为– go构建的总体模型是构建软件包所需的文件应位于包含.go
文件的目录中。
以cgo为例,修改其他目录中的C源代码不会触发重建,而是您的构建将使用陈旧的缓存条目。cgo文档现在包括:
请注意,对其他目录中文件的更改不会导致重新编译该软件包,因此,该软件包的所有非Go源代码应存储在软件包目录中,而不是子目录中。
社区工具https://github.com/goware/modvendor允许您轻松地将一整套.c,.h,.s,.proto或其他文件从模块复制到vendor
Director中。尽管这可能会有所帮助,但是如果您有一些需要的文件来构建包含该.go
文件的目录之外的文件,则必须格外小心,以确保总体上可以正确处理go编译(与vendor无关)。
请参阅#26366中的其他讨论。
传统vendor的另一种方法是检入模块缓存。它最终可能会获得与传统vendor类似的好处,并且在某些方面最终会获得更高的保真度。将此方法解释为“通过示例执行模块” 演练。