依赖管理基础:依赖声明文件与版本号规范
依赖管理,说白了就是管好你项目里那些“借来的代码”。
我做SDK开发这些年,见过太多因为依赖没管好而翻车的案例。有的项目编译不过,有的运行时崩溃,有的甚至因为版本冲突导致整个团队加班三天。嗯,这些坑我都踩过。今天咱们就聊聊依赖管理的两个核心:声明文件和版本号规范。
一、依赖声明文件:项目的“购物清单”
依赖声明文件就像你去超市买东西前列的清单。没有它,你根本记不住买了什么、买了哪个牌子的、买了多少。
不同语言生态有各自的声明文件格式。我挑三个最常用的来讲:
1. Python:requirements.txt
Python的依赖管理,我个人习惯用 requirements.txt。它简单直接,就是一个文本文件,每行写一个包名和版本号。
# requirements.txt 示例
requests==2.28.1
numpy>=1.21.0, <1.24.0
flask~=2.2.0
pandas
这里有几个细节要注意:
==表示精确版本,锁定死>=和<表示版本范围~=是兼容版本,比如~=2.2.0表示 >=2.2.0 且 <2.3.0- 不写版本号,就装最新版——这其实很危险
2. JavaScript/Node.js:package.json
前端和Node.js项目用 package.json。它是个JSON文件,结构比requirements.txt复杂一些。
{
"name": "my-sdk",
"version": "1.0.0",
"dependencies": {
"axios": "^0.27.2",
"lodash": "~4.17.21",
"react": "18.2.0"
},
"devDependencies": {
"jest": "^29.0.0"
}
}
版本号前面的符号有讲究:
^表示兼容大版本,比如^0.27.2允许安装 0.27.x 的最新版~表示兼容小版本,比如~4.17.21只允许 4.17.x- 不写符号,就是精确版本
3. Java:pom.xml
Java生态用Maven,声明文件是 pom.xml。XML格式,看起来比前两个都啰嗦,但功能也更强。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>[1.7.0, 2.0.0)</version>
</dependency>
Maven的版本范围写法更灵活:
[1.7.0, 2.0.0)表示 >=1.7.0 且 <2.0.0(,1.0.0]表示 <=1.0.0- 不写范围,就是精确版本
二、语义化版本号:别让版本号“说谎”
版本号不是随便写的。你想想看,如果版本号不能反映变更的严重程度,用户怎么知道升级会不会出问题?
语义化版本(SemVer)就是来解决这个问题的。格式很简单:主版本号.次版本号.修订号。
| 位置 | 名称 | 什么时候加1 | 举例 |
|---|---|---|---|
| 第一位 | 主版本号 | 做了不兼容的API修改 | 1.0.0 → 2.0.0 |
| 第二位 | 次版本号 | 新增了功能,但向后兼容 | 1.0.0 → 1.1.0 |
| 第三位 | 修订号 | 做了bug修复,不改变API | 1.0.0 → 1.0.1 |
规则其实就三条:
- 主版本号变化:用户必须改代码才能用。比如我把某个函数的参数类型改了,这就是不兼容变更。
- 次版本号变化:用户可以直接升级,新功能不会破坏旧代码。比如我新增了一个函数,但旧的函数照常工作。
- 修订号变化:只是修bug,功能完全不变。用户应该无感升级。
三、预发布版本和构建元数据
除了标准的三段式,语义化版本还支持预发布版本和构建元数据。
1.0.0-alpha.1
2.0.0-beta.3+20231001
1.2.3-rc.2+build.123
预发布版本用连字符 - 连接,比如 alpha、beta、rc。这些版本的优先级低于正式版。比如 1.0.0-alpha 比 1.0.0 低。
构建元数据用加号 + 连接,比如 +20231001。它不影响版本优先级,只是用来标识构建信息。
四、版本范围与依赖解析
实际项目中,依赖关系往往是网状结构。A依赖B,B依赖C,C又依赖D。这时候版本声明就变得复杂了。
举个例子:
你的SDK依赖:
- library-a: ^1.2.0
- library-b: ^2.0.0
library-a依赖:
- library-c: >=1.0.0, <2.0.0
library-b依赖:
- library-c: >=1.5.0, <3.0.0
这时候依赖解析器会找一个 library-c 的版本,同时满足两个条件:>=1.0.0且<2.0.0,同时>=1.5.0且<3.0.0。最终结果是 >=1.5.0且<2.0.0。
如果找不到满足所有条件的版本,就会报依赖冲突。这就是为什么我强调要精确控制版本范围。
五、知识体系总览
下面这张图总结了依赖管理的核心知识结构:
依赖管理看起来是小事,但做不好真的会出大问题。我个人习惯是:每个SDK发布前,都会花10分钟检查一遍依赖声明文件和版本号。这10分钟,往往能省下后面几天的排查时间。
记住一句话:依赖声明文件是给机器看的,也是给人看的。写清楚、写规范,对谁都好。