我们如何避免CI驱动的开发……?


45

我正在与其他许多常规贡献者一起进行一个由研究主导的大型开源项目。因为该项目现在很大,所以一个财团(由两名全职员工和几名成员组成)负责维护该项目,持续集成(CI)等。他们只是没有时间进行外部集成虽然贡献。

该项目由一个“核心”框架组成,该框架包含大约半百万行代码,由联盟维护的一堆“插件”,以及几个外部插件,而我们大多数都不是。甚至没有意识到。

目前,我们的CI正在构建核心和维护的插件。

我们面临的主要问题之一是,大多数贡献者(尤其是偶尔的贡献者)并未构建90%的可维护插件,因此,当他们提议对核心进行重构更改时(这些日子经常发生),他们在GitHub上发出拉取请求之前检查了代码是否在其计算机上编译了。

代码工作正常,他们很高兴,然后CI结束了构建,问题开始了:在由联盟维护的插件中编译失败,表明贡献者未在其计算机上构建。

该插件可能依赖于第三方库(例如CUDA),并且用户不希望,不知道该怎么做,或者只是出于硬件原因而不能编译该损坏的插件。

因此,那么-PR永远处于永远不会合并的PR的边缘-或贡献者在损坏的插件的源中抓取重命名的变量,更改代码,推入他/她的分支,等待CI会完成编译,通常会收到更多错误,并重复执行该过程,直到CI满意为止-或财团中两个已经超额预定的永久物之一提供帮助,并尝试在其计算机上修复PR。

这些选项都不可行,但我们只是不知道如何做不同。您是否曾经遇到过类似的项目情况?如果是这样,您如何处理这个问题?有没有我在这里看不到的解决方案?


84
向系统提供插件API的最高规则是保持稳定或至少向后兼容。更改内核而无意更改插件API绝不能破坏任何插件的编译(可能会偶然破坏其功能,但不会破坏编译)。如果对内核内部的变量名进行简单更改会导致插件的编译失败,那么插件和内核之间的分隔似乎就完全被打破了。
布朗


1
@KevinKrumwiede:我确定他们已经知道这一点;-)如​​果您遇到不兼容的情况,我可以确定他们有意更改了API。
布朗

3
我想改一下这个问题,因为它确实有误导性。诸如PR打破当前CI时如何管理PR?我认为可以更好地捕获您的情况。
bracco23

2
您的构建/测试过程有多困难/复杂?只需运行一个命令或单击一个按钮即可。到那时,可以有理由期望用户在提交PR之前自己运行所有测试。
亚历山大

Answers:


68

