Skip to content

Commit 4134403

Browse files
committed
ci: enable standard ROS lint checks
Signed-off-by: Esteve Fernandez <esteve@apache.org>
1 parent 9281a6f commit 4134403

8 files changed

Lines changed: 108 additions & 31 deletions

File tree

rosidl_generator_rs/CMakeLists.txt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ if(BUILD_TESTING)
2424
APPEND_ENV PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}
2525
)
2626

27-
# The Python sources are checked by Ruff in CI. Copyright and docstring
28-
# linters are noisy for template files in this generator package.
29-
set(ament_cmake_copyright_FOUND TRUE)
30-
set(ament_cmake_flake8_FOUND TRUE)
31-
set(ament_cmake_pep257_FOUND TRUE)
3227
ament_lint_auto_find_test_dependencies()
3328
endif()
3429

rosidl_generator_rs/bin/rosidl_generator_rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
#!/usr/bin/env python3
2+
# Copyright 2018-2026 Esteve Fernandez <esteve@apache.org>
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""Command line entry point for the Rust ROS interface generator."""
217

318
import argparse
419
import os
@@ -16,13 +31,16 @@ except ImportError:
1631
raise
1732
from importlib.machinery import SourceFileLoader
1833

19-
loader = SourceFileLoader('rosidl_generator_rs', rosidl_generator_rs_module)
34+
loader = SourceFileLoader(
35+
'rosidl_generator_rs', rosidl_generator_rs_module)
2036
rosidl_generator_rs = loader.load_module()
2137
generate_rs = rosidl_generator_rs.generate_rs
2238

2339

2440
def main(argv=sys.argv[1:]):
25-
parser = argparse.ArgumentParser(description='Generate the Rust ROS interfaces.')
41+
"""Run the generator command line interface."""
42+
parser = argparse.ArgumentParser(
43+
description='Generate the Rust ROS interfaces.')
2644
parser.add_argument(
2745
'--generator-arguments-file',
2846
required=True,

rosidl_generator_rs/cmake/custom_command.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2017 Esteve Fernandez <esteve@apache.org>
1+
# Copyright 2018-2026 Esteve Fernandez <esteve@apache.org>
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.

rosidl_generator_rs/cmake/register_rs.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2016-2017 Esteve Fernandez <esteve@apache.org>
1+
# Copyright 2018-2026 Esteve Fernandez <esteve@apache.org>
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.

rosidl_generator_rs/cmake/rosidl_generator_rs_generate_interfaces.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2016-2017 Esteve Fernandez <esteve@apache.org>
1+
# Copyright 2018-2026 Esteve Fernandez <esteve@apache.org>
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.

rosidl_generator_rs/package.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
<buildtool_depend>ament_cmake</buildtool_depend>
1212
<buildtool_depend>ros_environment</buildtool_depend>
13-
13+
1414
<!-- <buildtool_depend>rosidl_runtime_rs</buildtool_depend> -->
1515

1616
<buildtool_export_depend>ament_cmake</buildtool_export_depend>
@@ -29,6 +29,9 @@
2929

3030
<test_depend>ament_cmake_gtest</test_depend>
3131
<test_depend>ament_cmake_pytest</test_depend>
32+
<test_depend>ament_cmake_copyright</test_depend>
33+
<test_depend>ament_cmake_flake8</test_depend>
34+
<test_depend>ament_cmake_pep257</test_depend>
3235
<test_depend>ament_lint_auto</test_depend>
3336
<test_depend>ament_lint_common</test_depend>
3437
<test_depend condition="humble >= $ROS_DISTRO">rosidl_cmake</test_depend>

rosidl_generator_rs/rosidl_generator_rs/__init__.py

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2016-2017 Esteve Fernandez <esteve@apache.org>
1+
# Copyright 2018-2026 Esteve Fernandez <esteve@apache.org>
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
"""Generate Rust code for ROS interfaces."""
16+
1517
import os
1618
import pathlib
1719

@@ -55,12 +57,15 @@ def _removesuffix(s, suffix):
5557

5658
package_name = ""
5759

60+
5861
# Taken from http://stackoverflow.com/a/6425628
5962
def convert_lower_case_underscore_to_camel_case(word):
63+
"""Convert a lower-case underscore name to CamelCase."""
6064
return ''.join(x.capitalize() or '_' for x in word.split('_'))
6165

6266

6367
def generate_rs(generator_arguments_file, typesupport_impls):
68+
"""Generate Rust code from rosidl generator arguments."""
6469
args = rosidl_pycommon.read_generator_arguments(generator_arguments_file)
6570

6671
global package_name
@@ -215,30 +220,38 @@ def generate_rs(generator_arguments_file, typesupport_impls):
215220

216221
return 0
217222

223+
218224
def get_rs_name(name):
225+
"""Return the Rust-safe spelling of a ROS field name."""
219226
keywords = [
220227
# strict keywords
221-
'as', 'break', 'const', 'continue', 'crate', 'else', 'enum', 'extern', 'false', 'fn', 'for', 'if', 'for',
222-
'impl', 'in', 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub', 'ref', 'return', 'self', 'Self', 'static',
223-
'struct', 'super', 'trait', 'true', 'type', 'unsafe', 'use', 'where', 'while',
228+
'as', 'break', 'const', 'continue', 'crate', 'else', 'enum',
229+
'extern', 'false', 'fn', 'for', 'if', 'for', 'impl', 'in', 'let',
230+
'loop', 'match', 'mod', 'move', 'mut', 'pub', 'ref', 'return',
231+
'self', 'Self', 'static', 'struct', 'super', 'trait', 'true',
232+
'type', 'unsafe', 'use', 'where', 'while',
224233
# Edition 2024+
225234
'gen',
226235
# Edition 2018+
227236
'async', 'await', 'dyn',
228237
# Reserved
229-
'abstract', 'become', 'box', 'do', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual',
238+
'abstract', 'become', 'box', 'do', 'final', 'macro', 'override',
239+
'priv', 'typeof', 'unsized', 'virtual',
230240
'yield', 'try'
231241
]
232242
# If the field name is a reserved keyword in Rust append an underscore
233243
return name if name not in keywords else name + '_'
234244

245+
235246
def escape_string(s):
247+
"""Escape a string literal for generated Rust code."""
236248
s = s.replace('\\', '\\\\')
237249
s = s.replace("'", "\\'")
238250
return s
239251

240252

241253
def value_to_rs(type_, value):
254+
"""Convert a rosidl default value to a Rust expression."""
242255
assert type_.is_primitive_type()
243256
assert value is not None
244257

@@ -253,6 +266,7 @@ def value_to_rs(type_, value):
253266

254267

255268
def primitive_value_to_rs(type_, value):
269+
"""Convert a primitive rosidl value to a Rust expression."""
256270
assert type_.is_primitive_type()
257271
assert value is not None
258272

@@ -285,6 +299,7 @@ def primitive_value_to_rs(type_, value):
285299

286300

287301
def constant_value_to_rs(type_, value):
302+
"""Convert a rosidl constant value to a Rust expression."""
288303
assert value is not None
289304

290305
if isinstance(type_, BasicType):
@@ -321,14 +336,20 @@ def constant_value_to_rs(type_, value):
321336

322337

323338
def pre_field_serde(type_):
339+
"""Return serde attributes needed before a generated Rust field."""
324340
if isinstance(type_, Array) and type_.size > 32:
325-
return '#[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))]\n '
341+
return (
342+
'#[cfg_attr(feature = "serde", '
343+
'serde(with = "serde_big_array::BigArray"))]\n '
344+
)
326345
else:
327346
return ''
328347

329348

330349
def make_get_rs_type(idiomatic):
350+
"""Create a function that converts rosidl types to Rust type names."""
331351
def get_rs_type(type_, current_idiomatic, desired_idiomatic):
352+
"""Convert a rosidl type to a Rust type name."""
332353
if isinstance(type_, BasicType):
333354
if type_.typename == 'boolean':
334355
return 'bool'
@@ -359,22 +380,41 @@ def get_rs_type(type_, current_idiomatic, desired_idiomatic):
359380
elif type_.typename == 'uint64':
360381
return 'u64'
361382
elif isinstance(type_, BoundedString):
362-
return 'rosidl_runtime_rs::BoundedString<{}>'.format(type_.maximum_size)
383+
return 'rosidl_runtime_rs::BoundedString<{}>'.format(
384+
type_.maximum_size)
363385
elif isinstance(type_, BoundedWString):
364-
return 'rosidl_runtime_rs::BoundedWString<{}>'.format(type_.maximum_size)
386+
return 'rosidl_runtime_rs::BoundedWString<{}>'.format(
387+
type_.maximum_size)
365388
elif isinstance(type_, UnboundedString):
366-
return 'std::string::String' if current_idiomatic and desired_idiomatic else 'rosidl_runtime_rs::String'
389+
if current_idiomatic and desired_idiomatic:
390+
return 'std::string::String'
391+
return 'rosidl_runtime_rs::String'
367392
elif isinstance(type_, UnboundedWString):
368-
return 'std::string::String' if current_idiomatic and desired_idiomatic else 'rosidl_runtime_rs::WString'
393+
if current_idiomatic and desired_idiomatic:
394+
return 'std::string::String'
395+
return 'rosidl_runtime_rs::WString'
369396
elif isinstance(type_, Array):
370-
return f'[{get_rs_type(type_.value_type, current_idiomatic, desired_idiomatic)}; {type_.size}]'
397+
nested_type = get_rs_type(
398+
type_.value_type, current_idiomatic, desired_idiomatic)
399+
return f'[{nested_type}; {type_.size}]'
371400
elif isinstance(type_, UnboundedSequence):
372-
container_type = 'Vec' if current_idiomatic and desired_idiomatic else 'rosidl_runtime_rs::Sequence'
373-
return f'{container_type}<{get_rs_type(type_.value_type, current_idiomatic, desired_idiomatic)}>'
401+
if current_idiomatic and desired_idiomatic:
402+
container_type = 'Vec'
403+
else:
404+
container_type = 'rosidl_runtime_rs::Sequence'
405+
nested_type = get_rs_type(
406+
type_.value_type, current_idiomatic, desired_idiomatic)
407+
return f'{container_type}<{nested_type}>'
374408
elif isinstance(type_, BoundedSequence):
375-
# BoundedSequences can be in the idiomatic API, but the containing type cannot be from the
376-
# idiomatic API because we do not implement SequenceAlloc for idiomatic types.
377-
return f'rosidl_runtime_rs::BoundedSequence<{get_rs_type(type_.value_type, current_idiomatic, False)}, {type_.maximum_size}>'
409+
# BoundedSequences can be in the idiomatic API, but the
410+
# containing type cannot be from the idiomatic API because we do
411+
# not implement SequenceAlloc for idiomatic types.
412+
nested_type = get_rs_type(
413+
type_.value_type, current_idiomatic, False)
414+
return (
415+
'rosidl_runtime_rs::BoundedSequence<'
416+
f'{nested_type}, {type_.maximum_size}>'
417+
)
378418
elif isinstance(type_, NamespacedType):
379419
# All types should be referencable like this
380420
# `super::msg::rmw::Foo` (From idiomatic modules)
@@ -384,8 +424,9 @@ def get_rs_type(type_, current_idiomatic, desired_idiomatic):
384424

385425
symbol = f'{prefix}{"::".join(type_.namespaced_name()[1:])}'
386426

387-
# This symbol is coming from an external crate (or needs a `use` statement).
388-
# So it should not be relative (i.e., no `super::`) and should have the top level
427+
# This symbol is coming from an external crate (or needs a `use`
428+
# statement). So it should not be relative (i.e., no `super::`)
429+
# and should have the top level
389430
# package name (i.e., `builtin_interfaces::`)
390431
top_level_package = type_.namespaces[0]
391432
if top_level_package != package_name:
@@ -400,6 +441,7 @@ def get_rs_type(type_, current_idiomatic, desired_idiomatic):
400441

401442
assert False, "unknown type '%s'" % type_.typename
402443

403-
# Start out by assuming all calls have matching current and desired idiomatic values.
404-
# (i.e. symbols within the `...::rmw` scope want other values in the `...::rmw` scope).
444+
# Start out by assuming all calls have matching current and desired
445+
# idiomatic values. Symbols within the `...::rmw` scope want other values
446+
# in the `...::rmw` scope.
405447
return lambda _type: get_rs_type(_type, idiomatic, idiomatic)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2018-2026 Esteve Fernandez <esteve@apache.org>
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""Tests for Rust keyword escaping."""
17+
118
from rosidl_generator_rs import get_rs_name
219

320

421
def test_rust_keywords_are_escaped():
22+
"""Check that Rust keywords get a trailing underscore."""
523
for keyword in ("try", "type", "const", "async", "match"):
624
assert get_rs_name(keyword) == f"{keyword}_"
725

826

927
def test_non_keyword_names_are_unchanged():
28+
"""Check that non-keyword names are unchanged."""
1029
assert get_rs_name("plain_field") == "plain_field"

0 commit comments

Comments
 (0)