版权声明:我已加入“维权骑士”(http://rightknights.com)的版权保护计划,知乎专栏“网路行者”下的所有文章均为我本人(知乎ID:弈心)原创,未经允许不得转载。

如果你喜欢我的文章,请关注我的知乎专栏“网路行者”https://zhuanlan.zhihu.com/c_126268929, 里面有更多像本文一样深度讲解计算机网络技术的优质文章。


学习过Python的读者都知道,在Python中我们使用pip来下载和管理Python的第三方模块(库)。和Python的pip一样,Golang也有自己的模块管理工具(也称为依赖管理工具),叫做Go Modules,不过它的历史有点“说来话长”。

1. Go模块管理的发展历史

Go的模块管理历经了GOPATH、Go vendor、第三方模块管理工具,直到2018年Go Modules推出后一统江湖,Golang的包管理争论才最终尘埃落定。

首先回顾一下Go模块管理的发展历史。

1.1 GOPATH

Go所有的模块是通过go get命令来下载的,在Go版本1.5之前,下载好的模块被保存在$GOPATH/src文件夹下面,在《网络工程师的Golang之路》系列的开篇里讲解如何安装Golang时我们已经提到过GOPATH,GOPATH的绝对路径可以通过go env命令查看,如下图所示。

根据GOPATH的绝对路径进入C:\Users\WANGY0L\go后,可以看到一个叫做src的文件夹,这里就是Go 1.5前存放模块的地方了,如下图所示。

使用go get命令无法进行版本控制,而随着Go生态圈的快速发展,无法进行版本控制会导致项目因为第三方模块版本的更新而编译不过的问题,用户必须手动修改项目中的代码来适应模块的最新版本。更极端的情况是,如果不同项目同时依赖于一个第三方模块开发,还会导致版本冲突的问题。

1.2 Go vendor

在Go 1.5版中引入了 vendor 机制。所谓vendor机制就是在每个项目的根目录下里加入一个vendor目录,里面存放了该项目用到的第三方模块。编译的时候会先去vendor目录查找项目依赖的第三方模块,如果没有找到会再去GOPATH下查找。

这种将原来放在$GOPATH/src的第三方模块放到项目的vendor目录中进行管理的方法为项目独立地管理自己所用到的第三方模块提供了保证,不同项目之间也不会相互影响。使用vendor的另一个好处是将项目代码复制到其他Go编译环境时不需要再去下载第三方模块直接就能编译。

不过vendor也有缺点,它缺乏对外第三方模块的版本管理。在Go中我们使用go get -u命令更新第三方模块,不过这个命令无法指定第三方模块的版本,升级后完成后如果发现新版本有问题无法快速回退,这就引出了Golang社区的众多第三方模块管理工具来解决这个问题。

1.3 第三方模块管理工具

为了解决vendor机制的痛点,Golang社区涌现了很多第三方模块管理工具,比如dep、glide、govendor等等,不过这些第三方模块管理工具并没有存在多长时间。2018年,谷歌的杰出工程师(Distinguished Engineer),Go语言的核心设计开发团队的技术负责人Russ Cox决定针对Go语言的模块管理重新设计,以期实现最小化的模块版本选择算法,这为Go Modules的诞生埋下了伏笔。

1.4 Go Modules

2018年8月24号,谷歌发布了Go 1.11版,也就是从这个版本开始Golang开始使用Go Modules来管理第三方模块。为了向后兼容,这个版本的Go里多了一个叫做GO111MODULE的环境变量,需要将它设为GO111MODULE=on才能使用Go Modules(从1.12版开始GO111MODULE默认为ON),如果GO111MODULE=off,则Go依然使用最传统的方式来管理模块,即将模块存放在之前讲到的$GOPATH/src文件夹下面。

GO111MODULE的值可以通过go env命令查看,如下图所示。

接下来详细介绍Go Modules的相关知识点。


2. Go Modules知识点

在《网络工程师的Golang之路--基础篇》里讲到过,包(package)是Go语言最基本的管理单位,它是多个Go源码的集合。在Go Modules诞生之前,Go语言的各种模块都是以包来组织的,一个Go语言的项目包含一个根目录和一个(或多个)子目录,项目下的每个子目录代表一个包,所以换句话说一个项目包含一个包或者多个包。

而Go Modules的本质则是包的集合,对包实行统一的版本控制和发布。Go Modules会在项目的根目录下生成go.mod和go.sum两个文件。在《网络工程师的Golang之路--基础篇》里讲到过,如果根目录下缺少go.mod和go.sum文件且GO111MODULE=on,那么在编译go程序时会报错:

Build Error: go build -o c:\Users\WANGY0L\Desktop\Golang\__debug_bin.exe -gcflags all=-N -l c:\Users\WANGY0L\Desktop\Golang go: go.mod file not found in current directory or any parent directory; see 'go help modules' (exit status 1)

2.1 go.mod

Go.mod文件位于项目的根目录下,用来标记一个模块和它的依赖库以及依赖库的版本。

首先我们在操作系统桌面上创建一个叫做GOLANG_TEST的项目文件夹。

在VSCode里面依次点开File->Open Folder将该项目文件夹打开,此时该项目的根目录下为空,没有任何子目录(包),如下图所示。

在VSCode里点开Terminal->New Terminal,在终端里使用go mod init [module name]命令来生成go.mod文件,这里我将模块名设为test,如下图所示。

此时项目的根目录下多出了go.mod文件,go.mod文件里包含模块名和golang的版本(go 1.20),注意这个版本并不是指你当前使用的Go版本,而是指要运行模块所需要的Go的最低版本。

使用Go Modules后,模块的存放位置已经从$GOPATH/src变为了$GOPATH/pkg/mod,如下图所示。

在根目录下新建一个叫做hello.go的文件,在里面写入下列代码:

这里我们导入了fmt和http://rsc.io/quote两个模块,fmt我们都知道是Golang的内置模块,而http://rsc.io/quote是第三方模块,由前面提到的Russ Cox发布(Russ Cox在github上的用户名就是rsc),用来记录他的一些语录,实则是供Go初学者测试Go Modules使用(因为初学者不知道有哪些知名的第三方模块可用)。

因为此时$GOPATH/pkg/mod下并不存在"http://rsc.io/quote"这个第三方模块,因此运行hello.go时系统会提示该模块不存在,需要通过go get http://rsc.io/quote来安装,如下图所示。

在终端输入命令go get http://rsc.io/quote后安装http://rsc.io模块成功,打开$GOPATH/pkg/mod可验证,如下图所示。

除了http://rsc.io/quote之外,go get也同时安装了http://golang.org/x/text和http://rsc.io/sampler两个模块。此时打开go.mod文件,会看到里面多出了一行require段。require段用来列出项目所需要的依赖库以及它们的版本号。

注意这里http://golang.org/x/text的版本号和另外两个有些不同,它使用的是伪版本号v0.0.0-20170915032832-14c0d48ead0c,如下图所示。

这是Go Modules为该模块生成的版本号,实际上这个模块并没有发布这个版本。之所以使用伪版本号是因为Go Modules需要为每个模块指定一个确定的版本,而这个模块本身没有发布版本,因此Go Modules替它生成了这个伪版本号。

另外我们还可以看到每个require段下的模块的最后都加上了// indirect这个注释,indirect的意思顾名思义是指项目间接地使用了这些模块,更准确地说是当前我们的项目GOLANG_TEST依赖模块http://rsc.io/quote,但是http://rsc.io/quote本身并没有被添加进go.mod,并且http://rsc.io/quote自身还依赖另外两个模块(http://rsc.io/sampler和http://golang.org/x/text),因此这三个模块都被打上了// indirect这个注释。

如果我们继续输入命令go mod tidy,则会将http://rsc.io/quote正式加入go.mod里,此时http://rsc.io/quote后面的// indirect被去掉了,而它所依赖的http://rsc.io/sampler和http://golang.org/x/text则因为是间接地被项目使用,所以还是保留了// indirect注释,如下图所示。

2.2 go.sum

在终端输入命令go get http://rsc.io/quote后,项目根目录下除了go.mod外也会生成一个go.sum文件,如下图所示。

go.sum的作用是记录所有项目依赖的模块的安全校验信息,以防止下载的模块被恶意篡改。

go.sum里面的每个模块被写为两个条目,第一个条目的版本号后面不带/go.mod,第二个条目的版本号后面带/go.mod。带/go.mod的条目表示它包含了单独的go.mod文件的校验和,作用是为了确保我们在加载或改变依赖模块关系时的一致性。而没有/go.mod的条目表示它包含该模块的全部源代码的校验和,包括其中所有包的.go源文件。

h1表示校验和的的算法是第一版的哈希算法(SHA256)。


此时http://rsc.io/quote以及它的依赖模块已经被我们安装好了,最后回到terminal下输入go run hello.go,此时程序成功运行,如下图所示。