diff --git a/CMakeLists.txt b/CMakeLists.txt
deleted file mode 100644
index 24c479f..0000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-cmake_minimum_required(VERSION 3.0.2)
-project(rqt_dep)
-# Load catkin and all dependencies required for this package
-find_package(catkin REQUIRED)
-catkin_package()
-catkin_python_setup()
-
-if(CATKIN_ENABLE_TESTING)
- catkin_add_nosetests(test/dotcode_pack_test.py)
-endif()
-
-install(FILES plugin.xml
- DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
-)
-
-install(DIRECTORY resource
- DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
-)
-
-catkin_install_python(PROGRAMS scripts/rqt_dep
- DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
-)
diff --git a/package.xml b/package.xml
index 58f8238..c104d51 100644
--- a/package.xml
+++ b/package.xml
@@ -18,24 +18,17 @@
Dirk Thomas
Thibault Kruse
- catkin
- python-setuptools
- python3-setuptools
-
- python_qt_binding
- python-rospkg
- python3-rospkg
- qt_dotgraph
- qt_gui
+ ament_index_python
+ python_qt_binding
+ qt_dotgraph
+ rqt_gui
+ rqt_gui_py
+ python3-rospkg
qt_gui_py_common
rqt_graph
- rqt_gui_py
-
- python-mock
- python3-mock
-
+ ament_python
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..50d6d01
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+junit_family=xunit2
\ No newline at end of file
diff --git a/resource/rqt_dep b/resource/rqt_dep
new file mode 100644
index 0000000..e69de29
diff --git a/setup.py b/setup.py
index ebafa82..13a22b9 100644
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,39 @@
-#!/usr/bin/env python
-
from setuptools import setup
-from catkin_pkg.python_setup import generate_distutils_setup
-d = generate_distutils_setup(
- packages=['rqt_dep'],
+package_name = 'rqt_dep'
+setup(
+ name=package_name,
+ version='1.3.1',
package_dir={'': 'src'},
- scripts=['scripts/rqt_dep']
+ packages=[package_name],
+ data_files=[
+ ('share/ament_index/resource_index/packages',
+ ['resource/' + package_name]),
+ ('share/' + package_name + '/resource', ['resource/RosPackGraph.ui']),
+ ('share/' + package_name, ['package.xml']),
+ ('share/' + package_name, ['plugin.xml']),
+ ('lib/' + package_name, ['scripts/rqt_dep']),
+ ],
+ install_requires=['setuptools'],
+ zip_safe=True,
+ author='Dirk Thomas',
+ maintainer='Dirk Thomas, Aaron Blasdel, Thibault Kruse',
+ maintainer_email='dthomas@osrfoundation.org',
+ keywords=['ROS'],
+ classifiers=[
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development',
+ ],
+ description=(
+ 'rqt_dep provides a Python GUI plugin to visualize a ROS package network..'
+ ),
+ license='BSD',
+ tests_require=['pytest'],
+ entry_points={
+ 'console_scripts': [
+ 'rqt_dep = rqt_dep.main:main',
+ ],
+ },
)
-
-setup(**d)
diff --git a/src/rqt_dep/dotcode_pack.py b/src/rqt_dep/dotcode_pack.py
index 83971a0..9ea1d04 100644
--- a/src/rqt_dep/dotcode_pack.py
+++ b/src/rqt_dep/dotcode_pack.py
@@ -32,13 +32,21 @@
from __future__ import with_statement, print_function
-import os
+import xml.etree.ElementTree as ET
+
import re
+from typing import Any, Callable, List
+from typing_extensions import Protocol
+
+class GetItem(Protocol):
+ def __getitem__(self: 'Getitem', key: Any) -> Any: pass # type: ignore
+ def __getitem__(self: 'Getitem', key: Any) -> Any: pass # type: ignore
-from rospkg import MANIFEST_FILE
-from rospkg.common import ResourceNotFound
+from rospkg.common import PACKAGE_FILE
from qt_dotgraph.colors import get_color_for_string
+from ament_index_python.packages import get_search_paths, get_package_prefix, get_package_share_path, PackageNotFoundError
+import apt
def matches_any(name, patternlist):
for pattern in patternlist:
@@ -51,16 +59,17 @@ def matches_any(name, patternlist):
class RosPackageGraphDotcodeGenerator:
-
- def __init__(self, rospack, rosstack):
+ def __init__(self, list_fun:Callable[[],List[str]], system_cache:GetItem):
"""
:param rospack: use rospkg.RosPack()
:param rosstack: use rospkg.RosStack()
"""
- self.rospack = rospack
- self.rosstack = rosstack
- self.stacks = {}
- self.packages = {}
+ # self.rospack = rospack
+ # self.rosstack = rosstack
+ self.list_fun = list_fun
+ self.system_cache = system_cache
+ # self.stacks = {}
+ # self.packages = {}
self.package_types = {}
self.edges = {}
self.traversed_ancestors = {}
@@ -68,6 +77,9 @@ def __init__(self, rospack, rosstack):
self.last_drawargs = None
self.last_selection = None
+ search_paths = get_search_paths()
+ self.dry_path = get_search_paths()[-1] if len(search_paths) else ""
+
def generate_dotcode(self,
dotcode_factory,
selected_names=None,
@@ -150,26 +162,26 @@ def generate_dotcode(self,
self.traversed_ancestors = {}
self.traversed_descendants = {}
# update internal graph structure
- for name in self.rospack.list():
+ for name in self.list_fun():
if matches_any(name, self.selected_names):
if descendants:
self.add_package_descendants_recursively(name)
if ancestors:
self.add_package_ancestors_recursively(name)
- for stackname in self.rosstack.list():
- if matches_any(stackname, self.selected_names):
- manifest = self.rosstack.get_manifest(stackname)
- if manifest.is_catkin:
- if descendants:
- self.add_package_descendants_recursively(stackname)
- if ancestors:
- self.add_package_ancestors_recursively(stackname)
- else:
- for package_name in self.rosstack.packages_of(stackname):
- if descendants:
- self.add_package_descendants_recursively(package_name)
- if ancestors:
- self.add_package_ancestors_recursively(package_name)
+ # for stackname in self.rosstack.list():
+ # if matches_any(stackname, self.selected_names):
+ # manifest = self.rosstack.get_manifest(stackname)
+ # if manifest.is_wet:
+ # if descendants:
+ # self.add_package_descendants_recursively(stackname)
+ # if ancestors:
+ # self.add_package_ancestors_recursively(stackname)
+ # else:
+ # for package_name in self.rosstack.packages_of(stackname):
+ # if descendants:
+ # self.add_package_descendants_recursively(package_name)
+ # if ancestors:
+ # self.add_package_ancestors_recursively(package_name)
drawing_args = {
'dotcode_factory': dotcode_factory,
@@ -246,11 +258,11 @@ def _generate_package(self, dotcode_factory, graph, package_name, attributes=Non
if self.mark_selected and \
'.*' not in self.selected_names and \
matches_any(package_name, self.selected_names):
- if attributes and attributes['is_catkin']:
+ if attributes and attributes['is_wet']:
color = 'red'
else:
color = 'tomato'
- elif attributes and not attributes['is_catkin']:
+ elif attributes and not attributes['is_wet']:
color = 'gray'
if attributes and 'not_found' in attributes and attributes['not_found']:
color = 'orange'
@@ -272,23 +284,23 @@ def _add_package(self, package_name, parent=None):
if package_name in self.packages:
return False
- catkin_package = self._is_package_wet(package_name)
- if catkin_package is None:
+ wet_package = self._is_package_wet(package_name)
+ if wet_package is None:
return False
- self.packages[package_name] = {'is_catkin': catkin_package}
-
- if self.with_stacks:
- try:
- stackname = self.rospack.stack_of(package_name)
- except ResourceNotFound as e:
- print(
- 'RosPackageGraphDotcodeGenerator._add_package(%s), '
- 'parent %s: ResourceNotFound:' % (package_name, parent), e)
- stackname = None
- if not stackname is None and stackname != '':
- if not stackname in self.stacks:
- self._add_stack(stackname)
- self.stacks[stackname]['packages'].append(package_name)
+ self.packages[package_name] = {'is_wet': wet_package}
+
+ # if self.with_stacks:
+ # try:
+ # stackname = self.rospack.stack_of(package_name)
+ # except ResourceNotFound as e:
+ # print(
+ # 'RosPackageGraphDotcodeGenerator._add_package(%s), '
+ # 'parent %s: ResourceNotFound:' % (package_name, parent), e)
+ # stackname = None
+ # if not stackname is None and stackname != '':
+ # if not stackname in self.stacks:
+ # self._add_stack(stackname)
+ # self.stacks[stackname]['packages'].append(package_name)
return True
def _hide_package(self, package_name):
@@ -305,10 +317,10 @@ def _hide_package(self, package_name):
def _is_package_wet(self, package_name):
if package_name not in self.package_types:
try:
- package_path = self.rospack.get_path(package_name)
- manifest_file = os.path.join(package_path, MANIFEST_FILE)
- self.package_types[package_name] = not os.path.exists(manifest_file)
- except ResourceNotFound:
+ # package_path = self.rospack.get_path(package_name)
+ # manifest_file = os.path.join(package_path, MANIFEST_FILE)
+ self.package_types[package_name] = get_package_prefix(package_name) != self.dry_path # not os.path.exists(manifest_file)
+ except PackageNotFoundError:
return None
return self.package_types[package_name]
@@ -317,6 +329,29 @@ def _add_edge(self, name1, name2, attributes=None):
return
self.edges[(name1, name2)] = attributes
+ @staticmethod
+ def _get_depends_on(package_name:str):
+ r = get_package_share_path(package_name) / PACKAGE_FILE
+ tree = ET.parse(r)
+ dep_types = [
+ "build_depend",
+ # "build_export_depend",
+ # "buildtool_depend",
+ # "buildtool_export_depend",
+ "exec_depend",
+ "depend",
+ # "doc_depend",
+ # "test_depend",
+ ]
+
+ deps:set[str] = set()
+
+ for dep_t in dep_types:
+ for dep in [x.text for x in tree.iter(dep_t) if x.text]:
+ deps.add(dep)
+
+ return deps
+
def add_package_ancestors_recursively(
self, package_name, expanded_up=None,
depth=None, implicit=False, parent=None):
@@ -347,8 +382,8 @@ def add_package_ancestors_recursively(
expanded_up.append(package_name)
if (depth != 1):
try:
- depends_on = self.rospack.get_depends_on(package_name, implicit=implicit)
- except ResourceNotFound as e:
+ depends_on = RosPackageGraphDotcodeGenerator._get_depends_on(package_name)
+ except PackageNotFoundError as e:
print(
'RosPackageGraphDotcodeGenerator.add_package_ancestors_recursively(%s),'
' parent %s: ResourceNotFound:' % (package_name, parent), e)
@@ -389,29 +424,43 @@ def add_package_descendants_recursively(
expanded = []
expanded.append(package_name)
if (depth != 1):
+ is_ros_package = False
+ is_system_package = False
+ search_problem = '???'
try:
- try:
- depends = self.rospack.get_depends(package_name, implicit=implicit)
- except ResourceNotFound:
- # try falling back to rosstack to find wet metapackages
- manifest = self.rosstack.get_manifest(package_name)
- if manifest.is_catkin:
- depends = [d.name for d in manifest.depends]
- else:
- raise
- except ResourceNotFound as e:
- print(
- 'RosPackageGraphDotcodeGenerator.add_package_descendants_recursively(%s), '
- 'parent: %s: ResourceNotFound:' % (package_name, parent), e)
+ # try:
+ depends = RosPackageGraphDotcodeGenerator._get_depends_on(package_name) #self.rospack.get_depends(package_name, implicit=implicit)
+ is_ros_package = True
+ # except ResourceNotFound:
+ # # try falling back to rosstack to find wet metapackages
+ # manifest = self.rosstack.get_manifest(package_name)
+ # if manifest.is_wet:
+ # depends = [d.name for d in manifest.depends]
+ # else:
+ # raise
+ except PackageNotFoundError:
depends = []
+ search_problem = f"Resource not found in: {get_search_paths()}"
+
# get system dependencies without recursion
- if self.show_system:
- rosdeps = self.rospack.get_rosdeps(package_name, implicit=implicit)
- for dep_name in [x for x in rosdeps if not matches_any(x, self.excludes)]:
+ if self.show_system and not is_ros_package:
+
+ if any(key.includes(package_name) for key in self.system_cache):
+ # rosdeps = self.rospack.get_rosdeps(package_name, implicit=implicit)
+ # for dep_name in [x for x in rosdeps if not matches_any(x, self.excludes)]:
+ dep_name = package_name
if not self.hide_transitives or not dep_name in expanded:
self._add_edge(package_name, dep_name)
self._add_package(dep_name, parent=package_name)
expanded.append(dep_name)
+ else:
+ search_problem = f"Resource not found in system"
+
+ if not is_ros_package and not is_system_package:
+ print(
+ 'RosPackageGraphDotcodeGenerator.add_package_descendants_recursively(%s), '
+ 'parent: %s: ' % (package_name, parent), search_problem)
+
new_nodes = []
for dep_name in [x for x in depends if not matches_any(x, self.excludes)]:
if not self.hide_transitives or not dep_name in expanded:
diff --git a/src/rqt_dep/ros_pack_graph.py b/src/rqt_dep/ros_pack_graph.py
index 5c65c1c..0d0a275 100644
--- a/src/rqt_dep/ros_pack_graph.py
+++ b/src/rqt_dep/ros_pack_graph.py
@@ -35,8 +35,13 @@
import os
import pickle
import sys
+from typing import List, cast
+
+import apt
+
+from ros2pkg.api import get_package_names
+from ament_index_python.packages import get_package_share_directory
-import rospkg
from python_qt_binding import loadUi
from python_qt_binding.QtCore import QFile, QIODevice, Qt, Signal, Slot, QAbstractListModel
@@ -44,8 +49,8 @@
from python_qt_binding.QtWidgets import QFileDialog, QGraphicsScene, QWidget, QCompleter
from python_qt_binding.QtSvg import QSvgGenerator
-import rosservice
-import rostopic
+from ros2service.api import get_service_names_and_types
+from ros2topic.api import get_msg_class
from .dotcode_pack import RosPackageGraphDotcodeGenerator
from qt_dotgraph.pydotfactory import PydotFactory
@@ -77,9 +82,9 @@ class StackageCompletionModel(QAbstractListModel):
"""Ros package and stacknames"""
- def __init__(self, linewidget, rospack, rosstack):
+ def __init__(self, linewidget):
super(StackageCompletionModel, self).__init__(linewidget)
- self.allnames = sorted(list(set(list(rospack.list()) + list(rosstack.list()))))
+ self.allnames = cast(List[str], sorted(get_package_names()))
self.allnames = self.allnames + ['-%s' % name for name in self.allnames]
def rowCount(self, parent):
@@ -108,20 +113,19 @@ def __init__(self, context):
self.setObjectName('RosPackGraph')
- rospack = rospkg.RosPack()
- rosstack = rospkg.RosStack()
+ # rospack = rospkg.RosPack()
+ # rosstack = rospkg.RosStack()
# factory builds generic dotcode items
self.dotcode_factory = PydotFactory()
# self.dotcode_factory = PygraphvizFactory()
# generator builds rosgraph
- self.dotcode_generator = RosPackageGraphDotcodeGenerator(rospack, rosstack)
+ self.dotcode_generator = RosPackageGraphDotcodeGenerator(lambda: list(get_package_names()), apt.Cache())
# dot_to_qt transforms into Qt elements using dot layout
self.dot_to_qt = DotToQtGenerator()
self._widget = QWidget()
- rp = rospkg.RosPack()
- ui_file = os.path.join(rp.get_path('rqt_dep'), 'resource', 'RosPackGraph.ui')
+ ui_file = os.path.join(get_package_share_directory('rqt_dep'), 'resource', 'RosPackGraph.ui')
loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
self._widget.setObjectName('RosPackGraphUi')
if context.serial_number() > 1:
@@ -149,7 +153,7 @@ def __init__(self, context):
self._widget.package_type_combo_box.insertItem(2, self.tr('dry only'), 1)
self._widget.package_type_combo_box.currentIndexChanged.connect(self._refresh_rospackgraph)
- completionmodel = StackageCompletionModel(self._widget.filter_line_edit, rospack, rosstack)
+ completionmodel = StackageCompletionModel(self._widget.filter_line_edit)
completer = RepeatedWordCompleter(completionmodel, self)
completer.setCompletionMode(QCompleter.PopupCompletion)
completer.setWrapAround(True)
@@ -323,6 +327,7 @@ def _generate_dotcode(self):
excludes.append(name.strip()[1:])
else:
includes.append(name.strip())
+
# orientation = 'LR'
descendants = True
ancestors = True
@@ -353,21 +358,23 @@ def _update_graph(self, dotcode):
def _generate_tool_tip(self, url):
if url is not None and ':' in url:
+ print(url)
item_type, item_path = url.split(':', 1)
if item_type == 'node':
tool_tip = 'Node:\n %s' % (item_path)
- service_names = rosservice.get_service_list(node=item_path)
- if service_names:
+ services = get_service_names_and_types(node=item_path)
+ if services:
tool_tip += '\nServices:'
- for service_name in service_names:
- try:
- service_type = rosservice.get_service_type(service_name)
- tool_tip += '\n %s [%s]' % (service_name, service_type)
- except rosservice.ROSServiceIOException as e:
- tool_tip += '\n %s' % (e)
+ for service_name, service_type in services:
+ # try:
+ # service_type = rosservice.get_service_type(service_name)
+ tool_tip += '\n %s [%s]' % (service_name, service_type)
+ # except rosservice.ROSServiceIOException as e:
+ # tool_tip += '\n %s' % (e)
return tool_tip
elif item_type == 'topic':
- topic_type, topic_name, _ = rostopic.get_topic_type(item_path)
+ print(item_path)
+ topic_type, topic_name, _ = get_msg_class(item_path, item_path)
return 'Topic:\n %s\nType:\n %s' % (topic_name, topic_type)
return url