API 领域和版本号地图
概要:依赖于底层库的特定版本号可能过于不精确,并可能在上游库演变和更改时导致麻烦。需要更详细的方法。在这篇文章中,我概述了当前和潜在的工作,旨在更全面地检查基于 API 的需求和库的动态固定。
构成良好版本号的要素
版本号应构成具有以下属性的集合
- 该集合必须是无界的
- 该集合必须是可排序的(可能)
当然,满足这些要求的集合可能无法传达关于它们所代表的软件的太多信息,除了两件事是否等效以及它们的相对年龄。请注意,可能不需要可排序的要求,但在考虑“升级”的想法时通常很有用,因为它提供了新旧软件包之间的清晰界限。在许多情况下,版本号的结构提供了额外的信息。对于某些项目,版本号包括发布的日期,通常使用 日历版本。许多项目使用 语义版本控制,它试图在版本号中编码关于底层源代码 API 的信息。
版本号和 API 固定
指定版本号的最重要位置之一是在 API 固定期间。源代码通常需要它使用的库的特定 API。这需要一个 pin 来指定可以使用哪些版本的底层库。然后,包管理器使用这些 pin 来确保创建兼容的环境。
然而,这些 pin(甚至缺少 pin)都会产生问题。首先,这些 pin 是关于当前和未来软件包的全局生态系统的一次性本地声明。例如,将 scipy
固定到当前主要版本号可能无法长期维持,较新版本的 scipy
可能会破坏 API,而不会更改主要版本号。同样,缺少 scipy
的 pin 可能是错误的,因为 API 可能会被破坏。即使建立牢固的上限和下限的 pin 也可能是错误的,因为新版本的固定库恢复了丢失的 API。对于将 pin 与特定版本的源代码绑定的依赖项系统来说,这些问题尤其成问题,需要创建一个新版本来更新 pin。Conda-Forge 能够通过 repodata 补丁 避免其中一些问题,动态更新软件包声明的需求。总的来说,这个过程充满了风险,因为每个软件包都依赖于库 API 的不同部分,破坏一个软件包的版本升级可能会让其他软件包毫发无损。
潜在的前进道路
以上所有问题都是由 地图与疆域关系 的混淆引起的。在这种情况下,地图(库的版本号)无法准确地代表疆域(API 本身)。为了解决这个问题,我们需要更准确地描述疆域。实现这一点并非易事,但我认为有一种方法可以足够接近以限制错误的数量。
我们需要一种程序化的方法来检查特定库的特定版本是否提供了所需的 API。我认为这可以迭代实现,每个步骤都提供额外的清晰度和实现难度。请注意,在下面的步骤中,我以 python 打包为例,但我认为这些步骤足够通用,可以应用于其他语言和生态系统。
- 确定代码需要的库,这由 depfinder 等工具提供,并且正在集成到 Conda-Forge 机器人系统中(尽管它们仍然是高度实验性的并且正在开发中)。
- 确定库的版本是否提供了所需的模块。这可以通过使用 depfinder 查找导入并使用 libcfgraph 提供的导入名称和发布这些导入的软件包版本之间的映射来完成。
- 确定导入的模块是否提供了正在导入的符号。这将需要列出给定 python 模块中的所有符号,包括顶级作用域变量、函数名称、类名称、方法等。
- 对于可调用对象,确定使用的调用签名是否与方法或函数定义匹配。
depfinder 项目在这条道路上取得了重大进展,提供了一个易于使用的工具,可以从源代码中提取准确的导入和软件包需求数据。Depfinder 甚至有处理代码块中导入的情况,这些代码块可能会使需求成为可选的或使用 python 标准库。depfinder 的未来工作,包括使用更准确的导入和软件包名称之间的映射以及提供软件包需求的元数据(这些元数据是完全详尽的,例如在 try: except:
块中导入 pyqt4
与 pyqt5
),将提供更准确的需求信息。
在上述每个阶段,我们都可以通过帮助用户、维护者和源代码作者保持其需求一致并在发生冲突时发出警告来为他们提供重要的价值。当创建导入库的新版本时,Conda-Forge 可以更新其 repodata,以正确表示该版本是否与其下游消费者 API 兼容。此外,可以向可能想要修补自己的元数据或检查一段源代码在其需求中是否自洽的第三方消费者提供列出所有符号和调用签名的表。这将有助于放宽 pin,为 Conda-Forge 和其他打包生态系统创建更多可解决的环境。此外,随着此工具的成熟和变得更加准确,它可以被纳入 Conda-Forge 机器人系统,以在版本升级和 repodata 补丁期间自动更新依赖项,从而帮助减轻维护负担。
从符号表构建的工具还可以产生远远超出 Conda-Forge 的影响。例如,符号表可以允许源代码作者逐行检查他们的代码,揭示哪些行强制使用旧版本或新版本的依赖项。这可以实现大规模的源代码迁移,并具有外科手术般的精度,使开发人员能够提取和重写少量代码,从而阻止使用新版本的库。
注意事项
这种方法有一些重要的注意事项需要牢记。
- 所有这些工作都旨在了解给定库的 API,这种方法无法深入了解 API 内部的代码,或者那里的更改是否会影响下游消费者。例如,修复库代码中的错误和安全漏洞的版本更新可能根本不会更改 API。从这个工具的角度来看,没有理由升级,因为 API 没有不同。当然,在这种情况下有强烈的升级理由,因为有缺陷或易受攻击的库可能会给下游代码带来巨大的麻烦和责任,应该尽快删除。
- 某些功能可能取决于社区的更广泛采用。例如,这种方法将从 python 类型提示中受益匪浅,因为 API 可以被约束到预期的类型。这样的类型约束将为 API 版本范围提供更高的准确性,因为可以检测到任何更改。但是,类型提示可能不会在 python 社区中以足够高的速率被采用,以至于对于此应用程序真正有用。
- 源代码从根本上来说是灵活的。可能存在即使这种方法也无法解决的代码难题,尤其是在多种语言和运行时模块加载进入视野时。我个人的希望是,代码能够识别何时发生这些情况,提供其对正在发生的事情的最佳猜测,并向用户提供足够的元数据,以便他们了解结果准确性的降低。从根本上来说,该工具只能为用户提供非常有根据的猜测和上下文,然后用户需要去弄清楚代码内部实际发生了什么。
结论
基于版本号的 pin 是 API 兼容性的不精确表示。基于源代码检查的更精确的表示将使 Conda-Forge 生态系统更加健壮和灵活,同时减少维护负担。实现这一目标的部分路径已经建成,并且可以使用当前的工具和数据库来实现近期的步骤。