GOPATH时代
从golang诞生以来关于GOPATH
和包管理的纠纷就层出不穷。google内部是所有代码在一个大仓库里,因此golang也设计成所有代码都放到GOPATH
下,依赖代码更新后构建时直接使用最新的版本。这对于外部依赖等于没有管理,所有go get
获取的外部库全部下载到GOPATH中。
这时候如果我们有两个项目serverA、serverB分别依赖libA的1.0版本和2.0版本,那这两个项目的GOPATH
必须不同,否则就会出现serverA引用了2.0的libA或者serverB引用了1.0的libA。
这个时候出现了很多对GOPATH
管理的工具如godep。
godep通过把当前的GOPATH
中的依赖包备份到Godeps/_workspace/
保证依赖版本的快照。
这种管理很简陋也没有兼容性等信息,完全依赖开发者自己。
vendor时代
显然纯GOPATH
的方式并不符合社会主义科学发展观。go官方引入了vendor来统一各种Godeps/_workspace/
。所有放在包vendor目录下的依赖都会被优先使用。但本质上并没有比GOPATH好太多,版本号等信息同样没有。
于是各种vendor管理工具一时间百花齐放。glide、gvt、govendor。这些工具都把依赖包信息汇总到配置文件中,并记录版本号,可以从简单的配置文件生成特定的vendor目录。这种情况下绝大部分需求都可以满足,只是没有官方统一的工具,而且同一个版本库内的不同模块的相同依赖经常会被vendor拷贝多份。
当然glide等工具也有一些全局的缓存保证不同项目拉取相同依赖时不需要额外请求网络。
顺带一提,在vendor时代的尾声,go官方的基于vendor的dep工具仍未脱掉experiment的帽子。
module时代
虽然大部分需求在vendor时代都解决了,但是我们前面也说过GOPATH
并不符合社会主义科学发展观,为了完全摆脱GOPATH
的阴影,module被引入了。
类似于其他管理工具的配置文件,go module也有自己的go.mod
配置文件,其中也记录具体的依赖信息。最大的不同就是vendor目录不见了,那么依赖包到底去哪儿了呢?
如果你是用go mod download
下载了依赖包之后,就可以在$GOPATH/pkg/mod/
下发现和之前$GOPATH/src
类似的目录结构,并且包路径上都包含了版本号。由此vendor被淘汰,GOPATH
不再与项目相关。
其他语言
再回头看看其他语言,最原始的GOPATH依赖模式除了一些老项目,几乎没有使用了。
而vendor模式一直是各种脚本语言的最爱:
- node一直把依赖放在node_modules中,通过package.json管理
- php composer把依赖放在vendor中,通过composer.json管理
采用module模式则有:
- rust cargo把依赖分版本统一放到$HOME/.cargo下,通过Cargo.toml管理
- c conan也是类似的情况
当然还有比较另类的python:最开始用pip全局依赖,后来有了virtualenv虚拟出不同的依赖环境,到最新的pipenv
总结
其实看看各种语言的包管理,发展趋势基本都是相同的。
- 支持锁定版本
- 支持semver
- 本地多版本缓存(优化性能)
其实还有一个菱形依赖的问题,这个太复杂就不深入了。