Golang 模块(Module)官方手册_语义化

 

 

 

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

​vgo​

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

近期变动

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

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

三个值得注意的变化:

​GO111MODULE=auto​​GO111MODULE=auto​​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:

初始化一个新模块:

编写代码:

构建并运行:

​go.mod​​v1.5.2​

日常工作流程

​go get​

典型的日常工作流程可以是:

​.go​​go build​​go test​​go.mod​​go get foo@v1.2.3​​go get foo@master​​foo@tip​​go get foo@e3702bed2​​go.mod​

您可能会使用的其他常见功能的简要介绍:

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

新概念

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

模块

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

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

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

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

  • 一个存储库包含一个或多个Go模块。
  • 每个模块包含一个或多个Go软件包。
  • 每个软件包都在一个目录中包含一个或多个Go源文件。
​v(major).(minor).(patch)​​v0.1.0​​v1.2.3​​v1.5.0-rc.1​​v​

go.mod

​go.mod​​module​​require​​replace​​exclude​
​go.mod​​github.com/my/thing​
​go.mod​​module​​go.mod​
​github.com/my/repo​​github.com/my/repo/foo​​github.com/my/repo/bar​​go.mod​​module github.com/my/repo​
​go.mod​​module example.com/my/module​
​mypkg​​example.com/my/module​
​exclude​​replace​​exclude​​replace​​replace​​exclude​​replace​

版本选择

​require​​go.mod​​go.mod​​require​​v1.2.3​​go.mod​​require M v1.2.3​
​require​
​require D v1.0.0​​require D v1.1.1​​v1.1.1​​require​​v1.1.1​​v1.2.0​

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

​go list -m all​

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

语义导入版本控制

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

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

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

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

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

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

​v1.2.3​​/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​
​math/rand​​crypto/rand​​example.com/my/mod/mypkg​​example.com/my/mod/v2/mypkg​
​go​

到目前为止,本节的重点是已选择加入模块并导入其他模块的代码。但是,将主要版本置于v2 +模块的导入路径中可能会与Go的较早版本或尚未选择加入模块的代码产生不兼容性。为了解决这个问题,上述行为和规则有三种重要的过渡性特殊情况或例外。随着越来越多的程序包加入模块,这些过渡性异常将不再重要。

三个例外:

有关发布v2 +模块所需的确切机制,请参阅下面的​​“发布模块(v2或更高版本)”​​部分。

如何使用模块

如何安装和激活模块支持

要使用模块,两个安装选项是:

安装后,您可以通过以下两种方式之一激活模块支持:

​go​​$GOPATH/src​​go.mod​​GO111MODULE​​auto​​go​​GO111MODULE=on​

如何定义模块

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

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

​go.mod​
​Gopkg.lock​
​go mod init​​go mod init​​go mod init​
​go mod init​​go.mod​​.go​​/vN​​go mod init​​dep​​go mod init​​go mod tidy​​go build ./...​
​./...​​go build​
  1. 按照配置测试模块,以确保它可以与所选版本一起使用:
  1. (可选)运行模块的测试以及所有直接和间接依赖项的测试,以检查不兼容性:

在标记发行版之前,请参见下面的​​“如何准备发行版”​​部分。

有关所有这些主题的更多信息,​​可在golang.org​​​上​​找到​​官方模块文档的主要入口点。

如何升级和降级依赖项

​go.mod​​go.mod​
​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​​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​
​go get foo@v1.6.2​​go get foo@e3702bed2​​go get foo@'
​go get foo@master​​foo@tip​
​go.mod​
​go​
​go.mod​​go.mod​

在升级或降级任何依赖项之后,您可能想要对构建中的所有软件包(包括直接和间接依赖项)再次运行测试以检查不兼容性:

如何准备发布

发行模块(所有版本)

创建模块发行版的最佳实践有望作为初始模块实验的一部分出现。其中许多最终可能会由​​将来的“发布”工具​​自动化。

在标记版本之前,应考虑一些当前建议的最佳做法:

​go build​​go test​​go.mod​​go.mod​
​go test all​
​go test all​

发行模块(v2或更高版本)

如果要发布v2或更高版本的模块,请首先查看上面​​“语义导入版本控制”​​部分中的讨论,其中包括为何在v2 +模块的模块路径和导入路径中包含主要版本以及Go版本1.9的方式.7+和1.10.3+已更新,以简化该过渡。

