去年4月写过一篇《五分钟上手Go Module》。那时项目刚过渡到module模式, 所以文章写的也仅仅是非常基础的Go module的应用技巧。其实Go module的使用技巧远比那篇文章所描述得要多。今天,想通过一个假想的故事,再来讲一讲Go module的使用技巧(这篇文章假设你有一定的Go语言编程基础)。

序章 – 从头开始

张三准备写一个分析射电雷达接收到的数据中是否含有疑似外星人发出的信号的项目。他发现之前曾建过一些Go项目并早已删除。通过检查Go module的缓存,他发现已经下载过一些module:

$ ls -al <GO_HOME>/pkg/mod
total 0
drwxr-xr-x   6 xiali  staff  192 Jan 16 10:50 .
drwxr-xr-x   8 xiali  staff  256 Jan 15 22:47 ..
drwxr-xr-x   5 xiali  staff  160 Jan 16 10:56 cache
drwxr-xr-x  24 xiali  staff  768 Jan 16 14:48 github.com
drwxr-xr-x   3 xiali  staff   96 Jan 16 10:16 golang.org
drwxr-xr-x   9 xiali  staff  288 Jan 16 15:04 gopkg.in

张三觉得,既然项目都已经被删除了,这些module也不再需要了。于是,张三清除了这些缓存:

$ go clean -modcache

缓存被清除了!

接下来,张三开始构这个新项目,取名为“First Contact"(第一次接触),并编写了他的main.go文件:

$ mkdir first_contact
$ cd first_contact
$ touch main.go

随后,他用Go module为项目构建了source tree:

$ go mod init github.cm/zhang3/first_contact

这时,根目录下出现了一个新文件: go.mod,俗称为“mod文件“。

第一章 – 数据清洗

张三需要在main.go中引入工具包github.com/iamharvey/A来对雷达数据进行预处理。于是张三在main.go中手工添加了这个包依赖:

package main
 
import (
    github.com/iamharvey/A
)
...

这时,IDE报告这个包找不到。于是张三通过运行go mod tidy来下载:

$ go mod tidy

不一会儿,A就被下载了下来。张三打开mod文件,发现里面多了2个依赖记录:

$ cat go.mod
module github.com/zhang3/first_contact

go 1.13

require (
    gitHub.com/iamharvey/A v1.0.1
    github.com/iamharvey/X v5.0.1 //indirect
)

唉?怎么会多了一个X?而且后面还写着’indirect’!

第二章 – 神秘的X包

到底这个家伙到底是谁给“介绍”进来的呢?张三立马查了查依赖图谱:

$ go mod graph | grep X
github.com/iamharvey/A@v1.0.1 github.com/iamharvey/X@v5.0.1

通过图谱,张三发现,原来A是中间人的:X是A的一个依赖,所以X是一个间接依赖!这也解释了为啥会有“indirect"字样。

第三章 – 不一致的版本号

张三经常关注A的动向,总感觉下载的版本好像不是最新的,于是他查了查到底A有哪些版本可以被下载:

$go list -m --versions github.com/iamharvey/A
github.com/zhang3/viper v1.0.0 v1.0.1 v1.0.2 v1.0.3 v1.0.4 v1.0.5

通过查询,他发现的确如此,最新的版本号是v1.0.5。于是他打算手工把最新的版本给下载下来:

$go get -u github.com/iamharvey/A

不一会儿,最新版本的A被成功下载了下来。张三再次调出依赖图谱,发现一个新情况:X多了一个v5.0.3版本的引用:

$go mod graph | grep X
github.com/zhang3/first_contact github.com/iamharvey/X@v5.0.3
github.com/iamharvey/A@v1.0.1 github.com/iamharvey/X@v5.0.1

通过查阅资料,张三发现:原来是使用-u导致的:当使用-u时,Go会自动去下载latest的包,所以项目的间接引用也就变成了最新版本。这才导致了mod文件中关于X这个间接依赖有两个版本并存的情况。

第四章 – “奇怪”的sum文件

张三完成了雷达数据预处理这部分代码的编写。接下来,他需要编写分析的部分。他知道github.com/iamharvey/B是一个不错的分析模型工具包,于是打算在main.go中引用它:

package main
 
import (
    github.com/iamharvey/A
    github.com/iamharvey/B
)
...

通过运行go mod tidy,张三完成了对B的下载。打开mod文件,张三发现B也如期地被添加进来:

$ cat go.mod
module github.com/zhang3/first_contact

go 1.13

require (
    gitHub.com/iamharvey/A v1.0.1
    github.com/iamharvey/X v5.0.3 //indirect
    github.com/iamharvey/B v1.1.2
)

这时,张三不经意间打开了“sum文件“。这个不经意的动作让他大吃一惊:怎么会有三个版本的X呢!

$ cat go.sum
...
github.com/iamharvey/X v4.8.7/go.mod h1:lLunBs/Ym6LB5Z9jYTR76Fiu...
github.com/iamharvey/X v5.0.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76Fiu...
github.com/iamharvey/X v5.0.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76Fiu...
...

不应该是两个(v5.0.3和v5.0.1)吗?这个v4.8.7到底是怎么变出来的?

张三又调出了依赖图谱,他发现:这一次,充当中间人角色的竟然是B:

$go mod graph | grep X
github.com/iamharvey/B@v1.1.2 github.com/iamharvey/X@v4.8.7

看来,sum文件并不像mod文件那样,仅包含对项目使用依赖的描述,它包含了比使用的依赖更多的信息!可这是为什么呢?通过一番查找,张三在Go官方的issue中找到了答案:

“The go.sum file still includes the older version (1.0.6) because its transitive requirements may affect the selected versions of other modules. We really only need the checksum for the go.mod file, since that is what declares those transitive requirements, but we end up retaining the checksum for the source code too because go mod tidy is not as precise as it ought to be.” golang.org/issue/33008. [1]

第五章 – 花落谁家

张三有点犯嘀咕:到底哪一个版本会在项目编译时使用呢?张三通过进一步查阅资料了解到,Go使用了Minimal Version Selection (MVS)算法来选择module的版本,MVS倡导的是选择那个“lastest non-greatest"的版本。也就是说,在众多版本中,Go会选择那个最大的版本号,但这个版本号不一定是包可供下载中版本最新的。基于这个知识点,张三推断项目采用的应该是v5.0.3, 因为这个版本是所有版本中最新的。他的推断也得到了验证:

$ go list -m all | grep X
github.com/iamharvey/X v5.0.3

结局 – 大功告成

花了不到两天时间,张三完成了所有代码的编写。最后,他使用go get更新了整个source tree中使用到的依赖:

$go get -d -t -v ./...
(旁白):

    -d      不build, 不install, 只下载source,
    -t      考虑到test中的依赖,
    -v      在下载时查看到更多信息,
    ./...   尽更新source tree中使用到的部分。当然如果要更新整个source tree, 用‘all’。

他满脸喜悦地把代码提交到了代码仓。合上了他的笔记本,塞进他的包里。他麻利地抄上包,轻快地撂在肩上,快步走出公寓。今天是个好日子,是他完成项目日子,也是开启他朝圣之旅的日子。去哪儿朝圣?目的地:黔东南布依苗族自治州平塘县。

Reference

  1. Modules Part 03: Minimal Version Selection

发表评论

电子邮件地址不会被公开。 必填项已用*标注