概述
最近我将一些项目的依赖管理工具从 dep 迁移到了 go module,有一些爽的地方,也有一些不爽的地方,所以这里就简单介绍一下我的迁移历程中一些个人的观点感受。
先提一下,截止到我写这篇文章的时候,Go 的最新 release 版本是 1.16。
dep
好处
指定版本很友好
Dep 中支持在 Gopkg.toml 指定一个 package 的特定版本,而且支持多种语义,例如:
- version:指定使用特定的版本,可以是
>,=,< ... ...
- branch:指定使用特定的分支
- revision:指定使用特定的 commit
- version:指定使用特定的版本,可以是
支持保存代码中没有用到的 package
Dep 可以通过 ignored 选项来过滤代码中没有使用到的依赖,这对于引用一些工具是非常有用的。例如我们创建 mock 的工具,可以直接放在 vendor 里面,然后 ci 的时候通过
go install vendor/xxx
来安装;在 go module 中,则不行了,常见的用法是创建一个 tools.go 的文件,然后里面import _ xxxx
痛点
连接管理不稳定
这是最多同学吐槽的点,就是 dep 经常一个
dep ensure
命令就是一天,然后还完不成,这其实就是 dep 连接管理和重试的问题了;此外,还有问题就是 dep 的本地缓存也处理得不是很好,经常会出现远程 master 分支已经更新了,但是本地缓存因为处于另外一个分支(例如是 v5 这个 release 分支),但是 dep 就是反应不过来,不会切换到 master 分支之类,然后就导致你 dep ensure 的时候它报错说找到你想要的那个版本。这个时候,我常用的处理方式就是
rm -rf $GOPATH/pkg/dep/sources/git---xxxx
清除掉本地的缓存,然后再dep ensure
一遍就可以了。无法更新指定 package
例如我想只更新一个
github.com/pkg/errors
的版本,其他无关的依赖不更新,那么对不起,不支持这个操作,我只能使用dep ensure
更新所有的 package。如果我就是这么执拗,就只想更新这个 package,可以,我需要把所有的项目直接使用到的其他 package 的当前版本在 Gokpkg.toml 里面都指定好版本了,这样就不会被更新到了。
包管理奇怪
- 例如我想添加一个依赖,如果我在项目中已经
import github.com/pkg/errors
了,那么这个时候我用dep ensure -add github.com/pkg/errors
会提示我已经使用了这个 package,如果我想将这个依赖更新到 Gopkg.lock 里面,那么我只能dep ensure
- 例如我想添加一个依赖,如果我在项目中已经
go module
好处
多版本支持
如果你的项目想同时使用一个 Module 的多个版本,例如主要的代码都是使用一个最新版本的 Module,但是可能因为兼容旧版本的接口之类的,你还需要在某些 API 调用的地方使用比较旧版本的 Module,那么 go module 可以帮助你做到,因为它支持在 import module 的时候指定版本。
支持环境变量设置
比较常用的:
- GOPRIVATE:私有项目,不使用 Proxy
- GOPROXY:支持 proxy,在内网 CI 时格外有用
简单直接的包依赖管理
例如我想添加一个新的依赖包,直接使用
go get xxx
即可被添加在 go.mod 中;想升级,直接就go get -u
即可;如果想指定版本,也是直接go get xxx@commit-id
支持撤消发布
这是一个新的特性,如果你使用了一个错误发布的版本,那么 go mod 会提醒你,然后你就需要进行升级或者降级了。
缺点
包版本管理不友好
虽然前面说了 go module 的包依赖管理很简单,但是,对于一些 tag 来说就不友好了,例如我想安装一个 v5 版本的 tag:
go get github.com/godbus/[email protected]
,对不起,你会遇到这个问题:[root@liqiang.io]# go get github.com/godbus/dbus@v5.0.3
go get: github.com/godbus/dbus@v5.0.3: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v5
那么,我需要怎么做呢?下面两种方式都是官方允许的,其中,推荐第一种(虽然我觉得很别扭,而且升级到 v6 之后还得修改代码,但是人家也是认为这是 feature,见优点 1):
[root@liqiang.io]# go get github.com/godbus/dbus/v5
[root@liqiang.io]# go get github.com/godbus/dbus@37bf87eef99d69c4f1d3528bd66e3a87dc201472
go 生态的版本管理不好
go mod 认为所有被管理的包都是遵循语义化版本(见我的文档中有是说明),但是,实际上有些包是不讲武德的,例如最近我被坑的 grpc-go 这个包,x.y.z 版本的不同 y 版本之间居然是不兼容的,这都属于 google 自己家的东西你让我情何以堪(不是说 Google 没有 KPI 压力的吗,还这么分裂的么)。
除了 grpc-go,另外一个常用的被人吐槽的库就是 etcd 了,但是因为我没怎么用(很久以前用了,但是那时还没有 go mod,而且可能那时还是比较简单的,没有太多版本的问题)。
其他命令会检查 go.mod 版本
我遇到过的常见命令就是
go run
和go test
,他们都会检查一遍 go.sum 中的版本是否和 go.mod 以及项目中的依赖匹配,如果不匹配时,他们就会更新一把。有时这让我很心累,例如前面提到的我要向后兼容限制 grpc-go 的版本,经常就被升上去了(还是那个只能限制最小版本的锅啊)。
对比感受
- 大家都支持替换 package 的源地址
- dep:override
- gomodule:replace
- 大家都有创建和填充 vendor 目录的功能
dep ensure --vendor-only
go mod vendor
- 都不能很好地解决私有项目的问题
- 内网不受信任的 github
- github 上的私有项目
- 这两个场景都需要设置本地的 git config 来解决
整体来看,go module 还是值得升级了,唯一让我留念 dep 的 feature 就是精准的 version 指定,在 go module 中,没法做到精准的指定,即使你指定了,也是认为这是最低要求版本 “>=”,后面很可能因为有依赖的其他 package,会将这个 package 的版本升上去,当然,偶尔也是可以满足你的指定,就是使用你想要的那个版本的。
让我最舒心的就是我提交代码不用加 vendor 目录,而是使用一个 private 的 PROXY Server,这样 CI 速度不受影响,代码提交记录也很清爽,是个非常不过的改进。