Skip to content

Commit

Permalink
support veresion specific install for pip (and others)
Browse files Browse the repository at this point in the history
  • Loading branch information
k-okada committed Jul 19, 2019
1 parent ab1f06e commit 604affe
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 58 deletions.
5 changes: 3 additions & 2 deletions src/rosdep2/installers.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,9 @@ def get_depends(self, rosdep_args):
"""
return [] # Default return empty list

def resolve(self, rosdep_args_dict):
def resolve(self, rosdep, rosdep_args_dict):
"""
:param rosdep: rosdep key (catkin_pkg.package.Dependency object) to resolve
:param rosdep_args_dict: argument dictionary to the rosdep rule for this package manager
:returns: [resolutions]. resolved objects should be printable to a user, but are otherwise opaque.
"""
Expand Down Expand Up @@ -336,7 +337,7 @@ def elevate_priv(self, cmd):
"""
return (self.sudo_command.split() if self.as_root else []) + cmd

def resolve(self, rosdep_args):
def resolve(self, rosdep, rosdep_args):
"""
See :meth:`Installer.resolve()`
"""
Expand Down
40 changes: 22 additions & 18 deletions src/rosdep2/lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,11 @@ def merge(self, update_entry, override=False, verbose=False):
db[dep_name].reverse_merge(dep_data, update_entry.origin, verbose=verbose)


def prune_catkin_packages(rosdep_keys, verbose=False):
def prune_catkin_packages(rosdeps, verbose=False):
workspace_pkgs = catkin_packages.get_workspace_packages()
if not workspace_pkgs:
return rosdep_keys
return rosdeps
rosdep_keys = [d.name for d in rosdeps]
for i, rosdep_key in reversed(list(enumerate(rosdep_keys))):
if rosdep_key in workspace_pkgs:
# If workspace packages listed (--catkin-workspace)
Expand All @@ -239,22 +240,23 @@ def prune_catkin_packages(rosdep_keys, verbose=False):
print("rosdep key '{0}'".format(rosdep_key) +
' is in the catkin workspace, skipping.',
file=sys.stderr)
del rosdep_keys[i]
return rosdep_keys
del rosdeps[i]
return rosdeps


def prune_skipped_packages(rosdep_keys, skipped_keys, verbose=False):
def prune_skipped_packages(rosdeps, skipped_keys, verbose=False):
if not skipped_keys:
return rosdep_keys
return rosdeps
rosdep_keys = [d.name for d in rosdeps]
for i, rosdep_key in reversed(list(enumerate(rosdep_keys))):
if rosdep_key in skipped_keys:
# If the key is in the list of keys to explicitly skip, skip it
if verbose:
print("rosdep key '{0}'".format(rosdep_key) +
' was listed in the skipped packages, skipping.',
file=sys.stderr)
del rosdep_keys[i]
return rosdep_keys
del rosdeps[i]
return rosdeps


