Skip to content

Commit 31204f4

Browse files
committed
Generate missing messages at runtime
1 parent 0448667 commit 31204f4

File tree

6 files changed

+134
-0
lines changed

6 files changed

+134
-0
lines changed

lib/interface_loader.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,66 @@ let interfaceLoader = {
9090
return pkg;
9191
},
9292

93+
_searchAndGenerateInterface(packageName, type, messageName, filePath) {
94+
// Check if it's a valid package
95+
for (const pkgPath of generator.getInstalledPackagePaths()) {
96+
if (pkgPath.includes('ros2-linux')) {
97+
continue;
98+
}
99+
100+
// Recursively search for files named messageName.* under pkgPath/
101+
if (fs.existsSync(pkgPath)) {
102+
// Recursive function to search for files
103+
function searchForFile(dir) {
104+
try {
105+
const items = fs.readdirSync(dir, { withFileTypes: true });
106+
for (const item of items) {
107+
const fullPath = path.join(dir, item.name);
108+
109+
if (item.isFile()) {
110+
const baseName = path.parse(item.name).name;
111+
// Check if the base filename matches messageName
112+
if (baseName === messageName) {
113+
return fullPath;
114+
}
115+
} else if (item.isDirectory()) {
116+
// Recursively search subdirectories
117+
const result = searchForFile(fullPath);
118+
if (result) {
119+
return result;
120+
}
121+
}
122+
}
123+
} catch (err) {
124+
// Skip directories we can't read
125+
console.log('Error reading directory:', dir, err.message);
126+
}
127+
return null;
128+
}
129+
130+
const foundFilePath = searchForFile(
131+
path.join(pkgPath, 'share', packageName)
132+
);
133+
if (foundFilePath && foundFilePath.length > 0) {
134+
// Use worker thread to generate interfaces synchronously
135+
try {
136+
generator.generateInPathSyncWorker(pkgPath);
137+
// Now try to load the interface again from the generated files
138+
if (fs.existsSync(filePath)) {
139+
return require(filePath);
140+
}
141+
} catch (err) {
142+
console.error('Error in interface generation:', err);
143+
}
144+
} else {
145+
throw new Error(
146+
`The message required does not exist: ${packageName}, ${type}, ${messageName} at ${generator.generatedRoot}`
147+
);
148+
}
149+
}
150+
}
151+
},
152+
93153
loadInterface(packageName, type, messageName) {
94154
if (arguments.length === 1) {
95155
const type = arguments[0];
@@ -110,6 +170,13 @@ let interfaceLoader = {
110170

111171
if (fs.existsSync(filePath)) {
112172
return require(filePath);
173+
} else {
174+
return this._searchAndGenerateInterface(
175+
packageName,
176+
type,
177+
messageName,
178+
filePath
179+
);
113180
}
114181
}
115182
throw new Error(

rosidl_gen/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const generateJSStructFromIDL = require('./idl_generator.js');
1919
const packages = require('./packages.js');
2020
const path = require('path');
2121
const idlConvertor = require('../rosidl_convertor/idl_convertor.js');
22+
const { Worker } = require('worker_threads');
2223
const generatedRoot = path.join(__dirname, '../generated/');
2324
const serviceMsgPath = path.join(generatedRoot, 'srv_msg');
2425
const idlPath = path.join(generatedRoot, 'share');
@@ -53,6 +54,35 @@ async function generateInPath(path) {
5354
);
5455
}
5556

57+
function generateInPathSyncWorker(targetPath) {
58+
try {
59+
// Use child_process.spawnSync for truly synchronous execution
60+
const result = require('child_process').spawnSync(
61+
'node',
62+
[path.join(__dirname, 'generate-worker.js')],
63+
{
64+
env: { ...process.env, WORKER_TARGET_PATH: targetPath },
65+
encoding: 'utf8',
66+
timeout: 30000,
67+
}
68+
);
69+
70+
if (result.error) {
71+
throw result.error;
72+
}
73+
74+
if (result.status !== 0) {
75+
throw new Error(
76+
`Worker process exited with code ${result.status}. stderr: ${result.stderr}`
77+
);
78+
}
79+
80+
return result.stdout;
81+
} catch (error) {
82+
throw error;
83+
}
84+
}
85+
5686
async function generateAll(forcedGenerating) {
5787
// If we want to create the JavaScript files compulsively (|forcedGenerating| equals to true)
5888
// or the JavaScript files have not been created (|exist| equals to false),
@@ -86,7 +116,9 @@ const generator = {
86116

87117
generateAll,
88118
generateInPath,
119+
generateInPathSyncWorker,
89120
generatedRoot,
121+
getInstalledPackagePaths,
90122
};
91123

92124
module.exports = generator;

test/overlay_test_ws/geometry_msgs/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ find_package(rosidl_default_generators REQUIRED)
1515

1616
rosidl_generate_interfaces(${PROJECT_NAME}
1717
"msg/Point.msg"
18+
"msg/Testing.msg"
1819
)
1920

2021
ament_package()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# This contains the position of a point in free space
2+
float64 x
3+
float64 y
4+
float64 z
5+
string data
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# This contains the position of a point in free space
2+
float64 x
3+
float64 y
4+
float64 z
5+
string data

test/test-rosidl-message-generator.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
const assert = require('assert');
1818
const os = require('os');
1919
const rclnodejs = require('../index.js');
20+
const path = require('path');
2021

2122
describe('ROSIDL Node.js message generator test suite', function () {
2223
before(function () {
@@ -220,4 +221,27 @@ describe('ROSIDL Node.js message generator test suite', function () {
220221
assert.equal(array.size, 6);
221222
assert.equal(array.capacity, 6);
222223
});
224+
225+
it('Generate message at runtime', function () {
226+
const amentPrefixPathOriginal = process.env.AMENT_PREFIX_PATH;
227+
const customizeMsgPath = path.join(
228+
__dirname,
229+
'overlay_test_ws',
230+
'geometry_msgs',
231+
'install',
232+
'geometry_msgs'
233+
);
234+
process.env.AMENT_PREFIX_PATH += path.delimiter;
235+
process.env.AMENT_PREFIX_PATH += customizeMsgPath;
236+
console.log(process.env.AMENT_PREFIX_PATH);
237+
assert.doesNotThrow(() => {
238+
const Testing = rclnodejs.require('geometry_msgs/msg/Testing');
239+
console.log(Testing);
240+
const t = new Testing();
241+
assert.equal(typeof t, 'object');
242+
assert.equal(typeof t.x, 'number');
243+
assert.equal(typeof t.data, 'string');
244+
}, 'This function should not throw');
245+
process.env.AMENT_PREFIX_PATH = amentPrefixPathOriginal;
246+
});
223247
});

0 commit comments

Comments
 (0)