Golang 模块(Module)官方手册

 官方原文: https://github.com/golang/go/wiki/Modules

 

 

 

Go 1.11包括此处建议的对版本模块的初步支持模块是Go 1.11中的实验性加入功能,并计划纳入反馈并最终确定 Go 1.14中的功能。即使某些细节可能会更改,将来的发行版也将支持使用Go 1.11、1.12和1.13定义的模块。

最初的原型vgo2018年2月宣布。2018年7月,版本化的模块进入了主Go存储库。

请通过现有问题或新问题以及经验报告提供有关模块的反馈

近期变动

Go 1.13中对模块进行了重大改进和更改。

如果使用模块,请务必仔细阅读Go 1.13发行说明模块部分

三个值得注意的变化:

  1. go工具现在默认为从https://proxy.golang.org上的公共Go模块镜像下载模块,并且还默认为针对https://sum.golang.org的公共Go校验和数据库验证下载的模块(无论源如何)

    • 如果您有私人代码,则很可能应该配置GOPRIVATE设置(例如go env -w GOPRIVATE=*.corp.com,github.com/secret/repo),或者配置更细粒度的变体,GONOPROXY或者GONOSUMDB支持使用频率较低的用例。有关更多详细信息,请参见文档
  2. GO111MODULE=auto如果找到任何go.mod,即使在GOPATH内部,也将启用模块模式。(在Go 1.13之前,GO111MODULE=auto永远不会在GOPATH中启用模块模式)。

  3. go get 参数已更改:

    • go get -u(不带任何参数)现在仅升级当前软件包的直接和间接依赖关系,而不再检查整个模块
    • go get -u ./... 从模块根目录升级模块的所有直接和间接依赖关系,现在不包括测试依赖关系。
    • go get -u -t ./... 相似,但也升级了测试依赖项。
    • go get不再受支持-m(因为go get -d由于其他更改,它会在很大程度上与重叠;您通常可以替换go get -m foogo 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 <<EOF > 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 foo@v1.2.3go get foo@masterfoo@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.modOS,架构和构建标签的其他组合中修剪所有不需要的依赖项,并添加其他依赖项所需的任何依赖项(详细信息
  • replace指令或gohack—使用派生,本地副本或依赖项的确切版本(详细信息
  • go mod vendor—创建vendor目录的可选步骤(详细信息

在阅读了有关“新概念”的下四个部分之后,您将获得足够的信息来开始使用大多数项目的模块。查看上面的目录(包括此处的FAQ常见问题解答)以使自己熟悉更详细的主题列表也很有用

新概念

这些部分对主要的新概念进行了高级介绍。有关更多详细信息和原理,请观看Russ Cox的这段40分钟的介绍性视频,其中介绍了设计背后的理念正式的建议文档或更为详细的初始vgo博客系列

模块

一个模块是一些以版本作为单元相关的包的集合。

模块记录精确的依赖要求并创建可复制的构建。

通常,版本控制存储库仅包含在存储库根目录中定义的一个模块。单个存储库中支持多个模块,但是通常,与每个存储库中的单个模块相比,这将导致正在进行的工作更多)。

总结存储库,模块和软件包之间的关系:

  • 一个存储库包含一个或多个Go模块。
  • 每个模块包含一个或多个Go软件包。
  • 每个软件包都在一个目录中包含一个或多个Go源文件。

模块必须根据在语义版本语义化版本号,通常在形式v(major).(minor).(patch),如 v0.1.0v1.2.3,或v1.5.0-rc.1领导v是必需的。如果使用Git,则标记发布会提交其版本。公共和私有模块存储库和代理都可以使用(请参阅下面的常见问题解答)。

go.mod

模块由Go源文件树定义,该go.mod文件在树的根目录中。模块源代码可能位于GOPATH之外。有四种指令:modulerequirereplaceexclude

这是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.modas中声明其身份module example.com/my/module,则使用者可以执行以下操作:

import "example.com/my/module/mypkg"

mypkg将从模块导入包example.com/my/module

excludereplace指令仅在当前(“主”)模块上运行。在构建主模块时,将忽略除主模块以外的其他模块中的指令excludereplace指令。replaceexclude语句,因此,允许在自己的构建主要模块的完全控制权,也没有受制于由依赖于完全控制。(有关何时使用指令的讨论,请参见下面常见问题解答replace)。

版本选择

如果您在源代码中添加了一个尚未被requirein 覆盖的新导入,则go.mod大多数go命令(例如“ go build”和“ go test”)将自动查找适当的模块,并将该新直接依赖项的最高版本添加到您的模块go.modrequire指令。例如,如果您的新导入对应于依赖项M,其最新标记版本为v1.2.3,则模块的go.mod结尾将为require M v1.2.3,这表示模块M是允许版本> = v1.2.3(且<v2,因为v2被认为不兼容)的依赖项与v1)。

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

例如,如果您的模块依赖于具有A的模块A require D v1.0.0,而您的模块也依赖于具有A的模块B require D v1.1.1,则最小的版本选择将选择v1.1.1D包含在构建中(假设它是列出的最高require版本)。v1.1.1即使稍后某个v1.2.0D可用,对D的选择仍保持一致这是模块系统如何提供100%可复制构建的示例。准备就绪后,模块作者或用户可以选择升级到D的最新可用版本,或为D选择一个显式版本。

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

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

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

语义导入版本控制

多年以来,官方的Go常见问题解答已在软件包版本管理中包括以下建议:

“面向公共用途的软件包应在发展过程中尝试保持向后兼容性。Go1兼容性指南在此处是很好的参考:请勿删除导出的名称,鼓励带标签的复合文字等等。如果需要不同的功能,请添加一个新名称,而不是更改旧名称。如果需要完全中断,请创建一个具有新导入路径的新软件包。”

最后一句特别重要-如果破坏兼容性,则应更改软件包的导入路径。使用Go 1.11模块,该建议被正式化为导入兼容性规则

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

当v1或更高版本的软件包进行向后不兼容的更改时,召回语义化版本号需要对主要版本进行更改。遵循导入兼容性规则和语义化版本号的结果称为语义导入版本控制,其中主要版本包含在导入路径中-这可确保在主要版本由于兼容性中断而增加时,导入路径都会更改。

由于语义导入版本控制,选择加入Go模块的代码必须遵守以下规则

  • 跟随语义化版本号(示例VCS标签为v1.2.3)。
  • 如果模块的版本为v2或更高版本,则必须/vNgo.mod文件(例如module github.com/my/mod/v2require 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请注意,在该示例中同时包含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的较早版本或尚未选择加入模块的代码产生不兼容性。为了解决这个问题,上述行为和规则有三种重要的过渡性特殊情况或例外。随着越来越多的程序包加入模块,这些过渡性异常将不再重要。

三个例外:

  1. gopkg.in

    使用导入路径gopkg.in(以gopkg.in/yaml.v1开头)的现有代码gopkg.in/yaml.v2即使选择加入模块,也可以继续将这些格式用于其模块路径和导入路径。

  2. 导入非模块v2 +软件包时为“ +不兼容”

    模块可以导入尚未选择加入模块的v2 +软件包。具有有效v2 + 语义化版本号标签的非模块v2 +软件包+incompatible在导入模块的go.mod文件中记录后缀+incompatible后缀表示,即使V2 +包有一个有效的V2 + 语义化版本号标签,例如v2.0.0,使V2 +包没有主动选择的模块和假设,因此该V2 +包都没有被与语义进口版本的含义的理解产生以及如何在导入路径中使用主要版本。因此,当以模块模式运行时go该工具会将非模块v2 +软件包视为该软件包的v1版本系列的(不兼容)扩展,并假定该软件包不了解语义导入版本控制,并且+incompatible后缀表示该go工具正在这样做。

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

    为了帮助向后兼容,对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$GOPATH/src之外的目录中调用命令,并go.mod在当前目录或其任何父目录中使用有效文件,并且未GO111MODULE设置(或显式设置为auto环境变量
  • 调用go带有GO111MODULE=on环境变量设置命令

如何定义模块

go.mod现有项目创建一个

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

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

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

    或者,如果要在GOPATH中工作:

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

    $ go mod init                  
    

    此步骤从任何现有文件或其他所有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 ./...或类似操作后才能运行,这是本节中显示的顺序)。

  3. 生成模块。从模块的根目录执行时,该./...模式将匹配当前模块中的所有软件包。 go build会根据需要自动添加缺少或未转换的依赖项,以满足此特定构建调用的导入需求:

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

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

    $ 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更新到的最新版本foogo get foo等效于go get foo@latest—换句话说,@latest如果未@指定版本则为默认值

在本节中,“最新”是带有语义化版本号标签的最新版本,或者如果没有语义化版本号标签则是最新的已知提交。除非存储库中没有其他语义化版本号标签,否则不会将预发布标签选择为“最新”标签(details)。

一个普遍的错误是认为go get -u foo仅获取最新版本的foo实际上,-uin go get -u foo或的go get -u foo@latest意思是获得的所有直接和间接依赖关系的最新版本foo升级时,一个共同的起点foo是不是做go get foogo get foo@latest没有-u(和后一切正常,可以考虑go get -u=patch foogo get -u=patchgo get -u foo,或go get -u)。

要升级或降级到一个更具体的版本,“去把”允许版本选择通过添加一个后缀@version或覆盖“模块查询”到包的说法,比如go get foo@v1.6.2go get foo@e3702bed2或者go get foo@'<v1.6.2'

无论是否具有语义化版本号标记,使用分支名称(例如go get foo@masterfoo@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采用采用模块(因此将第一个版本的footo作为)。包含一个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发行版为例,两个选项是:

  1. Major分支:更新go.mod文件以/v3module指令的模块路径末尾包含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
  2. 主要子目录:创建一个新的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和非模块消费者的较旧版本提供依赖信息

  • 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 buildgo test根据需要自动下载foo和更新go.mod)。
  • 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/logrusgithub.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 +模块

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

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

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

    该方法使用“主要分支”方法,并依赖于“最小模块感知”,该模型被反向移植到1.9.7和1.10.3。有关更多详细信息,请参见上面的“语义导入版本控制”“发布模块(v2或更高版本)”部分。

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

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

  3. 等待加入模块

    在这种策略下,事情继续与选择了模块的客户端代码以及未选择模块的客户端代码一起工作。随着时间的流逝,Go版本1.9.7 +,1.10.3 +和1.11+的发布时间将越来越长,并且在将来的某个时候,要求Go版本变得更加自然或对客户友好1.9.7 + / 1.10.3 + / 1.11 +,此时,您可以实施以上策略1(需要Go版本1.9.7 +,1.10.3 +或1.11+),甚至可以实施以上策略2(但是如果最终要采用上述策略2来支持1.8等旧版Go,那么您现在就可以这样做。

 

常见问题

版本如何标记为不兼容?

require指令允许任何模块声明其应使用依赖项D的版本> = xyz构建(由于与模块D的版本<xyz不兼容而可能指定)。经验数据表明,这是在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=ongo.mod运行时不在文件树内部会发生这种情况go get

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

回想一下存在的主要原因之一是记录精确的依赖项信息。此依赖项信息将写入您的current go.mod如果您不在带有的文件树中,go.mod但是go get通过设置告诉命令以模块模式GO111MODULE=on运行,则运行go get将导致错误,cannot find main module因为没有go.mod可用来记录依赖项信息的信息。

解决方案的替代方案包括:

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

  2. export GO111MODULE=on,但根据需要暂时禁用模块,并在过程中启用Go 1.10行为go get,例如via GO111MODULE=off go get example.com/cmd可以将其转换为简单的脚本或shell别名,例如alias oldget='GO111MODULE=off go get'

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

  4. gobin是可识别模块的命令,用于安装和运行主软件包。默认情况下,gobin无需先手动创建模块即可安装/运行主程序包,但-m可以通过标志将该命令告知使用现有模块来解决依赖关系。请参阅gobin 自述文件常见问题解答以了解详细信息和其他用例。

  5. 创建一个go.mod用于跟踪运行的全局安装工具(例如中的)~/global-tools/go.mod,然后cd在运行之前跟踪该目录,go get跟踪go install所有全局安装的工具。

  6. go.mod为每个工具在单独的目录(例如~/tools/gorename/go.mod和)中创建一个~/tools/goimports/go.mod,并cd在运行前为该工具go getgo 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:工作正在进行中,正在寻找有助于的人。跟踪问题是#1532VS Code模块状态Wiki页面中描述了初始beta 
  • 带有加号的原子:跟踪问题是#761
  • 带vim-go的vim:语法高亮显示和格式的最初支持go.mod已经到来#1906年获得了更广泛的支持
  • 带go-mode.el的emacs#237中的跟踪问题

在雨伞问题中一直跟踪其他工具(例如goimports,guru,gorename和类似工具)的状态#24661请查看该伞的最新状态。

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

通常,即使您的编辑器,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相应的requirefor foo如果你不知道在用什么版本的require指令,你可以经常使用v0.0.0require foo v0.0.0这在Go 1.12中的#26241中得到了解决

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

有关更多详细信息,请参见“ go mod edit”文档

github.com/rogpeppe/gohack使这些类型的工作流变得更加容易,尤其是如果您的目标是对模块依赖项进行可变签出时。有关概述,请参见存储库或之前的常见问题解答。

有关在replaceVCS之外完全使用的详细信息,请参见下一个FAQ 

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

是。不需要VCS。

如果您要一次在VCS之外编辑单个模块,那么这非常简单(并且您总共只有一个模块,或者其他模块位于VCS中)。在这种情况下,可以将包含单个文件的文件树放置go.mod在方便的位置。go buildgo 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副本”部分。

是否存在“始终在线”的模块存储库和企业代理?

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

例如:

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

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

默认情况下,类似的命令go build会根据需要到达网络以达到导入要求。

有些团队可能希望禁止go工具在某些时候接触网络,或者想要更好地控制go工具何时更新go.mod,如何获得依赖关系以及如何使用vendor。

转到工具提供了相当数量的灵活调整或关闭这些默认的行为,包括通过-mod=readonly-mod=vendorGOFLAGSGOPROXY=offGOPROXY=file:///filesystem/pathgo mod vendor,和go mod download

这些选项的详细信息遍布整个官方文档。此处是一个社区,试图对与这些行为相关的旋钮进行综合概述,其中包括指向官方文档的链接,以获取更多信息。

如何将模块与Travis或CircleCI等CI系统一起使用?

最简单的方法可能只是设置环境变量GO111MODULE=on,该变量应适用于大多数CI系统。

但是,由于您的某些用户尚未选择加入模块,因此在启用和禁用模块的Go 1.11上的CI中运行测试可能很有价值。vendor也是要考虑的话题。

以下两个博客文章更具体地介绍了这些主题:

常见问题解答— 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提供电流下被请求包导入的包GOOSGOARCH和建立标签(这是一个原因go mod tidy想补充一点,没有被要求添加go build或类似)。

如果您模块的依赖项本身不具有go.mod(例如,因为该依赖项尚未选择加入模块本身),或者其go.mod文件缺少其一个或多个依赖项(例如,由于模块作者未运行go mod tidy) ,那么缺少的传递依赖将被添加到您的模块的要求,与沿// indirect注释,表明依赖是不是从你的模块中的直接进口。

请注意,这还意味着直接或间接依赖项中缺少的任何测试依赖项也将记录在go.mod(以下情况很重要的示例:对模块go test all所有直接和间接依赖项进行测试,这是验证您当前版本组合是否可以协同工作的一种方法。如果在运行时测试在您的一种依赖项中失败go test all,重要的是要记录一整套测试依赖项信息,以便您具有可重现的go test all行为。

// indirect您的go.mod文件中可能具有依赖项的另一个原因是,如果您已经升级(或降级了)一个间接依赖项,超出了直接依赖项所要求的范围(例如,运行go get -u或)go get foo@1.2.3go工具需要一个位置来记录这些新版本,并且它会在您的go.mod文件中记录(并且不会深入到您的依赖项中来修改 go.mod文件)。

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

如果你是好奇,为什么一个特定的模块,显示在你起来go.mod,你可以运行go mod why -m <module>回答这个问题。用于检查需求和版本的其他有用工具包括go mod graphgo list -m all

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

不,go.sum不是锁定文件。go.mod构建中文件为100%可复制的构建提供了足够的信息。

为了进行验证,go.sum包含特定模块版本的内容的预期密码校验和。有关详细信息(包括为什么通常需要签入)以及技巧文档中“模块下载和验证”部分,请参见下面FAQgo.sumgo.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.3foo自身尚未选择加入模块,则在模块M的内部运行go get foogo get foo@v1.2.3从模块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中描述的行为时要记住的特别重要的核心原则会有所帮助。

工具以模块模式(例如运行时,以下核心原则始终是正确的goGO111MODULE=on

  1. 软件包的导入路径定义了软件包的标识。
    • 具有不同导入路径的软件包被视为不同的软件包。
    • 具有相同导入路径的软件包被视为相同的软件包(即使 VCS标签说这些软件包具有不同的主要版本,也是如此)。
  2. 不带a的导入路径/vN被视为v1或v0模块(即使导入的程序包未选择加入模块并且具有表示主要版本大于1的VCS标记,也是如此)。
  3. 在模块文件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的末尾没有使用– 在模块路径和导入路径中使用是语义导入版本控制的功能,并且未表示接受并理解了语义导入版本控制(尚未给出)通过在其内部包含文件来选择加入模块换句话说,即使具有语义化版本号标签也不会被授予语义导入版本控制的权利和责任(例如,在导入路径中使用),因为尚未声明要这样做。oldpackagego getrequire/vNoldpackageoldpackagego.modoldpackageoldpackagev3.0.1oldpackage/vNoldpackage

+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.0v2.0.0以及v3.0.1使用相同的导入路径都始终输入:

import  "oldpackage"

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

通常,具有不同导入路径的软件包是不同的软件包。在这个例子中,给出的版本v1.0.0v2.0.0v3.0.1oldpackage会使用相同的导入路径需要进口,因此它们是通过构建视为同一包(也因为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版本不具有完整模块支持,这些版本始终在禁用完整模块模式的情况下始终有效运行)。

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

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

  2. 允许旧代码能够使用v2 +模块,而无需使用旧的消费者代码在使用/vNv2 +模块时立即更改为使用新的导入路径。

  3. 这样做无需依赖模块作者即可创建/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)。
  • 用于实现“最小模块兼容性”的机制故意非常狭窄:
    • 整个逻辑是–当在GOPATH模式下运行时,如果导入语句位于选择加入模块的代码内(即,文件中的导入语句),则/vN在删除后,将再次尝试包含a的无法解析的导入语句带有有效文件)。/vN.gogo.mod
    • 最终结果是,导入语句(例如import "foo/v2"位于模块内部的代码内)仍将在GOPATH模式下(分别为1.9.7 +,1.10.3 +和1.11)正确编译,并且将像说的那样进行解析import "foo"(不带/v2) ,这意味着它将使用foo驻留在您的GOPATH中的版本,而不会被多余的混淆/v2
    • “最小模块兼容性”不会影响其他任何内容,包括它不会影响go命令行中使用的路径(例如go get或的参数go list)。
  • 这种过渡的“最小模块感知”机制有意打破了“将具有不同导入路径的软件包视为不同的软件包”的规则,以实现非常具体的向后兼容性目标–允许旧代码在使用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"要求提供foovs 的v1版本,import "foo/v2"要求提供的v2版本foo

如果我创建go.mod但不将语义化版本号标记应用于存储库,会发生什么情况?

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

  1. 要求模块遵循语义化版本号规范,以使该go命令按照记录的方式运行。这包括遵循关于如何以及何时允许更改的语义化版本号规范。

  2. 消费者使用伪版本形式的语义化版本号版本记录没有语义化版本号 VCS标签的模块通常,这将是v0主版本,除非模块作者遵循“主子目录”方法构造了v2 +模块

  3. 因此,不应用语义化版本号 VCS标记且未创建“主要子目录”的模块将有效地声明自己属于语义化版本号 v0主版本系列,并且基于模块的使用者将其视为具有语义化版本号 v0主版本。

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

一个模块可以依赖于其自身的不同主要版本:总的来说,这相当于依赖于不同的模块。出于各种原因,这可能很有用,包括允许将模块的主要版本实现为围绕其他主要版本的填充程序。

此外,一个模块可以在一个周期中依赖于其自身的不同主要版本,就像两个完全不同的模块可以在一个周期中彼此​​依赖一样。

但是,如果您不希望模块依赖于其自身的不同版本,则可能是错误的征兆。例如,打算从v3模块导入软件包的.go代码可能缺少/v3import语句中所需的内容。根据本身的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一个存储库中可能有多个合理的场景

  1. 如果您有一些使用示例,其中这些示例本身具有一组复杂的依赖关系(例如,也许您的软件包很小,但包括一个将软件包与kubernetes结合使用的示例)。在这种情况下,对于您的存储库来说,拥有一个examples一个_examples自己的目录是很有意义go.mod,例如here

  2. 如果您的存储库具有一组复杂的依赖关系,但是您的客户端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,让我们通过上面的存储库逐步进行操作:

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

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

  3. git tag v1.3.0

  4. git tag mig/v1.0.0

  5. 接下来,让我们测试一下。我们不能go build还是go test天真地做,因为go命令会尝试从模块缓存中获取每个相关模块。因此,我们需要使用替换规则来使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
    
  6. 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中则很少出现。
  • Go 1.11中的模块缓存有时可能会导致各种错误,主要是如果先前存在网络问题或go并行执行多个命令(请参阅#26794,Go 1.12已解决)。作为故障排除步骤,您可以将$ GOPATH / pkg / mod复制到备份目录(以防稍后需要进一步调查),运行go clean -modcache,然后查看原始问题是否仍然存在。
  • 如果您使用的是Docker,则检查是否可以在Docker之外重现行为可能会有所帮助(并且如果该行为仅在Docker中发生,则上面的项目符号列表可以用作比较Docker内部与外面)。

当前正在检查的错误可能是由于构建中没有特定模块或软件包的预期版本而引起的第二个问题。因此,如果导致特定错误的原因不明显,则可以按照下一个FAQ中的说明对您的版本进行抽查。

如果没有看到期望的依赖版本,该如何检查?

  1. 一个好的第一步是运行go mod tidy这可能会解决问题,但也有可能使您的go.mod文件相对于.go源代码处于一致状态,这将使以后的调查更加容易。(如果go mod tidy它本身以某种您不希望的方式更改了依赖关系的版本,请先阅读'go mod tidy'上的常见问题解答。如果仍无法解决,您可以尝试重置您的go.mod,然后运行go list -mod=readonly all,这可能会带来更多的变化。有关需要更改其版本的特定消息)。

  2. 第二步通常应检查go list -m all以查看为您的构建选择的实际版本列表。 go list -m all向您显示最终选择的版本,包括用于间接依赖性的版本以及在解决所有共享依赖性的版本之后的版本。它还显示any replaceexclude指令的结果

  3. 下一步是检查go mod graph的输出go mod graph | grep <module-of-interest>。 go mod graph打印模块需求图(包括考虑的更换)。输出中的每一行都有两个字段:第一列是使用模块,第二列是该模块的要求之一(包括该使用模块所需的版本)。这是查看哪些模块需要特定依赖项的快速方法,包括当构建的依赖项具有与构建中的不同使用者不同的所需版本时(如果是这种情况,熟悉该模块很重要)。行为在上面的“版本选择”部分中进行了说明)。

go mod why -m <module> 在这里也可以是有用的,尽管通常对于查看为什么完全包含依赖项(而不是为什么依赖项以特定版本结束)更为有用。

go list提供了更多的查询变体,在需要时可以用于查询模块。以下是一个示例,它将显示构建中使用的确切版本,不包括仅测试依赖项:

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

用于询问你的模块的更详细的命令集和实施例的能够在可运行“转到模块通过实施例”中可以看出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.lockglide.lock)或包含相应指令vendor.jsongo.mod文件自动从先前的依赖项管理器转换requireGopkg.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/logrusvs. 之间区分大小写的问题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/logrusgithub.com/sirupsen/logrus),或者由于虚荣导入路径而导致模块有时在模块之前通过两个不同的名称使用(例如github.com/golang/syncvs.建议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/go-consistent@v0.0.0-20190521200055-c6f3937de18c:解析go.mod:意外的模块路径“ github.com/quasilyte/go-consistent”转到:错误加载模块要求

解决

错误的最常见形式是:

转到:example.com/some/OLD/name@vX.YZ:解析go.mod:意外的模块路径“ example.com/some/NEW/name”

如果您访问存储库的信息example.com/some/NEW/name(从错误的右侧开始),则可以检查go.mod文件的最新版本,或者master查看它是否在go.modas 的第一行中声明了自己module example.com/some/NEW/name如果是这样,则暗示您看到的是“旧模块名称”与“新模块名称”的问题。

本节的其余部分重点在于按顺序执行以下步骤来解决此错误的“旧名称”和“新名称”形式:

  1. 检查您自己的代码,看看是否要使用导入example.com/some/OLD/name如果是这样,请更新您的代码以导入example.com/some/NEW/name

  2. 如果您在升级过程中收到此错误,则应尝试使用Go的尖端版本进行升级,该尖端版本具有针对性更强的升级逻辑(#26902),通常可以回避此问题,并且在这种情况下通常还具有更好的错误消息。请注意,go gettip / 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/lintgolang.org/x/lint问题。

  1. 如果您在执行go get -u foo或时收到此错误go get -u foo@latest,请尝试删除-u这将为您提供一组所使用的依赖项,foo@latest而无需升级发布foofoo可能会验证为工作过去版本的依赖项foo这在过渡时期可能很重要,在这种过渡时期,某些直接或间接的依赖关系foo可能尚未采用语义化版本号或模块。(一个常见的错误是认为go get -u foo仅获取的最新版本foo。实际上,-uin go get -u foogo get -u foo@latest手段获取的所有直接和间接依赖项的最新版本foo可能正是您想要的,但如果由于深层间接依赖而导致失败,则可能不会特别如此。

  2. 如果上述步骤不能解决错误,则下一种方法会稍微复杂一些,但大多数情况下应该可以解决此错误的“旧名称”和“新名称”形式。这仅使用仅来自错误消息本身的信息,并简要介绍了一些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 <version-just-before-go.mod> 使用我们先前的示例,其中哪里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中进行了跟踪)。

  1. 如果上述步骤不能解决问题,则可能是因为一个或多个依赖项的最新版本仍在使用有问题的旧导入路径。在这种情况下,重要的是确定谁仍在使用有问题的旧导入路径,并查找或打开一个问题,要求有问题的进口商更改为使用现在的规范导入路径。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
  1. 如果这些步骤不足以解决问题,或者您是某个项目的维护者,但由于循环引用而似乎无法删除对较旧的有问题的导入路径的引用,请在上查看有关该问题的详细说明。单独的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或其他文件从模块复制到vendorDirector中。尽管这可能会有所帮助,但是如果您有一些需要的文件来构建包含该.go文件的目录之外的文件,则必须格外小心,以确保总体上可以正确处理go编译(与vendor无关)

请参阅#26366中的其他讨论

传统vendor的另一种方法是检入模块缓存。它最终可能会获得与传统vendor类似的好处,并且在某些方面最终会获得更高的保真度。将此方法解释为“通过示例执行模块” 演练

posted @ 2019-10-17 17:25  sunsky303  阅读(5766)  评论(0编辑  收藏  举报
显示搜索