Skip to content
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

add idl pipeline using a separate extension point #331

Merged
merged 3 commits into from
Nov 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion rosidl_adapter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ if(BUILD_TESTING)
ament_lint_auto_find_test_dependencies()
endif()

ament_package()
ament_package(
CONFIG_EXTRAS "rosidl_adapter-extras.cmake"
)

install(
DIRECTORY cmake
DESTINATION share/${PROJECT_NAME}
)

install(PROGRAMS
scripts/msg2idl.py
Expand Down
67 changes: 67 additions & 0 deletions rosidl_adapter/cmake/rosidl_adapt_interfaces.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright 2018 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# Convert non-idl interface files into `.idl` files.
#
# The input file might be a `.msg` or `.srv` file.
#
# :param idl_var: the variable name to return the list of generated `.idl`
# files, each item is a tuple separated by a colon with an absolute base path
# and a path relative to that base path
# :type idl_var: string
# :param arguments_file: the path of the arguments file containing the paths of
# the non-idl files.
# :type arguments_file: string
# :param TARGET: the name of the generation target
# :type TARGET: string
#
# @public
#
function(rosidl_adapt_interfaces idl_var arguments_file)
cmake_parse_arguments(ARG "" "TARGET" ""
${ARGN})
if(ARG_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "rosidl_adapt_interfaces() called with unused "
"arguments: ${ARG_UNPARSED_ARGUMENTS}")
endif()

find_package(PythonInterp REQUIRED)
if(NOT PYTHON_EXECUTABLE)
message(FATAL_ERROR "Variable 'PYTHON_EXECUTABLE' must not be empty")
endif()

set(idl_output "${CMAKE_CURRENT_BINARY_DIR}/rosidl_adapter/${ARG_TARGET}.idls")
set(cmd
"${PYTHON_EXECUTABLE}" -m rosidl_adapter
--package-name ${PROJECT_NAME}
--arguments-file "${arguments_file}"
--output-dir "${CMAKE_CURRENT_BINARY_DIR}/rosidl_adapter/${PROJECT_NAME}"
--output-file "${idl_output}")
execute_process(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like execute_process() happens at configure time. It doesn't look like it can have file or target dependencies, or even run at build time. If I configure a package, change an .msg file and build, then I would expect this to not re-generate the .idl file. Does anything tell CMake to re-run rosidl_adapter at build time?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call ensures that CMake is re-run when any of the passed in interface files changes.

COMMAND ${cmd}
OUTPUT_QUIET
ERROR_VARIABLE error
RESULT_VARIABLE result
)
if(NOT result EQUAL 0)
string(REPLACE ";" " " cmd_str "${cmd}")
message(FATAL_ERROR
"execute_process(${cmd_str}) returned error code ${result}:\n${error}")
endif()

file(STRINGS "${idl_output}" idl_tuples)

set(${idl_var} ${idl_tuples} PARENT_SCOPE)
endfunction()
17 changes: 17 additions & 0 deletions rosidl_adapter/rosidl_adapter-extras.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2018 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# copied from rosidl_adapter/rosidl_adapter-extras.cmake

include("${rosidl_adapter_DIR}/rosidl_adapt_interfaces.cmake")
11 changes: 7 additions & 4 deletions rosidl_adapter/rosidl_adapter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ def convert_to_idl(package_dir, package_name, interface_file, output_dir):
if interface_file.suffix == '.msg':
from rosidl_adapter.msg import convert_msg_to_idl
return convert_msg_to_idl(
package_dir, package_name, interface_file,
output_dir / package_name / 'msg')
package_dir, package_name, interface_file, output_dir / 'msg')

if interface_file.suffix == '.srv':
from rosidl_adapter.srv import convert_srv_to_idl
return convert_srv_to_idl(
package_dir, package_name, interface_file,
output_dir / package_name / 'srv')
package_dir, package_name, interface_file, output_dir / 'srv')

if interface_file.suffix == '.action':
from rosidl_adapter.action import convert_action_to_idl
return convert_action_to_idl(
package_dir, package_name, interface_file, output_dir / 'action')

assert False, "Unsupported interface type '{interface_file.suffix}'" \
.format_map(locals())
19 changes: 19 additions & 0 deletions rosidl_adapter/rosidl_adapter/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2018 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys

from rosidl_adapter.main import main

sys.exit(main())
62 changes: 62 additions & 0 deletions rosidl_adapter/rosidl_adapter/action/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright 2018 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from rosidl_adapter.parser import parse_action_string
from rosidl_adapter.resource import expand_template


