软件开发是一个不断迭代累加的过程,版本号被用来标识不同版本经常出现在用户和开发者眼前。Semantic Versioning 是一个广泛使用的版本号标识方法,它使用Major.Minor.Patch的形式,修补bug只影响版本号的patch部分,添加或者改变向后兼容的API会增加版本号的minor部分,如果向后不兼容的API改变了就要增加major部分。用户可以通过 Semantic Versioning 定义的版本号知道当前版本添加内容的兼容性,减少了软件依赖项管理的时间精力。

简介

软件开发过程中,随着软件系统越来越庞大,软件中集成的包会越来越多,可能突然有一天你发现自己掉入了“依赖项的地狱”。

在一个包含很多依赖项的软件中,发布新版本到一定数量之后会带来很多问题。如果依赖项明细非常紧密具体,你就有可能遇到“版本锁”(升级新版本软件必须同时发布所有依赖项的版本);如果依赖项明细非常模糊松散,你很可能会遇到“版本混乱”(你以为未来能够兼容的版本未必兼容)。“依赖项的地狱”就是你遇到了版本锁或者版本混乱,让你的项目不能轻松地继续发布新版本。

解决这个问题的方法是使用使用一套简单的规则来要求版本号如何赋值和增加,这套系统被称为“Semantic Versioning”,首先需要定义一个公共的API,它的文档或者代码约束了它的用法和功能,这时它需要一个版本号,后续对这个API的改变会增加版本号数字。Semantic Versioning(也被称为SemVer)使用X.Y.Z(Major.Minor.Patch)的形式,修补bug只影响版本号的patch部分,添加或者改变向后兼容的API会增加版本号的minor部分,如果向后不兼容的API改变了就要增加major部分。

为什么使用 Semantic Versioning

版本号不是一个特别革命性的想法。在开发和编写中,自然就会想到给不同的版本编号。但是只是想到编号还不够好,如果没有一个统一的标准明细,版本号在依赖项管理的时候就是没有用的。通过将这个想法通过一个名称和清晰的定义来实现,这就成了软件开发者和用户之间沟通的渠道。

除了 Semantic Versioning,还有其它版本号的规则,比如 Assembly Versioning ,但是语义版本号是目前更加流行普遍的解决方案。

Semantic Versioning 能够解决“依赖项地狱”的一个简单例子就是,假设有一个库教“救火云梯”,它需要一个版本的包叫“梯子”,当“救火云梯”被开发出来的时候,“梯子”的版本号是3.1.0,当“救火云梯”被开发出来的时候你可以知道3.1.0版本及以上的“梯子”能够作为依赖项,但是必须低于4.0.0。当“梯子”的版本从3.1.1升级到3.2.0的时候,你可以发布它们到包管理系统,他们会和已有的依赖软件兼容。

负责人的开发者会验证它的包能够像宣传的那样工作,现实世界是复杂混乱的,我们能做的就是小心谨慎。你可以使用 Semantic Versioning 来作为一个方法来保证包升级的时候依赖项正常工作,从而节省时间精力。

简单的例子

符合 Semantic Versioning 的版本号使用 MAJOR.MINOR.PATCH 格式,在增加数字的时候遵循下面的规则:

  • MAJOR 数字在出现向后不兼容的API变动时候增加;
  • MINOR 数字在增加向后兼容的功能的时候增加;
  • PATCH 数字在修改向后兼容的BUG的时候增加。

  • 0.1.0 SemVer的第一个版本从这里开始,而不是0.0.1
  • 0.2.0 以0作为Major部分的版本号都是开发版本,作为给开发者参考使用
  • 0.2.1 修复了之前一个版本的bug
  • 0.3.1 添加了一个新功能
  • 1.0.0 这是第一个稳定版本
  • 1.1.0 添加了一个新功能
  • 2.0.0 破坏了原来的API
  • 2.1.0 继续添加了新功能

更加复杂的情况

额外的 label,pre-release 和 build metadata 可以作为扩展添加在 MAJOR.MINOR.PATCH 的后面。更加复杂的语义版本号可以由五个部分组成 主版本号、次版本号、补丁号、预发布版本标签 和 构建号。例如:

  • 2.1.0-alpha pre-release可以用来提醒这是一个用于测试的版本,在SemVer中使用一个-符号加上符号或者数字,比如alpha,beta
  • 2.1.0-alpha.1B pre-release加上输入表示这是一个不同的pre-release版本
  • 2.1.0-alpha.1B+amy-72a3e 有的时候希望在版本号中显示关于这个版本构建的metadata,比如例如是谁构建了这个版本,用了什么机器,在什么时间,这个版本的checksum,在SemVer中可以使用一个+加上后续的构建信息
  • 2.1.0-beta+exp.sha.7214g8e 一个符号为beta的pre-release版本
  • 2.1.1+20220111 也可以只包含构建信息,不包含pre-releaes部分

