Skip to content

Migrate project from NAN to Node-API for improved compatibility #1088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@
'./src/shadow_node.cpp',
],
'include_dirs': [
'.',
"<!(node -e \"require('nan')\")",
'<!@(node -p "require(\"node-addon-api\").include")',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice that a consistent failure on Actions https://github.com/RobotWebTools/rclnodejs/actions/runs/13857494263/job/38777515813?pr=1088#step:7:25, can you pass the compiling locally?

'<(ros_include_root)',
],
'cflags!': [
Expand Down Expand Up @@ -129,6 +128,30 @@
"defines": ["NODE_RUNTIME_ELECTRON=1"]
}
],
],
'dependencies': [
'scripts/config.js',
'<!(node -p "require(\"node-addon-api\").gyp")',
],
},
{
"target_name": "addon",
"sources": [ "src/addon.cpp" ],
"include_dirs": [
"<!(node -e \"require('node-addon-api').include\")"
],
"dependencies": [
"<!(node -e \"require('node-addon-api').gyp\")"
],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
"conditions": [
[ 'OS=="win"', {
"msvs_settings": {
"VCCLCompilerTool": { "ExceptionHandling": 1 }
}
}]
]
}
]
Expand Down
13 changes: 13 additions & 0 deletions example_napi.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <node_api.h>

// ...existing code...

// N-API initialization function
napi_value Init(napi_env env, napi_value exports) {
// ...existing code...
return exports;
}

// ...existing code...

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
13 changes: 13 additions & 0 deletions example_node_api.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <node_api.h>

// ...existing code...

// Node-API initialization function
napi_value Init(napi_env env, napi_value exports) {
// ...existing code...
return exports;
}

// ...existing code...

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"is-close": "^1.3.3",
"json-bigint": "^1.0.0",
"nan": "^2.22.0",
"node-addon-api": "^5.0.0",
"terser": "^5.39.0",
"walk": "^2.3.15"
},
Expand Down
76 changes: 36 additions & 40 deletions src/addon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <nan.h>
#include <napi.h>
#include <node_api.h>

#include "macros.hpp"
#include "rcl_action_bindings.hpp"
Expand All @@ -23,62 +24,55 @@
#include "rcutils/macros.h"
#include "shadow_node.hpp"

