Permalink
Browse files

n-api: add support for abi stable module API

Add support for abi stable module API (N-API) as "Experimental feature".
The goal of this API is to provide a stable Node API for native
module developers. N-API aims to provide ABI compatibility guarantees
across different Node versions and also across different
Node VMs - allowing N-API enabled native modules to just work
across different versions and flavors of Node.js without recompilation.

A more detailed introduction is provided in:
https://github.com/nodejs/node-eps/blob/master/005-ABI-Stable-Module-API.md
and https://github.com/nodejs/abi-stable-node/blob/doc/VM%20Summit.pdf.

The feature, during its experimental state, will be guarded by a runtime
flag "--napi-modules". Only when this flag is added to the command line
will N-API modules along with regular non N-API modules be supported.

The API is defined by the methods in "src/node_api.h" and
"src/node_api_types.h". This is the best
starting point to review the API surface. More documentation will follow.

In addition to the implementation of the API using V8, which is included
in this PR, the API has also been validated against chakracore and that
port is available in
https://github.com/nodejs/abi-stable-node/tree/api-prototype-chakracore-8.x.

The current plan is to provide N-API support in versions 8.X and 6.X
directly. For older versions, such as 4.X or pre N-API versions of 6.X,
we plan to create an external npm module to provide a migration path
that will allow modules targeting older Node.js versions to use the API,
albeit without getting the advantage of not having to recompile.

In addition, we also plan an external npm package with C++ sugar to
simplify the use of the API. The sugar will be in-line only and will
only use the exported N-API methods but is not part of the N-API
itself. The current version is in:
https://github.com/nodejs/node-api.

This PR is a result of work in the abi-stable-node repo:
https://github.com/nodejs/abi-stable-node/tree/doc,
with this PR being the cumulative work on the api-prototype-8.x
branch with the following contributors in alphabetical order:

