本文根据《语义化版本 2.0.0》一文稍作修改。
摘要
版本格式
主版本号.次版本号.修订版本号
版本号递增规则
主版本号:做了不兼容的API修改;
次版本号:做了向下兼容的功能性新增;
修订号:做了向下兼容的问题修正。
先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
API:(Application Programming Interface,应用程序接口)是一些预先定义的接口(比如函数、HTTP接口),或指软件系统中不同组成部分衔接的约定。目的是提供应用程序以及开发人员基于某软件或硬件得以访问一组例程的能力。
简介
用一组简单的规则及条件来约束版本号的配置和增长。需要定义好公共的API,可以通过文件定义或代码强制要求来实现。这样可以修改相应的版本号来说明修改。
采用以下格式:
X.Y.Z(主版本号.次版本号.修订号)
修复问题但不影响API时,递增修订号;API保持向下兼容的新增及修改时,递增次版本号;进行不向下兼容的修改时,递增主版本号。
语义化版本控制规范(SemVer)
RFC(Request for Comments,意见征求稿),涵盖了互联网的各种标准。RFC 2119讲的是在RFC中用于指示需求级别的关键字,包括“MUST”,“MUST NOT”,“REQUIRED”,“SHALL”,“SHALL NOT”,“SHOULD”,“SHOULD NOT”, “RECOMMENDED”,“MAY” 以及 “OPTIONAL”。
使用语义化版本控制的软件必须(MUST)定义公共API。该API可以在代码中被定义或出现于严谨的文件内。无论何种形式都应该力求精确且完整。
标准的版本号必须(MUST)采用X.Y.Z的格式,其中X、Y和Z为非负的整数,且禁止(MUST NOT)在数字前方补零。X是主版本号、Y是次版本号、Z是修订号。每个元素必须(MUST)以数值来递增。例如:1.9.1-->1.10.1-->1.11.0。
标记版本号的软件发行后,禁止(MUST NOT)改变该版本软件的内容。任何修改都必须(MUST)以新版本发行。
主版本号为零(0.Y.Z)的软件处于开发初始阶段,一切都有可能随时被改变。这样的公共API不应该被视为稳定版。
1.0.0的版本号用于界定公共API的形成。这一版本后所有的版本号更新都基于公共API及其修改内容。
修订号Z(X.Y.Z|x>0)必须(MUST)在只做了向下兼容的修正时才递增。这里指的是针对不正确的结果而进行的内部修改。
次版本号Y(X.Y.Z|X>0)必须(MUST)在有向下兼容的新功能出现时递增。在任何公共API的功能被标记为弃用时也必须(MUST)递增。也可以(MAY)在内部程序有大量新功能或改进被加入时递增,其中可以(MAY)包括修订级别的改变。每当次版本号递增时,修订号必须(MUST)归零。
主版本号X(X.Y.Z|X>0)必须(MUST)在有任何不兼容的修改被加入公共API时递增。其中可以(MAY)包括次版本号及修订级别的改变。每当版本号递增时,次版本号和修订号必须(MUST)归零。
先行版本号可以(MAY)被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由ASCII字母数字和连接号[0-9A-Za-z]组成,且禁止(MUST NOT)留白。数字型的标识符禁止(MUST NOT)在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。例如:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.37、1.0.0-x.7.z.92。
版本编译元数据可以(MAY)被标注在修订版或先行版本号之后,先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由ASCII字母数字和连接号[0-9A-Za-z]组成,且禁止(MUST NOT)留白。当判断版本的优先层级时,版本编译元数据可(SHOLUD)被忽略。因此当两个版本只有在版本编译元数据有差别时,属于相同的优先层级。例如:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。
版本的优先层级:指的是不同版本在排序时如何比较。判断优先层级时,必须(MUST)把版本依序拆分为主版本号、次版本号、修订号以及先行版本号后进行比较(版本编译元数据不在这份比较的列表中)。由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较,例如1.0.0<2.0.0<2.1.0<2.1.1。当主版本号、次版本号及修订号的两个先行版本号,其优先层级必须(MUST)通过由左到右的每个被句点分隔的标识符来比较,直到找到一个差异值后决定:只有数字的标识符以数值高低比较,有字母或连接号时则逐字以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。
为什么要使用语义化的版本控制
这并不是一个新的或者革命性的想法。实际上,你可能已经在做一些近似的事情了。问题在于只是“近似”还不够。如果没有某个正式的规范可寻,版本号对于依赖的管理并无实质意义。将上述的想法命名并给予清楚的定义,让你对软件使用这传达意向变得容易。一旦这些意向变得清楚,弹性(但又不会太弹性)的依赖规范就能达成。
例如:可以展示语义化的版本控制如何让”依赖地狱“成为过去。假设这有个名为“救火车”的函式库,它需要另一个名为“梯子”并已经有使用语义化版本控制的包。当“救火车”创建时,“梯子”的版本号为3.1.0。因为“救火车”使用了一些版本3.1.0中的新增的功能,你可以放心地指定依赖于“梯子”的版本号大于等于3.1.0但小于4.0.0。这样,当“梯子”版本3.1.1和3.2.0发布时,你可以直接将它们纳入你的包管理系统,因为它们能与原有依赖的软件兼容。
作为一位负责任的开发者,你理当确保每次包升级的运作与版本号的表述一致。现实世界是复杂的,我们除了提高警觉以外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包,而无需推出新的依赖包,节省你的 时间及烦恼。
如果你对此认同,希望立即开始使用语义化版本控制,你只需声明你的函数库正在使用它并遵循这些规则就可以了。请在你的README文件中保留此页链接,让别人也知道这些规则并从中受益。
问答及建议
-
在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时,应该:
更新文件让使用者知道这个改变。
在适当的时机将弃用的功能通过新的次版本号发布。在新的主版本完全移除弃用功能,至少要有一个次版本包含这个弃用信息看,这样使用者才能平顺地转移到新版。
-
语义化版本对于版本的字串长度是否有限制?
没有限制,请自行做适当的判断。举例来说,长度为255个字节的版本过度夸张。再者,特定的系统对于字串长度可能会有它们自己的限制。