开源依赖管理是一项平衡之术

409 位读者喜欢这篇文章。
A diagram of a bug.

Opensource.com

在我的职业生涯中,我花了很多时间打包别人的代码、编写自己的代码以及从事大型软件框架的工作。我见过一些项目仍然没有发布稳定版本,始终没有达到 1.0 版本,而另一些项目在开始开发的几个月内就发布了 1.0 版本,然后迅速转向 2.0、3.0 等版本。这些发布周期存在很大差异,再加上维护大型项目,可能会使事情变得困难。

我将介绍我在工作中遇到的项目中的一些决策以及项目面临的压力。一方面,用户希望拥有一个永不改变的稳定 API,其依赖项不指定最低版本,以便他们可以选择最适合的版本。另一方面,我们被推动使用语言、硬件加速 API、编译器以及我们所依赖的库的最新功能。

您应该在第一天就使用这些最新版本,还是给您的用户、发行版和其他用户时间来更新?当 API 更改变得必要时,您是立即更改它,还是等待并将更改批量放入主要版本中,并在其中发出适当的警告,从而降低 API 更改的频率?

开放化学与 C++11

当我们开始开发 Open Chemistry 项目时,我们非常认真地考虑了要求使用 C++11,当时我们社区中的几个人劝阻了我。我们最终使用了 C++11 的一些小部分,这些部分可以设为可选,并回退到 Boost 实现/空宏定义。当时我认为这可能有点过于激进,但如果我可以回到过去,我会告诉以前的自己去尝试。该项目是新的,现有用户很少,并且主要针对桌面。此外,采用通常需要几年时间,并且存在支持旧编译器的成本。

几个主要项目,例如 Qt,仅在去年才开始要求使用 C++11,并且越来越多项目将在前后一年内这样做。我认为现在很明显,优势大于成本,但是要求使用非常新的编译器可能很难推销。随着 Microsoft 发布了具有完整 C++11 支持的 Visual Studio 2015 社区版,这种情况变得更容易了,而 Linux 和 Mac 已经有支持良好的 C++11 编译器一段时间了。Linux 发行版的安全和长期支持版本仍然带来一些挑战,但这在很大程度上现在已成为一个已解决的问题。

我已经使我们的其余依赖项与 Visual Studio 2015(仍然是最难让依赖项良好运行的平台)一起工作,并将很快删除我们的可选 Boost 回退代码。现在还有其他明显的优势,例如 PyBind11 项目,该项目为 C++ 提供了一个仅标头的 Python 包装 API,除了 C++11 之外没有其他依赖项。语言的更改也使一些本地实现变得无关紧要,并消除了使用线程、原子和其他新功能时对复杂回退的需求。C++14 的问题仍然迫在眉睫,我倾向于直接跳到 C++14,因为它看起来我们所有的平台现在都具有良好的支持。C++14 看起来更像是一个错误修复版本,但它包含一些很棒的修复!

第三方库

作为在 Gentoo Linux 上打包软件多年的程序员,如果您在项目中捆绑了一堆第三方库,我会诅咒您的名字。作为一名软件开发人员,我完全理解项目为什么要这样做,以及这样做如何使启动和运行变得更加容易。如果项目行为良好,开发人员将提出一个解决方案,该解决方案默认构建捆绑库,并使打包人员和其他人可以轻松使用系统库。

这依赖于第三方库的上游维护稳定的 API,下游项目坚持这些稳定的 API,以及在下游项目中快速使用的定期发布版本。对于 Linux 发行版,理想情况下,安全修复应该仅在一个或两个位置应用,然后所有依赖项目要么重新构建,要么在下次重新加载时简单地链接到更新的库。

当库的 API 发生更改,或者当项目的开发人员不定期更新其第三方库时(或者在 API 更改迫使依赖项更新时更新太频繁时),这种情况变得更加困难。理想的解决方案显然是使用具有定期发布周期的项目,快速集成其新版本,理想情况下,维护一个稳定的 API,该 API 在可以使用的库版本中提供一定的灵活性。

在 Windows 和 Mac OS X 平台上,这些库在软件包安装后很少更新,因此跟踪上游并确保集成任何安全修复程序甚至更为重要。这样做具有挑战性,这也是我一直使用 CMake 的外部项目来构建依赖项的原因之一,这些依赖项提供了捆绑第三方库的许多优势,并且更易于集成更新版本。

OpenGL 和科学可视化

大约两年前,我撰写了一篇论文,讨论了 Visualization Toolkit (VTK) 的渲染后端的重写。我认为这种重写早就应该进行了,并且作为在加入 Kitware 之前编写过渲染代码的人,我发现我们无法使用 OpenGL 的最新进展(或者至少是过去十年的某些进展)令人沮丧。我知道我感兴趣的几个渲染工作负载可以从更新中受益,并且这似乎是普遍适用的。

我开始研究我们如何进行一些增量更改,但问题在于 OpenGL 的变化非常大,以至于这很困难。其他人也同意需要重写,但认为需要更多资金来重写所有渲染代码。这是一项相当大的任务,至今仍未完成。已经取得了很大的进展,但是总是有人想要新功能、优化以及正在编写的新渲染驱动程序部件,以充分利用最新的显卡。

