Skip to content

Commit d6a6cd2

Browse files
committed
support veresion specific install for pip (and others)
1 parent ab1f06e commit d6a6cd2

File tree

9 files changed

+148
-40
lines changed

9 files changed

+148
-40
lines changed

src/rosdep2/installers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,9 @@ def get_depends(self, rosdep_args):
278278
"""
279279
return [] # Default return empty list
280280

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

339-
def resolve(self, rosdep_args):
340+
def resolve(self, rosdep, rosdep_args):
340341
"""
341342
See :meth:`Installer.resolve()`
342343
"""

src/rosdep2/lookup.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,11 @@ def merge(self, update_entry, override=False, verbose=False):
226226
db[dep_name].reverse_merge(dep_data, update_entry.origin, verbose=verbose)
227227

228228

229-
def prune_catkin_packages(rosdep_keys, verbose=False):
229+
def prune_catkin_packages(rosdeps, verbose=False):
230230
workspace_pkgs = catkin_packages.get_workspace_packages()
231231
if not workspace_pkgs:
232-
return rosdep_keys
232+
return rosdeps
233+
rosdep_keys = [d.name for d in rosdeps]
233234
for i, rosdep_key in reversed(list(enumerate(rosdep_keys))):
234235
if rosdep_key in workspace_pkgs:
235236
# If workspace packages listed (--catkin-workspace)
@@ -239,22 +240,23 @@ def prune_catkin_packages(rosdep_keys, verbose=False):
239240
print("rosdep key '{0}'".format(rosdep_key) +
240241
' is in the catkin workspace, skipping.',
241242
file=sys.stderr)
242-
del rosdep_keys[i]
243-
return rosdep_keys
243+
del rosdeps[i]
244+
return rosdeps
244245

245246

246-
def prune_skipped_packages(rosdep_keys, skipped_keys, verbose=False):
247+
def prune_skipped_packages(rosdeps, skipped_keys, verbose=False):
247248
if not skipped_keys:
248-
return rosdep_keys
249+
return rosdeps
250+
rosdep_keys = [d.name for d in rosdeps]
249251
for i, rosdep_key in reversed(list(enumerate(rosdep_keys))):
250252
if rosdep_key in skipped_keys:
251253
# If the key is in the list of keys to explicitly skip, skip it
252254
if verbose:
253255
print("rosdep key '{0}'".format(rosdep_key) +
254256
' was listed in the skipped packages, skipping.',
255257
file=sys.stderr)
256-
del rosdep_keys[i]
257-
return rosdep_keys
258+
del rosdeps[i]
259+
return rosdeps
258260

259261

260262
class RosdepLookup(object):
@@ -390,15 +392,16 @@ def resolve_all(self, resources, installer_context, implicit=False):
390392
# TODO: resolutions dictionary should be replaced with resolution model instead of mapping (undefined) keys.
391393
for resource_name in resources:
392394
try:
393-
rosdep_keys = self.get_rosdeps(resource_name, implicit=implicit)
395+
rosdeps = self.get_rosdeps(resource_name, implicit=implicit)
394396
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:
397+
print('resolve_all: resource [%s] requires rosdep keys [%s]' % (resource_name, ', '.join([d.name for d in rosdeps])), file=sys.stderr)
398+
rosdeps = prune_catkin_packages(rosdeps, self.verbose)
399+
rosdeps = prune_skipped_packages(rosdeps, self.skipped_keys, self.verbose)
400+
for rosdep in rosdeps:
399401
try:
400402
installer_key, resolution, dependencies = \
401-
self.resolve(rosdep_key, resource_name, installer_context)
403+
self.resolve(rosdep, resource_name, installer_context)
404+
rosdep_key = rosdep.name
402405
depend_graph[rosdep_key]['installer_key'] = installer_key
403406
depend_graph[rosdep_key]['install_keys'] = list(resolution)
404407
depend_graph[rosdep_key]['dependencies'] = list(dependencies)
@@ -430,13 +433,13 @@ def resolve_all(self, resources, installer_context, implicit=False):
430433

431434
return resolutions_flat, errors
432435

433-
def resolve(self, rosdep_key, resource_name, installer_context):
436+
def resolve(self, rosdep, resource_name, installer_context):
434437
"""
435438
Resolve a :class:`RosdepDefinition` for a particular
436439
os/version spec.
437440
438441
:param resource_name: resource (e.g. ROS package) to resolve key within
439-
:param rosdep_key: rosdep key to resolve
442+
:param rosdeps: rosdep key (catkin_pkg.package.Dependency object) to resolve
440443
:param os_name: OS name to use for resolution
441444
:param os_version: OS name to use for resolution
442445
@@ -450,6 +453,7 @@ def resolve(self, rosdep_key, resource_name, installer_context):
450453
:raises: :exc:`ResolutionError` If *rosdep_key* cannot be resolved for *resource_name* in *installer_context*
451454
:raises: :exc:`rospkg.ResourceNotFound` if *resource_name* cannot be located
452455
"""
456+
rosdep_key = rosdep.name
453457
os_name, os_version = installer_context.get_os_name_and_version()
454458

455459
view = self.get_rosdep_view_for_resource(resource_name)
@@ -489,7 +493,7 @@ def resolve(self, rosdep_key, resource_name, installer_context):
489493
installer = installer_context.get_installer(installer_key)
490494
except KeyError:
491495
raise ResolutionError(rosdep_key, definition.data, os_name, os_version, 'Unsupported installer [%s]' % (installer_key))
492-
resolution = installer.resolve(rosdep_args_dict)
496+
resolution = installer.resolve(rosdep, rosdep_args_dict)
493497
dependencies = installer.get_depends(rosdep_args_dict)
494498

495499
# cache value

src/rosdep2/platforms/osx.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ def get_version_strings(self):
281281
except OSError:
282282
return ['Homebrew not-found']
283283

284-
def resolve(self, rosdep_args):
284+
def resolve(self, rosdep, rosdep_args):
285285
"""
286286
See :meth:`Installer.resolve()`
287287
"""

src/rosdep2/platforms/pip.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import pkg_resources
3333
import subprocess
3434

35-
from ..core import InstallFailed
35+
from ..core import InstallFailed, InvalidData
3636
from ..installers import PackageManagerInstaller
3737
from ..shell_utils import read_stdout
3838

@@ -100,6 +100,53 @@ class PipInstaller(PackageManagerInstaller):
100100
def __init__(self):
101101
super(PipInstaller, self).__init__(pip_detect, supports_depends=True)
102102

103+
def resolve(self, rosdep, rosdep_args):
104+
"""
105+
See :meth:`Installer.resolve()`
106+
"""
107+
packages = None
108+
if type(rosdep_args) == dict:
109+
packages = rosdep_args.get('packages', [])
110+
if isinstance(packages, str):
111+
packages = packages.split()
112+
elif isinstance(rosdep_args, str):
113+
packages = rosdep_args.split(' ')
114+
elif type(rosdep_args) == list:
115+
packages = rosdep_args
116+
else:
117+
raise InvalidData('Invalid rosdep args: %s' % (rosdep_args))
118+
119+
pip_specify_version = None
120+
if rosdep.version_eq:
121+
for i, package in list(enumerate(packages)):
122+
packages[i] = package + '==' + rosdep.version_eq
123+
pip_specify_version = True
124+
if rosdep.version_gte:
125+
for i, package in list(enumerate(packages)):
126+
package = package + ',' if pip_specify_version else package
127+
packages[i] = package + '>=' + rosdep.version_gte
128+
pip_specify_version = True
129+
if rosdep.version_lte:
130+
for i, package in list(enumerate(packages)):
131+
package = package + ',' if pip_specify_version else package
132+
packages[i] = package + '<=' + rosdep.version_lte
133+
pip_specify_version = True
134+
if rosdep.version_gt:
135+
for i, package in list(enumerate(packages)):
136+
package = package + ',' if pip_specify_version else package
137+
packages[i] = package + '>' + rosdep.version_gt
138+
pip_specify_version = True
139+
if rosdep.version_lt:
140+
for i, package in list(enumerate(packages)):
141+
package = package + ',' if pip_specify_version else package
142+
packages[i] = package + '<' + rosdep.version_lt
143+
pip_specify_version = True
144+
if pip_specify_version:
145+
for i, package in list(enumerate(packages)):
146+
packages[i] = "'" + package + "'"
147+
148+
return packages
149+
103150
def get_version_strings(self):
104151
pip_version = pkg_resources.get_distribution('pip').version
105152
setuptools_version = pkg_resources.get_distribution('setuptools').version

src/rosdep2/platforms/source.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def __init__(self):
201201
super(SourceInstaller, self).__init__(source_detect, supports_depends=True)
202202
self._rdmanifest_cache = {}
203203

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

247247
def get_depends(self, rosdep_args):
248248
deps = rosdep_args.get('depends', [])
249-
for r in self.resolve(rosdep_args):
249+
for r in self.resolve({}, rosdep_args):
250250
deps.extend(r.dependencies)
251251
return deps
252252

src/rosdep2/rospkg_loader.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,26 @@ def get_rosdeps(self, resource_name, implicit=True):
136136
137137
:raises: :exc:`rospkg.ResourceNotFound` if *resource_name* cannot be found.
138138
"""
139-
140139
if resource_name in self.get_catkin_paths():
141140
pkg = catkin_pkg.package.parse_package(self.get_catkin_paths()[resource_name])
142141
pkg.evaluate_conditions(os.environ)
143142
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]
143+
return [d for d in deps if d.evaluated_condition]
145144
elif resource_name in self.get_loadable_resources():
146-
return self._rospack.get_rosdeps(resource_name, implicit=implicit)
145+
if implicit:
146+
s = set()
147+
packages = self._rospack.get_depends(resource_name, implicit=True)
148+
for p in packages:
149+
s.update(self._rospack.get_rosdeps(p, implicit=False))
150+
# add in our own deps
151+
m = self._rospack.get_manifest(resource_name)
152+
s.update(m.rosdeps)
153+
# cache the return value as a list
154+
s = list(s)
155+
return s
156+
else:
157+
m = self._rospack.get_manifest(resource_name)
158+
return m.rosdeps
147159
elif resource_name in self._rosstack.list():
148160
# stacks currently do not have rosdeps of their own, implicit or otherwise
149161
return []

