文章目录

  • 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_golangv1.0.0版本。

可重复的构建是指,项目无论在谁的环境中(同平台)构建,其产物都是相同的。回想一下GOPATH时代,虽然大家拥有同一个项目的代码,但由于各自 的GOPATH中github.com/prometheus/client_golang版本不一样,虽然项目可以构建,但构建出的可执行文件很可能是不同的。 可重复构建至关重要,避免出现“我这运行没问题,肯定是你环境问题”等类似问题出现。

mod使用方法

有了go modules功能后,建议使用go modules的方法,项目目录也不需要再放到GOPATH/src目录下了。

首先要设定环境变量GO111MODULE,它有三个取值:

  • auto:go判断当前目录是否满足以下两种情况,都满足的话就会开启modules功能
  1. 该项目目录不在GOPATH/src下
  2. 当前目录or上一层目录存在go.mod文件
  • on:启用modules功能。
  • off:不启用modules功能。

接下来的步骤是

  1. 运行命令 go mod init 生成一个go.mod文件,文件里module默认使用项目目录名,此外还包含了go的版本、用到的第三方库等信息行。
  2. 运行命令go mod tidy 检测依赖:(1)引用项目需要的依赖增加到go.mod文件;(2)去掉go.mod文件中项目不需要的依赖。
  3. 运行命令 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)中的信息)