From 71d3f8240857277511e56bd0b96798f874e60e1a Mon Sep 17 00:00:00 2001 From: ipa-nhg Date: Mon, 26 Feb 2024 17:29:24 +0100 Subject: [PATCH] [WIP] Refactor --- .../docker-compose.yml | 0 {foxy => docker/foxy}/Dockerfile | 0 {foxy => docker/foxy}/README.md | 0 haros_runner.sh => docker/haros_runner.sh | 5 +- {humble => docker/humble}/Dockerfile | 14 +- {humble => docker/humble}/README.md | 0 {melodic => docker/melodic}/Dockerfile | 0 {melodic => docker/melodic}/README.md | 0 {noetic => docker/noetic}/Dockerfile | 16 +- {noetic => docker/noetic}/README.md | 0 {ssh_config => docker/ssh_config}/dummy_ssh | 0 haros_runner.py | 44 ------ .../generate_messages_model_helper.sh | 0 .../messages_generator_runner.sh | 0 test.sh => helper_scripts/test.sh | 0 .../ros_code_analysis/__init__.py | 0 .../ros_code_analysis/ros1_cpp_extractor.py | 140 ++++++++++++++++++ .../ros_code_analysis/ros2_cpp_extractor.py | 94 ++++++++++++ .../ros_code_analysis/ros_common_extractor.py | 37 +++++ .../ros_code_analysis/ros_model_extractor.py | 112 ++++---------- ros_code_analysis/setup.py | 35 +++++ 21 files changed, 353 insertions(+), 144 deletions(-) rename docker-compose.yml => docker/docker-compose.yml (100%) rename {foxy => docker/foxy}/Dockerfile (100%) rename {foxy => docker/foxy}/README.md (100%) rename haros_runner.sh => docker/haros_runner.sh (87%) rename {humble => docker/humble}/Dockerfile (89%) rename {humble => docker/humble}/README.md (100%) rename {melodic => docker/melodic}/Dockerfile (100%) rename {melodic => docker/melodic}/README.md (100%) rename {noetic => docker/noetic}/Dockerfile (86%) rename {noetic => docker/noetic}/README.md (100%) rename {ssh_config => docker/ssh_config}/dummy_ssh (100%) delete mode 100644 haros_runner.py rename generate_messages_model_helper.sh => helper_scripts/generate_messages_model_helper.sh (100%) rename messages_generator_runner.sh => helper_scripts/messages_generator_runner.sh (100%) rename test.sh => helper_scripts/test.sh (100%) create mode 100644 ros_code_analysis/ros_code_analysis/__init__.py create mode 100755 ros_code_analysis/ros_code_analysis/ros1_cpp_extractor.py create mode 100755 ros_code_analysis/ros_code_analysis/ros2_cpp_extractor.py create mode 100755 ros_code_analysis/ros_code_analysis/ros_common_extractor.py rename ros_model_extractor.py => ros_code_analysis/ros_code_analysis/ros_model_extractor.py (69%) create mode 100755 ros_code_analysis/setup.py diff --git a/docker-compose.yml b/docker/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to docker/docker-compose.yml diff --git a/foxy/Dockerfile b/docker/foxy/Dockerfile similarity index 100% rename from foxy/Dockerfile rename to docker/foxy/Dockerfile diff --git a/foxy/README.md b/docker/foxy/README.md similarity index 100% rename from foxy/README.md rename to docker/foxy/README.md diff --git a/haros_runner.sh b/docker/haros_runner.sh similarity index 87% rename from haros_runner.sh rename to docker/haros_runner.sh index 012359b..b0e3834 100755 --- a/haros_runner.sh +++ b/docker/haros_runner.sh @@ -95,10 +95,9 @@ then then if [ "${2}" = "--all" ] then - python3 /call_generator.py - python3 /ros_model_extractor.py --clang-version $clang_version --package "$1" --"${3}" --model-path "${4}" --ws "${5}" --path-to-src "$path_to_src_code" --repo $model_repo -a >> ${4}/extractor.log + python3 /ros_code_analysis/ros_model_extractor.py --clang-version $clang_version --package "$1" --"${3}" --model-path "${4}" --ws "${5}" --path-to-src "$path_to_src_code" --repo $model_repo -a >> ${4}/extractor.log else - python3 /ros_model_extractor.py --clang-version $clang_version --package "$1" --name "$2" --"${3}" --model-path "${4}" --ws "${5}" --path-to-src "$path_to_src_code" --repo $model_repo>> ${4}/extractor.log + python3 /ros_code_analysis/ros_model_extractor.py --clang-version $clang_version --package "$1" --name "$2" --"${3}" --model-path "${4}" --ws "${5}" --path-to-src "$path_to_src_code" --repo $model_repo>> ${4}/extractor.log fi else echo "Python version not supported" diff --git a/humble/Dockerfile b/docker/humble/Dockerfile similarity index 89% rename from humble/Dockerfile rename to docker/humble/Dockerfile index ed83c7d..843108f 100644 --- a/humble/Dockerfile +++ b/docker/humble/Dockerfile @@ -42,7 +42,6 @@ RUN echo "extractor ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/extra USER extractor RUN mkdir -p /home/extractor/ws/src #RUN git clone https://github.com/ipa320/ros2model.git /home/extractor/ws/src/ros2model -RUN echo "test" RUN git clone https://github.com/ipa-nhg/ros2model.git -b PythonInstallTemplatesFolder /home/extractor/ws/src/ros2model RUN mkdir -p /home/extractor/results @@ -58,7 +57,7 @@ RUN if [ $enable_ssh ] ; then mkdir -p /home/extractor/.ssh/ && \ touch /home/extractor/.ssh/config && \ chmod 600 /home/extractor/.ssh/config ; fi -COPY --chown=extractor:root ssh_config/ /keys/ +COPY --chown=extractor:root docker/ssh_config/ /keys/ RUN if [ $enable_ssh ] ; then cat /keys/ssh_key.pub >> /home/extractor/.ssh/authorized_keys ; fi RUN if [ $enable_ssh ] ; then cat /keys/config >> /home/extractor/.ssh/config ; fi #### @@ -75,13 +74,12 @@ ENV PYTHON_VERSION 3 RUN echo 'source /home/extractor/ws/install/setup.bash' >> /home/extractor/.bashrc RUN echo "test" -COPY ${path_to_scripts}messages_generator_runner.sh / -COPY ${path_to_scripts}generate_messages_model_helper.sh / -COPY ${path_to_scripts}haros_runner.sh / -COPY ${path_to_scripts}ros_model_extractor.py / -COPY ${path_to_scripts}call_generator.py / +#COPY ${path_to_scripts}messages_generator_runner.sh / +#COPY ${path_to_scripts}generate_messages_model_helper.sh / +COPY docker/haros_runner.sh / +COPY ros_code_analysis / -COPY ${path_to_scripts}test.sh / +COPY helper_scripts/test.sh / EXPOSE 4005 #CMD sudo chown -R extractor:extractor /home/extractor/results diff --git a/humble/README.md b/docker/humble/README.md similarity index 100% rename from humble/README.md rename to docker/humble/README.md diff --git a/melodic/Dockerfile b/docker/melodic/Dockerfile similarity index 100% rename from melodic/Dockerfile rename to docker/melodic/Dockerfile diff --git a/melodic/README.md b/docker/melodic/README.md similarity index 100% rename from melodic/README.md rename to docker/melodic/README.md diff --git a/noetic/Dockerfile b/docker/noetic/Dockerfile similarity index 86% rename from noetic/Dockerfile rename to docker/noetic/Dockerfile index 5569b41..e01b2f1 100644 --- a/noetic/Dockerfile +++ b/docker/noetic/Dockerfile @@ -26,10 +26,11 @@ ENV LD_LIBRARY_PATH $LD_LIBRARY_PATH:/usr/lib/llvm-10/lib RUN pip3 install --upgrade pip RUN pip3 install -Iv clang==10.0.1 RUN pip3 install -e git+https://github.com/timtadh/pyflwor.git#egg=pyflwor -RUN pip3 install -e git+https://github.com/ipa320/ros_model_parser.git#egg=ros_model_parser +#RUN pip3 install -e git+https://github.com/ipa320/ros_model_parser.git#egg=ros_model_parser RUN pip3 install -e git+https://github.com/git-afsantos/bonsai#egg=bonsai-code RUN pip3 install -e git+https://github.com/ipa-nhg/haros@FixPythonExtractTopic#egg=haros # RUN pip3 install -e git+https://github.com/git-afsantos/haros#egg=haros +RUN pip3 install -e git+https://github.com/ipa320/ros2model.git#egg=ros2model RUN apt-get update && apt-get install -y ros-noetic-desktop && apt upgrade -y @@ -56,7 +57,7 @@ RUN if [ $enable_ssh ] ; then mkdir -p /home/extractor/.ssh/ && \ touch /home/extractor/.ssh/config && \ chmod 600 /home/extractor/.ssh/config ; fi -COPY --chown=extractor:root ssh_config/ /keys/ +COPY --chown=extractor:root docker/ssh_config/ /keys/ RUN if [ $enable_ssh ] ; then cat /keys/ssh_key.pub >> /home/extractor/.ssh/authorized_keys ; fi RUN if [ $enable_ssh ] ; then cat /keys/config >> /home/extractor/.ssh/config ; fi #### @@ -75,11 +76,12 @@ RUN source /opt/ros/$ROS_DISTRO/setup.bash;\ ENV PYTHON_VERSION 3 RUN echo 'source /home/extractor/ws/devel/setup.bash' >> /home/extractor/.bashrc -COPY ${path_to_scripts}messages_generator_runner.sh / -COPY ${path_to_scripts}generate_messages_model_helper.sh / -COPY ${path_to_scripts}haros_runner.sh / -COPY ${path_to_scripts}ros_model_extractor.py / -COPY ${path_to_scripts}test.sh / +#COPY ${path_to_scripts}messages_generator_runner.sh / +#COPY ${path_to_scripts}generate_messages_model_helper.sh / +COPY docker/haros_runner.sh / +COPY ros_code_analysis / + +COPY helper_scripts/test.sh / EXPOSE 4004 #CMD sudo chown -R extractor:extractor /home/extractor/results diff --git a/noetic/README.md b/docker/noetic/README.md similarity index 100% rename from noetic/README.md rename to docker/noetic/README.md diff --git a/ssh_config/dummy_ssh b/docker/ssh_config/dummy_ssh similarity index 100% rename from ssh_config/dummy_ssh rename to docker/ssh_config/dummy_ssh diff --git a/haros_runner.py b/haros_runner.py deleted file mode 100644 index 8c43c25..0000000 --- a/haros_runner.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -import sys -import os -import subprocess -from datetime import datetime -import shutil -import glob - -from rosHumble_model_extractor_scripts.ros2runner import ros2Runner - -# Arguments: -# 1: Package name or Git repository name -# 2: Node name or launch file name or '--all' to analyse all the available nodes -# 3: Type of the request: either 'launch' or 'node' -# 4: Path to the folder where the resulting model files should be stored -# 5: Path to the ROS workspace -# 6: Http address links of the Git repositories (to indicate the branch put the name of the repository between quotes and add the suffix -b *banch_name*, for example "https://github.com/ipa320/ros-model-extractors b main" - -def main(): - - CLI_args = sys.argv[1:] - - if len(CLI_args) < 6: - print(f'ERROR: At least 6 arguments expected, only {len(CLI_args)} are given.') - sys.exit() - - pkgName = sys.argv[1] - NodeName = sys.argv[2] - typeOfRequest = sys.argv[3] - pathToOutput = sys.argv[4] - pathToROSws = sys.argv[5] - gitRepo = sys.argv[6] - - ros2Extractor = ros2Runner(inputPkgName=pkgName, - inputNodeName=NodeName, - typeOfRequest=typeOfRequest, - pathToROSws=pathToROSws, - gitRepo=gitRepo, - outputDir=pathToOutput) - ros2Extractor.extractorRun(True) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/generate_messages_model_helper.sh b/helper_scripts/generate_messages_model_helper.sh similarity index 100% rename from generate_messages_model_helper.sh rename to helper_scripts/generate_messages_model_helper.sh diff --git a/messages_generator_runner.sh b/helper_scripts/messages_generator_runner.sh similarity index 100% rename from messages_generator_runner.sh rename to helper_scripts/messages_generator_runner.sh diff --git a/test.sh b/helper_scripts/test.sh similarity index 100% rename from test.sh rename to helper_scripts/test.sh diff --git a/ros_code_analysis/ros_code_analysis/__init__.py b/ros_code_analysis/ros_code_analysis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ros_code_analysis/ros_code_analysis/ros1_cpp_extractor.py b/ros_code_analysis/ros_code_analysis/ros1_cpp_extractor.py new file mode 100755 index 0000000..de1d898 --- /dev/null +++ b/ros_code_analysis/ros_code_analysis/ros1_cpp_extractor.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# +# Copyright 2024 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA) +# +# Nadia Hammoudeh Garcia +# +# 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 bonsai.analysis import CodeQuery, resolve_expression +from ros_common_extractor import RosCommonExtractor +import ros2model.core.metamodels.metamodel_ros as RosModelMetamodel + +class Ros1CppExtractor(): + def extract_primitives(node, parser, analysis): + gs = parser.global_scope + node.source_tree = parser.global_scope + publishers=[] + subscribers=[] + serviceservers=[] + serviceclients=[] + actionservers=[] + actionclients=[] + parameters=[] + for call in (CodeQuery(gs).all_calls.get()): + for call in (CodeQuery(gs).all_calls.where_name("SimpleActionServer").get()): + if len(call.arguments) > 0: + name = analysis._extract_action(call) + action_type = analysis._extract_action_type(call).split("_<",1)[0] + if name!="?" or act_type!="?": + acs = RosModelMetamodel.ActionServer(name=name,type=act_type.replace(".action","")) + actionservers.append(acs) + for call in (CodeQuery(gs).all_calls.where_name("SimpleActionClient").get()): + if len(call.arguments) > 0: + name = analysis._extract_action(call) + action_type = analysis._extract_action_type(call).split("_<",1)[0] + if name!="?" or act_type!="?": + ac = RosModelMetamodel.ActionServer(name=name,type=act_type.replace(".action","")) + actionclients.append(ac) + for call in (CodeQuery(gs).all_calls.where_name("advertise").where_result("ros::Publisher").get()): + if len(call.arguments) > 1: + name = analysis._extract_topic(call, topic_pos=0) + msg_type = analysis._extract_message_type(call) + queue_size = analysis._extract_queue_size(call, queue_pos=1) + if name!="?" or msg_type!="?": + pub = RosModelMetamodel.Publisher(name=name,type=msg_type.replace(".msg","")) + publishers.append(pub) + for call in (CodeQuery(gs).all_calls.where_name("subscribe").where_result("ros::Subscriber").get()): + if len(call.arguments) > 1: + name = analysis._extract_topic(call, topic_pos=0) + msg_type = analysis._extract_message_type(call) + queue_size = analysis._extract_queue_size(call, queue_pos=1) + if name!="?" or msg_type!="?": + sub = RosModelMetamodel.Subscriber(name=name,type=msg_type.replace(".msg","")) + subscribers.append(sub) + for call in (CodeQuery(gs).all_calls.where_name("advertiseService").where_result("ros::ServiceServer").get()): + if len(call.arguments) > 1: + name = analysis._extract_topic(call) + srv_type = analysis._extract_message_type(call) + if name!="?" or srv_type!="?": + ss = RosModelMetamodel.ServiceServer(name=name,type=srv_type.replace(".srv","").replace("Request","")) + serviceservers.append(ss) + for call in (CodeQuery(gs).all_calls.where_name("serviceClient").where_result("ros::ServiceClient").get()): + if len(call.arguments) > 1: + name = analysis._extract_topic(call) + srv_type = analysis._extract_message_type(call) + if name!="?" or srv_type!="?": + sc = RosModelMetamodel.ServiceClient(name=name,type=srv_type.replace(".srv","").replace("Response","")) + serviceclients.append(sc) + + #PARAMETERS nhg:this needs review + nh_prefix = "c:@N@ros@S@NodeHandle@" + gets = ("getParam", "getParamCached", "param") + reads = gets + ("hasParam", "searchParam") + sets = ("setParam",) + writes = sets + ("deleteParam",) + for call in CodeQuery(gs).all_calls.where_name(reads).get(): + if (call.full_name.startswith("ros::NodeHandle") or (isinstance(call.reference, str) and call.reference.startswith(nh_prefix))): + param_type = default_value = None + param_name = analysis._extract_topic(call) + if call.name in gets: + param_type = RosCommonExtractor.transform_type(analysis._extract_param_type(call.arguments[1])) + if call.name == "param": + if len(call.arguments) > 2: + default_value = analysis._extract_param_value( call, arg_pos=2) + elif len(call.arguments) == 2: + default_value = analysis._extract_param_value( call, arg_pos=1) + if not ((default_value is None or default_value == "") and param_type is None): + param = RosModelMetamodel.Parameter(name=param_name,type=param_type,value=default_value) + parameters.append(param) + + for call in CodeQuery(gs).all_calls.where_name(writes).get(): + if (call.full_name.startswith("ros::NodeHandle") or (isinstance(call.reference, str) and call.reference.startswith(nh_prefix))): + param_type = value = None + param_name = analysis._extract_topic(call) + if len(call.arguments) >= 2 and call.name in sets: + param_type = RosCommonExtractor.transform_type(analysis._extract_param_type(call.arguments[1])) + value = analysis._extract_param_value(call, arg_pos=1) + if not ((default_value is None or default_value == "") and param_type is None): + param = RosModelMetamodel.Parameter(name=param_name,type=param_type,value=default_value) + parameters.append(param) + ros_prefix = "c:@N@ros@N@param@" + gets = ("get", "getCached", "param") + reads = gets + ("has",) + sets = ("set",) + writes = sets + ("del",) + for call in CodeQuery(gs).all_calls.where_name(reads).get(): + if (call.full_name.startswith("ros::param") or (isinstance(call.reference, str) and call.reference.startswith(ros_prefix))): + param_type = default_value = None + param_name = analysis._extract_topic(call) + if call.name == "param": + if call.name in gets: + param_type = RosCommonExtractor.transform_type(analysis._extract_param_type(call.arguments[1])) + if len(call.arguments) > 2: + default_value = analysis._extract_param_value(call, arg_pos=2) + elif len(call.arguments) == 2: + default_value = analysis._extract_param_value(call, arg_pos=1) + if not ((default_value is None or default_value == "") and param_type is None): + param = RosModelMetamodel.Parameter(name=param_name,type=param_type,value=default_value) + parameters.append(param) + for call in CodeQuery(gs).all_calls.where_name(writes).get(): + if (call.full_name.startswith("ros::param") or (isinstance(call.reference, str) and call.reference.startswith(ros_prefix))): + param_type = value = None + if len(call.arguments) >= 2 and call.name in sets: + param_type = RosCommonExtractor.transform_type(analysis._extract_param_type(call.arguments[1])) + value = analysis._extract_param_value(call, arg_pos=1) + param_name = analysis._extract_topic(call) + if not ((default_value is None or default_value == "") and param_type is None): + param = RosModelMetamodel.Parameter(name=param_name,type=param_type,value=default_value) + parameters.append(param) + return publishers, subscribers, serviceservers, serviceclients, actionservers, actionclients, parameters \ No newline at end of file diff --git a/ros_code_analysis/ros_code_analysis/ros2_cpp_extractor.py b/ros_code_analysis/ros_code_analysis/ros2_cpp_extractor.py new file mode 100755 index 0000000..1b9311d --- /dev/null +++ b/ros_code_analysis/ros_code_analysis/ros2_cpp_extractor.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# +# Copyright 2024 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA) +# +# Nadia Hammoudeh Garcia +# +# 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 bonsai.analysis import CodeQuery, resolve_expression +from ros_common_extractor import RosCommonExtractor +import ros2model.core.metamodels.metamodel_ros as RosModelMetamodel + +class Ros2CppExtractor(): + def extract_primitives(node, parser, analysis): + gs = parser.global_scope + node.source_tree = parser.global_scope + publishers=[] + subscribers=[] + serviceservers=[] + serviceclients=[] + actionservers=[] + actionclients=[] + parameters=[] + for call in (CodeQuery(gs).all_calls.get()): + if "Publisher" in str(call): + if len(call.arguments) > 1: + name = analysis._extract_topic(call, topic_pos=0) + msg_type = analysis._extract_message_type(call) + queue_size = analysis._extract_queue_size(call, queue_pos=1) + if name!="?" or msg_type!="?": + pub = RosModelMetamodel.Publisher(name=name,type=msg_type.replace(".msg","")) + publishers.append(pub) + if "Subscription" in str(call): # Subscription or Subscriber? + if len(call.arguments) > 1: + name = analysis._extract_topic(call, topic_pos=0) + msg_type = analysis._extract_message_type(call) + queue_size = analysis._extract_queue_size(call, queue_pos=1) + if name!="?" or msg_type!="?": + sub = RosModelMetamodel.Subscriber(name=name,type=msg_type.replace(".msg","")) + subscribers.append(sub) + if "Service" in str(call) and "::srv::" in str(call): #or? + if len(call.arguments) > 1: + name = analysis._extract_topic(call, topic_pos=0) + srv_type = analysis._extract_message_type(call) + queue_size = analysis._extract_queue_size(call, queue_pos=1) + if name!="?" or srv_type!="?": + ss = RosModelMetamodel.ServiceServer(name=name,type=srv_type.replace(".srv","")) + serviceservers.append(ss) + if "Client" in str(call) and "::srv::" in str(call): + if len(call.arguments) > 1: + name = analysis._extract_topic(call, topic_pos=0) + srv_type = analysis._extract_message_type(call) + queue_size = analysis._extract_queue_size(call, queue_pos=1) + if name!="?" or srv_type!="?": + sc = RosModelMetamodel.ServiceClient(name=name,type=srv_type.replace(".srv","")) + serviceclients.append(sc) + if "Server" in str(call) and "::action::" in str(call): + if len(call.arguments) > 1: + name = analysis._extract_topic(call, topic_pos=0) + act_type = analysis._extract_message_type(call) + queue_size = analysis._extract_queue_size(call, queue_pos=1) + if name!="?" or act_type!="?": + acs = RosModelMetamodel.ActionServer(name=name,type=act_type.replace(".action","")) + actionservers.append(acs) + if "Client" in str(call) and "::action::" in str(call): + if len(call.arguments) > 1: + name = analysis._extract_topic(call, topic_pos=0) + act_type = analysis._extract_message_type(call) + queue_size = analysis._extract_queue_size(call, queue_pos=1) + if name!="?" or act_type!="?": + ac = RosModelMetamodel.ActionServer(name=name,type=act_type.replace(".action","")) + actionclients.append(ac) + if "declare_parameter" in str(call): + if len(call.arguments) > 1: + name = analysis._extract_topic(call, topic_pos=0) + default_value = resolve_expression(call.arguments[1]) + param_type = RosCommonExtractor.transform_type(resolve_expression(call)) + if not default_value: + param = RosModelMetamodel.Parameter(name=name,type=param_type) + else: + param = RosModelMetamodel.Parameter(name=name,type=param_type,value=str(default_value)) + parameters.append(param) + + return publishers, subscribers, serviceservers, serviceclients, actionservers, actionclients, parameters \ No newline at end of file diff --git a/ros_code_analysis/ros_code_analysis/ros_common_extractor.py b/ros_code_analysis/ros_code_analysis/ros_common_extractor.py new file mode 100755 index 0000000..8934db7 --- /dev/null +++ b/ros_code_analysis/ros_code_analysis/ros_common_extractor.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# +# Copyright 2024 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA) +# +# Nadia Hammoudeh Garcia +# +# 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. + +class RosCommonExtractor(): + def transform_type(param_type): + #if os.environ.get("ROS_VERSION") == "2": + param_type=str(param_type) + param_type=param_type[param_type.find("[")+1:param_type.find("]")] + if param_type == 'double': + return 'Double' + elif param_type == 'bool': + return 'Boolean' + elif param_type == 'int' or param_type == 'long': + return 'Integer' + elif (param_type == 'str' or 'basic_string' in param_type or param_type == 'std::string'): + return 'String' + #elif param_type == 'yaml': + #return 'Struct' + #elif 'std::vector' in param_type: + #return 'List' + else: + return None \ No newline at end of file diff --git a/ros_model_extractor.py b/ros_code_analysis/ros_code_analysis/ros_model_extractor.py similarity index 69% rename from ros_model_extractor.py rename to ros_code_analysis/ros_code_analysis/ros_model_extractor.py index b8ab0e7..dab4828 100755 --- a/ros_model_extractor.py +++ b/ros_code_analysis/ros_code_analysis/ros_model_extractor.py @@ -30,11 +30,14 @@ from haros.launch_parser import LaunchParser, LaunchParserError, NodeTag from haros.cmake_parser import RosCMakeParser from bonsai.analysis import CodeQuery, resolve_expression +from ros2_cpp_extractor import Ros2CppExtractor +from ros1_cpp_extractor import Ros1CppExtractor + try: - from bonsai.cpp.clang_parser import CppAstParser + from bonsai.cpp.clang_parser import CppAstParser except ImportError: - CppAstParser = None + CppAstParser = None from bonsai.py.py_parser import PyAstParser class RosExtractor(): @@ -64,11 +67,12 @@ def extract_node(self, name, node_name, pkg_name, ns, ws, rossystem): self.pkg.path= self.args.path_to_src self.pkg_type="AmentPackage" roscomponent = None - #HAROS NODE EXTRACTOR + #HAROS NODE EXTRACTOR srcdir = self.pkg.path[len(ws):] srcdir = os.path.join(ws, srcdir.split(os.sep, 1)[0]) bindir = os.path.join(ws, "build") + #HAROS CMAKE PARSER parser = RosCMakeParser(srcdir, bindir, pkgs = [self.pkg]) model_str = "" @@ -104,7 +108,6 @@ def extract_node(self, name, node_name, pkg_name, ns, ws, rossystem): if node.language == "python": parser = PyAstParser(workspace = ws) analysis = RospyExtractor(self.pkg, ws) - #node.source_tree = parser.global_scope for sf in node.source_files: try: if parser.parse(sf.path) is not None: @@ -113,16 +116,36 @@ def extract_node(self, name, node_name, pkg_name, ns, ws, rossystem): node_name=node_name.replace(".py","") graph_name = RosModelMetamodel.GraphName(name=node_name, namespace="", full_name=node_name) try: - publishers = self.extract_primitives(node, parser, analysis, roscomponent, pkg_name, node_name, node_name) - #, subscribers, serviceservers, serviceclients, actionservers, actionclients, parameters = self.extract_primitives(node, parser, analysis, roscomponent, pkg_name, node_name, node_name) + if os.environ.get("ROS_VERSION") == "1": + if (node.language=="cpp"): + [publishers, subscribers, + serviceservers, serviceclients, + actionservers, actionclients, + parameters] = Ros1CppExtractor.extract_primitives(node, parser, analysis) + elif (node.language=="python"): + print("ROS1 python") + elif os.environ.get("ROS_VERSION") == "2": + if (node.language=="cpp"): + [publishers, subscribers, + serviceservers, serviceclients, + actionservers, actionclients, + parameters] = Ros2CppExtractor.extract_primitives(node, parser, analysis) + elif (node.language=="python"): + print("ROS2 python") RosModel_node=RosModelMetamodel.Node(name=graph_name, - publisher=publishers) + publisher=publishers, subscriber=subscribers, + serviceserver=serviceservers, serviceclient=serviceclients, + actionserver=actionservers, actionclient=actionclients, + parameter=parameters + ) except error: print("Interfaces not found") print(error) RosModel_node=RosModelMetamodel.Node(name=graph_name) RosModel_artifact=RosModelMetamodel.Artifact(name=node_name, node=[RosModel_node]) RosModel_package=RosModelMetamodel.Package(name=self.args.package_name, artifact=[RosModel_artifact]) + print(RosModel_package) + #Model file generator node_generator = ComponentGenerator() node_generator.generate_a_file( model=RosModel_package, @@ -138,51 +161,6 @@ def extract_node(self, name, node_name, pkg_name, ns, ws, rossystem): if self.args.output: print(model_str) - def transform_type(self, param_type): - if os.environ.get("ROS_VERSION") == "2": - param_type=str(param_type) - param_type=param_type[param_type.find("[")+1:param_type.find("]")] - if param_type == 'double': - return 'Double' - elif param_type == 'bool': - return 'Boolean' - elif param_type == 'int' or param_type == 'long': - return 'Integer' - elif (param_type == 'str' or 'basic_string' in param_type or param_type == 'std::string'): - return 'String' - #elif param_type == 'yaml': - #return 'Struct' - #elif 'std::vector' in param_type: - #return 'List' - else: - return None - - - def extract_primitives(self, node, parser, analysis, roscomponent, pkg_name, node_name, art_name): - gs = parser.global_scope - node.source_tree = parser.global_scope - publishers=[] - subscribers=[] - erviceservers=[] - serviceclients=[] - actionservers=[] - actionclients=[] - parameters=[] - if os.environ.get("ROS_VERSION") == "2": - #ROS2 - if node.language == "cpp": - for call in (CodeQuery(gs).all_calls.get()): - if "Publisher" in str(call): - if len(call.arguments) > 1: - name = analysis._extract_topic(call, topic_pos=0) - msg_type = analysis._extract_message_type(call) - queue_size = analysis._extract_queue_size(call, queue_pos=1) - if name!="?" or msg_type!="?": - pub = RosModelMetamodel.Publisher(name=name,type=msg_type.replace("/",".").replace(".msg","")) - publishers.append(pub) - return publishers#, subscribers, serviceservers, serviceclients, actionservers, actionclients, parameters - - def parse_arg(self): parser = argparse.ArgumentParser() mutually_exclusive = parser.add_mutually_exclusive_group() @@ -201,36 +179,6 @@ def parse_arg(self): parser.add_argument('--clang-version', required=True, dest='clang_version') self.args = parser.parse_args() - -class RosInterface: - def __init__(self, name, ref): - self.name = name - self.ref = ref - -class ros_component: - def __init__(self, name, ns): - self.name = ns+name if ns else name - self.ns = ns - self.pubs = [] - self.subs = [] - self.srvsrvs = [] - self.srvcls = [] - self.actsrvs = [] - self.actcls = [] - def add_interface(self, name, interface_type, ref): - if interface_type == "pubs": - self.pubs.append(RosInterface(name,ref)) - if interface_type == "subs": - self.subs.append(RosInterface(name,ref)) - if interface_type == "srvsrvs": - self.srvsrvs.append(RosInterface(name,ref)) - if interface_type == "srvcls": - self.srvcls.append(RosInterface(name,ref)) - if interface_type == "actsrvs": - self.actsrvs.append(RosInterface(name,ref)) - if interface_type == "actcls": - self.actcls.append(RosInterface(name,ref)) - def main(argv = None): extractor = RosExtractor() if extractor.launch(): diff --git a/ros_code_analysis/setup.py b/ros_code_analysis/setup.py new file mode 100755 index 0000000..098907e --- /dev/null +++ b/ros_code_analysis/setup.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# +# Copyright 2024 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA) +# +# Nadia Hammoudeh Garcia +# +# 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 setuptools import setup + +setup( + name='ros_code_analysis', + version='0.0.1', + description='Static code analysis plugin based on HAROS to extract information from ROS nodes', + url='https://github.com/ipa320/roscode2model', + author='Nadia Hammoudeh Garcia', + author_email='nadia.hammoudeh.garcia@ipa.fraunhofer.de', + license='Apache 2.0', + packages=['ros_code_analysis'], + install_requires=['haros', + 'bonsai', + ], + +) +