包管理工具简介

大一点的项目,通常会依赖很多第三方包。Golang把所有的第三方包都放在GOPATH/src目录下,且每个包仅保留一个版本。如果两个项目依赖不同版本的第三方包,就会产生问题。

为了解决这个问题,go在1.5版本引入了vendor属性(默认关闭),并在1.6版本中默认开启了vendor属性。简单来说,vendor属性就是让go编译时,优先从项目源码树根目录下的vendor目录查找代码(可以理解为切了一次GOPATH),如果vendor中有,则不再去GOPATH中去查找。

但是vendor目录又带来了新的问题:
(1)vendor目录中依赖包没有版本信息。这样依赖包脱离了版本管理,对于升级、问题追溯,会有点困难。
(2)如何方便的得到本项目依赖了哪些包,并方便的将其拷贝到vendor目录下?

为了解决这些问题,开发者在vendor基础上开发了多个管理工具,比较常用的有godep、govendor、glide,以及官方的dep和gomod。下面来学习一下godep、govendor和gomod,更多内容参考go依赖包管理工具对比、golang包管理解决之道——go modules初探、再探go modules:使用与细节。

理论篇

2.1. godep

godep的使用者众多,如docker、kubernetes、coreos等go项目很多都是使用godep来管理其依赖,当然原因可能是早期也没的工具可选。

godep早期版本并不依赖vendor,所以对go的版本要求很松,go 1.5之前的版本也可以用,只是行为上有所不同。在vendor推出以后,godep也改为使用vendor了。

godep save

其他命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 安装
go get -u -v github.com/tools/godep

# 安装新的包
go get github.com/voidking/k8s-client-go
import "github.com/voidking/k8s-client-go"
godep save

# 更新依赖包
go get -u github.com/voidking/k8s-client-go
godep update github.com/voidking/k8s-client-go
# or
godep update github.com/voidking/...

# 下载godeps.json里的包到GOPATH/src
godep restore

2.2. govendor

govendor initgovendor add +external

其他命令:

1
2
3
4
5
6
7
8
9
10
11
# 安装
go get -u github.com/kardianos/govendor

# 获取指定版本的包
govendor fetch golang.org/x/net/context@v1

# 查看依赖的包
govendor list

# 查看哪些包使用了fmt
govendor list -v fmt

2.3. gomod

go modules随着golang1.11的发布和我们见面了,这是官方提倡的新的包管理,乃至项目管理机制,可以不再需要GOPATH的存在。

现在modules机制仍在早期阶段,所以golang提供了一个环境变量“GO111MODULE”,默认值为auto,如果当前目录里有go.mod文件,就使用go modules,否则使用旧的GOPATH和vendor机制,因为在modules机制下go get只会下载go modules,这一行为会在以后版本中成为默认值,这里我们保持auto即可,如果你想直接使用modules而不需要从GOPATH过度,那么把“GO111MODULE”设置为on。

modules和传统的GOPATH不同,不需要包含例如src,bin这样的子目录,一个源代码目录甚至是空目录都可以作为module,只要其中包含有go.mod文件。

1、启用modules机制

1
2
3
4
5
# linux and mac
export GO111MODULE=on

# windows powershell
$env:GO111MODULE = "on"
go mod init test
go mod tidy -v
  • 这条命令会自动下载依赖的module到cache,清除无用的module。
  • 生成go.mod的require部分,和go.sum文件。go.sum是一个构建状态跟踪文件,它会记录当前module所有的顶层和间接依赖,go modules根据这些记录去寻找对应的依赖。
  • 依赖的module下载到GOPATH/pkg/cache/和GOPATH/pkg/mod/目录下。

4、Goland启用gomod
File,Settings,Go,Go Modules,勾选Enable Go Modules(vgo) integration,Apply。

其他命令:

1
2
3
4
5
6
7
8
9
10
11
# 帮助
go help mod

# 已有go.mod,下载依赖到cache
go mod download

# 拷贝依赖到vendor(没有则先下载)
go mod vendor

# 清空cache
go clean -modcache
gomod实践篇

3.1. 安装client-go

假设一个项目依赖client-go,那么参考官方说明进行安装。

kubectl version

2、查找对应版本的client-go
在kubernetes/client-go上查找k8s v1.15.0对应的client-go版本,找到版本为12.0

go get k8s.io/client-go/12.0/kubernetes
1
2
3
4
# export GOPROXY=https://goproxy.cn
export GOPROXY=https://goproxy.io
export GO111MODULE=on
go get k8s.io/client-go/12.0/kubernetes

失败了,返回404,猜测是12.0版本在代理服务器不存在,使用另外一种下载方式。

1
go get k8s.io/client-go@kubernetes-1.15.0


此处下载了两个版本的client-go,我们使用v0.0.0版本。

$GOPATH/pkg/mod/k8s.io$GOPATH/src
1
2
3
export GOPROXY=https://goproxy.io
export GO111MODULE=on
go mod vendor

完成之后,所有的依赖都下载到了client-go/vendor目录下,一个完整的client-go依赖库就准备好了。

3.2. 依赖库版本管理

client-go依赖库准备好了,但是另一个问题来了:假设项目vk-project依赖client-go和库A,client-go依赖库A,两个依赖库A版本不一致,那么vk-project在编译时,会同时依赖两个不同的库A,导致编译出的二进制包有问题。

此时,一个简单的办法是把GOPATH/src/k8s.io/client-go/vendor目录剪切覆盖vk-project/vendor目录,然后把GOPATH/src/k8s.io/client-go目录移动到vk-project/vendor/k8s.io/目录下。这么做的话,client-go就和自己的依赖是平级的存在了。

但是,这么做有另一个问题:依赖库版本没有记录。因为没有版本记录,那么在上传vk-project项目时,就要把依赖全部上传,否则别人下载了vk-project也没办法正确安装依赖。

这里,使用gomod进行依赖库版本管理。

1
2
3
go mod init vk-project
go mod tidy -v
go mod vendor

上传代码时忽略vendor目录,在gitignore中添加:

1
/vendor/

3.3. 安装依赖

cd vk-project
go mod download
go mod vendor