Go Modules 终极入门

Go modules 是 Go 语言中正式官宣的项目依赖解决方案,Go modules(前身为vgo)于 Go1.11 正式发布,在 Go1.14 已经准备好,而且能够用在生产上(ready for production)了,Go官方也鼓励全部用户从其余依赖项管理工具迁移到 Go modules。html

而 Go1.14,在近期也终于正式发布,Go 官方亲自 “喊” 你来用:git

image

所以在今天这篇文章中,我将给你们带来 Go modules 的 “终极入门”,欢迎你们一块儿共同探讨。github

Go modules 是 Go 语言中正式官宣的项目依赖管理工具,Go modules(前身为vgo)于 Go1.11 正式发布,在 Go1.14 已经准备好,而且能够用在生产上(ready for production)了,鼓励全部用户从其余依赖项管理工具迁移到 Go modules。golang

什么是Go Modules

Go modules 是 Go 语言的依赖解决方案,发布于 Go1.11,成长于 Go1.12,丰富于 Go1.13,正式于 Go1.14 推荐在生产上使用。算法

Go moudles 目前集成在 Go 的工具链中,只要安装了 Go,天然而然也就可使用 Go moudles 了,而 Go modules 的出现也解决了在 Go1.11 前的几个常见争议问题:缓存

  1. Go 语言长久以来的依赖管理问题。
  2. “淘汰”现有的 GOPATH 的使用模式。
  3. 统一社区中的其它的依赖管理工具(提供迁移功能)。

GOPATH的那些点点滴滴

咱们有提到 Go modules 的解决的问题之一就是“淘汰”掉 GOPATH,可是 GOPATH 又是什么呢,为何在 Go1.11 前就使用 GOPATH,而 Go1.11 后就开始逐步建议使用 Go modules,再也不推荐 GOPATH 的模式了呢?安全

GOPATH是什么

咱们先看看第一个问题,GOPATH 是什么,咱们能够输入以下命令查看:bash

$ go env
GOPATH="/Users/eddycjy/go"
...
复制代码

咱们输入go env命令行后能够查看到 GOPATH 变量的结果,咱们进入到该目录下进行查看,以下:app

go
├── bin
├── pkg
└── src
    ├── github.com
    ├── golang.org
    ├── google.golang.org
    ├── gopkg.in
    ....
复制代码

GOPATH目录下一共包含了三个子目录,分别是:ide

  • bin:存储所编译生成的二进制文件。
  • pkg:存储预编译的目标文件,以加快程序的后续编译速度。
  • src:存储全部.go文件或源代码。在编写 Go 应用程序,程序包和库时,通常会以$GOPATH/src/github.com/foo/bar的路径进行存放。

所以在使用 GOPATH 模式下,咱们须要将应用代码存放在固定的$GOPATH/src目录下,而且若是执行go get来拉取外部依赖会自动下载并安装到$GOPATH目录下。

为何弃用GOPATH模式

在 GOPATH 的 $GOPATH/src 下进行 .go 文件或源代码的存储,咱们能够称其为 GOPATH 的模式,这个模式,看起来好像没有什么问题,那么为何咱们要弃用呢,参见以下缘由:

  • GOPATH 模式下没有版本控制的概念,具备致命的缺陷,至少会形成如下问题:
    • 在执行go get的时候,你没法传达任何的版本信息的指望,也就是说你也没法知道本身当前更新的是哪个版本,也没法经过指定来拉取本身所指望的具体版本。
    • 在运行Go应用程序的时候,你没法保证其它人与你所指望依赖的第三方库是相同的版本,也就是说在项目依赖库的管理上,你没法保证全部人的依赖版本都一致。
    • 你没办法处理 v一、v二、v3 等等不一样版本的引用问题,由于 GOPATH 模式下的导入路径都是同样的,都是github.com/foo/bar
  • Go 语言官方从 Go1.11 起开始推动 Go modules(前身vgo),Go1.13 起再也不推荐使用 GOPATH 的使用模式,Go modules 也渐趋稳定,所以新项目也没有必要继续使用GOPATH模式。

在GOPATH模式下的产物

Go1 在 2012 年 03 月 28 日发布,而 Go1.11 是在 2018 年 08 月 25 日才正式发布(数据来源:Github Tag),在这个空档的时间内,并无 Go modules 这一个东西,最先期可能还好说,由于刚发布,用的人很少,因此没有明显暴露,可是后期 Go 语言使用的人愈来愈多了,那怎么办?