def convert_action_to_idl(package_dir, package_name, input_file, output_dir):
assert package_dir.is_absolute()
assert not input_file.is_absolute()
assert input_file.suffix == '.action'

abs_input_file = package_dir / input_file
print('Reading input file: {abs_input_file}'.format_map(locals()))
abs_input_file = package_dir / input_file
content = abs_input_file.read_text(encoding='utf-8')
action = parse_action_string(package_name, input_file.stem, content)
sloretz marked this conversation as resolved.
Show resolved Hide resolved

# HACK as long as the return action specification contains implicitly added
# fields they have to be skipped when generating .idl files
assert len(action.goal_service.request.fields) >= 1
assert action.goal_service.request.fields[0].name == 'uuid'
action.goal_service.request.fields.pop(0)
assert len(action.goal_service.response.fields) >= 2
assert action.goal_service.response.fields[0].name == 'accepted'
assert action.goal_service.response.fields[1].name == 'stamp'
action.goal_service.response.fields.pop(1)
action.goal_service.response.fields.pop(0)

assert len(action.result_service.request.fields) >= 1
assert action.result_service.request.fields[0].name == 'uuid'
action.result_service.request.fields.pop(0)
assert len(action.result_service.response.fields) >= 1
assert action.result_service.response.fields[0].name == 'status'
action.result_service.response.fields.pop(0)

assert len(action.feedback.fields) >= 1
assert action.feedback.fields[0].name == 'uuid'
action.feedback.fields.pop(0)

output_file = output_dir / input_file.with_suffix('.idl').name
abs_output_file = output_file.absolute()
print('Writing output file: {abs_output_file}'.format_map(locals()))
data = {
'pkg_name': package_name,
'relative_input_file': input_file,
'action': action,
}

expand_template('action.idl.em', data, output_file)
return output_file
60 changes: 60 additions & 0 deletions rosidl_adapter/rosidl_adapter/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2018 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import json
import pathlib
import sys


from rosidl_adapter import convert_to_idl


def main(argv=sys.argv[1:]):
parser = argparse.ArgumentParser(
description='Convert interface files to .idl')
parser.add_argument(
'--package-name', required=True,
help='The name of the package')
parser.add_argument(
'--arguments-file', required=True,
help='The JSON file containing the non-idl tuples to convert to .idl')
parser.add_argument(
'--output-dir', required=True,
help='The base directory to create .idl files in')
parser.add_argument(
'--output-file', required=True,
help='The output file containing the tuples for the generated .idl '
'files')
args = parser.parse_args(argv)
output_dir = pathlib.Path(args.output_dir)
output_file = pathlib.Path(args.output_file)

with open(args.arguments_file, 'r') as h:
data = json.load(h)

idl_tuples = []
for non_idl_tuple in data['non_idl_tuples']:
# only take the filastrst : for separation, since the first tuple
# contains an absolute path which on Windows contains a colon
basepath, relative_path = non_idl_tuple.rsplit(':', 1)
abs_idl_file = convert_to_idl(
pathlib.Path(basepath), args.package_name,
pathlib.Path(relative_path), output_dir)
idl_tuples.append((output_dir, abs_idl_file.relative_to(output_dir)))

output_file.parent.mkdir(exist_ok=True)
with output_file.open('w') as h:
for basepath, relative_path in idl_tuples:
h.write('{basepath}:{relative_path}\n'.format_map(locals()))
42 changes: 42 additions & 0 deletions rosidl_adapter/rosidl_adapter/resource/action.idl.em
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// generated from rosidl_adapter/resource/action.idl.em
// with input from @(pkg_name)/@(relative_input_file)

@{
from rosidl_adapter.msg import get_include_file
include_files = set()
fields = action.goal_service.request.fields + \
action.goal_service.response.fields + \
action.result_service.request.fields + \
action.result_service.response.fields + \
action.feedback.fields
for field in fields:
include_file = get_include_file(field.type)
if include_file is not None:
include_files.add(include_file)
}@
@[for include_file in sorted(include_files)]@
#include "@(include_file)"
@[end for]@

module @(pkg_name) {
module action {
@{
TEMPLATE(
'struct.idl.em',
msg=action.goal_service.request,
)
}@
@{
TEMPLATE(
'struct.idl.em',
msg=action.result_service.response,
)
}@
@{
TEMPLATE(
'struct.idl.em',
msg=action.feedback,
)
}@
};
};
Loading