Skip to content

Commit

Permalink
Catkin plugin: Support ROS Kinetic. (#842)
Browse files Browse the repository at this point in the history
This involves refactoring the repository source handling into a function
that could be re-implemented by subclasses, as well as adding a map of
ROS releases to Ubuntu releases.

LP: #1629003

Signed-off-by: Kyle Fazzari <[email protected]>
  • Loading branch information
kyrofa authored and sergiusens committed Sep 30, 2016
1 parent 2d429cf commit 5d447ae
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 84 deletions.
2 changes: 1 addition & 1 deletion snapcraft/_baseplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def schema(cls):

@property
def PLUGIN_STAGE_SOURCES(self):
"""Define additional sources.list."""
"""Define alternative sources.list."""
return getattr(self, '_PLUGIN_STAGE_SOURCES', [])

def __init__(self, name, options, project=None):
Expand Down
37 changes: 24 additions & 13 deletions snapcraft/plugins/catkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,15 @@

logger = logging.getLogger(__name__)

# Map ROS releases to Ubuntu releases
_ROS_RELEASE_MAP = {
'indigo': 'trusty',
'jade': 'trusty',
'kinetic': 'xenial'
}

class CatkinPlugin(snapcraft.BasePlugin):

_PLUGIN_STAGE_SOURCES = '''
deb http://packages.ros.org/ros/ubuntu/ trusty main
deb http://${prefix}.ubuntu.com/${suffix}/ trusty main universe
deb http://${prefix}.ubuntu.com/${suffix}/ trusty-updates main universe
deb http://${prefix}.ubuntu.com/${suffix}/ trusty-security main universe
deb http://${security}.ubuntu.com/${suffix} trusty-security main universe
'''
class CatkinPlugin(snapcraft.BasePlugin):

@classmethod
def schema(cls):
Expand Down Expand Up @@ -100,6 +99,16 @@ def schema(cls):

return schema

@property
def PLUGIN_STAGE_SOURCES(self):
return """
deb http://packages.ros.org/ros/${{suffix}}/ {0} main
deb http://${{prefix}}.ubuntu.com/${{suffix}}/ {0} main universe
deb http://${{prefix}}.ubuntu.com/${{suffix}}/ {0}-updates main universe
deb http://${{prefix}}.ubuntu.com/${{suffix}}/ {0}-security main universe
deb http://${{security}}.ubuntu.com/${{suffix}} {0}-security main universe
""".format(_ROS_RELEASE_MAP[self.options.rosdistro])

def __init__(self, name, options, project):
super().__init__(name, options, project)
self.build_packages.extend(['gcc', 'libc6-dev', 'make'])
Expand Down Expand Up @@ -507,12 +516,14 @@ def resolve_dependency(self, dependency_name):
#
# 1) The dependency we're trying to lookup.
# 2) The rosdistro being used.
# 3) The version of Ubuntu being used. We're currently using only
# the Trusty ROS sources, so we're telling rosdep to resolve
# dependencies using Trusty (even if we're running on something
# else).
# 3) The version of Ubuntu being used. We're telling rosdep to
# resolve dependencies using the version of Ubuntu that
# corresponds to the ROS release (even if we're running on
# something else).
output = self._run(['resolve', dependency_name, '--rosdistro',
self._ros_distro, '--os', 'ubuntu:trusty'])
self._ros_distro, '--os',
'ubuntu:{}'.format(
_ROS_RELEASE_MAP[self._ros_distro])])
except subprocess.CalledProcessError:
raise SystemDependencyNotFound(
'{!r} does not resolve to a system dependency'.format(
Expand Down
164 changes: 94 additions & 70 deletions snapcraft/tests/test_plugin_catkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,24 @@ def test_schema(self):
self.assertTrue('source-space' in pull_properties)
self.assertTrue('include-roscore' in pull_properties)

def test_get_stage_sources_indigo(self):
self.properties.rosdistro = 'indigo'
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)
self.assertTrue('trusty' in plugin.PLUGIN_STAGE_SOURCES)

def test_get_stage_sources_jade(self):
self.properties.rosdistro = 'jade'
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)
self.assertTrue('trusty' in plugin.PLUGIN_STAGE_SOURCES)