我们将新的渲染后端称为 OpenGL2,旧的后端主要是 OpenGL 1.1,带有一些新功能的运行时检测。最初,我们的目标是桌面上的 OpenGL 2.1 和嵌入式系统上的 OpenGL ES 2.0,这使 OpenGL2 感觉非常合适。此外,从我的许多研究中,我清楚地发现 OpenGL 2.1 和 OpenGL ES 2.0 具有许多其他项目使用的通用 API 子集。随着项目的发展,人们希望使用越来越多来自 OpenGL 3.2 和 OpenGL ES 3.0 的功能——新的最低要求不断发展。

我们在一些我们希望部署到的系统/芯片上遇到了问题,并且软件渲染有时具有挑战性。总的来说,我们已经能够使事情正常运行。这归结为精细的平衡,极端的两端可能很清楚,但中间地带通常感觉更难定义。推向任何一个极端都有实际成本,我认为 VTK 由于使用非常旧的 API 而面临被抛在后面的危险,并且如果我们下周开始要求使用 OpenGL 4.4,我们将远远超出频谱的另一端。

Qt 3 vs. 4 vs. 5

在我的开发生涯中,我看到了 Qt 的三个主要版本,其中从 3 到 4 的过渡最具挑战性。这些更新中的每一个都涉及相当大的更改,但在 4 到 5 的过渡中则少得多。Avogadro 2 项目在几年前迁移到了 Qt 5,因为它更新(它始于 4,这是另一个案例,回顾起来,我希望我们早点向前迈进一步)。在 Tomviz 项目中,我们重用了 ParaView,并且一直使用 Qt 4,直到大约一年前,那时我们完成了迁移到 5 的工作。

ParaView 项目本身仍然默认使用 Qt 4,并且在现代操作系统上支持 Qt 4 变得越来越困难。他们已经同时支持两者一段时间了,但是他们仍然有一些问题阻碍了他们。现代操作系统已经发生了重大变化,高分辨率显示器和用户界面更新都导致与 Qt 4 目标产生重大分歧。

他们还对 Qt 与主机操作系统的集成方式以及它与 OpenGL 的交互方式进行了重大更改。再加上线程模型的重大更改以及利用 C++11 及更高版本中的新功能进行的更新,同时支持两个主要版本可能会很困难。

Python 2 vs. 3

这让我夜不能寐,并且一些项目不得不选择跳向哪一方或跨越两者,以便他们可以同时提供两者。在 VTK 项目中,我们的生成器代码必须更新,并且我们能够提供 Python 2 或 3,但尚未投入工作来同时提供两者。ParaView 项目建立在 VTK 之上,最近更新了其代码以同时支持 Python 2 和 3,但是 Python 3 支持在那里仍然非常实验性。我希望在未来一两个月内更新 Tomviz 以提供对 Python 3 的支持,并且随着越来越多的 Python 模块切换到仅 Python 3,这变得越来越重要。

在 Web 方面,我参与了一个扩展 Girder 平台的项目,该平台本身使用 CherryPy、PyMongo 和许多其他模块。当我们启动该项目时,它仅支持 Python 2,但是他们在此后不久添加了对 Python 3 的支持——使我们能够切换到 Python 3。在那里,我们决定不支持两者,因为它是一个新项目,并且我们知道它在短期内不太可能被广泛使用。在 Google Summer of Code 导师峰会上,Python 社区中的几个人对这种情况表示沮丧,并表示他们对支持两者和放弃新语言功能的成本是否值得感到矛盾。

结束语

希望我们能够保持良好的中间立场,最好地服务于我们的用户,并意识到过于保守或过于激进的成本。大多数开发人员都渴望使用最新功能,并且知道存在更好的方法但无法采用可能会非常令人沮丧。我认为过于保守会付出巨大的代价,但是我见过其他一些项目更新和更改过于激进,以至于失去了市场份额。

标签
Marcus D. Hanwell
Marcus D. Hanwell | Marcus 领导 Open Chemistry 项目,开发用于化学、生物信息学和材料科学研究的开源工具。

2 条评论

人们为什么要提供 API?大概是因为他们希望人们使用它们。他们提供什么来使 API 对其他人有用?当然是功能。但这只是使其有用的一个方面。稳定性必定是另一个方面。如果有人知道 API 的作者会不断更改它,谁会想承诺使用特定的 API?提供 API 应该被视为带有维护稳定性的隐含承诺。

正如我在文章中说的那样,在理想的世界中,API 将永远稳定,但现实情况是这种情况很少发生。新技术被开发出来,更好的批处理命令、组合数据等方法。如果您不改进您的 API 以利用这些优势,那么人们通常会转向那些这样做的库,这就是为什么大多数库都使用版本号来表示可能发生的变化 - 主要版本可能是 API 更改,次要版本通常是对 API 的添加,错误修复版本仅修复错误/问题。存在一个隐含的承诺,但也存在一个隐含的承诺,即继续开发库、添加功能以及利用新技术/方法/想法。

回复 作者:igoddard (未验证)

Creative Commons 许可协议本作品根据 Creative Commons Attribution 4.0 International License 许可协议获得许可。
© . All rights reserved.