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 Ken Conley/kwc@willowgarage.com
29
30"""
31Library for loading rosdep files from the ROS package/stack
32filesystem.
33"""
34
35from __future__ import print_function
36
37import os
38
39import catkin_pkg.package
40import rospkg
41
42from .loader import RosdepLoader
43
44# Default view key is the view that packages that are not in stacks
45# see. It is the root of all dependencies.  It is superceded by an
46# explicit underlay_key.
47DEFAULT_VIEW_KEY = '*default*'
48
49# Implementation details: this API was originally conceived under the
50# rosdep 1 design.  It has since been retrofitted for the rosdep 2
51# design, which means it is a bit overbuilt.  There really is no need
52# for a notion of views for rospkg -- all rospkgs have the same view.
53# It we be nice to refactor this API into something much, much
54# simpler, which would probably involve merging RosPkgLoader and
55# SourcesListLoader.  RosPkgLoader would provide identification of
56# resources and SourcesListLoader would build a *single* view that was
57# no longer resource-dependent.
58
59
60class RosPkgLoader(RosdepLoader):
61
62    def __init__(self, rospack=None, rosstack=None, underlay_key=None):
63        """
64        :param underlay_key: If set, all views loaded by this loader
65            will depend on this key.
66        """
67        if rospack is None:
68            rospack = rospkg.RosPack()
69        if rosstack is None:
70            rosstack = rospkg.RosStack()
71
72        self._rospack = rospack
73        self._rosstack = rosstack
74        self._rosdep_yaml_cache = {}
75        self._underlay_key = underlay_key
76
77        # cache computed list of loadable resources
78        self._loadable_resource_cache = None
79        self._catkin_packages_cache = None
80
81    def load_view(self, view_name, rosdep_db, verbose=False):
82        """
83        Load view data into *rosdep_db*. If the view has already
84        been loaded into *rosdep_db*, this method does nothing.  If
85        view has no rosdep data, it will be initialized with an empty
86        data map.
87
88        :raises: :exc:`InvalidData` if view rosdep.yaml is invalid
89        :raises: :exc:`rospkg.ResourceNotFound` if view cannot be located
90
91        :returns: ``True`` if view was loaded.  ``False`` if view
92          was already loaded.
93        """
94        if rosdep_db.is_loaded(view_name):
95            return
96        if view_name not in self.get_loadable_views():
97            raise rospkg.ResourceNotFound(view_name)
98        elif view_name == 'invalid':
99            raise rospkg.ResourceNotFound('FOUND' + view_name + str(self.get_loadable_views()))
100        if verbose:
101            print('loading view [%s] with rospkg loader' % (view_name))
102        # chain into underlay if set
103        if self._underlay_key:
104            view_dependencies = [self._underlay_key]
105        else:
106            view_dependencies = []
107        # no rospkg view has actual data
108        rosdep_db.set_view_data(view_name, {}, view_dependencies, '<nodata>')
109
110    def get_loadable_views(self):
111        """
112        'Views' map to ROS stack names.
113        """
114        return list(self._rosstack.list()) + [DEFAULT_VIEW_KEY]
115
116    def get_loadable_resources(self):
117        """
118        'Resources' map to ROS packages names.
119        """
120        if not self._loadable_resource_cache:
121            self._loadable_resource_cache = list(self._rospack.list())
122        return self._loadable_resource_cache
123
124    def get_catkin_paths(self):
125        if not self._catkin_packages_cache:
126            def find_catkin_paths(src):
127                return map(lambda x: (x, src.get_path(x)),
128                           filter(lambda x: src.get_manifest(x).is_catkin, src.list()))
129            self._catkin_packages_cache = dict(find_catkin_paths(self._rospack))
130            self._catkin_packages_cache.update(find_catkin_paths(self._rosstack))
131        return self._catkin_packages_cache
132
133    def get_rosdeps(self, resource_name, implicit=True):
134        """
135        If *resource_name* is a stack, returns an empty list.
136
137        :raises: :exc:`rospkg.ResourceNotFound` if *resource_name* cannot be found.
138        """
139
140        if resource_name in self.get_catkin_paths():
141            pkg = catkin_pkg.package.parse_package(self.get_catkin_paths()[resource_name])
142            pkg.evaluate_conditions(os.environ)
143            deps = pkg.build_depends + pkg.buildtool_depends + pkg.run_depends + pkg.test_depends
144            return [d.name for d in deps if d.evaluated_condition]
145        elif resource_name in self.get_loadable_resources():
146            return self._rospack.get_rosdeps(resource_name, implicit=implicit)
147        elif resource_name in self._rosstack.list():
148            # stacks currently do not have rosdeps of their own, implicit or otherwise
149            return []
150        else:
151            raise rospkg.ResourceNotFound(resource_name)
152
153    def is_metapackage(self, resource_name):
154        if resource_name in self._rosstack.list():
155            m = self._rosstack.get_manifest(resource_name)
156            return m.is_catkin
157        return False
158
159    def get_view_key(self, resource_name):
160        """
161        Map *resource_name* to a view key.  In rospkg, this maps the
162        DEFAULT_VIEW_KEY if *resource_name* exists.
163
164        :raises: :exc:`rospkg.ResourceNotFound`
165        """
166        if (
167            resource_name in self.get_catkin_paths() or
168            resource_name in self.get_loadable_resources()
169        ):
170            return DEFAULT_VIEW_KEY
171        else:
172            raise rospkg.ResourceNotFound(resource_name)
173