这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记
1.Golang依赖管理演进历程
Golang作为一门高效的服务端语言,和Java一样具备依赖管理体系,和Maven不同的事,Golang一直没有独立的版本管理平台,而是借助Github作为其版本管理/托管的主要平台。Golang的依赖管理发展历程如下:
go install/go get阶段(2010年至2012年)
在Golang诞生的初期,使用的依赖管理方式是通过go install及go get命令,先将程序所依赖的代码库下载到本地,并通过import引用这些库,来达到构建程序的目的。这会导致一个很严重的问题,因为无法指定版本,导致所有的Golang项目都只能依赖某个下载好的代码库。
godep阶段(2013.09)
godep提供了一个依赖文件,记录所有依赖具体的版本和路径,编译时将依赖下载到workspace中,然后切换到指定版本,并设置GOPATH访问(解决go get没有版本管理的缺陷)
gopkg.in阶段(2014.03)
通过import路径中添加版本号来标示不同版本,而实际代码存放于github中,go通过redirect获取代码。例如(import gopkg.in/yaml.v1,实际代码地址为:github.com/go-yaml/yam…)
vendor阶段(2015.06)
Go 1.5版本引入vendor(类似godep),存放于项目根目录,编译时优先使用vendor目录,之后再去GOPATH,GOROOT目录查找(解决GOPATH无法管控依赖变更和丢失的问题)
dep阶段(2016.08)
dep期望统一Go依赖管理,虽然提供了兼容其它依赖管理工具的功能,但是本质上还是利用GOPATH和vendor解
决依赖管理
Go Modules阶段(2018.08)
Go 1.11发布的官方依赖管理解决方案,并最终统一了Go依赖管理。Go Modules以语义版本化和最小版本选择为核心,相比dep更具稳定性;同时也解决了vendor代码库依赖过于庞大,造成存储浪费的问题。Go Modules也是目前Golang程序开发中最常用的包依赖解决方案。
2.Go Modules
关于Go Modules的安装以及使用这里不再赘述,可以参考网上的教程进行操作,本文主要想讨论的是Go Modules的包管理原理,以更好地理解其工作过程,解决工作中遇到的关于Go依赖相关的错误问题。
版本语义化
Go Modules提出了版本语义化规范,提倡通过Go Modules管理的包都应遵循这种版本命名规范: 主版本号(major).次版本号(minor).修订号(patch),如v1.2.3就是一个版本语义化的命名,要求如下:
主版本号:做了不兼容的 API修改 次版本号:向下兼容的功能性新增或改动 修订号:向下兼容的问题修正等 根据这种定义, 主版本号改动即视为不兼容之前大版本, 包管理机制也要使用不同主版本的包得到正确管理, 而次版本号以下的改动视为兼容, 会做合并。
依赖构建过程
最大版本选择 Go modules依赖构建的过程是依赖图深度优先遍历的过程,首先会以第一层直接依赖作为起始遍历节点,假设一个go modules依赖情况如下,那么会从A 1开始遍历,找到A 1的go.mod文件中定义的包名和版本号:B 1.2以及C 1.2,接着顺着B 1.2的go.mod找到D 1.3,借助C 1.2的go.mod找到D 1.4,然后找到E 1.2,就可以得到依赖关系:
将遍历得到的依赖关系组合成rough list(基本的依赖关系),可以看出D包同时有D 1.3和D 1.4两个版本的包被依赖了,根据版本语义化原则,v1.3和v1.4只是做了次版本号的更新,是可以向下兼容的,所以这里会取该包所依赖的“最新版本”D 1.4作为最终依赖关系(final list)。
依赖包升级过程
使用go get -u … 命令,这会导致整个项目的所有依赖都由上至下进行升级,全部依赖都会升级到最新的那个可获取的版本:
依赖包降级过程
对某个依赖包执行降级操作,如将D 1.3降级为D 1.2:go get D@1.2 ,那么首先会将高于D 1.2版本的依赖包删除,即删除D 1.3和D 1.4,然后进行回溯,对依赖于D 1.3的B 1.2进行删除,并降级为B 1.1,同时对依赖于D 1.4的C 1.2删除,降级为C 1.1,并对D1.3和D1.4依赖的下游E1.2进行删除,所以此时得到的final list 为[A1,B1.1,C1.1,D1.2,E1.1]。所以对指定包执行降级操作会影响到该包的直接上下游,会把相关的依赖也进行降级和删除。