这时候社区中逐渐的涌现出了大量的依赖解决方案,百花齐放,让人难以挑选,其中包括咱们所熟知的 vendor 目录的模式,以及曾经一度被认为是“官宣”的 dep 的这类依赖管理工具。

但为何 dep 没有正在成为官宣呢,实际上是由于随着 Russ Cox 与 Go 团队中的其余成员不断深刻地讨论,发现dep 的一些细节彷佛愈来愈不适合 Go,所以官方采起了另起 proposal 的方式来推动,其方案的结果一开始先是释出 vgo(Go modules的前身,知道便可,不须要深刻了解),最终演变为咱们如今所见到的 Go modules,也在 Go1.11 正式进入了 Go 的工具链。

所以与其说是 “在GOPATH模式下的产物”,不如说是历史为当前提供了重要的教训,所以出现了 Go modules。

Go Modules基本使用

在初步了解了 Go modules 的前世此生后,咱们正式进入到 Go modules 的使用,首先咱们将从头开始建立一个 Go modules 的项目(原则上所建立的目录应该不要放在 GOPATH 之中)。

所提供的命令

在 Go modules 中,咱们可以使用以下命令进行操做:

命令 做用
go mod init 生成 go.mod 文件
go mod download 下载 go.mod 文件中指明的全部依赖
go mod tidy 整理现有的依赖
go mod graph 查看现有的依赖结构
go mod edit 编辑 go.mod 文件
go mod vendor 导出项目全部的依赖到vendor目录
go mod verify 校验一个模块是否被篡改过
go mod why 查看为何须要依赖某模块

所提供的环境变量

在 Go modules 中有以下经常使用环境变量,咱们能够经过 go env 命令来进行查看,以下:

$ go env
GO111MODULE="auto"
GOPROXY="https://proxy.golang.org,direct"
GONOPROXY=""
GOSUMDB="sum.golang.org"
GONOSUMDB=""
GOPRIVATE=""
...
复制代码

GO111MODULE

Go语言提供了 GO111MODULE 这个环境变量来做为 Go modules 的开关,其容许设置如下参数:

  • auto:只要项目包含了 go.mod 文件的话启用 Go modules,目前在 Go1.11 至 Go1.14 中仍然是默认值。
  • on:启用 Go modules,推荐设置,将会是将来版本中的默认值。
  • off:禁用 Go modules,不推荐设置。
GO111MODULE的小历史

你可能会留意到 GO111MODULE 这个名字比较“奇特”,实际上在 Go 语言中常常会有这类阶段性的变量, GO111MODULE 这个命名表明着Go语言在 1.11 版本添加的,针对 Module 的变量。

像是在 Go1.5 版本的时候,也发布了一个系统环境变量 GO15VENDOREXPERIMENT,做用是用于开启 vendor 目录的支持,当时其默认值也不是开启,仅仅做为 experimental。其随后在 Go1.6 版本时也将默认值改成了开启,而且最后做为了official,GO15VENDOREXPERIMENT 系统变量就退出了历史舞台。

而将来 GO111MODULE 这一个系统环境变量也会面临这个问题,也会先调整为默认值为 on(曾经在Go1.13想一想改成 on,而且已经合并了 PR,但最后由于种种缘由改回了 auto),而后再把 GO111MODULE 的支持给去掉,咱们猜想应该会在 Go2 将 GO111MODULE 给去掉,由于若是直接去掉 GO111MODULE 的支持,会存在兼容性问题。

GOPROXY

这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其做用是用于使 Go 在后续拉取模块版本时可以脱离传统的 VCS 方式,直接经过镜像站点来快速拉取。

GOPROXY 的默认值是:https://proxy.golang.org,direct,这有一个很严重的问题,就是 proxy.golang.org 在国内是没法访问的,所以这会直接卡住你的第一步,因此你必须在开启 Go modules 的时,同时设置国内的 Go 模块代理,执行以下命令:

$ go env -w GOPROXY=https://goproxy.cn,direct
复制代码

GOPROXY的值是一个以英文逗号 “,” 分割的 Go 模块代理列表,容许设置多个模块代理,假设你不想使用,也能够将其设置为 “off” ,这将会禁止 Go 在后续操做中使用任何 Go 模块代理。

direct是什么

而在刚刚设置的值中,咱们能够发现值列表中有 “direct” 标识,它又有什么做用呢?

