Julia的版本发布流程

7 September 2019 | Stefan Karpinski (JuliaHub)

从事软件开发的行家里手们对版本发布流程与节奏如此了若指掌,以至于他们将其精髓内化(internalize)并以为人人都懂得这些“浅显的道理”。 可是事实恰好相反,外行一眼望去如同雾里看花。 所以为了整个Julia社区,乃至于其它编程语言社区,我觉得有必要将Julia的开发过程白纸黑字地写下来。 在本文中,我将阐述:

这些文字材料是从discourse论坛和Slack协作交流群中摘录而来。 所有资料都是现成的,我只是将其归纳在一处。 如果大家觉得这篇文章颇有益处,我们会考虑将其变成一份官方文档。 宏观上来说,Julia遵循SemVer标准制定的“语义化版本”。 但SemVer在微观上提供了许多自由度,供使用者自行解释。 这篇文章正是为填补这些微观细节所作。

  1. 补丁版本(Patch releases)
  2. 次要版本(Minor releases)
  3. 主要版本(Major releases)
  4. 长期支持(Long term support)
  5. 不同的版本分支对应于不同的风险承受力
  6. 发布流程
  7. 要预发布版本有何用?
  8. 版本维护
  9. 结论

补丁版本(Patch releases)

次要版本(Minor releases)

主要版本(Major releases)

长期支持(Long term support)

一些用户乐于时刻更新Julia以获得最炫最酷的新特性。 另一些用户甚至乐此不疲地每天重新编译Julia的master分支以做第一个吃螃蟹的人。 还有的用户,恰恰相反,一年到头也懒得升级一次。 理想情况下,我们愿意为每个存在于世的次要版本永远提供bug修复服务。 如果我们有无限的资源,我们会将每一个bug修复反向移植到每一个兼容的版本分支上。 理想很丰满,现实很骨感。 我们的资源仅够我们维护几个活跃版本分支。 因此,我们退而求其次,决定在任何时间节点上仅维护至多四个活跃分支:

现在问题只剩一个:什么时候LTS分支会更替? release-1.0是我们目前唯一确立过的LTS分支。 获得了四个补丁版本后,该分支相当稳定并被广泛支持。 可是,它从master获得的bug修复补丁会与日俱减,并且越来越多的第三方库会放弃对其的支持(这些库需要用到Julia新版本中的特性)。 当合适的时机来临时,我们不得不选择一个新的LTS分支并宣布放弃维护1.0.x系列。 这个新的LTS分支可能是1.41.8,也可能是2.0。 我们现在还无法预言,但这一天终究会到来。 幸运的是,即便如此,1.0.x系列的使用者们也并非一定要升级版本。 他们大可以使用这一旧版本,并只与和该版本兼容的第三方库版本打交道。 到那时,它将成为最稳定,最充分测试的Julia版本。 所以,只要你不需要新特性,你大可以放心地继续无限期地使用它。 另外,如果某人或某组织出于自身利益,愿意继续维护某个旧版本分支,也就是甄选(cherry-pick)反向移植并运行PkgEval以确保兼容性,我们将很乐意接受这些帮助从而发布更多的版本。 因此,你总可以通过自己维护或雇人维护来获得更长期的支持。 就目前来说,release-1.0仍将继续是一个优秀的,稳定的LTS分支。 并且,当我们打算更替LTS分支时,我们会提前发布大量警告(warning)。

不同的版本分支对应于不同的风险承受力

不同的用户有不同的风险承受力(risk tolerance)。 一些风险承受力高的用户能驾轻就熟地发现并汇报零星的bug,并侦察出为什么某个第三方库与Julia的新版本不兼容。 另一些风险承受力低的用户希望使用久经考验,广泛兼容的版本。 还有些用户介于这两个极端间的某处。 大致可以把大多数用户根据风险承受力分为下面四类:

  1. 高风险承受力(high risk tolerance):“人生只有一次,我在master分支上翩翩起舞。况且,master分支在未来的相当长一段时间内不会有破坏兼容性的更新[4]。现在master只偶尔出现bug,不过就算出现bug,我也可以帮忙解决。”

    [4] 译者注:根据前文,破坏兼容性的更新在开发2.0版本时才会出现。
  2. 普通风险承受力(normal risk tolerance):“我想要能用的东西,我不想要master分支上忽隐忽现的bug。所以我会坚守最新的稳定版本并打上最新的补丁,这样我的系统又安全又高效。惟一的烦恼是当我使用的第三方库因为依赖淘汰的Julia内部代码而在新版本上失灵时,我需要等上一段时间第三方库作者才会更新。”

  3. 低风险承受力(low risk tolerance):“我保守,厌恶风险。我使用当前的LTS分支,因为它已经经历了充分的测试。当LTS分支更替时,我将升级到新的LTS分支。因为新的LTS分支在成为长期支持前已经经历了数个补丁版本,所以bug应该已经被修复,第三方库不兼容问题应该也已经被解决。”

  4. 极低风险承受力(very low risk tolerance):“我极端厌恶风险。除了严重的bug和安全问题,我从不升级Julia(或其它任何东西)。我运行一个已经不再被支持的LTS版本,但这个版本已经经历了两位数的补丁,相当可靠。如果我需要修复一个新的bug,我将自己动手反向移植。”

这些不同类型的需求很好地诠释了LTS分支的关键特性:

如果一个新的LTS分支满足这两个条件,低风险承受力用户便会升级到该版本,因为他们相信该新LTS分支可靠,经过充分调试,并且需要的第三方库已经向其提供支持(可能需要同时更新库版本)。 我们将从实践中学习新的LTS分支在独当一面前需要滞后稳定分支多少版本。

