企业软件团队如何与上游开源项目团队维持健康的协作关系一直是业界的热门话题。OSChina 编辑部作者 Lola 用翔实的案例介绍了「上游优先」原则是什么,并表示「(上游优先)与善良无关,这是一种开放协作的必要态度,甚至是一种话语权的争夺,你行动了,在社区中赢得了声望和影响力,社区信任和尊重你,你的决策有了分量,这才是开源原本的样子。」
在开源中国写文章这么久,常常会自我怀疑。总会有一些读者会留下诸如「开源就应该免费」这样一听就想和他约架的评论,这些评论像一记记重锤锤在小编脸上,让小编不禁自问:这些年的文章难道都白写了吗?
开源的一些常识性原则,我们好像懂了,又好像没懂。这种令人恼火的情况,还经常出现在开源另一个重要的原则 ——「上游优先」(Upstream First)上。这一原则,反复出现在一些开源布道师的口中,有点苦口婆心的意思了。
什么是「上游优先」?相信大家看字面意思也 get 到了一大半了,一旦你有 fork 的动作,你所 fork 的那个项目就是你的上游。而上游优先则是一种鼓励你直接与开源社区互动并从源头解决问题的工作方式。
与「上游优先」相关有一个有趣的故事。当时,红旗 Linux 还没倒闭,还是国产 Linux 版本的标杆,内部针对要不要采用「上游优先」产生了争议,这给当时还在红旗 Linux 任职的李建盛带来了冲击:
彼时许多国产 Linux 发行版只自顾自进行孤岛式开发。但是,比如说我在一个包里面添加了一点功能,半年之后,却发现上游比我做的东西更多,而且更完善,甚至一些 bug 人家很早就已经修复了,修复得很好,还加了 feature。那我不得不把自己原来做的工作全废掉,然后把上游那个包再拿过来。
无疑,李建盛是认为有必要「上游优先」的,而大环境让他感到无力。几年后,李建盛成为国内少有的全职开源文化布道师,在接受 OSCHINA 采访时,他表示那场争议几乎是他开源职业道路上的分叉口。
对此颇有同感的还有今年刚被选举为 ASF(Apache Software Foundation)董事的姜宁,同样是在 OSCHINA 的采访中,姜宁表示:
观念的转变是最难的。国内大部分的开源参与者都是搭便车的心理,只是把开源当成免费的、能让他快速交活的工具。把东西拿来用,用的过程出问题了,要么在社区问一下,要么自己埋头修一修,修完了也不回馈上游。大家不习惯和上游社区进行任何交互。
姜宁将这些难题归结于观念问题,是大家缺乏了开放式协作的意识。而除了扭转观念,要想在实践层面上做好这一原则,我们要走的路则更长。
实际上,无论是哪个开源许可证都没有规定必须「上游优先」。其他宽松的许可证就不用说了,就算是最强力的 GPL 所要求的也只是:「分发时一定要提供源代码。」
这里的「提供源代码」,是指当用户想要得到源码时,他有途径获得。它没有指定你非要反馈回上游,更谈不上上游优先了。
那么,在没强制也没规定的情况下,我们为什么还要去做反馈上游的事呢?这个问题其实已经被不少开源布道者论述过,比如之前提到的姜宁和李建盛。他们的论点基本上是围绕以下 3 点的:
1)降低自己的维护成本。
一旦 fork,就意味着与上游脱钩,不再参与上游的维护和更新,而 fork 者则要负责 fork 版本的生老病死。然而,这个代价要比想象中大得多。而且,就算这些维护成本你都能扛下来了,大概率也是重复造轮子。
但你把修改回馈给上游就是另一码事了,一旦被合并,所有维护都是上游在承担,立马解放双手。
2)方便下次使用上游更新。
如果你想要使用上游更新的新版本,你需要在自己的分支上做适配,更新得越快,适配工作量越大。但如果你自己的代码本来就在这条「主干线」上,你就可以零成本享受到。
3)不要单兵作战,多和上游交流。
深度参与开源,你的确有必要和上游有所交流。交流也正是开源的魅力所在,你不再孤独,而是在众多有相同兴趣爱好的开发者中交流探讨。越往上游交流,你越可能联系到项目创始人甚至你的偶像,也许你还会奇妙地发现他们还挺乐于助人的。
在这一层面上,分支的主体是「个人」。我一个人力量有限,即使分支了顶多也是个沟渠,很快就会干枯。最好也是最明智的做法是,拿出分享精神,带着自己的代码,到上游去,找到组织。
但落在组织和集体层面,则可能另一种局面:这种 fork 不是意外,而是带有某种特定目的,比如南非富豪 Mark Shuttleworth 为了实现梦想,从 Debian 分叉出了 Ubuntu;又比如 MySQL 创始人因为甲骨文的收购一气之下分叉出 Maria DB。
这些分叉后来散发出了不可小觑的力量,丰富了开源项目的版图。他们的逻辑是,直接创建一个新的社区,不按上游社区的逻辑走了,修改之后照样发新版本,打出自己的特色,自己成长为一条浩荡大河。
但实际上,在国内更常见的一种情况是公司层面的「拿来主义」。开发人员并没有什么理想主义的目的,而仅仅是为了完成任务,比如说老板要一个 XXX 的解决方案,给了几个月时间和几十万预算,于是项目经理直接从上游项目拿来,花些时间修改变成了这个解决方案。
这个项目没有任何反馈上游的动力,区区预算也没有维护和支持的能力。从长远上看,这种做法是毫无意义且浪费成本的。
因此,把以上 3 种情况都捋清楚了以后,「我们需要反馈上游吗?」这个问题瞬间就变成了「我有实力不反馈上游吗?」。
如果没有,请接着往下看。
「我觉得 GPLv2 是一个非常棒的协议,我喜欢它的理由很简单:我给你源代码,你给我你的修改,我们就扯平了。」
——Linux 之父 Linus Torvalds
打一开始,Linus 就想得很清楚,他需要一个机制,这个机制能确保下游的修改能够被反馈到自己手上。我们没法回溯「上游优先」最先被提出的那个时间点,但有一点是可以确定的:包括 Linux 内核社区在内的许多社区都在沿用并遵守这一「开源潜规则」。
2010 年 Linux 内核社区一场围绕「上游优先政策」的讨论中,Linus 明确表示:绝不可能停止实施「上游优先」政策。
后来,Linux 基金会也曾在公共场合表示过「上游优先」的重要性,认为如果(代码贡献者们)没有这种(社区)自律,内核早就在自重下崩溃了。
2020 年,Linux 基金会发布了一份报告,呼吁用开源的方式解决「技术债」(technical debt)。该报告指出一旦项目累积有技术债务,就会出现交付时间拉长、安全问题增多、维护任务变重、无法与上游同步等症状和不良影响。而他们给出的解决方案则是「开源」,这里的开源最重要的原则就是「上游优先」。
与上游保持一致的开发脚步,几乎可以避免绝大多数的技术债务。当然,这需要你积极活跃地参与到上游项目中去。
你无法强制上游向对你有利的方向发展,但你可以通过贡献和分享去影响上游,这样上游和其他贡献者才会渐渐明白你的需求何在。
集成上游开源项目的开发流程
以上是来自上游的呼吁。而有实力的下游在分叉很久之后,「迷途知返」重新与上游加强联系的例子也不是没有。其中,最有名的要数谷歌的 Android 开发社区。
众所周知, Android 的最底层是 Linux 内核,而从最上游的 Linux 内核到各种五花八门的 Android 设备终端这个链条中,利益相关者众多,往往会经历多次分叉,而且还会加上一堆特定的更改。高通、三星等等 SoC 供应商们基本会为每个主要芯片版本制作一个特定于 SoC 的内核,然后每个内核都会获得特定设备的硬件支持。
这导致 Android 的内核碎片非常混乱。Android.com 官方文档指出:「这些修改非常广泛,以至于设备上运行的代码中有多达 50% 是树外代码(不是来自上游 Linux 或来自 AOSP 通用内核)。」
因此,谷歌 Android 可谓是臭名昭著。早在 2010 年,LWN.NET 上就有帖子批评 Android 开发社区不采用上游优先政策造成了麻烦。
事情的起因是,2009 年 2 月 Android 内核中的(挂起阻止程序 / 暂停拦截器)首次以 wakelocks 的形式出现,接下来一年多的时间里,Android 开发人员接收到非常多但是相互矛盾的建议,比如要修复 X 以合并此代码,但当修复了 X 就有开发者跳出来要求他们修复 Y,修复工作似乎永远完不成。
与此同时,在公开场合,Android 开发人员也因没有将他们的代码上传到上游而受到批评。很多批评直指,Android 是在闭门造车的情况下开发短期解决方案。毕竟,一旦代码交付并且应用程序开始依赖它,无论做何种更改都会变得更加困难。这使得大多数 Android 设备都在使用多年前的内核版本,而错过了新的 Linux 内核功能。
近两年, Android 社区的这种情况迎来了转机。2021 年 9 月,在 Linux Plumbers 大会上,谷歌软件工程师 Todd Kjos 在自我剖析 Android 这种情况的成因之后,他透露 Android 将追求「上游优先开发模型」,以确保新代码首先进入主线 Linux 内核,而不是直接定位在 Android 源代码树中。
此外,谷歌还承诺「努力将 Android Common Kernels 中的所有树外补丁上传到上游」。在他们的时间表中,谷歌计划在 2022 年底之前完成其现有功能的上游化和隔离供应商变更的工作。并且,从 2023 年开始,该公司计划采用这种 “上游优先” 的开发模式来避免未来出现此类问题。
谷歌有没有实力?但兜兜转转依然走回了「上游优先」。除了谷歌之外,对「上游优先」这一原则最卖力宣传的公司当属红帽。无论是自家网站上的文章,还是各种公共演讲场合,红帽都在强调该原则。
▲ 红帽开源参与指南的第一条就是「上游优先」
在 2021 年 GOTC 大会上,红帽全球副总裁兼大中华总裁曹衡康叙述了红帽整个 “上游优先” 路径:
以前红帽做完我们商业版之后,我会把它发布变成社区版的。但是我发现一个问题,很多所谓的生态伙伴或者是公司,他写代码不会贡献给上游,变成断供,断掉了,红帽觉得这些东西达不到上游优先概念。
在去年的时候,我们干脆把 Stream to RHEL 放到红帽的企业版软件上游去,为什么要这样做?因为我们拿到代码以后把它变成是一个可用的代码,然后由红帽代表拿到,变成企业版软件,再贡献给上游,就不会有遗漏。过去 Stream OS 在红帽下游会有遗漏问题,现在就不会有任何遗漏,真正能够达到当时红帽的想法,就是上游优先的概念。
「上游优先」开发的理念是,基于开源项目的产品中包含的任何更改(功能、错误修复)都应首先提交给项目,然后再包含在产品中。这可最大限度地减少长期维护的负担。
—— 红帽开源和标准团队成员 Dave Neary
一路看下来,实际上不管你实力几何,「上游优先」都是开源路上不可避开的一项重要原则。既然躲不掉,不如干脆好好参与进去。
Dave Neary 曾在一篇文章中详细阐述了到底该如何实践「上游优先」原则。在他的观念中,如果开发者想要实现某个客户的需求,可以尝试直接询问项目,但别希望社区能够像乙方一样在规定时间内满足一切需求,毕竟社区没有得到一点好处,也不想被拿来当冤大头。所以,最好的办法是用自己的团队去解决。
这就存在一个问题:因为上游并不在你的控制范围内,你无法像红帽一样,等待上游合并完成之后,再交付给客户;而只能事后再发送回上游开源项目,这就存在一个时间差。
在时间差无法消除的情况下,你反馈上游的动作应该越快越好。因为随着时间的推移,本地分支中的更改越积越多,使得迁移到上游变得无比麻烦。
在很多外行看来,向开源项目提交功能和补丁似乎很简单。其实,完全不是这么回事:
首先,从稳定分支中分出来的那段时间的代码,要想再回到上游中,势必涉及到大量返工;其次,刚提交上去的代码往往都很难消化,即使将其切割成一个个的独立补丁,每个补丁也需要独立提交和接受,这项工作无疑是巨量的;再次,一些社区的审查合并周期很长,这么长的周期非常考验人的耐心;最后,很有可能你的解决方案并不能受到上游的认可,上游根本就不打算合并你的分支。
这就非常头大了。对此,Dave Neary 提出,理想的工作方式是针对上游项目的不稳定分支来开发功能。如此一来,补丁一旦准备好就可以提出合并需求,而当你想要将新功能搬到自己的产品中时,这些功能其实是向后移植到稳定分支的,并且也降低了你自己的维护成本。
当然,Dave Neary 也表明这种方法有一个前提:你需要在所有工作开始之前,就在社区内公开讨论,这才能确保你提出的方案是可能被接受的。要知道合并有多不容易,在国外甚至有家叫做 Collabora 的咨询公司,专门帮助客户将他们的代码带到上游社区,帮助公司与主线社区建立联系。
发现了吗?最最最重要的一个环节是:与上游维护者建立关系。简单说,就是经常与他们交流,搞好关系,或者干脆自己成为他们中的一员,等你掌握话语权了,还不好办事吗?
一些文章都在探讨这样一个问题:「上游优先是否意味着善良?」而在笔者看来,这与善良无关,这是一种开放协作的必要态度,甚至是一种话语权的争夺,你行动了,在社区中赢得了声望和影响力,社区信任和尊重你,你的决策有了分量,这才是开源原本的样子。
(转自 OSChina「开源博客」)