语义化版本--2.0.0(Semantic Versioning)
2026/8/31 19:10:34 网站建设 项目流程

概要

版本号格式:主版本号.次版本号.修订号(MAJOR.MINOR.PATCH),按以下规则递增:

  • MAJOR(主版本号):做了不兼容的API变更时递增
  • MINOR(次版本号):以向后兼容的方式新增功能时递增
  • PATCH(修订号):做向后兼容的Bug修复时递增

还可以在MAJOR.MINOR.PATCH基础上增加预发布版本标签与构建元数据作为扩展。

引言

软件依赖管理领域存在一个令人头疼的问题,叫做依赖地狱。系统越大,集成的软件包越多,你就越有可能某天陷入这个困境。
在依赖繁多的系统中,新版本发布很快就会变成噩梦:

  • 如果依赖版本约束过严,会出现版本锁:升级一个包,就要同步升级所有依赖它的包。
  • 如果依赖版本约束过松,就会出现版本泛滥:错误地假设可以兼容未来大量更高版本。
    版本锁、版本泛滥导致项目无法简单、安全地迭代,这就是依赖地狱。
    为此,我们提出一套简单的规则与约定,用来定义版本号如何分配、如何递增。这套规则参考业界已有的开源/闭源软件实践,但不完全照搬。
    使用这套机制,首先你需要定义公开API,可以写在文档里,也可以由代码本身强制约束。API必须清晰明确。定义好公开API之后,就通过版本号的变化来表达API的改动。
    版本格式记为X.Y.Z(主版本.次版本.修订号):
  • 不改动API,仅修复Bug → 修订号+1
  • 向后兼容,新增API功能 → 次版本号+1
  • API发生不兼容变更 → 主版本号+1
    我们称之为语义化版本(Semantic Versioning)。版本号的变化,直接传递底层代码的改动信息。

语义化版本规范(SemVer)

本文中MUST(必须)、MUST NOT(严禁)、REQUIRED(要求)、SHALL(应当)、SHALL NOT(不应)、SHOULD(建议)、SHOULD NOT(不建议)、RECOMMENDED(推荐)、MAY(可以)、OPTIONAL(可选)按照 RFC 2119 释义。

  1. 使用语义化版本的软件必须(MUST)声明公开API。API可以写在代码内,也可以仅存在于文档,建议(SHOULD)做到精确完整
  2. 正式版本格式**必须(MUST)**为X.Y.Z;X/Y/Z为非负整数,禁止(MUST NOT)前导零
    • X:主版本号
    • Y:次版本号
    • Z:修订号
      每段数字必须按数值递增。例:1.9.0 → 1.10.0 → 1.11.0
  3. 版本包一旦发布,该版本内容绝不允许(MUST NOT)修改;修改必须(MUST)发布为新版本。
  4. 0.y.z(主版本为0)用于开发初期:一切都可能随时改动,公开API不保证(SHOULD NOT)稳定。
  5. 1.0.0代表公开API正式定型。1.0.0之后版本号的变更完全由公开API的改动决定。
  6. 修订号 Z(x.y.Z,x>0):必须(MUST)做向后兼容的Bug修复时递增。Bug修复指修复错误行为的内部改动。
  7. 次版本号 Y(x.Y.z,x>0)
    • 新增向后兼容的公开API功能,必须(MUST)递增;
    • 公开API标记废弃,必须(MUST)递增;
    • 私有代码新增大量功能/优化,可以(MAY)递增;
    • 可以(MAY)包含修订级别的修复;
    • 次版本号增加时,修订号必须(MUST)重置为0。
  8. 主版本号 X(X.y.z,X>0)
    • 公开API引入不兼容变更,必须(MUST)递增;
    • 可以(MAY)同时包含次版本、修订级别的改动;
    • 主版本号增加时,次版本、修订号必须(MUST)全部重置为0。

9.预发布版本

在版本核心后面加连字符-,后跟若干点分隔的标识符,代表预发布版本。

  • 标识符只能使用ASCII字母、数字、横杠[0-9A-Za-z‑];不能为空;数字标识符禁止前导零。
  • 预发布版本优先级低于对应的正式版本,表示版本不稳定,不一定满足正式版的兼容性约定。
    示例:
    1.0.0-alpha1.0.0-alpha.11.0.0-0.3.71.0.0-x.7.z.92

10.构建元数据

在版本/预发布版本后加加号(+),后跟点分隔标识符,代表构建元数据。

  • 标识符字符约束同上;不能为空。
  • 版本优先级比较时忽略构建元数据;仅构建元数据不同的两个版本,优先级相等。
    示例:
    1.0.0-alpha+0011.0.0+201303131447001.0.0-beta+exp.sha.5114f85

11.版本优先级(版本排序规则)

优先级用于版本之间大小比较:
(1) 拆分出:主版本、次版本、修订号、预发布标识符;构建元数据不参与优先级计算
(2) 从左到右依次对比,找到第一个不同项决定大小。主、次、修订号按数值比较。

示例:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1

(3) 主、次、修订号完全相同时:预发布版本 < 正式版本。

示例:1.0.0‑alpha < 1.0.0

(4) 同主/次/修订号的两个预发布版本,按点分割标识符逐项对比:

  • 纯数字标识符:按数值比较
  • 含字母/横杠:按ASCII字典序比较
  • 数字标识符优先级低于非数字标识符
  • 前面所有标识符都相等,字段更多的预发布版本优先级更高。