def test_get_stage_sources_kinetic(self):
self.properties.rosdistro = 'kinetic'
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)
self.assertTrue('xenial' in plugin.PLUGIN_STAGE_SOURCES)

def test_pull_debian_dependencies(self):
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)
Expand Down Expand Up @@ -539,55 +557,7 @@ def test_build_accounts_for_source_subdir(self, finish_mock, prepare_mock):

self.assertTrue(os.path.isdir(os.path.join(plugin.builddir, 'foo')))

def test_prepare_build(self):
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)
os.makedirs(os.path.join(plugin.rosdir, 'test'))

# Place a few .cmake files with incorrect paths, and some files that
# shouldn't be changed.
files = [
{
'path': 'fooConfig.cmake',
'contents': '"/usr/lib/foo"',
'expected': '"{}/usr/lib/foo"'.format(plugin.installdir),
},
{
'path': 'bar.cmake',
'contents': '"/usr/lib/bar"',
'expected': '"/usr/lib/bar"',
},
{
'path': 'test/bazConfig.cmake',
'contents': '"/test/baz;/usr/lib/baz"',
'expected': '"{0}/test/baz;{0}/usr/lib/baz"'.format(
plugin.installdir),
},
{
'path': 'test/quxConfig.cmake',
'contents': 'qux',
'expected': 'qux',
},
{
'path': 'test/installedConfig.cmake',
'contents': '"{}/foo"'.format(plugin.installdir),
'expected': '"{}/foo"'.format(plugin.installdir),
}
]

for fileInfo in files:
with open(os.path.join(plugin.rosdir, fileInfo['path']), 'w') as f:
f.write(fileInfo['contents'])

plugin._prepare_build()

for fileInfo in files:
with open(os.path.join(plugin.rosdir, fileInfo['path']), 'r') as f:
self.assertEqual(f.read(), fileInfo['expected'])

@mock.patch.object(catkin.CatkinPlugin, 'run')
@mock.patch.object(catkin.CatkinPlugin, 'run_output', return_value='foo')
def test_finish_build_python_shebangs(self, run_output_mock, run_mock):
def test_use_in_snap_python_rewrites_shebangs(self):
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)
os.makedirs(os.path.join(plugin.rosdir, 'bin'))
Expand Down Expand Up @@ -616,7 +586,7 @@ def test_finish_build_python_shebangs(self, run_output_mock, run_mock):
with open(file_info['path'], 'w') as f:
f.write(file_info['contents'])

plugin._finish_build()
plugin._use_in_snap_python()

for file_info in files:
with open(os.path.join(plugin.rosdir,
Expand All @@ -625,7 +595,25 @@ def test_finish_build_python_shebangs(self, run_output_mock, run_mock):

@mock.patch.object(catkin.CatkinPlugin, 'run')
@mock.patch.object(catkin.CatkinPlugin, 'run_output', return_value='foo')
def test_finish_build_absolute_python(self, run_output_mock, run_mock):
def test_use_in_snap_python_skips_binarys(self, run_output_mock, run_mock):
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)
os.makedirs(plugin.rosdir)

# Place a file to be discovered by _use_in_snap_python().
open(os.path.join(plugin.rosdir, 'foo'), 'w').close()

file_mock = mock.mock_open()
with mock.patch.object(builtins, 'open', file_mock):
# Reading a binary file may throw a UnicodeDecodeError. Make sure
# that's handled.
file_mock.return_value.read.side_effect = UnicodeDecodeError(
'foo', b'bar', 1, 2, 'baz')
# An exception will be raised if the function can't handle the
# binary file.
plugin._use_in_snap_python()

def test_use_in_snap_python_rewrites_10_ros_sh(self):
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)
os.makedirs(os.path.join(plugin.rosdir, 'etc', 'catkin', 'profile.d'))
Expand All @@ -637,36 +625,70 @@ def test_finish_build_absolute_python(self, run_output_mock, run_mock):
with open(ros_profile, 'w') as f:
f.write('/usr/bin/python foo')

plugin._finish_build()
plugin._use_in_snap_python()

# Verify that the absolute path in 10.ros.sh was rewritten correctly
with open(ros_profile, 'r') as f:
self.assertEqual(f.read(), 'python foo',
'The absolute path to python was not replaced as '
'expected')