test/test_rosdep_installers.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ class FakeInstaller2(Installer):
293293

294294

295295
def test_Installer_tripwire():
296+
from catkin_pkg.package import Dependency
296297
from rosdep2.installers import Installer
297298
try:
298299
Installer().is_installed('foo')
@@ -305,7 +306,7 @@ def test_Installer_tripwire():
305306
except NotImplementedError:
306307
pass
307308
try:
308-
Installer().resolve({})
309+
Installer().resolve(Dependency('null'), {})
309310
assert False
310311
except NotImplementedError:
311312
pass
@@ -340,26 +341,27 @@ def test_PackageManagerInstaller():
340341

341342

342343
def test_PackageManagerInstaller_resolve():
344+
from catkin_pkg.package import Dependency
343345
from rosdep2 import InvalidData
344346
from rosdep2.installers import PackageManagerInstaller
345347

346348
installer = PackageManagerInstaller(detect_fn_all)
347-
assert ['baz'] == installer.resolve(dict(depends=['foo', 'bar'], packages=['baz']))
348-
assert ['baz', 'bar'] == installer.resolve(dict(packages=['baz', 'bar']))
349+
assert ['baz'] == installer.resolve(Dependency('baz'), dict(depends=['foo', 'bar'], packages=['baz']))
350+
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), dict(packages=['baz', 'bar']))
349351