Semantic Versioning 规则明细

  1. 使用 Semantic Versioning 的软件必须声明一个公共的API,这个API可以由代码本身决定也可以由文档来约束。它一旦定义了,应该是准确而全面的。

  2. 正常的版本号必须采用 X.Y.Z 的形式,X,Y和Z都是非负的整数,而且不同前面有0。X是major版本,Y是minor版本,Z是patch版本。每个元素必须数值增加,比如:1.9.0 -> 1.10.0 -> 1.11.0。

  3. 一旦一个版本的包发布了,它的内容必须不能再改变,任何改变必须再发布一个新版本。

  4. Major 版本为0(比如0.y.z)的时候是用于初始开发的,任何改变可以发生在任何时候,这时候的公共API不应该被认为是稳定的。

  5. Version 1.0.0 定义了公共API。这个版本之后的版本号的变化依赖于这时的公共API和它的变化。

  6. 当一个向后兼容的bug被修复之后,patch 部分 Z (x.y.Z x > 0)必须增加。
  7. 当新的向后兼容的功能被添加到现有的公共API的时候,minor部分 Y (x.Y.z x > 0)必须增加。如果大量的功能和改进出现在私有代码的时候,它可能增加。它可能包含patch层次的改变。当minor版本增加的时候,patch版本必须重置为0。
  8. 如果公共API中加入了向后不兼容的变化,major版本号X(X.y.z X > 0)必须增加。它可以包含minor和patch层次的变化,当major版本增加的时候,patch和minor版本号必须重置为0。
  9. pre-release版本可以使用一个-符号和一系列用.来分隔的标识符号添加在patch版本号后面。标识符号必须在ASCII字母和数字以及-号范围内。标识符号必须不为空。数字标识符号必须不能开头为0。pre-release相对正常版本号有更低的优先级。pre-release版本说明这个版本不是稳定的,不一定能够满足正常版本号想表示的兼容性要求。例如:1.0.0-alpha,1.0.0-alpha.1,1.0.0-0.3.7,1.0.0-x.7.z.92,1.0.0-x-y-z.-。

  10. Build metadata 可以添加到patch和pre-release版本号后面,通过一个加号和用.来分隔的标识符。标识符必须在ASCII字母和数字以及-的范围。标识符好不能为空。在判断版本优先级的时候build metadata必须被忽略。两个本吧如果只有build metadta变化的时候拥有一样的优先级。比如:1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85, 1.0.0+21AF26D3—-117B344092BD.

  11. 优先级影响是版本之间对比之后如何排序。

    1. 优先级必须通过分隔的major,minor,patch和pre-release标识符,并以此为顺序计算(build metadata不影响优先级)。

    2. 优先级通过从左到右的对比来决定,majro,minor,patch一直都是数值之间的对比。

      例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1

    3. 当major,minor和patch都相等时候,pre-release的版本相对于正常版本有更低的优先级:

      例如:1.0.0-alpha < 1.0.0

    4. 当两个pre-release的版本有一样的major,minor和patch版本号的时候,优先级必须由从左到右对比由.分隔的标识符,直到发现下面的不同来决定:

      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

FAQ

初始开发阶段如何处理版本号?

最简单的方法是从0.1.0开始后续增加minor版本。

何时知道能够发布1.0.0版本?

如果你的软件已经被用在产品中,那它可能已经是1.0.0;如果你的API已经稳定并且由用户依赖在它之上,你应该到了1.0.0;如果你要担心非常多的向后兼容问题,你可能已经到了1.0.0。

如果意外发布了一个不兼容的minor版本怎么办?

当你发现这个问题的时候,去解决这个问题然后再发布一个minor版本来恢复兼容性。即使遇到这样的问题,也不要去修改已经发布的版本。如果可以的话在文档中标注出有问题的版本来提醒用户。

如何处理deprecate功能?

当你在public API中需要遗弃一些老旧部分,你应该做两件事情:1)更新文档让用户知道这里的变化;2)开发新的minor版本来替代老旧部分。在你完全移除旧功能到新的major版本的时候,应该至少有一个minor版本能够包含老旧部分,这样用户能够顺利转移过去。

参考