什么时候(不)适合将依赖项与应用程序捆绑在一起?

Posted

技术标签:

【中文标题】什么时候(不)适合将依赖项与应用程序捆绑在一起?【英文标题】:When is it (not) appropriate to bundle dependencies with an application? 【发布时间】:2010-10-10 13:02:13 【问题描述】:

总结

我最近与我的一个应用程序所依赖的框架的创建者进行了交谈。在那次谈话中,他顺便提到,如果我将他的框架与我的应用程序捆绑在一起,并向最终用户交付一个我知道与我的代码一致的版本,这将使我的生活更简单。直觉上,我一直试图避免这样做,事实上,我一直在努力分割自己的代码,以便在不占用整个项目的情况下重新分配部分代码(即使几乎没有任何人重用任何代码的机会)它)。然而,在考虑了一段时间之后,我无法想出一个特别好的理由为什么我这样做。事实上,现在我已经考虑过了,我看到了一个非常有说服力的案例来捆绑所有我较小的依赖项。我已经列出了利弊清单,希望有人能指出我遗漏的任何内容。

优点

版本的一致性意味着更容易 测试和故障排除。 应用范围可能更广 观众,因为似乎有 需要安装的组件更少。 对依赖项的小调整可以 更容易在下游和 随应用程序交付, 而不是等待他们 渗透到上游代码库中。

缺点

包含更复杂的封装过程 依赖关系。 用户可能会得到多个副本 对他们机器的依赖。 根据 bortzmeyer 的回复,无法升级单个组件可能存在安全隐患。

备注

作为参考,我的应用程序是用 Python 编写的,并且我引用的依赖项是“轻量级”的,我的意思是很小且不常用。 (所以它们并不存在于所有机器上,甚至不存在于所有存储库中。)当我说“打包”我的应用程序时,我的意思是在我自己的源代码树下分发,而不是使用驻留在我的包内的脚本安装,所以会有不可能有冲突的版本。我也只在 Linux 上进行开发,因此无需担心 Windows 安装问题。

话虽如此,我也很想听听关于更广泛的(与语言无关的)打包依赖问题的任何想法。是我遗漏了什么,还是我只是想多了一个简单的决定?

附录 1

值得一提的是,我对下游打包者的需求也相当敏感。我希望尽可能简单地将应用程序封装在特定于发行版的 Deb 或 RPM 中。

【问题讨论】:

【参考方案1】:

在将库/框架/等与应用程序捆绑在一起的缺点中似乎忘记了一个重要点:安全更新。

大多数 Web 框架都充满了安全漏洞,需要经常打补丁。无论如何,任何库都可能因安全漏洞而不得不升级。

如果您不捆绑,系统管理员只会升级库的一份副本并重新启动依赖的应用程序。

如果您捆绑,系统管理员可能甚至不会知道他们必须升级一些东西。

因此,捆绑的问题不在于磁盘空间,而是让旧的和危险的副本四处走动的风险。

【讨论】:

这是一个很好的观点。在我的特定情况下,我不认为这是相关的(因为相关的依赖项没有连接到网络,也没有在本地机器上授予任何权限),但它仍然是每个人都需要牢记的。我已经更新了 OP。【参考方案2】:

对于 Linux,甚至不要考虑捆绑。你并不比包管理器或打包器聪明,而且每个发行版都采用自己的方式——如果你试图按照自己的方式行事,他们不会高兴的。充其量,他们不会费心打包你的应用程序,这不是很好。

请记住,在 Linux 中,会自动为您提取依赖项。这不是让用户得到它们的问题。它已经为你完成了。

对于 Windows,请随意捆绑,你自己在那里。

【讨论】:

我非常感谢您对我的博客和 Vadi 的反馈,但我将不得不在这里达成共识并且不同意您的意见。它与“智能”无关,它关乎便利性,我还没有看到任何令人信服的反对捆绑不常见依赖项的论据。 鉴于我所指的依赖项的相对稀缺性和低优先级,打包人员不必担心它们似乎是一个福音。磁盘空间很小,因此唯一可能的争论点可能是方法的纯度。我不会捆绑 GTK... 发行版可能会在上游得到它之前修复他们提供的依赖项之一中的错误,并且您的用户不会获得好处。当它违反所有合理的法律时,也没有多少人愿意打包它。否则,每个人都会开始捆绑,它只会降级。 这是一个完全正确的观点,但我认为发生这种情况的可能性非常小,以至于不需要牺牲我通过包含依赖项获得的实际好处。如果我后来证明我离题了,我总是可以改变这个决定。 仅供参考,我还在我的博客上回复了你的贬义 cmets,因为我认为它们不适合这个场所。【参考方案3】:

如果您为最终用户制作软件,目标是让客户使用您的软件。任何阻碍都会适得其反。如果他们必须自己下载依赖项,他们可能会决定避免使用您的软件。您无法控制库是否向后兼容,并且您不希望您的软件因为用户更新了他们的系统而停止工作。同样,您不希望客户使用旧库安装旧版本的软件并让系统的其余部分中断。

