《Go是怎么解决依赖管理问题的》学习笔记

441 阅读4分钟

我们要理解清楚这个问题,首先需要知道Go为了解决这个问题做了工作,遇到了哪些困难,做了哪些努力和改进。 了解了这些,我们就可以更加清楚这个问题。

1、Go构建模式是怎么演化的:做了哪些工作

  • 早期的GOPATH
  • 1.5版本后的vendor
  • Go Module

1.1、GOPATH

在GOPATH模式下面,编译器可以在配置的GOPATH环境变量的目录下面,搜寻程序所需要的依赖包。

找的到就编译,找不到就报错。

如果没有设置GOPATH这个环境变量。

则GOPATH默认值是$HOME\go

举例: 你用到了第三方依赖包:

package main

import "github.com/sirupsen/logrus"

func main() {
    logrus.Println("hello, gopath mode")
}

假设,你并未下载这个github.com/sirupsen/logrus 这个依赖包。

go编译器先去你本地的go安装目录src目录找,如果没找到:

再去GOPATH配置的目录下面的src目录下面去找。

如果都没有,我们需要自己去下载一下,使用go get命令去下载。

这个命令会将第三方依赖下载至GOPATH配置的目录下面。

包括依赖的依赖,它也会去检测,如果不存在的话,也会去下载。

这个模式存在哪些问题?

很明显,就是版本的控制问题。

go get命令会拉取当前时刻,第三方依赖最新的版本。

通俗一点就是,不同的开发者,在不同时间下载第三方依赖的时候,会因为第三方依赖的偏差,导致编译出来的结果不一致。

甚至有可能,你需要的依赖包本身所依赖的包,由于作者的更新导致不可用。这种不可用会传递你的项目中。

1.2、vendor

为了解决上面的问题,引入了vendor的概念。

我认为vendor的概念非常好理解:

就是为了避免上面的版本不可控,引入的一种版本隔离。

具体实现简单介绍一下:

go开发者开发项目的时候,把依赖全部下载到vendor目录下面。以前是直接在GOPATH下面。

现在在vendor目录下面也优先存一份。

这个项目分享给其他人的时候,其他人进行编译,go编译器会自动优先从vendor目录下面获取依赖。

这样就实现了一个:可重现构建的功能。

所以,我们如果使用了vendoe,在提交代码的时候,也需要将vendor目录进行提交。

这就带了一个问题:非常占用空间,而且需要手工去管理vendor下面的依赖包,非常耗费心智。

这里需要有一点注意:要想让go编译器优先从vendor里面取,你的 Go 项目必须位于 GOPATH 环境变量配置的某个路径的 src 目录下面

1.3、Go Module

Go的1.11版本,带来了Go Module,与vendor诞生的理由差不多,为解决以前的痛点而诞生的。

Go Module 的三板斧:

  • go mod init 会生成一个go.mod ,将项目变成Go Module
  • go mod tidy 根据项目所需要的依赖,自动下载依赖,一些不需要的依赖,也会从项目中删除
  • go build 对Module进行构建

与前面两种不同:Go Module 项目,不必在GOPATH下面!

Go Module 还支持通过 Go Module 代理服务加速第三方依赖的下载。

2、深入Go Module构建模式

Go Module依赖版本去管理,也就需要一定的规矩。目前的规矩如下:

image.png

  • 主版本号相同,则兼容;反之则认为不兼容
  • 主版本号相同,导入的路径也是一样的

举例: v1.7.0、v1.8.1 是互相兼容的版本。

v2.0.0 与上面版本不兼容。

v1.7.0、v1.8.1 在程序中导入是这样的:

import "github.com/sirupsen/logrus/v2"

而v2.0.0在程序中导入是这样的:


import "github.com/sirupsen/logrus/v2"

Go Module 的最小版本选择原则:

image.png

go编译器在选择c依赖的时候会选择哪一个呢?

按照上面规则,v1.1.0、v1.3.0、v1.7.0的主版本号相同,应该是互相兼容的,所以选择哪一个都行。

Go设计者认为只要满足整体需求的“最小版本”即可。所以选择v1.3.0版本。

下面这个图需要牢记一下,万一碰到以前的历史项目,可以参考一下。

如果都是新项目,就不必要记录,因为默认是开了Go Module的

image.png