Go语言在1.11版本引入了Modules,这样以后就不需要GOPATH了。
Go语言Wiki里面有专门讲这个:https://github.com/golang/go/wiki/Modules
本文主要是想对这个篇Wiki翻译、理解。
Go Modules
从1.11版本开始,Go语言就包含了对版本化模块(versioned modules)功能的支持。原型机vgo于2018年2月发布,2018年7月,Modules进入了Go主仓库。
在Go 1.14版本中,模块功能(Modules)支持被认为可用于生产,并且鼓励所有用户从其他依赖管理系统迁移到Modules。
近期修改
Go 1.14
可以通过查看Go1.14版本Release Note获取完整细节。
1. 当主模块包含一个顶级vendor目录,其go.mod文件指定go1.14或更高版本时,go命令现在默认设置标志 -mod=vendor。
2. 当go.mod文件是只读,且没有顶级vendor目录时,-mod=readonly 默认设置。
3. 新标志 -modcacherw 指示go命令在模块缓存中以默认权限保留新创建的目录,而不是将其设置为只读权限。
4. 新标志 -modfile=file 指示go命令读(写)一个备用的go.mod文件,而不是模块根目录中的文件。
5. 当模块感知模式启用时(设置 GO111MODULE=on ),如果没有go.mod文件,大多数模块命令都有功能限制。
6. go命令现在支持模块模式下的Subversion(SVN)仓库。
Go 1.13
可以通过查看Go1.13版本Release Note获取完整细节。
1. 现在go工具默认是从公共Go模块镜像(https://proxy.golang.org)上下载模块,也可以根据Go公共校验数据库(https://sum.golang.org)来验证下载的模块(不管来源)。
如果您有私人代码,建议您配置GOPRIVATE设置项:go env -w GOPRIVATE=*.corp.com,github.com/secret/repo
或者使用GONOPROXY或GONOSUMDB。 具体参见文档。
2. 如果找到任何go.mod文件,即使是在GOPATH路径中,如果设置了 GO111MODULE=auto ,也会启用模块模式。
P.S. 在Go1.13之前,G111MODULE=auto永远不会在GOPATH中启用模块模式的。
3. go get 参数变化:
1. go get -u (不带任何参数)现在仅升级当前软件包的直接和间接依赖关系,而不会再检查整个模块。
2. go get -u ./… 从模块根目录升级模块的所有直接和间接依赖关系,现在不包括测试依赖关系。
3. go get -u -t ./… 在前面的基础上,还升级了测试依赖关系。
4. go get 命令不再支持 -m。(因为由于其他更改,它会与go get -d很大程度上重叠;通常可以将 go get -m foo 替换为 go get -d foo)
目录
对于刚刚使用Modules的人来说,“快速入门” 和 “新概念” 这两部分特别重要。
“如何使用Modules”部分介绍了有关机器的更多细节。
该页面的最大篇幅是在讲述FAQ,它回答了更具体的问题,浏览一下此处的FAQ列表是值得的。
- 快速入门
- 示例
- 日常工作流程
- 新概念
- Modules
- go.mod
- 版本选择
- 语义导入版本控制
- 使用Module功能
- 安装并使用Module
- 升级、降级依赖项
- 准备并发布发行版
快速入门
示例
详细信息在本页面的其余部分中进行了介绍,这里是一个从头开始创建使用Modules功能的简单示例。
在GOPATH之外创建目录,并选择初始化VSC(版本控制系统 version control system), P.S. 不是必须初始化git
1 2 3 4 |
$ mkdir -p /tmp/scratchpad/repo $ cd /tmp/scratchpad/repo $ git init -q $ git remote add origin https://github.com/my/repo |
初始化一个新的Modules
1 2 3 |
$ go mod init github.com/my/repo go: creating new go.mod: module github.com/my/repo |
写代码:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ cat <<EOF > hello.go package main import ( "fmt" "rsc.io/quote" ) func main() { fmt.Println(quote.Hello()) } EOF |
编译,运行:
1 2 3 4 |
$ go build -o hello $ ./hello Hello, world. |
go.mod文件已更新,包括依赖项的显式版本,其中v1.5.2是一个semver标签。 P.s. semver (语义版本号 Semantic Versioning)
1 2 3 4 5 |
$ cat go.mod module github.com/my/repo require rsc.io/quote v1.5.2 |
日常工作流程
请注意,在上面的示例中并不需要 go get 命令
日常工作流程:
1. 根据需要将导入语句添加到 .go 代码中,示例:
import “google.golang.org/grpc”
2. 类似 go build 或者 go test 之类的标准命令将根据需要自动添加新的依赖关系,以满足导入要求(更新 go.mod 并下载新的依赖项)
3. 在有需要的时候,可以使用以下命令选择更具体的依赖项版本,举例:
- go get foo@v1.2.3
- go get foo@master (foo@default with mercurial)
- go get foo@e3702bed2
- 或者直接编辑go.mod文件
可能会使用的其他常见功能简要介绍:
命令1: go list -m all —— 列出将在构建中用于所有直接和间接依赖项的最终版本。(具体查看 新概念-版本选择)
命令2: go list -u -m all —— 查看所有直接或间接依赖项的minor.patch升级。(npm 版本规则:major.minor.patch, 示例: v1.2.3)
命令3: 从模块根目录调用 go get -u ./… 或 go get -u=patch ./… —— 将所有直接和间接依赖项更新为最新的minor.patch版本(忽略预发行版)
命令4: 从模块根目录调用 go build ./… 或 go test ./… —— 构建 或 测试 模块中所有的软件包。
命令5: go mod tidy —— 精简go.mod中所有不需要的依赖项,并添加其他所有依赖项。
命令6: replace指令或 gohack —— 使用依赖项的fork、本地副本或精确版本。
命令7: go mod vendor 创建vendor目录。
P.S. 注意上文中的命令1 和 2用到的-m参数,根据G1.13版本更新,已经不再支持-m,应该修改为-d。
阅读“新概念”四个部分就可以在大多数项目中使用Modules,通过查看目录和更详细的话题列表(包括其中FAQ常见问题解答)可以让自己更熟练。
新概念
这部分主要对新概念进行了高级介绍,更多有关详细信息和原理,请观看Russ Cox的40分钟视频(介绍设计理念)、正式的建议文档或者更为详细的vgo博客。
Modules
Module(模块)是相关的Go软件包的集合,将这些Go软件包作为一个单元进行版本控制。模块准确记录了依赖要求并且创建可复制的构建。
通常版本控制仓库仅包含在仓库根目录中定义的一个模块。(支持在单个仓库中有多个模块,但是比起一个库一个模块的方案来说,它的工作量更大。)
总结:仓库、模块和软件包之间的关系:
- 一个仓库包含一个或多个模块
- 每个模块包含一个或多个软件包
- 每个软件包在同一个目录中,包含一个或多个Go源文件。
模块必须根据semver进行语义版本控制,通常采用 v(major).(minro).(patch)的形式,例如v0.1.0,v1.2.3或v1.5.0-rc.1。前导v是必需的。
如果使用git,tag release 提交会带有版本号。
公共和私有模块仓库和代理都可以使用。(查看下面的FAQ)
go.mod
模块定义:含有go源文件的文件夹,文件夹根目录中有go.mod文件。
模块的源代码可以在GOPATH之外。
有四个指令: module、require、replace、exclude。
以下是一个 go.mod 的示例文件,定义模块 github.com/my/thing:
1 2 3 4 5 6 |
module github.com/my/thing require ( github.com/some/dependency v1.2.3 github.com/another/dependency/v4 v4.0.0 ) |
通过module指令提供模块路径(module path),在go.mod中声明其本身。
模块中所有软件包的导入路径将模块路径共享为公共前缀。
模块路径和go.mod到软件包目录的相对路径共同确定了软件包的导入路径。
举个例子:为仓库创建一个模块(github.com/user/mymod),包含两个软件包,它们的导入路径是(github.com/user/mymod/foo) 和 (github.com/user/mymod/bar)。
那么go.mod文件中的第一行通常会将模块路径声明为 (github.com/user/mymod),相应的磁盘文件结构可能是:
1 2 3 4 5 6 |
mymod |-- bar | `-- bar.go |-- foo | `-- foo.go `-- go.mod |
在Go源代码中,将使用完整路径(包括模块路径)导入软件包。
例如,在上面的示例中,我们在go.mod中将模块标识声明为 module github.com/user/mymod ,则使用者可以执行以下操作:
import “github.com/user/mymod/bar”
这将从模块 github.com/user/mymod 中导入 bar 软件包。
exclude和replace指令仅在当前(“主”)模块上运行。
构建主模块时,将忽略除主模块以外的其他模块中exclude和replace指令。
因此,replace和exclude语句允许主模块完全控制其自身的构建,而不受依赖项的完全控制。
(有关何时使用replace指令的讨论,参见Wiki的FAQ。)
版本选择
如果源代码中新导入的包是go.mod中没有的,那么大多数go命令(go build、 go test、 go run)将自动查找合适的模块,使用require指令添加其最高版本到go.mod文件中。
举个例子,如果新导入依赖项M,其最新的版本标记为v1.2.3,那么go.mod文件将require M v1.2.3添加到文件尾,这表明模块要求依赖项M版本号>=v1.2.3并且小于v2。(因为v2被视为与v1不兼容)
最小版本选择(minimal version selection)算法用于选择构建中使用的所有模块的版本。
对于构建中的每个模块,通过算法选择的版本始终是:主模块或其依赖项中require指令明确列出的版本中语义最高的版本。
(P.S. 我在想如果一个要求v1, 另一个要求v2怎么办)
举个例子,如果你的模块依赖了模块A,模块A依赖 D@v1.0.0,同时你的模块也依赖了模块B,模块B依赖D@v1.1.1,那么通过最小版本选择会选择v1.1.1版本的D模块添加到构建中去(因为它是列出的最高版本)。即使某个时间后D@v1.2.0版本可用,还是会选择v1.1.1版本。这是展示模块系统提供100%可复制构建的例子。
当一切就绪之后,模块作者或者用户可以选择升级到D的最新可用版本,或者为模块D选择一个明确的版本。
关于最小版本选择算法的简要原理和概述,参阅官方建议 Update Timing & High-Fidelity Builds 或者查看 vgo博客 。
命令: go list -m all 用来查看所选模块包括间接依赖的版本列表。
另外可以查看原文FAQ, “How to Upgrade and Downgrade Dependencies” 和 “How are versions marked as incompatible?” 下面的常见问题解答。
语义导入版本控制
多年以来,官方Go FAQ在软件包管理方面就有下面的建议:
面向大众的软件包在发展过程中应当尽量向后兼容。Go1兼容性指南在这里是一个很棒的参考:
- 不要删除导出名称,鼓励使用复合标签。
- 如果需要其他的功能,最好添加一个新名称而不是更改旧名称。
- 如果和之前的版本不兼容,创建一个新的路径、新的软件包。
最后一句特别重要,如果跟之前的版本不再兼容,则应更改软件包的导入路径。随着G1.11版本引入的Modules,这条建议被正式化,作为导入兼容性规则:
如果新、旧软件包具有相同的路径,那么新软件包必须向后兼容旧软件包。
当v1或更高版本的软件包更新导致无法向后兼容时,semver需要修改主版本号。
同时遵循导入兼容规则和semver的成果被称为 Semantic Import Versioning(语义导入版本控制)。
主要版本包含在导入路径中,这样可以确保主版本号在由于兼容性中断加一的情况下,导入路径会更改。
有了语义版本导入控制,加入Go Modules的代码必须遵守以下规则:
1. 遵循语义化版本(semver)规则,示例VCS标签:v1.2.3。
2. 如果模块的版本为v2或更高版本,则go.mod文件中模块路径的末尾包含 /vN。
举例:模块 github.com/my/mod/v2,require github.com/my/mod/v2 v2.0.1
同时,导入路径中也需要包含 /vN。
举例: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.0.1)遵循语义化版本规则。
3. 如果模块的版本为v0或者v1,则在模块路径和导入路径中都不要包含主版本号。
通常来说,具有不同导入路径的软件包是不同的软件包。例如: math/rand 和 crypto/rand 是不同的软件包。
如果不同的导入路径是由于导入路径中出现的主要版本不同导致的,也被认为是不同的软件包。
所以 example.com/my/mod/mypkg 和 example.com/my/mod/v2/mypkg 是不同的软件包,他们两个可以在同一个构建中导入,这不仅可以帮助我们解决菱形继承问题,还可以用v2模块来替换v1模块,反过来也ok。
菱形继承问题(钻石依赖问题):
Java有多重继承,如果有两个类具有使用特定方法共享的超类,则在两个子类中都将其覆盖。然后,如果您决定从这两个继承 subClasses ,则如果您想调用该方法,则该语言无法确定您要调用的是哪一个。
同时,钻石依赖问题,讲的也是同一个构建中不同的两个模块都有相同名字的包,比如v1和v2版本都有mypkg这个包,但是因为它们名字相同,所以无法确定使用的是哪一个包。
查看go命令文档的”Module compatibility and semantic versioning”部分,了解有关语义导入版本控制更多详细信息。
查看 https://semver.org 以获取有关语义版本控制的更多信息。
到目前为止,本节的重点是选择使用Modules和导入其他其他模块。
但是将主要版本放入v2+模块的导入路径中,可能会与Go的较早版本或尚未选择使用Modules的代码产生不兼容性。
为了解决这个问题,上述行为和规则有三种重要的过渡性特殊情况或例外。随着越来越多的程序包选择使用Modules,这些过渡异常将不再重要。
三个过渡性例外情况:
1. gopkg.in
已经存在的代码使用 gopkg.in 开头的导入路径(比如 gopkg.in/yaml.v1 和 gopkg.in/yaml.v2),可以继续将这些格式用于其模块路径,甚至在使用Modules后也可以导入路径。
2. 当导入非Modules的v2+包时,会出现 ‘+incompatible’ 情况。
模块可以选择导入尚未使用Modules功能的v2+软件包。
具体这里不讲解了,我不关注这些,因为G1.14版本之后,建议所有模块、软件包都使用Modules功能,所以自己去看原文吧。
3. “最小模块兼容性”当未启用模块模式时
为了解决向后兼容问题,对Go版本1.9.7 +,1.10.3 +和1.11进行了更新,让使用这些发行版构建的代码能够更轻松地正确使用v2 +模块,而无需修改现有代码。
有关发布v2 +模块所需的确切机制,请参阅下面的“发布模块(v2或更高版本)”部分。
使用Modules功能
安装并使用模块:
要使用Modules功能,两个安装选项是:
- 安装了最新的Go 1.11 及以后版本
- 从master分支安装go工具链。
安装后,可以通过以下两种方式之一激活模块支持,
- 在$GOPATH/src目录外的目录中调用go命令,并且在当前目录或其任何父目录中有有效的go.mod文件,并且没有设置GO111MODULE环境变量,或者设置为auto也行。
- 设置了环境变量G111MODULE=on的情况下调用go命令。
定义Module:
给已存在的项目创建一个go.mod文件:
1. 切换目录,到GOPATH路径外的模块源码根目录:
1 |
$ cd <project path outside $GOPATH/src> # e.g., cd ~/projects/hello |
注意,因为Project在GOPATH路径外,所以需要设置GO111MODULE环境变量来激活Modules功能。
另外如果想在GOPATH目录中工作:
1 2 |
$ export GO111MODULE=on # manually active module mode $ cd $GOPATH/src/<project path> # e.g., cd $GOPATH/src/you/hello |
P.S. 上面是原文翻译,GO111MODULE环境变量设置在GO1.13版本后跟之前的不太一样。
根据文档,Go1.11版本的情况下,on 和 off 不管目录位置,都会强制启用、关闭模块功能。auto情况下,GOPATH内部忽略模块功能,GOPATH外部且有go.mod文件则启用模块功能。
在Go1.13版本更新日志:auto情况下,GOPATH内部有go.mod文件,启用模块功能;没有go.mod文件则不会启用模块功能。
2. 初始化模块功能,并写入go.mod文件:
1 |
$ go mod init |
看原文太长,简单来讲就是这个语句会自动匹配已有的配置,自动添加require语句。
go mod init 一般来说都可以自动确定模块路径,但是如果不能自动确定模块路径或者您需要改变该路径,可以将模块路径作为参数提供给go mod init :
1 |
$ go mod init github.com/my/repo |
请注意,如果依赖项模块v2+模块,或者正在初始化v2+模块,则在运行 go mod init 之后,还需要编辑 go.mod文件 和 .go代码来添加 /vN 到导入路径和模块路径。
这些上文 语义导入版本控制部分 已经讲过了。注意,即使 go mod init 自动转换了依赖项信息,也需要自动添加 /vN 路径。
因此,在运行 go mod init 之后,除非成功运行 go build ./… 或类似命令,否则不应该运行 go mod tidy 命令。
3. 编译模块
当从模块根目录执行时, ./… 模式匹配当前模块内的所有软件包。 go build 将根据需要自动添加缺少或者没有转换的依赖项,满足此次build调用的导入需求。
1 |
$ go build ./... |
4. 按照配置测试模块,以确保它可以与所选版本一起使用:
1 |
$ go test ./... |
5. (可选的)允许模块的测试以及所有直接和间接依赖项的测试,以检查不兼容性:
1 |
$ go test all |
在标记版本之前,请参阅下面的“如何准备发布”部分。
有关这些主题的更多信息,可以在golang.org上找到官方模块文档。
升级、降级依赖项:
一般来说,依赖关系升级和降级应该使用 go get 命令完成,它将自动更新 go.mod 文件,或者您可以直接编辑 go.mod 文件。
另外,go命令(比如 go build、 go test 或者甚至 go list)将根据需要自动添加新的依赖关系(更新 go.mod并下载新的依赖包)。
将依赖包升级到最新版本:
1 |
go get example.com/package |
将依赖包及其所有依赖关系升级到最新版本:
1 |
go get -u example.com/package |
查看所有直接和间接依赖项的可用升级。(semver中的 minor 和 patch)
1 |
go list -u -m all |
P.S. 注意这里的-m命令,它在Go1.13版本及以后就不再支持,改为使用 -d 参数。
如果想要仅查看直接依赖项和可用minor和patch补丁升级,运行以下代码:
1 |
go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}' -m all 2> /dev/null |
要将当前模块的所有直接、间接依赖项关系升级到最新版本,可以从模块根目录运行以下两个命令:
命令1:这个命令会将依赖包升级到最新的minor.patch版本,添加 -t 参数可以升级测试依赖项。
1 |
go get -u ./... |
命令2:这个命令会将依赖包升级到最新的patch版本,添加 -t 参数可以升级测试依赖项。
1 |
go get -u=patch ./... |
命令 go get foo 可以将foo软件包升级到最新版本,它等同于 go get foo@latest 命令。换句话说,如果没有指定版本,则默认为 @latest 版本。
在本章节中,latest指的是带有semver标签的最新版本,如果没有semver标签,则是最新的已知提交。预发布标签不会被选为最新版本,除非存储库中没有其他semver标签。
一个常见的错误是认为 go get -u foo 仅获取foo的最新版本,实际上 go get -u foo 或者 go get -u foo@latest 中的 -u 参数 意味着还可以获得foo软件包的所有直接和间接依赖的最新版本。
一个普遍的观点是不带 -u 参数进行foo包的升级,比如 go get foo 或者 go get foo@latest 这两种命令。在一切正常后,再考虑用 go get -u=patch foo、 go get -u=patch、 go get -u foo、 或者 go get -u 命令升级依赖包。
若要将版本升级或者降级到特定的版本, go get 命令允许通过在软件包后面添加@version后缀,或者“模块查询”来覆盖版本选择。例如 go get foo@v1.6.2、 go get foo@e3702bed2、 go get foo@'<v1.6.2’。
使用分支名称是获取最新提交的一种方法,无论它是否具有semver标签。例如: go get foo@master。
通常,无法解析为semver标签的模块查询,将在go.mod文件中被记录为伪版本(pseudo-versions)。
查看go命令文档中的 “go get模块支持” 和 “模块查询” 部分,了解有关此处主题的更多信息。
模块能够使用尚未加入模块的软件包,包括在go.mod中记录的任何可用的semver标签,并使用这些标签进行升级或降级。模块还可以使用尚没有适当semver标签的软件包(在这种情况下,他们将使用go.mod中的伪版本进行记录)
在对依赖项进行升级或降级后,可能想要对构建中的所有软件包(包括直接、间接依赖项)进行测试检查兼容性问题:
1 |
$ go test all |
准备并发布发行版:
发布模块(所有版本)
发布模块(v2或更高版本)
发布发行版
这一块不翻译了…暂时用不到,没有接触过,担心会翻译出错。