Skip to content

Commit

Permalink
Add a shorthand option for switching to alternate rmws (osrf#268)
Browse files Browse the repository at this point in the history
* Basic rocker config to install and enable different rmw layers
  • Loading branch information
tfoote authored Feb 29, 2024
1 parent 80a9292 commit 35e68c1
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Then you can run pytest.
Notes:

- Make sure to use the python3 instance of pytest from inside the environment.
- The tests include an nvidia test which assumes you're using a machine with an nvidia gpu.
- The tests include an nvidia test which assumes you're using a machine with an nvidia gpu. To skip them use `-m "not nvidia"`


# Example usage
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
'port = rocker.extensions:Port',
'privileged = rocker.extensions:Privileged',
'pulse = rocker.extensions:PulseAudio',
'rmw = rocker.rmw_extension:RMW',
'ssh = rocker.ssh_extension:Ssh',
'user = rocker.extensions:User',
'volume = rocker.volume_extension:Volume',
Expand Down
84 changes: 84 additions & 0 deletions src/rocker/rmw_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright 2024 Open Source Robotics Foundation

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from argparse import ArgumentTypeError
import os
import pkgutil

from .em import empy_expand
from rocker.extensions import RockerExtension
from rocker.extensions import name_to_argument


class RMW(RockerExtension):
rmw_map = {
'cyclonedds': ['ros-${ROS_DISTRO}-rmw-cyclonedds-cpp'],
'fastrtps' : ['ros-${ROS_DISTRO}-rmw-fastrtps-cpp'],
# TODO(tfoote) Enable connext with license acceptance method
# 'connextdds': ['ros-${ROS_DISTRO}-rmw-connextdds'],
}

@staticmethod
def get_package_names(rmw_name):
return RMW.rmw_map[rmw_name]

@staticmethod
def get_name():
return 'rmw'

def __init__(self):
self._env_subs = None
self.name = RMW.get_name()

def get_docker_args(self, cli_args):
rmw_config = cli_args.get('rmw')
if not rmw_config:
return '' # not active
implementation = rmw_config[0]
args = f' -e RMW_IMPLEMENTATION=rmw_{implementation}_cpp'
return args #% self.get_environment_subs()

def get_environment_subs(self):
if not self._env_subs:
self._env_subs = {}
return self._env_subs

def get_preamble(self, cliargs):
return ''

def get_snippet(self, cliargs):
snippet = pkgutil.get_data('rocker', 'templates/%s_snippet.Dockerfile.em' % RMW.get_name()).decode('utf-8')
data = self.get_environment_subs()
# data['rosdistro'] = cliargs.get('rosdistro', 'rolling')
rmw = cliargs.get('rmw', None)
if rmw:
rmw = rmw[0]
else:
return '' # rmw not active
data['rmw'] = rmw
data['packages'] = RMW.get_package_names(rmw)
# data['rosdistro'] = 'rolling'
return empy_expand(snippet, data)

@staticmethod
def register_arguments(parser, defaults={}):
parser.add_argument(name_to_argument(RMW.get_name()),
default=defaults.get('rmw', None),
nargs=1,
choices=RMW.rmw_map.keys(),
help="Set the default RMW implementation")

# parser.add_argument('rosdistro',
# default=defaults.get('rosdistro', None),
# help="Set the default rosdistro, else autodetect")
16 changes: 16 additions & 0 deletions src/rocker/templates/rmw_snippet.Dockerfile.em
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# workspace development helpers


@[ if rmw ]@
RUN \
if [ -z "${ROS_DISTRO}" ]; then echo "ROS_DISTRO is unset cannot override RMW" ; exit 1 ; fi ;\
if dpkg -l @(' '.join(packages)) > /dev/null 2>&1; then \
apt-get update \
&& DEBIAN_FRONTENT=non-interactive apt-get install -qy --no-install-recommends\
@(' '.join(packages)) \
&& apt-get clean ;\
else \
echo "Found rmw packages @(' '.join(packages)) no need to install" ; \
fi
@[ end if ]@

94 changes: 94 additions & 0 deletions test/test_rmw_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

import em
import os
import unittest
import pytest


from rocker.core import DockerImageGenerator
from rocker.core import list_plugins

from test_extension import plugin_load_parser_correctly



class rmwExtensionTest(unittest.TestCase):

def setUp(self):
# Work around interference between empy Interpreter
# stdout proxy and test runner. empy installs a proxy on stdout
# to be able to capture the information.
# And the test runner creates a new stdout object for each test.
# This breaks empy as it assumes that the proxy has persistent
# between instances of the Interpreter class
# empy will error with the exception
# "em.Error: interpreter stdout proxy lost"
em.Interpreter._wasProxyInstalled = False

def test_rmw_extension(self):
plugins = list_plugins()
rmw_plugin = plugins['rmw']
self.assertEqual(rmw_plugin.get_name(), 'rmw')

p = rmw_plugin()
self.assertTrue(plugin_load_parser_correctly(rmw_plugin))


mock_cliargs = {'rmw': ['cyclonedds']}
self.assertEqual(p.get_preamble(mock_cliargs), '')
args = p.get_docker_args(mock_cliargs)
self.assertIn('-e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp', args)
snippet = p.get_snippet(mock_cliargs)
self.assertIn('rmw-cyclonedds-cpp', snippet)


#without it set
mock_cliargs = {'rmw': None}
args = p.get_docker_args(mock_cliargs)
snippet = p.get_snippet(mock_cliargs)
self.assertNotIn('RMW_IMPLEMENTATION', args)
self.assertNotIn('rmw-cyclonedds-cpp', snippet)


@pytest.mark.docker
class rmwRuntimeExtensionTest(unittest.TestCase):

def setUp(self):
# Work around interference between empy Interpreter
# stdout proxy and test runner. empy installs a proxy on stdout
# to be able to capture the information.
# And the test runner creates a new stdout object for each test.
# This breaks empy as it assumes that the proxy has persistent
# between instances of the Interpreter class
# empy will error with the exception
# "em.Error: interpreter stdout proxy lost"
em.Interpreter._wasProxyInstalled = False

def test_rmw_extension(self):
plugins = list_plugins()
rmw_plugin = plugins['rmw']

p = rmw_plugin()
self.assertTrue(plugin_load_parser_correctly(rmw_plugin))

mock_cliargs = {'rmw': ['cyclonedds']}
dig = DockerImageGenerator([rmw_plugin()], mock_cliargs, 'ros:rolling')
self.assertEqual(dig.build(), 0)
self.assertEqual(dig.run(command='dpkg -l ros-rolling-rmw-cyclonedds-cpp'), 0)
self.assertIn('-e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp', dig.generate_docker_cmd('', mode='dry-run'))

0 comments on commit 35e68c1

Please sign in to comment.