1# Copyright (c) 2011, Willow Garage, Inc. 2# All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above copyright 10# notice, this list of conditions and the following disclaimer in the 11# documentation and/or other materials provided with the distribution. 12# * Neither the name of the Willow Garage, Inc. nor the names of its 13# contributors may be used to endorse or promote products derived from 14# this software without specific prior written permission. 15# 16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26# POSSIBILITY OF SUCH DAMAGE. 27 28# Author Tully Foote/tfoote@willowgarage.com, Ken Conley/kwc@willowgarage.com 29 30from __future__ import print_function 31 32import sys 33import yaml 34 35from collections import defaultdict 36 37from rospkg import RosPack, RosStack, ResourceNotFound 38 39from .core import RosdepInternalError, InvalidData, rd_debug 40from .model import RosdepDatabase 41from .rospkg_loader import RosPkgLoader 42from .dependency_graph import DependencyGraph 43 44from .sources_list import SourcesListLoader 45 46from . import catkin_packages 47 48 49class RosdepDefinition(object): 50 """ 51 Single rosdep dependency definition. This data is stored as the 52 raw dictionary definition for the dependency. 53 54 See REP 111, 'Multiple Package Manager Support for Rosdep' for a 55 discussion of this raw format. 56 """ 57 58 def __init__(self, rosdep_key, data, origin='<dynamic>'): 59 """ 60 :param rosdep_key: key/name of rosdep dependency 61 :param data: raw rosdep data for a single rosdep dependency, ``dict`` 62 :param origin: string that indicates where data originates from (e.g. filename) 63 """ 64 self.rosdep_key = rosdep_key 65 if not isinstance(data, dict): 66 raise InvalidData('rosdep data for [%s] must be a dictionary' % (self.rosdep_key), origin=origin) 67 self.data = data 68 self.origin = origin 69 70 def reverse_merge(self, new_data, origin='<dynamic>', verbose=False): 71 """ 72 Merge two definitions together, with existing rules taking precendence. 73 Definitions are merged at the os_name level, meaning that if two rules 74 exist with the same os_name, the first one wins. 75 76 :param data: raw rosdep data for a single rosdep dependency, ``dict`` 77 :param origin: string that indicates where this new data comes from (e.g. filename) 78 """ 79 for os_name, rules in new_data.items(): 80 if os_name not in self.data: 81 if verbose: 82 print('[%s] adding rules for os [%s] to [%s]' % (origin, os_name, self.rosdep_key), file=sys.stderr) 83 self.data[os_name] = rules 84 elif verbose: 85 print('[%s] ignoring [%s] for os [%s], already loaded' % (origin, self.rosdep_key, os_name), file=sys.stderr) 86 87 def get_rule_for_platform(self, os_name, os_version, installer_keys, default_installer_key): 88 """ 89 Get installer_key and rule for the specified rule. See REP 111 for precedence rules. 90 91 :param os_name: OS name to get rule for 92 :param os_version: OS version to get rule for 93 :param installer_keys: Keys of installers for platform, ``[str]`` 94 :param default_installer_key: Default installer key for platform, ``[str]`` 95 :returns: (installer_key, rosdep_args_dict), ``(str, dict)`` 96 97 :raises: :exc:`ResolutionError` If no rule is available 98 :raises: :exc:`InvalidData` If rule data is not valid 99 """ 100 rosdep_key = self.rosdep_key 101 data = self.data 102 103 if type(data) != dict: 104 raise InvalidData('rosdep value for [%s] must be a dictionary' % (self.rosdep_key), origin=self.origin) 105 if os_name not in data: 106 raise ResolutionError(rosdep_key, data, os_name, os_version, 'No definition of [%s] for OS [%s]' % (rosdep_key, os_name)) 107 data = data[os_name] 108 return_key = default_installer_key 109 110 # REP 111: rosdep first interprets the key as a 111 # PACKAGE_MANAGER. If this test fails, it will be interpreted 112 # as an OS_VERSION_CODENAME. 113 if type(data) == dict: 114 for installer_key in installer_keys: 115 if installer_key in data: 116 data = data[installer_key] 117 return_key = installer_key 118 break 119 else: 120 # data must be a dictionary, string, or list 121 if type(data) == dict: 122 # check for 123 # hardy: 124 # apt: 125 # stuff 126 127 # we've already checked for PACKAGE_MANAGER_KEY, so 128 # version key must be present here for data to be valid 129 # dictionary value. 130 # if the os_version is not defined and there is no wildcard 131 if os_version not in data and '*' not in data: 132 raise ResolutionError(rosdep_key, self.data, os_name, os_version, 'No definition of [%s] for OS version [%s]' % (rosdep_key, os_version)) 133 # if the os_version has the value None 134 if os_version in data and data[os_version] is None: 135 raise ResolutionError(rosdep_key, self.data, os_name, os_version, '[%s] defined as "not available" for OS version [%s]' % (rosdep_key, os_version)) 136 # if os version is not defined (and there is a wildcard) fallback to the wildcard 137 if os_version not in data: 138 os_version = '*' 139 data = data[os_version] 140 if type(data) == dict: 141 for installer_key in installer_keys: 142 if installer_key in data: 143 data = data[installer_key] 144 return_key = installer_key 145 break 146 147 if type(data) not in (dict, list, type('str')): 148 raise InvalidData('rosdep OS definition for [%s:%s] must be a dictionary, string, or list: %s' % (self.rosdep_key, os_name, data), origin=self.origin) 149 150 return return_key, data 151 152 def __str__(self): 153 return '%s:\n%s' % (self.origin, yaml.dump(self.data, default_flow_style=False)) 154 155 156class ResolutionError(Exception): 157 158 def __init__(self, rosdep_key, rosdep_data, os_name, os_version, message): 159 self.rosdep_key = rosdep_key 160 self.rosdep_data = rosdep_data 161 self.os_name = os_name 162 self.os_version = os_version 163 super(ResolutionError, self).__init__(message) 164 165 def __str__(self): 166 if self.rosdep_data: 167 pretty_data = yaml.dump(self.rosdep_data, default_flow_style=False) 168 else: 169 pretty_data = '<no data>' 170 return """%s 171\trosdep key : %s 172\tOS name : %s 173\tOS version : %s 174\tData: %s""" % (self.args[0], self.rosdep_key, self.os_name, self.os_version, pretty_data) 175 176 177class RosdepView(object): 178 """ 179 View of :class:`RosdepDatabase`. Unlike :class:`RosdepDatabase`, 180 which stores :class:`RosdepDatabaseEntry` data for all stacks, a 181 view merges entries for a particular stack. This view can then be 182 queried to lookup and resolve individual rosdep dependencies. 183 """ 184 185 def __init__(self, name): 186 self.name = name 187 self.rosdep_defs = {} # {str: RosdepDefinition} 188 189 def __str__(self): 190 return '\n'.join(['%s: %s' % val for val in self.rosdep_defs.items()]) 191 192 def lookup(self, rosdep_name): 193 """ 194 :returns: :class:`RosdepDefinition` 195 :raises: :exc:`KeyError` If *rosdep_name* is not declared 196 """ 197 return self.rosdep_defs[rosdep_name] 198 199 def keys(self): 200 """ 201 :returns: list of rosdep names in this view 202 """ 203 return self.rosdep_defs.keys() 204 205 def merge(self, update_entry, override=False, verbose=False): 206 """ 207 Merge rosdep database update into main database. Merge rules 208 are first entry to declare a key wins. There are no 209 conflicts. This rule logic is modelled after the apt sources 210 list. 211 212 :param override: Ignore first-one-wins rules and instead 213 always use rules from update_entry 214 """ 215 if verbose: 216 print('view[%s]: merging from cache of [%s]' % (self.name, update_entry.origin)) 217 db = self.rosdep_defs 218 219 for dep_name, dep_data in update_entry.rosdep_data.items(): 220 # convert data into RosdepDefinition model 221 update_definition = RosdepDefinition(dep_name, dep_data, update_entry.origin) 222 # First rule wins or override, no rule-merging. 223 if override or dep_name not in db: 224 db[dep_name] = update_definition 225 elif dep_name in db: 226 db[dep_name].reverse_merge(dep_data, update_entry.origin, verbose=verbose) 227 228 229def prune_catkin_packages(rosdep_keys, verbose=False): 230 workspace_pkgs = catkin_packages.get_workspace_packages() 231 if not workspace_pkgs: 232 return rosdep_keys 233 for i, rosdep_key in reversed(list(enumerate(rosdep_keys))): 234 if rosdep_key in workspace_pkgs: 235 # If workspace packages listed (--catkin-workspace) 236 # and if the rosdep_key is a package in that 237 # workspace, then skip it rather than resolve it 238 if verbose: 239 print("rosdep key '{0}'".format(rosdep_key) + 240 ' is in the catkin workspace, skipping.', 241 file=sys.stderr) 242 del rosdep_keys[i] 243 return rosdep_keys 244 245 246def prune_skipped_packages(rosdep_keys, skipped_keys, verbose=False): 247 if not skipped_keys: 248 return rosdep_keys 249 for i, rosdep_key in reversed(list(enumerate(rosdep_keys))): 250 if rosdep_key in skipped_keys: 251 # If the key is in the list of keys to explicitly skip, skip it 252 if verbose: 253 print("rosdep key '{0}'".format(rosdep_key) + 254 ' was listed in the skipped packages, skipping.', 255 file=sys.stderr) 256 del rosdep_keys[i] 257 return rosdep_keys 258 259 260class RosdepLookup(object): 261 """ 262 Lookup rosdep definitions. Provides API for most 263 non-install-related commands for rosdep. 264 265 :class:`RosdepLookup` caches data as it is loaded, so changes made 266 on the filesystem will not be reflected if the rosdep information 267 has already been loaded. 268 """ 269 270 def __init__(self, rosdep_db, loader): 271 """ 272 :param loader: Loader to use for loading rosdep data by stack 273 name, ``RosdepLoader`` 274 :param rosdep_db: Database to load definitions into, :class:`RosdepDatabase` 275 """ 276 self.rosdep_db = rosdep_db 277 self.loader = loader 278 279 self._view_cache = {} # {str: {RosdepView}} 280 self._resolve_cache = {} # {str : (os_name, os_version, installer_key, resolution, dependencies)} 281 282 # some APIs that deal with the entire environment save errors 283 # in to self.errors instead of raising them in order to be 284 # robust to single-stack faults. 285 self.errors = [] 286 287 # flag for turning on printing to console 288 self.verbose = False 289 290 self.skipped_keys = [] 291 292 def get_loader(self): 293 return self.loader 294 295 def get_errors(self): 296 """ 297 Retrieve error state for API calls that do not directly report 298 error state. This is the case for APIs like 299 :meth:`RosdepLookup.where_defined` that are meant to be 300 fault-tolerant to single-stack failures. 301 302 :returns: List of exceptions, ``[Exception]`` 303 """ 304 return self.errors[:] 305 306 def get_rosdeps(self, resource_name, implicit=True): 307 """ 308 Get rosdeps that *resource_name* (e.g. package) requires. 309 310 :param implicit: If ``True``, include implicit rosdep 311 dependencies. Default: ``True``. 312 313 :returns: list of rosdep names, ``[str]`` 314 """ 315 return self.loader.get_rosdeps(resource_name, implicit=implicit) 316 317 def get_resources_that_need(self, rosdep_name): 318 """ 319 :param rosdep_name: name of rosdep dependency 320 321 :returns: list of package names that require rosdep, ``[str]`` 322 """ 323 return [k for k in self.loader.get_loadable_resources() if rosdep_name in self.get_rosdeps(k, implicit=False)] 324 325 @staticmethod 326 def create_from_rospkg(rospack=None, rosstack=None, 327 sources_loader=None, 328 verbose=False): 329 """ 330 Create :class:`RosdepLookup` based on current ROS package 331 environment. 332 333 :param rospack: (optional) Override :class:`rospkg.RosPack` 334 instance used to crawl ROS packages. 335 :param rosstack: (optional) Override :class:`rospkg.RosStack` 336 instance used to crawl ROS stacks. 337 :param sources_loader: (optional) Override SourcesLoader used 338 for managing sources.list data sources. 339 """ 340 # initialize the loader 341 if rospack is None: 342 rospack = RosPack() 343 if rosstack is None: 344 rosstack = RosStack() 345 if sources_loader is None: 346 sources_loader = SourcesListLoader.create_default(verbose=verbose) 347 348 rosdep_db = RosdepDatabase() 349 350 # Use sources list to initialize rosdep_db. Underlay has no 351 # notion of specific resources, and its view keys are just the 352 # individual sources it can load from. SourcesListLoader 353 # cannot do delayed evaluation of OS setting due to matcher. 354 underlay_key = SourcesListLoader.ALL_VIEW_KEY 355 356 # Create the rospkg loader on top of the underlay 357 loader = RosPkgLoader(rospack=rospack, rosstack=rosstack, 358 underlay_key=underlay_key) 359 360 # create our actual instance 361 lookup = RosdepLookup(rosdep_db, loader) 362 363 # load in the underlay 364 lookup._load_all_views(loader=sources_loader) 365 # use dependencies to implement precedence 366 view_dependencies = sources_loader.get_loadable_views() 367 rosdep_db.set_view_data(underlay_key, {}, view_dependencies, underlay_key) 368 369 return lookup 370 371 def resolve_all(self, resources, installer_context, implicit=False): 372 """ 373 Resolve all the rosdep dependencies for *resources* using *installer_context*. 374 375 :param resources: list of resources (e.g. packages), ``[str]`` 376 :param installer_context: :class:`InstallerContext` 377 :param implicit: Install implicit (recursive) dependencies of 378 resources. Default ``False``. 379 380 :returns: (resolutions, errors), ``([(str, [str])], {str: ResolutionError})``. resolutions provides 381 an ordered list of resolution tuples. A resolution tuple's first element is the installer 382 key (e.g.: apt or homebrew) and the second element is a list of opaque resolution values for that 383 installer. errors maps package names to an :exc:`ResolutionError` or :exc:`KeyError` exception. 384 385 :raises: :exc:`RosdepInternalError` if unexpected error in constructing dependency graph 386 :raises: :exc:`InvalidData` if a cycle occurs in constructing dependency graph 387 """ 388 depend_graph = DependencyGraph() 389 errors = {} 390 # TODO: resolutions dictionary should be replaced with resolution model instead of mapping (undefined) keys. 391 for resource_name in resources: 392 try: 393 rosdep_keys = self.get_rosdeps(resource_name, implicit=implicit) 394 if self.verbose: 395 print('resolve_all: resource [%s] requires rosdep keys [%s]' % (resource_name, ', '.join(rosdep_keys)), file=sys.stderr) 396 rosdep_keys = prune_catkin_packages(rosdep_keys, self.verbose) 397 rosdep_keys = prune_skipped_packages(rosdep_keys, self.skipped_keys, self.verbose) 398 for rosdep_key in rosdep_keys: 399 try: 400 installer_key, resolution, dependencies = \ 401 self.resolve(rosdep_key, resource_name, installer_context) 402 depend_graph[rosdep_key]['installer_key'] = installer_key 403 depend_graph[rosdep_key]['install_keys'] = list(resolution) 404 depend_graph[rosdep_key]['dependencies'] = list(dependencies) 405 while dependencies: 406 depend_rosdep_key = dependencies.pop() 407 # prevent infinite loop 408 if depend_rosdep_key in depend_graph: 409 continue 410 installer_key, resolution, more_dependencies = \ 411 self.resolve(depend_rosdep_key, resource_name, installer_context) 412 dependencies.extend(more_dependencies) 413 depend_graph[depend_rosdep_key]['installer_key'] = installer_key 414 depend_graph[depend_rosdep_key]['install_keys'] = list(resolution) 415 depend_graph[depend_rosdep_key]['dependencies'] = list(more_dependencies) 416 417 except ResolutionError as e: 418 errors[resource_name] = e 419 except ResourceNotFound as e: 420 errors[resource_name] = e 421 422 try: 423 # TODO: I really don't like AssertionErrors here; this should be modeled as 'CyclicGraphError' 424 # or something more explicit. No need to continue if this API errors. 425 resolutions_flat = depend_graph.get_ordered_dependency_list() 426 except AssertionError as e: 427 raise InvalidData('cycle in dependency graph detected: %s' % (e)) 428 except KeyError as e: 429 raise RosdepInternalError(e) 430 431 return resolutions_flat, errors 432 433 def resolve(self, rosdep_key, resource_name, installer_context): 434 """ 435 Resolve a :class:`RosdepDefinition` for a particular 436 os/version spec. 437 438 :param resource_name: resource (e.g. ROS package) to resolve key within 439 :param rosdep_key: rosdep key to resolve 440 :param os_name: OS name to use for resolution 441 :param os_version: OS name to use for resolution 442 443 :returns: *(installer_key, resolution, dependencies)*, ``(str, 444 [opaque], [str])``. *resolution* are the system 445 dependencies for the specified installer. The value is an 446 opaque list and meant to be interpreted by the 447 installer. *dependencies* is a list of rosdep keys that the 448 definition depends on. 449 450 :raises: :exc:`ResolutionError` If *rosdep_key* cannot be resolved for *resource_name* in *installer_context* 451 :raises: :exc:`rospkg.ResourceNotFound` if *resource_name* cannot be located 452 """ 453 os_name, os_version = installer_context.get_os_name_and_version() 454 455 view = self.get_rosdep_view_for_resource(resource_name) 456 if view is None: 457 raise ResolutionError(rosdep_key, None, os_name, os_version, '[%s] does not have a rosdep view' % (resource_name)) 458 try: 459 # print("KEYS", view.rosdep_defs.keys()) 460 definition = view.lookup(rosdep_key) 461 except KeyError: 462 rd_debug(view) 463 raise ResolutionError(rosdep_key, None, os_name, os_version, 'Cannot locate rosdep definition for [%s]' % (rosdep_key)) 464 465 # check cache: the main motivation for the cache is that 466 # source rosdeps are expensive to resolve 467 if rosdep_key in self._resolve_cache: 468 cache_value = self._resolve_cache[rosdep_key] 469 cache_os_name = cache_value[0] 470 cache_os_version = cache_value[1] 471 cache_view_name = cache_value[2] 472 if ( 473 cache_os_name == os_name and 474 cache_os_version == os_version and 475 cache_view_name == view.name 476 ): 477 return cache_value[3:] 478 479 # get the rosdep data for the platform 480 try: 481 installer_keys = installer_context.get_os_installer_keys(os_name) 482 default_key = installer_context.get_default_os_installer_key(os_name) 483 except KeyError: 484 raise ResolutionError(rosdep_key, definition.data, os_name, os_version, 'Unsupported OS [%s]' % (os_name)) 485 installer_key, rosdep_args_dict = definition.get_rule_for_platform(os_name, os_version, installer_keys, default_key) 486 487 # resolve the rosdep data for the platform 488 try: 489 installer = installer_context.get_installer(installer_key) 490 except KeyError: 491 raise ResolutionError(rosdep_key, definition.data, os_name, os_version, 'Unsupported installer [%s]' % (installer_key)) 492 resolution = installer.resolve(rosdep_args_dict) 493 dependencies = installer.get_depends(rosdep_args_dict) 494 495 # cache value 496 # the dependencies list is copied to prevent mutation before next cache hit 497 self._resolve_cache[rosdep_key] = os_name, os_version, view.name, installer_key, resolution, list(dependencies) 498 499 return installer_key, resolution, dependencies 500 501 def _load_all_views(self, loader): 502 """ 503 Load all available view keys. In general, this is equivalent 504 to loading all stacks on the package path. If 505 :exc:`InvalidData` errors occur while loading a view, 506 they will be saved in the *errors* field. 507 508 :param loader: override self.loader 509 :raises: :exc:`RosdepInternalError` 510 """ 511 for resource_name in loader.get_loadable_views(): 512 try: 513 self._load_view_dependencies(resource_name, loader) 514 except ResourceNotFound as e: 515 self.errors.append(e) 516 except InvalidData as e: 517 self.errors.append(e) 518 519 def _load_view_dependencies(self, view_key, loader): 520 """ 521 Initialize internal :exc:`RosdepDatabase` on demand. Not 522 thread-safe. 523 524 :param view_key: name of view to load dependencies for. 525 526 :raises: :exc:`rospkg.ResourceNotFound` If view cannot be located 527 :raises: :exc:`InvalidData` if view's data is invaid 528 :raises: :exc:`RosdepInternalError` 529 """ 530 rd_debug('_load_view_dependencies[%s]' % (view_key)) 531 db = self.rosdep_db 532 if db.is_loaded(view_key): 533 return 534 try: 535 loader.load_view(view_key, db, verbose=self.verbose) 536 entry = db.get_view_data(view_key) 537 rd_debug('_load_view_dependencies[%s]: %s' % (view_key, entry.view_dependencies)) 538 for d in entry.view_dependencies: 539 self._load_view_dependencies(d, loader) 540 except InvalidData: 541 # mark view as loaded: as we are caching, the valid 542 # behavior is to not attempt loading this view ever 543 # again. 544 db.mark_loaded(view_key) 545 # re-raise 546 raise 547 except KeyError as e: 548 raise RosdepInternalError(e) 549 550 def create_rosdep_view(self, view_name, view_keys, verbose=False): 551 """ 552 :param view_name: name of view to create 553 :param view_keys: order list of view names to merge, first one wins 554 :param verbose: print debugging output 555 """ 556 # Create view and initialize with dbs from all of the 557 # dependencies. 558 view = RosdepView(view_name) 559 560 db = self.rosdep_db 561 for view_key in view_keys: 562 db_entry = db.get_view_data(view_key) 563 view.merge(db_entry, verbose=verbose) 564 if verbose: 565 print('View [%s], merged views:\n' % (view_name) + '\n'.join([' * %s' % view_key for view_key in view_keys]), file=sys.stderr) 566 return view 567 568 def get_rosdep_view_for_resource(self, resource_name, verbose=False): 569 """ 570 Get a :class:`RosdepView` for a specific ROS resource *resource_name*. 571 Views can be queries to resolve rosdep keys to 572 definitions. 573 574 :param resource_name: Name of ROS resource (e.g. stack, 575 package) to create view for, ``str``. 576 577 :returns: :class:`RosdepView` for specific ROS resource 578 *resource_name*, or ``None`` if no view is associated with this resource. 579 580 :raises: :exc:`RosdepConflict` if view cannot be created due 581 to conflict rosdep definitions. 582 :raises: :exc:`rospkg.ResourceNotFound` if *view_key* cannot be located 583 :raises: :exc:`RosdepInternalError` 584 """ 585 view_key = self.loader.get_view_key(resource_name) 586 if not view_key: 587 # NOTE: this may not be the right behavior and this happens 588 # for packages that are not in a stack. 589 return None 590 return self.get_rosdep_view(view_key, verbose=verbose) 591 592 def get_rosdep_view(self, view_key, verbose=False): 593 """ 594 Get a :class:`RosdepView` associated with *view_key*. Views 595 can be queries to resolve rosdep keys to definitions. 596 597 :param view_key: Name of rosdep view (e.g. ROS stack name), ``str`` 598 599 :raises: :exc:`RosdepConflict` if view cannot be created due 600 to conflict rosdep definitions. 601 :raises: :exc:`rospkg.ResourceNotFound` if *view_key* cannot be located 602 :raises: :exc:`RosdepInternalError` 603 """ 604 if view_key in self._view_cache: 605 return self._view_cache[view_key] 606 607 # lazy-init 608 self._load_view_dependencies(view_key, self.loader) 609 610 # use dependencies to create view 611 try: 612 dependencies = self.rosdep_db.get_view_dependencies(view_key) 613 except KeyError as e: 614 # convert to ResourceNotFound. This should be decoupled 615 # in the future 616 raise ResourceNotFound(str(e.args[0])) 617 # load views in order 618 view = self.create_rosdep_view(view_key, dependencies + [view_key], verbose=verbose) 619 self._view_cache[view_key] = view 620 return view 621 622 def get_views_that_define(self, rosdep_name): 623 """ 624 Locate all views that directly define *rosdep_name*. A 625 side-effect of this method is that all available rosdep files 626 in the configuration will be loaded into memory. 627 628 Error state from single-stack failures 629 (e.g. :exc:`InvalidData`, :exc:`ResourceNotFound`) are 630 not propagated. Caller must check 631 :meth:`RosdepLookup.get_errors` to check for single-stack 632 error state. Error state does not reset -- it accumulates. 633 634 :param rosdep_name: name of rosdep to lookup 635 :returns: list of (stack_name, origin) where rosdep is defined. 636 637 :raises: :exc:`RosdepInternalError` 638 """ 639 # TODOXXX: change this to return errors object so that caller cannot ignore 640 self._load_all_views(self.loader) 641 db = self.rosdep_db 642 retval = [] 643 for view_name in db.get_view_names(): 644 entry = db.get_view_data(view_name) 645 # not much abstraction in the entry object 646 if rosdep_name in entry.rosdep_data: 647 retval.append((view_name, entry.origin)) 648 649 return retval 650