完整示例:
1.0.0‑alpha < 1.0.0‑alpha.1 < 1.0.0‑alpha.beta < 1.0.0‑beta < 1.0.0‑beta.2 < 1.0.0‑beta.11 < 1.0.0‑rc.1 < 1.0.0

BNF语法(语义化版本文法)

<validsemver>::=<versioncore>|<versioncore>"-"<pre-release>|<versioncore>"+"<build>|<versioncore>"-"<pre-release>"+"<build><versioncore>::=<major>"."<minor>"."<patch><major>::=<numericidentifier><minor>::=<numericidentifier><patch>::=<numericidentifier><pre-release>::=<dot-separatedpre-releaseidentifiers><dot-separatedpre-releaseidentifiers>::=<pre-releaseidentifier>|<pre-releaseidentifier>"."<dot-separatedpre-releaseidentifiers><build>::=<dot-separatedbuildidentifiers><dot-separatedbuildidentifiers>::=<buildidentifier>|<buildidentifier>"."<dot-separatedbuildidentifiers><pre-releaseidentifier>::=<alphanumericidentifier>|<numericidentifier><buildidentifier>::=<alphanumericidentifier>|<digits><alphanumericidentifier>::=<non-digit>|<non-digit><identifiercharacters>|<identifiercharacters><non-digit>|<identifiercharacters><non-digit><identifiercharacters><numericidentifier>::= "0" |<positivedigit>|<positivedigit><digits><identifiercharacters>::=<identifiercharacter>|<identifiercharacter><identifiercharacters><identifiercharacter>::=<digit>|<non-digit><non-digit>::=<letter>| "-"<digits>::=<digit>|<digit><digits><digit>::= "0" |<positivedigit><positivedigit>::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"<letter>::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"

为什么要用语义化版本?

这并不是什么全新的理念,很多人已经在做类似的事。但“差不多”是不够的。没有正式规范约束,版本号对依赖管理几乎没有意义。
明确定义语义化版本之后,你可以向使用者清晰传递版本意图,从而写出松紧适度的依赖约束。
举个例子:
假设有库Firetruck,依赖包Ladder。开发Firetruck时,Ladder版本为3.1.0,用到了3.1.0新增的API。
那依赖就可以声明为>=3.1.0 <4.0.0
后续Ladder发布3.1.13.2.0,都可以安全升级,不会破坏上层库。
当然现实世界复杂,你依然需要做验证。但语义化版本提供一套合理的发布、升级逻辑,避免大量依赖包被迫跟着改版本,节省大量时间。
想要使用语义化版本,只需要声明遵循该规范,并且照规则执行。可以在README中附上官网链接,方便其他人了解。

FAQ–常见问题

0.y.z开发阶段版本怎么处理?

最简单:初始版本0.1.0,每次发布递增次版本号。

什么时候发布1.0.0?

  • 软件已经投入生产环境使用;
  • 用户已经依赖这套稳定API;
  • 你开始大量关心向后兼容性。
    满足以上任意情况,就应该升级到1.0.0

会不会阻碍快速迭代?

0.y.z版本就是用来快速迭代的。如果API天天改动,就保持0.y.z,或者开分支开发下个大版本。

微小的不兼容改动也要升主版本号,版本号会不会飙升到42.0.0?

这考验开发者的设计与预判。对大量使用者的包,不兼容变更不能随意做。主版本号强制提升,会迫使你评估变更的代价与收益。

完整记录公开API文档工作量太大?

作为面向外部使用者的软件开发者,写好文档是你的责任。管理复杂度是项目高效运转的关键;如果没人知道哪些接口可以安全调用,项目很难维护。长期来看,语义化版本 + 清晰API文档,让所有人都更省心。

不小心在次版本发布里引入不兼容变更怎么办?

意识到问题后,立刻修复、发布新次版本恢复兼容性。绝对不能修改已经发布的旧版本。视情况记录问题版本,告知使用者。

只更新内部依赖,公开API没变,版本怎么变?

公开API没改动,属于兼容变更。要看更新依赖是为了修复Bug,还是引入新能力:修复Bug走修订号;引入新代码/新能力走次版本号。上层项目自己处理依赖冲突。

补丁版本错误地引入破坏性API变更怎么办?

自行权衡。如果受影响用户极多,回滚行为冲击很大,哪怕严格来说属于Bug修复,也直接发布主版本。语义化版本核心是向用户传递变更的意义

如何处理API废弃?

  1. 更新文档告知用户;
  2. 次版本发布中标记废弃;
  3. 至少保留一个带废弃标记的次版本,再在大版本中彻底删除接口,给用户充足迁移时间。

版本字符串长度有限制吗?

规范本身没有限制,但要合理。例如255字符就过长;部分系统会自带限制。

v1.2.3是语义化版本吗?

不是。v只是版本标记前缀。例如git标签v1.2.3,标签名带v,真正的语义化版本是1.2.3

校验SemVer的正则表达式?

  1. 支持命名分组(PCRE、Python、Go等)
^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
  1. 数字捕获分组,兼容JS等ECMAScript环境
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$

关于

语义化版本规范原始作者:Tom Preston‑Werner(Gravatar发明者、GitHub联合创始人)。

如果你需要,我可以帮你整理一份可直接复制的SemVer速查表,方便开发查阅。

参考

https://semver.org/spec/v2.0.0.html 语义化版本规范
https://www.doubao.com 豆包AI翻译

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询