CI驱动的开发很好!这比不运行测试和包含损坏的代码要好得多!但是,有几件事可以使涉及的每个人都更容易做到这一点:

  • 设定期望:有贡献文档说明CI通常会发现其他问题,并且这些问题必须在合并之前解决。也许可以解释一下,较小的本地更改更可能有效,因此将大型更改分为多个PR可能是明智的。

  • 鼓励本地测试:轻松为系统设置测试环境。验证是否已安装所有依赖项的脚本?可以使用的Docker容器了吗?虚拟机映像?您的测试跑步者是否具有允许对更重要的测试进行优先级排序的机制?

  • 解释如何自己使用CI:感到沮丧的部分原因是,此反馈仅在提交PR后出现。如果贡献者为自己的存储库设置CI,他们将得到更早的反馈-并为其他人生成更少的CI通知。

  • 可以通过以下两种方法来解决所有PR: 如果某些东西由于损坏而无法合并,并且在解决问题上没有进展,则只需将其关闭即可。这些废弃的开放式PR只会使一切变得混乱,任何反馈都比不理会这个问题要好。可以很好地表达这句话,并明确指出,当问题解决后,您当然很乐意合并。(参见:关闭的艺术由杰西Frazelle对维护人员的最佳实践:学习说不

    还可以考虑使这些废弃的PR成为可发现的,以便其他人可以捡起它们。如果剩下的问题更加机械化并且不需要对系统有深入的了解,那么对于新的贡献者来说,这甚至可能是个好工作。

从长远角度来看,这种更改似乎破坏了不相关的功能,因此通常可能意味着您当前的设计有点问题。例如,插件接口是否正确封装了内核的内部结构?C ++使得意外泄漏实现细节变得容易,但是也使得创建难以滥用的强大抽象成为可能。您不能在一夜之间更改此设置,但可以将软件的长期演进引向更不那么脆弱的体系结构。


13
“这些被遗弃的尚未解决的PR只是弄乱一切”我希望更多的维护者有这样的态度😔
GammaGames

34

构建可持续的插件模型需要您的核心框架公开插件可以依赖的稳定接口。黄金法则是,随着时间的推移,您可以引入新的界面,但永远不能修改已经发布的界面。如果按照这个规则,你可以重构实现核心框架的所有你想要的,而不必担心不小心打破插件,无论是财团维护一个或外部之一。

根据您的描述,听起来好像您没有一个定义明确的界面,这使得很难判断更改是否会破坏插件。努力定义此接口并使之在代码库中显式,以便贡献者知道他们不应修改的内容。


20
CI应该具有自动化测试。如果要确保插件具有相同的接口,则每个插件都应提供测试以表达其所需的接口。以这种方式进行操作,并且当界面更改时(它将更改),您将知道要破坏哪些插件。给我这些测试以便在本地运行,在发布PR之前,我会知道我要打破的东西。
candied_orange

1
@lagarkane的明确定义更多是政策问题,而不是技术问题。那里有像您一样的软件,只需放弃升级中的先前行为即可。Perl5与Perl6不兼容,Python2.7与Python3.4等不完全兼容。然后有些软件无论发生什么都仍然支持旧代码。您仍然可以在现代浏览器中运行为Netscape Navigator 4编写的几乎所有javascript代码。Tcl的编程语言是backwords兼容的方式回原来的版本等等
slebetman

3
@lagarkane:组建财团是朝着正确方向迈出的一步,如果核心成员将精力集中在构建这些界面上,那么您就可以利用未来的博士和实习生的力量来保持项目的发展,同时最大程度地减少破损。:)
卡萨布兰卡

4
@Fattie:这对Apple有用,因为他们开发了成功的面向消费者的产品,并且开发人员如果想加入其中,就不得不参与其中。这些开发人员不太可能真正喜欢打破变更,而且绝对不是开源项目的好模型。
卡萨布兰卡

1
@casablanca MacOS和WindowsOS沿袭都很成功。(可以说,这是人类生存中两个最大的产品。)几十年来,它们采取了截然相反的方法。显然两个都是成功的!
Fattie

8

老实说,我认为您不能以更好的方式来处理此问题-如果更改导致您的项目维护部分中断,CI将会失败。

您的项目是否有一个contributing.md或类似项目来帮助新的和偶尔的贡献者准备他们的贡献?您是否有明确的清单,哪些插件是核心的一部分,需要保持兼容?

如果由于依赖关系等原因很难在一台机器上构建所有内容,您可以考虑创建可立即使用的Docker映像作为构建环境以供贡献者使用。


1
谢谢回复!是的,我们确实有公开提供的贡献准则,但是并未按照您的建议列出插件,这已经是一个好主意了。制作docker映像听起来对当前的贡献过程已经有了很大的改进!感谢您的输入
lagarkane

8

因此,当他们提议对内核进行重构更改时(这些日子经常发生),他们在对github发出拉取请求之前检查了代码是否在其计算机上编译了。

因此,我认为这是开放源代码项目的松散风格可以解决的地方。大多数中央组织的项目都对核心重构保持警惕,尤其是当它跨越API边界时。如果它们确实重构了API边界,则通常是一次“大爆炸”,其中所有更改都将在API主版本的基础上递增一次进行调度,维护旧的API。

我会建议一条规则“必须预先计划所有API更改”:如果PR导致对API进行向后不兼容的更改,则是由尚未与维护人员联系以事先同意其方法的人员进行的,只是简单地关闭,提交者指出了规则。

您还将需要插件API的显式版本控制。这使您可以在所有v1插件继续构建和运行的同时开发v2。

