参与项目日常开发的人员往往对项目的节奏和流程非常熟悉,他们会将流程内化,感觉每个人都应该知道每个阶段是如何展开的。当然,从外部看进来就不那么明显了。因此,我认为将 Julia 的版本发布流程记录下来,包括以下内容,可能会对更广泛的 Julia 社区(甚至其他编程语言社区)有所帮助:
有哪些类型的版本
每种版本中可以和不可以进行哪些更改
版本发布流程的各个阶段是什么
根据风险承受能力,哪些人应该使用哪些版本
版本发布的阶段以及围绕它的事件顺序。
这些信息收集自 discourse 上的一些帖子和 Slack 上的对话,因此信息是“存在的”,但这篇博客文章将所有内容整合到一个地方。如果反响良好,我们可能会将这篇文章变成一份正式文档。Julia 遵循 SemVer 标准中指定的“语义化版本控制”,但 SemVer 为解释留下了相当大的空间,对流程的描述也很少,因此这篇文章旨在补充这些细节。
补丁版本会递增 Julia 版本号的最后一位数字,例如从 1.2.3
到 1.2.4
。
补丁版本,按照 SemVer 的要求,应该只包含 bug 修复、低风险的性能改进和文档更新。当然,究竟什么是 bug 修复可能比人们想象的更主观,因为人们编写的代码依赖于有 bug 的行为。一般来说,我们会在补丁版本发布方面保持谨慎,并使用 PkgEval[1] 来确保风险降到最低。人们应该相信他们可以升级到最新的补丁版本,而不用担心它会破坏东西。
补丁版本还应该避免更改内部代码,除非为了修复 bug 是必要的。即使在任何版本中更改非公开代码都是合法的,但我们希望在尽量降低补丁升级相关风险的名义下避免这种情况。
补丁版本将大约每月发布一次,针对当前活跃的发布分支(下面会详细说明),除非发布分支上的 bug 修复不足以发布新版本,在这种情况下,可能会跳过一个月。
在补丁版本预计发布前大约五天,我们将在 backports 分支上运行 PkgEval;如果看起来不错,我们将合并它,然后冻结发布分支并在 discourse 上宣布发布分支已准备好进行测试。如果五天后一切看起来都很好,新的补丁版本将被标记。
次要版本会递增 Julia 版本号的中间一位数字,例如从 1.2.3
到 1.3.0
。
次要版本可能包括 bug 修复、新功能和“次要更改”——我们使用这个术语来指代技术上来说是破坏性的更改,但这些更改不太可能破坏任何人的代码,并且在运行 PkgEval 确认没有出现任何问题后,实际上也没有破坏包生态系统。
次要版本也是内部代码进行重大重构的地方,因为我们只应该在补丁版本中修复 bug 所必须的程度上进行重构。这意味着,如果您依赖于一些非公开的 Julia 内部代码,那么您的代码可能会在次要版本中出现问题。根据 SemVer,这是允许的,因为更改并非针对公开 API——因此技术上来说它可以在任何时候出现问题;但我们会在补丁版本中避免这种情况,因此次要版本是您需要注意的地方,如果您以某种方式依赖于内部代码的话。
次要版本每 **四个月** 分支一次,这意味着每年会有三个次要版本。该速率通过对次要版本进行定时功能冻结来控制:每四个月,我们将在 discourse 上宣布当前的开发版本即将进行功能冻结(大约提前两周通知);然后在冻结日期,我们将为次要版本创建一个 release-1.3
分支,并且在那之后,该分支将不再允许进行任何功能开发,该版本将从此分支进行标记。更多关于此流程的信息请见下文。
主要版本,根据 SemVer,可以破坏任何东西。然而,现实中我们知道我们想要如何编写 Julia 代码,并且这种方式不会从根本上改变:大多数用户级代码在 Julia 2.0
中将保持不变,即使我们被“允许”进行破坏。毫无理由地破坏东西并不是我们的目的。
然而,主要版本允许做的是修复明显的 API 设计错误——那些让每个人都乐于摆脱的糟糕、混乱的 API。它还允许更改底层内容,这将破坏一些库,但为了对语言进行真正根本性的改进,这些东西需要被破坏。
一些用户乐于经常升级 Julia,以便尽快获得最新的功能。有些人甚至乐于每天构建 Julia 的 master 分支,并在功能完全成熟之前尝试使用它们。另一些人则不希望一年内升级 Julia 超过一次,如果有的话。理想情况下,我们希望永远为我们发布的每一个 Julia 次要版本提供 bug 修复。如果我们有无限的资源,我们会将每个 bug 修复移植到它适用的每一个旧发布分支。然而,现实情况是,我们没有能力同时维护多个活跃的移植分支。因此,我们决定妥协,在任何时候最多有四个活跃的分支:
master 分支:所有功能开发都在这里进行,大部分 bug 修复也在这里进行;最终,当我们开始开发 2.0
时,破坏性更改也会在这里进行。
不稳定发布 分支(目前为 release-1.3
):该发布分支的功能已冻结,但在此之前,活跃的 bug 修复和性能工作仍在进行中,直到下一个次要版本(即 1.3.0
)。通常,bug 修复是在 master 上进行,然后移植到这个分支。并不总是存在不稳定发布分支:它只在功能冻结后,但在相应版本发布之前存在;之后,它将成为稳定发布分支,并且直到下一个功能冻结之前,不会存在不稳定发布分支。
稳定发布 分支(目前为 release-1.2
):最近发布的次要(或主要)版本的发布分支。它始终存在,并且会将所有适用的 bug 修复从 master
移植到它。该次要版本的未来 bug 修复版本将从此分支发布(例如 1.2.1
)。
长期支持 (LTS) 分支(目前为 release-1.0
):一个较旧的发布分支,它将继续获得适用的 bug 修复,只要它继续是 LTS 分支。会付出额外的努力将 bug 修复移植到该分支——当后续修复无法干净地应用时,它可能会获得它自己的 bug 修复版本。
每次功能冻结时,都会创建一个新的不稳定发布分支,一旦该分支上发布了第一个稳定版本,它就会成为新的稳定发布分支:也就是说,当我们发布 1.3.0
最终版时,release-1.3
分支将成为新的稳定发布分支,release-1.2
将不再维护,并且直到下一个功能冻结之前,不会有任何当前的不稳定发布分支。
最关键的问题是何时更改长期支持分支。release-1.0
分支是我们有史以来唯一的 LTS 分支。它已经发布了四个补丁版本,并且变得非常稳定,得到了广泛的支持。然而,在某个时候,在 master
上做出的 bug 修复补丁将越来越少地适用于 release-1.0
,越来越少的包的当前版本将支持 Julia 的 1.0.x
版本——它们将使用太多新功能。当这种情况发生时——正确的时间需要判断——我们将不得不选择一个新的长期支持分支,并宣布 1.0.x
系列不再维护。新的 LTS 分支最终可能是 1.4
或 1.8
——也可能要等到 2.0
才会发生。我们不确定,但这会在某个时候发生。幸运的是,即使这样也不会强迫使用 Julia 1.0.x
的用户进行升级:他们可以继续使用最后一个 1.0.x
版本以及与其兼容的包。到那时,它将成为现存的 Julia 最稳定、经过最彻底测试、打过补丁的版本,因此,如果不需要更新的功能,可以无限期地安全使用。此外,如果某个人或组织对维护任何特定的旧发布分支有既得利益,并且愿意为实现这一点付出努力(精选移植和启动 PkgEval 运行以确保一切正常),我们很乐意接受这种帮助并发布更多版本。因此,您始终可以通过自己进行维护(或付费让其他人来做)来获得更长时间的支持。目前,release-1.0
仍然是一个优秀的、稳定的 LTS 分支,在我们更改 LTS 分支之前,会有足够的警告时间。
不同语言用户对风险的承受能力差别很大。有些用户完全可以接受发现并报告偶尔出现的 bug,并帮助找出为什么某些包无法与新版本一起使用。另一些用户只希望使用经过多次 bug 修复并且每个包都已在很长一段时间内完美运行的语言版本。在这两种情况下,风险承受能力之间存在一个连续谱。大多数用户将属于以下四种风险承受能力类别之一
高风险承受能力:“YOLO,我在 master 上生活。当然,现在没有以前那么冒险了,因为 master 上一段时间内不会出现破坏性更改,所以即使在 master 上,包也应该继续工作,但 bug 总会发生,你知道吗?我愿意帮助找到它们。”
正常风险承受能力:“我喜欢事情正常工作,不想处理 master 上的瞬时 bug。所以我会坚持使用最新的稳定版本,并在可用时升级到该版本的最新补丁版本,因为这很安全,并且我会获得 bug 修复和性能改进。唯一令人恼火的是,当我使用的包出现问题时,因为它依赖于一些 Julia 内部代码,并且可能需要一段时间才能发布新版本。”
低风险承受能力:“我比较保守,对风险厌恶。我会关注当前的 LTS 分支,因为它已经经过了大量的测试。当 LTS 分支更改时,我会升级,因为到它成为新的 LTS 分支时,它已经发布了第四个或第五个补丁版本,所以 bug 已经被解决,最初可能存在的任何包故障也早就被解决了。”
极低风险承受能力: "我极度厌恶风险。我从不升级 Julia(或任何东西),除非是关键的错误修复和安全问题。我运行的是不再获得积极支持的 Julia 版本,但它是以前 LTS 分支的最后一个版本,所以有一个两位数的补丁号,并且经过了非常彻底的调试。如果在这个古老的发布分支上需要新的错误修复,我会自己回溯它并帮助发布一个新的、更可靠的补丁版本。"
这些配置文件更清楚地表明了长期支持分支的主要标准是该分支具有以下属性
它已经有了足够的补丁版本,我们有信心它非常可靠;
任何将来要支持它的包都已经发布了支持它的版本。
如果一个新的 LTS 分支满足这两个标准,那么 "低风险承受能力" 类别的用户将能够升级到新的 LTS 分支,因为他们已经可以确定它将是可靠的、经过良好调试的,并且他们需要的包将可以使用(尽管他们可能需要升级他们的包版本)。我们将需要从经验中学习 LTS 分支应该比稳定发布分支滞后多少个版本。
我们已经讨论过各种类型的发布意味着什么,以及哪些类型的更改可以进入它们,但我们并没有过多地讨论一个发布是如何实际完成的。在本节中,我将概述我们如何从在 master
分支上开发功能,到标记发布的最终版本,以及之后制作该版本的补丁。我将使用 "bug" 一词来指代通常意义上的错误代码,以及 "性能错误" - 即运行速度低于我们认为可以接受的代码。在 Julia 中,性能是一个至关重要的属性,我们通常将性能问题视为阻塞性错误。以下是围绕 x.y.0
次要版本的一系列事件概述
开发(4 个月)
在 master
分支上
开发新功能、修复错误等。
标记 x.y.0-alpha
(可选)
新版本的非常早期的预览 - 它还没有功能冻结,可能存在已知错误
标记 x.y.0-beta
(可选)
新版本的稍晚的预览 - 它还没有功能冻结,可能存在已知错误
x.y.0
功能冻结
创建 release-x.y
,新的不稳定发布分支
发布分支上不会合并任何新功能,只有错误修复
新功能可以继续合并到 master
分支上,只是它们不会进入 x.y.z
版本
稳定化(1-4 个月)
在 release-x.y
分支上
修复所有已知的发布阻塞性错误
标记 x.y.0-rc1
修复所有已知的发布阻塞性错误
标记 x.y.0-rc2
修复所有已知的发布阻塞性错误
...
标记 x.y.0-rcN
连续一周没有发布阻塞性错误
标记 x.y.0
最终版本
维护(直到 x.y
被宣布不再维护)
在 release-x.y
分支上
将错误修复回溯到 release-x.y
分支
标记 x.y.1
(一两个月后)
将错误修复回溯到 release-x.y
分支
...
你只需看一眼就能明白这可能是一个漫长而不可预测的过程。特别是,稳定化阶段可能需要非常不确定的时间 - 从几周到几个月不等。这在确保质量和希望拥有可预测的发布速度之间造成了紧张关系。一方面,我们不希望仓促发布候选版本流程,因为它在很大程度上确保了每个 Julia 版本都具有你期望的质量和稳定性。另一方面,我们不希望总体发布速度成为调试需要多长时间的变幻莫测的牺牲品 - 我们都知道,对于任何项目来说,这可能是一个漫长而痛苦的过程,对于像编程语言一样复杂的东西来说尤其如此。
我们通过将一个版本的稳定化与下一个版本的开发重叠来解决在确保发布质量和保持可预测发布速度之间存在的紧张关系。每个版本的开发阶段都设定了四个月的时间,x.(y+1)
的开发阶段在 x.y
的开发阶段结束后立即开始。无论风雨,我们每四个个月就会有一次新的功能冻结:我们会选择一个日期,你必须在那天之前将你的功能合并。如果新的功能没有合并,它们就不会进入该版本。但这没关系,它们会在下一个版本中进入。这种方法还意味着 master
始终对新功能开放,而不是在稳定化期间被冻结。
由于开发和稳定化重叠,如果发布候选版本流程花费的时间异常长,那么 x.y.0
的最终版本可能会与 x.(y+1).0
的功能冻结同时发生。例如,这种情况发生在 1.2.0
和 1.3.0
上。在讨论区上对此表达了一些困惑和不安,但这正是为了保持可预测发布速度而付出的不可避免的代价。1.2
的稳定化阶段异常长,这种情况有时会发生。我们一直在检查我们的流程,并思考如何改进它。一个可能有所帮助的改变是更频繁地以完全自动化的方式运行 PkgEval,以便我们能更早地知道开发过程中的更改何时会破坏包。尽早频繁地运行 PkgEval 使得更容易缩小导致破坏的更改范围。如果有人想参与进来并帮助改进 Julia 的发布流程,帮助使用 PkgEval 将是一个非常有影响力的工作,它不需要深入的技术知识。
有一点需要注意,因为有些人对此感到困惑:功能冻结只影响新功能 - 错误可以在任何分支的任何时间修复。修复错误永远不会太晚。唯一不会在发布分支上修复错误的时间是当它不再维护时。即使那时,如果其他人想要修复错误并完成发布新版本的流程,我们也会很乐意提供帮助,只是我们不会自己去做。
尽管它们是发布流程的标准部分,但人们可能不清楚 alpha 和 beta 版本的目的是什么,或者 "发布候选版本" 是什么。为什么存在这些 "预发布" 版本?我知道,在我开始尝试实际制作软件版本之前,这一点对我来说并不完全清楚。这些版本都是为了与依赖你的软件的人进行沟通。它们充当一个信号,表示 "请现在测试它"。每个版本都要求不同类型的用户提供不同类型的反馈
alpha 版本表示:"这还没有功能完整,几乎肯定存在错误,但我们希望获得一些重要新功能的早期反馈,以便我们可以在它们成为定局之前更改或修复它们。"
beta 版本与 alpha 版本非常相似,但人们可以预期它会更完善,错误更少,因为可能已经有了 alpha 版本。我们只对 Julia 0.6
和 0.7
(也称为 1.0
,但有弃用警告)进行了 beta 版本发布,它们都先进行了 alpha 版本发布。
发布候选版本表示:"这实际上已经快要准备好了,请现在测试它,并让我们知道是否发现任何错误,因为否则我们最终可能会发布一个包含影响你的应用程序的错误的版本。" 发布候选版本实际上应该是一个版本,据我们所知,在它被标记时,它可以成为下一个版本。换句话说,它不应该包含任何已知的发布阻塞性错误。
因此,当你看到 alpha 版本或 beta 版本或发布候选版本时,请尝试一下!让我们知道它是否在任何方面都不适合你。这样做将有助于确保最终版本尽可能地平稳、高质量,为你。
关于错误修复:当 x.y.0
被标记时,一个版本的生命周期并没有结束 - 还有可能存在许多 x.y.z
错误修复版本也会被标记。这个过程是如何运作的?所有活动分支都会修复错误,但通常会先在具有该错误的最新分支上修复错误,然后 "回溯" 到所有仍然活动的早期分支。因此,例如,如果 master
上存在错误,它将被修复在 master
上,并且修复它的拉取请求 (PR) 在 GitHub 上用 backport x.y
标记,用于所有具有该错误的活动分支。由于当前的活动分支是 master
、release-1.3
(不稳定)、release-1.2
(稳定)和 release-1.0
(LTS),因此针对 master 上的错误的修复将用 backport 1.3
、backport 1.2
和 backport 1.0
标记。然后,更改将被 cherry-pick(使用 git cherry-pick -x
)到这些分支中的每一个,用于该分支的下一个补丁版本。如果修复干净地应用并通过测试,那就太好了。如果不是,则可能需要额外的手动工作才能做出适用于该分支的修复。
一旦发布分支积累了足够的错误修复,并且经过了足够的时间,就会发布一个新的错误修复版本 x.y.z
。这将在讨论区上提前五天宣布,以便人们可以测试新版本。我们目前没有带宽或资源来为补丁版本制作二进制文件或发布候选版本 - 它们太多了。因此,为了测试,你需要使用 nightly 版本或从源代码构建 Julia。帮助自动化和简化补丁版本流程是任何希望参与该项目的人的另一个高影响力领域。
希望你发现这篇关于 Julia 发布流程和策略的概述很有启发。我们能希望的最好的事情是,你们中的一些人阅读后会觉得它很有趣并想参与进来,通过消除误解,我们帮助让成为 Julia 开发人员更容易一些。
[1] | PkgEval 是一个用于运行所有 Julia 包的测试套件的工具,它帮助我们确保我们没有无意中破坏任何东西。每个失败都会在发布版本时进行检查:我们会验证失败是否不是由于违反了 SemVer 造成的,并尝试提出拉取请求来修复包,无论失败的原因是什么。 |