Skip to content

Commit

Permalink
Move rosidl_cmake Python module to a new package rosidl_pycommon (#696)
Browse files Browse the repository at this point in the history
* Move common Python functions into new package

Deprecate the Python module in rosidl_cmake and move the implementation to the new package rosidl_pycommon.

This works towards removing a dependency on rosidl_cmake in generator packages.

Signed-off-by: Jacob Perron <[email protected]>

* Replace rosidl_cmake imports with rosidl_pycommon

Signed-off-by: Jacob Perron <[email protected]>
  • Loading branch information
jacobperron authored Aug 10, 2022
1 parent da29cb0 commit b8381d9
Show file tree
Hide file tree
Showing 39 changed files with 364 additions and 222 deletions.
4 changes: 2 additions & 2 deletions rosidl_cmake/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
<buildtool_export_depend>ament_cmake</buildtool_export_depend>
<buildtool_export_depend>python3-empy</buildtool_export_depend>

<exec_depend>rosidl_adapter</exec_depend>
<exec_depend>rosidl_parser</exec_depend>
<!-- Forwarding Python module -->
<exec_depend>rosidl_pycommon</exec_depend>

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
Expand Down
193 changes: 6 additions & 187 deletions rosidl_cmake/rosidl_cmake/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,192 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from io import StringIO
import json
import os
import pathlib
import re
import sys
import warnings

import em
from rosidl_parser.definition import IdlLocator
from rosidl_parser.parser import parse_idl_file
from rosidl_pycommon import * # noqa: F401, F403


def convert_camel_case_to_lower_case_underscore(value):
# insert an underscore before any upper case letter
# which is followed by a lower case letter
value = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', value)
# insert an underscore before any upper case letter
# which is preseded by a lower case letter or number
value = re.sub('([a-z0-9])([A-Z])', r'\1_\2', value)
return value.lower()


def read_generator_arguments(input_file):
with open(input_file, mode='r', encoding='utf-8') as h:
return json.load(h)


def get_newest_modification_time(target_dependencies):
newest_timestamp = None
for dep in target_dependencies:
ts = os.path.getmtime(dep)
if newest_timestamp is None or ts > newest_timestamp:
newest_timestamp = ts
return newest_timestamp


def generate_files(
generator_arguments_file, mapping, additional_context=None,
keep_case=False, post_process_callback=None
):
args = read_generator_arguments(generator_arguments_file)

template_basepath = pathlib.Path(args['template_dir'])
for template_filename in mapping.keys():
assert (template_basepath / template_filename).exists(), \
'Could not find template: ' + template_filename

latest_target_timestamp = get_newest_modification_time(args['target_dependencies'])
generated_files = []

for idl_tuple in args.get('idl_tuples', []):
idl_parts = idl_tuple.rsplit(':', 1)
assert len(idl_parts) == 2
locator = IdlLocator(*idl_parts)
idl_rel_path = pathlib.Path(idl_parts[1])
idl_stem = idl_rel_path.stem
if not keep_case:
idl_stem = convert_camel_case_to_lower_case_underscore(idl_stem)
try:
idl_file = parse_idl_file(locator)
for template_file, generated_filename in mapping.items():
generated_file = os.path.join(
args['output_dir'], str(idl_rel_path.parent),
generated_filename % idl_stem)
generated_files.append(generated_file)
data = {
'package_name': args['package_name'],
'interface_path': idl_rel_path,
'content': idl_file.content,
}
if additional_context is not None:
data.update(additional_context)
expand_template(
os.path.basename(template_file), data,
generated_file, minimum_timestamp=latest_target_timestamp,
template_basepath=template_basepath,
post_process_callback=post_process_callback)
except Exception as e:
print(
'Error processing idl file: ' +
str(locator.get_absolute_path()), file=sys.stderr)
raise(e)

return generated_files


template_prefix_path = []


def get_template_path(template_name):
global template_prefix_path
for basepath in template_prefix_path:
template_path = basepath / template_name
if template_path.exists():
return template_path
raise RuntimeError(f"Failed to find template '{template_name}'")


interpreter = None


def expand_template(
template_name, data, output_file, minimum_timestamp=None,
template_basepath=None, post_process_callback=None
):
# in the legacy API the first argument was the path to the template
if template_basepath is None:
template_name = pathlib.Path(template_name)
template_basepath = template_name.parent
template_name = template_name.name

global interpreter
output = StringIO()
interpreter = em.Interpreter(
output=output,
options={
em.BUFFERED_OPT: True,
em.RAW_OPT: True,
},
)

global template_prefix_path
template_prefix_path.append(template_basepath)
template_path = get_template_path(template_name)

# create copy before manipulating
data = dict(data)
_add_helper_functions(data)

try:
with template_path.open('r') as h:
template_content = h.read()
interpreter.invoke(
'beforeFile', name=template_name, file=h, locals=data)
interpreter.string(template_content, template_path, locals=data)
interpreter.invoke('afterFile')
except Exception as e: # noqa: F841
if os.path.exists(output_file):
os.remove(output_file)
print(f"{e.__class__.__name__} when expanding '{template_name}' into "
f"'{output_file}': {e}", file=sys.stderr)
raise
finally:
template_prefix_path.pop()

content = output.getvalue()
interpreter.shutdown()

if post_process_callback:
content = post_process_callback(content)

# only overwrite file if necessary
# which is either when the timestamp is too old or when the content is different
if os.path.exists(output_file):
timestamp = os.path.getmtime(output_file)
if minimum_timestamp is None or timestamp > minimum_timestamp:
with open(output_file, 'r', encoding='utf-8') as h:
if h.read() == content:
return
else:
# create folder if necessary
try:
os.makedirs(os.path.dirname(output_file))
except FileExistsError:
pass

with open(output_file, 'w', encoding='utf-8') as h:
h.write(content)


def _add_helper_functions(data):
data['TEMPLATE'] = _expand_template


def _expand_template(template_name, **kwargs):
global interpreter
template_path = get_template_path(template_name)
_add_helper_functions(kwargs)
with template_path.open('r') as h:
interpreter.invoke(
'beforeInclude', name=str(template_path), file=h, locals=kwargs)
content = h.read()
try:
interpreter.string(content, str(template_path), kwargs)
except Exception as e: # noqa: F841
print(f"{e.__class__.__name__} in template '{template_path}': {e}",
file=sys.stderr)
raise
interpreter.invoke('afterInclude')
warnings.warn(
"The 'rosidl_cmake' Python module is deprecated. Use 'rosidl_pycommon' instead.",
UserWarning
)
1 change: 0 additions & 1 deletion rosidl_generator_c/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ endif()
find_package(ament_cmake_python REQUIRED)
find_package(ament_cmake_ros REQUIRED)

ament_export_dependencies(rosidl_cmake)
ament_export_dependencies(rosidl_typesupport_interface)
ament_index_register_resource("rosidl_generator_packages")

Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_c/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<buildtool_export_depend>ament_cmake_core</buildtool_export_depend>
<buildtool_export_depend>python3</buildtool_export_depend>
<buildtool_export_depend>rosidl_cmake</buildtool_export_depend>
<buildtool_export_depend>rosidl_pycommon</buildtool_export_depend>

<build_export_depend>rosidl_typesupport_interface</build_export_depend>
<build_export_depend>rcutils</build_export_depend>
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_c/resource/idl.h.em
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@#######################################################################
@
@{
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
include_parts = [package_name] + list(interface_path.parents[0].parts) + \
[convert_camel_case_to_lower_case_underscore(interface_path.stem)]
include_parts_detail = [package_name] + list(interface_path.parents[0].parts) + [
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_c/resource/idl__functions.c.em
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@# - content (IdlContent, list of elements, e.g. Messages or Services)
@#######################################################################
@{
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
include_parts = [package_name] + list(interface_path.parents[0].parts) + [
'detail', convert_camel_case_to_lower_case_underscore(interface_path.stem)]
include_base = '/'.join(include_parts)
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_c/resource/idl__functions.h.em
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@# - content (IdlContent, list of elements, e.g. Messages or Services)
@#######################################################################
@{
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
include_parts = [package_name] + list(interface_path.parents[0].parts) + [
'detail', convert_camel_case_to_lower_case_underscore(interface_path.stem)]
header_guard_variable = '__'.join([x.upper() for x in include_parts]) + \
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_c/resource/idl__struct.h.em
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@# - content (IdlContent, list of elements, e.g. Messages or Services)
@#######################################################################
@{
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
include_parts = [package_name] + list(interface_path.parents[0].parts) + [
'detail', convert_camel_case_to_lower_case_underscore(interface_path.stem)]
header_guard_variable = '__'.join([x.upper() for x in include_parts]) + \
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_c/resource/idl__type_support.h.em
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@# - content (IdlContent, list of elements, e.g. Messages or Services)
@#######################################################################
@{
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
include_parts = [package_name] + list(interface_path.parents[0].parts) + [
'detail', convert_camel_case_to_lower_case_underscore(interface_path.stem)]
header_guard_variable = '__'.join([x.upper() for x in include_parts]) + \
Expand Down
4 changes: 2 additions & 2 deletions rosidl_generator_c/rosidl_generator_c/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_cmake import generate_files
from rosidl_parser.definition import AbstractGenericString
from rosidl_parser.definition import AbstractSequence
from rosidl_parser.definition import AbstractString
Expand All @@ -24,6 +22,8 @@
from rosidl_parser.definition import CHARACTER_TYPES
from rosidl_parser.definition import NamespacedType
from rosidl_parser.definition import OCTET_TYPE
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import generate_files


def generate_c(generator_arguments_file):
Expand Down
2 changes: 0 additions & 2 deletions rosidl_generator_cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ project(rosidl_generator_cpp)
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)

ament_export_dependencies(rosidl_cmake)

ament_index_register_resource("rosidl_generator_packages")

ament_python_install_package(${PROJECT_NAME})
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_cpp/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<buildtool_export_depend>ament_cmake_core</buildtool_export_depend>
<buildtool_export_depend>python3</buildtool_export_depend>
<buildtool_export_depend>rosidl_cmake</buildtool_export_depend>
<buildtool_export_depend>rosidl_pycommon</buildtool_export_depend>

<!-- This is needed for the rosidl_message_type_support_t struct and visibility macros -->
<build_export_depend>rosidl_generator_c</build_export_depend>
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_cpp/resource/idl.hpp.em
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@#######################################################################
@
@{
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
include_parts = [package_name] + list(interface_path.parents[0].parts) + \
[convert_camel_case_to_lower_case_underscore(interface_path.stem)]
include_parts_detail = [package_name] + list(interface_path.parents[0].parts) + [
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_cpp/resource/idl__builder.hpp.em
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@# - content (IdlContent, list of elements, e.g. Messages or Services)
@#######################################################################
@{
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
include_parts = [package_name] + list(interface_path.parents[0].parts) + [
'detail', convert_camel_case_to_lower_case_underscore(interface_path.stem)]
include_base = '/'.join(include_parts)
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_cpp/resource/idl__struct.hpp.em
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@# - content (IdlContent, list of elements, e.g. Messages or Services)
@#######################################################################
@{
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
include_parts = [package_name] + list(interface_path.parents[0].parts) + [
'detail', convert_camel_case_to_lower_case_underscore(interface_path.stem)]
header_guard_variable = '__'.join([x.upper() for x in include_parts]) + \
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_cpp/resource/idl__traits.hpp.em
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@# - content (IdlContent, list of elements, e.g. Messages or Services)
@#######################################################################
@{
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
include_parts = [package_name] + list(interface_path.parents[0].parts) + [
'detail', convert_camel_case_to_lower_case_underscore(interface_path.stem)]
include_base = '/'.join(include_parts)
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_cpp/resource/msg__struct.hpp.em
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ msvc_common_macros = ('DELETE', 'ERROR', 'NO_ERROR')
@# Collect necessary include directives for all members
@{
from collections import OrderedDict
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
includes = OrderedDict()
for member in message.structure.members:
type_ = member.type
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_cpp/resource/msg__traits.hpp.em
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ message_fully_qualified_name = '/'.join(message.structure.namespaced_type.namesp
@# Collect necessary include directives for all members
@{
from collections import OrderedDict
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
includes = OrderedDict()
for member in message.structure.members:
type_ = member.type
Expand Down
2 changes: 1 addition & 1 deletion rosidl_generator_cpp/rosidl_generator_cpp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

from ast import literal_eval

from rosidl_cmake import generate_files
from rosidl_parser.definition import AbstractGenericString
from rosidl_parser.definition import AbstractNestedType
from rosidl_parser.definition import AbstractSequence
Expand All @@ -26,6 +25,7 @@
from rosidl_parser.definition import FLOATING_POINT_TYPES
from rosidl_parser.definition import NamespacedType
from rosidl_parser.definition import UnboundedSequence
from rosidl_pycommon import generate_files


def generate_cpp(generator_arguments_file):
Expand Down
Loading

0 comments on commit b8381d9

Please sign in to comment.