​v2.0.0​​foo​​foo​​v2.2.2​​foo​​v3.0.0​​foo​​foo​​go.mod​​foo​​foo​​foo​​import "foo"​​require foo v2.2.2+incompatible​​import "foo/v3"​​require foo/v3 v3.0.0​
​v3.0.0​
​go.mod​​/v3​​module​​module github.com/my/module/v3​​/v3​​import "github.com/my/module/v3/mypkg"​​v3.0.0​
​v3​​my/module/v3​​go.mod​​/v3​​v3​​/v3​​import "github.com/my/module/v3/mypkg"​​v3.0.0​
​goforward​​goforward​​dep​

有关这些替代方案的更深入讨论,请参见​​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和非模块消费者的较旧版本提供依赖信息

更新现有安装说明

​go get -u foo​​foo​​-u​
​-u​​go​​foo​​go get -u foo​​-u​​go get -u foo​
​go get foo​
​import "foo"​​go build​​go test​​foo​​go.mod​
​vendor​

避免破坏现有的导入路径

​go.mod​​module​​module github.com/my/module​​go​​unexpected module path​

在为一组预先存在的软件包采用模块时,应注意避免破坏现有使用者使用的现有导入路径,除非在采用模块时增加主版本。

​import "gopkg.in/foo.v1"​​go.mod​​module gopkg.in/foo.v1​​gopkg.in​​module github.com/repo/foo/v2​
​github.com/Sirupsen/logrus​​github.com/sirupsen/logrus​
​go.mod​
​go.uber.org/zap​​github.com/uber-go/zap​
​package zap // import "go.uber.org/zap"​

go.mod文件的module语句已淘汰了导入路径注释。

首次采用带有v2 +软件包的模块时增加主要版本

v2 +模块允许在一个内部版本中使用多个主要版本

​foo​​foo/v3​
​foo​​foo/v3​​init​

消耗非模块代码的模块

非模块代码消费模块

  • 非模块代码消耗v0和v1模块:
  • 尚未选择使用模块的代码可以使用和构建v0和v1模块(与使用的Go版本无关)。
  • 非模块代码消耗v2 +模块:

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

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

 

常见问题

版本如何标记为不兼容?

​require​​exclude​​replace​

版本模块建议的主要目标之一是为工具和开发人员在Go代码的版本周围添加通用词汇和语义。这为将来声明不兼容的其他形式奠定了基础,例如:

什么时候出现旧行为与新的基于模块的行为?

通常,模块是Go 1.11的可选组件,因此,根据设计,默认情况下会保留旧的行为。

总结何时获得旧的1.10现状行为与新的基于选择加入模块的行为:

​go.mod​
​auto​​on​​off​
​go get​​cannot find main module​
​GO111MODULE=on​​go.mod​​go get​
​GO111MODULE​​GO111MODULE=auto​
​go.mod​​go.mod​​go get​​GO111MODULE=on​​go get​​cannot find main module​​go.mod​

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

​GO111MODULE​​GO111MODULE=auto​​go get​​cannot find main module​​export GO111MODULE=on​​go get​​GO111MODULE=off go get example.com/cmd​​alias oldget='GO111MODULE=off go get'​​go.mod​​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.mod​
​tools.go​​import _ "golang.org/x/tools/cmd/stringer"​​// +build tools​​go​​go.mod​​// +build tools​

有关如何执行此操作的具体示例,请参见本​​“通过示例执行模块”演练​​。

​​#25922​​​中的​​此注释中​​讨论了该方法以及更早的具体示例。