350352
# test string logic
351-
assert ['baz'] == installer.resolve(dict(depends=['foo', 'bar'], packages='baz'))
352-
assert ['baz', 'bar'] == installer.resolve(dict(packages='baz bar'))
353-
assert ['baz'] == installer.resolve('baz')
354-
assert ['baz', 'bar'] == installer.resolve('baz bar')
353+
assert ['baz'] == installer.resolve(Dependency('baz'), dict(depends=['foo', 'bar'], packages='baz'))
354+
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), dict(packages='baz bar'))
355+
assert ['baz'] == installer.resolve(Dependency('baz'), 'baz')
356+
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), 'baz bar')
355357

356358
# test list logic
357-
assert ['baz'] == installer.resolve(['baz'])
358-
assert ['baz', 'bar'] == installer.resolve(['baz', 'bar'])
359+
assert ['baz'] == installer.resolve(Dependency('baz'), ['baz'])
360+
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), ['baz', 'bar'])
359361

360362
# test invalid data
361363
try:
362-
installer.resolve(0)
364+
installer.resolve(Dependency('baz'), 0)
363365
assert False, 'should have raised'
364366
except InvalidData:
365367
pass

test/test_rosdep_pip.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,47 @@ def test_PipInstaller_get_depends():
6666
assert ['foo'] == installer.get_depends(dict(depends=['foo']))
6767

6868

