Go语言依赖管理¶
在工程代码中,每种语言基本上都有自己的依赖管理工具,比如python的pip
、node.js的npm
,java的maven
,rust的cargo
,Go语言也有提供自己的依赖库管理工具。Go语言从v1.5开始开始引入vendor模式,如果项目目录下有vendor目录,那么go工具链会优先使用vendor内的包进行编译、测试等。在go1.11之后,Go语言主要使用Go modules
对代码依赖进行管理。
Go Modules要点¶
GO111MODULE环境变量¶
这个环境变量是Go Modules的开关,主要有以下参数:
- auto:只在项目包含了
go.mod
文件时启动go modules,在Go1.13版本中是默认值 - on:无脑启动Go Modules,推荐设置,Go1.14版本以后的默认值
- off:禁用Go Modules,一般没有使用
go modules
的工程使用;
GOPROXY¶
该环境变量用于设置Go模块代理,Go后续在拉取模块版本时能够脱离传统的VCS方式从镜像站点快速拉取,GOPROXY
的值要以英文逗号分割,默认值是https://proxy.golang.org,direct
,但是该地址在国内无法访问,所以可以使用goproxy.cn
来代替(七牛云配置),设置命令:
Bash | |
---|---|
也可以使用其他配置,例如阿里配置:
Bash | |
---|---|
该环境变量也可以关闭,可以设置为"off",禁止Go在后续操作中使用任何Go module proxy;
上面的配置中我们用逗号分割后面的值是direct
,它是什么意思呢?
direct
为特殊指示符,因为我们指定了镜像地址,默认是从镜像站点拉取,但是有些库可能不存在镜像站点中,direct
可以指示Go回源到模块版本的源地址去抓取,比如github
,当go module proxy
返回404、410
这类错误时,其会自动尝试列表中的下一个,遇见direct
时回源地址抓取;
GOSUMDB¶
GOSUMDB
(go checksum database)是Go官方为了go modules安全考虑,设定的module校验数据库,你在本地对依赖进行变动(更新/添加)操作时,Go 会自动去这个服务器进行数据校验,保证你下的这个代码库和世界上其他人下的代码库是一样的,保证Go在拉取模块版本时拉取到的模块版本数据未经篡改
GOSUMDB的值自定义格式如下:
- 格式 1:
+ 。 - 格式 2:
+ 。
GOSUMDB
的默认值是sum.golang.org
,默认值与自定义值的格式不一样,默认值在国内是无法访问,这个值我们一般不用动,因为我们一般已经设置好了GOPROXY
,goproxy.cn
支持代理sum.golang.org
;
所以,环境变量GOSUMDB
可以用来配置你使用哪个校验服务器和公钥来做依赖包的校验
但是在使用的时候需要注意,如果你的代码仓库或者模块是私有的,那么它的校验值不应该出现在互联网的公有数据库里面,但是我们本地编译的时候默认所有的依赖下载都会去尝试做校验,这样不仅会校验失败,更会泄漏一些私有仓库的路径等信息,我们可以使用GONOSUMDB
这个环境变量来设置不做校验的代码仓库, 它可以设置多个匹配路径,用逗号相隔.
例如:
Bash | |
---|---|
git.example.com
, test.xyz/com
这些公司和自己的私有仓库就都不会做校验了。
GONOPROXY/GONOSUMDB/GOPRIVATE¶
这三个环境变量放在一起说,一般在项目中不经常使用,这三个环境变量主要用于私有模块的拉取,在GOPROXY、GOSUMDB中无法访问到模块的场景中,例如拉取git上的私有仓库; GONOPROXY、GONOSUMDB的默认值是GOPRIVATE的值,所以我们一般直接使用GOPRIVATE即可,其值也是可以设置多个,以英文逗号进行分割;例如:
Bash | |
---|---|
也可以使用通配符的方式进行设置,对域名设置通配符号,这样子域名就都不经过Go module proxy
和Go checksum database
全局缓存¶
go mod download
会将依赖缓存到本地,缓存的目录是GOPATH/pkg/mod/cache
、GOPATH/pkg/sum
,这些缓存依赖可以被多个项目使用,未来可能会迁移到$GOCACHE
下面;
可以使用go clean -modcache
清理所有已缓存的模块版本数据;
Go Modules命令¶
我们可以使用go help mod
查看可以使用的命令:
命令 | 作用 |
---|---|
go mod init | 生成go.mod文件 |
go mod download | 下载go.mod文件中指明的所有依赖放到全局缓存 |
go mod tidy | 整理现有的依赖,添加缺失或移除不使用的modules |
go mod graph | 查看现有的依赖结构 |
go mod edit | 编辑go.mod文件 |
go mod vendor | 导出项目所有的依赖到vendor目录 |
go mod verify | 校验一个模块是否被篡改过 |
go mod why | 解释为什么需要依赖某个模块 |
go.mod文件¶
go.mod
是启用Go modules的项目所必须且最重要的文件,其描述了当前项目的元信息,每个go.mod
文件开头符合包含如下信息:
module:用于定义当前项目的模块路径(突破$GOPATH路径) go:当前项目Go版本,目前只是标识作用 require:用于设置一个特定的模块版本 exclude:用于从使用中排除一个特定的模块版本 replace:用于将一个模块版本替换为另外一个模块版本,例如chromedp使用golang.org/x/image这个package一般直连是获取不了的,但是它有一个github.com/golang/image的镜像,所以我们要用replace来用镜像替换它 retract:用来声明该第三方模块的某些发行版本不能被其他模块使用,在Go1.16引入
示例如下:
go.mod
文件,接下来我们分模块详细介绍一下各个部分
module¶
go.mod
文件的第一行是module
, 表示工程里的依赖的基路径,例如上面的项目:
Go | |
---|---|
import
的路径都是以rotatebot
开头的字符串
go version¶
go.mod
文件的第二行是go version
,其是用来指定你的代码所需要的最低版本:
Go | |
---|---|
require¶
require
用来指定该项目所需要的各个依赖库以及他们的版本,从上面的例子中我们看到版本部分有不同的写法,还有注释,接下来我们来解释一下这部分;
indirect注释¶
Go | |
---|---|
以下场景才会添加indirect
注释:
- 当前项目依赖包A,A又依赖包B,但是A的
go.mod
文件中缺失B,所以在当前项目go.mod
中补充B并添加indirect
注释 - 当前项目依赖包A,A又依赖包B,但是依赖包A没有
go.mod
文件,所以在当前项目go.mod
中补充B并添加indirect
注释 - 当前项目依赖包A,A又依赖包B,当依赖包A降级不再依赖B时,这个时候就会标记indirect注释,可以执行go mod tidy移除该依赖;
Go1.17版本对此做了优化,indirect
的module将被放在单独require
块的,这样看起来更加清晰明了。
incompatible标记¶
incompatible
标记其实是一个module标签归规范约束,Go module 的版本选择机制规定,Module 的版本号需要遵循 v\<major>.\<minor>.\<patch> 的格式,此外,如果major版本号大于1
时,其版本号还需要体现在Module名字中。
比如Module github.com/RainbowMango/m,如果其版本号增长到 v2.x.x 时,其 Module 名字也需要相应的改变为:github.com/RainbowMango/m/v2。即,如果major版本号大于1
时,需要在Module名字中体现版本。
那么如果 Module 的 major 版本号虽然变成了 v2.x.x,但 Module 名字仍保持原样会怎么样呢? 其他项目是否还可以引用呢?
假设github.com/gin-contrib/sse的当前版本为v3.5.0,按照Go module 的版本选择机制,其 Module 名字需要相应的改变为: github.com/gin-contrib/sse/v3,但是如果module名没改,还是github.com/gin-contrib/sse,则在被形目引用的时候,就会在后面加上incompatible标记,变成
除了增加 +incompatible(不兼容)标识外,在其使用上没有区别
版本号¶
go module拉取依赖包本质也是go get行为,go get主要提供了以下命令:
命令 | 作用 |
---|---|
go get | 拉取依赖,会进行指定性拉取(更新),并不会更新所依赖的其它模块 |
go get -u | 更新现有的依赖,会强制更新它所依赖的其它全部模块,不包括自身 |
go get -u -t ./... | 更新所有直接依赖和间接依赖的模块版本,包括单元测试中用到的 |
go get拉取依赖包取决于依赖包是否有发布的tags:
- 拉取的依赖包没有发布tags
- 默认取主分支最近一次的commit的commit hash,生成一个伪版本号
- 拉取的依赖包有发布tags
- 如果只有单个模块,那么就取主版本号最大的那个tag
- 如果有多个模块,则推算相应的模块路径,取主版本号最大的那个tag
没有发布的tags:
Go | |
---|---|
v0.0.0:根据commit的base version生成的:
- 如果没有base version,那么就是vx.0.0的形式
- 如果base version是一个预发版本,那么就是vx.y.z-pre.0的形式
- 如果base version是一个正式发布的版本,那么它就patch号加1,就是vx.y.(z+1)-0的形式
20190718012654:是这次提交的时间,格式是yyyyMMddhhmmss fb15b899a751:是这个版本的commit id,通过这个可以确定这个库的特定的版本
有发布的tags:
Go | |
---|---|
replace¶
因为某些未知原因,并不是所有的包都能直接用go get
获取到,或者说是我们想要在官方的依赖库中集成一些我们自己的功能,这时我们就需要使用go modules
的replace
功能了
replace
顾名思义,就是用新的package
去替换另一个package
,他们可以是不同的package
,也可以是同一个package
的不同版本。看一下基本的语法:
Bash | |
---|---|
old
是要被替换的package,new
就是用于替换的package。
replace的使用步骤:
- 首先
go get new-package
(如果你知道package的版本tag,那么这一步其实可以省略,如果想使用最新的版本而不想确认版本号,则需要这一步) - 然后查看
go.mod
,手动复制new-package
的版本号(如果你知道版本号,则跳过) go mod edit -replace=old[@v]=new[@v]
- 接着
go mod tidy
或者go build
或者使用其他的go tools
,他们会去获取new-package
然后替换掉old-package
- 最后,在你的代码里直接使用
old-package
的名字,golang会自动识别出replace
,然后实际你的程序将会使用new-package
,替换成功
exclude¶
这个特性是在Go1.16版本中引入,用来声明该第三方模块的某些发行版本不能被其他模块使用;
使用场景:发生严重问题或者无意发布某些版本后,模块的维护者可以撤回该版本,支持撤回单个或多个版本;
这种场景以前的解决办法:
维护者删除有问题版本的tag
,重新打一个新版本的tag
;
使用者发现有问题的版本tag
丢失,手动介入升级,并且不明真因;
引入retract
后,维护者可以使用retract
在go.mod
中添加有问题的版本:
重新发布新版本后,在引用该依赖库的使用执行go list
可以看到 版本和"严重bug..."的提醒。该特性的主要目的是将问题更直观的反馈到开发者的手中;
go.sum文件¶
Go 在做依赖管理时会创建两个文件,go.mod
和 go.sum
,go.mod
的重要性不言而喻,这个文件几乎提供了依赖版本的全部信息。而 go.sum
则是记录了所有依赖的 module 的校验信息,以防下载的依赖被恶意篡改,主要用于安全校验。这个文件我们一般不需要编辑,更新以来的时候会自动更新。
每行的格式如下:
比如:
Text Only | |
---|---|
其中 module
是依赖的路径,version
是依赖的版本号。如果 version
后面跟 /go.mod
表示对哈希值是 module
的 go.mod
文件;否则,哈希值是 module
的 .zip
文件。
hash
是以 h1:
开头的字符串,表示生成 checksum 的算法是第一版的HASH算法(SHA256)。如果将来在SHA-256中发现漏洞,将添加对另一种算法的支持,可能会命名为h2
。
Go Modules使用¶
使用 go modules
的一个前置条件是Go语言版本大于等于Go1.11;然后我们要检查环境变量 GO111MODULE
是否开启,执行 go env
查看:
如果GO111MODULE=off,可以执行一下命令打开
Bash | |
---|---|
接下来就可以使用GO MODULE管理项目工程了,先创建一个项目目录
执行:
Bash | |
---|---|
运行结果 :
Text Only | |
---|---|
会在go_tour目录下生成一个go.mod文件。在main包下建立main.go文件
Go | |
---|---|
然后在 go_tour
目录下执行 go mod tidy
命令,可以看到:
Text Only | |
---|---|
可以看到此时的go.mod文件内容:
Go | |
---|---|
依赖已经安装好,并且可以使用了,在 go.mod
下还可以看到一个 go.sum
文件对依赖包的校验,go.sum
我们一般不用管
此时执行 go run main.go
就可以看到执行结果了
Text Only | |
---|---|
以上就是用go module
管理依赖库的简单用法。