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.
- package/README.md +38 -110
- package/binding.gyp +2 -0
- package/index.js +1 -1
- package/lib/action/client.js +1 -1
- package/lib/action/graph.js +1 -1
- package/lib/action/server.js +1 -1
- package/lib/action/server_goal_handle.js +1 -1
- package/lib/client.js +1 -1
- package/lib/clock.js +1 -1
- package/lib/context.js +1 -1
- package/lib/duration.js +1 -1
- package/lib/event_handler.js +1 -1
- package/lib/guard_condition.js +1 -1
- package/lib/interface_loader.js +97 -1
- package/lib/lifecycle.js +1 -1
- package/lib/lifecycle_publisher.js +1 -1
- package/lib/logging.js +1 -1
- package/lib/native_loader.js +173 -0
- package/lib/node.js +1 -1
- package/lib/publisher.js +1 -1
- package/lib/serialization.js +1 -1
- package/lib/service.js +1 -1
- package/lib/subscription.js +1 -1
- package/lib/time.js +1 -1
- package/lib/time_source.js +1 -1
- package/lib/timer.js +1 -1
- package/lib/type_description_service.js +1 -1
- package/lib/utils.js +37 -0
- package/lib/validator.js +1 -1
- package/package.json +13 -12
- package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
- package/rosidl_gen/deallocator.js +1 -1
- package/rosidl_gen/generate_worker.js +63 -0
- package/rosidl_gen/generator.json +1 -1
- package/rosidl_gen/index.js +31 -0
- package/rosidl_gen/primitive_types.js +2 -2
- package/rosidl_gen/templates/message.dot +2 -2
- package/scripts/install.js +113 -0
- package/scripts/tag_prebuilds.js +70 -0
- package/src/addon.cpp +3 -0
- package/third_party/ref-napi/index.js +15 -0
- package/third_party/ref-napi/lib/ref.js +1741 -0
- package/third_party/ref-napi/src/ref_napi_bindings.cpp +736 -0
- package/third_party/ref-napi/src/ref_napi_bindings.h +26 -0
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
# rclnodejs [](https://github.com/RobotWebTools/rclnodejs/actions/workflows/linux-x64-push-test.yml?query=branch%3Adevelop)[](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
|
-
##
|
|
18
|
-
|
|
19
|
-
**Node.js**
|
|
20
|
-
|
|
21
|
-
- [Node.js](https://nodejs.org/en/) version >= 16.13.0.
|
|
19
|
+
## Installation
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
### Prerequisites
|
|
24
22
|
|
|
25
|
-
-
|
|
26
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
const rclnodejs = require('../index.js');
|
|
34
|
+
### Prebuilt Binaries
|
|
82
35
|
|
|
83
|
-
|
|
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
|
-
|
|
88
|
-
console.log(`Received message: ${typeof msg}`, msg);
|
|
89
|
-
});
|
|
38
|
+
**Supported Platforms:**
|
|
90
39
|
|
|
91
|
-
|
|
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
|
-
|
|
45
|
+
**Force Building from Source:**
|
|
96
46
|
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
142
|
-
|
|
143
|
-
}
|
|
71
|
+
"target": "es2020",
|
|
72
|
+
},
|
|
144
73
|
}
|
|
145
74
|
```
|
|
146
75
|
|
|
147
|
-
|
|
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
|
-
|
|
88
|
+
See [TypeScript demos](https://github.com/RobotWebTools/rclnodejs/tree/develop/ts_demo) for more examples.
|
|
160
89
|
|
|
161
|
-
|
|
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
|
-
|
|
92
|
+
## Electron-based Visualization
|
|
168
93
|
|
|
169
|
-
|
|
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
|
-
|
|
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. |  |
|
|
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. |  |
|
|
172
100
|
|
|
173
|
-
|
|
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('
|
|
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');
|
package/lib/action/client.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const rclnodejs = require('
|
|
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');
|
package/lib/action/graph.js
CHANGED
package/lib/action/server.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const rclnodejs = require('
|
|
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('
|
|
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('
|
|
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
package/lib/context.js
CHANGED
package/lib/duration.js
CHANGED
package/lib/event_handler.js
CHANGED
package/lib/guard_condition.js
CHANGED
package/lib/interface_loader.js
CHANGED
|
@@ -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('
|
|
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');
|
package/lib/logging.js
CHANGED
|
@@ -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