实际上 “direct” 是一个特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取(好比 GitHub 等),场景以下:当值列表中上一个 Go 模块代理返回 404 或 410 错误时,Go 自动尝试列表中的下一个,碰见 “direct” 时回源,也就是回到源地址去抓取,而碰见 EOF 时终止并抛出相似 “invalid version: unknown revision...” 的错误。

GOSUMDB

它的值是一个 Go checksum database,用于在拉取模块版本时(不管是从源站拉取仍是经过 Go module proxy 拉取)保证拉取到的模块版本数据未通过篡改,若发现不一致,也就是可能存在篡改,将会当即停止。

GOSUMDB的默认值为:sum.golang.org,在国内也是没法访问的,可是 GOSUMDB 能够被 Go 模块代理所代理(详见:Proxying a Checksum Database)。

所以咱们能够经过设置 GOPROXY 来解决,而先前咱们所设置的模块代理 goproxy.cn 就能支持代理 sum.golang.org,因此这一个问题在设置 GOPROXY 后,你能够不须要过分关心。

另外若对 GOSUMDB 的值有自定义需求,其支持以下格式:

  • 格式 1:<SUMDB_NAME>+<PUBLIC_KEY>
  • 格式 2:<SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>

也能够将其设置为“off”,也就是禁止 Go 在后续操做中校验模块版本。

GONOPROXY/GONOSUMDB/GOPRIVATE

这三个环境变量都是用在当前项目依赖了私有模块,例如像是你公司的私有 git 仓库,又或是 github 中的私有库,都是属于私有模块,都是要进行设置的,不然会拉取失败。

更细致来说,就是依赖了由 GOPROXY 指定的 Go 模块代理或由 GOSUMDB 指定 Go checksum database 都没法访问到的模块时的场景。

而通常建议直接设置 GOPRIVATE,它的值将做为 GONOPROXY 和 GONOSUMDB 的默认值,因此建议的最佳姿式是直接使用 GOPRIVATE。

而且它们的值都是一个以英文逗号 “,” 分割的模块路径前缀,也就是能够设置多个,例如:

$ go env -w GOPRIVATE="git.example.com,github.com/eddycjy/mquote"
复制代码

设置后,前缀为 git.xxx.com 和 github.com/eddycjy/mquote 的模块都会被认为是私有模块。

若是不想每次都从新设置,咱们也能够利用通配符,例如:

$ go env -w GOPRIVATE="*.example.com"
复制代码

这样子设置的话,全部模块路径为 example.com 的子域名(例如:git.example.com)都将不通过 Go module proxy 和 Go checksum database,须要注意的是不包括 example.com 自己。

开启Go Modules

目前Go modules并非默认开启,所以Go语言提供了GO111MODULE这个环境变量来做为Go modules的开关,其容许设置如下参数:

  • auto:只要项目包含了go.mod文件的话启用 Go modules,目前在Go1.11至Go1.14中仍然是默认值。
  • on:启用 Go modules,推荐设置,将会是将来版本中的默认值。
  • off:禁用 Go modules,不推荐设置。

若是你不肯定你当前的值是什么,能够执行go env命令,查看结果:

$ go env
GO111MODULE="off"
...
复制代码

若是须要对GO111MODULE的值进行变动,推荐经过go env命令进行设置:

$ go env -w GO111MODULE=on
复制代码

可是须要注意的是若是对应的系统环境变量有值了(进行过设置),go env是不支持覆盖写入的,不然会出现以下报错信息:warning: go env -w GO111MODULE=... does not override conflicting OS environment variable

又或是能够经过直接设置系统环境变量(写入对应的.bash_profile文件亦可)来实现这个目的:

$ export GO111MODULE=on
复制代码

初始化项目

在完成 Go modules 的开启后,咱们须要建立一个示例项目来进行演示,执行以下命令:

$ mkdir -p $HOME/eddycjy/module-repo 
$ cd $HOME/eddycjy/module-repo
复制代码

而后进行Go modules的初始化,以下:

$ go mod init github.com/eddycjy/module-repo
go: creating new go.mod: module github.com/eddycjy/module-repo
复制代码

在执行 go mod init 命令时,咱们指定了模块导入路径为 github.com/eddycjy/module-repo。接下来咱们在该项目根目录下建立 main.go 文件,以下:

package main

import (
    "fmt"
    "github.com/eddycjy/mquote"
)

func main() {
	fmt.Println(mquote.GetHello())
}
复制代码

而后在项目根目录执行 go get github.com/eddycjy/mquote 命令,以下:

$ go get github.com/eddycjy/mquote 
go: finding github.com/eddycjy/mquote latest
go: downloading github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
go: extracting github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
复制代码

