ABI 稳定性
简介
应用程序二进制接口 (ABI) 是一种程序调用其他已编译程序的函数和使用数据结构的方式。它是应用程序编程接口 (API) 的编译版本。换句话说,描述类、函数、数据结构、枚举和常量的头文件(这些文件使应用程序能够执行所需的任务)通过编译对应于一组地址和预期的参数值以及内存结构大小和布局,而 ABI 提供者正是以此进行编译的。
使用 ABI 的应用程序必须进行编译,以便可用的地址、预期的参数值以及内存结构大小和布局与 ABI 提供者编译时使用的那些一致。这通常通过针对 ABI 提供者提供的头文件进行编译来实现。
由于 ABI 提供者和 ABI 用户可能在不同的时间使用不同版本的编译器进行编译,因此确保 ABI 兼容性的一部分责任在于编译器。不同版本的编译器(可能由不同的供应商提供)必须都从具有特定内容的头文件中生成相同的 ABI,并且必须为使用 ABI 的应用程序生成代码,该代码根据由头文件中的描述生成的 ABI 的约定访问给定头文件中描述的 API。现代编译器在不破坏其编译的应用程序的 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 将为一个主版本内发布的所有次要版本和补丁版本 Node.js 提供稳定的 ABI。实际上,这意味着 Node.js 项目已承诺确保针对给定 Node.js 主版本编译的 Node.js 原生插件在由其编译所针对的主版本内的任何 Node.js 次要版本或补丁版本加载时都能成功加载。
N-API
迫切需要为 Node.js 提供一个 API,使其生成的 ABI 能够在多个 Node.js 主版本之间保持稳定。创建此类 API 的动机如下:
JavaScript 语言从早期至今一直保持着向后兼容性,而执行 JavaScript 代码的引擎的 ABI 却随着每个 Node.js 主版本的发布而发生变化。这意味着完全用 JavaScript 编写的 Node.js 包组成的应用程序无需在新的 Node.js 主版本部署到生产环境时重新编译、重新安装或重新部署。相反,如果应用程序依赖于包含原生插件的包,则每当将新的 Node.js 主版本引入生产环境时,就必须重新编译、重新安装和重新部署该应用程序。Node.js 包中包含原生插件的包与完全用 JavaScript 编写的包之间的这种差异增加了依赖于原生插件的生产系统的维护负担。
其他项目已开始制作本质上是 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 的给定版本 n 将在其发布的 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 尚未涵盖向前兼容性保证。