语义版本2.0.0

摘要

给定版本号MAJOR.MINOR.PATCH,增加:

  1. 当您进行不兼容的API更改时的主要版本,
  2. MINOR版本,当您以向后兼容的方式添加功能时,以及
  3. 向后兼容的bug修复程序时的PATCH版本。

预发布和构建元数据的其他标签可作为MAJOR.MINOR.PATCH格式的扩展名使用。

介绍

在软件管理的世界里,有一个可怕的地方,叫做“依赖地狱”。系统规模越大,集成到软件中的软件包越多,有一天,您越有可能在这种绝望中找到自己。

在具有许多依赖性的系统中,发布新的软件包版本可能很快成为噩梦。如果依赖性说明太严格,则存在版本锁定的危险(无法升级软件包而不必释放每个依赖性软件包的新版本)。如果对依赖关系的定义过于宽松,您将不可避免地被版本混杂(假设与更多合理版本的将来版本兼容)所困扰。当版本锁定和/或版本混杂使您无法轻松,安全地将项目向前移动时,依赖地狱就在您身边。

作为此问题的解决方案,我提出了一组简单的规则和要求,这些规则和要求规定了如何分配和递增版本号。这些规则基于但不一定限于在封闭式和开放源代码软件中都使用的预先存在的普遍惯例。为了使该系统正常工作,您首先需要声明一个公共API。这可能包含文档或由代码本身强制执行。无论如何,此API必须清晰准确。识别公共API后,您将以版本号的特定增量传达对它的更改。考虑XYZ的版本格式(Major.Minor.Patch)。错误修复不影响API会增加补丁程序版本,向后兼容的API添加/更改会增加次要版本,而向后不兼容的API更改会增加主版本。

我将此系统称为“语义版本控制”。在这种方案下,版本号及其更改方式传达了有关底层代码以及从一个版本修改到另一个版本的含义。

语义版本规范(SemVer)

本文档中的关键字“必须”,“不得”,“必须”,“应”,“应禁止”,“应”,“不应”,“推荐”,“可以”和“可选”是按照RFC 2119中的描述进行解释

  1. 使用语义版本控制的软件必须声明一个公共API。该API可以在代码本身中声明,也可以严格存在于文档中。但是,它应该做到精确而全面。

  2. 普通版本号必须采用XYZ格式,其中X,Y和Z是非负整数,并且不得包含前导零。X是主要版本,Y是次要版本,Z是修补程序版本。每个元素必须按数字增加。例如:1.9.0-> 1.10.0-> 1.11.0。

  3. 一旦发布了版本化的软件包,就不得修改该版本的内容。任何修改都必须作为新版本发布。

  4. 主要版本零(0.yz)用于初始开发。任何时候都可能改变。公共API不应被认为是稳定的。

  5. 1.0.0版定义了公共API。在此发行版之后,版本号的递增方式取决于此公共API及其更改方式。

  6. 如果仅引入了向后兼容的错误修复,则必须增加补丁版本Z(xyZ | x> 0)。错误修正定义为修正错误行为的内部变更。

  7. 如果将新的向后兼容功能引入了公共API,则必须增加次要版本Y(xYz | x> 0)。如果任何公共API功能标记为已弃用,则必须递增。如果在私有代码中引入了实质性的新功能或改进,则可以增加它。它可能包括补丁程序级别的更改。当次要版本增加时,补丁版本必须重置为0。

  8. 如果向公共API引入了任何向后不兼容的更改,则必须增加主版本X(Xyz | X> 0)。它可能还包括次要和补丁级别的更改。当主要版本增加时,补丁和次要版本必须重置为0。

  9. 可以通过在补丁版本之后紧跟一个连字符和一系列由点分隔的标识符来表示一个预发行版本。标识符只能包含ASCII字母数字和连字符[0-9A-Za-z-]。标识符不得为空。数字标识符不得包含前导零。预发行版本的优先级低于关联的普通版本。预发行版本表示该版本不稳定,可能无法满足其关联的正常版本所表示的预期兼容性要求。示例:1.0.0-alpha,1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。

  10. 生成元数据可以通过在补丁或预发行版本之后立即加一个加号和一系列用点分隔的标识符来表示。标识符只能包含ASCII字母数字和连字符[0-9A-Za-z-]。标识符不得为空。确定版本优先级时,必须忽略构建元数据。因此,只有版本元数据不同的两个版本具有相同的优先级。例如:1.0.0-alpha + 001、1.0.0 + 20130313144700、1.0.0-beta + exp.sha.5114f85。

  11. 优先级是指订购时如何比较版本。优先级必须通过按以下顺序将版本分为主要,次要,补丁和预发布标识符来计算(Build元数据不优先)。由左至右比较这些标识符中的每个标识符时,优先级由第一个差异确定,如下所示:主版本,次版本和补丁版本始终以数字方式进行比较。示例:1.0.0 <2.0.0 <2.1.0 <2.1.1。当主要,次要和补丁程序相等时,预发行版本的优先级低于普通版本。例如:1.0.0-alpha <1.0.0。具有相同主要,次要和补丁程序版本的两个预发行版本的优先级,必须通过从左到右比较每个点分隔的标识符来确定,直到发现差异为止,如下所示:对仅由数字组成的标识符进行数字比较,对具有字母或连字符的标识符按ASCII排序顺序进行词法比较。数字标识符的优先级始终比非数字标识符低。如果所有前面的标识符都相等,则较大的一组预发布字段的优先级高于较小的一组。示例:1.0.0-alpha <1.0.0-alpha1 <1.0.0-alpha beta <1.0.0-beta <1.0.0-beta2 <1.0.0-beta.11 <1.0.0- rc.1 <1.0.0。