查看go.mod 文件

在初始化项目时,会生成一个 go.mod 文件,是启用了 Go modules 项目所必须的最重要的标识,同时也是GO111MODULE 值为 auto 时的识别标识,它描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头。

在咱们刚刚进行了初始化和简单拉取后,咱们再次查看go.mod文件,基本内容以下:

module github.com/eddycjy/module-repo

go 1.13

require (
	github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
)
复制代码

为了更进一步的讲解,咱们模拟引用以下:

module github.com/eddycjy/module-repo

go 1.13

require (
    example.com/apple v0.1.2
    example.com/banana v1.2.3
    example.com/banana/v2 v2.3.4
    example.com/pear // indirect
    example.com/strawberry // incompatible
)

exclude example.com/banana v1.2.4
replace example.com/apple v0.1.2 => example.com/fried v0.1.0 
replace example.com/banana => example.com/fish
复制代码
  • module:用于定义当前项目的模块路径。
  • go:用于标识当前模块的 Go 语言版本,值为初始化模块时的版本,目前来看还只是个标识做用。
  • require:用于设置一个特定的模块版本。
  • exclude:用于从使用中排除一个特定的模块版本。
  • replace:用于将一个模块版本替换为另一个模块版本。

另外你会发现 example.com/pear 的后面会有一个 indirect 标识,indirect 标识表示该模块为间接依赖,也就是在当前应用程序中的 import 语句中,并无发现这个模块的明确引用,有多是你先手动 go get 拉取下来的,也有多是你所依赖的模块所依赖的,状况有好几种。

查看go.sum文件

在第一次拉取模块依赖后,会发现多出了一个 go.sum 文件,其详细罗列了当前项目直接或间接依赖的全部模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在从此的操做中保证项目所依赖的那些模块版本不会被篡改。

github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=
github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=
github.com/eddycjy/mquote/module/tour v0.0.1 h1:cc+pgV0LnR8Fhou0zNHughT7IbSnLvfUZ+X3fvshrv8=
github.com/eddycjy/mquote/module/tour v0.0.1/go.mod h1:8uL1FOiQJZ4/1hzqQ5mv4Sm7nJcwYu41F3nZmkiWx5I=
...
复制代码

咱们能够看到一个模块路径可能有以下两种:

github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=
github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=
复制代码

h1 hash 是 Go modules 将目标模块版本的 zip 文件开包后,针对全部包内文件依次进行 hash,而后再把它们的 hash 结果按照固定格式和算法组成总的 hash 值。

而 h1 hash 和 go.mod hash 二者,要不就是同时存在,要不就是只存在 go.mod hash。那什么状况下会不存在 h1 hash 呢,就是当 Go 认为确定用不到某个模块版本的时候就会省略它的 h1 hash,就会出现不存在 h1 hash,只存在 go.mod hash 的状况。

查看全局缓存

咱们刚刚成功的将 github.com/eddycjy/mquote 模块拉取了下来,其拉取的结果缓存在 $GOPATH/pkg/mod$GOPATH/pkg/sumdb 目录下,而在mod目录下会以 github.com/foo/bar 的格式进行存放,以下:

mod
├── cache
├── github.com
├── golang.org
├── google.golang.org
├── gopkg.in
...
复制代码

须要注意的是同一个模块版本的数据只缓存一份,全部其它模块共享使用。若是你但愿清理全部已缓存的模块版本数据,能够执行 go clean -modcache 命令。

Go Modules下的go get行为

在拉取项目依赖时,你会发现拉取的过程总共分为了三大步,分别是 finding(发现)、downloading(下载)以及 extracting(提取), 而且在拉取信息上一共分为了三段内容:

image

须要注意的是,所拉取版本的 commit 时间是以UTC时区为准,而并不是本地时区,同时咱们会发现咱们 go get 命令所拉取到的版本是 v0.0.0,这是由于咱们是直接执行 go get -u 获取的,并无指定任何的版本信息,由 Go modules 自行按照内部规则进行选择。

go get的拉取行为

刚刚咱们用 go get 命令拉取了新的依赖,那么 go get 又提供了哪些功能呢,经常使用的拉取命令以下:

命令 做用
go get 拉取依赖,会进行指定性拉取(更新),并不会更新所依赖的其它模块。
go get -u 更新现有的依赖,会强制更新它所依赖的其它所有模块,不包括自身。
go get -u -t ./... 更新全部直接依赖和间接依赖的模块版本,包括单元测试中用到的。

