diff --git a/examples/ros/snapcraft.yaml b/examples/ros/snapcraft.yaml index e4932ce03c..2559ff9dd4 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 d42f08453b..f2ddb963fc 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 4d7ff436ea..a627a28d16 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 with the one from + # roscore. Remove this line once the roscore plugin is gone. 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 efd05ad168..bc55bb0d47 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)