@mock.patch.object(catkin.CatkinPlugin, 'run')
@mock.patch.object(catkin.CatkinPlugin, 'run_output', return_value='foo')
def test_finish_build_binary(self, run_output_mock, run_mock):
@mock.patch.object(catkin.CatkinPlugin, '_use_in_snap_python')
def test_prepare_build(self, use_in_snap_python_mock):
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)
os.makedirs(plugin.rosdir)
os.makedirs(os.path.join(plugin.rosdir, 'test'))

# Place a file to be discovered by _finish_build().
open(os.path.join(plugin.rosdir, 'foo'), 'w').close()
# Place a few .cmake files with incorrect paths, and some files that
# shouldn't be changed.
files = [
{
'path': 'fooConfig.cmake',
'contents': '"/usr/lib/foo"',
'expected': '"{}/usr/lib/foo"'.format(plugin.installdir),
},
{
'path': 'bar.cmake',
'contents': '"/usr/lib/bar"',
'expected': '"/usr/lib/bar"',
},
{
'path': 'test/bazConfig.cmake',
'contents': '"/test/baz;/usr/lib/baz"',
'expected': '"{0}/test/baz;{0}/usr/lib/baz"'.format(
plugin.installdir),
},
{
'path': 'test/quxConfig.cmake',
'contents': 'qux',
'expected': 'qux',
},
{
'path': 'test/installedConfig.cmake',
'contents': '"{}/foo"'.format(plugin.installdir),
'expected': '"{}/foo"'.format(plugin.installdir),
}
]

file_mock = mock.mock_open()
with mock.patch.object(builtins, 'open', file_mock):
# Reading a binary file may throw a UnicodeDecodeError. Make sure
# that's handled.
file_mock.return_value.read.side_effect = UnicodeDecodeError(
'foo', b'bar', 1, 2, 'baz')
# An exception will be raise if build can't handle the binary file.
plugin._finish_build()
for file_info in files:
path = os.path.join(plugin.rosdir, file_info['path'])
with open(path, 'w') as f:
f.write(file_info['contents'])

plugin._prepare_build()

self.assertTrue(use_in_snap_python_mock.called)

for file_info in files:
path = os.path.join(plugin.rosdir, file_info['path'])
with open(path, 'r') as f:
self.assertEqual(f.read(), file_info['expected'])

@mock.patch.object(catkin.CatkinPlugin, 'run')
@mock.patch.object(catkin.CatkinPlugin, 'run_output', return_value='foo')
def test_finish_build_cmake_prefix_path(self, run_output_mock, run_mock):
@mock.patch.object(catkin.CatkinPlugin, '_use_in_snap_python')
def test_finish_build_cmake_prefix_path(self, use_in_snap_python_mock,
run_output_mock, run_mock):
plugin = catkin.CatkinPlugin('test-part', self.properties,
self.project_options)

Expand All @@ -679,6 +701,8 @@ def test_finish_build_cmake_prefix_path(self, run_output_mock, run_mock):

plugin._finish_build()

self.assertTrue(use_in_snap_python_mock.called)

expected = 'CMAKE_PREFIX_PATH = []\n'

with open(setup_file, 'r') as f:
Expand Down Expand Up @@ -807,7 +831,7 @@ class RosdepTestCase(tests.TestCase):
def setUp(self):
super().setUp()
self.project = snapcraft.ProjectOptions()
self.rosdep = catkin._Rosdep('ros_distro', 'package_path',
self.rosdep = catkin._Rosdep('kinetic', 'package_path',
'rosdep_path', 'sources',
self.project)

Expand Down Expand Up @@ -908,8 +932,8 @@ def test_resolve_dependency(self):
self.assertEqual(self.rosdep.resolve_dependency('foo'), ['mylib-dev'])

self.check_output_mock.assert_called_with(
['rosdep', 'resolve', 'foo', '--rosdistro', 'ros_distro', '--os',
'ubuntu:trusty'],
['rosdep', 'resolve', 'foo', '--rosdistro', 'kinetic', '--os',
'ubuntu:xenial'],
env=mock.ANY)

def test_resolve_invalid_dependency(self):
Expand Down

0 comments on commit 5d447ae

Please sign in to comment.