那么我想选择具体版本应当如何执行呢,以下:

命令 做用
go get golang.org/x/text@latest 拉取最新的版本,若存在tag,则优先使用。
go get golang.org/x/text@master 拉取 master 分支的最新 commit。
go get golang.org/x/text@v0.3.2 拉取 tag 为 v0.3.2 的 commit。
go get golang.org/x/text@342b2e 拉取 hash 为 342b231 的 commit,最终会被转换为 v0.3.2。

go get的版本选择

咱们回顾一下咱们拉取的 go get github.com/eddycjy/mquote,其结果是 v0.0.0-20200220041913-e066a990ce6f,对照着上面所提到的 go get 行为来看,你可能还会有一些疑惑,那就是在 go get 没有指定任何版本的状况下,它的版本选择规则是怎么样的,也就是为何 go get 拉取的是 v0.0.0,它何时会拉取正常带版本号的 tags 呢。实际上这须要区分两种状况,以下:

  1. 所拉取的模块有发布 tags:
    • 若是只有单个模块,那么就取主版本号最大的那个tag。
    • 若是有多个模块,则推算相应的模块路径,取主版本号最大的那个tag(子模块的tag的模块路径会有前缀要求)
  2. 所拉取的模块没有发布过 tags:
    • 默认取主分支最新一次 commit 的 commithash。

没有发布过 tags

那么为何会拉取的是 v0.0.0 呢,是由于 github.com/eddycjy/mquote 没有发布任何的tag,以下:

image

所以它默认取的是主分支最新一次 commit 的 commit 时间和 commithash,也就是 20200220041913-e066a990ce6f,属于第二种状况。

有发布 tags

在项目有发布 tags 的状况下,还存在着多种模式,也就是只有单个模块和多个模块,咱们统一以多个模块来进行展现,由于多个模块的状况下就已经包含了单个模块的使用了,以下图:

image

在这个项目中,咱们一共打了两个tag,分别是:v0.0.1 和 module/tour/v0.0.1。这时候你可能会奇怪,为何要打 module/tour/v0.0.1 这么“奇怪”的tag,这有什么用意吗?

实际上是 Go modules 在同一个项目下多个模块的tag表现方式,其主要目录结构为:

mquote
├── go.mod
├── module
│   └── tour
│       ├── go.mod
│       └── tour.go
└── quote.go
复制代码

能够看到在 mquote 这个项目的根目录有一个 go.mod 文件,而在 module/tour 目录下也有一个 go.mod 文件,其模块导入和版本信息的对应关系以下:

tag 模块导入路径 含义
v0.0.1 github.com/eddycjy/mquote mquote 项目的v 0.0.1 版本
module/tour/v0.01 github.com/eddycjy/mquote/module/tour mquote 项目下的子模块 module/tour 的 v0.0.1 版本

导入主模块和子模块

结合上述内容,拉取主模块的话,仍是照旧执行以下命令:

$ go get github.com/eddycjy/mquote@v0.0.1
go: finding github.com/eddycjy/mquote v0.0.1
go: downloading github.com/eddycjy/mquote v0.0.1
go: extracting github.com/eddycjy/mquote v0.0.1
复制代码

若是是想拉取子模块,执行以下命令:

$ go get github.com/eddycjy/mquote/module/tour@v0.0.1
go: finding github.com/eddycjy/mquote/module v0.0.1
go: finding github.com/eddycjy/mquote/module/tour v0.0.1
go: downloading github.com/eddycjy/mquote/module/tour v0.0.1
go: extracting github.com/eddycjy/mquote/module/tour v0.0.1
复制代码

咱们将主模块和子模块的拉取进行对比,你会发现子模块的拉取会多出一步,它会先发现 github.com/eddycjy/mquote/module,再继续推算,最终拉取到 module/tour

Go Modules的导入路径说明

不一样版本的导入路径

在前面的模块拉取和引用中,你会发现咱们的模块导入路径就是 github.com/eddycjy/mquotegithub.com/eddycjy/mquote/module/tour,彷佛并无什么特殊的。

其实否则,实际上 Go modules 在主版本号为 v0 和 v1 的状况下省略了版本号,而在主版本号为v2及以上则须要明确指定出主版本号,不然会出现冲突,其tag与模块导入路径的大体对应关系以下:

tag 模块导入路径
v0.0.0 github.com/eddycjy/mquote
v1.0.0 github.com/eddycjy/mquote
v2.0.0 github.com/eddycjy/mquote/v2
v3.0.0 github.com/eddycjy/mquote/v3