发布流程

我们已经讨论了各种版本以及它们所允许的修改,但我们还没深入讨论这些版本的发布流程。 在这一节里,我将描绘这些细节,诸如从master上的新特性到次要版本的发布,再到为次要版本发布补丁。 在这节里,“bug”一词不仅指代传统意义上的错误代码,也同时指代性能问题(运行效率不可接受之低的代码)。 在Julia语言中,性能至关重要,我们经常将性能问题视为不可绕过的bug。 以下是一连串围绕x.y.0次要版本展开的各阶段与标志性事件:

一眼望去,你就能发现这是一条道阻且长的征途。 尤其是稳定化阶段,它的费时忽长忽短,从几周到数月不等,难以预料。 质量至上的愿望和如期发布的憧憬相互冲突,如同一根两头尖的针。 一方面,我们不想还未调试好就匆忙发布,以免因为粗制滥造而叫人失望。 另一方面,我们不想因为在调试上花费超额时间而导致无法准时地发布次要版本——尽管我们都知道软件开发,尤其是复杂的程序语言开发,跳票是家常便饭的事。

为了解决这一矛盾,我们想了一个好主意。 如果我们同时开展一个版本的稳定化和下一版本的开发,我们就有望如期完成版本迭代。 每个次要版本的开发阶段占用固定的四个月的时间,x.y版本的开发阶段一结束,x.(y+1)版本的开发阶段就立即开始。 雷打不动地,我们每四个月进行一次特性冻结:一旦我们决定了特性冻结的日子,你要么加把劲在这之前汇入(merge)你开发的特性,要么索性等下一个版本。 这个操作方法也意味着master分支永远开放用于接受新特性,而不会像不稳定分支那样在稳定化阶段冻结。

由于开发与稳定化的时间重叠,如果版本候选过程耗时过长,很有可能x.y.0的最终版本将在x.(y+1).0特性冻结时发布。 一个最好的例子便是1.2.0版本和1.3.0版本。 虽然这在discourse上引起了一些困惑和惊愕,但这种副作用是维持可预测发布周期所必要的。 1.2版本的稳定化阶段不寻常的长,但这并没有什么好奇怪的。 我们时时检视我们的开发流程,反思如何改进。 一个可能的改进是更频繁地调用PkgEval以及自动化这一过程。 这样我们就能尽早地知道何时我们破坏了与第三方库的兼容性。 调用PkgEval越早,调用PkgEval越频繁,我们也就越容易锁定破坏兼容性的变动。 如果有人愿意帮助改善Julia的发布流程,一个行之有效的途径就是替我们多多调用PkgEval,而且这不需要什么高深的技术知识。

有一点需要注意,特性冻结只冻结了特性,不冻结bug修复。 Bug修复在任何时间在任何分支上都是允许的。 修复bug永远不会迟。 只有一种情况bug修复不进入版本分支,那就是该分支已被遗弃了。 即便如此,如果有人愿意修复遗弃分支的bug并发布一个新版本,我们举双手欢迎,只不过我们不自己带头罢了。

要预发布版本有何用?

虽然预发布版本(pre-release)是版本发布流程的标准组成部分,并不是所有人都对alpha和beta版本乃至候选版本(release candidate)的意义了若指掌。 这些预发布版本的意义何在? 我起初对此也懵懵懂懂,直到我开始自己发布软件版本。 这些预发布版本其实是一种沟通,一种和所有依赖你的软件的用户的沟通。 它们向你的用户发出信号:“亲,来试试看这个。” 每一个预发布版本向各种用户请求不同的反馈:

所以,下次当你看到一个预发布版本,不要错过尝试它的机会! 让我们知道它是否为你正常工作。 如果你这样做的话,最终版本就会给你带来平滑,高质量的使用体验。

版本维护

关于bug修复,一个(次要)版本的生命并不随着其贴上x.y.0标签而结束。 后面一系列叫x.y.z的补丁版本正翘首以待呢。 这又是怎么一回事? 所有活跃分支都需要修复bug,但bug修复通常进行于最新的分支,随后才反向移植到之前的活跃分支。 譬如,master上有个bug,这个bug会以pull请求(pull request,PR)的方式被修复。 同时,这个bug每波及一个活跃分支,该PR就会被贴上相应的backport x.yGitHub标签(label)。 当前的活跃分支为masterrelease-1.3(不稳定),release-1.2(稳定),和release-1.0(LTS),这个PR会被贴上相应的backport 1.3backport 1.2,和backport 1.0标签。 这个代码改动随后通过甄选(使用git cherry-pick -x)运用于这些分支中的每一个,并成为下个补丁版本的一部分。 如果修复成功,测试通过,则皆大欢喜。 如果失败,则需通过额外的手工劳动修复这些分支上的bug。

一旦某个版本分支积累了足够的bug修复,并且经历了足够的时间,一个新的补丁版本x.y.z就诞生了。 相关消息会在discourse上提前五天公布,以便于用户测试新版本。 我们目前没有精力或资源为补丁版本制作二进制程序体(binary)或候选版本——它们多如牛毛。 因此,你要么使用一个每日构建(nightly build),要么自己从源码编译。 如果你想助我们一臂之力,自动化并精简补丁版本发布流程是另一个高影响力的工作[6]

[6] 译者注:前一个高影响力的工作是帮助调用PkgEval。

结论

但愿你读完这篇关于Julia版本发布流程和政策的综述后有所启迪。 我们最想看到的是你们当中的某些人读完之后参与到Julia的事业中,同时也希望通过揭秘Julia的发布流程,我们降低了成为Julia开发人员的门槛。

Translator: Wenjie Zheng