-
Notifications
You must be signed in to change notification settings - Fork 79
feat: add descriptor namespaces #1095
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
5e5d647
c5e5cf7
903cff3
6f32900
221138b
e7e262b
aff6dfc
507c93d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,8 @@ const fs = require('fs'); | |
| const loader = require('../lib/interface_loader.js'); | ||
| const pkgFilters = require('../rosidl_gen/filter.js'); | ||
|
|
||
| const descriptorInterfaceNamespace = 'descriptor'; | ||
|
|
||
| async function generateAll() { | ||
| // load pkg and interface info (msgs and srvs) | ||
| const generatedPath = path.join(__dirname, '../generated/'); | ||
|
|
@@ -119,47 +121,30 @@ function savePkgInfoAsTSD(pkgInfos, fd) { | |
| for (const subfolder of pkgInfo.subfolders.keys()) { | ||
| fs.writeSync(fd, ` namespace ${subfolder} {\n`); | ||
|
|
||
| for (const rosInterface of pkgInfo.subfolders.get(subfolder)) { | ||
| const type = rosInterface.type(); | ||
| const fullInterfaceName = `${type.pkgName}/${type.subFolder}/${type.interfaceName}`; | ||
| const fullInterfacePath = `${type.pkgName}.${type.subFolder}.${type.interfaceName}`; | ||
| const fullInterfaceConstructor = fullInterfacePath + 'Constructor'; | ||
|
|
||
| if (isMsgInterface(rosInterface)) { | ||
| // create message interface | ||
| saveMsgAsTSD(rosInterface, fd); | ||
| saveMsgConstructorAsTSD(rosInterface, fd); | ||
| messagesMap[fullInterfaceName] = fullInterfacePath; | ||
| } else if (isSrvInterface(rosInterface)) { | ||
| if ( | ||
| !isValidService(rosInterface, pkgInfo.subfolders.get(subfolder)) | ||
| ) { | ||
| let type = rosInterface.type(); | ||
| console.log( | ||
| `Incomplete service: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.` | ||
| ); | ||
| continue; | ||
| } | ||
|
|
||
| // create service interface | ||
| saveSrvAsTSD(rosInterface, fd); | ||
| if (!isInternalActionSrvInterface(rosInterface)) { | ||
| servicesMap[fullInterfaceName] = fullInterfaceConstructor; | ||
| } | ||
| } else if (isActionInterface(rosInterface)) { | ||
| if (!isValidAction(rosInterface, pkgInfo.subfolders.get(subfolder))) { | ||
| let type = rosInterface.type(); | ||
| console.log( | ||
| `Incomplete action: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.` | ||
| ); | ||
| continue; | ||
| } | ||
|
|
||
| // create action interface | ||
| saveActionAsTSD(rosInterface, fd); | ||
| actionsMap[fullInterfaceName] = fullInterfaceConstructor; | ||
| } | ||
| } | ||
| // generate real msg/srv/action interfaces | ||
| generateRosMsgInterfaces( | ||
| pkgInfo, | ||
| subfolder, | ||
| messagesMap, | ||
| servicesMap, | ||
| actionsMap, | ||
| fd | ||
| ); | ||
|
|
||
| // generate descriptor msg/srv/action interfaces | ||
| fs.writeSync(fd, ` namespace ${descriptorInterfaceNamespace} {\n`); | ||
| const descriptorInterfaceType = true; | ||
| generateRosMsgInterfaces( | ||
| pkgInfo, | ||
| subfolder, | ||
| messagesMap, | ||
| servicesMap, | ||
| actionsMap, | ||
| fd, | ||
| descriptorInterfaceType | ||
| ); | ||
| // close namespace descriptor declare | ||
| fs.writeSync(fd, ' }\n'); | ||
|
|
||
| // close namespace declare | ||
| fs.writeSync(fd, ' }\n'); | ||
|
|
@@ -238,16 +223,84 @@ function savePkgInfoAsTSD(pkgInfos, fd) { | |
| fs.writeSync(fd, '}\n'); | ||
| } | ||
|
|
||
| function saveMsgAsTSD(rosMsgInterface, fd) { | ||
| fs.writeSync( | ||
| fd, | ||
| ` export interface ${rosMsgInterface.type().interfaceName} {\n` | ||
| function generateRosMsgInterfaces( | ||
| pkgInfo, | ||
| subfolder, | ||
| messagesMap, | ||
| servicesMap, | ||
| actionsMap, | ||
| fd, | ||
| descriptorInterfaceType = false | ||
| ) { | ||
| const descriptorNamespaceName = descriptorInterfaceType | ||
| ? `${descriptorInterfaceNamespace}/` | ||
| : ''; | ||
| const descriptorNamespacePath = descriptorInterfaceType | ||
| ? `${descriptorInterfaceNamespace}.` | ||
| : ''; | ||
| for (const rosInterface of pkgInfo.subfolders.get(subfolder)) { | ||
| const type = rosInterface.type(); | ||
| const fullInterfaceName = `${type.pkgName}/${type.subFolder}/${descriptorNamespaceName}${type.interfaceName}`; | ||
| const fullInterfacePath = `${type.pkgName}.${type.subFolder}.${descriptorNamespacePath}${type.interfaceName}`; | ||
| const fullInterfaceConstructor = fullInterfacePath + 'Constructor'; | ||
|
|
||
| if (isMsgInterface(rosInterface)) { | ||
| // create message interface | ||
| saveMsgAsTSD(rosInterface, fd, descriptorInterfaceType); | ||
| saveMsgConstructorAsTSD(rosInterface, fd, descriptorInterfaceType); | ||
| messagesMap[fullInterfaceName] = fullInterfacePath; | ||
| } else if (isSrvInterface(rosInterface)) { | ||
| if (!isValidService(rosInterface, pkgInfo.subfolders.get(subfolder))) { | ||
| let type = rosInterface.type(); | ||
| console.log( | ||
| `Incomplete service: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.` | ||
| ); | ||
| continue; | ||
| } | ||
|
|
||
| // create service interface | ||
| saveSrvAsTSD(rosInterface, fd, descriptorInterfaceType); | ||
| if (!isInternalActionSrvInterface(rosInterface)) { | ||
| servicesMap[fullInterfaceName] = fullInterfaceConstructor; | ||
| } | ||
| } else if (isActionInterface(rosInterface)) { | ||
| if (!isValidAction(rosInterface, pkgInfo.subfolders.get(subfolder))) { | ||
| let type = rosInterface.type(); | ||
| console.log( | ||
| `Incomplete action: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.` | ||
| ); | ||
| continue; | ||
| } | ||
|
|
||
| // create action interface | ||
| saveActionAsTSD(rosInterface, fd, descriptorInterfaceType); | ||
| actionsMap[fullInterfaceName] = fullInterfaceConstructor; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function saveMsgAsTSD(rosMsgInterface, fd, descriptorInterfaceType = false) { | ||
| const indentlevel = descriptorInterfaceType ? 8 : 6; | ||
|
||
| const tmpl = indentString( | ||
| `export interface ${rosMsgInterface.type().interfaceName} {\n`, | ||
| indentlevel | ||
| ); | ||
| fs.writeSync(fd, tmpl); | ||
| const useSamePkg = | ||
| isInternalActionMsgInterface(rosMsgInterface) || | ||
| isInternalServiceEventMsgInterface(rosMsgInterface); | ||
| saveMsgFieldsAsTSD(rosMsgInterface, fd, 8, ';', '', useSamePkg); | ||
| fs.writeSync(fd, ' }\n'); | ||
| const indentLevel = descriptorInterfaceType ? 10 : 8; | ||
|
||
| saveMsgFieldsAsTSD( | ||
| rosMsgInterface, | ||
| fd, | ||
| indentLevel, | ||
| ';', | ||
| '', | ||
| useSamePkg, | ||
| descriptorInterfaceType | ||
| ); | ||
| const tmplEnd = indentString('}\n', indentlevel); | ||
|
||
| fs.writeSync(fd, tmplEnd); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -261,6 +314,7 @@ function saveMsgAsTSD(rosMsgInterface, fd) { | |
| * @param {string} typePrefix The prefix to put before the type name for | ||
| * non-primitive types | ||
| * @param {boolean} useSamePackageSubFolder Indicates if the sub folder name should be taken from the message | ||
| * @param {boolean} descriptorInterfaceType Indicates if descriptor interface is being generated | ||
| * when the field type comes from the same package. This is needed for action interfaces. Defaults to false. | ||
| * @returns {undefined} | ||
| */ | ||
|
|
@@ -270,7 +324,8 @@ function saveMsgFieldsAsTSD( | |
| indent = 0, | ||
| lineEnd = ',', | ||
| typePrefix = '', | ||
| useSamePackageSubFolder = false | ||
| useSamePackageSubFolder = false, | ||
| descriptorInterfaceType = false | ||
|
||
| ) { | ||
| let type = rosMsgInterface.type(); | ||
| let fields = rosMsgInterface.ROSMessageDef.fields; | ||
|
|
@@ -280,49 +335,62 @@ function saveMsgFieldsAsTSD( | |
| useSamePackageSubFolder && field.type.pkgName === type.pkgName | ||
| ? type.subFolder | ||
| : 'msg'; | ||
| let fieldType = fieldType2JSName(field, subFolder); | ||
| let fieldType = fieldType2JSName(field, subFolder, descriptorInterfaceType); | ||
| let tp = field.type.isPrimitiveType ? '' : typePrefix; | ||
| if (typePrefix === 'rclnodejs.') { | ||
| fieldType = 'any'; | ||
| tp = ''; | ||
| } | ||
|
|
||
| const tmpl = indentString(`${field.name}: ${tp}${fieldType}`, indent); | ||
| fs.writeSync(fd, tmpl); | ||
| let arrayString = ''; | ||
| if (field.type.isArray) { | ||
| fs.writeSync(fd, '[]'); | ||
| arrayString = '[]'; | ||
|
|
||
| if (field.type.isFixedSizeArray && descriptorInterfaceType) { | ||
| arrayString = `[${field.type.arraySize}]`; | ||
| } | ||
|
|
||
| if (fieldType === 'number') { | ||
| if (fieldType === 'number' && !descriptorInterfaceType) { | ||
| // for number[] include alternate typed-array types, e.g., number[] | uint8[] | ||
| let jsTypedArrayName = fieldTypeArray2JSTypedArrayName(field.type.type); | ||
|
|
||
| if (jsTypedArrayName) { | ||
| fs.writeSync(fd, ` | ${jsTypedArrayName}`); | ||
| arrayString += ` | ${jsTypedArrayName}`; | ||
| } | ||
| } | ||
| } | ||
| const fieldString = descriptorInterfaceType | ||
| ? `${field.name}: '${tp}${fieldType}${arrayString}'` | ||
| : `${field.name}: ${tp}${fieldType}${arrayString}`; | ||
| const tmpl = indentString(fieldString, indent); | ||
| fs.writeSync(fd, tmpl); | ||
|
|
||
| fs.writeSync(fd, lineEnd); | ||
| fs.writeSync(fd, '\n'); | ||
| } | ||
| } | ||
|
|
||
| function saveMsgConstructorAsTSD(rosMsgInterface, fd) { | ||
| function saveMsgConstructorAsTSD( | ||
| rosMsgInterface, | ||
| fd, | ||
| descriptorInterfaceType = false | ||
| ) { | ||
| const type = rosMsgInterface.type(); | ||
| const msgName = type.interfaceName; | ||
|
|
||
| fs.writeSync(fd, ` export interface ${msgName}Constructor {\n`); | ||
| let interfaceTmpl = [`export interface ${msgName}Constructor {`]; | ||
|
|
||
| for (const constant of rosMsgInterface.ROSMessageDef.constants) { | ||
| const constantType = primitiveType2JSName(constant.type); | ||
| fs.writeSync(fd, ` readonly ${constant.name}: ${constantType};\n`); | ||
| interfaceTmpl.push(` readonly ${constant.name}: ${constantType};`); | ||
| } | ||
|
|
||
| fs.writeSync(fd, ` new(other?: ${msgName}): ${msgName};\n`); | ||
| fs.writeSync(fd, ' }\n'); | ||
| interfaceTmpl.push(` new(other?: ${msgName}): ${msgName};`); | ||
| interfaceTmpl.push('}'); | ||
| interfaceTmpl.push(''); | ||
| const indentLevel = descriptorInterfaceType ? 8 : 6; | ||
| fs.writeSync(fd, indentLines(interfaceTmpl, indentLevel).join('\n')); | ||
| } | ||
|
|
||
| function saveSrvAsTSD(rosSrvInterface, fd) { | ||
| function saveSrvAsTSD(rosSrvInterface, fd, descriptorInterfaceType = false) { | ||
| const serviceName = rosSrvInterface.type().interfaceName; | ||
|
|
||
| const interfaceTemplate = [ | ||
|
|
@@ -332,11 +400,15 @@ function saveSrvAsTSD(rosSrvInterface, fd) { | |
| '}', | ||
| '', | ||
| ]; | ||
|
|
||
| fs.writeSync(fd, indentLines(interfaceTemplate, 6).join('\n')); | ||
| const indentLevel = descriptorInterfaceType ? 8 : 6; | ||
| fs.writeSync(fd, indentLines(interfaceTemplate, indentLevel).join('\n')); | ||
| } | ||
|
|
||
| function saveActionAsTSD(rosActionInterface, fd) { | ||
| function saveActionAsTSD( | ||
| rosActionInterface, | ||
| fd, | ||
| descriptorInterfaceType = false | ||
| ) { | ||
| const actionName = rosActionInterface.type().interfaceName; | ||
|
|
||
| const interfaceTemplate = [ | ||
|
|
@@ -347,8 +419,8 @@ function saveActionAsTSD(rosActionInterface, fd) { | |
| '}', | ||
| '', | ||
| ]; | ||
|
|
||
| fs.writeSync(fd, indentLines(interfaceTemplate, 6).join('\n')); | ||
| const indentLevel = descriptorInterfaceType ? 8 : 6; | ||
| fs.writeSync(fd, indentLines(interfaceTemplate, indentLevel).join('\n')); | ||
| } | ||
|
|
||
| function isMsgInterface(rosInterface) { | ||
|
|
@@ -451,7 +523,16 @@ function isValidAction(rosActionInterface, infos) { | |
| return matches === SUCCESS_MATCH_COUNT; | ||
| } | ||
|
|
||
| function fieldType2JSName(fieldInfo, subFolder = 'msg') { | ||
| function fieldType2JSName( | ||
| fieldInfo, | ||
| subFolder = 'msg', | ||
| descriptorInterfaceType = false | ||
| ) { | ||
| if (descriptorInterfaceType) { | ||
| return fieldInfo.type.isPrimitiveType | ||
| ? `${fieldInfo.type.type}` | ||
| : `${fieldInfo.type.pkgName}/${subFolder}/${fieldInfo.type.type}`; | ||
| } | ||
| return fieldInfo.type.isPrimitiveType | ||
| ? primitiveType2JSName(fieldInfo.type.type) | ||
| : `${fieldInfo.type.pkgName}.${subFolder}.${fieldInfo.type.type}`; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| /// <reference path='../../types/index.d.ts' /> | ||
|
|
||
| import { expectType } from 'tsd'; | ||
| import { expectType, expectAssignable } from 'tsd'; | ||
| import * as rclnodejs from 'rclnodejs'; | ||
|
|
||
| const NODE_NAME = 'test_node'; | ||
|
|
@@ -390,3 +390,40 @@ param.value.integer_value = BigInt(123); | |
| expectType<bigint>(param.value.integer_value); | ||
| param.value.byte_array_value = [1, 2, 3]; | ||
| expectType<number[]>(param.value.byte_array_value); | ||
|
|
||
| // ---- Descriptors ----- | ||
| // Note: All fields are of type string exactly equal to the type of interface. | ||
| // built-in msg | ||
| const duration = rclnodejs.createMessageObject( | ||
| 'builtin_interfaces/msg/descriptor/Duration' | ||
| ); | ||
| expectType<rclnodejs.builtin_interfaces.msg.descriptor.Duration>(duration); | ||
| expectAssignable<'int32'>(duration.sec); | ||
| expectAssignable<'uint32'>(duration.nanosec); | ||
| // msg containing complex types | ||
| const poseStampedDescriptor = rclnodejs.createMessageObject( | ||
| 'geometry_msgs/msg/descriptor/PoseStamped' | ||
| ); | ||
| expectType<rclnodejs.geometry_msgs.msg.descriptor.PoseStamped>( | ||
| poseStampedDescriptor | ||
| ); | ||
| expectAssignable<'std_msgs/msg/Header'>(poseStampedDescriptor.header); | ||
| expectAssignable<'geometry_msgs/msg/Pose'>(poseStampedDescriptor.pose); | ||
| // action interface | ||
| const fibonacciFeedback = rclnodejs.createMessageObject( | ||
| 'example_interfaces/action/descriptor/Fibonacci_Feedback' | ||
| ); | ||
| expectType<rclnodejs.example_interfaces.action.descriptor.Fibonacci_Feedback>( | ||
| fibonacciFeedback | ||
| ); | ||
| expectAssignable<'int32[]'>(fibonacciFeedback.sequence); | ||
| // srv interface | ||
| const cancelGoalRequestDescriptor = rclnodejs.createMessageObject( | ||
| 'action_msgs/srv/descriptor/CancelGoal_Request' | ||
| ); | ||
| expectType<rclnodejs.action_msgs.srv.descriptor.CancelGoal_Request>( | ||
| cancelGoalRequestDescriptor | ||
| ); | ||
| expectAssignable<'action_msgs/msg/GoalInfo'>( | ||
| cancelGoalRequestDescriptor.goal_info | ||
| ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. really like these ut 👍
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
descriptorInterfaceType=>willGenerateDescriptionInterface