Golang官方包依赖管理进化之路

思考:在现有项目中是如何做好golang包管理的?

最近重温golang,在极客时间上看到部分留言表示不习惯gopath这种方式。

思考了下之前开发的几个golang项目,包括小型内部工具、对外SDK、部分业务接口等。

开发小型内部工具时,依赖的大部分是系统包,我还是比较喜欢gopath方式,快速查看管理自己调用的方法。

涉及到对外SDK、业务功能依赖的一些三方包放在vendor中,每次都需要大段大段的提交代码。

对比业界其他语言,简单列举一下业界主流语言的包管理方式如下

ps:可以说但凡是主流语言,包管理、依赖管理的工具就能列出不少,这里只列几个相对熟悉的

  • Java
    • maven
      • 通过pom.xml来描述maven项目
      • 指定依赖jar包的名称、版本等
  • Python
    • easy_install
      • 指定包地址,完成自动下载、编译、安装和管理
    • pip
      • 指定包名和版本号,支持包搜索和安装
      • pip 算是easy_install的升级版,提供更好的提示信息,删除package等功能,支持的参数更多
  • PHP
    • Composer
      • composer.json 里面定义了依赖包的名称和版本
  • Ruby
    • Gem
    • Bundler
      • https://bundler.io/

接下来理一下,golang在依赖包管理方面的一个进阶曲线。

  1. GOPATH
  2. vendor
  3. godep/govendor .etc
  4. go mod

1 GOPATH

  • GOPATH的目的是为了告知go,需要代码的时候,去哪里查找。
  • GOPATH下的代码,可以包括本项目、引用外部项目 两方面的代码。
  • GOPATH可以设置多个路径。
  • GOPATH下会有3个目录:src, bin, pkg。
    • src目录:go编译时查找代码的地方
    • bin目录:go get godep这种bin工具的时候,二进制文件下载的目的地
    • pkg目录:编译生成的lib文件存储的地方

golang提供了go get的形式,从指定的git地址clone代码库到对应的gopath路径下,以供业务方调用。这种形式对于个人开发或者是所有人都在一个代码库上进行开发的场景来说,已经完全满足需求。

2 vendor

依赖GOPATH来解决go import有个很严重的问题:如果项目依赖的包做了修改,或者干脆删掉了,在重新go get后会影响到当前的业务功能。

因此在1.5版本以前,为了规避这个问题,通常会将当前使用的依赖包拷贝出来。

项目使用依赖包的方式大概是如下几个:

  • 将依赖包拷贝到项目源码树中,然后修改import
  • 将依赖包拷贝到项目源码树中,然后修改GOPATH
  • 在某个文件中记录依赖包的版本,然后将GOPATH中的依赖包更新到对应的版本(因为依赖包实际是个git库,可以切换版本)

为了解决这个问题,go在1.5版本引入了vendor属性(默认关闭,需要设置go环境变量GO15VENDOREXPERIMENT=1),并在1.6版本中默认开启了vendor属性。

简单来说,vendor属性就是让go编译时,优先从项目源码树根目录下的vendor目录查找代码(可以理解为切了一次GOPATH),如果vendor中有,则不再去GOPATH中去查找。

vendor一定程度上解决了代码被更新覆盖的情况,但是也引入了新的问题

  • vendor目录中依赖包没有版本信息的记录,对于后续包的升级、问题追溯比较困难。
  • 项目依赖的包,都需要手工将其拷贝到vendor目录下

3 godep、glide、govendor

为了解决上述vendor问题,很多开源的解决方案应运而出,比较知名的是

  • godep
  • glide
  • govendor

https://github.com/golang/go/wiki/PackageManagementTools

这里列一个官方的对比说明,这三个在具体配置实现上是有区别的,但是整体的思想是一致的,均是利用json或是xml形式的配置文件,写明依赖的包路径以及版本。

这些工具通过良好的命令和配置文件形式帮我们解决了一些工程上的问题,但是依旧存在一个痛点,那就是vendor目录下的大量代码,造成代码量的急剧上升,对于IDE也是个巨大的负担。

4 go module

简称go mod,基于上述问题衍生的,于1.11版本引入的golang的包管理工具。 提供了GO111MODULE 临时环境变量,GO111MODULE 的取值包括 off、on、auto三种选项。

具体解释如下,向前兼容的话选择auto即可。

off: GOPATH mode,查找vendor和GOPATH目录
on:module-aware mode,使用 go module,忽略GOPATH目录
auto:如果当前目录不在$GOPATH 并且当前目录(或者父目录)下有go.mod文件,则使用 GO111MODULE, 否则仍旧使用 GOPATH mode

其中: 当module功能启用时,GOPATH在项目构建过程中不再担当import的角色,但它仍然存储下载的依赖包,具体位置在$GOPATH/pkg/mod

具体的使用,详细阅读下官方的wiki:

https://github.com/googege/blog/blob/master/go/tool/goMod/README.md