ABI 稳定性
简介
应用程序二进制接口 (ABI) 是一种允许程序调用其他编译程序中的函数并使用数据结构的方式。它是应用程序编程接口 (API) 的编译版本。换句话说,描述类、函数、数据结构、枚举和常量的头文件,这些头文件使应用程序能够执行所需的任务,通过编译对应于一组地址和预期的参数值以及内存结构大小和布局,这些地址和布局是 ABI 提供者编译的。
使用 ABI 的应用程序必须进行编译,以使可用的地址、预期的参数值以及内存结构大小和布局与 ABI 提供者编译的那些一致。这通常通过针对 ABI 提供者提供的头文件进行编译来实现。
由于 ABI 的提供者和 ABI 的用户可能在不同的时间使用不同的编译器版本进行编译,因此确保 ABI 兼容性的一部分责任在于编译器。不同的编译器版本(可能由不同的供应商提供)必须从具有特定内容的头文件中生成相同的 ABI,并且必须为使用 ABI 的应用程序生成代码,该代码根据 ABI 的约定访问给定头文件中描述的 API,该 ABI 来自头文件中的描述。现代编译器在不破坏其编译的应用程序的 ABI 兼容性方面有着良好的记录。
确保 ABI 兼容性的剩余责任在于维护提供 API 的头文件的团队,该 API 在编译后会生成要保持稳定的 ABI。可以对头文件进行更改,但必须密切跟踪更改的性质,以确保在编译后,ABI 不会以使现有 ABI 用户与新版本不兼容的方式发生更改。
Node.js 中的 ABI 稳定性
Node.js 提供由多个独立团队维护的头文件。例如,node.h
和 node_buffer.h
等头文件由 Node.js 团队维护。v8.h
由 V8 团队维护,该团队虽然与 Node.js 团队密切合作,但却是独立的,并且有自己的时间表和优先事项。因此,Node.js 团队只能部分控制项目提供的头文件中引入的更改。因此,Node.js 项目采用了语义版本控制。这确保了项目提供的 API 将为同一个主版本中发布的所有次要版本和修补程序版本生成稳定的 ABI。实际上,这意味着 Node.js 项目已承诺确保针对特定主版本 Node.js 编译的 Node.js 本地插件在由其编译的任何 Node.js 次要版本或修补程序版本加载时都能成功加载。
N-API
人们需要为 Node.js 提供一个 API,该 API 会生成一个在多个 Node.js 主版本中保持稳定的 ABI。创建此类 API 的动机如下
-
JavaScript 语言从其早期开始就一直与自身兼容,而执行 JavaScript 代码的引擎的 ABI 会随着每个 Node.js 主版本的发布而发生变化。这意味着完全用 JavaScript 编写的 Node.js 包组成的应用程序无需重新编译、重新安装或重新部署,因为新的 Node.js 主版本被放入这些应用程序运行的生产环境中。相反,如果应用程序依赖于包含本地插件的包,则每当在生产环境中引入新的 Node.js 主版本时,应用程序都必须重新编译、重新安装和重新部署。这种包含本地插件的 Node.js 包与完全用 JavaScript 编写的 Node.js 包之间的差异增加了依赖本地插件的生产系统的维护负担。
-
其他项目已经开始生产本质上是 Node.js 替代实现的 JavaScript 接口。由于这些项目通常构建在与 V8 不同的 JavaScript 引擎上,因此它们的原生插件必然采用不同的结构并使用不同的 API。然而,在 Node.js JavaScript API 的不同实现中使用单个 API 来实现原生插件将使这些项目能够利用围绕 Node.js 积累的 JavaScript 包生态系统。
-
Node.js 未来可能会包含不同的 JavaScript 引擎。这意味着,从外部来看,所有 Node.js 接口将保持不变,但 V8 头文件将不存在。如果 Node.js 首先没有提供一个与 JavaScript 引擎无关的 API 并被原生插件采用,那么这样的步骤将导致 Node.js 生态系统,特别是原生插件生态系统的混乱。
为了实现这些目标,Node.js 在 8.6.0 版本中引入了 N-API,并在 Node.js 8.12.0 中将其标记为项目的稳定组件。该 API 在头文件 node_api.h
和 node_api_types.h
中定义,并提供跨越 Node.js 主版本边界的向前兼容性保证。该保证可以表述如下
给定版本的 N-API 将在发布它的 Node.js 主版本中可用,并在所有后续版本的 Node.js 中可用,包括后续的主版本。
原生插件作者可以通过确保插件仅使用 node_api.h
中定义的 API 以及 node_api_types.h
中定义的数据结构和常量来利用 N-API 向前兼容性保证。通过这样做,作者可以通过向生产用户表明,在他们的项目中添加原生插件不会比添加纯 JavaScript 编写的包增加更多的维护负担,从而促进他们的插件的采用。
N-API 是有版本的,因为新的 API 会不时添加。与语义版本控制不同,N-API 版本控制是累积的。也就是说,每个版本的 N-API 都与 semver 系统中的次要版本具有相同的含义,这意味着对 N-API 的所有更改都将向后兼容。此外,新的 N-API 在实验性标志下添加,以便社区有机会在生产环境中对其进行验证。实验性状态意味着,尽管已经采取措施确保新的 API 在未来不会以 ABI 不兼容的方式进行修改,但它尚未在生产环境中得到充分验证,以证明其设计正确且有用,因此,在最终合并到即将发布的 N-API 版本之前,可能会进行 ABI 不兼容的更改。也就是说,实验性的 N-API 尚未涵盖在向前兼容性保证中。