Go语言依赖管理 | 青训营笔记

102 阅读6分钟

这是我参与「第五届青训营 」笔记创作活动的第5天,这篇文章总结Go语言依赖管理的内容。

依赖管理

学习Go语言,我们需要有一个很重要的能力就是开发项目中能够利用他人已经封装好的,经过验证的开发组件来提升自己的开发效率。当我们开发复杂项目时,如果花费大量时间使用标准库实现编码从0到1构建的效率是不高的,我们应该更注重业务逻辑的实现。
所以我们要注重对依赖库的管理,当使用其他框架时通过SDK来引入。

依赖管理演进

image.png

以下对各依赖版本进行简单解释:

GOPATH

GOPATH是我们配置Go语言的一个环境变量,是我们Go项目的一个工作区。目录有以下结构:
src: 存放Go项目的源码;
pkg: 存放编译的中间产物,加快编译速度;
bin:存放Go项目编译生成的二进制文件。
此项目存在一定的弊端:
如图,同一个pkg,有2个版本,A->A(),B->B()。而src下只能有一个版本存在,那AB项目无法保证都能编译通过。也就是在gopath管理模式下,如果多个项目依赖同个库,则依赖该库是同一份代码,所以不同项目不能依赖同个库的不同版本,这很显然不能满足我们的项目依赖需求。为了解决这问题,govender出现了。

image.png

Go Vendor

Vendor是当前项目中的一个目录,其中存放了当前项目依赖的副本。在Vendor机制下,如果当前项目存在Vendor目录,会优先使用该目录下的依赖,如果依赖不存在,会从GOPATH中寻找。这样不同的项目调用自己vendor文件夹中的依赖,而不会产生必须使用同一个版本库的问题。

image.png

但vendor无法很好解决依赖包的版本变动问题和一个项目依赖同一个包的不同版本的问题,下面我们看一个场景。

image.png

如图项目A依赖pkg B和C,而B和C依赖了D的不同版本,通过vendor的管理模式我们不能很好的控制对于D的依赖版本,一旦更新项目,有可能导致项目不知道调用pkg D的哪个版本。归根到底vendor不能很清晰的标识依赖的版本概念。基于这个问题go module就应运而生了。

Go Module

Go Module是Go语言官方推出的一个依赖管理系统,解决了之前依赖管理系统存在的诸如无法依赖同一个库的多个版本等问题。go module从Go 1.11开始实验性引入,Go 1.16默认开启。我们通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包,最终使用go mod定义版本规则和管理项目依赖关系。下面讲解go.mod文件的语法结构。

依赖管理三要素

image.png 以下举例说明:

image.png

首先模块路径用来标识一个模块,从模块路径可以看出从哪里找到该模块,如果是github前缀则表示可以从Github仓库找到该模块,依赖包的源代码由github托管,如果项目的子包想被单独引用,则需要通过单独的在项目中定义go.mod文件进行依赖管理。
下面是依赖的原生sdk版本。
最下面是单元依赖,每个依赖单元用模块路径+版本来唯一标示。

依赖配置

gopath和govendor都是源码副本方式依赖,没有版本规则概念,而go mod为了方便管理则定义了版本规则,分为语义化版本和基于commit的伪版本。其中语义化版本包括MAJOR、MINOR、PATCH,不同的MAJOR版本表示是不兼容的API,所以即使是同一个库,MAJOR版本不同也会被认为是不同的模块; MINOR版本通常是新增函数或功能,向后兼容;而patch版本一般是修复bug。而基于commit的伪版本包括,基础版本前缀是和语义化版本一样的; 时间戳(wyddhmmss),也就是提交Commit的时间,最后是校验码(abcdefabcdef),包含12位的哈希前缀;每次提交commit后Go都会默认生成一个伪版本号。两种版本的举例如下:

image.png

特殊标识符

下面我们再来看下依赖单元中的特殊标识符,首先是indirect后缀, 表示go.mod对应的当前模块,没有直接导入该依赖模块的包,也就是非直接依赖,表示间接依赖,例如:

image.png

下一个常见是的是incompatible,主版本2+模块会在模块路径增加/vN后缀,这能让go module按照不同的模块来处理同一个项目不同主版本的依赖。由于go module是1.11实验性引入的所以这项规则提出之前已经有一些仓库打上了2或者更高版本的tag了,为了兼容这部分仓库,对于没有go.mod文件并且主版本在2或者以上的依赖,会在版本号后加上+incompatible后缀

image.png

依赖分发

对于依赖分发,也就是从哪里下载,如何下载的问题。
1.github是比较常见给的代码托管系统平台,而Go Modules系统中定义的依赖,最终可以对应到多版本代码管理系统中某一项目的特定提交或版本,这样的话,对于go.mod中定义的依赖,则直接可以从对应仓库中下载指定软件依赖,从而完成依赖分发。
2.但直接使用版本管理仓库下载依赖,存在多个问题:
(1)首先无法保证构建确定性。软件作者可以直接代码平台增加/修改/删除软件版本,导致下次构建使用另外版本的依赖,或者找不到依赖版本。
(2)无法保证依赖可用性:依赖软件作者可以直接在代码平台删除软件,导致依赖不可用。这样会大幅增加第三方代码托管平台压力。
(3)而Go Proxy就是解决这些问题的方案,Go Proxy是一个服务站点, 它会缓存源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用。从而实现了供"immutability"和"available"的依赖分发;使用Go Proxy之后,构建时会直接从Go Proxy站点拉取依赖。
(4)下面讲一下go proxy的使用,Go Modules通过GOPROXY环境变量控制如何使用Go Proxy。GOPROXY是一个Go Proxy站点URL列表,可以使用"diret"表示源站。对于示例配置,整体的依赖寻址路径,会优先从proxy1下载依赖,如果proxy1不存在,然后从proxy2寻找,如果proxy2,中不存在则会回源到源站直接下载依赖,缓存到proxy站点中。

image.png

工具 go get

image.png

工具 go mod

image.png