Author: Arunesh Chandra <arunesh.chandra@microsoft.com>
Author: Gabriel Schulhof <gabriel.schulhof@intel.com>
Author: Hitesh Kanwathirtha <hiteshk@microsoft.com>
Author: Ian Halliday <ianhall@microsoft.com>
Author: Jason Ginchereau <jasongin@microsoft.com>
Author: Michael Dawson <michael_dawson@ca.ibm.com>
Author: Sampson Gao <sampsong@ca.ibm.com>
Author: Taylor Woll <taylor.woll@microsoft.com>
PR-URL: #11975
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information...
jasongin authored and addaleax committed Mar 20, 2017
1 parent 6481c93 commit 56e881d0b0b2997b518753cb627eb3b50eeb6f62
Showing with 6,580 additions and 23 deletions.
  1. +52 −8 Makefile
  2. +8 −0 doc/api/cli.md
  3. +5 −0 doc/node.1
  4. +3 −0 node.gyp
  5. +30 −9 src/node.cc
  6. +2,528 −0 src/node_api.cc
  7. +479 −0 src/node_api.h
  8. +95 −0 src/node_api_types.h
  9. +7 −0 test/addons-napi/.gitignore
  10. +22 −0 test/addons-napi/1_hello_world/binding.c
  11. +8 −0 test/addons-napi/1_hello_world/binding.gyp
  12. +6 −0 test/addons-napi/1_hello_world/test.js
  13. +58 −0 test/addons-napi/2_function_arguments/binding.c
  14. +8 −0 test/addons-napi/2_function_arguments/binding.gyp
  15. +6 −0 test/addons-napi/2_function_arguments/test.js
  16. +51 −0 test/addons-napi/3_callbacks/binding.c
  17. +8 −0 test/addons-napi/3_callbacks/binding.gyp
  18. +22 −0 test/addons-napi/3_callbacks/test.js
  19. +31 −0 test/addons-napi/4_object_factory/binding.c
  20. +8 −0 test/addons-napi/4_object_factory/binding.gyp
  21. +8 −0 test/addons-napi/4_object_factory/test.js
  22. +36 −0 test/addons-napi/5_function_factory/binding.c
  23. +8 −0 test/addons-napi/5_function_factory/binding.gyp
  24. +7 −0 test/addons-napi/5_function_factory/test.js
  25. +7 −0 test/addons-napi/6_object_wrap/binding.cc
  26. +8 −0 test/addons-napi/6_object_wrap/binding.gyp
  27. +201 −0 test/addons-napi/6_object_wrap/myobject.cc
  28. +26 −0 test/addons-napi/6_object_wrap/myobject.h
  29. +19 −0 test/addons-napi/6_object_wrap/test.js
  30. +32 −0 test/addons-napi/7_factory_wrap/binding.cc
  31. +8 −0 test/addons-napi/7_factory_wrap/binding.gyp
  32. +110 −0 test/addons-napi/7_factory_wrap/myobject.cc
  33. +26 −0 test/addons-napi/7_factory_wrap/myobject.h
  34. +14 −0 test/addons-napi/7_factory_wrap/test.js
  35. +57 −0 test/addons-napi/8_passing_wrapped/binding.cc
  36. +8 −0 test/addons-napi/8_passing_wrapped/binding.gyp
  37. +81 −0 test/addons-napi/8_passing_wrapped/myobject.cc
  38. +26 −0 test/addons-napi/8_passing_wrapped/myobject.h
  39. +9 −0 test/addons-napi/8_passing_wrapped/test.js
  40. +8 −0 test/addons-napi/test_array/binding.gyp
  41. +37 −0 test/addons-napi/test_array/test.js
  42. +137 −0 test/addons-napi/test_array/test_array.c
  43. +8 −0 test/addons-napi/test_buffer/binding.gyp
  44. +25 −0 test/addons-napi/test_buffer/test.js
  45. +160 −0 test/addons-napi/test_buffer/test_buffer.c
  46. +8 −0 test/addons-napi/test_constructor/binding.gyp
  47. +28 −0 test/addons-napi/test_constructor/test.js
  48. +104 −0 test/addons-napi/test_constructor/test_constructor.c
  49. +8 −0 test/addons-napi/test_conversions/binding.gyp
  50. +140 −0 test/addons-napi/test_conversions/test.js
  51. +237 −0 test/addons-napi/test_conversions/test_conversions.c
  52. +8 −0 test/addons-napi/test_error/binding.gyp
  53. +57 −0 test/addons-napi/test_error/test.js
  54. +37 −0 test/addons-napi/test_error/test_error.cc
  55. +8 −0 test/addons-napi/test_exception/binding.gyp
  56. +53 −0 test/addons-napi/test_exception/test.js
  57. +75 −0 test/addons-napi/test_exception/test_exception.c
  58. +8 −0 test/addons-napi/test_function/binding.gyp
  59. +28 −0 test/addons-napi/test_function/test.js
  60. +55 −0 test/addons-napi/test_function/test_function.c
  61. +8 −0 test/addons-napi/test_instanceof/binding.gyp
  62. +87 −0 test/addons-napi/test_instanceof/test.js
  63. +39 −0 test/addons-napi/test_instanceof/test_instanceof.c
  64. +8 −0 test/addons-napi/test_number/binding.gyp
  65. +39 −0 test/addons-napi/test_number/test.js
  66. +55 −0 test/addons-napi/test_number/test_number.c
  67. +8 −0 test/addons-napi/test_object/binding.gyp
  68. +65 −0 test/addons-napi/test_object/test.js
  69. +246 −0 test/addons-napi/test_object/test_object.c
  70. +8 −0 test/addons-napi/test_properties/binding.gyp
  71. +27 −0 test/addons-napi/test_properties/test.js
  72. +85 −0 test/addons-napi/test_properties/test_properties.c
  73. +8 −0 test/addons-napi/test_string/binding.gyp
  74. +26 −0 test/addons-napi/test_string/test.js
  75. +134 −0 test/addons-napi/test_string/test_string.c
  76. +8 −0 test/addons-napi/test_symbol/binding.gyp
  77. +20 −0 test/addons-napi/test_symbol/test1.js
  78. +15 −0 test/addons-napi/test_symbol/test2.js
  79. +19 −0 test/addons-napi/test_symbol/test3.js
  80. +94 −0 test/addons-napi/test_symbol/test_symbol.c
  81. +8 −0 test/addons-napi/test_typedarray/binding.gyp
  82. +39 −0 test/addons-napi/test_typedarray/test.js
  83. +144 −0 test/addons-napi/test_typedarray/test_typedarray.c
  84. +6 −0 test/addons-napi/testcfg.py
  85. +1 −1 test/testpy/__init__.py
  86. +2 −0 tools/install.py
  87. +1 −0 tools/test.py
  88. +25 −5 vcbuild.bat
