rclnodejs 1.9.0-alpha.0 → 2.0.0-beta.0

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 (108) hide show
  1. package/.prettierignore +4 -0
  2. package/README.md +2 -2
  3. package/binding.gyp +6 -5
  4. package/index.js +10 -0
  5. package/lib/action/client.js +5 -4
  6. package/lib/action/server_goal_handle.js +26 -1
  7. package/lib/action/uuid.js +1 -1
  8. package/lib/client.js +0 -45
  9. package/lib/distro.js +11 -4
  10. package/lib/interface_loader.js +1 -1
  11. package/lib/message_introspector.js +1 -29
  12. package/lib/message_serialization.js +2 -2
  13. package/lib/native_loader.js +21 -9
  14. package/lib/node.js +209 -12
  15. package/lib/parameter_event_handler.js +98 -0
  16. package/lib/prebuilds.js +47 -0
  17. package/lib/qos_overriding_options.js +358 -0
  18. package/lib/rmw.js +6 -1
  19. package/lib/subscription.js +2 -2
  20. package/lib/timer.js +1 -1
  21. package/package.json +11 -6
  22. package/prebuilds/linux-arm64/humble-jammy-arm64-electron-rclnodejs.node +0 -0
  23. package/prebuilds/linux-arm64/humble-jammy-arm64-node-rclnodejs.node +0 -0
  24. package/prebuilds/linux-arm64/jazzy-noble-arm64-electron-rclnodejs.node +0 -0
  25. package/prebuilds/linux-arm64/jazzy-noble-arm64-node-rclnodejs.node +0 -0
  26. package/prebuilds/linux-arm64/kilted-noble-arm64-electron-rclnodejs.node +0 -0
  27. package/prebuilds/linux-arm64/kilted-noble-arm64-node-rclnodejs.node +0 -0
  28. package/prebuilds/linux-arm64/lyrical-resolute-arm64-electron-rclnodejs.node +0 -0
  29. package/prebuilds/linux-arm64/lyrical-resolute-arm64-node-rclnodejs.node +0 -0
  30. package/prebuilds/linux-x64/humble-jammy-x64-electron-rclnodejs.node +0 -0
  31. package/prebuilds/linux-x64/humble-jammy-x64-node-rclnodejs.node +0 -0
  32. package/prebuilds/linux-x64/jazzy-noble-x64-electron-rclnodejs.node +0 -0
  33. package/prebuilds/linux-x64/jazzy-noble-x64-node-rclnodejs.node +0 -0
  34. package/prebuilds/linux-x64/kilted-noble-x64-electron-rclnodejs.node +0 -0
  35. package/prebuilds/linux-x64/kilted-noble-x64-node-rclnodejs.node +0 -0
  36. package/prebuilds/linux-x64/lyrical-resolute-x64-electron-rclnodejs.node +0 -0
  37. package/prebuilds/linux-x64/lyrical-resolute-x64-node-rclnodejs.node +0 -0
  38. package/rosidl_gen/packages.js +4 -4
  39. package/rosidl_gen/templates/message-template.js +20 -6
  40. package/rosocket/README.md +152 -0
  41. package/rosocket/cli.js +168 -0
  42. package/rosocket/index.js +245 -0
  43. package/scripts/install.js +14 -3
  44. package/scripts/tag_prebuilds.js +26 -9
  45. package/src/rcl_action_client_bindings.cpp +4 -4
  46. package/src/rcl_graph_bindings.cpp +8 -8
  47. package/src/rcl_lifecycle_bindings.cpp +1 -1
  48. package/src/rcl_subscription_bindings.cpp +2 -2
  49. package/src/rcl_timer_bindings.cpp +21 -2
  50. package/src/rcl_utilities.cpp +4 -4
  51. package/src/rcl_utilities.h +2 -2
  52. package/types/distro.d.ts +15 -1
  53. package/types/node.d.ts +69 -5
  54. package/types/parameter_event_handler.d.ts +11 -0
  55. package/types/qos.d.ts +55 -0
  56. package/types/timer.d.ts +3 -2
  57. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  58. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  59. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  60. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  61. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  62. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  63. package/tools/jsdoc/Makefile +0 -5
  64. package/tools/jsdoc/README.md +0 -96
  65. package/tools/jsdoc/build-index.js +0 -610
  66. package/tools/jsdoc/publish.js +0 -854
  67. package/tools/jsdoc/regenerate-published-docs.js +0 -605
  68. package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.eot +0 -0
  69. package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.svg +0 -1830
  70. package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.woff +0 -0
  71. package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  72. package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
  73. package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  74. package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.eot +0 -0
  75. package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.svg +0 -1830
  76. package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.woff +0 -0
  77. package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.eot +0 -0
  78. package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.svg +0 -1831
  79. package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.woff +0 -0
  80. package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  81. package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
  82. package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  83. package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.eot +0 -0
  84. package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.svg +0 -1831
  85. package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.woff +0 -0
  86. package/tools/jsdoc/static/scripts/linenumber.js +0 -25
  87. package/tools/jsdoc/static/scripts/prettify/Apache-License-2.0.txt +0 -202
  88. package/tools/jsdoc/static/scripts/prettify/lang-css.js +0 -36
  89. package/tools/jsdoc/static/scripts/prettify/prettify.js +0 -738
  90. package/tools/jsdoc/static/styles/jsdoc-default.css +0 -1012
  91. package/tools/jsdoc/static/styles/prettify-jsdoc.css +0 -111
  92. package/tools/jsdoc/static/styles/prettify-tomorrow.css +0 -132
  93. package/tools/jsdoc/tmpl/augments.tmpl +0 -10
  94. package/tools/jsdoc/tmpl/container.tmpl +0 -193
  95. package/tools/jsdoc/tmpl/details.tmpl +0 -143
  96. package/tools/jsdoc/tmpl/example.tmpl +0 -2
  97. package/tools/jsdoc/tmpl/examples.tmpl +0 -13
  98. package/tools/jsdoc/tmpl/exceptions.tmpl +0 -17
  99. package/tools/jsdoc/tmpl/layout.tmpl +0 -83
  100. package/tools/jsdoc/tmpl/mainpage.tmpl +0 -163
  101. package/tools/jsdoc/tmpl/members.tmpl +0 -43
  102. package/tools/jsdoc/tmpl/method.tmpl +0 -124
  103. package/tools/jsdoc/tmpl/params.tmpl +0 -133
  104. package/tools/jsdoc/tmpl/properties.tmpl +0 -110
  105. package/tools/jsdoc/tmpl/returns.tmpl +0 -12
  106. package/tools/jsdoc/tmpl/source.tmpl +0 -8
  107. package/tools/jsdoc/tmpl/tutorial.tmpl +0 -19
  108. package/tools/jsdoc/tmpl/type.tmpl +0 -7
