4

I want compile Node.js C/C++ extension without using node-gyp package.

I have experience with meson and would use it to compile Node.js module.

Can u suggest example with how to compile Node.js native module?

P.S. I use a few subproject with static proprietary library and node-gyp is very simple utilite for this hard build.

Vitold S.
  • 402
  • 4
  • 13
  • 1
    I think we should develop something similar to node-gyp and CMake.js, but powered by Meson instead. – Kyrylo Polezhaiev Aug 29 '20 at 16:21
  • @KyryloPolezhaiev you right. But I want to invite Node developer to collaborate about support another one compile ecosystem. I can reproduce command in meson but better to involve Node developer to this activityes. – Vitold S. Sep 07 '20 at 16:51
  • Has anyone developed anything like this? – fabiomaia Jun 30 '22 at 21:14
  • @KyryloPolezhaiev well, a `meson.build` file for compiling such an extension I posted. From what I see, there's not much left to `node-gyp`. Probably, only the ability to install a specified node version, and also the fact it has lots of `-I/path/to//bar` args upon compiling a file, disregarding if `bar` is used or not. The latter may be easily hardcoded to `meson.build` as well, though I don't see much reason for that – Hi-Angel Aug 08 '22 at 22:36
  • @fabiomaia yes, a `meson.build` I posted. – Hi-Angel Aug 08 '22 at 22:37

1 Answers1

1

I agree it's a nice idea, since node-gyp would generate Makefiles, whereas Meson uses modern Ninja that works much faster. Also, building with node-gyp results in warnings from node.h you can do little about, but Meson allows to suppress them with is_system: true.

For this answer I use an example addon from here. The C++ file:

// hello.cc
#include <node.h>

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(
      isolate, "world").ToLocalChecked());
}

void Initialize(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

}  // namespace demo

The meson.build; it would compile the addon and also test that it's functional by making node load it then invoke hello() from the addon:

project('C++ node example addon', 'cpp')

inc_node = include_directories('/usr/include/node', is_system: true)

# For compatibility with node-gyp we use "shared_module" rather than
# "shared_library". The difference important in the current context is that
# shared_module will happily allow undefined references at link-time. It may or may
# not be what you want, feel free to change to `shared_library`, in which case you
# have to also link it with find_library('node')
shared_module(
  'hello',
  ['hello.cc'],
  include_directories : [inc_node],
  name_prefix: '',     # don't prepend "lib"
  name_suffix: 'node', # required for addon to load properly
)

node = find_program('node')

test('addon',
     node,
     args: ['-e',
            'const addon = require("./hello.node");'
            + 'console.log(addon.hello());'
           ]
    )

Here's an example of running it:

$ meson build
The Meson build system
Version: 0.62.2
Source dir: /tmp/node_C_hello_world
Build dir: /tmp/node_C_hello_world/build
Build type: native build
Project name: C++ node example addon
Project version: undefined
C++ compiler for the host machine: c++ (gcc 12.1.1 "c++ (GCC) 12.1.1 20220507 (Red Hat 12.1.1-1)")
C++ linker for the host machine: c++ ld.bfd 2.37-27
Host machine cpu family: x86_64
Host machine cpu: x86_64
Library node found: YES
Program node found: YES (/usr/bin/node)
Build targets in project: 1

Found ninja-1.10.2 at /usr/bin/ninja
$ ninja -C build/
ninja: Entering directory `build/'
[2/2] Linking target hello.node
$ ninja -C build/ test
ninja: Entering directory `build/'
[0/1] Running all tests.
1/1 addon        OK              0.20s


Ok:                 1
Expected Fail:      0
Fail:               0
Unexpected Pass:    0
Skipped:            0
Timeout:            0

Full log written to /tmp/node_C_hello_world/build/meson-logs/testlog.txt

Some notes:

  • the addon library has to have .node postfix, otherwise node will fail to load it with a SyntaxError: Invalid or unexpected token
  • the nodejs-devel (or whatever the package with node.h is called on your system) package, unfortunately, lacks pkg-config .pc files, so you have to call include_directories() directly rather than simply using a dependency() call.
  • the node-gyp has some fancy arguments, which I'm not sure whether are needed or not. Am not a nodejs developer, so can't say anything useful on that matter, but here are ones I deemed to be potentially important for more complicated addons:
    • link argument -rdynamic
    • compile arguments '-DNODE_GYP_MODULE_NAME=addon' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DBUILDING_NODE_EXTENSION'
Hi-Angel
  • 4,933
  • 8
  • 63
  • 86