深色模式
Go module
查看所有命令
输入go help mod
,得到:
Go mod provides access to operations on modules.
Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.
Usage:
go mod <command> [arguments]
The commands are:
download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed
Use "go help mod <command>" for more information about a command.
核心文件:go.mod
以下就是 go.mod 文件的一个最全面的示例:
module my/thing
go 1.12
require other/thing v1.0.2 // 这是注释
require new/thing/v2 v2.3.4 // indirect
require(
new/thing v2.3.4
old/thing v0.0.0-20190603091049-60506f45cf65
)
exclude old/thing v1.2.3
replace bad/thing v1.4.5 => good/thing v1.4.5
很全面,也很复杂。但其实 go.mod 文件在实际项目没有这么复杂,而且一旦该文件存在,就不需要额外的步骤:像 go build、go test,甚至 go list 这样的命令都会根据需要自动添加新的依赖项以满足导入。
但现在我们还是来详细了解 go.mod 文件的组成:
go.mod 文件是面向行的, 当前模块(主模块)通常位于第一行,接下来是根据路径排序的依赖项。
每行包含一个指令,由一个前导动词后跟参数组成。
所有前导动词的作用如下:
module
:定义模块路径。go
:设置预期的语言版本。require
:要求给定版本或更高版本的特定模块。exclude
:排除特定版本模块的使用,不允许的模块版本被视为不可用,并且查询无法返回。replace
:使用不同的模块版本替换原有模块版本。
前导动词还可以按块的方式使用,用括号创建一个块(第 5-8 行),就像在 Go 语言中的导入一样:
import (
"errors"
"fmt"
"log"
)
注释(第 3-4 行)可以使用单行 // 这是注释
注释,但不能使用多行 /* 这是注释 */
注释。而 indirect
注释(第 4 行)标记了该模块不是被当前模块直接导入的,只是被间接导入。
go.mod 文件只存在于在模块的根目录下,子目录中的导入路径会使用模块的导入路径 + 子目录路径的形式。例如:如果创建了一个名叫 world 的子目录,并不需要在子目录中使用 go mod init
命令,Go 命令行工具会自动识别它作为 hello 模块的一部分,所以它的导入路径为 hello/world。
Go 命令行工具会自动处理 go.mod 中指定的模块版本。当源代码中 import
指向的模块不存在于 go.mod 文件中时,Go 命令行工具会自动搜索这个模块,并将最新版本(最后一个 tag 且非预发布的稳定版本)添加到 go.mod 文件中。
如果没有 tag,则使用伪版本(第 7 行),这是一种版本语法,专门用于标记没有 tag 的提交(一些 golang.org/x/ 下的包就是没有 tag 的)。如:v0.0.0-20190603091049-60506f45cf65
。
前面部分为语义化版本号,用于标记版本;中间部分为 UTC 的提交时间,用于比较两个伪版本以其确定先后顺序;后面部分是 commit 哈希的前缀,用于标记该版本位于哪个 commit。
版本管理文件:go.sum
示例如下:
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
每行由模块导入路径、模块的特定版本和预期哈希组成。
在每次缺少模块时,如果缓存中不存在,则需要下载并计算其哈希添加到 go.sum 中;如果缓存中存在,则需要匹配 go.sum 中的已有条目。
这样,构建软件的用户就可以使用哈希验证其构建是否跟你的构建相同(go mod verify
),而无论他们怎样获取依赖项,都可以得到相同的版本。同时也保证了项目依赖不会发生预料之外的恶意修改和其他问题。这也是为什么要将 go.sum 文件加入版本管理(Git)的原因。
再加上 Go Modules 选择的是最小版本选择策略(默认使用构建中涉及的每个模块的最旧允许版本,使得新版本的发布对构建没有影响)就可以实现可重现的构建(在重复构建时产生相同的结果)。
语义化版本
什么是语义化版本?语义化版本是一套由 Gravatars 创办者兼 GitHub 共同创办者 Tom Preston-Werner 所建立的约定。在这套约定下,语义化版本号及其更新方式包含了很多有用的信息。
语义化版本号格式为:X.Y.Z
(主版本号.次版本号.修订号),使用方法如下:
- 进行不向下兼容的修改时,递增主版本号。
- API 保持向下兼容的新增及修改时,递增次版本号。
- 修复问题但不影响 API 时,递增修订号。
举个例子,有一个语义化版本号为:v0.1.2
,则其主版本号为 0,次版本为 1,修订号为 2。而前面的 v
是 version(版本)的首字母,是 Go 语言惯例使用的,标准的语义化版本没有这个约定。
所以在使用 Go 命令行工具或 go.mod 文件时,就可以使用语义化版本号来进行模块查询,具体规则如下:
- 默认值(
@latest
):将匹配最新的可用标签版本或源码库的最新未标签版本。 - 完全指定版本(
@v1.2.3
):将匹配该指定版本。 - 版本前缀(
@v1
或@v1.2
):将匹配具有该前缀的最新可用标签版本。 - 版本比较(
@<v1.2.3
或@>=v1.5.6
):将匹配最接近比较目标的可用标签版本。<
则为小于该版本的最新版本,>
则为大于该版本的最旧版本。当使用类 Unix 系统时,需用引号将字符串包裹起来以防止大于小于号被解释为重定向。如:go get 'github.com/gin-gonic/gin@<v1.2.3'
。 - 指定某个 commit(
@c856192
):将匹配该 commit 时的版本。 - 指定某个分支(
@master
):将匹配该分支版本。
语义化导入版本
如上图所示,为了能让 Go Modules 的使用者能够从旧版本更方便地升级至新版本,Go 语言官方提出了两个重要的规则:
- 导入兼容性规则(import compatibility rule):如果旧包和新包具有相同的导入路径,则新包必须向后兼容旧包。
- 语义化导入版本规则(semantic import versioning rule):每个不同主版本(即不兼容的包
v1
或v2
)使用不同的导入路径,以主版本结尾,且每个主版本中最多一个。如:一个rsc.io/quote
、一个rsc.io/quote/v2
、一个rsc.io/quote/v3
。