@@ -0,0 +1,4 @@
1
+ # Prettier pads Markdown table separators to match cell width, which bloats
2
+ # long-URL badge tables. Exclude root READMEs to keep them minimal.
3
+ README.md
4
+ scripts/npmjs-readme.md
package/README.md CHANGED
@@ -20,7 +20,7 @@ This example assumes your ROS 2 environment is already sourced.
20
20
 
21
21
  ### Prerequisites
22
22
 
23
- - [Node.js](https://nodejs.org/en/) version >= 16.13.0
23
+ - [Node.js](https://nodejs.org/en/) version >= 20.20.2
24
24
  - [ROS 2 SDK](https://docs.ros.org/en/jazzy/Installation.html)
25
25
 
26
26
  Before installing or running rclnodejs, source your ROS 2 environment:
@@ -52,7 +52,7 @@ rclnodejs ships with prebuilt native binaries for common Linux configurations si
52
52
  - **Ubuntu 22.04 (Jammy)** - ROS 2 Humble
53
53
  - **Ubuntu 24.04 (Noble)** - ROS 2 Jazzy, Kilted
54
54
  - **Architectures:** x64, arm64
55
- - **Node.js:** >= 16.20.2 (N-API compatible)
55
+ - **Node.js:** >= 20.20.2 (N-API compatible)
56
56
 
57
57
  Installations outside this prebuilt matrix automatically fall back to building from source.
58
58
 
package/binding.gyp CHANGED
@@ -12,7 +12,6 @@
12
12
  'runtime%': 'node',
13
13
  'ros_lib_dir': "<!(node -p \"require('./scripts/config.js').getROSLibPath()\")",
14
14
  'ros_include_root': "<!(node -p \"require('./scripts/config.js').getROSIncludeRootPath()\")",
15
- 'node_major_version': '<!(node -p \"process.versions.node.split(\'.\')[0]\")',
16
15
  },
17
16
  'targets': [
18
17
  {
@@ -85,14 +84,15 @@
85
84
  ],
86
85
  'conditions': [
87
86
  [
88
- 'node_major_version >= 23', {
87
+ # Post-Kilted ROS distros require C++20.
88
+ 'ros_version > 2505', {
89
89
  'cflags_cc': [
90
90
  '-std=c++20'
91
91
  ]
92
92
  }
93
93
  ],
94
94
  [
95
- 'node_major_version < 23', {
95
+ 'ros_version <= 2505', {
96
96
  'cflags_cc': [
97
97
  '-std=c++17'
98
98
  ]
@@ -108,14 +108,15 @@
108
108
  ],
109
109
  'conditions': [
110
110
  [
111
- 'node_major_version >= 23', {
111
+ # Post-Kilted ROS distros require C++20.
112
+ 'ros_version > 2505', {
112
113
  'cflags_cc': [
113
114
  '-std=c++20'
114
115
  ]
115
116
  }
116
117
  ],
117
118
  [
118
- 'node_major_version < 23', {
119
+ 'ros_version <= 2505', {
119
120
  'cflags_cc': [
120
121
  '-std=c++17'
121
122
  ]
package/index.js CHANGED
@@ -39,6 +39,10 @@ const {
39
39
  } = require('./lib/parameter.js');
40
40
  const path = require('path');
41
41
  const QoS = require('./lib/qos.js');
42
+ const {
43
+ QoSPolicyKind,
44
+ QoSOverridingOptions,
45
+ } = require('./lib/qos_overriding_options.js');
42
46
  const rclnodejs = require('./lib/native_loader.js');
43
47
  const tsdGenerator = require('./rostsd_gen/index.js');
44
48
  const validator = require('./lib/validator.js');
@@ -258,6 +262,12 @@ let rcl = {
258
262
  /** {@link QoS} class */
259
263
  QoS: QoS,
260
264
 
265
+ /** {@link QoSPolicyKind} enum */
266
+ QoSPolicyKind: QoSPolicyKind,
267
+
268
+ /** {@link QoSOverridingOptions} class */
269
+ QoSOverridingOptions: QoSOverridingOptions,
270
+
261
271
  /** {@link RMWUtils} */
262
272
  RMWUtils: RMWUtils,
263
273
 
@@ -94,12 +94,13 @@ class ActionClient extends Entity {
94
94
  };
95
95
 
96
96
  // Enable feedback subscription content filter optimization.
97
- // Only supported on ROS2 Rolling and only effective when the native
98
- // binding provides the required functions AND the RMW implementation
99
- // actually supports content filtering on the feedback subscription.
97
+ // Only supported on ROS2 Lyrical or newer and only effective when the
98
+ // native binding provides the required functions AND the RMW
99
+ // implementation actually supports content filtering on the feedback
100
+ // subscription.
100
101
  this._enableFeedbackMsgOptimization =
101
102
  this._options.enableFeedbackMsgOptimization === true &&
102
- DistroUtils.getDistroId() >= DistroUtils.DistroId.ROLLING &&
103
+ DistroUtils.getDistroId() >= DistroUtils.DistroId.LYRICAL &&
103
104
  typeof rclnodejs.actionConfigureFeedbackSubFilterAddGoalId === 'function';
104
105
 
105
106
  let type = this.typeClass.type();
@@ -82,11 +82,26 @@ class ServerGoalHandle {
82
82
  }
83
83
 
84
84
  /**
85
- * Updates the goal handle with the execute status and begins exection.
85
+ * Transitions the goal to the executing state and begins execution.
86
+ * Has no effect if the goal is already executing, no longer active, or destroyed.
86
87
  * @param {function} callback - An optional callback to use instead of the one provided to the action server.
87
88
  * @returns {undefined}
88
89
  */
89
90
  execute(callback) {
91
+ if (this._destroyed) {
92
+ return;
93
+ }
94
+
95
+ // Guard: already executing — no-op
96
+ if (this.status === ActionInterfaces.GoalStatus.STATUS_EXECUTING) {
97
+ return;
98
+ }
99
+
100
+ // Guard: only transition if goal is still active
101
+ if (!this.isActive) {
102
+ return;
103
+ }
104
+
90
105
  if (!this.isCancelRequested) {
91
106
  this._updateState(GoalEvent.EXECUTE);
92
107
  }
@@ -105,6 +120,16 @@ class ServerGoalHandle {
105
120
  return;
106
121
  }
107
122
 
123
+ if (this.status !== ActionInterfaces.GoalStatus.STATUS_EXECUTING) {
124
+ this._actionServer._node
125
+ .getLogger()
126
+ .warn(
127
+ `publishFeedback() called on goal ${this.goalId.uuid} ` +
128
+ `in status ${this.status}, not executing; ignoring`
129
+ );
130
+ return;
131
+ }
132
+
108
133
  let feedbackMessage =
109
134
  new this._actionServer.typeClass.impl.FeedbackMessage();
110
135
  feedbackMessage['goal_id'] = this.goalId;
@@ -40,7 +40,7 @@ class ActionUuid {
40
40
  this._bytes = bytes;
41
41
  } else {
42
42
  // Generate random UUID.
43
- let uuid = randomUUID().replace(/-/g, '');
43
+ let uuid = randomUUID().replaceAll('-', '');
44
44
  this._bytes = Uint8Array.from(Buffer.from(uuid, 'hex'));
45
45
  }
46
46
  }
package/lib/client.js CHANGED
@@ -25,51 +25,6 @@ const {
25
25
  const { assertValidMessage } = require('./message_validation.js');
26
26
  const debug = require('debug')('rclnodejs:client');
27
27
 
28
- // Polyfill for AbortSignal.any() for Node.js <= 20.3.0
29
- // AbortSignal.any() was added in Node.js 20.3.0
30
- // See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static
31
- if (!AbortSignal.any) {
32
- AbortSignal.any = function (signals) {
33
- // Filter out null/undefined values and validate inputs
34
- const validSignals = Array.isArray(signals)
35
- ? signals.filter((signal) => signal != null)
36
- : [];
37
-
38
- // If no valid signals, return a never-aborting signal
39
- if (validSignals.length === 0) {
40
- return new AbortController().signal;
41
- }
42
-
43
- const controller = new AbortController();
44
- const listeners = [];
45
-
46
- // Cleanup function to remove all event listeners
47
- const cleanup = () => {
48
- listeners.forEach(({ signal, listener }) => {
49
- signal.removeEventListener('abort', listener);
50
- });
51
- };
52
-
53
- for (const signal of validSignals) {
54
- if (signal.aborted) {
55
- cleanup();
56
- controller.abort(signal.reason);
57
- return controller.signal;
58
- }
59
-
60
- const listener = () => {
61
- cleanup();
62
- controller.abort(signal.reason);
63
- };
64
-
65
- signal.addEventListener('abort', listener);
66
- listeners.push({ signal, listener });
67
- }
68
-
69
- return controller.signal;
70
- };
71
- }
72
-
73
28
  /**
74
29
  * @class - Class representing a Client in ROS
75
30
  * @hideconstructor
package/lib/distro.js CHANGED
@@ -26,7 +26,9 @@ const DistroId = {
26
26
  IRON: 2305,
27
27
  JAZZY: 2405,
28
28
  KILTED: 2505,
29
+ LYRICAL: 2605,
29
30
  ROLLING: 5000,
31
+ FUTURE: 9999, // unrecognized ROS_DISTRO assumed newer than Rolling
30
32
  };
31
33
 
32
34
  const DistroNameIdMap = new Map();
@@ -37,6 +39,7 @@ DistroNameIdMap.set('humble', DistroId.HUMBLE);
37
39
  DistroNameIdMap.set('iron', DistroId.IRON);
38
40
  DistroNameIdMap.set('jazzy', DistroId.JAZZY);
39
41
  DistroNameIdMap.set('kilted', DistroId.KILTED);
42
+ DistroNameIdMap.set('lyrical', DistroId.LYRICAL);
40
43
  DistroNameIdMap.set('rolling', DistroId.ROLLING);
41
44
 
42
45
  const DistroUtils = {
@@ -48,11 +51,15 @@ const DistroUtils = {
48
51
  * @return {number} Return the rclnodejs distro identifier
49
52
  */
50
53
  getDistroId: function (distroName) {
51
- const dname = distroName ? distroName.toLowerCase() : this.getDistroName();
54
+ const dname =
55
+ (distroName || this.getDistroName() || '').toLowerCase() || undefined;
52
56
 
53
- return DistroNameIdMap.has(dname)
54
- ? DistroNameIdMap.get(dname)
55
- : DistroId.UNKNOWN;
57
+ if (dname && DistroNameIdMap.has(dname)) {
58
+ return DistroNameIdMap.get(dname);
59
+ }
60
+
61
+ // Unrecognized but provided/resolved distro name → assume future; unset → unknown
62
+ return dname ? DistroId.FUTURE : DistroId.UNKNOWN;
56
63
  },
57
64
 
58
65
  /**
@@ -57,7 +57,7 @@ let interfaceLoader = {
57
57
  }
58
58
 
59
59
  // TODO(Kenny): more checks of the string argument
60
- if (name.indexOf('/') !== -1) {
60
+ if (name.includes('/')) {
61
61
  let [packageName, type, messageName] = name.split('/');
62
62
  return this.loadInterface(packageName, type, messageName);
63
63
  }
@@ -88,35 +88,7 @@ class MessageIntrospector {
88
88
  const instance = new this.#typeClass();
89
89
  this.#defaultsCache = toPlainObject(instance);
90
90
  }
91
- return this.#deepClone(this.#defaultsCache);
92
- }
93
-
94
- /**
95
- * Deep clone an object.
96
- * @param {any} obj - Object to clone
97
- * @returns {any} Cloned object
98
- * @private
99
- */
100
- #deepClone(obj) {
101
- if (obj === null || typeof obj !== 'object') {
102
- return obj;
103
- }
104
-
105
- if (Array.isArray(obj)) {
106
- return obj.map((item) => this.#deepClone(item));
107
- }
108
-
109
- if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) {
110
- return obj.slice();
111
- }
112
-
113
- const cloned = {};
114
- for (const key in obj) {
115
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
116
- cloned[key] = this.#deepClone(obj[key]);
117
- }
118
- }
119
- return cloned;
91
+ return structuredClone(this.#defaultsCache);
120
92
  }
121
93
  }
122
94
 
@@ -62,7 +62,7 @@ function toPlainArrays(obj) {
62
62
  if (typeof obj === 'object' && obj !== null) {
63
63
  const result = {};
64
64
  for (const key in obj) {
65
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
65
+ if (Object.hasOwn(obj, key)) {
66
66
  result[key] = toPlainArrays(obj[key]);
67
67
  }
68
68
  }
@@ -105,7 +105,7 @@ function toJSONSafe(obj) {
105
105
  if (typeof obj === 'object' && obj !== null) {
106
106
  const result = {};
107
107
  for (const key in obj) {
108
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
108
+ if (Object.hasOwn(obj, key)) {
109
109
  result[key] = toJSONSafe(obj[key]);
110
110
  }
111
111
  }
@@ -20,6 +20,10 @@ const { execSync } = require('child_process');
20
20
  const { NativeError } = require('./errors.js');
21
21
  const bindings = require('bindings');
22
22
  const debug = require('debug')('rclnodejs');
23
+ const {
24
+ detectPrebuildRuntime,
25
+ getTaggedPrebuildFilename,
26
+ } = require('./prebuilds');
23
27
  const { detectUbuntuCodename } = require('./utils');
24
28
 
25
29
  let nativeModule = null;
@@ -35,6 +39,7 @@ function customFallbackLoader() {
35
39
 
36
40
  const rosDistro = process.env.ROS_DISTRO;
37
41
  const arch = process.arch;
42
+ const runtime = detectPrebuildRuntime();
38
43
  const ubuntuCodename = detectUbuntuCodename();
39
44
 
40
45
  // Require all three components for exact match
@@ -58,16 +63,22 @@ function customFallbackLoader() {
58
63
  }
59
64
 
60
65
  try {
61
- // Look for exact match binary: {ros_distro}-{ubuntu_codename}-{arch}-rclnodejs.node
62
- const exactMatchFilename = `${rosDistro}-${ubuntuCodename}-${arch}-rclnodejs.node`;
63
- const exactMatchPath = path.join(prebuildDir, exactMatchFilename);
64
-
65
- if (fs.existsSync(exactMatchPath)) {
66
- debug(`Found exact match binary: ${exactMatchFilename}`);
67
- return require(exactMatchPath);
66
+ const candidate = getTaggedPrebuildFilename({
67
+ rosDistro,
68
+ ubuntuCodename,
69
+ arch,
70
+ runtime,
71
+ });
72
+ const candidatePath = path.join(prebuildDir, candidate);
73
+
74
+ if (fs.existsSync(candidatePath)) {
75
+ debug(`Found ${runtime} prebuilt binary: ${candidate}`);
76
+ return require(candidatePath);
68
77
  }
69
78
 
70
- debug(`No exact match found for: ${exactMatchFilename}`);
79
+ debug(
80
+ `No matching ${runtime} prebuilt binary found for ${rosDistro}-${ubuntuCodename}-${arch}`
81
+ );
71
82
  return null;
72
83
  } catch (e) {
73
84
  debug('Error in simplified prebuilt loader:', e.message);
@@ -110,10 +121,11 @@ function loadNativeAddon() {
110
121
  }
111
122
 
112
123
  const rosDistro = process.env.ROS_DISTRO;
124
+ const runtime = detectPrebuildRuntime();
113
125
  const ubuntuCodename = detectUbuntuCodename();
114
126
 
115
127
  debug(
116
- `Platform: ${process.platform}, Arch: ${process.arch}, Ubuntu: ${ubuntuCodename || 'unknown'}, ROS: ${rosDistro || 'unknown'}`
128
+ `Platform: ${process.platform}, Arch: ${process.arch}, Runtime: ${runtime}, Ubuntu: ${ubuntuCodename || 'unknown'}, ROS: ${rosDistro || 'unknown'}`
117
129
  );
118
130
 
119
131
  // Prebuilt binaries are only supported on Linux (Ubuntu)