依赖管理
依赖管理是软件工程中非常重要的一个问题,随着项目越来越庞大,需要有专门的依赖管理机制来理清项目依赖,减少依赖冲突,便于项目发布和依赖的升级。
依赖管理机制
对于每一种语言,当项目比较简单(比如只是写一个 Hello World 的应用)的时候,你不需要考虑依赖管理的问题。随着项目越来越复杂,你开始引用第三方库,那么问题来了:
- 如何保证依赖的版本信息,从而实现稳定的可复现的构建
- 同一个项目可能依赖同一个代码库的不同版本,如何保证它们不冲突
- 本项目可能依赖了几十个库,每个库也会有自己的依赖,如何检测库的依赖是否存在版本冲突?升级的时候怎么办?
早期的时候,我们会把项目和项目的依赖提交到一起。比如对于Java项目,我们直接把第三方依赖以jar包的形式存在代码库中。在Go语言中,我们早期也有Vendor机制,和Java类似会把第三方依赖和项目提交到一起,唯一的区别是提交的是依赖的源代码,而不是二进制库,比如 Kubernetes的vendor目录。
这种方式可以work,但是也存在着一系列的问题:
- 这种需要将第三方依赖和项目提交在一起并不优雅,冗余的代码存在使得项目臃肿不堪
- 同一个工程中存在同一个代码包的多个版本,这迟早带来问题
我们期待的是什么?项目的第三方依赖与项目不需要存在于同一个工程中,项目只需要声明其依赖的库的版本等元信息,构建系统可以自动的从第三方地址下载对应的依赖,从而实现可复现的构建。这个时候我们就需要依赖管理机制了,其核心是:
- 要有一种依赖库的命名规则,或者叫坐标(Coordinates)的定义规则,可以通过坐标准确找到依赖的库。
- 要有对应的配置文件规则,来描述和定义依赖。
- 要有中心仓库保存这些依赖库,以及依赖库的元数据(metadata),供使用方拉取。
- 还需要一个本地工具去解析这个配置文件,实现依赖的拉取。
现在,不同语言都有着自己的依赖管理工具,比如Java的Maven,NodeJS的 NPM,Rust的Cargo,Go的Module等。
语义化版本
在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。
在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。当你专案的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。
语义化版本 (Sematic Version) 是为了解决依赖地狱的一个解决方案,它将软件版本定义为以下格式 Major.Minor.Patch,如下图所示:
- Major主版本号:当你做了不兼容的 API 修改
- Minor次版本号:当你做了向下兼容的功能性新增,可以理解为Feature版本
- Patch修正号:当你做了向下兼容的问题修正,可以理解为Bug Fix版本
作为延伸,还可以将 pre-release tag(比如 -alpha,-beta或者是 -rc)和 build meta tag加到 Major.Minor.Patch 后面。备注下,这里的alpha版本也叫内部版本,beta版本也叫公测版本,rc版本即Release Candidate,正式版本的候选版本。
比如:1.0.0-alpha.0, 1.0.0-alpha.1, 1.0.0-beta.0, 1.0.0-rc.0, 1.0.p-rc.1 等版本。alpha, beta, rc后需要带上次数信息。
加入build信息后,一个可能的版本号形如:16.3.0-alpha.7926752
下面是 SemVer 规范定义中比较实用的版本发布准则:
- 标准的版本号必须采用XYZ的格式,并且X、Y 和 Z 为非负的整数,禁止在数字前方补零,版本发布需要严格递增。例如:1.9.1 -> 1.10.0 -> 1.11.0。
- 某个软件版本发行后,任何修改都必须以新版本发行。
- 1.0.0 的版本号用于界定公共 API。当你的软件发布到了正式环境,或者有稳定的API时,就可以发布1.0.0版本了。
- 版本的优先层级指的是不同版本在排序时如何比较。判断优先层级时,必须把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较。
锁定依赖版本
如前所述,我们需要有配置文件来描述和定义依赖,现代的依赖管理工具有两种方式:
- 指定依赖的特定版本,比如Java的Maven
- 配置文件分为两个部分,一个指定可接受的依赖的版本范围,另一个
lock file指定当前应用构建依赖的确切版本。这种在NPM,Cargo等工具中应用更为广泛。
常见的锁定配置文件有,Ruby 中的 Gemfile.lock,Node.js 中的 package-lock.json 或者 yarn.lock。无论你如何修改 package.json 的内容,它都会按照 package-lock.json 来安装依赖。我们可以通过版本控制系统(比如git)来管理依赖锁定配置文件,从而记录已经安装的依赖的确切版本。lock文件描述了生成的依赖树及其关系,以便于可重现安装依赖(即在后续的安装中能够生成相同的依赖树),而忽略依赖的更新。
菱形依赖
菱形依赖是依赖管理中一个常见的问题,以下图为例:项目A依赖于项目B和项目C,而项目B和项目C都依赖于项目D。当项目C的版本从1.0升级到1.1的时候,其依赖的项目D也需要升级到1.1,但是这个时候项目B仍然只依赖于项目D的1.0版本。这就导致你要目需要解决这个矛盾,要么项目D的两个版本都同时保留。
参考资料
-
No backlinks found.