简单来说,就是主版本号为 v0 和 v1 时,不须要在模块导入路径包含主版本的信息,而在 v1 版本之后,也就是 v2 起,必需要在模块的导入路径末尾加上主版本号,引用时就须要调整为以下格式:

import (
    "github.com/eddycjy/mquote/v2/example"
)
复制代码

另外忽略主版本号 v0 和 v1 是强制性的(不是可选项),所以每一个软件包只有一个明确且规范的导入路径。

为何忽略v0和v1的主版本号

  1. 导入路径中忽略 v1 版本的缘由是:考虑到许多开发人员建立一旦到达 v1 版本便永不改变的软件包,这是官方所鼓励的,不认为全部这些开发人员在无心发布 v2 版时都应被迫拥有明确的 v1 版本尾缀,这将致使 v1 版本变成“噪音”且无心义。

  2. 导入路径中忽略了 v0 版本的缘由是:根据语义化版本规范,v0的这些版本彻底没有兼容性保证。须要一个显式的 v0 版本的标识对确保兼容性没有多大帮助。

Go Modules的语义化版本控制

咱们不断地在 Go Modules 的使用中提到版本号,其实质上被称为“语义化版本”,假设咱们的版本号是 v1.2.3,以下:

image

其版本格式为“主版本号.次版本号.修订号”,版本号的递增规则以下:

  1. 主版本号:当你作了不兼容的 API 修改。
  2. 次版本号:当你作了向下兼容的功能性新增。
  3. 修订号:当你作了向下兼容的问题修正。

假设你是先行版本号或特殊状况,能够将版本信息追加到“主版本号.次版本号.修订号”的后面,做为延伸,以下:

image

至此咱们介绍了 Go modules 所支持的两类版本号方式,在咱们发布新版本打 tag 的时候,须要注意遵循,不然不遵循语义化版本规则的版本号都是没法进行拉取的。

Go Modules的最小版本选择

如今咱们已经有一个模块,也有发布的 tag,可是一个模块每每依赖着许多其它许许多多的模块,而且不一样的模块在依赖时颇有可能会出现依赖同一个模块的不一样版本,以下图(来自Russ Cox):

image

在上述依赖中,模块 A 依赖了模块 B 和模块 C,而模块 B 依赖了模块 D,模块 C 依赖了模块 D 和 F,模块 D 又依赖了模块 E,并且同模块的不一样版本还依赖了对应模块的不一样版本。那么这个时候 Go modules 怎么选择版本,选择的是哪个版本呢?

咱们根据 proposal 可得知,Go modules 会把每一个模块的依赖版本清单都整理出来,最终获得一个构建清单,以下图(来自Russ Cox):

image

咱们看到 rough list 和 final list,二者的区别在于重复引用的模块 D(v1.三、v1.4),其最终清单选用了模块 D 的 v1.4 版本,主要缘由:

  1. 语义化版本的控制:由于模块 D 的 v1.3 和 v1.4 版本变动,都属于次版本号的变动,而在语义化版本的约束下,v1.4 必须是要向下兼容 v1.3 版本,所以认为不存在破坏性变动,也就是兼容的。

  2. 模块导入路径的规范:主版本号不一样,模块的导入路径不同,所以若出现不兼容的状况,其主版本号会改变,模块的导入路径天然也就改变了,所以不会与第一点的基础相冲突。

go.sum文件要不要提交

理论上 go.mod 和 go.sum 文件都应该提交到你的 Git 仓库中去。

假设咱们不上传 go.sum 文件,就会形成每一个人执行 Go modules 相关命令,又会生成新的一份 go.sum,也就是会从新到上游拉取,再拉取时有可能就是被篡改过的了,会有很大的安全隐患,失去了与基准版本(第一个所提交的人,所指望的版本)的校验内容,所以 go.sum文件是须要提交。

总结

至此咱们介绍了 Go modules 的前世此生、基本使用和在 Go modules 模式下 go get 命令的行为转换,同时咱们对常见的多版本导入路径、语义化版本控制以及多模块的最小版本选择规则进行了大体的介绍。

Go modules 的成长和发展经历了必定的过程,若是你是刚接触的读者,直接基于 Go modules 的项目开始便可,若是既有老项目,那么是时候考虑切换过来了,Go1.14起已经准备就绪,并推荐你使用。

个人公众号

image

参考

相关文章
相关标签/搜索