From c2e8bfeb92724a839f105b50474576e1abce3622 Mon Sep 17 00:00:00 2001 From: Kyle Fazzari Date: Thu, 3 Dec 2015 15:17:52 +0000 Subject: [PATCH] Catkin plugin: Add support for ROS tools. 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 --- examples/ros/snapcraft.yaml | 4 +- .../ros/src/beginner_tutorials/CMakeLists.txt | 11 +-- snapcraft/plugins/catkin.py | 49 +++++++----- snapcraft/tests/test_plugin_catkin.py | 76 ++++++++++++++++++- 4 files changed, 112 insertions(+), 28 deletions(-) diff --git a/examples/ros/snapcraft.yaml b/examples/ros/snapcraft.yaml index e4932ce03c1..2559ff9dd49 100644 --- a/examples/ros/snapcraft.yaml +++ b/examples/ros/snapcraft.yaml @@ -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: diff --git a/examples/ros/src/beginner_tutorials/CMakeLists.txt b/examples/ros/src/beginner_tutorials/CMakeLists.txt index d42f08453bc..f2ddb963fcc 100644 --- a/examples/ros/src/beginner_tutorials/CMakeLists.txt +++ b/examples/ros/src/beginner_tutorials/CMakeLists.txt @@ -1,4 +1,3 @@ -# %Tag(FULLTEXT)% cmake_minimum_required(VERSION 2.8.3) project(beginner_tutorials) @@ -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) @@ -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)% \ No newline at end of file +## 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} +) diff --git a/snapcraft/plugins/catkin.py b/snapcraft/plugins/catkin.py index 4d7ff436eae..f9366eb6ea8 100644 --- a/snapcraft/plugins/catkin.py +++ b/snapcraft/plugins/catkin.py @@ -32,6 +32,7 @@ import tempfile import logging import shutil +import re import snapcraft from snapcraft import repo @@ -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), @@ -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( @@ -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 @@ -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"', diff --git a/snapcraft/tests/test_plugin_catkin.py b/snapcraft/tests/test_plugin_catkin.py index efd05ad168b..bc55bb0d47d 100644 --- a/snapcraft/tests/test_plugin_catkin.py +++ b/snapcraft/tests/test_plugin_catkin.py @@ -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(""" + + buildtool_depend + build_depend + run_depend + """) + + 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(""" + + package_2 + """) + + # 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(""" + + """) + + 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)