From f351c448cbb998f22d5b33a9e2d4ea27ce891b7e Mon Sep 17 00:00:00 2001 From: Luis Blanco Date: Thu, 7 Nov 2019 22:14:00 +0300 Subject: [PATCH] wip doc --- README.md | 641 +--------------------------------------- bat/cp.bat | 1 - bat/mkdir.bat | 5 - bat/rm.bat | 6 - doc/addon-tools.md | 238 +++++++++++++++ doc/class-wrapping.md | 1 + doc/script-helpers.md | 1 + doc/snippets.md | 197 ++++++++++++ include/addon-tools.hpp | 334 +++++++++++---------- 9 files changed, 635 insertions(+), 789 deletions(-) delete mode 100644 bat/cp.bat delete mode 100644 bat/mkdir.bat delete mode 100644 bat/rm.bat create mode 100644 doc/addon-tools.md create mode 100644 doc/class-wrapping.md create mode 100644 doc/script-helpers.md create mode 100644 doc/snippets.md diff --git a/README.md b/README.md index e7cb03f..15645db 100644 --- a/README.md +++ b/README.md @@ -12,536 +12,23 @@ This is a part of [Node3D](https://github.com/node-3d) project. ## Synopsis -Helpers for Node.js **NAPI** addons and dependency packages: - -* Supported platforms (x64): Windows, Linux, OSX. -* C++ helpers: - * Macro shortcuts for NAPI. - * `eventEmit()` function. - * `getData()` function. - * **Es5** class wrapper (like `Napi::ObjectWrap`, but Es5 functions). -* Module helpers: - * Crossplatform commands for GYP: `cp`, `rm`, `mkdir`. - * Deps unzip installer. - * Url-to-buffer downloader. - * Binary copy helper. - -Useful links: [N-API Docs](https://nodejs.org/api/n-api.html), -[Napi Docs](https://github.com/nodejs/node-addon-api/blob/master/doc/setup.md), -[GYP Docs](https://gyp.gsrc.io/docs/UserDocumentation.md). - -**Jump to**: - -[Snippets](#snippets) - -[include/addon-tools.hpp](#includeaddon-toolshpp) - -[index.js](#indexjs) -[cpbin.js](#cpbinjs) -[download.js](#downloadjs) -[install.js](#installjs) -[writable-buffer.js](#writablebufferjs) - -[Crossplatform commands](#crossplatform-commands) - -[Function eventEmit](#function-eventEmit) - -[Es5 class wrapper](#es5-class-wrapper) - ---- - - -## Snippets - - -### Crossplatform commands - -``` -'variables': { - 'rm' : ' - -See a snipped for binding.gyp here - -* Assume `EXT_LIB` is the name of a binary dependency. -* Assume `deps-EXT_LIB` is the name of an Addon Tools compliant dependency module. -* Assume `MY_ADDON` is the name of this addon's resulting binary. -* Assume C++ code goes to `cpp` subdirectory. - -``` -{ - 'variables': { - 'rm' : ' - ---- - - -## include/addon-tools.hpp - -There is a C++ header file, `addon-tools.hpp`, shipped with this package. It -introduces several useful macros and utilities. Also it includes Napi automatically, -so that you can replace: - -``` -#include -``` - -with - -``` -#include -``` - -In gyp, the include directory should be set for your addon to know where -to get it. An actual path to the directory is exported from the module -and is accessible like this: - -``` -require('addon-tools-raub').include // a string -``` - -### Helpers in **addon-tools.hpp**: - -Usually all the helpers work within the context of JS call. In this case we -have `CallbackInfo info` passed as an argument. - -``` -#define NAPI_ENV Napi::Env env = info.Env(); -#define NAPI_HS Napi::HandleScope scope(env); -``` - -
- -Return value - -* `RET_VALUE(VAL)`- return a given Napi::Value. -* `RET_UNDEFINED`- return `undefined`. -* `RET_NULL` - return `null`. -* `RET_STR(VAL)` - return `Napi::String`, expected `VAL` is `const char *`. -* `RET_NUM(VAL)` - return `Napi::Number`, expected `VAL` is `double`. -* `RET_EXT(VAL)` - return `Napi::External`, expected `VAL` is `void *`. -* `RET_BOOL(VAL)` - return `Napi::Boolean`, expected `VAL` is `bool`. -* `RET_FUN(VAL)` - return `Napi::Function`, expected `VAL` is a `napi_value`. -* `RET_OBJ(VAL)` - return `Napi::Object`, expected `VAL` is a `napi_value`. - -
- - - -
- -New JS value - -* `JS_STR(VAL)` - create a `Napi::String` value. -* `JS_NUM(VAL)` - create a `Napi::Number` value. -* `JS_EXT(VAL)` - create a `Napi::External` (from pointer) value. -* `JS_BOOL(VAL)` - create a `Napi::Boolean` value. -* `JS_FUN(VAL)` - create a `Napi::Function` value. -* `JS_OBJ(VAL)` - create a `Napi::Object` value. - -
- - -
- -Method check - -These checks throw JS TypeError if not passed. Here `T` is always used as a typename -in error messages. `C` is a -[Napi::Value](https://github.com/nodejs/node-addon-api/blob/master/doc/value.md#isboolean) -check method, like `IsObject()`. `I` is the index of argument as in `info[I]`, -starting from `0`. - -* `REQ_ARGS(N)` - check if at least `N` arguments passed -* `IS_ARG_EMPTY(I)` - check if argument `I` is `undefined` or `null` -* `CHECK_REQ_ARG(I, C, T)` - check if argument `I` is approved by `C` check. -* `CHECK_LET_ARG(I, C, T)` - check if argument `I` is approved by `C` check or empty. -* `CTOR_CHECK(T)` - check if method is called as a constructor -* `SETTER_CHECK(C, T)` - check if setter `value` is approved by `C` check. -* `DES_CHECK` - within dynamic method check if the instance wasn't -destroyed by `destroy()`. - -
- - -
- -Method arguments - -The idea is to ease the transition from what inside the `CallbackInfo` to -what you work with in C++. -Three types of argument retrieval are supported: `REQ_`, `USE_` and `LET_`. -The difference: -* `REQ_` - 2 params, requires an argument to have a value -* `USE_` - 3 params, allows the argument to be empty and have a default -* `LET_` - 2 params, is `USE_` with a preset zero-default. - -What it does, basically: -``` -// REQ_DOUBLE_ARG(0, x) -double x = info[0].ToNumber().DoubleValue(); - -// USE_DOUBLE_ARG(0, x, 5.7) -double x = IS_ARG_EMPTY(0) ? 5.7 : info[0].ToNumber().DoubleValue(); - -// LET_DOUBLE_ARG(0, x) -double x = IS_ARG_EMPTY(0) ? 0.0 : info[0].ToNumber().DoubleValue(); -``` - -That extrapolates well to all the helpers below: -* `REQ_STR_ARG` - JS `string` => C++ `std::string`. -* `USE_STR_ARG` -* `LET_STR_ARG` - default: `""`. -* `REQ_INT32_ARG` - JS `number` => C++ `int32_t`. -* `USE_INT32_ARG` -* `LET_INT32_ARG` - default: `0`. -* `REQ_INT_ARG` - JS `number` => C++ `int32_t`. -* `USE_INT_ARG` -* `LET_INT_ARG` - default: `0`. -* `REQ_UINT32_ARG` - JS `number` => C++ `uint32_t`. -* `USE_UINT32_ARG` -* `LET_UINT32_ARG` - default: `0`. -* `REQ_UINT_ARG` - JS `number` => C++ `uint32_t`. -* `USE_UINT_ARG` -* `LET_UINT_ARG` - default: `0`. -* `REQ_BOOL_ARG` - JS `Boolean` => C++ `bool`. -* `USE_BOOL_ARG` -* `LET_BOOL_ARG` - default: `false`. -* `REQ_OFFS_ARG` - JS `number` => C++ `size_t`. -* `USE_OFFS_ARG` -* `LET_OFFS_ARG` - default: `0`. -* `REQ_DOUBLE_ARG` - JS `number` => C++ `double`. -* `USE_DOUBLE_ARG` -* `LET_DOUBLE_ARG` - default: `0.0`. -* `REQ_FLOAT_ARG` - JS `number` => C++ `float`. -* `USE_FLOAT_ARG` -* `LET_FLOAT_ARG` - default: `0.f`. -* `REQ_EXT_ARG` - JS `native` => C++ `void*`. -* `USE_EXT_ARG` -* `LET_EXT_ARG` - default: `nullptr`. -* `REQ_FUN_ARG` - JS `function` => C++ `Napi::Function`. -* `REQ_OBJ_ARG` - JS `object` => C++ `Napi::Object`. -* `USE_OBJ_ARG` -* `LET_OBJ_ARG` - default: `{}`. -* `REQ_ARRV_ARG` - JS `ArrayBuffer` => C++ `Napi::ArrayBuffer`. -* `REQ_BUF_ARG` - JS `Buffer` => C++ `Napi::Buffer`. - - -``` -NAN_METHOD(test) { - - REQ_UINT32_ARG(0, width); - REQ_UINT32_ARG(1, height); - LET_FLOAT_ARG(2, z); - // Variables created: unsigned int width, height; float z; - ... -``` - -
- - -
- -Set object accessors - -Simplified accessor assignment, adds accessors of NAME for OBJ. Read accessor is -assumed to have the name `NAME+'Getter'` and write accessor is `NAME+'Setter'`. - -* `ACCESSOR_RW(CLASS, NAME)` - add read and write accessors NAME of CLASS. -* `ACCESSOR_R(CLASS, NAME)` - add read accessor NAME of CLASS. -* `ACCESSOR_M(CLASS, NAME)` - add method NAME of CLASS. - - -``` -void MyClass::init(Napi::Env env, Napi::Object exports) { - ... - Napi::Function ctor = DefineClass(env, "MyClass", { - ACCESSOR_R(MyClass, isDestroyed), - ACCESSOR_RW(MyClass, x), - ACCESSOR_M(MyClass, reset), - }); - ... -} -JS_GETTER(MyClass::isDestroyedGetter) { ... -JS_GETTER(MyClass::xGetter) { ... -JS_SETTER(MyClass::xSetter) { ... -JS_METHOD(MyClass::save) { ... -``` - -
- - -
- -Setter argument - -Works similar to method arguments. But there is always `value` -argument, from which a C++ value is extracted. - -* `SETTER_STR_ARG` -* `SETTER_INT32_ARG` -* `SETTER_INT_ARG` -* `SETTER_BOOL_ARG` -* `SETTER_UINT32_ARG` -* `SETTER_UINT_ARG` -* `SETTER_OFFS_ARG` -* `SETTER_DOUBLE_ARG` -* `SETTER_FLOAT_ARG` -* `SETTER_EXT_ARG` -* `SETTER_FUN_ARG` -* `SETTER_OBJ_ARG` -* `SETTER_ARRV_ARG` - -``` -JS_SETTER(MyClass::x) { SETTER_STR_ARG; - // Variable created: std::string v; - ... -``` - -
- - -
- -Data retrieval - -* `T *getArrayData(value, num = NULL)` - extracts TypedArray data of any type from -the given JS value. Does not accept `Array`. Checks with `IsArrayBuffer()`. -Returns `nullptr` for empty JS values. For unacceptable values throws TypeError. - -* `void *getData(value)` - if value is a TypedArray, then the result of -`getArrayData(value)` is returned. Otherwise if value has `'data'` property, it's -content is then returned as `node::Buffer`. Returns `nullptr` in other cases. - -
- ---- +Helpers for Node.js **NAPI** addons and dependency packages. + +**Go to**: + +[include/addon-tools.hpp](doc/addon-tools.md) +Macro shortcuts for NAPI. +[Es5 Class Wrapping](doc/class-wrapping.md) +An alternative, lightweight native class-defining mechanism for addons. +* [Script Helpers](doc/script-helpers.md) +Additional scripts for addon management. +* [Snippets](doc/snippets.md) +Some repetitive bits of code for addons. ## index.js -Exports: +Main exports for cross-platform addon configuration. Exports: * `paths(dir)` - function. Returns a set of platform dependent paths depending on input `dir`. * `bin` - platform binary directory absolute path. @@ -552,105 +39,3 @@ Use with `node -p` through list context command expansion ` Note: only use this within JS function stack - - -## Function eventEmit - -In N-API there is no inheritance. And no `eval('require(...)')` possible at -the time of module init. But `require('events')` is very tempting... -The solution is: - -``` -// JS -const EventEmitter = require('events'); -const { bin } = require('addon-tools-raub'); -const MyClass = require(`./${bin}/addon`); -MyClass.prototype.__proto__ = EventEmitter.prototype; -// Since now it is possible to call `emit` from instancess of MyClass -``` - -``` -// C++ -void MyClass::emit(const Napi::CallbackInfo& info, const char* name) { - NAPI_ENV; - THIS_OBJ(that); - eventEmit(env, that, name); -} -``` - -Signature: -``` -inline void eventEmit( - Napi::Env env, - Napi::Object that, - const std::string &name, - int argc = 0, - Napi::Value *argv = nullptr -) -``` diff --git a/bat/cp.bat b/bat/cp.bat deleted file mode 100644 index 1f14d31..0000000 --- a/bat/cp.bat +++ /dev/null @@ -1 +0,0 @@ -copy /y %1 %2 diff --git a/bat/mkdir.bat b/bat/mkdir.bat deleted file mode 100644 index 6fd346a..0000000 --- a/bat/mkdir.bat +++ /dev/null @@ -1,5 +0,0 @@ -for %%x in (%*) do ( - - if not %%x=="-p" if not exist %%x md %%x - -) diff --git a/bat/rm.bat b/bat/rm.bat deleted file mode 100644 index 48dd3b5..0000000 --- a/bat/rm.bat +++ /dev/null @@ -1,6 +0,0 @@ -for %%x in (%*) do ( - - if not %%x=="-rf" if not %%x=="-r" if not %%x=="-f" if exist %%~x\ rd /s /q %%x - if not %%x=="-rf" if not %%x=="-r" if not %%x=="-f" if not exist %%~x\ if exist %%x del /f /q %%x - -) diff --git a/doc/addon-tools.md b/doc/addon-tools.md new file mode 100644 index 0000000..525f802 --- /dev/null +++ b/doc/addon-tools.md @@ -0,0 +1,238 @@ +# include/addon-tools.hpp + +There is a C++ header file, `addon-tools.hpp`, shipped with this package. It +introduces several useful macros and utilities. Also it includes Napi automatically, +so that you can replace: + +``` +#include +``` + +with + +``` +#include +``` + +In gyp, the include directory should be set for your addon to know where +to get it. An actual path to the directory is exported from the module +and is accessible like this: + +``` +require('addon-tools-raub').include // a string +``` + +### Helpers in **addon-tools.hpp**: + +Usually all the helpers work within the context of JS call. In this case we +have `CallbackInfo info` passed as an argument. + +``` +#define NAPI_ENV Napi::Env env = info.Env(); +#define NAPI_HS Napi::HandleScope scope(env); +``` + +
+ +Return value + +* `RET_VALUE(VAL)`- return a given Napi::Value. +* `RET_UNDEFINED`- return `undefined`. +* `RET_NULL` - return `null`. +* `RET_STR(VAL)` - return `Napi::String`, expected `VAL` is `const char *`. +* `RET_NUM(VAL)` - return `Napi::Number`, expected `VAL` is `double`. +* `RET_EXT(VAL)` - return `Napi::External`, expected `VAL` is `void *`. +* `RET_BOOL(VAL)` - return `Napi::Boolean`, expected `VAL` is `bool`. +* `RET_FUN(VAL)` - return `Napi::Function`, expected `VAL` is a `napi_value`. +* `RET_OBJ(VAL)` - return `Napi::Object`, expected `VAL` is a `napi_value`. + +
+ + + +
+ +New JS value + +* `JS_STR(VAL)` - create a `Napi::String` value. +* `JS_NUM(VAL)` - create a `Napi::Number` value. +* `JS_EXT(VAL)` - create a `Napi::External` (from pointer) value. +* `JS_BOOL(VAL)` - create a `Napi::Boolean` value. +* `JS_FUN(VAL)` - create a `Napi::Function` value. +* `JS_OBJ(VAL)` - create a `Napi::Object` value. + +
+ + +
+ +Method check + +These checks throw JS TypeError if not passed. Here `T` is always used as a typename +in error messages. `C` is a +[Napi::Value](https://github.com/nodejs/node-addon-api/blob/master/doc/value.md#isboolean) +check method, like `IsObject()`. `I` is the index of argument as in `info[I]`, +starting from `0`. + +* `REQ_ARGS(N)` - check if at least `N` arguments passed +* `IS_ARG_EMPTY(I)` - check if argument `I` is `undefined` or `null` +* `CHECK_REQ_ARG(I, C, T)` - check if argument `I` is approved by `C` check. +* `CHECK_LET_ARG(I, C, T)` - check if argument `I` is approved by `C` check or empty. +* `CTOR_CHECK(T)` - check if method is called as a constructor +* `SETTER_CHECK(C, T)` - check if setter `value` is approved by `C` check. +* `DES_CHECK` - within dynamic method check if the instance wasn't +destroyed by `destroy()`. + +
+ + +
+ +Method arguments + +The idea is to ease the transition from what inside the `CallbackInfo` to +what you work with in C++. +Three types of argument retrieval are supported: `REQ_`, `USE_` and `LET_`. +The difference: +* `REQ_` - 2 params, requires an argument to have a value +* `USE_` - 3 params, allows the argument to be empty and have a default +* `LET_` - 2 params, is `USE_` with a preset zero-default. + +What it does, basically: +``` +// REQ_DOUBLE_ARG(0, x) +double x = info[0].ToNumber().DoubleValue(); + +// USE_DOUBLE_ARG(0, x, 5.7) +double x = IS_ARG_EMPTY(0) ? 5.7 : info[0].ToNumber().DoubleValue(); + +// LET_DOUBLE_ARG(0, x) +double x = IS_ARG_EMPTY(0) ? 0.0 : info[0].ToNumber().DoubleValue(); +``` + +That extrapolates well to all the helpers below: +* `REQ_STR_ARG` - JS `string` => C++ `std::string`. +* `USE_STR_ARG` +* `LET_STR_ARG` - default: `""`. +* `REQ_INT32_ARG` - JS `number` => C++ `int32_t`. +* `USE_INT32_ARG` +* `LET_INT32_ARG` - default: `0`. +* `REQ_INT_ARG` - JS `number` => C++ `int32_t`. +* `USE_INT_ARG` +* `LET_INT_ARG` - default: `0`. +* `REQ_UINT32_ARG` - JS `number` => C++ `uint32_t`. +* `USE_UINT32_ARG` +* `LET_UINT32_ARG` - default: `0`. +* `REQ_UINT_ARG` - JS `number` => C++ `uint32_t`. +* `USE_UINT_ARG` +* `LET_UINT_ARG` - default: `0`. +* `REQ_BOOL_ARG` - JS `Boolean` => C++ `bool`. +* `USE_BOOL_ARG` +* `LET_BOOL_ARG` - default: `false`. +* `REQ_OFFS_ARG` - JS `number` => C++ `size_t`. +* `USE_OFFS_ARG` +* `LET_OFFS_ARG` - default: `0`. +* `REQ_DOUBLE_ARG` - JS `number` => C++ `double`. +* `USE_DOUBLE_ARG` +* `LET_DOUBLE_ARG` - default: `0.0`. +* `REQ_FLOAT_ARG` - JS `number` => C++ `float`. +* `USE_FLOAT_ARG` +* `LET_FLOAT_ARG` - default: `0.f`. +* `REQ_EXT_ARG` - JS `native` => C++ `void*`. +* `USE_EXT_ARG` +* `LET_EXT_ARG` - default: `nullptr`. +* `REQ_FUN_ARG` - JS `function` => C++ `Napi::Function`. +* `REQ_OBJ_ARG` - JS `object` => C++ `Napi::Object`. +* `USE_OBJ_ARG` +* `LET_OBJ_ARG` - default: `{}`. +* `REQ_ARRV_ARG` - JS `ArrayBuffer` => C++ `Napi::ArrayBuffer`. +* `REQ_BUF_ARG` - JS `Buffer` => C++ `Napi::Buffer`. + + +``` +NAN_METHOD(test) { + + REQ_UINT32_ARG(0, width); + REQ_UINT32_ARG(1, height); + LET_FLOAT_ARG(2, z); + // Variables created: unsigned int width, height; float z; + ... +``` + +
+ + +
+ +Set object accessors + +Simplified accessor assignment, adds accessors of NAME for OBJ. Read accessor is +assumed to have the name `NAME+'Getter'` and write accessor is `NAME+'Setter'`. + +* `ACCESSOR_RW(CLASS, NAME)` - add read and write accessors NAME of CLASS. +* `ACCESSOR_R(CLASS, NAME)` - add read accessor NAME of CLASS. +* `ACCESSOR_M(CLASS, NAME)` - add method NAME of CLASS. + + +``` +void MyClass::init(Napi::Env env, Napi::Object exports) { + ... + Napi::Function ctor = DefineClass(env, "MyClass", { + ACCESSOR_R(MyClass, isDestroyed), + ACCESSOR_RW(MyClass, x), + ACCESSOR_M(MyClass, reset), + }); + ... +} +JS_GETTER(MyClass::isDestroyedGetter) { ... +JS_GETTER(MyClass::xGetter) { ... +JS_SETTER(MyClass::xSetter) { ... +JS_METHOD(MyClass::save) { ... +``` + +
+ + +
+ +Setter argument + +Works similar to method arguments. But there is always `value` +argument, from which a C++ value is extracted. + +* `SETTER_STR_ARG` +* `SETTER_INT32_ARG` +* `SETTER_INT_ARG` +* `SETTER_BOOL_ARG` +* `SETTER_UINT32_ARG` +* `SETTER_UINT_ARG` +* `SETTER_OFFS_ARG` +* `SETTER_DOUBLE_ARG` +* `SETTER_FLOAT_ARG` +* `SETTER_EXT_ARG` +* `SETTER_FUN_ARG` +* `SETTER_OBJ_ARG` +* `SETTER_ARRV_ARG` + +``` +JS_SETTER(MyClass::x) { SETTER_STR_ARG; + // Variable created: std::string v; + ... +``` + +
+ + +
+ +Data retrieval + +* `T *getArrayData(value, num = NULL)` - extracts TypedArray data of any type from +the given JS value. Does not accept `Array`. Checks with `IsArrayBuffer()`. +Returns `nullptr` for empty JS values. For unacceptable values throws TypeError. + +* `void *getData(value)` - if value is a TypedArray, then the result of +`getArrayData(value)` is returned. Otherwise if value has `'data'` property, it's +content is then returned as `node::Buffer`. Returns `nullptr` in other cases. + +
diff --git a/doc/class-wrapping.md b/doc/class-wrapping.md new file mode 100644 index 0000000..c9a368a --- /dev/null +++ b/doc/class-wrapping.md @@ -0,0 +1 @@ +# Es5 class wrapping diff --git a/doc/script-helpers.md b/doc/script-helpers.md new file mode 100644 index 0000000..5b755c2 --- /dev/null +++ b/doc/script-helpers.md @@ -0,0 +1 @@ +# Script Helpers diff --git a/doc/snippets.md b/doc/snippets.md new file mode 100644 index 0000000..2b7a19e --- /dev/null +++ b/doc/snippets.md @@ -0,0 +1,197 @@ +# Snippets + +## C++ Addon building + +N-API addons are built separately from the installation, so we can't/shouldn't +put the file **binding.gyp** to the module root anymore. It is better to have a +separate folder with a separate **package.json**, **binding.gyp** and the sources. + +A snippet for **src/package.json**: +``` +{ + "name": "build", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "node-gyp rebuild && node -e \"require('addon-tools-raub/cpbin')('ADDON')\"" + }, + "dependencies": { + "addon-tools-raub": "5.0.0", + "DEPS": "1.0.0" + } +} +``` + +* `ADDON` - the name of this addon (and subsequently of its binary). +* `DEPS` - dependency package(s). + + + +### Binary distribution + +In **package.json** use the `"postinstall"` script to download the libraries. +For example the following structure might work. Note that **Addon Tools** will +append any given URL with `/${platform}.zip` + +``` + "config" : { + "install" : "v1.0.0" + }, + "scripts": { + "postinstall": "install", + }, +``` + +Here `config.install` is the tag name to download the binaries from. +To use it, create the *install.js* file in your addon: + +``` +const install = require('addon-tools-raub/install'); +const prefix = 'https://github.com/USER/ADDON/releases/download'; +const tag = process.env.npm_package_config_install; +install(`${prefix}/${tag}`); +``` + +**Addon Tools** will unzip the downloaded file into the platform binary +directory. E.g. on Windows it will be **bin-windows**. + +* For a dependency package: + + Place the following piece of code in the `index.js` without changes. Method `paths()` + is described [here](../README.md). + ``` + module.exports = require('addon-tools-raub').paths(__dirname); + ``` + +* For a compiled addon: + + Require it in your **index.js** from the platform-specific directory. + ``` + const { bin } = require('addon-tools-raub'); + const core = require(`./${bin}/ADDON`); + ``` + + +Publishing binaries is done by attaching a zipped platform folder to the Github +release. Zip file must NOT contain platform folder as a subfolder, but rather +contain the final binaries. The tag of the release should be the same as in +`npm_package_config_install` - that is the way installer will find it. + +> NOTE: You can publish your binaries to anywhere, not necessarily Github. +Just tweak **YOUR install.js** script as appropriate. The only limitation +from **Addon Tools** is that it should be a zipped set of files/folders. + + +### GYP Variables + +``` + 'variables': { + 'bin' : ' + +See a snipped for src/binding.gyp here + +* Assume `DEPS` is the name of an Addon Tools compliant dependency module. +* Assume `ADDON` is the name of this addon's resulting binary. +* Assume C++ code goes to `cpp` subdirectory. + +``` +{ + 'variables': { + 'bin' : ' diff --git a/include/addon-tools.hpp b/include/addon-tools.hpp index 5e6b611..ebdca20 100644 --- a/include/addon-tools.hpp +++ b/include/addon-tools.hpp @@ -1,5 +1,5 @@ -#ifndef _ADDON_TOOLS_HPP_ -#define _ADDON_TOOLS_HPP_ +#ifndef ADDON_TOOLS_HPP +#define ADDON_TOOLS_HPP #define NODE_ADDON_API_DISABLE_DEPRECATED #define NAPI_DISABLE_CPP_EXCEPTIONS @@ -7,7 +7,7 @@ #ifdef _WIN32 - #define strcasestr(s, t) strstr(strupr(s), strupr(t)) + #define strcasestr(s, t) strstr(strupr(s), strupr(t)) #endif @@ -41,6 +41,7 @@ #define REQ_ARGS(N) \ if (info.Length() < (N)) { \ JS_THROW("Expected at least " #N " arguments"); \ + RET_UNDEFINED; \ } @@ -51,6 +52,7 @@ #define CHECK_REQ_ARG(I, C, T) \ if (info.Length() <= (I) || ! info[I].C) { \ JS_THROW("Argument " #I " must be of type `" T "`"); \ + RET_UNDEFINED; \ } #define CHECK_LET_ARG(I, C, T) \ @@ -60,6 +62,7 @@ " must be of type `" T \ "` or be `null`/`undefined`" \ ); \ + RET_UNDEFINED; \ } @@ -195,6 +198,7 @@ REQ_OBJ_ARG(I, _obj_##VAR); \ if ( ! _obj_##VAR.IsArray() ) { \ JS_THROW("Argument " #I " must be of type `Array`"); \ + RET_UNDEFINED; \ } \ Napi::Array VAR = _obj_##VAR.As(); @@ -203,14 +207,11 @@ REQ_OBJ_ARG(I, _obj_##VAR); \ if ( ! _obj_##VAR.IsTypedArray() ) { \ JS_THROW("Argument " #I " must be of type `TypedArray`"); \ + RET_UNDEFINED; \ } \ Napi::TypedArray VAR = _obj_##VAR.As(); -#define CTOR_CHECK(T) \ - if ( ! info.IsConstructCall() ) \ - JS_THROW(T " must be called with the 'new' keyword."); - #define DES_CHECK \ if (_isDestroyed) return; @@ -220,17 +221,15 @@ #define CACHE_CAS(CACHE, V) \ if (CACHE == V) { \ - return; \ + RET_UNDEFINED; \ } \ CACHE = V; -#define THIS_SETTER_CHECK \ - if (_isDestroyed) return; \ - NAPI_ENV; - #define SETTER_CHECK(C, T) \ - if ( ! value.C ) \ - JS_THROW("Value must be " T); + if ( ! value.C ) { \ + JS_THROW("Value must be " T); \ + RET_UNDEFINED; \ + } #define JS_METHOD(NAME) Napi::Value NAME(const Napi::CallbackInfo &info) @@ -335,7 +334,11 @@ template -inline Type* getArrayData(Napi::Env env, Napi::Object obj, int *num = nullptr) { +inline Type* getArrayData( + Napi::Env env, + Napi::Object obj, + int *num = nullptr +) { Type *data = nullptr; @@ -366,7 +369,11 @@ inline Type* getArrayData(Napi::Env env, Napi::Object obj, int *num = nullptr) { } template -inline Type* getBufferData(Napi::Env env, Napi::Object obj, int *num = nullptr) { +inline Type* getBufferData( + Napi::Env env, + Napi::Object obj, + int *num = nullptr +) { Type *data = nullptr; @@ -410,7 +417,11 @@ inline void *getData(Napi::Env env, Napi::Object obj) { } -inline void consoleLog(Napi::Env env, int argc, const Napi::Value *argv) { +inline void consoleLog( + Napi::Env env, + int argc, + const Napi::Value *argv +) { JS_RUN_2("console.log", log); std::vector args; for (int i = 0; i < argc; i++) { @@ -481,9 +492,17 @@ inline void eventEmitAsync( } -inline void inheritEs5(napi_env env, Napi::Function ctor, Napi::Function superCtor) { +inline void inheritEs5( + napi_env env, + Napi::Function ctor, + Napi::Function superCtor +) { - napi_value global, globalObject, setProto, ctorProtoProp, superCtorProtoProp; + napi_value global; + napi_value globalObject; + napi_value setProto; + napi_value ctorProtoProp; + napi_value superCtorProtoProp; napi_value argv[2]; napi_get_global(env, &global); @@ -510,153 +529,170 @@ typedef Napi::Value (*Es5GetterCallback)(const Napi::CallbackInfo& info); typedef void (*Es5SetterCallback)(const Napi::CallbackInfo& info); -#define DECLARE_ES5_CLASS(CLASS, NAME) \ -private: \ - static Napi::FunctionReference _ctorEs5; \ - static const char *_nameEs5; \ - static void _finalizeEs5(napi_env e, void *dest, void* hint); \ - static napi_value _createEs5(napi_env e, napi_callback_info i); \ - inline void super( \ - const Napi::CallbackInfo& info, \ - int argc, \ - const Napi::Value *argv \ - ) { \ - Napi::Function ctor = _ctorEs5.Value(); \ - if (ctor.Has("super_")) { \ - Napi::Function _super = ctor.Get("super_").As(); \ - std::vector args; \ - for (int i = 0; i < argc; i++) { \ - args.push_back(argv[i]); \ - } \ - _super.Call(info.This(), args); \ - } \ - } \ - inline void super( \ - const Napi::CallbackInfo& info, \ - int argc = 0, \ - const napi_value *argv = nullptr \ - ) { \ - Napi::Function ctor = _ctorEs5.Value(); \ - if (ctor.Has("super_")) { \ - Napi::Function _super = ctor.Get("super_").As(); \ - _super.Call(info.This(), argc, argv); \ - } \ - } \ - inline static Napi::Function wrap(Napi::Env env) { \ - napi_value __initResult; \ - napi_create_function(env, #NAME, 0, _createEs5, nullptr, &__initResult); \ - Napi::Function ctor = Napi::Function(env, __initResult); \ - _ctorEs5 = Napi::Persistent(ctor); \ - _ctorEs5.SuppressDestruct(); \ - return ctor; \ - } \ - inline static Napi::Function wrap( \ - Napi::Env env, \ - Napi::Function superCtor \ - ) { \ - Napi::Function ctor = wrap(env); \ - inheritEs5(env, ctor, superCtor); \ - return ctor; \ - } \ - inline static void method( \ - const char *name, \ - Es5MethodCallback cb \ - ) { \ - Napi::Function proto = _ctorEs5.Value().Get("prototype").As(); \ - proto.DefineProperty( \ - Napi::PropertyDescriptor::Function(proto.Env(), proto, name, cb) \ - ); \ - } \ - inline static void accessorR( \ - const char *name, \ - Es5GetterCallback getter \ - ) { \ - Napi::Function proto = _ctorEs5.Value().Get("prototype").As(); \ - proto.DefineProperty( \ - Napi::PropertyDescriptor::Accessor(proto.Env(), proto, name, getter) \ - ); \ - } \ - inline static void accessorRw( \ - const char *name, \ - Es5GetterCallback getter, \ - Es5SetterCallback setter \ - ) { \ - Napi::Function proto = _ctorEs5.Value().Get("prototype").As(); \ - proto.DefineProperty( \ - Napi::PropertyDescriptor::Accessor( \ - proto.Env(), \ - proto, \ - name, \ - getter, \ - setter \ - ) \ - ); \ - } \ -public: \ - inline static CLASS *unwrap(Napi::Object thatObj) { \ - CLASS *that; \ - napi_unwrap( \ - thatObj.Env(), \ - thatObj.Get(_nameEs5), \ - reinterpret_cast(&that) \ - ); \ - return that; \ +#define DECLARE_ES5_CLASS(CLASS, NAME) \ +public: \ + inline static CLASS *unwrap(Napi::Object thatObj) { \ + CLASS *that; \ + napi_unwrap( \ + thatObj.Env(), \ + thatObj.Get(_nameEs5), \ + reinterpret_cast(&that) \ + ); \ + return that; \ + } \ +private: \ + static Napi::FunctionReference _ctorEs5; \ + static const char *_nameEs5; \ + static void _finalizeEs5(napi_env e, void *dest, void* hint); \ + static napi_value _createEs5(napi_env e, napi_callback_info i); \ + inline void super( \ + const Napi::CallbackInfo& info, \ + int argc, \ + const Napi::Value *argv \ + ) { \ + Napi::Function ctor = _ctorEs5.Value(); \ + if (ctor.Has("super_")) { \ + Napi::Function _super = ctor.Get("super_").As(); \ + std::vector args; \ + for (int i = 0; i < argc; i++) { \ + args.push_back(argv[i]); \ + } \ + _super.Call(info.This(), args); \ + } \ + } \ + inline void super( \ + const Napi::CallbackInfo& info, \ + int argc = 0, \ + const napi_value *argv = nullptr \ + ) { \ + Napi::Function ctor = _ctorEs5.Value(); \ + if (ctor.Has("super_")) { \ + Napi::Function _super = ctor.Get("super_").As(); \ + _super.Call(info.This(), argc, argv); \ + } \ + } \ + inline static Napi::Function wrap(Napi::Env env) { \ + napi_value __initResult; \ + napi_create_function( \ + env, #NAME, 0, _createEs5, nullptr, &__initResult \ + ); \ + Napi::Function ctor = Napi::Function(env, __initResult); \ + _ctorEs5 = Napi::Persistent(ctor); \ + _ctorEs5.SuppressDestruct(); \ + return ctor; \ + } \ + inline static Napi::Function wrap( \ + Napi::Env env, \ + Napi::Function superCtor \ + ) { \ + Napi::Function ctor = wrap(env); \ + inheritEs5(env, ctor, superCtor); \ + return ctor; \ + } \ + inline static void method( \ + const char *name, \ + Es5MethodCallback cb \ + ) { \ + Napi::Function proto = ( \ + _ctorEs5.Value().Get("prototype").As() \ + ); \ + proto.DefineProperty( \ + Napi::PropertyDescriptor::Function( \ + proto.Env(), proto, name, cb \ + ) \ + ); \ + } \ + inline static void accessorR( \ + const char *name, \ + Es5GetterCallback getter \ + ) { \ + Napi::Function proto = ( \ + _ctorEs5.Value().Get("prototype").As() \ + ); \ + proto.DefineProperty( \ + Napi::PropertyDescriptor::Accessor( \ + proto.Env(), proto, name, getter \ + ) \ + ); \ + } \ + inline static void accessorRw( \ + const char *name, \ + Es5GetterCallback getter, \ + Es5SetterCallback setter \ + ) { \ + Napi::Function proto = ( \ + _ctorEs5.Value().Get("prototype").As() \ + ); \ + proto.DefineProperty( \ + Napi::PropertyDescriptor::Accessor( \ + proto.Env(), \ + proto, \ + name, \ + getter, \ + setter \ + ) \ + ); \ } -#define JS_GET_THAT(CLASS) \ - CLASS *that; \ - Napi::Object thatObj = info.This().As(); \ - napi_unwrap(info.Env(), thatObj.Get(_nameEs5), reinterpret_cast(&that)); +#define JS_GET_THAT(CLASS) \ + CLASS *that; \ + Napi::Object thatObj = info.This().As(); \ + napi_unwrap( \ + info.Env(), thatObj.Get(_nameEs5), reinterpret_cast(&that) \ + ); -#define JS_DECLARE_METHOD(CLASS, NAME) \ - inline static Napi::Value __st_##NAME(const Napi::CallbackInfo &info) { \ - JS_GET_THAT(CLASS); \ - return that->__i_##NAME(info); \ - }; \ +#define JS_DECLARE_METHOD(CLASS, NAME) \ + inline static Napi::Value __st_##NAME(const Napi::CallbackInfo &info) { \ + JS_GET_THAT(CLASS); \ + return that->__i_##NAME(info); \ + }; \ Napi::Value __i_##NAME(const Napi::CallbackInfo &info); #define JS_DECLARE_GETTER(CLASS, NAME) JS_DECLARE_METHOD(CLASS, NAME##Getter) -#define JS_DECLARE_SETTER(CLASS, NAME) \ - inline static void __st_##NAME##Setter( \ - const Napi::CallbackInfo &info \ - ) { \ - JS_GET_THAT(CLASS); \ - that->__i_##NAME##Setter(info, info[0]); \ - } \ - void __i_##NAME##Setter(const Napi::CallbackInfo &info, const Napi::Value &value); +#define JS_DECLARE_SETTER(CLASS, NAME) \ + inline static void __st_##NAME##Setter( \ + const Napi::CallbackInfo &info \ + ) { \ + JS_GET_THAT(CLASS); \ + that->__i_##NAME##Setter(info, info[0]); \ + } \ + Napi::Value __i_##NAME##Setter( \ + const Napi::CallbackInfo &info, \ + const Napi::Value &value \ + ); -#define JS_IMPLEMENT_METHOD(CLASS, NAME) \ +#define JS_IMPLEMENT_METHOD(CLASS, NAME) \ Napi::Value CLASS::__i_##NAME(const Napi::CallbackInfo &info) #define JS_IMPLEMENT_GETTER(CLASS, NAME) JS_IMPLEMENT_METHOD(CLASS, NAME##Getter) -#define JS_IMPLEMENT_SETTER(CLASS, NAME) \ - void CLASS::__i_##NAME##Setter( \ - const Napi::CallbackInfo &info, \ - const Napi::Value &value \ +#define JS_IMPLEMENT_SETTER(CLASS, NAME) \ + Napi::Value CLASS::__i_##NAME##Setter( \ + const Napi::CallbackInfo &info, \ + const Napi::Value &value \ ) #define JS_ASSIGN_METHOD(NAME) method(#NAME, __st_##NAME) #define JS_ASSIGN_GETTER(NAME) accessorR(#NAME, __st_##NAME##Getter) -#define JS_ASSIGN_SETTER(NAME) accessorRw(#NAME, __st_##NAME##Getter, __st_##NAME##Setter) +#define JS_ASSIGN_SETTER(NAME) \ + accessorRw(#NAME, __st_##NAME##Getter, __st_##NAME##Setter) -#define IMPLEMENT_ES5_CLASS(CLASS) \ - Napi::FunctionReference CLASS::_ctorEs5; \ - const char *CLASS::_nameEs5 = #CLASS; \ - void CLASS::_finalizeEs5(napi_env e, void *dest, void* hint) { \ - CLASS *instance = reinterpret_cast(dest); \ - delete instance; \ - } \ - napi_value CLASS::_createEs5(napi_env env, napi_callback_info i) { \ - Napi::CallbackInfo info(env, i); \ - CLASS *instance = new CLASS(info); \ - Napi::Object wrapObj = Napi::Object::New(env); \ - info.This().As().Set(_nameEs5, wrapObj); \ - napi_wrap(env, wrapObj, instance, _finalizeEs5, nullptr, nullptr); \ - return info.Env().Undefined(); \ +#define IMPLEMENT_ES5_CLASS(CLASS) \ + Napi::FunctionReference CLASS::_ctorEs5; \ + const char *CLASS::_nameEs5 = #CLASS; \ + void CLASS::_finalizeEs5(napi_env e, void *dest, void* hint) { \ + CLASS *instance = reinterpret_cast(dest); \ + delete instance; \ + } \ + napi_value CLASS::_createEs5(napi_env env, napi_callback_info i) { \ + Napi::CallbackInfo info(env, i); \ + CLASS *instance = new CLASS(info); \ + Napi::Object wrapObj = Napi::Object::New(env); \ + info.This().As().Set(_nameEs5, wrapObj); \ + napi_wrap(env, wrapObj, instance, _finalizeEs5, nullptr, nullptr); \ + return info.Env().Undefined(); \ } - -#endif // _ADDON_TOOLS_HPP_ +#endif // ADDON_TOOLS_HPP