wip doc
This commit is contained in:
parent
cbae1f5fa7
commit
f351c448cb
641
README.md
641
README.md
|
@ -12,536 +12,23 @@ This is a part of [Node3D](https://github.com/node-3d) project.
|
||||||
|
|
||||||
## Synopsis
|
## Synopsis
|
||||||
|
|
||||||
Helpers for Node.js **NAPI** addons and dependency packages:
|
Helpers for Node.js **NAPI** addons and dependency packages.
|
||||||
|
|
||||||
* Supported platforms (x64): Windows, Linux, OSX.
|
**Go to**:
|
||||||
* C++ helpers:
|
|
||||||
* Macro shortcuts for NAPI.
|
[include/addon-tools.hpp](doc/addon-tools.md)
|
||||||
* `eventEmit()` function.
|
Macro shortcuts for NAPI.
|
||||||
* `getData()` function.
|
[Es5 Class Wrapping](doc/class-wrapping.md)
|
||||||
* **Es5** class wrapper (like `Napi::ObjectWrap`, but Es5 functions).
|
An alternative, lightweight native class-defining mechanism for addons.
|
||||||
* Module helpers:
|
* [Script Helpers](doc/script-helpers.md)
|
||||||
* Crossplatform commands for GYP: `cp`, `rm`, `mkdir`.
|
Additional scripts for addon management.
|
||||||
* Deps unzip installer.
|
* [Snippets](doc/snippets.md)
|
||||||
* Url-to-buffer downloader.
|
Some repetitive bits of code for addons.
|
||||||
* 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' : '<!(node -p "require(\'addon-tools-raub\').rm")',
|
|
||||||
'cp' : '<!(node -p "require(\'addon-tools-raub\').cp")',
|
|
||||||
'mkdir' : '<!(node -p "require(\'addon-tools-raub\').mkdir")',
|
|
||||||
},
|
|
||||||
...
|
|
||||||
'action': ['<(mkdir)', '-p', '<(binary)']
|
|
||||||
```
|
|
||||||
|
|
||||||
On both Windows and Unix those commands now have the same result.
|
|
||||||
|
|
||||||
|
|
||||||
### Addon binary directory
|
|
||||||
|
|
||||||
```
|
|
||||||
'variables': {
|
|
||||||
'binary' : '<!(node -p "require(\'addon-tools-raub\').bin")',
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Include directories
|
|
||||||
|
|
||||||
```
|
|
||||||
'include_dirs': [
|
|
||||||
'<!@(node -p "require(\'addon-tools-raub\').include")',
|
|
||||||
],
|
|
||||||
```
|
|
||||||
|
|
||||||
Those are the directory paths to C++ include files for **Addon Tools** and
|
|
||||||
**Napi** (which is preinstalled with Addon Tools).
|
|
||||||
|
|
||||||
|
|
||||||
### Binary dependency package
|
|
||||||
|
|
||||||
If you design a module with binary dependencies for several platforms,
|
|
||||||
**Addon Tools** may help within the following guidelines:
|
|
||||||
|
|
||||||
* In **package.json** use a `"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",
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
where `config.install` is **YOUR install.js** is:
|
|
||||||
|
|
||||||
```
|
|
||||||
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**.
|
|
||||||
|
|
||||||
* Per platform binary directories:
|
|
||||||
|
|
||||||
* bin-windows
|
|
||||||
* bin-linux
|
|
||||||
* bin-osx
|
|
||||||
|
|
||||||
* The following piece of code in your `index.js` without changes. Method `paths()`
|
|
||||||
is described [here](#indexjs).
|
|
||||||
|
|
||||||
```
|
|
||||||
module.exports = require('addon-tools-raub').paths(__dirname);
|
|
||||||
```
|
|
||||||
|
|
||||||
* Publishing 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.
|
|
||||||
|
|
||||||
|
|
||||||
### Compiled addon
|
|
||||||
|
|
||||||
N-API ABI is compatible across Node.js versions, now addons
|
|
||||||
are just plain DLLs. Therefore distribution of the binaries is covered in the
|
|
||||||
previous section. But for an addon you have to provide a GYP compilation step.
|
|
||||||
N-API changes it's role to out-of-install compilation, so we can't/shouldnt
|
|
||||||
put the file **binding.gyp** to the module root anymore.
|
|
||||||
|
|
||||||
The workaround would be to have a separate directory within your project
|
|
||||||
(with simplified package.json) for the sake of addon compilation.
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"name": "build",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"addon-tools-raub": "5.0.0",
|
|
||||||
"deps-EXT_LIB": "1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
That together with **binding.gyp**, this would be enough to get the addon compiled.
|
|
||||||
Then the binaries are published to the Github release. When the addon
|
|
||||||
is installed, its **index.js** is responsible for reexport of the binary.
|
|
||||||
Just require the built module like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
const { bin } = require('addon-tools-raub');
|
|
||||||
const core = require(`./${bin}/MY_ADDON`);
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>See a snipped for binding.gyp here</summary>
|
|
||||||
|
|
||||||
* 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' : '<!(node -e "require(\'addon-tools-raub\').rm")',
|
|
||||||
'cp' : '<!(node -e "require(\'addon-tools-raub\').cp")',
|
|
||||||
'mkdir' : '<!(node -e "require(\'addon-tools-raub\').mkdir")',
|
|
||||||
'binary' : '<!(node -e "require(\'addon-tools-raub\').bin")',
|
|
||||||
'EXT_LIB_include' : '<!(node -e "require(\'deps-EXT_LIB\').include")',
|
|
||||||
'EXT_LIB_bin' : '<!(node -e "require(\'deps-EXT_LIB\').bin")',
|
|
||||||
},
|
|
||||||
'targets': [
|
|
||||||
{
|
|
||||||
'target_name': 'MY_ADDON',
|
|
||||||
'sources': [
|
|
||||||
'cpp/MY_ADDON.cpp',
|
|
||||||
],
|
|
||||||
'include_dirs': [
|
|
||||||
'<!(node -e "require(\'addon-tools-raub\').include")',
|
|
||||||
'<(EXT_LIB_include)',
|
|
||||||
'<(module_root_dir)/include',
|
|
||||||
],
|
|
||||||
'library_dirs': [ '<(EXT_LIB_bin)' ],
|
|
||||||
'conditions': [
|
|
||||||
[
|
|
||||||
'OS=="linux"',
|
|
||||||
{
|
|
||||||
'libraries': [
|
|
||||||
'-Wl,-rpath,<(EXT_LIB_bin)',
|
|
||||||
'<(EXT_LIB_bin)/libEXT_LIB.so',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'OS=="mac"',
|
|
||||||
{
|
|
||||||
'libraries': [
|
|
||||||
'-Wl,-rpath,<(EXT_LIB_bin)',
|
|
||||||
'<(EXT_LIB_bin)/EXT_LIB.dylib',
|
|
||||||
],
|
|
||||||
'xcode_settings': {
|
|
||||||
'DYLIB_INSTALL_NAME_BASE': '@rpath',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'OS=="win"',
|
|
||||||
{
|
|
||||||
'libraries': [ 'EXT_LIB.lib' ],
|
|
||||||
'defines' : [
|
|
||||||
'WIN32_LEAN_AND_MEAN',
|
|
||||||
'VC_EXTRALEAN'
|
|
||||||
],
|
|
||||||
'msvs_version' : '2013',
|
|
||||||
'msvs_settings' : {
|
|
||||||
'VCCLCompilerTool' : {
|
|
||||||
'AdditionalOptions' : [
|
|
||||||
'/O2','/Oy', # Comment this for debugging
|
|
||||||
# '/Z7', # Unomment this for debugging
|
|
||||||
'/GL','/GF','/Gm-','/EHsc',
|
|
||||||
'/MT','/GS','/Gy','/GR-','/Gd',
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'VCLinkerTool' : {
|
|
||||||
'AdditionalOptions' : ['/OPT:REF','/OPT:ICF','/LTCG']
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
'target_name' : 'make_directory',
|
|
||||||
'type' : 'none',
|
|
||||||
'dependencies' : ['MY_ADDON'],
|
|
||||||
'actions' : [{
|
|
||||||
'action_name' : 'Directory created.',
|
|
||||||
'inputs' : [],
|
|
||||||
'outputs' : ['build'],
|
|
||||||
'action': ['<(mkdir)', '-p', '<(binary)']
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name' : 'copy_binary',
|
|
||||||
'type' : 'none',
|
|
||||||
'dependencies' : ['make_directory'],
|
|
||||||
'actions' : [{
|
|
||||||
'action_name' : 'Module copied.',
|
|
||||||
'inputs' : [],
|
|
||||||
'outputs' : ['binary'],
|
|
||||||
'action' : [
|
|
||||||
'<(cp)',
|
|
||||||
'build/Release/MY_ADDON.node',
|
|
||||||
'<(binary)/MY_ADDON.node',
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
## 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 <napi.h>
|
|
||||||
```
|
|
||||||
|
|
||||||
with
|
|
||||||
|
|
||||||
```
|
|
||||||
#include <addon-tools.hpp>
|
|
||||||
```
|
|
||||||
|
|
||||||
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);
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>Return value</summary>
|
|
||||||
|
|
||||||
* `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`.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>New JS value</summary>
|
|
||||||
|
|
||||||
* `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.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>Method check</summary>
|
|
||||||
|
|
||||||
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()`.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>Method arguments</summary>
|
|
||||||
|
|
||||||
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<uint8_t>`.
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
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;
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>Set object accessors</summary>
|
|
||||||
|
|
||||||
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) { ...
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>Setter argument</summary>
|
|
||||||
|
|
||||||
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;
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>Data retrieval</summary>
|
|
||||||
|
|
||||||
* `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.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
## index.js
|
## index.js
|
||||||
|
|
||||||
Exports:
|
Main exports for cross-platform addon configuration. Exports:
|
||||||
* `paths(dir)` - function. Returns a set of platform dependent paths depending on
|
* `paths(dir)` - function. Returns a set of platform dependent paths depending on
|
||||||
input `dir`.
|
input `dir`.
|
||||||
* `bin` - platform binary directory absolute path.
|
* `bin` - platform binary directory absolute path.
|
||||||
|
@ -552,105 +39,3 @@ Use with `node -p` through list context command expansion `<!@(...)`
|
||||||
* `cp` - the location of `'_cp.bat'` file on Windows and plain `cp` on Unix.
|
* `cp` - the location of `'_cp.bat'` file on Windows and plain `cp` on Unix.
|
||||||
* `mkdir` - the location of `'_mkdir.bat'` file on Windows and plain `mkdir` on Unix.
|
* `mkdir` - the location of `'_mkdir.bat'` file on Windows and plain `mkdir` on Unix.
|
||||||
* `bin` - platform binary directory name.
|
* `bin` - platform binary directory name.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
### mkdir
|
|
||||||
|
|
||||||
On Unix, it will be an actual system `mkdir`, whereas on Windows it will use the
|
|
||||||
**mkdir.bat** file, located at the root of this package. This BAT file behaves
|
|
||||||
as if it was a `mkdir -p ...` call. You can still pass `-p` switch, which is
|
|
||||||
ignored. And the limitation is that you can not create a relative-path **-p**
|
|
||||||
folder. This can possibly be bypassed by supplying `./-p` or something like this.
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
'variables': {
|
|
||||||
'mkdir' : '<!(node -p "require(\'addon-tools-raub\').mkdir")',
|
|
||||||
},
|
|
||||||
...
|
|
||||||
'action' : ['<(mkdir)', '-p', 'binary'],
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### rm
|
|
||||||
|
|
||||||
Disregard `del` and `rd` on Windows command line. Now the same command can
|
|
||||||
be used on all platforms to remove single and multiple files and directories.
|
|
||||||
|
|
||||||
```
|
|
||||||
'variables': {
|
|
||||||
'rm' : '<!(node -e "require(\'addon-tools-raub\').rm")',
|
|
||||||
},
|
|
||||||
...
|
|
||||||
'action' : ['<(rm)', '-rf', 'dirname'],
|
|
||||||
```
|
|
||||||
|
|
||||||
### cp
|
|
||||||
|
|
||||||
For Windows the `/y` flag was embedded.
|
|
||||||
|
|
||||||
```
|
|
||||||
'variables': {
|
|
||||||
'cp' : '<!(node -e "require(\'addon-tools-raub\').cp")',
|
|
||||||
},
|
|
||||||
...
|
|
||||||
'action' : ['<(cp)', 'a', 'b'],
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Function consoleLog
|
|
||||||
|
|
||||||
In C++ addons, the use of **iostream** is discouraged because **Node.js** has its own
|
|
||||||
perspective on **stdout** behavior.
|
|
||||||
At first it may look as if `cout << "msg" << endl;` works nice, but it doesn't.
|
|
||||||
After a while, it just ceases on a midword, and you end up thinking something has
|
|
||||||
broken really hard in your addon.
|
|
||||||
|
|
||||||
To overcome this, we can use some `eval` magic to make a real `console.log`
|
|
||||||
call from C++ land. And this is where `consoleLog` comes into play.
|
|
||||||
|
|
||||||
* `inline void consoleLog(int argc, V8_VAR_VAL *argv)` - a generic logger,
|
|
||||||
receives any set of arguments.
|
|
||||||
|
|
||||||
* `inline void consoleLog(const std::string &message)` - an alias to log a single
|
|
||||||
string.
|
|
||||||
|
|
||||||
> 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
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
copy /y %1 %2
|
|
|
@ -1,5 +0,0 @@
|
||||||
for %%x in (%*) do (
|
|
||||||
|
|
||||||
if not %%x=="-p" if not exist %%x md %%x
|
|
||||||
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
|
|
||||||
)
|
|
|
@ -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 <napi.h>
|
||||||
|
```
|
||||||
|
|
||||||
|
with
|
||||||
|
|
||||||
|
```
|
||||||
|
#include <addon-tools.hpp>
|
||||||
|
```
|
||||||
|
|
||||||
|
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);
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Return value</summary>
|
||||||
|
|
||||||
|
* `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`.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>New JS value</summary>
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Method check</summary>
|
||||||
|
|
||||||
|
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()`.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Method arguments</summary>
|
||||||
|
|
||||||
|
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<uint8_t>`.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
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;
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Set object accessors</summary>
|
||||||
|
|
||||||
|
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) { ...
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Setter argument</summary>
|
||||||
|
|
||||||
|
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;
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Data retrieval</summary>
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
|
||||||
|
</details>
|
|
@ -0,0 +1 @@
|
||||||
|
# Es5 class wrapping
|
|
@ -0,0 +1 @@
|
||||||
|
# Script Helpers
|
|
@ -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' : '<!(node -p "require(\'addon-tools-raub\').bin")',
|
||||||
|
'DEPS_include' : '<!(node -p "require(\'DEPS\').include")',
|
||||||
|
'DEPS_bin' : '<!(node -p "require(\'DEPS\').bin")',
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
* `bin` - the name of this platform's binary directory, e.g. *bin-linux*.
|
||||||
|
* `DEPS_include` - the include folder for some dependency package.
|
||||||
|
* `DEPS_bin` - the binary folder for some dependency package.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Include directories
|
||||||
|
|
||||||
|
```
|
||||||
|
'include_dirs' : [
|
||||||
|
'<!@(node -p "require(\'addon-tools-raub\').include")',
|
||||||
|
'<(DEPS_include)',
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
The former contains both the path to include **Addon Tools** and the one for
|
||||||
|
**Napi** (which is preinstalled with Addon Tools). The latter can be any other
|
||||||
|
dependency include path(s).
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>See a snipped for src/binding.gyp here</summary>
|
||||||
|
|
||||||
|
* 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' : '<!(node -p "require(\'addon-tools-raub\').bin")',
|
||||||
|
'DEPS_include' : '<!(node -p "require(\'DEPS\').include")',
|
||||||
|
'DEPS_bin' : '<!(node -p "require(\'DEPS\').bin")',
|
||||||
|
},
|
||||||
|
'targets': [
|
||||||
|
{
|
||||||
|
'target_name' : 'bullet',
|
||||||
|
'sources' : [
|
||||||
|
'cpp/addon.cpp',
|
||||||
|
],
|
||||||
|
'include_dirs' : [
|
||||||
|
'<!@(node -p "require(\'addon-tools-raub\').include")',
|
||||||
|
'<(DEPS_include)',
|
||||||
|
],
|
||||||
|
'library_dirs' : [ '<(DEPS_bin)' ],
|
||||||
|
'libraries' : [ '-lDEPS' ],
|
||||||
|
'cflags!': ['-fno-exceptions'],
|
||||||
|
'cflags_cc!': ['-fno-exceptions'],
|
||||||
|
'conditions': [
|
||||||
|
|
||||||
|
[
|
||||||
|
'OS=="linux"',
|
||||||
|
{
|
||||||
|
'libraries': [
|
||||||
|
"-Wl,-rpath,'$$ORIGIN'",
|
||||||
|
"-Wl,-rpath,'$$ORIGIN/../node_modules/DEPS/<(bin)'",
|
||||||
|
"-Wl,-rpath,'$$ORIGIN/../../DEPS/<(bin)'",
|
||||||
|
],
|
||||||
|
'defines': ['__linux__'],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'OS=="mac"',
|
||||||
|
{
|
||||||
|
'libraries': [
|
||||||
|
'-Wl,-rpath,@loader_path',
|
||||||
|
'-Wl,-rpath,@loader_path/../node_modules/DEPS/<(bin)',
|
||||||
|
'-Wl,-rpath,@loader_path/../../DEPS/<(bin)',
|
||||||
|
],
|
||||||
|
'defines': ['__APPLE__'],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'OS=="win"',
|
||||||
|
{
|
||||||
|
'defines' : [
|
||||||
|
'WIN32_LEAN_AND_MEAN',
|
||||||
|
'VC_EXTRALEAN',
|
||||||
|
'_WIN32',
|
||||||
|
],
|
||||||
|
'msvs_settings' : {
|
||||||
|
'VCCLCompilerTool' : {
|
||||||
|
'AdditionalOptions' : [
|
||||||
|
'/GL', '/GF', '/EHsc', '/GS', '/Gy', '/GR-',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'VCLinkerTool' : {
|
||||||
|
'AdditionalOptions' : ['/RELEASE','/OPT:REF','/OPT:ICF','/LTCG'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef _ADDON_TOOLS_HPP_
|
#ifndef ADDON_TOOLS_HPP
|
||||||
#define _ADDON_TOOLS_HPP_
|
#define ADDON_TOOLS_HPP
|
||||||
|
|
||||||
#define NODE_ADDON_API_DISABLE_DEPRECATED
|
#define NODE_ADDON_API_DISABLE_DEPRECATED
|
||||||
#define NAPI_DISABLE_CPP_EXCEPTIONS
|
#define NAPI_DISABLE_CPP_EXCEPTIONS
|
||||||
|
@ -41,6 +41,7 @@
|
||||||
#define REQ_ARGS(N) \
|
#define REQ_ARGS(N) \
|
||||||
if (info.Length() < (N)) { \
|
if (info.Length() < (N)) { \
|
||||||
JS_THROW("Expected at least " #N " arguments"); \
|
JS_THROW("Expected at least " #N " arguments"); \
|
||||||
|
RET_UNDEFINED; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
#define CHECK_REQ_ARG(I, C, T) \
|
#define CHECK_REQ_ARG(I, C, T) \
|
||||||
if (info.Length() <= (I) || ! info[I].C) { \
|
if (info.Length() <= (I) || ! info[I].C) { \
|
||||||
JS_THROW("Argument " #I " must be of type `" T "`"); \
|
JS_THROW("Argument " #I " must be of type `" T "`"); \
|
||||||
|
RET_UNDEFINED; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CHECK_LET_ARG(I, C, T) \
|
#define CHECK_LET_ARG(I, C, T) \
|
||||||
|
@ -60,6 +62,7 @@
|
||||||
" must be of type `" T \
|
" must be of type `" T \
|
||||||
"` or be `null`/`undefined`" \
|
"` or be `null`/`undefined`" \
|
||||||
); \
|
); \
|
||||||
|
RET_UNDEFINED; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -195,6 +198,7 @@
|
||||||
REQ_OBJ_ARG(I, _obj_##VAR); \
|
REQ_OBJ_ARG(I, _obj_##VAR); \
|
||||||
if ( ! _obj_##VAR.IsArray() ) { \
|
if ( ! _obj_##VAR.IsArray() ) { \
|
||||||
JS_THROW("Argument " #I " must be of type `Array`"); \
|
JS_THROW("Argument " #I " must be of type `Array`"); \
|
||||||
|
RET_UNDEFINED; \
|
||||||
} \
|
} \
|
||||||
Napi::Array VAR = _obj_##VAR.As<Napi::Array>();
|
Napi::Array VAR = _obj_##VAR.As<Napi::Array>();
|
||||||
|
|
||||||
|
@ -203,14 +207,11 @@
|
||||||
REQ_OBJ_ARG(I, _obj_##VAR); \
|
REQ_OBJ_ARG(I, _obj_##VAR); \
|
||||||
if ( ! _obj_##VAR.IsTypedArray() ) { \
|
if ( ! _obj_##VAR.IsTypedArray() ) { \
|
||||||
JS_THROW("Argument " #I " must be of type `TypedArray`"); \
|
JS_THROW("Argument " #I " must be of type `TypedArray`"); \
|
||||||
|
RET_UNDEFINED; \
|
||||||
} \
|
} \
|
||||||
Napi::TypedArray VAR = _obj_##VAR.As<Napi::TypedArray>();
|
Napi::TypedArray VAR = _obj_##VAR.As<Napi::TypedArray>();
|
||||||
|
|
||||||
|
|
||||||
#define CTOR_CHECK(T) \
|
|
||||||
if ( ! info.IsConstructCall() ) \
|
|
||||||
JS_THROW(T " must be called with the 'new' keyword.");
|
|
||||||
|
|
||||||
#define DES_CHECK \
|
#define DES_CHECK \
|
||||||
if (_isDestroyed) return;
|
if (_isDestroyed) return;
|
||||||
|
|
||||||
|
@ -220,17 +221,15 @@
|
||||||
|
|
||||||
#define CACHE_CAS(CACHE, V) \
|
#define CACHE_CAS(CACHE, V) \
|
||||||
if (CACHE == V) { \
|
if (CACHE == V) { \
|
||||||
return; \
|
RET_UNDEFINED; \
|
||||||
} \
|
} \
|
||||||
CACHE = V;
|
CACHE = V;
|
||||||
|
|
||||||
#define THIS_SETTER_CHECK \
|
|
||||||
if (_isDestroyed) return; \
|
|
||||||
NAPI_ENV;
|
|
||||||
|
|
||||||
#define SETTER_CHECK(C, T) \
|
#define SETTER_CHECK(C, T) \
|
||||||
if ( ! value.C ) \
|
if ( ! value.C ) { \
|
||||||
JS_THROW("Value must be " T);
|
JS_THROW("Value must be " T); \
|
||||||
|
RET_UNDEFINED; \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#define JS_METHOD(NAME) Napi::Value NAME(const Napi::CallbackInfo &info)
|
#define JS_METHOD(NAME) Napi::Value NAME(const Napi::CallbackInfo &info)
|
||||||
|
@ -335,7 +334,11 @@
|
||||||
|
|
||||||
|
|
||||||
template<typename Type = uint8_t>
|
template<typename Type = uint8_t>
|
||||||
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;
|
Type *data = nullptr;
|
||||||
|
|
||||||
|
@ -366,7 +369,11 @@ inline Type* getArrayData(Napi::Env env, Napi::Object obj, int *num = nullptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Type = uint8_t>
|
template<typename Type = uint8_t>
|
||||||
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;
|
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);
|
JS_RUN_2("console.log", log);
|
||||||
std::vector<napi_value> args;
|
std::vector<napi_value> args;
|
||||||
for (int i = 0; i < argc; i++) {
|
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_value argv[2];
|
||||||
|
|
||||||
napi_get_global(env, &global);
|
napi_get_global(env, &global);
|
||||||
|
@ -511,6 +530,16 @@ typedef void (*Es5SetterCallback)(const Napi::CallbackInfo& info);
|
||||||
|
|
||||||
|
|
||||||
#define DECLARE_ES5_CLASS(CLASS, NAME) \
|
#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<void**>(&that) \
|
||||||
|
); \
|
||||||
|
return that; \
|
||||||
|
} \
|
||||||
private: \
|
private: \
|
||||||
static Napi::FunctionReference _ctorEs5; \
|
static Napi::FunctionReference _ctorEs5; \
|
||||||
static const char *_nameEs5; \
|
static const char *_nameEs5; \
|
||||||
|
@ -544,7 +573,9 @@ private: \
|
||||||
} \
|
} \
|
||||||
inline static Napi::Function wrap(Napi::Env env) { \
|
inline static Napi::Function wrap(Napi::Env env) { \
|
||||||
napi_value __initResult; \
|
napi_value __initResult; \
|
||||||
napi_create_function(env, #NAME, 0, _createEs5, nullptr, &__initResult); \
|
napi_create_function( \
|
||||||
|
env, #NAME, 0, _createEs5, nullptr, &__initResult \
|
||||||
|
); \
|
||||||
Napi::Function ctor = Napi::Function(env, __initResult); \
|
Napi::Function ctor = Napi::Function(env, __initResult); \
|
||||||
_ctorEs5 = Napi::Persistent(ctor); \
|
_ctorEs5 = Napi::Persistent(ctor); \
|
||||||
_ctorEs5.SuppressDestruct(); \
|
_ctorEs5.SuppressDestruct(); \
|
||||||
|
@ -562,18 +593,26 @@ private: \
|
||||||
const char *name, \
|
const char *name, \
|
||||||
Es5MethodCallback cb \
|
Es5MethodCallback cb \
|
||||||
) { \
|
) { \
|
||||||
Napi::Function proto = _ctorEs5.Value().Get("prototype").As<Napi::Function>(); \
|
Napi::Function proto = ( \
|
||||||
|
_ctorEs5.Value().Get("prototype").As<Napi::Function>() \
|
||||||
|
); \
|
||||||
proto.DefineProperty( \
|
proto.DefineProperty( \
|
||||||
Napi::PropertyDescriptor::Function(proto.Env(), proto, name, cb) \
|
Napi::PropertyDescriptor::Function( \
|
||||||
|
proto.Env(), proto, name, cb \
|
||||||
|
) \
|
||||||
); \
|
); \
|
||||||
} \
|
} \
|
||||||
inline static void accessorR( \
|
inline static void accessorR( \
|
||||||
const char *name, \
|
const char *name, \
|
||||||
Es5GetterCallback getter \
|
Es5GetterCallback getter \
|
||||||
) { \
|
) { \
|
||||||
Napi::Function proto = _ctorEs5.Value().Get("prototype").As<Napi::Function>(); \
|
Napi::Function proto = ( \
|
||||||
|
_ctorEs5.Value().Get("prototype").As<Napi::Function>() \
|
||||||
|
); \
|
||||||
proto.DefineProperty( \
|
proto.DefineProperty( \
|
||||||
Napi::PropertyDescriptor::Accessor(proto.Env(), proto, name, getter) \
|
Napi::PropertyDescriptor::Accessor( \
|
||||||
|
proto.Env(), proto, name, getter \
|
||||||
|
) \
|
||||||
); \
|
); \
|
||||||
} \
|
} \
|
||||||
inline static void accessorRw( \
|
inline static void accessorRw( \
|
||||||
|
@ -581,7 +620,9 @@ private: \
|
||||||
Es5GetterCallback getter, \
|
Es5GetterCallback getter, \
|
||||||
Es5SetterCallback setter \
|
Es5SetterCallback setter \
|
||||||
) { \
|
) { \
|
||||||
Napi::Function proto = _ctorEs5.Value().Get("prototype").As<Napi::Function>(); \
|
Napi::Function proto = ( \
|
||||||
|
_ctorEs5.Value().Get("prototype").As<Napi::Function>() \
|
||||||
|
); \
|
||||||
proto.DefineProperty( \
|
proto.DefineProperty( \
|
||||||
Napi::PropertyDescriptor::Accessor( \
|
Napi::PropertyDescriptor::Accessor( \
|
||||||
proto.Env(), \
|
proto.Env(), \
|
||||||
|
@ -591,23 +632,15 @@ private: \
|
||||||
setter \
|
setter \
|
||||||
) \
|
) \
|
||||||
); \
|
); \
|
||||||
} \
|
|
||||||
public: \
|
|
||||||
inline static CLASS *unwrap(Napi::Object thatObj) { \
|
|
||||||
CLASS *that; \
|
|
||||||
napi_unwrap( \
|
|
||||||
thatObj.Env(), \
|
|
||||||
thatObj.Get(_nameEs5), \
|
|
||||||
reinterpret_cast<void**>(&that) \
|
|
||||||
); \
|
|
||||||
return that; \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#define JS_GET_THAT(CLASS) \
|
#define JS_GET_THAT(CLASS) \
|
||||||
CLASS *that; \
|
CLASS *that; \
|
||||||
Napi::Object thatObj = info.This().As<Napi::Object>(); \
|
Napi::Object thatObj = info.This().As<Napi::Object>(); \
|
||||||
napi_unwrap(info.Env(), thatObj.Get(_nameEs5), reinterpret_cast<void**>(&that));
|
napi_unwrap( \
|
||||||
|
info.Env(), thatObj.Get(_nameEs5), reinterpret_cast<void**>(&that) \
|
||||||
|
);
|
||||||
|
|
||||||
#define JS_DECLARE_METHOD(CLASS, NAME) \
|
#define JS_DECLARE_METHOD(CLASS, NAME) \
|
||||||
inline static Napi::Value __st_##NAME(const Napi::CallbackInfo &info) { \
|
inline static Napi::Value __st_##NAME(const Napi::CallbackInfo &info) { \
|
||||||
|
@ -625,7 +658,10 @@ public: \
|
||||||
JS_GET_THAT(CLASS); \
|
JS_GET_THAT(CLASS); \
|
||||||
that->__i_##NAME##Setter(info, info[0]); \
|
that->__i_##NAME##Setter(info, info[0]); \
|
||||||
} \
|
} \
|
||||||
void __i_##NAME##Setter(const Napi::CallbackInfo &info, const Napi::Value &value);
|
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)
|
Napi::Value CLASS::__i_##NAME(const Napi::CallbackInfo &info)
|
||||||
|
@ -633,14 +669,15 @@ public: \
|
||||||
#define JS_IMPLEMENT_GETTER(CLASS, NAME) JS_IMPLEMENT_METHOD(CLASS, NAME##Getter)
|
#define JS_IMPLEMENT_GETTER(CLASS, NAME) JS_IMPLEMENT_METHOD(CLASS, NAME##Getter)
|
||||||
|
|
||||||
#define JS_IMPLEMENT_SETTER(CLASS, NAME) \
|
#define JS_IMPLEMENT_SETTER(CLASS, NAME) \
|
||||||
void CLASS::__i_##NAME##Setter( \
|
Napi::Value CLASS::__i_##NAME##Setter( \
|
||||||
const Napi::CallbackInfo &info, \
|
const Napi::CallbackInfo &info, \
|
||||||
const Napi::Value &value \
|
const Napi::Value &value \
|
||||||
)
|
)
|
||||||
|
|
||||||
#define JS_ASSIGN_METHOD(NAME) method(#NAME, __st_##NAME)
|
#define JS_ASSIGN_METHOD(NAME) method(#NAME, __st_##NAME)
|
||||||
#define JS_ASSIGN_GETTER(NAME) accessorR(#NAME, __st_##NAME##Getter)
|
#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) \
|
#define IMPLEMENT_ES5_CLASS(CLASS) \
|
||||||
Napi::FunctionReference CLASS::_ctorEs5; \
|
Napi::FunctionReference CLASS::_ctorEs5; \
|
||||||
|
@ -658,5 +695,4 @@ public: \
|
||||||
return info.Env().Undefined(); \
|
return info.Env().Undefined(); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // ADDON_TOOLS_HPP
|
||||||
#endif // _ADDON_TOOLS_HPP_
|
|
||||||
|
|
Loading…
Reference in New Issue