适用于有效SemVer版本的Backus–Naur格式语法

<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"

为什么要使用语义版本控制?

这不是一个新的或革命性的想法。实际上,您可能已经做过一些类似的事情。问题是“关闭”还不够好。如果不遵守某种形式上的规范,版本号对于依赖关系管理基本上是无用的。通过给上述想法起一个名称和清晰的定义,可以很容易地将您的意图传达给您的软件用户。一旦明确了这些意图,就可以最终制定出灵活(但不太灵活)的依赖关系规范。

一个简单的示例将说明语义版本控制如何使依赖关系成为过去。考虑一个名为“救火车”的图书馆。它需要一个名为“ Ladder”的语义版本化的程序包。在创建Firetruck时,梯子的版本为3.1.0。由于Firetruck使用了3.1.0中首次引入的某些功能,因此您可以安全地将Ladder依赖关系指定为大于或等于3.1.0但小于4.0.0。现在,当Ladder版本3.1.1和3.2.0可用时,您可以将它们发布到软件包管理系统中,并且知道它们将与现有的从属软件兼容。

作为负责任的开发人员,您当然需要验证所有软件包升级是否均如广告中所述。现实世界是一个混乱的地方。除了保持警惕,我们无能为力。您可以做的是让语义版本控制为您提供一种发布和升级软件包的明智方法,而不必推出依赖软件包的新版本,从而节省了时间和麻烦。

如果所有这些听起来都令人满意,那么开始使用语义版本控制所需要做的就是声明您正在这样做,然后遵守规则。从您的自述文件链接到本网站,以便其他人知道这些规则并从中受益。

常问问题

我应该如何在0.yz初始开发阶段处理修订?

最简单的方法是从0.1.0开始初始开发版本,然后为每个后续版本增加次要版本。

我怎么知道什么时候发布1.0.0?

如果您的软件正在生产中使用,则可能已经是1.0.0。如果您拥有用户依赖的稳定API,则应为1.0.0。如果您担心向后兼容性,那么您应该已经是1.0.0。

这不妨碍快速开发和快速迭代吗?

