Skip to content

Estabilidade da ABI

Introdução

Uma Interface Binária de Aplicação (ABI) é uma maneira de programas chamarem funções e usarem estruturas de dados de outros programas compilados. É a versão compilada de uma Interface de Programação de Aplicação (API). Em outras palavras, os arquivos de cabeçalho que descrevem as classes, funções, estruturas de dados, enumerações e constantes que permitem que um aplicativo execute uma tarefa desejada correspondem, por meio da compilação, a um conjunto de endereços e valores de parâmetros esperados e tamanhos e layouts de estrutura de memória com os quais o fornecedor da ABI foi compilado.

O aplicativo que usa a ABI deve ser compilado de forma que os endereços disponíveis, os valores de parâmetros esperados e os tamanhos e layouts de estrutura de memória concordem com aqueles com os quais o fornecedor da ABI foi compilado. Isso geralmente é feito compilando com os cabeçalhos fornecidos pelo fornecedor da ABI.

Como o fornecedor da ABI e o usuário da ABI podem ser compilados em momentos diferentes com versões diferentes do compilador, parte da responsabilidade por garantir a compatibilidade da ABI fica com o compilador. Diferentes versões do compilador, talvez fornecidas por diferentes fornecedores, devem produzir a mesma ABI a partir de um arquivo de cabeçalho com um determinado conteúdo e devem produzir código para o aplicativo que usa a ABI que acessa a API descrita em um determinado cabeçalho de acordo com as convenções da ABI resultante da descrição no cabeçalho. Os compiladores modernos têm um histórico bastante bom de não quebrar a compatibilidade da ABI dos aplicativos que compilam.

A responsabilidade restante por garantir a compatibilidade da ABI fica com a equipe que mantém os arquivos de cabeçalho que fornecem a API que resulta, após a compilação, na ABI que deve permanecer estável. Alterações nos arquivos de cabeçalho podem ser feitas, mas a natureza das alterações deve ser acompanhada de perto para garantir que, após a compilação, a ABI não mude de uma forma que torne os usuários existentes da ABI incompatíveis com a nova versão.

Estabilidade ABI no Node.js

O Node.js fornece arquivos de cabeçalho mantidos por várias equipes independentes. Por exemplo, arquivos de cabeçalho como node.h e node_buffer.h são mantidos pela equipe do Node.js. v8.h é mantido pela equipe do V8, que, embora em estreita cooperação com a equipe do Node.js, é independente, com seu próprio cronograma e prioridades. Assim, a equipe do Node.js tem apenas controle parcial sobre as alterações introduzidas nos cabeçalhos que o projeto fornece. Como resultado, o projeto Node.js adotou o versionamento semântico. Isso garante que as APIs fornecidas pelo projeto resultarão em um ABI estável para todas as versões menores e de patch do Node.js lançadas dentro de uma versão principal. Na prática, isso significa que o projeto Node.js se comprometeu a garantir que um addon nativo do Node.js compilado contra uma determinada versão principal do Node.js será carregado com sucesso quando carregado por qualquer versão menor ou de patch do Node.js dentro da versão principal contra a qual foi compilado.

N-API

Surgiu a demanda por equipar o Node.js com uma API que resulte em um ABI que permanece estável em várias versões principais do Node.js. A motivação para criar tal API é a seguinte:

  • A linguagem JavaScript manteve-se compatível consigo mesma desde os seus primórdios, enquanto o ABI do motor que executa o código JavaScript muda a cada versão principal do Node.js. Isso significa que aplicativos consistindo em pacotes Node.js escritos inteiramente em JavaScript não precisam ser recompilados, reinstalados ou reimplementados à medida que uma nova versão principal do Node.js é inserida no ambiente de produção em que esses aplicativos são executados. Em contraste, se um aplicativo depende de um pacote que contém um addon nativo, o aplicativo tem que ser recompilado, reinstalado e reimplementado sempre que uma nova versão principal do Node.js é introduzida no ambiente de produção. Essa disparidade entre os pacotes Node.js que contêm addons nativos e aqueles que são escritos inteiramente em JavaScript aumentou a carga de manutenção dos sistemas de produção que dependem de addons nativos.

  • Outros projetos começaram a produzir interfaces JavaScript que são essencialmente implementações alternativas do Node.js. Como esses projetos geralmente são construídos em um mecanismo JavaScript diferente do V8, seus addons nativos necessariamente assumem uma estrutura diferente e usam uma API diferente. No entanto, usar uma única API para um addon nativo em diferentes implementações da API JavaScript do Node.js permitiria que esses projetos aproveitassem o ecossistema de pacotes JavaScript que se acumulou em torno do Node.js.

  • O Node.js pode conter um mecanismo JavaScript diferente no futuro. Isso significa que, externamente, todas as interfaces do Node.js permaneceriam as mesmas, mas o arquivo de cabeçalho do V8 estaria ausente. Tal passo causaria a interrupção do ecossistema Node.js em geral, e dos addons nativos em particular, se uma API agnóstica do mecanismo JavaScript não for primeiro fornecida pelo Node.js e adotada pelos addons nativos.

Para esses fins, o Node.js introduziu a N-API na versão 8.6.0 e a marcou como um componente estável do projeto a partir do Node.js 8.12.0. A API é definida nos cabeçalhos node_api.h e node_api_types.h, e fornece uma garantia de compatibilidade futura que cruza o limite da versão principal do Node.js. A garantia pode ser declarada da seguinte forma:

Uma determinada versão n da N-API estará disponível na versão principal do Node.js em que foi publicada e em todas as versões subsequentes do Node.js, incluindo versões principais subsequentes.

Um autor de addon nativo pode tirar vantagem da garantia de compatibilidade futura da N-API, garantindo que o addon use apenas as APIs definidas em node_api.h e estruturas de dados e constantes definidas em node_api_types.h. Ao fazer isso, o autor facilita a adoção de seu addon, indicando aos usuários de produção que a carga de manutenção de seu aplicativo não aumentará mais com a adição do addon nativo ao seu projeto do que aumentaria com a adição de um pacote escrito puramente em JavaScript.

A N-API é versionada porque novas APIs são adicionadas de tempos em tempos. Ao contrário do versionamento semântico, o versionamento da N-API é cumulativo. Ou seja, cada versão da N-API transmite o mesmo significado que uma versão menor no sistema semver, o que significa que todas as alterações feitas na N-API serão retrocompatíveis. Além disso, novas APIs N-API são adicionadas sob uma flag experimental para dar à comunidade a oportunidade de analisá-las em um ambiente de produção. O status experimental significa que, embora se tenha tomado cuidado para garantir que a nova API não precise ser modificada de forma incompatível com o ABI no futuro, ela ainda não foi suficientemente comprovada na produção para ser correta e útil conforme projetado e, como tal, pode sofrer alterações incompatíveis com o ABI antes de ser finalmente incorporada a uma versão futura da N-API. Ou seja, uma N-API experimental ainda não é coberta pela garantia de compatibilidade futura.