Стабильность ABI
Введение
Интерфейс прикладного двоичного кода (ABI) — это способ, с помощью которого программы могут вызывать функции и использовать структуры данных из других скомпилированных программ. Это скомпилированная версия интерфейса прикладного программирования (API). Другими словами, файлы заголовков, описывающие классы, функции, структуры данных, перечисления и константы, которые позволяют приложению выполнять требуемую задачу, соответствуют после компиляции набору адресов и ожидаемых значений параметров, а также размерам и компоновкам структуры памяти, с которыми был скомпилирован поставщик ABI.
Приложение, использующее ABI, должно быть скомпилировано таким образом, чтобы доступные адреса, ожидаемые значения параметров и размеры и компоновки структуры памяти совпадали с теми, с которыми был скомпилирован поставщик ABI. Это обычно достигается компиляцией с использованием заголовков, предоставленных поставщиком ABI.
Поскольку поставщик ABI и пользователь ABI могут быть скомпилированы в разное время с использованием разных версий компилятора, часть ответственности за обеспечение совместимости ABI лежит на компиляторе. Разные версии компилятора, возможно, предоставленные разными поставщиками, должны создавать один и тот же ABI из файла заголовка с определенным содержимым и должны создавать код для приложения, использующего ABI, который обращается к API, описанному в данном заголовке, в соответствии с соглашениями ABI, результирующими из описания в заголовке. Современные компиляторы имеют довольно хорошую репутацию в том, что не нарушают совместимость ABI приложений, которые они компилируют.
Оставшаяся ответственность за обеспечение совместимости ABI лежит на команде, поддерживающей файлы заголовков, которые предоставляют API, который в результате компиляции приводит к ABI, который должен оставаться стабильным. Изменения в файлах заголовков могут быть внесены, но характер изменений должен тщательно отслеживаться, чтобы гарантировать, что после компиляции ABI не изменится таким образом, который сделает существующих пользователей ABI несовместимыми с новой версией.
Стабильность ABI в Node.js
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, успешно загружался при загрузке любой второстепенной или патч-версией Node.js в рамках основной версии, для которой он был скомпилирован.
N-API
Возникла необходимость в оснащении Node.js API, которое приводит к ABI, остающемуся стабильным в течение нескольких основных версий Node.js. Мотивация для создания такого API следующая:
Язык JavaScript оставался совместимым с самим собой с самых ранних дней своего существования, тогда как ABI движка, выполняющего код JavaScript, меняется с каждой основной версией Node.js. Это означает, что приложения, состоящие из пакетов Node.js, написанных полностью на JavaScript, не нуждаются в перекомпиляции, переустановке или повторном развертывании, когда новая основная версия Node.js внедряется в производственную среду, в которой работают такие приложения. Напротив, если приложение зависит от пакета, содержащего нативный модуль, приложение должно быть перекомпилировано, переустановлено и повторно развернуто всякий раз, когда в производственную среду вводится новая основная версия Node.js. Это несоответствие между пакетами Node.js, содержащими нативные модули, и пакетами, написанными полностью на JavaScript, увеличило нагрузку на обслуживание производственных систем, которые полагаются на нативные модули.
Другие проекты начали создавать интерфейсы JavaScript, которые по сути являются альтернативными реализациями Node.js. Поскольку эти проекты обычно построены на другом движке JavaScript, чем V8, их нативные модули обязательно принимают другую структуру и используют другой API. Тем не менее, использование единого API для нативного модуля в различных реализациях API JavaScript Node.js позволит этим проектам воспользоваться экосистемой пакетов JavaScript, которая накопилась вокруг Node.js.
В будущем Node.js может содержать другой движок JavaScript. Это означает, что внешне все интерфейсы Node.js останутся прежними, но заголовочный файл V8 будет отсутствовать. Такой шаг вызовет сбой экосистемы Node.js в целом и нативных модулей в частности, если API, не зависящее от движка JavaScript, не будет сначала предоставлено Node.js и принято нативными модулями.
В этих целях Node.js представил N-API в версии 8.6.0 и отметил его как стабильный компонент проекта, начиная с Node.js 8.12.0. API определяется в заголовках node_api.h
и node_api_types.h
и обеспечивает гарантию обратной совместимости, которая пересекает границу основной версии Node.js. Гарантия может быть сформулирована следующим образом:
Данная версия n N-API будет доступна в основной версии Node.js, в которой она была опубликована, и во всех последующих версиях Node.js, включая последующие основные версии.
Автор нативного модуля может воспользоваться гарантией обратной совместимости N-API, убедившись, что модуль использует только API, определенные в node_api.h
, и структуры данных и константы, определенные в node_api_types.h
. Поступая таким образом, автор способствует внедрению своего модуля, указывая пользователям производственной среды, что нагрузка на обслуживание их приложения не увеличится больше с добавлением нативного модуля к своему проекту, чем с добавлением пакета, написанного чисто на JavaScript.
N-API имеет версионирование, поскольку новые API добавляются время от времени. В отличие от семантического версионирования, версионирование N-API является кумулятивным. То есть каждая версия N-API передает тот же смысл, что и второстепенная версия в системе semver, а это означает, что все изменения, внесенные в N-API, будут обратно совместимы. Кроме того, новые N-API добавляются под экспериментальным флагом, чтобы дать сообществу возможность проверить их в производственной среде. Экспериментальный статус означает, что, хотя были приняты меры предосторожности для того, чтобы новый API не пришлось изменять несовместимым с ABI способом в будущем, он еще не был достаточно проверен в производстве, чтобы быть правильным и полезным по своему замыслу, и, как таковой, может претерпеть несовместимые с ABI изменения, прежде чем он будет окончательно включен в будущую версию N-API. То есть экспериментальный N-API еще не охватывается гарантией обратной совместимости.