• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RobotWebTools / rclnodejs / 27192212469

09 Jun 2026 07:57AM UTC coverage: 91.22% (+5.7%) from 85.523%
27192212469

Pull #1530

github

web-flow
Merge 818e1579f into 9488c15ff
Pull Request #1530: Phase 2: convert lib/, index.js and tests to native ES modules

2045 of 2403 branches covered (85.1%)

Branch coverage included in aggregate %.

369 of 370 new or added lines in 65 files covered. (99.73%)

942 existing lines in 47 files now uncovered.

16688 of 18133 relevant lines covered (92.03%)

228.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.86
/lib/native_loader.js
1
// Copyright (c) 2025, The Robot Web Tools Contributors
29✔
2
//
29✔
3
// Licensed under the Apache License, Version 2.0 (the "License");
29✔
4
// you may not use this file except in compliance with the License.
29✔
5
// You may obtain a copy of the License at
29✔
6
//
29✔
7
//     http://www.apache.org/licenses/LICENSE-2.0
29✔
8
//
29✔
9
// Unless required by applicable law or agreed to in writing, software
29✔
10
// distributed under the License is distributed on an "AS IS" BASIS,
29✔
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29✔
12
// See the License for the specific language governing permissions and
29✔
13
// limitations under the License.
29✔
14

29✔
15
import fs from 'fs';
29✔
16
import path from 'path';
29✔
17
import childProcess from 'child_process';
29✔
18
import { fileURLToPath } from 'url';
29✔
19
import { createRequire } from 'module';
29✔
20
import { NativeError } from './errors.js';
29✔
21
import createDebug from 'debug';
29✔
22
import {
29✔
23
  detectPrebuildRuntime,
29✔
24
  getTaggedPrebuildFilename,
29✔
25
} from './prebuilds.js';
29✔
26
import { detectUbuntuCodename } from './utils.js';
29✔
27

29✔
28
const require = createRequire(import.meta.url);
29✔
29
// 'bindings' is a CommonJS helper that relies on CJS caller context to locate
29✔
30
// the compiled .node addon; load it via require() to preserve that context.
29✔
31
const bindings = require('bindings');
29✔
32
const __dirname = path.dirname(fileURLToPath(import.meta.url));
29✔
33
const debug = createDebug('rclnodejs');
29✔
34

29✔
35
let nativeModule = null;
29✔
36

29✔
37
// Simplified loader: only use prebuilt binaries with exact Ubuntu/ROS2/arch match
29✔
38
// Note: Prebuilt binaries are only supported on Linux (Ubuntu) platform
29✔
39
function customFallbackLoader() {
33✔
40
  // Prebuilt binaries are only for Linux platform
33✔
41
  if (process.platform !== 'linux') {
33✔
42
    debug('Prebuilt binaries are only supported on Linux platform');
1✔
43
    return null;
1✔
44
  }
1✔
45

32✔
46
  const rosDistro = process.env.ROS_DISTRO;
32✔
47
  const arch = process.arch;
32✔
48
  const runtime = detectPrebuildRuntime();
32✔
49
  const ubuntuCodename = detectUbuntuCodename();
32✔
50

32✔
51
  // Require all three components for exact match
32✔
52
  if (!rosDistro || !ubuntuCodename || !arch) {
33✔
53
    debug(
1✔
54
      `Missing environment info - ROS: ${rosDistro}, Ubuntu: ${ubuntuCodename}, Arch: ${arch}`
1✔
55
    );
1✔
56
    return null;
1✔
57
  }
1✔
58

31✔
59
  const prebuildDir = path.join(
31✔
60
    __dirname,
31✔
61
    '..',
31✔
62
    'prebuilds',
31✔
63
    `${process.platform}-${arch}`
31✔
64
  );
31✔
65

31✔
66
  if (!fs.existsSync(prebuildDir)) {
33✔
67
    debug('No prebuilds directory found');
29✔
68
    return null;
29✔
69
  }
29✔
70

2✔
71
  try {
2✔
72
    const candidate = getTaggedPrebuildFilename({
2✔
73
      rosDistro,
2✔
74
      ubuntuCodename,
2✔
75
      arch,
2✔
76
      runtime,
2✔
77
    });
2✔
78
    const candidatePath = path.join(prebuildDir, candidate);
2✔
79

2✔
80
    if (fs.existsSync(candidatePath)) {
2✔
81
      debug(`Found ${runtime} prebuilt binary: ${candidate}`);
2✔
82
      return require(candidatePath);
2✔
83
    }
2✔
UNCOV
84

×
85
    debug(
×
UNCOV
86
      `No matching ${runtime} prebuilt binary found for ${rosDistro}-${ubuntuCodename}-${arch}`
×
UNCOV
87
    );
×
88
    return null;
×
89
  } catch (e) {
6✔
90
    debug('Error in simplified prebuilt loader:', e.message);
2✔
91
  }
2✔
92

2✔
93
  return null;
2✔
94
}
33✔
95

