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

RobotWebTools / rclnodejs / 27124979142

08 Jun 2026 08:21AM UTC coverage: 91.221% (+5.7%) from 85.523%
27124979142

Pull #1530

github

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

2044 of 2402 branches covered (85.1%)

Branch coverage included in aggregate %.

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

942 existing lines in 47 files now uncovered.

16690 of 18135 relevant lines covered (92.03%)

227.36 hits per line

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

75.65
/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 bindings from 'bindings';
29✔
22
import createDebug from 'debug';
29✔
23
import {
29✔
24
  detectPrebuildRuntime,
29✔
25
  getTaggedPrebuildFilename,
29✔
26
} from './prebuilds.js';
29✔
27
import { detectUbuntuCodename } from './utils.js';
29✔
28

29✔
29
const require = createRequire(import.meta.url);
29✔
30
const __dirname = path.dirname(fileURLToPath(import.meta.url));
29✔
31
const debug = createDebug('rclnodejs');
29✔
32

29✔
33
let nativeModule = null;
29✔
34

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

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

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

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

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

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

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

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

2✔
91
  return null;
2✔
92
}
33✔
93

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

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

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

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

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

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

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

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

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

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

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

29✔
194
const addon = loadNativeAddon();
29✔
195

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

29✔
204
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