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

 

初始化一个新的Modules

 

写代码:

 

编译,运行:

 

go.mod文件已更新,包括依赖项的显式版本,其中v1.5.2是一个semver标签。 P.s. semver (语义版本号 Semantic Versioning)

 

日常工作流程

请注意,在上面的示例中并不需要 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:

 

通过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),相应的磁盘文件结构可能是:

 

在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路径外的模块源码根目录:

注意,因为Project在GOPATH路径外,所以需要设置GO111MODULE环境变量来激活Modules功能。

另外如果想在GOPATH目录中工作:

 

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文件:

看原文太长,简单来讲就是这个语句会自动匹配已有的配置,自动添加require语句。

 

go mod init 一般来说都可以自动确定模块路径,但是如果不能自动确定模块路径或者您需要改变该路径,可以将模块路径作为参数提供给go mod init :

 

请注意,如果依赖项模块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调用的导入需求。

 

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

 

5. (可选的)允许模块的测试以及所有直接和间接依赖项的测试,以检查不兼容性:

 

在标记版本之前,请参阅下面的“如何准备发布”部分。

有关这些主题的更多信息,可以在golang.org上找到官方模块文档。

 

升级、降级依赖项:

一般来说,依赖关系升级和降级应该使用 go get 命令完成,它将自动更新 go.mod 文件,或者您可以直接编辑 go.mod 文件。

另外,go命令(比如 go build、 go test 或者甚至 go list)将根据需要自动添加新的依赖关系(更新 go.mod并下载新的依赖包)。

 

将依赖包升级到最新版本:

 

将依赖包及其所有依赖关系升级到最新版本:

 

查看所有直接和间接依赖项的可用升级。(semver中的 minor 和 patch)

P.S. 注意这里的-m命令,它在Go1.13版本及以后就不再支持,改为使用 -d 参数。

 

如果想要仅查看直接依赖项和可用minor和patch补丁升级,运行以下代码:

 

要将当前模块的所有直接、间接依赖项关系升级到最新版本,可以从模块根目录运行以下两个命令:

命令1:这个命令会将依赖包升级到最新的minor.patch版本,添加 -t 参数可以升级测试依赖项。

命令2:这个命令会将依赖包升级到最新的patch版本,添加 -t 参数可以升级测试依赖项。

 

 

 

命令 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中的伪版本进行记录)

 

在对依赖项进行升级或降级后,可能想要对构建中的所有软件包(包括直接、间接依赖项)进行测试检查兼容性问题:

 

 

准备并发布发行版:

发布模块(所有版本)

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

发布发行版

这一块不翻译了…暂时用不到,没有接触过,担心会翻译出错。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

【Go】Go语言版本化模块 Modules
Tagged on:
0 0 投票数
Article Rating
订阅评论
提醒

0 评论
内联反馈
查看所有评论