29✔
96
// Simplified prebuilt binary loader: exact match or build from source
29✔
97
function loadNativeAddon() {
29✔
98
  if (nativeModule) {
29!
99
    return nativeModule;
×
UNCOV
100
  }
×
101

29✔
102
  // Environment variable to force building from source
29✔
103
  if (process.env.RCLNODEJS_FORCE_BUILD === '1') {
29✔
104
    debug('Forcing build from source (RCLNODEJS_FORCE_BUILD=1)');
1✔
105

1✔
106
    // Trigger actual compilation
1✔
107
    try {
1✔
108
      debug('Running forced node-gyp rebuild...');
1✔
109
      childProcess.execSync('npm run rebuild', {
1✔
110
        stdio: 'inherit',
1✔
111
        cwd: path.join(__dirname, '..'),
1✔
112
        timeout: 300000, // 5 minute timeout
1✔
113
      });
1✔
114

1✔
115
      // Load the newly built binary
1✔
116
      nativeModule = bindings('rclnodejs');
1✔
117
      debug('Successfully force compiled and loaded from source');
1✔
118
      return nativeModule;
1✔
119
    } catch (compileError) {
1!
120
      debug('Forced compilation failed:', compileError.message);
×
121
      throw new NativeError(
×
UNCOV
122
        `Failed to force build rclnodejs from source: ${compileError.message}`,
×
UNCOV
123
        'Forced compilation',
×
UNCOV
124
        { cause: compileError }
×
UNCOV
125
      );
×
UNCOV
126
    }
×
127
  }
1✔
128

28✔
129
  const rosDistro = process.env.ROS_DISTRO;
28✔
130
  const runtime = detectPrebuildRuntime();
28✔
131
  const ubuntuCodename = detectUbuntuCodename();
28✔
132

28✔
133
  debug(
28✔
134
    `Platform: ${process.platform}, Arch: ${process.arch}, Runtime: ${runtime}, Ubuntu: ${ubuntuCodename || 'unknown'}, ROS: ${rosDistro || 'unknown'}`
29!
135
  );
29✔
136

29✔
137
  // Prebuilt binaries are only supported on Linux (Ubuntu)
29✔
138
  if (process.platform === 'linux') {
29✔
139
    // Try exact match prebuilt binary first
28✔
140
    try {
28✔
141
      const prebuiltModule = customFallbackLoader();
28✔
142
      if (prebuiltModule) {
28!
143
        nativeModule = prebuiltModule;
×
144
        return nativeModule;
×
UNCOV
145
      }
×
146
    } catch (e) {
28!
147
      debug('Exact match prebuilt loading failed:', e.message);
×
UNCOV
148
    }
×
149

28✔
150
    debug(
28✔
151
      'No exact match prebuilt binary found, falling back to build from source'
28✔
152
    );
28✔
153
  } else {
28!
154
    debug(
×
UNCOV
155
      `Platform ${process.platform} does not support prebuilt binaries, will try existing build or compile from source`
×
UNCOV
156
    );
×
UNCOV
157
  }
×
158

28✔
159
  try {
28✔
160
    // Try to find existing built binary first (works on all platforms)
28✔
161
    // The 'bindings' module will search standard locations like:
28✔
162
    // - build/Release/rclnodejs.node
28✔
163
    // - build/Debug/rclnodejs.node
28✔
164
    // - compiled/{node_version}/{platform}/{arch}/rclnodejs.node
28✔
165
    // etc.
28✔
166
    nativeModule = bindings('rclnodejs');
28✔
167
    debug('Found and loaded existing built binary');
28✔
168
    return nativeModule;
28✔
169
  } catch {
28!
170
    debug('No existing built binary found, triggering compilation...');
×
UNCOV
171

×
UNCOV
172
    // Trigger actual compilation
×
173
    try {
×
174
      debug('Running node-gyp rebuild...');
×
NEW
175
      childProcess.execSync('npm run rebuild', {
×
UNCOV
176
        stdio: 'inherit',
×
UNCOV
177
        cwd: path.join(__dirname, '..'),
×
UNCOV
178
        timeout: 300000, // 5 minute timeout
×
UNCOV
179
      });
×
UNCOV
180

×
UNCOV
181
      // Try to load the newly built binary
×
182
      nativeModule = bindings('rclnodejs');
×
183
      debug('Successfully compiled and loaded from source');
×
184
      return nativeModule;
×
UNCOV
185
    } catch (compileError) {
×
186
      debug('Compilation failed:', compileError.message);
×
187
      throw new NativeError(
×
UNCOV
188
        `Failed to build rclnodejs from source: ${compileError.message}`,
×
UNCOV
189
        'Compilation',
×
UNCOV
190
        { cause: compileError }
×
UNCOV
191
      );
×
UNCOV
192
    }
×
UNCOV
193
  }
×
194
}
29✔
195

29✔
196
const addon = loadNativeAddon();
29✔
197

29✔
198
// Export internal functions for testing purposes
29✔
199
if (process.env.NODE_ENV === 'test') {
29✔
200
  addon.TestHelpers = {
28✔
201
    customFallbackLoader,
28✔
202
    loadNativeAddon,
28✔
203
  };
28✔
204
}
28✔
205

29✔
206
export default addon;
29✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc