Skip to content

Commit

Permalink
Catkin plugin: Add support for ROS tools.
Browse files Browse the repository at this point in the history
The current Catkin plugin distributes only the built version of
a ROS package that would typically go into the devel workspace,
but that version doesn't include non-source, non-binary files,
such as .launch files. This commit changes the plugin to actually
install ROS packages, thereby including any non-source, non-binary
files specified by the developer.

This commit also updates the wrapper environment to correctly
setup the ROS environment, which allows for the use of typical
ROS tools within the snapcraft.yaml, such as roslaunch, rosrun,
etc., and updates the ROS examples to make use of them.

This commit also increases the test coverage of the Catkin plugin
twofold.

Signed-off-by: Kyle Fazzari <kyle@canonical.com>
  • Loading branch information
kyrofa committed Dec 5, 2015
1 parent cee6740 commit c2e8bfe
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 28 deletions.
4 changes: 2 additions & 2 deletions examples/ros/snapcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ description: A really small ROS example

binaries:
listener:
exec: opt/ros/indigo/beginner_tutorials/lib/beginner_tutorials/listener
exec: rosrun beginner_tutorials listener
talker:
exec: opt/ros/indigo/beginner_tutorials/lib/beginner_tutorials/talker
exec: rosrun beginner_tutorials talker

services:
rosmaster:
Expand Down
11 changes: 6 additions & 5 deletions examples/ros/src/beginner_tutorials/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# %Tag(FULLTEXT)%
cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)

Expand All @@ -25,7 +24,6 @@ add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})

## Build service client and server
# %Tag(SRVCLIENT)%
add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server beginner_tutorials_gencpp)
Expand All @@ -34,6 +32,9 @@ add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client beginner_tutorials_gencpp)

# %EndTag(SRVCLIENT)%

# %EndTag(FULLTEXT)%
## Mark executables for installation
install(TARGETS talker listener add_two_ints_server add_two_ints_client
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
49 changes: 29 additions & 20 deletions snapcraft/plugins/catkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import tempfile
import logging
import shutil
import re

import snapcraft
from snapcraft import repo
Expand Down Expand Up @@ -91,7 +92,6 @@ def env(self, root):
'lib',
self.python_version,
'dist-packages')),
'DESTDIR={0}'.format(self.installdir),
# ROS needs it but doesn't set it :-/
'CPPFLAGS="-std=c++11 $CPPFLAGS -I{0} -I{1}"'.format(
os.path.join(root, 'usr', 'include', 'c++', self.gcc_version),
Expand All @@ -105,6 +105,10 @@ def env(self, root):
root,
self.options.rosversion),
'ROS_MASTER_URI=http://localhost:11311',

