rclnodejs 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +38 -110
  2. package/binding.gyp +2 -0
  3. package/index.js +1 -1
  4. package/lib/action/client.js +1 -1
  5. package/lib/action/graph.js +1 -1
  6. package/lib/action/server.js +1 -1
  7. package/lib/action/server_goal_handle.js +1 -1
  8. package/lib/client.js +1 -1
  9. package/lib/clock.js +1 -1
  10. package/lib/context.js +1 -1
  11. package/lib/duration.js +1 -1
  12. package/lib/event_handler.js +1 -1
  13. package/lib/guard_condition.js +1 -1
  14. package/lib/interface_loader.js +97 -1
  15. package/lib/lifecycle.js +1 -1
  16. package/lib/lifecycle_publisher.js +1 -1
  17. package/lib/logging.js +1 -1
  18. package/lib/native_loader.js +173 -0
  19. package/lib/node.js +1 -1
  20. package/lib/publisher.js +1 -1
  21. package/lib/serialization.js +1 -1
  22. package/lib/service.js +1 -1
  23. package/lib/subscription.js +1 -1
  24. package/lib/time.js +1 -1
  25. package/lib/time_source.js +1 -1
  26. package/lib/timer.js +1 -1
  27. package/lib/type_description_service.js +1 -1
  28. package/lib/utils.js +37 -0
  29. package/lib/validator.js +1 -1
  30. package/package.json +13 -12
  31. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  32. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  33. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  34. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  35. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  36. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  37. package/rosidl_gen/deallocator.js +1 -1
  38. package/rosidl_gen/generate_worker.js +63 -0
  39. package/rosidl_gen/generator.json +1 -1
  40. package/rosidl_gen/index.js +31 -0
  41. package/rosidl_gen/primitive_types.js +2 -2
  42. package/rosidl_gen/templates/message.dot +2 -2
  43. package/scripts/install.js +113 -0
  44. package/scripts/tag_prebuilds.js +70 -0
  45. package/src/addon.cpp +3 -0
  46. package/third_party/ref-napi/index.js +15 -0
  47. package/third_party/ref-napi/lib/ref.js +1741 -0
  48. package/third_party/ref-napi/src/ref_napi_bindings.cpp +736 -0
  49. package/third_party/ref-napi/src/ref_napi_bindings.h +26 -0
package/README.md CHANGED
@@ -1,7 +1,9 @@
1
- # rclnodejs [![Linux](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-x64-build-and-test.yml/badge.svg?branch=develop)](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-x64-build-and-test.yml?query=branch%3Adevelop)[![Linux](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-arm64-build-and-test.yml/badge.svg?branch=develop)](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-arm64-build-and-test.yml?query=branch%3Adevelop)
1
+ # rclnodejs [![Linux](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-x64-push-test.yml/badge.svg?branch=develop)](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-x64-push-test.yml?query=branch%3Adevelop)[![Linux](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-arm64-push-test.yml/badge.svg?branch=develop)](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-arm64-push-test.yml?query=branch%3Adevelop)
2
2
 
3
3
  `rclnodejs` is a Node.js client for the Robot Operating System (ROS 2). It provides a simple and easy JavaScript API for ROS 2 programming. TypeScript declarations are included to support use of rclnodejs in TypeScript projects.
4
4
 
5
+ \* rclnodejs development and maintenance is limited to all active ROS 2 LTS releases and the Rolling development branch
6
+
5
7
  Here's an example for how to create a ROS 2 node that publishes a string message in a few lines of JavaScript.
6
8
 
7
9
  ```JavaScript
@@ -14,137 +16,64 @@ rclnodejs.init().then(() => {
14
16
  });
15
17
  ```
16
18
 
17
- ## Prerequisites
18
-
19
- **Node.js**
20
-
21
- - [Node.js](https://nodejs.org/en/) version >= 16.13.0.
19
+ ## Installation
22
20
 
23
- **ROS 2 SDK**
21
+ ### Prerequisites
24
22
 
25
- - See the ROS 2 SDK [Installation Guide](https://docs.ros.org/en/kilted/Installation.html) for details.
26
- - **DON'T FORGET TO [SOURCE THE ROS 2 STARTUP FILES](https://docs.ros.org/en/kilted/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.htmls)**
23
+ - [Node.js](https://nodejs.org/en/) version >= 16.13.0
24
+ - [ROS 2 SDK](https://docs.ros.org/en/jazzy/Installation.html) - **Don't forget to [source the setup file](https://docs.ros.org/en/jazzy/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html#source-the-setup-files)**
27
25
 
28
- ## Install rclnodejs
29
-
30
- Install the rclnodejs version that is compatible with your installed version of ROS 2 (see table below).
31
-
32
- Run the following command for the most current version of rclnodejs
26
+ ### Install rclnodejs
33
27
 
34
28
  ```bash
35
29
  npm i rclnodejs
36
30
  ```
37
31
 
38
- or to install a specific version of rclnodejs use
39
-
40
- ```bash
41
- npm i rclnodejs@x.y.z
42
- ```
43
-
44
- #### RCLNODEJS - ROS 2 Version Compatibility
45
-
46
- | RCLNODEJS Version | Compatible ROS 2 LTS |
47
- | :----------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
48
- | latest version (currently [v1.5.0](https://github.com/RobotWebTools/rclnodejs/tree/1.5.0)) | [Kilted](https://github.com/RobotWebTools/rclnodejs/tree/kilted)<br>[Jazzy](https://github.com/RobotWebTools/rclnodejs/tree/jazzy)<br>[Humble](https://github.com/RobotWebTools/rclnodejs/tree/humble-hawksbill) |
49
-
50
- ## Documentation
51
-
52
- API [documentation](https://robotwebtools.github.io/rclnodejs/docs/index.html) is available online.
53
-
54
- ## JavaScript Examples
55
-
56
- The source for the following examples and many others can be found [here](https://github.com/RobotWebTools/rclnodejs/tree/develop/example).
57
-
58
- Use complex message
59
-
60
- ```JavaScript
61
- const publisher = node.createPublisher('sensor_msgs/msg/JointState', 'topic');
62
- publisher.publish({
63
- header: {
64
- stamp: {
65
- sec: 123456,
66
- nanosec: 789,
67
- },
68
- frame_id: 'main frame',
69
- },
70
- name: ['Tom', 'Jerry'],
71
- position: [1, 2],
72
- velocity: [2, 3],
73
- effort: [4, 5, 6],
74
- });
75
-
76
- ```
77
-
78
- Create a subscription
32
+ - **Note:** to install rclnodejs from GitHub: add `"rclnodejs":"RobotWebTools/rclnodejs#<branch>"` to your `package.json` dependency section.
79
33
 
80
- ```JavaScript
81
- const rclnodejs = require('../index.js');
34
+ ### Prebuilt Binaries
82
35
 
83
- // Create a ROS node and then print out the string message received from publishers
84
- rclnodejs.init().then(() => {
85
- const node = rclnodejs.createNode('subscription_example_node');
36
+ rclnodejs ships with prebuilt native binaries for common Linux configurations since `v1.5.2`, eliminating the need for compilation during installation. This significantly speeds up installation and reduces dependencies.
86
37
 
87
- node.createSubscription('std_msgs/msg/String', 'topic', (msg) => {
88
- console.log(`Received message: ${typeof msg}`, msg);
89
- });
38
+ **Supported Platforms:**
90
39
 
91
- rclnodejs.spin(node);
92
- });
93
- ```
40
+ - **Ubuntu 22.04 (Jammy)** - ROS 2 Humble
41
+ - **Ubuntu 24.04 (Noble)** - ROS 2 Jazzy, Kilted
42
+ - **Architectures:** x64, arm64
43
+ - **Node.js:** >= 16.20.2 (N-API compatible)
94
44
 
95
- Create a service
45
+ **Force Building from Source:**
96
46
 
97
- ```JavaScript
98
- node.createService('example_interfaces/srv/AddTwoInts', 'add_two_ints', (request, response) => {
99
- console.log(`Incoming request: ${typeof request}`, request);
100
- let result = response.template;
101
- result.sum = request.a + request.b;
102
- console.log(`Sending response: ${typeof result}`, result, '\n--');
103
- response.send(result);
104
- });
47
+ If you need to build from source even when a prebuilt binary is available, set the environment variable:
105
48
 
49
+ ```bash
50
+ export RCLNODEJS_FORCE_BUILD=1
51
+ npm install rclnodejs
106
52
  ```
107
53
 
108
- Send a request in a client
109
-
110
- ```JavaScript
111
- const client = node.createClient('example_interfaces/srv/AddTwoInts', 'add_two_ints');
112
- const request = {
113
- a: Math.floor(Math.random() * 100),
114
- b: Math.floor(Math.random() * 100),
115
- };
54
+ ## Documentation
116
55
 
117
- console.log(`Sending: ${typeof request}`, request);
118
- client.sendRequest(request, (response) => {
119
- console.log(`Result: ${typeof response}`, response);
120
- });
56
+ API [documentation](https://robotwebtools.github.io/rclnodejs/docs/index.html) is available online.
121
57
 
122
- ```
58
+ ## JavaScript Examples
123
59
 
124
- Check out more [examples](https://github.com/RobotWebTools/rclnodejs/tree/develop/example).
60
+ Try the [examples](https://github.com/RobotWebTools/rclnodejs/tree/develop/example) to get started.
125
61
 
126
62
  ## Using rclnodejs with TypeScript
127
63
 
128
- In your node project install the rclnodejs package as described above. You will also need the TypeScript compiler and node typings installed.
129
-
130
- ```
131
- npm install typescript @types/node -D
132
- ```
133
-
134
- In your project's tsconfig.json file include the following compiler options:
64
+ TypeScript declaration files are included in the `types/` folder. Configure your `tsconfig.json`:
135
65
 
136
66
  ```jsonc
137
67
  {
138
68
  "compilerOptions": {
139
69
  "module": "commonjs",
140
70
  "moduleResolution": "node",
141
- "target": "es6",
142
- ...
143
- }
71
+ "target": "es2020",
72
+ },
144
73
  }
145
74
  ```
146
75
 
147
- Here's an earlier JavaScript example reimplemented in TypeScript.
76
+ TypeScript example:
148
77
 
149
78
  ```typescript
150
79
  import * as rclnodejs from 'rclnodejs';
@@ -156,21 +85,20 @@ rclnodejs.init().then(() => {
156
85
  });
157
86
  ```
158
87
 
159
- Type-aliases for the ROS2 messages can be found in the `types/interfaces.d.ts` file. To use a message type-alias follow the naming pattern <pkg_name>.[msg|srv].<type>, e.g., sensor_msgs.msg.LaserScan or the std_msgs.msg.String as shown below.
88
+ See [TypeScript demos](https://github.com/RobotWebTools/rclnodejs/tree/develop/ts_demo) for more examples.
160
89
 
161
- ```typescript
162
- const msg: rclnodejs.std_msgs.msg.String = {
163
- data: 'hello ROS2 from rclnodejs',
164
- };
165
- ```
90
+ **Note** that the interface.d.ts file is updated each time the generate_messages.js script is run.
166
91
 
167
- Check out more TypeScript [demos](https://github.com/RobotWebTools/rclnodejs/tree/develop/ts_demo).
92
+ ## Electron-based Visualization
168
93
 
169
- **Note** that the interface.d.ts file is updated each time the generate_messages.js script is run.
94
+ Create rich, interactive desktop applications using Electron and web technologies like Three.js. Build 3D visualizations, monitoring dashboards, and control interfaces that run on Windows, macOS, and Linux.
170
95
 
171
- ## Using rclnodejs with Electron
96
+ | Demo | Description | Screenshot |
97
+ | :-----------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: |
98
+ | **🐢 [turtle_tf2](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo/turtle_tf2)** | Real-time coordinate frame visualization with turtle control. Features TF2 transforms, keyboard control, and dynamic frame updates. | ![turtle_tf2](https://github.com/RobotWebTools/rclnodejs/blob/develop/electron_demo/turtle_tf2/turtle-tf2-demo.png?raw=true) |
99
+ | **🦾 [manipulator](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo/manipulator)** | Interactive two-joint robotic arm simulation. Features 3D joint visualization, manual/automatic control, and visual movement markers. | ![manipulator](https://github.com/RobotWebTools/rclnodejs/blob/develop/electron_demo/manipulator/manipulator-demo.png?raw=true) |
172
100
 
173
- Check out [demos](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo).
101
+ Explore more examples in [electron_demo](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo).
174
102
 
175
103
  ## License
176
104
 
package/binding.gyp CHANGED
@@ -42,9 +42,11 @@
42
42
  './src/rcl_timer_bindings.cpp',
43
43
  './src/rcl_utilities.cpp',
44
44
  './src/shadow_node.cpp',
45
+ './third_party/ref-napi/src/ref_napi_bindings.cpp',
45
46
  ],
46
47
  'include_dirs': [
47
48
  '.',
49
+ './third_party/ref-napi/src',
48
50
  '<(ros_include_root)',
49
51
  "<!@(node -p \"require('node-addon-api').include\")",
50
52
  ],
package/index.js CHANGED
@@ -37,7 +37,7 @@ const {
37
37
  } = require('./lib/parameter.js');
38
38
  const path = require('path');
39
39
  const QoS = require('./lib/qos.js');
40
- const rclnodejs = require('bindings')('rclnodejs');
40
+ const rclnodejs = require('./lib/native_loader.js');
41
41
  const tsdGenerator = require('./rostsd_gen/index.js');
42
42
  const validator = require('./lib/validator.js');
43
43
  const Time = require('./lib/time.js');
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('../native_loader.js');
18
18
  const ActionInterfaces = require('./interfaces.js');
19
19
  const ActionUuid = require('./uuid.js');
20
20
  const ClientGoalHandle = require('./client_goal_handle.js');
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('../native_loader.js');
18
18
 
19
19
  /**
20
20
  * Get a list of action names and types for action clients associated with a node.
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('../native_loader.js');
18
18
  const ActionInterfaces = require('./interfaces.js');
19
19
  const ActionUuid = require('./uuid.js');
20
20
  const DistroUtils = require('../distro.js');
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('../native_loader.js');
18
18
  const ActionInterfaces = require('./interfaces.js');
19
19
  const Deferred = require('./deferred.js');
20
20
  const { GoalEvent } = require('./response.js');
package/lib/client.js CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('./native_loader.js');
18
18
  const DistroUtils = require('./distro.js');
19
19
  const Entity = require('./entity.js');
20
20
  const debug = require('debug')('rclnodejs:client');
package/lib/clock.js CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('./native_loader.js');
18
18
  const Time = require('./time.js');
19
19
  const ClockType = require('./clock_type.js');
20
20
 
package/lib/context.js CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('./native_loader.js');
18
18
 
19
19
  let defaultContext = null;
20
20
 
package/lib/duration.js CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('./native_loader.js');
18
18
  const S_TO_NS = 10n ** 9n;
19
19
 
20
20
  /**
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('./native_loader.js');
18
18
  const DistroUtils = require('./distro.js');
19
19
  const Entity = require('./entity.js');
20
20
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  'use strict';
14
14
 
15
- const rclnodejs = require('bindings')('rclnodejs');
15
+ const rclnodejs = require('./native_loader.js');
16
16
  const Entity = require('./entity.js');
17
17
  const Context = require('./context.js');
18
18
 
@@ -90,6 +90,95 @@ let interfaceLoader = {
90
90
  return pkg;
91
91
  },
92
92
 
93
+ _isRos2InstallationPath(pkgPath) {
94
+ // Use "which ros2" to dynamically find the ROS2 installation root
95
+ try {
96
+ const whichResult = require('child_process').spawnSync(
97
+ 'which',
98
+ ['ros2'],
99
+ {
100
+ encoding: 'utf8',
101
+ timeout: 5000,
102
+ }
103
+ );
104
+
105
+ if (whichResult.status === 0 && whichResult.stdout) {
106
+ const ros2BinPath = whichResult.stdout.trim();
107
+ // Get the ROS2 installation root (typically /opt/ros/<distro> or similar)
108
+ const ros2Root = path.dirname(path.dirname(ros2BinPath));
109
+
110
+ return pkgPath.includes(ros2Root);
111
+ }
112
+ } catch (err) {
113
+ console.error('Error running which ros2:', err.message);
114
+ // If "which ros2" fails, fall back to hardcoded check
115
+ return pkgPath.includes('ros2-linux');
116
+ }
117
+
118
+ return false;
119
+ },
120
+
121
+ _searchAndGenerateInterface(packageName, type, messageName, filePath) {
122
+ // Check if it's a valid package
123
+ for (const pkgPath of generator.getInstalledPackagePaths()) {
124
+ // We are going to ignore the path where ROS2 is installed.
125
+ if (this._isRos2InstallationPath(pkgPath)) {
126
+ continue;
127
+ }
128
+
129
+ // Recursively search for files named messageName.* under pkgPath/
130
+ if (fs.existsSync(pkgPath)) {
131
+ // Recursive function to search for files
132
+ function searchForFile(dir) {
133
+ try {
134
+ const items = fs.readdirSync(dir, { withFileTypes: true });
135
+ for (const item of items) {
136
+ const fullPath = path.join(dir, item.name);
137
+
138
+ if (item.isFile()) {
139
+ const baseName = path.parse(item.name).name;
140
+ // Check if the base filename matches messageName
141
+ if (baseName === messageName) {
142
+ return fullPath;
143
+ }
144
+ } else if (item.isDirectory()) {
145
+ // Recursively search subdirectories
146
+ const result = searchForFile(fullPath);
147
+ if (result) {
148
+ return result;
149
+ }
150
+ }
151
+ }
152
+ } catch (err) {
153
+ // Skip directories we can't read
154
+ console.error('Error reading directory:', dir, err.message);
155
+ }
156
+ return null;
157
+ }
158
+
159
+ const foundFilePath = searchForFile(
160
+ path.join(pkgPath, 'share', packageName)
161
+ );
162
+
163
+ if (foundFilePath && foundFilePath.length > 0) {
164
+ // Use worker thread to generate interfaces synchronously
165
+ try {
166
+ generator.generateInPathSyncWorker(pkgPath);
167
+ // Now try to load the interface again from the generated files
168
+ if (fs.existsSync(filePath)) {
169
+ return require(filePath);
170
+ }
171
+ } catch (err) {
172
+ console.error('Error in interface generation:', err);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ throw new Error(
178
+ `The message required does not exist: ${packageName}, ${type}, ${messageName} at ${generator.generatedRoot}`
179
+ );
180
+ },
181
+
93
182
  loadInterface(packageName, type, messageName) {
94
183
  if (arguments.length === 1) {
95
184
  const type = arguments[0];
@@ -100,7 +189,6 @@ let interfaceLoader = {
100
189
  }
101
190
  throw new Error(`The message required does not exist: ${type}`);
102
191
  }
103
-
104
192
  if (packageName && type && messageName) {
105
193
  let filePath = path.join(
106
194
  generator.generatedRoot,
@@ -110,8 +198,16 @@ let interfaceLoader = {
110
198
 
111
199
  if (fs.existsSync(filePath)) {
112
200
  return require(filePath);
201
+ } else {
202
+ return this._searchAndGenerateInterface(
203
+ packageName,
204
+ type,
205
+ messageName,
206
+ filePath
207
+ );
113
208
  }
114
209
  }
210
+ // We cannot parse `packageName`, `type` and `messageName` from the string passed.
115
211
  throw new Error(
116
212
  `The message required does not exist: ${packageName}, ${type}, ${messageName} at ${generator.generatedRoot}`
117
213
  );
package/lib/lifecycle.js CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('./native_loader.js');
18
18
  const LifecyclePublisher = require('./lifecycle_publisher.js');
19
19
  const loader = require('./interface_loader.js');
20
20
  const Context = require('./context.js');
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('./native_loader.js');
18
18
  const Logging = require('./logging.js');
19
19
  const Publisher = require('./publisher.js');
20
20
 
package/lib/logging.js CHANGED
@@ -15,7 +15,7 @@
15
15
  'use strict';
16
16
 
17
17
  const path = require('path');
18
- const rclnodejs = require('bindings')('rclnodejs');
18
+ const rclnodejs = require('./native_loader.js');
19
19
 
20
20
  /**
21
21
  * Enum for LoggingSeverity
@@ -0,0 +1,173 @@
1
+ // Copyright (c) 2025, The Robot Web Tools Contributors
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ 'use strict';
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { execSync } = require('child_process');
20
+ const bindings = require('bindings');
21
+ const debug = require('debug')('rclnodejs');
22
+ const { detectUbuntuCodename } = require('./utils');
23
+
24
+ let nativeModule = null;
25
+
26
+ // Simplified loader: only use prebuilt binaries with exact Ubuntu/ROS2/arch match
27
+ // Note: Prebuilt binaries are only supported on Linux (Ubuntu) platform
28
+ function customFallbackLoader() {
29
+ // Prebuilt binaries are only for Linux platform
30
+ if (process.platform !== 'linux') {
31
+ debug('Prebuilt binaries are only supported on Linux platform');
32
+ return null;
33
+ }
34
+
35
+ const rosDistro = process.env.ROS_DISTRO;
36
+ const arch = process.arch;
37
+ const ubuntuCodename = detectUbuntuCodename();
38
+
39
+ // Require all three components for exact match
40
+ if (!rosDistro || !ubuntuCodename || !arch) {
41
+ debug(
42
+ `Missing environment info - ROS: ${rosDistro}, Ubuntu: ${ubuntuCodename}, Arch: ${arch}`
43
+ );
44
+ return null;
45
+ }
46
+
47
+ const prebuildDir = path.join(
48
+ __dirname,
49
+ '..',
50
+ 'prebuilds',
51
+ `${process.platform}-${arch}`
52
+ );
53
+
54
+ if (!fs.existsSync(prebuildDir)) {
55
+ debug('No prebuilds directory found');
56
+ return null;
57
+ }
58
+
59
+ try {
60
+ // Look for exact match binary: {ros_distro}-{ubuntu_codename}-{arch}-rclnodejs.node
61
+ const exactMatchFilename = `${rosDistro}-${ubuntuCodename}-${arch}-rclnodejs.node`;
62
+ const exactMatchPath = path.join(prebuildDir, exactMatchFilename);
63
+
64
+ if (fs.existsSync(exactMatchPath)) {
65
+ debug(`Found exact match binary: ${exactMatchFilename}`);
66
+ return require(exactMatchPath);
67
+ }
68
+
69
+ debug(`No exact match found for: ${exactMatchFilename}`);
70
+ return null;
71
+ } catch (e) {
72
+ debug('Error in simplified prebuilt loader:', e.message);
73
+ }
74
+
75
+ return null;
76
+ }
77
+
78
+ // Simplified prebuilt binary loader: exact match or build from source
79
+ function loadNativeAddon() {
80
+ if (nativeModule) {
81
+ return nativeModule;
82
+ }
83
+
84
+ // Environment variable to force building from source
85
+ if (process.env.RCLNODEJS_FORCE_BUILD === '1') {
86
+ debug('Forcing build from source (RCLNODEJS_FORCE_BUILD=1)');
87
+
88
+ // Trigger actual compilation
89
+ try {
90
+ debug('Running forced node-gyp rebuild...');
91
+ execSync('npm run rebuild', {
92
+ stdio: 'inherit',
93
+ cwd: path.join(__dirname, '..'),
94
+ timeout: 300000, // 5 minute timeout
95
+ });
96
+
97
+ // Load the newly built binary
98
+ nativeModule = bindings('rclnodejs');
99
+ debug('Successfully force compiled and loaded from source');
100
+ return nativeModule;
101
+ } catch (compileError) {
102
+ debug('Forced compilation failed:', compileError.message);
103
+ throw new Error(
104
+ `Failed to force build rclnodejs from source: ${compileError.message}`
105
+ );
106
+ }
107
+ }
108
+
109
+ const rosDistro = process.env.ROS_DISTRO;
110
+ const ubuntuCodename = detectUbuntuCodename();
111
+
112
+ debug(
113
+ `Platform: ${process.platform}, Arch: ${process.arch}, Ubuntu: ${ubuntuCodename || 'unknown'}, ROS: ${rosDistro || 'unknown'}`
114
+ );
115
+
116
+ // Prebuilt binaries are only supported on Linux (Ubuntu)
117
+ if (process.platform === 'linux') {
118
+ // Try exact match prebuilt binary first
119
+ try {
120
+ const prebuiltModule = customFallbackLoader();
121
+ if (prebuiltModule) {
122
+ nativeModule = prebuiltModule;
123
+ return nativeModule;
124
+ }
125
+ } catch (e) {
126
+ debug('Exact match prebuilt loading failed:', e.message);
127
+ }
128
+
129
+ debug(
130
+ 'No exact match prebuilt binary found, falling back to build from source'
131
+ );
132
+ } else {
133
+ debug(
134
+ `Platform ${process.platform} does not support prebuilt binaries, will try existing build or compile from source`
135
+ );
136
+ }
137
+
138
+ try {
139
+ // Try to find existing built binary first (works on all platforms)
140
+ // The 'bindings' module will search standard locations like:
141
+ // - build/Release/rclnodejs.node
142
+ // - build/Debug/rclnodejs.node
143
+ // - compiled/{node_version}/{platform}/{arch}/rclnodejs.node
144
+ // etc.
145
+ nativeModule = bindings('rclnodejs');
146
+ debug('Found and loaded existing built binary');
147
+ return nativeModule;
148
+ } catch {
149
+ debug('No existing built binary found, triggering compilation...');
150
+
151
+ // Trigger actual compilation
152
+ try {
153
+ debug('Running node-gyp rebuild...');
154
+ execSync('npm run rebuild', {
155
+ stdio: 'inherit',
156
+ cwd: path.join(__dirname, '..'),
157
+ timeout: 300000, // 5 minute timeout
158
+ });
159
+
160
+ // Try to load the newly built binary
161
+ nativeModule = bindings('rclnodejs');
162
+ debug('Successfully compiled and loaded from source');
163
+ return nativeModule;
164
+ } catch (compileError) {
165
+ debug('Compilation failed:', compileError.message);
166
+ throw new Error(
167
+ `Failed to build rclnodejs from source: ${compileError.message}`
168
+ );
169
+ }
170
+ }
171
+ }
172
+
173
+ module.exports = loadNativeAddon();
package/lib/node.js CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const rclnodejs = require('bindings')('rclnodejs');
17
+ const rclnodejs = require('./native_loader.js');
18
18
 
19
19
  const ActionInterfaces = require('./action/interfaces.js');
20
20
  const Client = require('./client.js');