我还要提出更多问题,为什么要进行如此多的核心重构和API更改。他们真的有必要还是仅仅是人们在项目中施加个人品味?


2

听起来像CI流程需要更严格,更全面并且对贡献者更有价值,然后才能提出PR。例如,BitBucket具有允许使用此功能的管道功能,您可以在其中提供一个文件,该文件在代码中定义CI构建过程,如果失败,则阻止合并分支。

无论采用哪种技术,在贡献者推送到分支机构时提供自动构建都会使他们更快地反馈进行更改时要注意的问题,并且会导致PR不需要事后进行修复。

设计问题将很容易解决,但与该问题正交。


2

代码工作正常,他们很高兴,然后CI结束了构建,问题开始了:在由联盟维护的插件中编译失败,表明贡献者未在其计算机上构建。

该插件可能依赖于第三方库(例如CUDA),并且用户不希望,不知道该怎么做,或者只是出于硬件原因而不能编译该损坏的插件。

您的解决方案很简单:降低贡献壁垒

(1)加快编辑-编译-测试周期和(2)消除环境差异的最简单方法是提供构建服务器

  • 挑选强大的机器:24、48或96核,2GB RAM /核,SSD,以加快编译速度。
  • 确保他们具有合适的硬件:FPGA,图形卡,无论需要什么。
  • 创建一个预安装了所有必需软件库的Docker映像。

然后向贡献者开放那些构建服务器。他们应该能够远程登录到新的Docker映像,并在此计算机上进行远程编辑-编译-测试。

然后:

  • 他们没有理由不构建/测试维护的插件:他们拥有一切可用的东西。
  • 他们不必等待CI驱动的PR的漫长反馈:它们具有增量编译功能,并且具有调试(而不是猜测)的能力。

通常,构建服务器可以在多个贡献者之间共享,但是,当涉及特殊的硬件外围设备时,贡献者可能有必要自己使用所述外围设备。


资料来源:考虑到野兽的价格以及我们所需的各种型号,使用FPGA进行软件开发,您不会发现每个开发人员的机器上都安装了每种FPGA模型。


1

如果在不更改任何合同的情况下为核心做出贡献会破坏相关软件,则建议:

  • 您的接口合同可能不明确。也许在函数和函数参数上添加属性将有助于向客户端代码公开其他约束,以使合同更加清晰。或者,如果您要应用违反合同的更改,则采用语义版本控制可能会有所帮助。
  • 单元测试未涵盖足够的可能的调用方案。

这两个问题都应该易于解决,但是您提到核心团队可能没有能力解决。一种选择是向社区寻求帮助以解决该问题。


1

似乎没有其他人提出这一潜在解决方案。

  • 列出您可以访问的所有插件。
  • 运行这些插件定义的所有测试
  • 记录核心和所有插件之间的所有请求/响应/交互
  • 存储这些记录,这些现在是粗略的兼容性测试。

在开发内核时,鼓励开发人员运行这些兼容性测试。如果失败,请不要签入。

这不会100%确保兼容性,但是会及早发现更多问题。

第二个好处是,这些记录可以突出显示正在使用的接口以及正在使用的功能。


0

我似乎无法理解这种情况:CI仅建立了一个分支?

您是否有理由不能使用CI构建多个分支?

解决此问题的最简单方法是,使任何贡献者都可以在其功能分支上运行CI构建

然后,您只需在功能分支上成功构建CI,即可接受该分支的请求请求。


这似乎可以总结问题。
Fattie

1
问题说:“或者贡献者更改代码,进入他/她的分支,等待CI完成编译,通常会出现更多错误,并重申该过程,直到CI满意为止”-所以我认为已经是这种情况了,但是问题是,使用如此长的编辑-调试周期进行开发有些痛苦。
npostavs

@npostavs谢谢,我想那是我第一次或两次读到的东西。即使如此,我想我似乎也不是问题。有很多依赖关系,它们不能被打破,因此贡献者需要与所有这些保持兼容。那就是大型软件的本质。当然,也许可以进行一些工作来加快构建速度,但是除此之外,还有什么捷径可走?
Kyralessa
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.