# Various ROS tools (e.g. rospack) keep a cache, and they determine
# where the cache goes using $ROS_HOME.
'ROS_HOME=$SNAP_APP_USER_DATA_PATH/.ros',
'_CATKIN_SETUP_DIR={}'.format(os.path.join(
root, 'opt', 'ros', self.options.rosversion)),
'echo FOO=BAR\nif `test -e {0}` ; then\n. {0} ;\nfi\n'.format(
Expand Down Expand Up @@ -239,20 +243,19 @@ def build(self):
self._find_package_deps()
self._build_packages_deps()

# the hacks
findcmd = ['find', self.installdir, '-name', '*.cmake', '-delete']
self.run(findcmd)

self.run(
['rm', '-f',
'opt/ros/' + self.options.rosversion + '/.catkin',
'opt/ros/' + self.options.rosversion + '/.rosinstall',
'opt/ros/' + self.options.rosversion + '/setup.sh',
'opt/ros/' + self.options.rosversion + '/_setup_util.py'],
cwd=self.installdir)

# FIXME: Removing this here since it'll clash from the one with roscore.
# Remove this line once the roscore plugin no longer exists.
os.remove(os.path.join(self.installdir, 'usr/bin/xml2-config'))

# Fix the shebang in _setup_util.py to be portable
with open('{}/opt/ros/{}/_setup_util.py'.format(
self.installdir, self.options.rosversion), 'r+') as f:
pattern = re.compile(r'#!.*python')
replaced = pattern.sub(r'#!/usr/bin/env python', f.read())
f.seek(0)
f.truncate()
f.write(replaced)

def _build_packages_deps(self):
# Ugly dependency resolution, just loop through until we can
# find something to build. When we do, build it. Loop until we
Expand All @@ -276,26 +279,32 @@ def _build_packages_deps(self):
def _handle_package(self, pkg):
catkincmd = ['catkin_make_isolated']

# Install the package
catkincmd.append('--install')

# Specify the package to be built
catkincmd.append('--pkg')
catkincmd.append(pkg)

# Define the location
# Don't clutter the real ROS workspace-- use the Snapcraft build
# directory
catkincmd.extend(['--directory', self.builddir])

# Specify that the package should be installed along with the rest of
# the ROS distro.
catkincmd.extend(['--install-space', self.rosdir])

# Start the CMake Commands
catkincmd.append('--cmake-args')

# CMake directories
catkincmd.append('-DCATKIN_DEVEL_PREFIX={}'.format(self.rosdir))
catkincmd.append('-DCMAKE_INSTALL_PREFIX={}'.format(self.installdir))

# Dep CMake files
# Make sure all ROS dependencies can be found, even without the
# workspace setup
for dep in self.dependencies:
catkincmd.append('-D{0}_DIR={1}'.format(
dep.replace('-', '_'),
os.path.join(self.rosdir, 'share', dep, 'cmake')))

# Compiler fun
# Make sure we're using the compilers included in this .snap
catkincmd.extend([
'-DCMAKE_C_FLAGS="$CFLAGS"',
'-DCMAKE_CXX_FLAGS="$CPPFLAGS"',
Expand Down
76 changes: 75 additions & 1 deletion snapcraft/tests/test_plugin_catkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,90 @@ class _IOError(IOError):
errno = os.errno.EACCES


class CatkinTestCase(tests.TestCase):
class CatkinPluginTestCase(tests.TestCase):

def setUp(self):
super().setUp()

class props:
rosversion = 'foo'
catkin_packages = ['my_package']

self.properties = props()

patcher = mock.patch('snapcraft.repo.Ubuntu')
self.ubuntu_mock = patcher.start()
self.addCleanup(patcher.stop)

def test_pull_debian_dependencies(self):
plugin = catkin.CatkinPlugin('test-part', self.properties)

tmpdir = tempfile.TemporaryDirectory()
self.addCleanup(tmpdir.cleanup)
plugin.sourcedir = tmpdir.name

# Create ROS package directory
os.mkdir(os.path.join(plugin.sourcedir, "my_package"))

# Now create my_package's package.xml:
with open(os.path.join(plugin.sourcedir, "my_package",
"package.xml"), 'w') as f:
f.write("""<?xml version="1.0"?>
<package>
<buildtool_depend>buildtool_depend</buildtool_depend>
<build_depend>build_depend</build_depend>
<run_depend>run_depend</run_depend>
</package>""")

plugin.pull()

self.ubuntu_mock.assert_has_calls([
mock.call().get([
'ros-foo-buildtool-depend',
'ros-foo-build-depend',
'ros-foo-run-depend']),
mock.call().unpack(plugin.installdir)])

def test_pull_local_dependencies(self):
self.properties.catkin_packages.append('package_2')

plugin = catkin.CatkinPlugin('test-part', self.properties)

tmpdir = tempfile.TemporaryDirectory()
self.addCleanup(tmpdir.cleanup)
plugin.sourcedir = tmpdir.name

# Create ROS package directory for both packages
os.mkdir(os.path.join(plugin.sourcedir, "my_package"))
os.mkdir(os.path.join(plugin.sourcedir, "package_2"))

# Now create my_package's package.xml, specifying that is depends upon
# package_2:
with open(os.path.join(plugin.sourcedir, "my_package",
"package.xml"), 'w') as f:
f.write("""<?xml version="1.0"?>
<package>
<build_depend>package_2</build_depend>
</package>""")

# Finally, create package_2's package.xml, specifying that is has no
# dependencies:
with open(os.path.join(plugin.sourcedir, "package_2",
"package.xml"), 'w') as f:
f.write("""<?xml version="1.0"?>
<package>
</package>""")

plugin.pull()

self.assertTrue('my_package' in plugin.package_local_deps,
'Expected "my_package" to be in the dependencies')
self.assertEqual(plugin.package_local_deps['my_package'],
{'package_2'},
'Expected "my_package" to depend upon "package_2"')
self.assertFalse(self.ubuntu_mock.called,
"Ubuntu packages were unexpectedly pulled down")

def test_log_warning_when_unable_to_find_a_catkin_package(self):
fake_logger = fixtures.FakeLogger()
self.useFixture(fake_logger)
Expand Down

0 comments on commit c2e8bfe

Please sign in to comment.