class RosdepLookup(object):
Expand Down Expand Up @@ -320,7 +322,7 @@ def get_resources_that_need(self, rosdep_name):
:returns: list of package names that require rosdep, ``[str]``
"""
return [k for k in self.loader.get_loadable_resources() if rosdep_name in self.get_rosdeps(k, implicit=False)]
return [k for k in self.loader.get_loadable_resources() if rosdep_name in [d.name for d in self.get_rosdeps(k, implicit=False)]]

@staticmethod
def create_from_rospkg(rospack=None, rosstack=None,
Expand Down Expand Up @@ -390,15 +392,16 @@ def resolve_all(self, resources, installer_context, implicit=False):
# TODO: resolutions dictionary should be replaced with resolution model instead of mapping (undefined) keys.
for resource_name in resources:
try:
rosdep_keys = self.get_rosdeps(resource_name, implicit=implicit)
rosdeps = self.get_rosdeps(resource_name, implicit=implicit)
if self.verbose:
print('resolve_all: resource [%s] requires rosdep keys [%s]' % (resource_name, ', '.join(rosdep_keys)), file=sys.stderr)
rosdep_keys = prune_catkin_packages(rosdep_keys, self.verbose)
rosdep_keys = prune_skipped_packages(rosdep_keys, self.skipped_keys, self.verbose)
for rosdep_key in rosdep_keys:
print('resolve_all: resource [%s] requires rosdep keys [%s]' % (resource_name, ', '.join([d.name for d in rosdeps])), file=sys.stderr)
rosdeps = prune_catkin_packages(rosdeps, self.verbose)
rosdeps = prune_skipped_packages(rosdeps, self.skipped_keys, self.verbose)
for rosdep in rosdeps:
try:
installer_key, resolution, dependencies = \
self.resolve(rosdep_key, resource_name, installer_context)
self.resolve(rosdep, resource_name, installer_context)
rosdep_key = rosdep.name
depend_graph[rosdep_key]['installer_key'] = installer_key
depend_graph[rosdep_key]['install_keys'] = list(resolution)
depend_graph[rosdep_key]['dependencies'] = list(dependencies)
Expand Down Expand Up @@ -430,13 +433,13 @@ def resolve_all(self, resources, installer_context, implicit=False):

return resolutions_flat, errors

def resolve(self, rosdep_key, resource_name, installer_context):
def resolve(self, rosdep, resource_name, installer_context):
"""
Resolve a :class:`RosdepDefinition` for a particular
os/version spec.
:param resource_name: resource (e.g. ROS package) to resolve key within
:param rosdep_key: rosdep key to resolve
:param rosdeps: rosdep key (catkin_pkg.package.Dependency object) to resolve
:param os_name: OS name to use for resolution
:param os_version: OS name to use for resolution
Expand All @@ -450,6 +453,7 @@ def resolve(self, rosdep_key, resource_name, installer_context):
:raises: :exc:`ResolutionError` If *rosdep_key* cannot be resolved for *resource_name* in *installer_context*
:raises: :exc:`rospkg.ResourceNotFound` if *resource_name* cannot be located
"""
rosdep_key = rosdep.name
os_name, os_version = installer_context.get_os_name_and_version()

view = self.get_rosdep_view_for_resource(resource_name)
Expand Down Expand Up @@ -489,7 +493,7 @@ def resolve(self, rosdep_key, resource_name, installer_context):
installer = installer_context.get_installer(installer_key)
except KeyError:
raise ResolutionError(rosdep_key, definition.data, os_name, os_version, 'Unsupported installer [%s]' % (installer_key))
resolution = installer.resolve(rosdep_args_dict)
resolution = installer.resolve(rosdep, rosdep_args_dict)
dependencies = installer.get_depends(rosdep_args_dict)

# cache value
Expand Down
2 changes: 1 addition & 1 deletion src/rosdep2/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ def command_keys(lookup, packages, options):
rosdep_keys = get_keys(lookup, packages, options.recursive)
prune_catkin_packages(rosdep_keys, options.verbose)
_print_lookup_errors(lookup)
print('\n'.join(rosdep_keys))
print('\n'.join([d.name for d in rosdep_keys]))


def get_keys(lookup, packages, recursive):
Expand Down
4 changes: 2 additions & 2 deletions src/rosdep2/platforms/osx.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def get_version_strings(self):
except OSError:
return ['Homebrew not-found']

def resolve(self, rosdep_args):
def resolve(self, rosdep, rosdep_args):
"""
See :meth:`Installer.resolve()`
"""
Expand Down Expand Up @@ -318,7 +318,7 @@ def handle_options(options):

return options

packages = super(HomebrewInstaller, self).resolve(rosdep_args)
packages = super(HomebrewInstaller, self).resolve(rosdep, rosdep_args)
resolution = []
if packages:
options = []
Expand Down
49 changes: 48 additions & 1 deletion src/rosdep2/platforms/pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import pkg_resources
import subprocess

from ..core import InstallFailed
from ..core import InstallFailed, InvalidData
from ..installers import PackageManagerInstaller
from ..shell_utils import read_stdout

Expand Down Expand Up @@ -100,6 +100,53 @@ class PipInstaller(PackageManagerInstaller):
def __init__(self):
super(PipInstaller, self).__init__(pip_detect, supports_depends=True)

def resolve(self, rosdep, rosdep_args):
"""
See :meth:`Installer.resolve()`
"""
packages = None
if type(rosdep_args) == dict:
packages = rosdep_args.get('packages', [])
if isinstance(packages, str):
packages = packages.split()
elif isinstance(rosdep_args, str):
packages = rosdep_args.split(' ')
elif type(rosdep_args) == list:
packages = rosdep_args
else:
raise InvalidData('Invalid rosdep args: %s' % (rosdep_args))

pip_specify_version = None
if rosdep.version_eq:
for i, package in list(enumerate(packages)):
packages[i] = package + '==' + rosdep.version_eq
pip_specify_version = True
if rosdep.version_gte:
for i, package in list(enumerate(packages)):
package = package + ',' if pip_specify_version else package
packages[i] = package + '>=' + rosdep.version_gte
pip_specify_version = True
if rosdep.version_lte:
for i, package in list(enumerate(packages)):
package = package + ',' if pip_specify_version else package
packages[i] = package + '<=' + rosdep.version_lte
pip_specify_version = True
if rosdep.version_gt:
for i, package in list(enumerate(packages)):
package = package + ',' if pip_specify_version else package
packages[i] = package + '>' + rosdep.version_gt
pip_specify_version = True
if rosdep.version_lt:
for i, package in list(enumerate(packages)):
package = package + ',' if pip_specify_version else package
packages[i] = package + '<' + rosdep.version_lt
pip_specify_version = True
if pip_specify_version:
for i, package in list(enumerate(packages)):
packages[i] = "'" + package + "'"

return packages

def get_version_strings(self):
pip_version = pkg_resources.get_distribution('pip').version
setuptools_version = pkg_resources.get_distribution('setuptools').version
Expand Down
4 changes: 2 additions & 2 deletions src/rosdep2/platforms/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def __init__(self):
super(SourceInstaller, self).__init__(source_detect, supports_depends=True)
self._rdmanifest_cache = {}

def resolve(self, rosdep_args):
def resolve(self, rosdep, rosdep_args):
"""
:raises: :exc:`InvalidData` If format invalid or unable
to retrieve rdmanifests.
Expand Down Expand Up @@ -246,7 +246,7 @@ def get_install_command(self, resolved, interactive=True, reinstall=False, quiet

def get_depends(self, rosdep_args):
deps = rosdep_args.get('depends', [])
for r in self.resolve(rosdep_args):
for r in self.resolve({}, rosdep_args):
deps.extend(r.dependencies)
return deps

Expand Down
27 changes: 24 additions & 3 deletions src/rosdep2/rospkg_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,35 @@ def get_rosdeps(self, resource_name, implicit=True):
:raises: :exc:`rospkg.ResourceNotFound` if *resource_name* cannot be found.
"""

if resource_name in self.get_catkin_paths():
pkg = catkin_pkg.package.parse_package(self.get_catkin_paths()[resource_name])
pkg.evaluate_conditions(os.environ)
deps = pkg.build_depends + pkg.buildtool_depends + pkg.run_depends + pkg.test_depends
return [d.name for d in deps if d.evaluated_condition]
return [d for d in deps if d.evaluated_condition]
elif resource_name in self.get_loadable_resources():
return self._rospack.get_rosdeps(resource_name, implicit=implicit)
# expand 'self._rospack.get_rosdeps(resource_name, implicit=implicit)' to return Dependency
def rospack_get_rosdeps(rospack, package, implicit=implicit):
if implicit:
return rospack_implicit_rosdeps(rospack, package)
else:
m = rospack.get_manifest(package)
return m.rosdeps

def rospack_implicit_rosdeps(rospack, package):
# set the key before recursive call to prevent infinite case
s = set()
# take the union of all dependencies
packages = rospack.get_depends(package, implicit=True)
for p in packages:
s.update(rospack_get_rosdeps(rospack, p, implicit=False))
# add in our own deps
m = rospack.get_manifest(package)
s.update(m.rosdeps)
# cache the return value as a list
s = list(s)
return s

return rospack_get_rosdeps(self._rospack, resource_name, implicit)
elif resource_name in self._rosstack.list():
# stacks currently do not have rosdeps of their own, implicit or otherwise
return []
Expand Down
22 changes: 12 additions & 10 deletions test/test_rosdep_installers.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ class FakeInstaller2(Installer):


def test_Installer_tripwire():
from catkin_pkg.package import Dependency
from rosdep2.installers import Installer
try:
Installer().is_installed('foo')
Expand All @@ -305,7 +306,7 @@ def test_Installer_tripwire():
except NotImplementedError:
pass
try:
Installer().resolve({})
Installer().resolve(Dependency('null'), {})
assert False
except NotImplementedError:
pass
Expand Down Expand Up @@ -340,26 +341,27 @@ def test_PackageManagerInstaller():


def test_PackageManagerInstaller_resolve():
from catkin_pkg.package import Dependency
from rosdep2 import InvalidData
from rosdep2.installers import PackageManagerInstaller

installer = PackageManagerInstaller(detect_fn_all)
assert ['baz'] == installer.resolve(dict(depends=['foo', 'bar'], packages=['baz']))
assert ['baz', 'bar'] == installer.resolve(dict(packages=['baz', 'bar']))
assert ['baz'] == installer.resolve(Dependency('baz'), dict(depends=['foo', 'bar'], packages=['baz']))
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), dict(packages=['baz', 'bar']))

# test string logic
assert ['baz'] == installer.resolve(dict(depends=['foo', 'bar'], packages='baz'))
assert ['baz', 'bar'] == installer.resolve(dict(packages='baz bar'))
assert ['baz'] == installer.resolve('baz')
assert ['baz', 'bar'] == installer.resolve('baz bar')
assert ['baz'] == installer.resolve(Dependency('baz'), dict(depends=['foo', 'bar'], packages='baz'))
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), dict(packages='baz bar'))
assert ['baz'] == installer.resolve(Dependency('baz'), 'baz')
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), 'baz bar')

# test list logic
assert ['baz'] == installer.resolve(['baz'])
assert ['baz', 'bar'] == installer.resolve(['baz', 'bar'])
assert ['baz'] == installer.resolve(Dependency('baz'), ['baz'])
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), ['baz', 'bar'])

# test invalid data
try:
installer.resolve(0)
installer.resolve(Dependency('baz'), 0)
assert False, 'should have raised'
except InvalidData:
pass
Expand Down
Loading

0 comments on commit 604affe

Please sign in to comment.