Go Modules的前世今生

发布时间 2023-07-07 15:37:07作者: 易先讯

以前,Go语言的依赖包的管理一直被诟病,Go官方从最初的GOPATH到GO VENDOR,再到目前的GO Modules,一直在努力提供更方便的包管理方案。

目前最主流的方案是官方推荐的GO Modules。先介绍一下以前的GOPATH和GO VENDOR,了解它们的发展历程,才能知道GO Modules多么来之不易。

1、      古老的GOPATH

配置环境的时候对其应该都不陌生,可以将其理解为“工作目录”。

工作目录下通常有如下目录:

将自己或者别人的包全部放在$GOPATH/src目录下来管理的方式,就称为GOPATH模式。

在此模式下,使用go install,生成的可执行文件会放在$GOPATH/bin下。

如果安装的是一个库,则会生成.a 文件到 $GOPATH/pkg 下对应的平台目录中。

 

使用GOPATH去开发程序有个最严重的问题,就是版本管理问题,因为GOPATH根本没有版本的概念。

 

会遇到的问题:

u  无法在你的项目中,使用指定版本的包,因为不同版本的包的导入方法都一样;

u  其他人运行你的开发的程序时,无法保证他下载的包版本是你所期望的版本,当对方使用了其他版本,有可能导致程序无法正常运行

u  在本地,一个包只能保留一个版本,意味着你在本地开发的所有项目,都得用同一个版本的包,这几乎是不可能的

 

2、      Go vendor模式的过渡

为了解决GOPATH模式下不同项目下无法使用多个版本库的问题,Golang从v1.5开始支持vendor。

以前使用GOPATH的时候,所有项目都共享一个GOPATH,需要依赖的时候,都来这里找,在GOPATH模式下只能有一个版本的三方库。

Vendor模式的思路就是为每个项目都创建一个vendor目录,每个项目的依赖都下载到自己的vendor目录下,项目之间的依赖包互不影响。编译时,默认开启vendor特性,相较于GOPATH,提升vendor目录的依赖包搜索路径的优先级。

搜索包的优先级顺序:

(1)    当前包下的vendor目录‘

(2)    向上级目录查找,直到找到src下的vendor目录;

(3)    在GOROOT目录下查找;

(4)    在GOPATH下面查找依赖包

 

此方案的不足:

(1)    如果多个项目用到了同一个包的同一个版本,这个包会存在于该机器上的不同目录下,不仅对磁盘空间是一种浪费,而且没法对第三方包进行集中式的管理(分散在各个角落)。

(2)    如果要分享开源你的项目,你需要将你的所有的依赖包悉数上传,别人使用的时候,除了你的项目源码外,还有所有的依赖包全部下载下来,才能保证别人使用的时候,不会因为版本问题导致项目不能如你预期那样正常运行。

 

3、      Go mod的横空出世

go modules 在 v1.11 版本正式推出,在最新发布的 v1.14 版本中,官方正式发话,称其已经足够成熟,可以应用于生产上。

从 v1.11 开始,go env多了个环境变量: GO111MODULE,这里的 111,其实就是 v1.11 的象征标志, go 里好像很喜欢这样的命名方式,比如当初 vendor 出现的时候,也多了个GO15VENDOREXPERIMENT环境变量,其中 15,表示的vendor 是在 v1.5 时才诞生的。

GO111MODULE 是一个开关,通过它可以开启或关闭 go mod 模式。

它有三个可选值:off、on、auto,默认值是auto。

(1)GO111MODULE=off禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。

(2)GO111MODULE=on启用模块支持,编译时会忽略GOPATH和vendor文件夹,只根据 go.mod下载依赖。

(3)GO111MODULE=auto,当项目在$GOPATH/src外且项目根目录有go.mod文件时,自动开启模块支持。

 

开启go mod就将GO111MODULE设置为on。比如可以:

go env –w GO111MODULE=”on”

 

4、      go mod依赖的管理

初始化时,会多出go.mod文件;执行安装,会下载依赖包并且完善go.mod文件生成go.sum文件。

go mod会将下载的依赖包都安装到$GOPATH/pkg下,生成的可执行文件在$GOPATH/bin下。

          

    go.mod和go.sum是go modules的核心所在。

²   go.mod

go.mod的内容比较容易理解,第一行就是模块的引用路径,第二行就是项目所用的go版本,第三行就是项目所需的直接依赖包及版本。

有时会看到更加复杂的go.mod文件:

exclude表示忽略指定版本的依赖包,replace表示替换库。

²   go.sum

go.sum文件比较复杂:

每一行其实都是由模块路径、模块版本、哈希检验值 组成的。哈希检验值是用来保证当前缓存的模块不会被篡改。Hash是以h1:开头的字符串,表示生成检验值的算法是第一版的哈希算法。

 

值得注意的是,有点包只有一行,有的包有两行:

有两行的包,区别在于hash不一致,一个是h1:hash, 一个是go.mod h1:hash。h1:hash和go.mod h1:hash要不就是同时存在,要不就是只有go.mod h1:hash。什么情况下会没有h1:hash呢?就是当GO认为肯定用不到某个模块版本的时候就会省略它的h1:hash。

go.mod 和 go.sum 是 go modules 版本管理的指导性文件,因此 go.mod 和 go.sum 文件都应该提交到Git 仓库中去,避免其他人使用你的项目时,重新生成的go.mod 和 go.sum 与你开发的基准版本的不一致。

 

5、      go mod命令的使用

u go mod init

初始化go mod,生成go.mod文件,后可以接参数指定module名。

u go mod download

手动触发下载依赖包到本地缓存(默认$GOPATH/pkg/mod目录)

u go mod graph

打印项目的模块依赖结构

u go mod tidy

添加缺少的包,删除无用的包

u go mod verify

校验模块是否被篡改过

u go mod why

查看为什么需要依赖

u go mod vendor

导出项目的所有依赖到vendor下

u go mod edit

编辑go.mod文件,接 -fmt 参数格式化 go.mod 文件,接 -require=golang.org/x/text 添加依赖,接 -droprequire=golang.org/x/text 删除依赖,详情可参考 go help mod edit

u go list –m –json all

以 json 的方式打印依赖详情

 

6、      如何给项目添加依赖写进go.mod

法(1):只要在项目中import,然后go build,go module就会自动下载并添加;

法(2):自己手动使用go get下载安装后,会自动写入go.mod