文章目录
- Go依赖管理历程
- GOPATH
- 环境变量GOROOT&GOPATH
- 依赖查找
- GOPATH的缺点
- vendor
- 依赖查找
- vendor的缺点
- Go Module
- mod使用方法
- 依赖包存储
参考 https://docs.kilvn.com/GoExpertProgramming/chapter12/
Go依赖管理历程
Go依赖管理经历了三个重要的阶段:
- GOPATH;
- vendor;
- Go Module;
下面依次介绍这三个方法
GOPATH
环境变量GOROOT&GOPATH
安装完golang1.13后,有两个路径GOROOT和GOPATH。
使用命令 go env 查看这两个路径:
GOROOT="/usr/local/go"
GOPATH="/home/zhanhzhong/go"
实际上Go语言项目是由一个或多个package组成的,这些package按照来源分为以下几种:
- 标准库
- 第三方库
- 项目私有库
其中标准库的package全部位于GOROOT环境变量指示的目录中,而第三方库和项目私有库都位于GOPATH环境变量所指示的目录中。
第三方库和自己写的项目都需要放在GOPATH的src目录下,而且需要使用go get命令来逐个下载第三方库(go get命令自动把第三方库下载到GOPATH/src目录)。
依赖查找
当某个package需要引用其他包时,编译器就会依次在下面的目录查找
GOROOT/src/
GOPATH/src/
如果某个包从GOROOT下找到的话,就不再到GOPATH目录下查找,所以如果项目中开发的包名与标准库相同的话,将会被自动忽略。
GOPATH的缺点
GOPATH的优点是足够简单,但由于不同项目的第三方库都存放在同一个目录下,而且同一个依赖库只能存放某一个版本,不能同时存放多个版本,因此不能很好的满足实际项目的工程需求。
比如,你有两个项目A和B,他们都引用某个第三方库T,但这两个项目使用了不同的T版本,即:
- 项目A 使用T v1.0
- 项目B 使用T v2.0
由于编译器依赖查找固定从GOPATH/src下查找GOPATH/src/T
,所以,无法在同一个GOPATH目录下保存第三方库T的两个版本。当某一个项目的一个依赖库无法使用特定的版本时,就有可能会出错,导致出现”在我的开发机上明明没问题,为什么到你开发机上就出问题了“的情况。
针对GOPATH的缺点,GO语言社区提供了Vendor机制,从此依赖管理进入第二个阶段:将项目的依赖包私有化。
vendor
自Go 1.6版本起,vendor机制正式启用,它允许把项目的依赖全部放到一个位于本项目的vendor目录中,这个vendor目录可以简单理解成私有的GOPATH目录。(但是vendor放进项目目录也会导致它随着git上传到远端,比较臃肿)
依赖查找
编译时,优先从vendor中寻找依赖包,如果vendor中找不到再到GOPATH中寻找。顺序如下
- Main.go所在目录下的vendor目录
- 再上一个层级的目录下的vendor目录
- …
GOROOT/src/
GOPATH/src/
可见一个项目里可以有多个vendor目录,但是一般建议只集中维护一个vendor目录。
vendor的缺点
vendor很好的解决了多项目间的隔离问题,但是位于vendor中的依赖包无法指定版本,某个依赖包,在把它放入vendor的那刻起,它就固定在当时版本,项目的其他开发者很难识别出你所使用的依赖版本。
比起这个,更严重的问题是二进制急剧扩大问题,比如你依赖某个开源包A和B,但A中也有一个vendor目录,其中也放了B,那么你的项目中将会出现两个开源包B。再进一步,如果这两个开源包B版本不一致呢?如果二者不兼容,那后果将是灾难性的。
一直到Go 1.11版本,官方社区推出了Modules机制,从此Go的版本管理走进第三个时代。
Go Module
Go Module 相比GOPATH和vendor而言功能强大得多,它基本上完全解决了GOPATH和vendor时代遗留的问题。 我们知道,GOPATH时代最大的困扰是无法让多个项目共享同一个pakage的不同版本,在vendor时代,通过把每个项目依赖的package放到vendor 中可以解决这个困扰,但是使用vendor的问题是无法很好的管理依赖的package。
Go Module是一种全新的依赖管理方案,它涉及一系列的特性,但究其核心,它主要解决两个重要的问题:
- 准确的记录项目依赖;(依赖哪些package、以及package的版本)
- 可重复的构建;(一旦项目的依赖被准确记录了,就很容易做到重复构建)
准确的记录项目依赖,比如你的项目依赖github.com/prometheus/client_golang
, 且必须是v1.0.0
版本,那么你可以通过Go Module指定(具体指定方法后面会介绍),任何人在任何环境下编译你的项目, 都必须要使用github.com/prometheus/client_golang
的v1.0.0
版本。
可重复的构建是指,项目无论在谁的环境中(同平台)构建,其产物都是相同的。回想一下GOPATH时代,虽然大家拥有同一个项目的代码,但由于各自 的GOPATH中github.com/prometheus/client_golang
版本不一样,虽然项目可以构建,但构建出的可执行文件很可能是不同的。 可重复构建至关重要,避免出现“我这运行没问题,肯定是你环境问题”等类似问题出现。
mod使用方法
有了go modules功能后,建议使用go modules的方法,项目目录也不需要再放到GOPATH/src目录下了。
首先要设定环境变量GO111MODULE,它有三个取值:
- auto:go判断当前目录是否满足以下两种情况,都满足的话就会开启modules功能
- 该项目目录不在GOPATH/src下
- 当前目录or上一层目录存在go.mod文件
- on:启用modules功能。
- off:不启用modules功能。
接下来的步骤是
- 运行命令 go mod init 生成一个go.mod文件,文件里module默认使用项目目录名,此外还包含了go的版本、用到的第三方库等信息行。
- 运行命令go mod tidy 检测依赖:(1)引用项目需要的依赖增加到go.mod文件;(2)去掉go.mod文件中项目不需要的依赖。
- 运行命令 go mod download 下载第三方库,也可以直接运行go build或者go install,也会自动安装第三方库。此时第三方库不再被安装在GOPATH/src下,而是下载到GOPATH/pkg/mod中
依赖包存储
在GOMODULE
模式下,go get
命令会将依赖包下载到$GOPATH/pkg/mod
目录下,并且按照依赖包的版本分别存放。例子:
${GOPATH}/pkg/mod/github.com/google
├── uuid@v1.0.0
├── uuid@v1.1.0
├── uuid@v1.1.1
相较于GOPATH
模式,GOMODULE
有两处不同点:
- 一是依赖包的目录中包含了版本号,每个版本占用一个目录;
- 二是依赖包的特定版本目录中只包含依赖包文件,不包含
.git
目录;(由于依赖包的每个版本都有唯一的目录,也表示该目录内容不会发生改变,也就不必再存储其位于版本管理系统(如git)中的信息)