这意味着捆绑通常是要走的路。如果您可以确保您的软件在不捆绑依赖项的情况下顺利安装,并且工作量更少,那么这可能是一个更好的选择。关键是什么能让您的客户满意。

【讨论】:

【参考方案4】:

小心重现经典的 Windows DLL 地狱。一定要尽量减少依赖的数量:理想情况下,只依赖于你的语言及其框架,如果可以的话,别无其他。

毕竟,保留硬盘空间已不再是目标,因此用户不必关心拥有多个副本。此外,除非您的用户数量很少,否则请务必自行承担打包的责任,而不是要求他们获取所有依赖项!

【讨论】:

【参考方案5】:

我总是包含我的 Web 应用程序的所有依赖项。这不仅使安装更简单,而且即使系统上的其他组件升级,应用程序也能保持稳定并按照您期望的方式工作。

【讨论】:

【参考方案6】:

我赞成捆绑依赖关系,if 使用系统进行自动依赖关系解析(即 setuptools)是不可行的,而 if 您可以在不引入版本冲突的情况下做到这一点。您仍然需要考虑您的应用程序和您的受众;认真的开发人员或爱好者更有可能希望使用特定(最新)版本的依赖项。捆绑东西可能会让他们讨厌,因为这不是他们所期望的。

但是,特别是对于应用程序的最终用户,我严重怀疑大多数人是否喜欢搜索依赖项。就复制副本而言,我宁愿多花 10 毫秒下载一些额外的千字节,或者在额外兆的磁盘空间上花费一分钱,而不是花 10 多分钟在网站上搜索(可能会下降) )、下载、安装(如果版本不兼容可能会失败)等。

我不在乎我的磁盘上有多少个库副本,只要它们不妨碍彼此。磁盘空间非常非常便宜。

【讨论】:

如果许可证兼容。 如果您在安全漏洞需要升级捆绑的库/框架时向系统管理员“回调”。如果框架 X 突然需要紧急安全升级,并且您在不同的地方捆绑了 N 个它的副本,那将是一场噩梦。【参考方案7】:

您不能只依赖这些依赖项的某个版本吗?例如。在带有setuptools 的 Python 中,您可以指定它需要的确切版本,甚至可以给出一些条件,例如 等。这当然仅适用于 Python 和特定的包管理器,但我个人总是首先尝试不捆绑所有内容。通过将其作为 Python Egg 发布,您还将自动安装所有依赖项。

您当然也可以使用双向策略来为您自己的包提供依赖项的链接,但仍以某些安装程序(如时尚)提供完整的设置。但即便如此(在 python 案例中),我还是建议简单地将鸡蛋与它捆绑在一起。

有关鸡蛋的一些介绍,请参阅this post of mine。

当然,这是 Python 特有的,但我认为其他语言可能也有类似的打包工具。

【讨论】:

感谢您对 Eggs 的介绍。我对他们很熟悉,但这是一个很好的复习。我认为我对这种包装方法的主要担忧是我不清楚它如何影响下游包装商。 (例如 Ubuntu)他们是包装 Egg 还是你自己整理依赖关系? 我从来没有将自己的东西打包为 deb 或 rpm(主要是因为我通常只安装 easy_install 然后让 easy_install 处理它,因为它通常会获取更新的包)。但我假设您将依赖关系树镜像为 rpms/debs。也使其更加模块化。【参考方案8】:

只是我的经验,持保留态度。

我对我编写的几个开源库的偏好是尽可能地独立于其他库。原因是,我不仅要与我的其他库一起分发其他库,而且我还必须更新我的应用程序以实现兼容性,因为其他库也已更新。

从我使用的其他库中携带“通用”库的依赖项,我最终总是需要在我的系统上安装多个版本的通用库。我使用的小众库的相对更新速度并没有那么快,而普通库的更新频率更高。版本控制地狱。

但这是泛泛而谈。如果我现在可以通过合并依赖项来帮助我的项目和用户,我一直在关注该决定的下游和后期影响。如果我可以管理它,我可以自信地包含依赖项。

与往常一样,您的里程可能会有所不同。

【讨论】:

也许我误解了你的第一个完整段落。自己分发依赖项不会缓解这个问题,因为您的 not 有义务更新您的依赖项副本,除非/直到您认为合适?或者你是说,“不要一开始就依赖”(如果你能避免的话)? 抱歉含糊不清。这可能取决于依赖的性质。如果依赖项保持向后兼容性,那么是的,它确实缓解了问题;如果向后兼容性被破坏,那么您就有维护问题。 通常,我尽量避免引入依赖项。这是一个权衡,并且再次取决于上下文。我从不想编写不必要的代码,但我会在这与将另一个库与我的项目捆绑在一起的影响之间取得平衡。

以上是关于什么时候(不)适合将依赖项与应用程序捆绑在一起?的主要内容,如果未能解决你的问题,请参考以下文章

如何捆绑包含依赖项的 Python 应用程序?

如何将 React 应用程序与汇总捆绑在一起

如何将 UI 与微服务捆绑在一起

框架与捆绑包

如何将第三方二进制文件与 Electron 捆绑在一起?

将捆绑包与 xibs XCode5 一起使用