bool IsRunningInElectronRenderer() {
auto global = Nan::GetCurrentContext()->Global();
auto process =
Nan::To<v8::Object>(Nan::Get(global, Nan::New("process").ToLocalChecked())
.ToLocalChecked())
.ToLocalChecked();
auto process_type =
Nan::Get(process, Nan::New("type").ToLocalChecked()).ToLocalChecked();
return process_type->StrictEquals(Nan::New("renderer").ToLocalChecked());
bool IsRunningInElectronRenderer(napi_env env) {
napi_value global, process, process_type;
napi_get_global(env, &global);
napi_get_named_property(env, global, "process", &process);
napi_get_named_property(env, process, "type", &process_type);

bool is_renderer;
napi_value renderer_str;
napi_create_string_utf8(env, "renderer", NAPI_AUTO_LENGTH, &renderer_str);
napi_strict_equals(env, process_type, renderer_str, &is_renderer);
return is_renderer;
}

void InitModule(v8::Local<v8::Object> exports) {
// workaround process name mangling by chromium
//
// rcl logging uses `program_invocation_name` to determine the log file,
// chromium mangles the program name to include all args, this causes a
// ENAMETOOLONG error when starting ros. Workaround is to replace the first
// occurence of ' -' with the null terminator. see:
// https://unix.stackexchange.com/questions/432419/unexpected-non-null-encoding-of-proc-pid-cmdline
napi_value Init(napi_env env, napi_value exports) {
// workaround process name mangling by chromium
//
// rcl logging uses `program_invocation_name` to determine the log file,
// chromium mangles the program name to include all args, this causes a
// ENAMETOOLONG error when starting ros. Workaround is to replace the first
// occurence of ' -' with the null terminator. see:
// https://unix.stackexchange.com/questions/432419/unexpected-non-null-encoding-of-proc-pid-cmdline
#if defined(__linux__) && defined(__GLIBC__)
if (IsRunningInElectronRenderer()) {
if (IsRunningInElectronRenderer(env)) {
auto prog_name = program_invocation_name;
auto end = strstr(prog_name, " -");
assert(end);
prog_name[end - prog_name] = 0;
}
#endif

v8::Local<v8::Context> context = exports->GetIsolate()->GetCurrentContext();
napi_value context;
napi_get_named_property(env, exports, "context", &context);

for (uint32_t i = 0; i < rclnodejs::binding_methods.size(); i++) {
Nan::Set(
exports, Nan::New(rclnodejs::binding_methods[i].name).ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(rclnodejs::binding_methods[i].function)
->GetFunction(context)
.ToLocalChecked());
napi_value func;
napi_create_function(env, NULL, 0, rclnodejs::binding_methods[i].function, NULL, &func);
napi_set_named_property(env, exports, rclnodejs::binding_methods[i].name, func);
}

for (uint32_t i = 0; i < rclnodejs::action_binding_methods.size(); i++) {
Nan::Set(
exports,
Nan::New(rclnodejs::action_binding_methods[i].name).ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(
rclnodejs::action_binding_methods[i].function)
->GetFunction(context)
.ToLocalChecked());
napi_value func;
napi_create_function(env, NULL, 0, rclnodejs::action_binding_methods[i].function, NULL, &func);
napi_set_named_property(env, exports, rclnodejs::action_binding_methods[i].name, func);
}

for (uint32_t i = 0; i < rclnodejs::lifecycle_binding_methods.size(); i++) {
Nan::Set(
exports,
Nan::New(rclnodejs::lifecycle_binding_methods[i].name).ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(
rclnodejs::lifecycle_binding_methods[i].function)
->GetFunction(context)
.ToLocalChecked());
napi_value func;
napi_create_function(env, NULL, 0, rclnodejs::lifecycle_binding_methods[i].function, NULL, &func);
napi_set_named_property(env, exports, rclnodejs::lifecycle_binding_methods[i].name, func);
}

rclnodejs::ShadowNode::Init(exports);
Expand All @@ -89,6 +83,8 @@ void InitModule(v8::Local<v8::Object> exports) {
RCUTILS_LOG_SEVERITY_DEBUG);
RCUTILS_UNUSED(result);
#endif

return exports;
}

NODE_MODULE(rclnodejs, InitModule);
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
83 changes: 34 additions & 49 deletions src/rcl_action_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,16 @@ NAN_METHOD(ActionCreateClient) {
}
}

NAN_METHOD(ActionCreateServer) {
v8::Local<v8::Context> currentContent = Nan::GetCurrentContext();
RclHandle* node_handle = RclHandle::Unwrap<RclHandle>(
Nan::To<v8::Object>(info[0]).ToLocalChecked());
napi_value ActionCreateServer(napi_env env, napi_callback_info info) {
Napi::Env env = info.Env();
RclHandle* node_handle = RclHandle::Unwrap<RclHandle>(info[0].As<Napi::Object>());
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
RclHandle* clock_handle = RclHandle::Unwrap<RclHandle>(
Nan::To<v8::Object>(info[1]).ToLocalChecked());
RclHandle* clock_handle = RclHandle::Unwrap<RclHandle>(info[1].As<Napi::Object>());
rcl_clock_t* clock = reinterpret_cast<rcl_clock_t*>(clock_handle->ptr());
std::string action_name(
*Nan::Utf8String(info[2]->ToString(currentContent).ToLocalChecked()));
std::string interface_name(
*Nan::Utf8String(info[3]->ToString(currentContent).ToLocalChecked()));
std::string package_name(
*Nan::Utf8String(info[4]->ToString(currentContent).ToLocalChecked()));
int64_t result_timeout = info[10]->IntegerValue(currentContent).FromJust();
std::string action_name = info[2].As<Napi::String>().Utf8Value();
std::string interface_name = info[3].As<Napi::String>().Utf8Value();
std::string package_name = info[4].As<Napi::String>().Utf8Value();
int64_t result_timeout = info[10].As<Napi::Number>().Int64Value();

const rosidl_action_type_support_t* ts =
GetActionTypeSupport(package_name, interface_name);
Expand Down Expand Up @@ -160,29 +155,24 @@ NAN_METHOD(ActionCreateServer) {
THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK, ret, rcl_get_error_string().str);
});

info.GetReturnValue().Set(js_obj);
return js_obj;
} else {
Nan::ThrowError(GetErrorMessageAndClear().c_str());
Napi::Error::New(env, GetErrorMessageAndClear()).ThrowAsJavaScriptException();
return env.Undefined();
}
}