简要理由(同样来自​​#25922​​):

我认为tools.go文件实际上是工具依赖关系的最佳实践,当然对于Go 1.11。

我喜欢它,因为它没有引入新的机制。

它只是简单地重用现有的。

IDE,编辑器和标准工具(例如goimports,gorename等)中模块支持的状态如何?

对模块的支持已开始在编辑器和IDE中获得。

例如:

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

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

​go mod vendor​
​go/build​​golang.org/x/tools/go/packages​​go/packages​

常见问题解答-附加控制

存在哪些社区工具来使用模块?

社区开始在模块之上构建工具。例如:

​replace​​gohack example.com/some/dependency​​replace​​go.mod​​gohack undo​
​go.mod​
​mgit -tag +0.0.1​
​vendor/​
  • 以人类友好的方式显示过时的依赖关系
  • 提供一种过滤间接依赖关系和无需更新的依赖关系的方法
  • 提供了一种在依赖项过时的情况下中断CI管道的方法

什么时候应该使用replace指令?

​replace​​go.mod​​replace​
​replace​​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​
​replace example.com/project/foo => ../foo​
​replace​​go.mod​​go.mod​​go mod init​
​=>​​replace​
​require​​replace​​foo​​replace foo => ../foo​​require​​foo​​require​​v0.0.0​​require foo v0.0.0​
​go list -m all​​replace​

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

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

​replace​

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

是。不需要VCS。

​go.mod​​go build​​go test​​replace​​go.mod​
​replace​​go.mod​​replace​​hello​​goodbye​
​v0.0.0​​require​​require​​require​

该​​线程中​​显示了一个小的可运行示例。

如何对模块使用vendor?vendor会消失吗?

​vgo​

简而言之,要对模块使用vendor:

​go mod vendor​​go build​​-mod=vendor​​go build -mod=vendor​​GOFLAGS=-mod=vendor​
​go mod vendor​

如果您正在考虑使用vendor,则值得阅读技巧文档中的​​“模块和vendor”​​​和​​“提供依赖关系的vendor副本”​​部分。

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

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

例如:

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

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

​go build​
​go.mod​
​-mod=readonly​​-mod=vendor​​GOFLAGS​​GOPROXY=off​​GOPROXY=file:///filesystem/path​​go mod vendor​​go mod download​

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

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

​GO111MODULE=on​

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

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

常见问题解答— go.mod和go.sum

为什么“ go mod tidy”在我的“ 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 foo@1.2.3​​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​
​go.sum​​go.sum​​go.sum​
​go.sum​
​go.sum​​go.sum​​go.mod​

我是否应该提交“ go.sum”文件以及“ go.mod”文件?

​go.sum​​go.mod​
​go.sum​​go.sum​​go mod verify​​go.sum​​go.sum​​go.mod​​go.sum​

如果我没有任何依赖关系,还应该添加一个“ go.mod”文件吗?

​module​​go.mod​

常见问题解答—语义导入版本控制

为什么主版本号必须出现在导入路径中?

请参阅上面的​​“语义导入版本控制”​​​概念部分中有关语义导入版本控制和导入兼容性规则的讨论。另请参阅​​宣布提案​​​的​​博客文章​​,其中更多地讨论了导入兼容性规则的动机和理由。

为什么导入路径中省略了主要版本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​​go get foo​​go get foo@v1.2.3​​go.mod​
​go​​go list -u=patch​​go list -u -m all​

有关尚未选择模块的v2 +软件包的更多详细信息,请参见下一个常见问题解答。

一个模块可以使用未选择模块的v2 +软件包吗?“ +不兼容”是什么意思?

​+incompatible​

额外细节

请熟悉上面​​“语义导入版本控制”​​部分中的材料。

首先回顾一些通常有用但在考虑本FAQ中描述的行为时要记住的特别重要的核心原则会有所帮助。

​go​​GO111MODULE=on​
  1. 软件包的导入路径定义了软件包的标识。
  • 具有不同导入路径的软件包被视为不同的软件包。
  • 具有相同导入路径的软件包被视为相同的软件包(即使VCS标签说这些软件包具有不同的主要版本,也是如此)。
​/vN​​module foo/v2​​go.mod​
  • 该模块身份的明确声明
  • 关于必须如何通过使用代码导入该模块的明确声明
​go​​go​
​+incompatible​
​/vN​
​go​​+incompatible​​go​

假设:

​oldpackage​​oldpackage​​go.mod​​oldpackage​​v3.0.1​
​go get oldpackage@latest​​go.mod​
​/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​​oldpackage​​v3.0.1​​oldpackage​​+incompatible​​go​
​v3.0.1​​oldpackage​​v1.0.0​​v2.0.0​​v3.0.1​
​/v3​​oldpackage​
​v1.0.0​​v2.0.0​​v3.0.1​​oldpackage​​oldpackage​​oldpackage​​require​
​v4.0.0​​oldpackage​​go.mod​​oldpackage​​/v4​

该版本将记录为:

​oldpackage/v4​​oldpackage​​import "oldpackage/v4"​​import "oldpackage"​​oldpackage/v4​​oldpackage/v5​

如果未启用模块支持,如何在版本中处理v2 +模块?1.9.7 +,1.10.3 +和1.11中的“最小模块兼容性”如何工作?

在考虑尚未加入模块的较旧的Go版本或Go代码时,语义导入版本控制具有与v2 +模块相关的显着向后兼容性含义。

​/vN​​go.mod​​/vN​

但是,预计生态系统将以不同的采用模块和语义导入版本控制的速度进行。

​mymodule/v2​​mymodule/v3​​import "mymodule/v2/mypkg"​
​/vN​​go.mod​​master​

为了在当前过渡时期提供帮助,Go 1.11 ​​引入​​了“最小模块兼容性” ,以为尚未加入模块的Go代码提供更大的兼容性,并且“最小模块兼容性”也被反向移植到Go 1.9。 7和1.10.3(鉴于那些旧版Go版本不具有完整模块支持,这些版本始终在禁用完整模块模式的情况下始终有效运行)。

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

​/vN​​/vN​​/vN​

其他详细信息–“最小模块兼容性”

​go​​GO111MODULE=off​
​/v2​​/vN​
  • 选择模块的软件包在任何导入的v2 +模块的导入路径中都不会包含主版本。
  • 相反,一个包已经选择加入的模块必须包括在导入路径主要版本导入任何V2 +模块。
​go​​foo​​import "foo"​​foo​
  • 用于实现“最小模块兼容性”的机制故意非常狭窄:
​/vN​​/vN​​.go​​go.mod​​import "foo/v2"​​import "foo"​​/v2​​foo​​/v2​​go​​go get​​go list​
  • 这种过渡的“最小模块感知”机制有意打破了“将具有不同导入路径的软件包视为不同的软件包”的规则,以实现非常具体的向后兼容性目标–允许旧代码在使用v2 +模块时进行编译,而无需修改。稍微详细一点:
​/vN​​import "foo/v2"​​import "foo"​​foo​​import "foo/v2"​
​go​
​go​​foo​​import "foo"​​foo​​import "foo/v2"​​foo​

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

​v0.1.0​​v1.2.3-rc.1​
​go​

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

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

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

​/v3​

如果您惊讶地看到一个模块依赖于其自身的不同版本,那么值得回顾一下上面的​​“语义导入版本控制”​​​部分以及常见问题解答​​“如果我没有看到预期的版本,该怎么办?依赖?” ​​。

两个程序包可能在一个周期中彼此不依赖仍然是一个约束。

FAQS —多模块存储库

什么是多模块存储库?

多模块存储库是一个包含多个模块的存储库,每个模块都有自己的go.mod文件。每个模块均从包含其go.mod文件的目录开始,并递归包含该目录及其子目录中的所有程序包,但不包括包含另一个go.mod文件的任何子树。

每个模块都有自己的版本信息。存储库根目录下的模块的版本标签必须包含相对目录作为前缀。例如,考虑以下存储库:

模块“ my-repo / foo / rop”的1.2.3版本的标签是“ foo / rop / v1.2.3”。

通常,存储库中一个模块的路径将是其他模块的前缀。例如,考虑以下存储库:

图。顶级模块的路径是另一个模块的路径的前缀。

该存储库包含两个模块。但是,模块“ my-repo”是模块“ my-repo / mig”的路径的前缀。

我应该在一个存储库中有多个模块吗?

在这样的配置中添加模块,删除模块和版本控制模块需要相当的谨慎和考虑,因此,管理单个模块存储库而不是现有存储库中的多个模块几乎总是更容易,更简单。

拉斯·考克斯(Russ Cox)在​​#26664中​​评论:

对于除电源用户以外的所有用户,您可能希望采用一种惯例,即一个repo =一个模块。对于代码存储选项的长期发展很重要,一个仓库可以包含多个模块,但是默认情况下您几乎肯定不想这样做。

关于如何使多模块更有效的两个示例:

​go test ./...​​replace​

但是,除了这两个示例之外,还有其他细微差别。如果您考虑在单个存储库中包含多个模块,请仔细阅读本​​小节中​​的FAQ 。

​go.mod​
​examples​​_examples​​go.mod​​api​​clientapi​​go.mod​​clientapi​

但是,对于这两种情况,如果您考虑为多组间接依赖项创建性能或下载大小的多模块存储库,则强烈建议您首先尝试使用GOPROXY,它将在Go中默认启用1.13。使用GOPROXY通常等同于可能会因创建多模块存储库而带来的任何性能优势或依赖项下载大小优势。

是否可以将模块添加到多模块存储库?

是。但是,此问题有两类:

第一类:要添加模块的软件包尚未处于版本控制中(新软件包)。这种情况很简单:将包和go.mod添加到同一提交中,标记该提交,然后推送。

第二类:添加模块的路径在版本控制中,并且包含一个或多个现有软件包。这种情况需要相当多的护理。为了说明,再次考虑以下存储库(现在位于github.com位置,以更好地模拟真实世界):

考虑添加模块“ 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:
​git commit​​git tag v1.3.0​​git tag mig/v1.0.0​​go build​​go test​​go​
​git push origin master v1.2.4 mig/v1.0.0​

请注意,将来​​golang.org/issue/28835​​应该使测试步骤更直接。

还要注意,在次要版本之间,代码已从模块“ github.com/my-repo”中删除。不将其视为主要更改似乎很奇怪,但是在这种情况下,传递性依存关系继续在其原始导入路径中提供已删除软件包的兼容实现。

是否可以从多模块存储库中删除模块?

是的,具有与上述相同的两种情况和类似的步骤。

一个模块可以依赖于内部模块吗?

是。一个模块中的程序包可以从另一个模块中导入内部程序包,只要它们共享与内部/路径组件相同的路径前缀即可。例如,考虑以下存储库:

在这里,只要模块“ my-repo / foo”依赖于模块“ my-repo”,软件包foo就可以导入/ my-repo / internal。同样,在以下存储库中:

在这里,只要模块“ 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.mod​
​.go​​go.mod​​.go​

常见问题解答–最小版本选择

最少的版本选择是否会使开发人员无法获得重要的更新?

请参阅问题“最小版本选择是否会使开发人员无法获得重要更新?” 在较早的​​FAQ中,来自官方提案的讨论​​。

常见问题解答-可能的问题

如果我发现问题,可以进行哪些常规检查?

​go env​​GOMOD​
​GOMOD​​go env​​GO111MODULE=on​​GO111MODULES=on​​S​
​-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​​go get -v -x foo​​go​
  • 您可以检查是否使用了特别旧的git版本
​vgo​
​go​​go clean -modcache​

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

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

​go mod tidy​​go.mod​​.go​​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​
​go mod why -m 
​go list​

用于询问你的模块的更详细的命令集和实施例的能够在可运行“转到模块通过实施例”中可以看出​​walkthough​​。

​go.mod​​v2.0.1​​module foo​​go.mod​​/v2​​.go​​/v3​​require​​go.mod​​/v4​​go.mod​

为什么会出现错误“找不到提供软件包foo的模块”?

这是一条常见的错误消息,可能会因几种不同的根本原因而发生。

在某些情况下,此错误仅是由于路径键入错误引起的,因此第一步可能应该是根据错误消息中列出的详细信息再次检查错误的路径。

​go get -v foo​​go get -v -x foo​
​go get​​go build​

其他一些可能的原因:

为什么“ go mod init”给出错误“无法确定源目录的模块路径”?

​go mod init​​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​
​require​​go.mod​​go.mod​​require github.com/some/nonmodule v1.2.3​​go.mod​
​github.com/sirupsen/logrus​​github.com/Sirupsen/logrus​

如何解决由于导入路径与声明的模块标识不匹配而导致的“解析go.mod:意外的模块路径”和“错误加载模块要求”错误?

为什么会发生此错误?

​go.mod​​module​​module example.com/m​​go​​go.mod​​module example.com/m​​import "example.com/m"​​import "example.com/m/sub/pkg"​
​go​​parsing go.mod: unexpected module path​​go​​error loading module requirements​
​github.com/Sirupsen/logrus​​github.com/sirupsen/logrus​​github.com/golang/sync​​golang.org/x/sync​
​github.com/Sirupsen/logrus​​github.com/golang/sync​​go.mod​

问题场景示例

​github.com/Quasilyte/go-consistent​​github.com/quasilyte/go-consistent​​Q​​q​​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.mod​​module example.com/some/NEW/name​

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

​example.com/some/OLD/name​​example.com/some/NEW/name​​go get​
​go mod tidy​​go.mod​​github.com/golang/lint​​golang.org/x/lint​
​replace​​go.mod​​go get -u​​go mod graph | grep github.com/Quasilyte/go-consistent​​replace​
​go.mod​​go.mod​​go.mod​​go.mod​​module​

为什么“开始构建”需要gcc,为什么不使用诸如net / http之类的预构建软件包?

简而言之:

因为预构建的软件包是非模块构建的,所以不能重复使用。抱歉。现在禁用cgo或安装gcc。

​GO111MODULE=on​
​import "./subdir"​

否。请参阅​​#26645​​,其中包括:

在模块中,最后有一个子目录的名称。如果父目录显示“模块m”,则子目录将导入为“ m / subdir”,而不再是“ ./subdir”。

某些vendor目录中可能没有所需的文件

​.go​​vendor​​go mod vendor​
​.go​

以cgo为例,修改其他目录中的C源代码不会触发重建,而是您的构建将使用陈旧的缓存条目。cgo文档现在​​包括​​:

请注意,对其他目录中文件的更改不会导致重新编译该软件包,因此,该软件包的所有非Go源代码应存储在软件包目录中,而不是子目录中。

​vendor​​.go​

请参阅​​#26366中的​​其他讨论。

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