69+
def test_PackageManagerInstaller_resolve():
70+
from rosdep2 import InvalidData
71+
from rosdep2.platforms.pip import PipInstaller
72+
from catkin_pkg.package import Dependency
73+
74+
installer = PipInstaller()
75+
assert ['baz'] == installer.resolve(Dependency('baz'), dict(depends=['foo', 'bar'], packages=['baz']))
76+
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), dict(packages=['baz', 'bar']))
77+
78+
# test string logic
79+
assert ['baz'] == installer.resolve(Dependency('baz'), dict(depends=['foo', 'bar'], packages='baz'))
80+
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), dict(packages='baz bar'))
81+
assert ['baz'] == installer.resolve(Dependency('baz'), 'baz')
82+
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), 'baz bar')
83+
84+
# test list logic
85+
assert ['baz'] == installer.resolve(Dependency('baz'), ['baz'])
86+
assert ['baz', 'bar'] == installer.resolve(Dependency('baz'), ['baz', 'bar'])
87+
88+
# version_eq
89+
assert ["'baz==1.0'"] == installer.resolve(Dependency('baz', version_eq='1.0'), dict(depends=['foo', 'bar'], packages=['baz']))
90+
assert ["'baz==1.0'", "'bar==1.0'"] == installer.resolve(Dependency('baz', version_eq='1.0'), dict(packages=['baz', 'bar']))
91+
# version_gte
92+
assert ["'baz>=1.0'"] == installer.resolve(Dependency('baz', version_gte='1.0'), dict(depends=['foo', 'bar'], packages=['baz']))
93+
assert ["'baz>=1.0'", "'bar>=1.0'"] == installer.resolve(Dependency('baz', version_gte='1.0'), dict(packages=['baz', 'bar']))
94+
# version_lte
95+
assert ["'baz<=1.0'"] == installer.resolve(Dependency('baz', version_lte='1.0'), dict(depends=['foo', 'bar'], packages=['baz']))
96+
assert ["'baz<=1.0'", "'bar<=1.0'"] == installer.resolve(Dependency('baz', version_lte='1.0'), dict(packages=['baz', 'bar']))
97+
# version_gt
98+
assert ["'baz>1.0'"] == installer.resolve(Dependency('baz', version_gt='1.0'), dict(depends=['foo', 'bar'], packages=['baz']))
99+
assert ["'baz>1.0'", "'bar>1.0'"] == installer.resolve(Dependency('baz', version_gt='1.0'), dict(packages=['baz', 'bar']))
100+
# version_lt
101+
assert ["'baz<1.0'"] == installer.resolve(Dependency('baz', version_lt='1.0'), dict(depends=['foo', 'bar'], packages=['baz']))
102+
assert ["'baz<1.0'", "'bar<1.0'"] == installer.resolve(Dependency('baz', version_lt='1.0'), dict(packages=['baz', 'bar']))
103+
# test invalid data
104+
try:
105+
installer.resolve({}, 0)
106+
assert False, 'should have raised'
107+
except InvalidData:
108+
pass
109+
69110
def test_PipInstaller():
70111
from rosdep2 import InstallFailed
71112
from rosdep2.platforms.pip import PipInstaller

test/test_rosdep_source.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ def test_SourceInstaller_get_install_command():
191191

192192

193193
def test_SourceInstaller_resolve():
194+
from catkin_pkg.package import Dependency
194195
from rosdep2.platforms.source import SourceInstaller, InvalidData
195196
test_dir = get_test_dir()
196197

@@ -200,16 +201,16 @@ def test_SourceInstaller_resolve():
200201

201202
installer = SourceInstaller()
202203
try:
203-
installer.resolve({})
204+
installer.resolve(Dependency('null'),{})
204205
assert False, 'should have raised'
205206
except InvalidData:
206207
pass
207208
try:
208-
installer.resolve(dict(uri=url, md5sum=md5sum_bad))
209+
installer.resolve(Dependency('null'),dict(uri=url, md5sum=md5sum_bad))
209210
assert False, 'should have raised'
210211
except InvalidData:
211212
pass
212-
resolved = installer.resolve(dict(uri=url, md5sum=md5sum_good))
213+
resolved = installer.resolve(Dependency('null'),dict(uri=url, md5sum=md5sum_good))
213214

214215
assert type(resolved) == list
215216
assert len(resolved) == 1
@@ -222,7 +223,7 @@ def test_SourceInstaller_resolve():
222223
assert resolved.check_presence_command == rep122_check_presence_command
223224

224225
# test again to activate caching
225-
resolved = installer.resolve(dict(uri=url, md5sum=md5sum_good))
226+
resolved = installer.resolve(Dependency('null'),dict(uri=url, md5sum=md5sum_good))
226227
assert type(resolved) == list, 'Cache should also return a list'
227228
assert len(resolved) == 1
228229
resolved = resolved[0]

0 commit comments

Comments
 (0)