Skip to content

Commit c9a3084

Browse files
frneerhidmicmergify[bot]christophebedard
authored
rosidl_cli: Add type description support (#857)
* Add type description support to rosidl_cli Signed-off-by: Francisco Rossi <[email protected]> * Fix typing annotation Signed-off-by: Francisco Rossi <[email protected]> * Please flake8, mypy Signed-off-by: Michel Hidalgo <[email protected]> * Please flake8 take 2 Signed-off-by: Michel Hidalgo <[email protected]> --------- Signed-off-by: Francisco Rossi <[email protected]> Signed-off-by: Michel Hidalgo <[email protected]> Co-authored-by: Michel Hidalgo <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Christophe Bedard <[email protected]>
1 parent b3d8446 commit c9a3084

File tree

15 files changed

+516
-98
lines changed

15 files changed

+516
-98
lines changed

rosidl_cli/rosidl_cli/cli.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717
from typing import Any, List, Union
1818

1919
from rosidl_cli.command.generate import GenerateCommand
20+
from rosidl_cli.command.hash import HashCommand
2021
from rosidl_cli.command.translate import TranslateCommand
2122
from rosidl_cli.common import get_first_line_doc
2223

2324

2425
def add_subparsers(
2526
parser: argparse.ArgumentParser,
2627
cli_name: str,
27-
commands: List[Union[GenerateCommand, TranslateCommand]]
28-
) -> argparse._SubParsersAction[argparse.ArgumentParser]:
28+
commands: List[Union[GenerateCommand, HashCommand, TranslateCommand]]
29+
) -> argparse._SubParsersAction:
2930
"""
3031
Create argparse subparser for each command.
3132
@@ -79,8 +80,8 @@ def main() -> Union[str, signal.Signals, Any]:
7980
formatter_class=argparse.RawDescriptionHelpFormatter
8081
)
8182

82-
commands: List[Union[GenerateCommand, TranslateCommand]] = \
83-
[GenerateCommand(), TranslateCommand()]
83+
commands: List[Union[GenerateCommand, TranslateCommand, HashCommand]] = \
84+
[GenerateCommand(), TranslateCommand(), HashCommand()]
8485

8586
# add arguments for command extension(s)
8687
add_subparsers(

rosidl_cli/rosidl_cli/command/generate/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
3939
'-ts', '--type-support', metavar='TYPESUPPORT',
4040
dest='typesupports', action='append', default=[],
4141
help='Target type supports for generation.')
42+
parser.add_argument(
43+
'-td', '--type-description-file', metavar='PATH',
44+
dest='type_description_files', action='append', default=[],
45+
help='Target type descriptions for generation.')
4246
parser.add_argument(
4347
'-I', '--include-path', type=pathlib.Path, metavar='PATH',
4448
dest='include_paths', action='append', default=[],
@@ -58,5 +62,6 @@ def main(self, *, args: argparse.Namespace) -> None:
5862
include_paths=args.include_paths,
5963
output_path=args.output_path,
6064
types=args.types,
61-
typesupports=args.typesupports
65+
typesupports=args.typesupports,
66+
type_description_files=args.type_description_files
6267
)

rosidl_cli/rosidl_cli/command/generate/api.py

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import inspect
1516
import os
1617
import pathlib
17-
from typing import List, Optional
18+
from typing import Any, Callable, Dict, List, Optional
1819

19-
from .extensions import GenerateCommandExtension
20-
from .extensions import load_type_extensions
21-
from .extensions import load_typesupport_extensions
20+
from .extensions import GenerateCommandExtension, load_type_extensions, load_typesupport_extensions
2221

2322

2423
def generate(
@@ -28,7 +27,8 @@ def generate(
2827
include_paths: Optional[List[str]] = None,
2928
output_path: Optional[pathlib.Path] = None,
3029
types: Optional[List[str]] = None,
31-
typesupports: Optional[List[str]] = None
30+
typesupports: Optional[List[str]] = None,
31+
type_description_files: Optional[List[str]] = None
3232
) -> List[List[str]]:
3333
"""
3434
Generate source code from interface definition files.
@@ -59,6 +59,7 @@ def generate(
5959
source code files, defaults to the current working directory
6060
:param types: optional list of type representations to generate
6161
:param typesupports: optional list of type supports to generate
62+
:param type_description_files: Optional list of paths to type description files
6263
:returns: list of lists of paths to generated source code files,
6364
one group per type or type support extension invoked
6465
"""
@@ -87,15 +88,39 @@ def generate(
8788
else:
8889
os.makedirs(output_path, exist_ok=True)
8990

90-
if len(extensions) > 1:
91-
return [
91+
def extra_kwargs(func: Callable, **kwargs: Any) -> Dict[str, Any]:
92+
matched_kwargs = {}
93+
signature = inspect.signature(func)
94+
for name, value in kwargs.items():
95+
if name in signature.parameters:
96+
if signature.parameters[name].kind not in [
97+
inspect.Parameter.POSITIONAL_ONLY,
98+
inspect.Parameter.VAR_POSITIONAL,
99+
inspect.Parameter.VAR_KEYWORD
100+
]:
101+
matched_kwargs[name] = value
102+
return matched_kwargs
103+
104+
generated_files = []
105+
if len(extensions) == 1:
106+
extension = extensions[0]
107+
generated_files.append(
92108
extension.generate(
93109
package_name, interface_files, include_paths,
94-
output_path=output_path / extension.name)
95-
for extension in extensions
96-
]
97-
98-
return [extensions[0].generate(
99-
package_name, interface_files,
100-
include_paths, output_path
101-
)]
110+
output_path=output_path,
111+
**extra_kwargs(extension.generate, type_description_files=type_description_files)
112+
)
113+
)
114+
else:
115+
for extension in extensions:
116+
generated_files.append(
117+
extension.generate(
118+
package_name, interface_files, include_paths,
119+
output_path=output_path / extension.name,
120+
**extra_kwargs(
121+
extension.generate,
122+
type_description_files=type_description_files
123+
)
124+
)
125+
)
126+
return generated_files

rosidl_cli/rosidl_cli/command/generate/extensions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def generate(
3232
package_name: str,
3333
interface_files: List[str],
3434
include_paths: List[str],
35-
output_path: Path
35+
output_path: Path,
36+
type_description_files: Optional[List[str]] = None
3637
) -> List[str]:
3738
"""
3839
Generate source code.
@@ -46,6 +47,7 @@ def generate(
4647
:param include_paths: list of paths to include dependency interface
4748
definition files from.
4849
:param output_path: path to directory to hold generated source code files
50+
:param type_description_files: Optional list of paths to type description files
4951
:returns: list of paths to generated source files
5052
"""
5153
raise NotImplementedError()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2021 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pathlib
16+
17+
from rosidl_cli.command import Command
18+
19+
from .api import generate_type_hashes
20+
21+
22+
class HashCommand(Command):
23+
"""Generate type description hashes from interface definition files."""
24+
25+
name = 'hash'
26+
27+
def add_arguments(self, parser):
28+
parser.add_argument(
29+
'-o', '--output-path', metavar='PATH',
30+
type=pathlib.Path, default=None,
31+
help=('Path to directory to hold generated '
32+
"source code files. Defaults to '.'."))
33+
parser.add_argument(
34+
'-I', '--include-path', type=pathlib.Path, metavar='PATH',
35+
dest='include_paths', action='append', default=[],
36+
help='Paths to include dependency interface definition files from.')
37+
parser.add_argument(
38+
'package_name', help='Name of the package to generate code for')
39+
parser.add_argument(
40+
'interface_files', metavar='interface_file', nargs='+',
41+
help=('Relative path to an interface definition file. '
42+
"If prefixed by another path followed by a colon ':', "
43+
'path resolution is performed against such path.'))
44+
45+
def main(self, *, args):
46+
generate_type_hashes(
47+
package_name=args.package_name,
48+
interface_files=args.interface_files,
49+
include_paths=args.include_paths,
50+
output_path=args.output_path,
51+
)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright 2021 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pathlib
16+
17+
from .extensions import load_hash_extensions
18+
19+
20+
def generate_type_hashes(
21+
*,
22+
package_name,
23+
interface_files,
24+
include_paths=None,
25+
output_path=None,
26+
):
27+
"""
28+
Generate type hashes from interface definition files.
29+
30+
To do so, this function leverages type description hash generation support
31+
as provided by third-party package extensions.
32+
33+
Each path to an interface definition file is a relative path optionally
34+
prefixed by another path followed by a colon ':', against which the first
35+
relative path is to be resolved.
36+
37+
The directory structure that these relative paths exhibit will be replicated
38+
on output (as opposed to the prefix path, which will be ignored).
39+
40+
:param package_name: name of the package to generate hashes for
41+
:param interface_files: list of paths to interface definition files
42+
:param include_paths: optional list of paths to include dependency
43+
interface definition files from
44+
:param output_path: optional path to directory to hold generated
45+
source code files, defaults to the current working directory
46+
:returns: list of lists of paths to generated hashed json files,
47+
one group per type or type support extension invoked
48+
"""
49+
extensions = []
50+
extensions.extend(load_hash_extensions())
51+
52+
if include_paths is None:
53+
include_paths = []
54+
if output_path is None:
55+
output_path = pathlib.Path.cwd()
56+
else:
57+
pathlib.Path.mkdir(output_path, parents=True, exist_ok=True)
58+
59+
generated_hashes = []
60+
for extension in extensions:
61+
generated_hashes.extend(extension.generate_type_hashes(
62+
package_name,
63+
interface_files,
64+
include_paths,
65+
output_path=output_path,
66+
))
67+
68+
return generated_hashes
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright 2021 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from rosidl_cli.extensions import Extension
16+
from rosidl_cli.extensions import load_extensions
17+
18+
19+
class HashCommandExtension(Extension):
20+
"""
21+
The extension point for source code generation.
22+
23+
The following methods must be defined:
24+
* `generate_type_hashes`
25+
"""
26+
27+
def generate_type_hashes(
28+
self,
29+
package_name,
30+
interface_files,
31+
include_paths,
32+
output_path,
33+
):
34+
"""
35+
Generate type hashes from interface definition files.
36+
37+
Paths to interface definition files are relative paths optionally
38+
prefixed by an absolute path followed by a colon ':', in which case
39+
path resolution is to be performed against that absolute path.
40+
41+
:param package_name: name of the package to generate source code for
42+
:param interface_files: list of paths to interface definition files
43+
:param include_paths: list of paths to include dependency interface
44+
definition files from.
45+
:param output_path: path to directory to hold generated source code files
46+
:returns: list of paths to generated source files
47+
"""
48+
raise NotImplementedError()
49+
50+
51+
def load_hash_extensions(**kwargs):
52+
"""Load extensions for type hash generation."""
53+
return load_extensions(
54+
'rosidl_cli.command.hash.extensions', **kwargs
55+
)

0 commit comments

Comments
 (0)