零主要版本是关于快速发展的。如果您每天都在更改API,那么您应该还是处于0.yz版本中,或者处于一个单独的开发分支中,该版本致力于下一个主要版本。

如果即使对公共API的最小的向后不兼容的更改也需要重大的版本改进,我是否会很快以42.0.0版结束?

这是负责任的发展和远见的问题。具有很多相关代码的软件不应轻易引入不兼容的更改。升级必须产生的成本可能是巨大的。必须修改主要版本以发布不兼容的更改意味着您将考虑更改的影响,并评估所涉及的成本/收益比。

记录整个公共API太麻烦了!

作为专业开发人员,您有责任正确记录要供他人使用的软件。管理软件复杂性是保持项目高效的极其重要的一部分,如果没人知道如何使用软件或可以安全调用的方法,则很难做到这一点。从长远来看,语义版本控制以及对定义良好的公共API的坚持可以使所有人和所有事情顺利运行。

如果我不小心将向后不兼容的更改作为次要版本发布,该怎么办?

一旦意识到您已经违反了语义版本规范,请解决该问题并发布新的次要版本,以纠正该问题并恢复向后兼容性。即使在这种情况下,修改版本发布也是不可接受的。如果合适,请记录有问题的版本,并通知您的用户该问题,以便他们知道有问题的版本。

如果我在不更改公共API的情况下更新自己的依赖项,该怎么办?

这将被认为是兼容的,因为它不会影响公共API。明确依赖与您的程序包相同依赖关系的软件应具有自己的依赖关系规范,并且作者会注意到任何冲突。确定更改是补丁程序级别还是次要级别的修改取决于您是否更新了依赖性以修复错误或引入新功能。我通常希望为后者提供更多代码,在这种情况下,这显然是一个较小的级别增量。

如果我无意中以与版本号更改不兼容的方式更改了公共API(即代码错误地在补丁程序发行版中引入了重大突破)怎么办?

用你最好的判断。如果您将行为重新更改为公共API的预期会极大地影响您的受众,那么即使将修订严格地视为补丁程序发布,也可能最好执行主要版本发布。请记住,语义版本控制就是通过版本号的更改传达含义。如果这些更改对您的用户很重要,请使用版本号通知他们。

我应该如何处理过时的功能?

弃用现有功能是软件开发的正常部分,通常需要取得进步。弃用公共API的一部分时,应该做两件事:(1)更新文档以使用户知道更改,(2)发行新的次要版本并弃用。在完全删除新的主要版本中的功能之前,至少应有一个包含弃用版本的次要版本,以便用户可以平稳地过渡到新的API。

SemVer是否对版本字符串有大小限制?

不可以,但是要有很好的判断力。例如,一个255个字符的版本字符串可能会过大。同样,特定的系统可能会对字符串的大小施加自己的限制。

“ v1.2.3”是语义版本吗?

不,“ v1.2.3”不是语义版本。但是,在语义版本前面加上“ v”是一种常见的方式(英语),以表明它是版本号。在版本控制中通常可以将“版本”缩写为“ v”。示例: git tag v1.2.3 -m "Release version 1.2.3",在这种情况下,“ v1.2.3”是标记名,语义版本是“ 1.2.3”。

是否有建议的正则表达式(RegEx)检查SemVer字符串?

那里有两个。具有支持这些系统的系统的命名组(PCRE [Perl兼容正则表达式,即Perl,PHP和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-]+)*))?$

而有一个带有编号的捕获组(因此cg1 =主要,cg2 =次要,cg3 =补丁,cg4 =预发行和cg5 = buildmetadata),它们与ECMA脚本(JavaScript),PCRE(Perl兼容的正则表达式,即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-]+)*))?$

关于

语义版本规范由Gravatar的发明者和GitHub的联合创始人Tom Preston-Werner撰写

如果您想留下反馈,请在GitHub上发表一个问题

执照

知识共享-CC BY 3.0

原文