I am trying to create a Node C++ module for the purpose of interfacing with the Steam api. The library file is ./steam/lib/linux64/libsteam_api.so, and header files are in ./steam.
I have created a small regular C++ file for testing, which successfully uses the Steam api, imported using #include "steam_api.h". I have complied and imported the shared library like this: g++ -L./steam/lib/linux64 -Wl,-rpath=./steam/lib/linux64 -Isteam -lsteam_api main.cpp
binding.gyp:
{
"targets": [ {
"target_name": "steam",
"sources": [ "steam.cpp" ],
"include_dirs": [
"steam",
"<!#(node -p \"require('node-addon-api').include\")"
],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"libraries": [ "./steam/lib/linux64/libsteam_api.so" ]
} ]
}
When I try to compile the Node module using node-gyp, I get g++: error: ./steam/lib/linux64/libsteam_api.so: No such file or directory
How do I correctly import the shared library?
After looking through some examples and a lot of trial and error, I was able to correct binding.gpy:
{
"targets": [ {
"target_name": "steam",
"sources": [ "steam.cpp" ],
"include_dirs": [
"steam",
"<!#(node -p \"require('node-addon-api').include\")"
],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"libraries": [
"-lsteam_api",
"-L../steam/lib/linux64",
"-Wl,-rpath=./steam/lib/linux64"
]
} ]
}
The libraries section needed to include the arguments similar to how they were invoked with g++, except "-L" differed from "-Wl,-rpath=" and the g++ inputs in needing to start one folder level up for some unknown reason.
It looks like node-gyp is changing the current directory as it runs, which invalidates your relative path. Either use an absolute path instead, or do some experimentation to find the new current directory and then use a path relative to that.
Related
I am using a library which depends on libsodium (libpaseto). I have installed it on my machine and I am trying to build a nodejs addon.
I have the following binding.gyp file:
{
"targets": [{
"target_name": "testaddon",
"cflags!": [ "-fno-exceptions"],
"cflags_cc!": [ "-fno-exceptions" ],
"cflags_cc":["-std=c++1z" ,"-lsodium","$(pkg-config --cflags libsodium)"],
"ldflags_cc":["$(pkg-config --libs libsodium)"],
"sources": [
"cppsrc/main.cpp",
"cppsrc/functionexample.cpp",
"cppsrc/libpaseto/src/helpers.c",
"cppsrc/libpaseto/src/paseto.c",
"cppsrc/libpaseto/src/paseto_v2_local.c",
"cppsrc/libpaseto/src/paseto_v2_public.c",
],
'include_dirs': [
"<!#(node -p \"require('node-addon-api').include\")"
],
'libraries': [],
'dependencies': [
"<!(node -p \"require('node-addon-api').gyp\")"
],
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ,'SODIUM_STATIC=1',
'HAVE_LIBM=1']
}]
}
Here is the code for my addon:
#include "./headers/functionexample.h"
#include <iostream>
#include <fstream>
#include "libpaseto/include/paseto.h"
#include <exception>
Napi::Boolean addon::InitPasetoWrapped(const Napi::CallbackInfo &info)
{
bool returnValue = paseto_init();
Napi::Env env = info.Env();
return Napi::Boolean::New(env, returnValue);
}
Napi::Object addon::Init(Napi::Env env, Napi::Object exports)
{
exports.Set("add", Napi::Function::New(env, addon::addWrapped));
exports.Set("getOsName", Napi::Function::New(env, addon::getOSNameWrapped));
exports.Set("writeToFile", Napi::Function::New(env, addon::writeFileWrapped));
exports.Set("initPaseto", Napi::Function::New(env, addon::InitPasetoWrapped));
return exports;
}
I am able to compile it without a problem. I am exporting some methods in my nodejs code. However when I try to run the exported method initPaseto from my js code I get the following error:
node:symbol lookup error: undefined symbol:sodium init
How can I fix it?
So I finally found the answer.In the libsodium documentation it mentions that you have to pass the -lsodium flag to be able to compile without problems.So what I had to do was to add this flag in my libraries in binding.gyp. So here is my final binding.gyp file:
{
"targets": [{
"target_name": "testaddon",
"cflags!": [ "-fno-exceptions"],
"cflags_cc!": [ "-fno-exceptions" ],
"cflags_cc":["-std=c++1z"],
"sources": [
"cppsrc/main.cpp",
"cppsrc/functionexample.cpp",
"cppsrc/libpaseto/src/helpers.c",
"cppsrc/libpaseto/src/paseto.c",
"cppsrc/libpaseto/src/paseto_v2_local.c",
"cppsrc/libpaseto/src/paseto_v2_public.c",
],
'include_dirs': [
"<!#(node -p \"require('node-addon-api').include\")"
],
'libraries': ['-lsodium'],
'dependencies': [
"<!(node -p \"require('node-addon-api').gyp\")"
],
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ,'SODIUM_STATIC=1',
'HAVE_LIBM=1']
}]
}
Also this github issue helped me to understand how to add a library in my project:
https://github.com/nodejs/node-gyp/issues/328
I can set module aliases with no issue:
"baseUrl": "./app",
"paths": {
"assets/*": ["assets/*"],
"components/*": ["components/*"],
...
},
This works. However, I (actually my client, who wants to extract some parts of the project as a module in the future) want to create subdirectories as aliases to different paths (assume that all the directories as values exist, and I cannot change the actual directory structure):
"paths": {
"assets/*": ["assets/*"],
"components/*": ["components/*"],
"#myapp/api/*": ["services/myapp/*"],
"#myapp/state/*": ["state/*"],
},
Putting aside whether this is a good pattern/practice or not (as I'm asked to do it this way), is this technically possible without touching the physical directory structure (and without the use of 3rd party dependencies)? (I'm using Typescript 3.4.3 and Vscode 1.33.1)
"baseUrl": "./",
"paths": {
"#myapp/environment": [ "./src/environments/environment" ],
"#myapp/extensions": [ "./src/extensions" ],
"#myapp/testing": [ "./src/testing" ],
"#myapp/constants": [ "./src/app/shared/constants" ],
"#myapp/data": [ "./src/app/data" ],
"#myapp/shared": [ "./src/app/shared" ],
"#myapp/core/*": [ "./src/app/core/*" ],
"#myapp/*": [ "./src/app/*" ]
}
this works like a charm on my project
I'm trying to build a native Node.js module that links against a 3rd party shared library. This library is delivered as part of a bundle that includes pre-built versions for different OSes and Architectures in different directories.
e.g.
/opt/Foo/linux/x86/lib/libfoo.so
/opt/Foo/linux/x86/include/foo.h
/opt/Foo/linux/x86_64/lib/libfoo.so
/opt/Foo/linux/x86_64/include/foo.h
/opt/Foo/linux/arm/lib/libfoo.so
/opt/Foo/linux/arm/include/foo.h
/opt/Foo/mac/x86_64/lib/libfoo.so
/opt/Foo/mac/x86_64/include/foo.h
my binding.gyp currently looks like this:
{
'targets': [
{
'target_name': 'foo',
'sources': ['foo.cpp', 'foo.h'],
'include_dirs': ["<!(node -e \"require('nan')\")"],
'conditions': [
['OS=="mac"', {
'include_dirs': ['/opt/Foo/mac/x86_64/include'],
'libraries': ['-L/opt/Foo/mac/x86_64/lib', '-lfoo']
}
],
['OS=="linux"', {
'include_dirs': ['/opt/Foo/linux/x86_64/include'],
'libraries': ['-L/opt/Foo/linux/x86_64/lib', '-lfoo']
}
]
]
}
]
}
I don't seem to be able to find the syntax for conditions to differentiate on the current platform architecture.
Having not found any other solutions I came up with the following:
...
['OS=="linux"', {
'include_dirs': ["<!(node -e \"console.log('/opt/Foo/linux/%s/include',require('process').arch);\")"],
'libraries': ["<!(node -e \"console.log('-L/opt/Foo/linux/%s/lib',require('process').arch);\")", '-lfoo']
}
]
...
I had to rename the /opt/Foo/Linux/x86_64 to directory to /opt/Foo/Linux/x64 to match the output from process.arch.
I am building a native module that needs to link a static library. The path to that library. My binding.gyp file has the following appearance:
{
"targets": [
{
"target_name": "DcpServer",
"sources": [
"DcpServer.cc"
],
"include_dirs": [
"../../coratools",
"../../../boost-1.65.1"
],
"libraries": [
"<(module_root_dir)/../../coratools/release_uni64/coratools.lib"
],
"defines": [ "CSIWEB_EMBEDDED", "UNICODE", "_UNICODE" ],
"configurations": {
"Release": {
"msvs_settings": {
"VCCLCompilerTool": {
"ExceptionHandling": 1,
"RuntimeTypeInfo": "true"
}
}
},
"Debug": {
"msvs_settings": {
"VCCLCompilerTool": {
"ExceptionHandling": 1,
"RuntimeTypeInfo": "true"
}
}
}
}
}
]
}
The path to coratools.lib will vary based upon whether the debug or release configuration is selected. The problem is that node-gyp did not allow me to place the "libraries" key within the "configurations" property. Is there a way of doing what I want by making the library path conditional?
I never did discover how to do this. In the end, I switched to using cmake-js to build my native module.
I'm developing a big node.js project which also includes several native libraries.
To use these libraries in JavaScript I'm compiling them to node addons (.node) using node-gyp.
I'd like to run node-gyp once from the root directory to compile all the available binding.gyp recursively (in all the subdirectories).
Is there any way to do that?
GYP allows to set a list of dependencies for a target. You can create a target of type: none in the top-level bindings.gyp and list there dependencies from subdirectories:
{
'targets': [
{
'target_name': 'build_all',
'type': 'none',
'dependencies': ['subdir1/bindings.gyp:*', 'subdir/subdir2/bindings.gyp:*'],
# or generate dependencies list with a command expansion
'dependencies': ['<!#(find -mindepth 2 -name binding.gyp | sed -e s/$/:*/)'],
}
]
}
This will compile all the dependencies and put them into build/ directory in the root.
For putting each addon in its corresponding directory, add a postbuild target inside the addon's binding.gyp:
{
"targets": [
{
"target_name": "my-target",
"sources": [ "example.cpp" ]
},
{
"target_name": "action_after_build",
"type": "none",
"dependencies": [ "my-target" ],
"copies": [
{
"files": [ "<(PRODUCT_DIR)/my-target.node" ],
"destination": "."
}
]
}
]
}
I didn't find any option to do this with just node-gyp, but one of the possible solutions is doing this in a script.
For example, adding the following to the package.json in the root folder:
"scripts": {
"install": "find ./app/* -name binding.gyp -execdir node-gyp rebuild ;"
}
This will cause all the native addons to compile when running npm install in the root folder.
An alternative to the other answers which seems to work so far (without ever having to update binding.gyp):
{
"targets": [
{
"target_name": "addon",
"sources": [
"<!#(node -p \"var fs=require('fs'),path=require('path'),walk=function(r){let t,e=[],n=null;try{t=fs.readdirSync(r)}catch(r){n=r.toString()}if(n)return n;var a=0;return function n(){var i=t[a++];if(!i)return e;let u=path.resolve(r,i);i=r+'/'+i;let c=fs.statSync(u);if(c&&c.isDirectory()){let r=walk(i);return e=e.concat(r),n()}return e.push(i),n()}()};walk('./sources').join(' ');\")"
]
}
]
}
(from https://stackoverflow.com/a/60947528/2016831)