NAN_METHOD(ActionServerIsAvailable) {
RclHandle* node_handle = RclHandle::Unwrap<RclHandle>(
Nan::To<v8::Object>(info[0]).ToLocalChecked());
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
RclHandle* action_client_handle = RclHandle::Unwrap<RclHandle>(
Nan::To<v8::Object>(info[1]).ToLocalChecked());
rcl_action_client_t* action_client =
reinterpret_cast<rcl_action_client_t*>(action_client_handle->ptr());
Napi::Value ActionServerIsAvailable(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
RclHandle* action_server_handle = RclHandle::Unwrap<RclHandle>(info[0].As<Napi::Object>());
rcl_action_server_t* action_server =
reinterpret_cast<rcl_action_server_t*>(action_server_handle->ptr());
rcl_action_goal_info_t* buffer = reinterpret_cast<rcl_action_goal_info_t*>(
node::Buffer::Data(info[1].As<Napi::Object>()));

bool is_available;
THROW_ERROR_IF_NOT_EQUAL(
RCL_RET_OK,
rcl_action_server_is_available(node, action_client, &is_available),
rcl_get_error_string().str);
bool exists = rcl_action_server_goal_exists(action_server, buffer);

v8::Local<v8::Boolean> result = Nan::New<v8::Boolean>(is_available);
info.GetReturnValue().Set(result);
return Napi::Boolean::New(env, exists);
}

NAN_METHOD(ActionSendGoalRequest) {
Expand Down Expand Up @@ -687,35 +677,31 @@ NAN_METHOD(ActionServerGoalExists) {
info.GetReturnValue().Set(result);
}

NAN_METHOD(ActionExpireGoals) {
v8::Local<v8::Context> currentContent = Nan::GetCurrentContext();
RclHandle* action_server_handle = RclHandle::Unwrap<RclHandle>(
Nan::To<v8::Object>(info[0]).ToLocalChecked());
Napi::Value ActionExpireGoals(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
RclHandle* action_server_handle = RclHandle::Unwrap<RclHandle>(info[0].As<Napi::Object>());
rcl_action_server_t* action_server =
reinterpret_cast<rcl_action_server_t*>(action_server_handle->ptr());
int64_t max_num_goals = info[1]->IntegerValue(currentContent).FromJust();
int64_t max_num_goals = info[1].As<Napi::Number>().Int64Value();
rcl_action_goal_info_t* buffer = reinterpret_cast<rcl_action_goal_info_t*>(
node::Buffer::Data(Nan::To<v8::Object>(info[2]).ToLocalChecked()));
node::Buffer::Data(info[2].As<Napi::Object>()));

size_t num_expired;
THROW_ERROR_IF_NOT_EQUAL(rcl_action_expire_goals(action_server, buffer,
max_num_goals, &num_expired),
RCL_RET_OK, rcl_get_error_string().str);

v8::Local<v8::Integer> result =
Nan::New<v8::Integer>(static_cast<int32_t>(num_expired));
info.GetReturnValue().Set(result);
return Napi::Number::New(env, static_cast<int32_t>(num_expired));
}

NAN_METHOD(ActionGetClientNamesAndTypesByNode) {
v8::Local<v8::Context> currentContent = Nan::GetCurrentContext();
RclHandle* node_handle = RclHandle::Unwrap<RclHandle>(
Nan::To<v8::Object>(info[0]).ToLocalChecked());
Napi::Value ActionGetClientNamesAndTypesByNode(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
RclHandle* node_handle = RclHandle::Unwrap<RclHandle>(info[0].As<Napi::Object>());
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
std::string node_name =
*Nan::Utf8String(info[1]->ToString(currentContent).ToLocalChecked());
info[1].As<Napi::String>().Utf8Value();
std::string node_namespace =
*Nan::Utf8String(info[2]->ToString(currentContent).ToLocalChecked());
info[2].As<Napi::String>().Utf8Value();

rcl_names_and_types_t names_and_types =
rcl_get_zero_initialized_names_and_types();
Expand All @@ -726,15 +712,14 @@ NAN_METHOD(ActionGetClientNamesAndTypesByNode) {
node_namespace.c_str(), &names_and_types),
"Failed to action client names and types.");

v8::Local<v8::Array> result_list =
Nan::New<v8::Array>(names_and_types.names.size);
ExtractNamesAndTypes(names_and_types, &result_list);
Napi::Array result_list = Napi::Array::New(env, names_and_types.names.size);
ExtractNamesAndTypes(env, names_and_types, result_list);

THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK,
rcl_names_and_types_fini(&names_and_types),
"Failed to destroy names_and_types");

info.GetReturnValue().Set(result_list);
return result_list;
}

NAN_METHOD(ActionGetServerNamesAndTypesByNode) {
Expand Down
Loading
Loading