View
@@ -193,9 +193,10 @@ v8:
test: all
$(MAKE) build-addons
$(MAKE) build-addons-napi
$(MAKE) cctest
$(PYTHON) tools/test.py --mode=release -J \
addons doctool inspector known_issues message pseudo-tty parallel sequential
addons addons-napi doctool inspector known_issues message pseudo-tty parallel sequential
$(MAKE) lint
test-parallel: all
@@ -262,6 +263,41 @@ test/addons/.buildstamp: config.gypi \
# TODO(bnoordhuis) Force rebuild after gyp update.
build-addons: $(NODE_EXE) test/addons/.buildstamp
ADDONS_NAPI_BINDING_GYPS := \
$(filter-out test/addons-napi/??_*/binding.gyp, \
$(wildcard test/addons-napi/*/binding.gyp))
ADDONS_NAPI_BINDING_SOURCES := \
$(filter-out test/addons-napi/??_*/*.cc, $(wildcard test/addons-napi/*/*.cc)) \
$(filter-out test/addons-napi/??_*/*.h, $(wildcard test/addons-napi/*/*.h))
# Implicitly depends on $(NODE_EXE), see the build-addons-napi rule for rationale.
test/addons-napi/.buildstamp: config.gypi \
deps/npm/node_modules/node-gyp/package.json \
$(ADDONS_NAPI_BINDING_GYPS) $(ADDONS_NAPI_BINDING_SOURCES) \
deps/uv/include/*.h deps/v8/include/*.h \
src/node.h src/node_buffer.h src/node_object_wrap.h src/node_version.h \
src/node_api.h src/node_api_types.h
# Cannot use $(wildcard test/addons-napi/*/) here, it's evaluated before
# embedded addons have been generated from the documentation.
@for dirname in test/addons-napi/*/; do \
printf "\nBuilding addon $$PWD/$$dirname\n" ; \
env MAKEFLAGS="-j1" $(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp \
--loglevel=$(LOGLEVEL) rebuild \
--python="$(PYTHON)" \
--directory="$$PWD/$$dirname" \
--nodedir="$$PWD" || exit 1 ; \
done
touch $@
# .buildstamp and .docbuildstamp need $(NODE_EXE) but cannot depend on it
# directly because it calls make recursively. The parent make cannot know
# if the subprocess touched anything so it pessimistically assumes that
# .buildstamp and .docbuildstamp are out of date and need a rebuild.
# Just goes to show that recursive make really is harmful...
# TODO(bnoordhuis) Force rebuild after gyp or node-gyp update.
build-addons-napi: $(NODE_EXE) test/addons-napi/.buildstamp
ifeq ($(OSTYPE),$(filter $(OSTYPE),darwin aix))
XARGS = xargs
else
@@ -274,20 +310,22 @@ clear-stalled:
test-gc: all test/gc/build/Release/binding.node
$(PYTHON) tools/test.py --mode=release gc
test-build: | all build-addons
test-build: | all build-addons build-addons-napi
test-build-addons-napi: all build-addons-napi
test-all: test-build test/gc/build/Release/binding.node
$(PYTHON) tools/test.py --mode=debug,release
test-all-valgrind: test-build
$(PYTHON) tools/test.py --mode=debug,release --valgrind
CI_NATIVE_SUITES := addons
CI_NATIVE_SUITES := addons addons-napi
CI_JS_SUITES := doctool inspector known_issues message parallel pseudo-tty sequential
# Build and test addons without building anything else
test-ci-native: LOGLEVEL := info
test-ci-native: | test/addons/.buildstamp
test-ci-native: | test/addons/.buildstamp test/addons-napi/.buildstamp
$(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \
--mode=release --flaky-tests=$(FLAKY_TESTS) \
$(TEST_CI_ARGS) $(CI_NATIVE_SUITES)
@@ -304,11 +342,11 @@ test-ci-js: | clear-stalled
fi
test-ci: LOGLEVEL := info
test-ci: | clear-stalled build-addons
test-ci: | clear-stalled build-addons build-addons-napi
out/Release/cctest --gtest_output=tap:cctest.tap
$(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \
--mode=release --flaky-tests=$(FLAKY_TESTS) \
$(TEST_CI_ARGS) $(CI_NATIVE_SUITES) $(CI_JS_SUITES)
$(TEST_CI_ARGS) $(CI_NATIVE_SUITES) addons-napi $(CI_JS_SUITES)
# Clean up any leftover processes
PS_OUT=`ps awwx | grep Release/node | grep -v grep | awk '{print $$1}'`; \
if [ "$${PS_OUT}" ]; then \
@@ -355,7 +393,10 @@ test-npm: $(NODE_EXE)
test-npm-publish: $(NODE_EXE)
npm_package_config_publishtest=true $(NODE) deps/npm/test/run.js
test-addons: test-build
test-addons-napi: test-build-addons-napi
$(PYTHON) tools/test.py --mode=release addons-napi
test-addons: test-build test-addons-napi
$(PYTHON) tools/test.py --mode=release addons
test-addons-clean:
@@ -821,6 +862,7 @@ CPPLINT_EXCLUDE += src/node_root_certs.h
CPPLINT_EXCLUDE += src/queue.h
CPPLINT_EXCLUDE += src/tree.h
CPPLINT_EXCLUDE += $(wildcard test/addons/??_*/*.cc test/addons/??_*/*.h)
CPPLINT_EXCLUDE += $(wildcard test/addons-napi/??_*/*.cc test/addons-napi/??_*/*.h)
CPPLINT_FILES = $(filter-out $(CPPLINT_EXCLUDE), $(wildcard \
src/*.c \
@@ -830,6 +872,8 @@ CPPLINT_FILES = $(filter-out $(CPPLINT_EXCLUDE), $(wildcard \
test/addons/*/*.h \
test/cctest/*.cc \
test/cctest/*.h \
test/addons-napi/*/*.cc \
test/addons-napi/*/*.h \
test/gc/binding.cc \
tools/icu/*.cc \
tools/icu/*.h \
@@ -869,4 +913,4 @@ endif
test-v8-intl test-v8-benchmarks test-v8-all v8 lint-ci bench-ci jslint-ci \
doc-only $(TARBALL)-headers test-ci test-ci-native test-ci-js build-ci \
clear-stalled coverage-clean coverage-build coverage-test coverage \
list-gtests
list-gtests test-addons-napi build-addons-napi
View
@@ -144,6 +144,14 @@ added: v6.0.0
Silence all process warnings (including deprecations).
### `--napi-modules`
<!-- YAML
added: REPLACEME
-->
Enable loading native modules compiled with the ABI-stable Node.js API (N-API)
(experimental).
### `--trace-warnings`
<!-- YAML
added: v6.0.0
View
@@ -119,6 +119,11 @@ Throw errors for deprecations.
.BR \-\-no\-warnings
Silence all process warnings (including deprecations).
.TP
.BR \-\-napi\-modules
Enable loading native modules compiled with the ABI-stable Node.js API (N-API)
(experimental).
.TP
.BR \-\-trace\-warnings
Print stack traces for process warnings (including deprecations).
View
@@ -167,6 +167,9 @@
'src/handle_wrap.cc',
'src/js_stream.cc',
'src/node.cc',
'src/node_api.cc',
'src/node_api.h',
'src/node_api_types.h',
'src/node_buffer.cc',
'src/node_config.cc',
'src/node_constants.cc',
View
@@ -180,6 +180,9 @@ static const char* trace_enabled_categories = nullptr;
std::string icu_data_dir; // NOLINT(runtime/string)
#endif
// N-API is in experimental state, disabled by default.
bool load_napi_modules = false;
// used by C++ modules as well
bool no_deprecation = false;
@@ -2463,15 +2466,25 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
}
if (mp->nm_version != NODE_MODULE_VERSION) {
char errmsg[1024];
snprintf(errmsg,
sizeof(errmsg),
"The module '%s'"
"\nwas compiled against a different Node.js version using"
"\nNODE_MODULE_VERSION %d. This version of Node.js requires"
"\nNODE_MODULE_VERSION %d. Please try re-compiling or "
"re-installing\nthe module (for instance, using `npm rebuild` or "
"`npm install`).",
*filename, mp->nm_version, NODE_MODULE_VERSION);
if (mp->nm_version == -1) {
snprintf(errmsg,
sizeof(errmsg),
"The module '%s'"
"\nwas compiled against the ABI-stable Node.js API (N-API)."
"\nThis feature is experimental and must be enabled on the "
"\ncommand-line by adding --napi-modules.",
*filename);
} else {
snprintf(errmsg,
sizeof(errmsg),
"The module '%s'"
"\nwas compiled against a different Node.js version using"
"\nNODE_MODULE_VERSION %d. This version of Node.js requires"
"\nNODE_MODULE_VERSION %d. Please try re-compiling or "
"re-installing\nthe module (for instance, using `npm rebuild` "
"or `npm install`).",
*filename, mp->nm_version, NODE_MODULE_VERSION);
}
// NOTE: `mp` is allocated inside of the shared library's memory, calling
// `uv_dlclose` will deallocate it
@@ -3537,6 +3550,7 @@ static void PrintHelp() {
" --trace-deprecation show stack traces on deprecations\n"
" --throw-deprecation throw an exception on deprecations\n"
" --no-warnings silence all process warnings\n"
" --napi-modules load N-API modules\n"
" --trace-warnings show stack traces on process warnings\n"
" --redirect-warnings=path\n"
" write warnings to path instead of\n"
@@ -3709,6 +3723,8 @@ static void ParseArgs(int* argc,
force_repl = true;
} else if (strcmp(arg, "--no-deprecation") == 0) {
no_deprecation = true;
} else if (strcmp(arg, "--napi-modules") == 0) {
load_napi_modules = true;
} else if (strcmp(arg, "--no-warnings") == 0) {
no_process_warnings = true;
} else if (strcmp(arg, "--trace-warnings") == 0) {
@@ -4489,6 +4505,11 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data,
if (debug_enabled)
EnableDebug(&env);
if (load_napi_modules) {
ProcessEmitWarning(&env, "N-API is an experimental feature "
"and could change at any time.");
}
{
SealHandleScope seal(isolate);
bool more;
Oops, something went wrong.

0 comments on commit 56e881d

Please sign in to comment.