dot/docs/SEMVER.md
2023-12-13 18:43:53 +08:00

226 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

语义化版本 2.0.0
===
摘要
---
版本格式:主版本号.次版本号.修订号,版本号递增规则如下:
1. 主版本号:当你做了不兼容的 API 修改,
2. 次版本号:当你做了向下兼容的功能性新增,
3. 修订号:当你做了向下兼容的问题修正。
先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
简介
---
在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。
在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。当你项目的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。
作为这个问题的解决方案之一,我提议用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开放源码软件所广泛使用的惯例所设计。为了让这套理论运作,你必须先有定义好的公共 API。这可能包括文档或代码的强制要求。无论如何这套 API 的清楚明了是十分重要的。一旦你定义了公共 API你就可以透过修改相应的版本号来向大家说明你的修改。考虑使用这样的版本号格式X.Y.Z主版本号.次版本号.修订号)修复问题但不影响 API 时递增修订号API 保持向下兼容的新增及修改时,递增次版本号;进行不向下兼容的修改时,递增主版本号。
我称这套系统为“语义化的版本控制”,在这套约定下,版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。
语义化版本控制规范SemVer
---
以下关键词 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、SHOULD NOT、 RECOMMENDED、MAY、OPTIONAL 依照 RFC 2119 的叙述解读。
1. 使用语义化版本控制的软件必须MUST定义公共 API。该 API 可以在代码中被定义或出现于严谨的文档内。无论何种形式都应该力求精确且完整。
2. 标准的版本号必须MUST采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数且禁止MUST NOT在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须MUST以数值来递增。例如1.9.1 -> 1.10.0 -> 1.11.0。
3. 标记版本号的软件发行后禁止MUST NOT改变该版本软件的内容。任何修改都必须MUST以新版本发行。
4. 主版本号为零0.y.z的软件处于开发初始阶段一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。
5. 1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。
6. 修订号 Zx.y.Z `|` x > 0必须MUST在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。
7. 次版本号 Yx.Y.z `|` x > 0必须MUST在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须MUST递增。也可以MAY在内部程序有大量新功能或改进被加入时递增其中可以MAY包括修订级别的改变。每当次版本号递增时修订号必须MUST归零。
8. 主版本号 XX.y.z `|` X > 0必须MUST在有任何不兼容的修改被加入公共 API 时递增。其中可以MAY包括次版本号及修订级别的改变。每当主版本号递增时次版本号和修订号必须MUST归零。
9. 先行版本号可以MAY被标注在修订版之后先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须MUST由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成且禁止MUST NOT留白。数字型的标识符禁止MUST NOT在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。范例1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。
10. 版本编译信息可以MAY被标注在修订版或先行版本号之后先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须MUST由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成且禁止MUST NOT留白。当判断版本的优先层级时版本编译信息可SHOULD被忽略。因此当两个版本只有在版本编译信息有差别时属于相同的优先层级。范例1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。
11. 版本的优先层级指的是不同版本在排序时如何比较。
1. 判断优先层级时必须MUST把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较版本编译信息不在这份比较的列表中
2. 由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较。
例如1.0.0 < 2.0.0 < 2.1.0 < 2.1.1
3. 当主版本号次版本号及修订号都相同时改以优先层级比较低的先行版本号决定
例如1.0.0-alpha < 1.0.0
4. 有相同主版本号次版本号及修订号的两个先行版本号其优先层级必须MUST透过由左到右的每个被句点分隔的标识符来比较直到找到一个差异值后决定
1. 只有数字的标识符以数值高低比较
2. 有字母或连接号时则逐字以 ASCII 的排序来比较
3. 数字的标识符比非数字的标识符优先层级低
4. 若开头的标识符都相同时栏位比较多的先行版本号优先层级比较高
例如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
合法语义化版本的巴科斯范式语法
--------------------------------------------------
```
<valid semver> ::= <version core>
| <version core> "-" <pre-release>
| <version core> "+" <build>
| <version core> "-" <pre-release> "+" <build>
<version core> ::= <major> "." <minor> "." <patch>
<major> ::= <numeric identifier>
<minor> ::= <numeric identifier>
<patch> ::= <numeric identifier>
<pre-release> ::= <dot-separated pre-release identifiers>
<dot-separated pre-release identifiers> ::= <pre-release identifier>
| <pre-release identifier> "." <dot-separated pre-release identifiers>
<build> ::= <dot-separated build identifiers>
<dot-separated build identifiers> ::= <build identifier>
| <build identifier> "." <dot-separated build identifiers>
<pre-release identifier> ::= <alphanumeric identifier>
| <numeric identifier>
<build identifier> ::= <alphanumeric identifier>
| <digits>
<alphanumeric identifier> ::= <non-digit>
| <non-digit> <identifier characters>
| <identifier characters> <non-digit>
| <identifier characters> <non-digit> <identifier characters>
<numeric identifier> ::= "0"
| <positive digit>
| <positive digit> <digits>
<identifier characters> ::= <identifier character>
| <identifier character> <identifier characters>
<identifier character> ::= <digit>
| <non-digit>
<non-digit> ::= <letter>
| "-"
<digits> ::= <digit>
| <digit> <digits>
<digit> ::= "0"
| <positive digit>
<positive digit> ::= "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"
```
为什么要使用语义化的版本控制
---
这并不是一个新的或者革命性的想法实际上你可能已经在做一些近似的事情了问题在于只是近似还不够如果没有某个正式的规范可循版本号对于依赖的管理并无实质意义将上述的想法命名并给予清楚的定义让你对软件使用者传达意向变得容易一旦这些意向变得清楚弹性但又不会太弹性的依赖规范就能达成
举个简单的例子就可以展示语义化的版本控制如何让依赖地狱成为过去假设有个名为救火车的函数库它需要另一个名为梯子并已经有使用语义化版本控制的包当救火车创建时梯子的版本号为 3.1.0因为救火车使用了一些版本 3.1.0 所新增的功能你可以放心地指定依赖于梯子的版本号大于等于 3.1.0 但小于 4.0.0这样当梯子版本 3.1.1 3.2.0 发布时你可以将直接它们纳入你的包管理系统因为它们能与原有依赖的软件兼容
作为一位负责任的开发者你理当确保每次包升级的运作与版本号的表述一致现实世界是复杂的我们除了提高警觉外能做的不多你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包而无需推出新的依赖包节省你的时间及烦恼
如果你对此认同希望立即开始使用语义化版本控制你只需声明你的函数库正在使用它并遵循这些规则就可以了请在你的 README 文件中保留此页链接让别人也知道这些规则并从中受益
FAQ
---
### 在 0.y.z 初始开发阶段,我该如何进行版本控制?
最简单的做法是以 0.1.0 作为你的初始化开发版本并在后续的每次发行时递增次版本号
### 如何判断发布 1.0.0 版本的时机?
当你的软件被用于正式环境它应该已经达到了 1.0.0 如果你已经有个稳定的 API 被使用者依赖也会是 1.0.0 如果你很担心向下兼容的问题也应该算是 1.0.0 版了
### 这不会阻碍快速开发和迭代吗?
主版本号为零的时候就是为了做快速开发如果你每天都在改变 API那么你应该仍在主版本号为零的阶段0.y.z或是正在下个主版本的独立开发分支中
### 对于公共 API若即使是最小但不向下兼容的改变都需要产生新的主版本号岂不是很快就达到 42.0.0 版?
这是开发的责任感和前瞻性的问题不兼容的改变不应该轻易被加入到有许多依赖代码的软件中升级所付出的代价可能是巨大的要递增主版本号来发行不兼容的改版意味着你必须为这些改变所带来的影响深思熟虑并且评估所涉及的成本及效益比
### 为整个公共 API 写文档太费事了!
为供他人使用的软件编写适当的文档是你作为一名专业开发者应尽的职责保持项目高效的一个非常重要的部分是掌控软件的复杂度如果没有人知道如何使用你的软件或不知道哪些函数的调用是可靠的要掌控复杂度会是困难的长远来看使用语义化版本控制以及对于公共 API 有良好规范的坚持可以让每个人及每件事都运行顺畅
### 万一不小心把一个不兼容的改版当成了次版本号发行了该怎么办?
一旦发现自己破坏了语义化版本控制的规范就要修正这个问题并发行一个新的次版本号来更正这个问题并且恢复向下兼容即使是这种情况也不能去修改已发行的版本可以的话将有问题的版本号记录到文档中告诉使用者问题所在让他们能够意识到这是有问题的版本
### 如果我更新了自己的依赖但没有改变公共 API 该怎么办?
由于没有影响到公共 API这可以被认定是兼容的若某个软件和你的包有共同依赖则它会有自己的依赖规范作者也会告知可能的冲突要判断改版是属于修订等级或是次版等级是依据你更新的依赖关系是为了修复问题或是加入新功能对于后者我经常会预期伴随着更多的代码这显然会是一个次版本号级别的递增
### 如果我变更了公共 API 但无意中未遵循版本号的改动怎么办呢?(意即在修订等级的发布中,误将重大且不兼容的改变加到代码之中)
自行做最佳的判断如果你有庞大的使用者群在依照公共 API 的意图而变更行为后会大受影响那么最好做一次主版本的发布即使严格来说这个修复仅是修订等级的发布记住 语义化的版本控制就是透过版本号的改变来传达意义若这些改变对你的使用者是重要的那就透过版本号来向他们说明
### 我该如何处理即将弃用的功能?
弃用现存的功能是软件开发中的家常便饭也通常是向前发展所必须的当你弃用部分公共 API 你应该做两件事1更新你的文档让使用者知道这个改变2在适当的时机将弃用的功能透过新的次版本号发布在新的主版本完全移除弃用功能前至少要有一个次版本包含这个弃用信息这样使用者才能平顺地转移到新版 API
### 语义化版本对于版本的字符串长度是否有限制呢?
没有请自行做适当的判断举例来说长到 255 个字符的版本已过度夸张再者特定的系统对于字符串长度可能会有他们自己的限制
### “v1.2.3” 是一个语义化版本号吗?
v1.2.3 并不是一个语义化的版本号但是在语义化版本号之前增加前缀 v 是用来表示版本号的常用做法在版本控制系统中 version 缩写为 v 是很常见的比如`git tag v1.2.3 -m "Release version 1.2.3"` ,“v1.2.3 表示标签名称 1.2.3 是语义化版本号
### 是否有推荐的正则表达式用以检查语义化版本号的正确性?
有两个推荐的正则表达式第一个用于支持按组名称提取的语言PCRE[Perl 兼容正则表达式比如 PerlPHP R]、Python Go)。
参见<https://regex101.com/r/Ly7O1x/3/>
```
^(?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-]+)*))?$
```
第二个用于支持按编号提取的语言与第一个对应的提取项按顺序分别为major、minor、patch、prerelease、buildmetadata。主要包括 ECMA ScriptJavaScript、PCREPerl 兼容正则表达式,比如 Perl、PHP 和 R、Python 和 Go。
参见:<https://regex101.com/r/vkijKf/1/>
```
^(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-]+)*))?$
```
关于
---
语义化版本控制的规范是由 Gravatars 创办者兼 GitHub 共同创办者 [Tom Preston-Werner](http://tom.preston-werner.com) 所建立。
如果您有任何建议,请到 [GitHub 上提出您的问题](https://github.com/semver/semver/issues)。
许可证
---
[知识共享 署名 3.0 (CC BY 3.0)](http://creativecommons.org/licenses/by/3.0/)