Setup ROS 2 Development Environment with Docker and Emacs on MacOS

[Zheng Qu] | Jan 11, 2024 min read

Setting up the right environment is crucial for effective development. In this blog post, I will guide you through setting up a ROS 2 development environment using Docker and Emacs on MacOS. This guide assumes that Emacs is already installed on your system.

Install Docker

You need to install Homebrew first. After that, you can install Docker with:

brew install --cask docker

Start the docker app to bring up the docker daemon.

Now you can check if the installation was successful by running the following command:

docker run hello-world

Add your ROS 2 workspace

Add a workspace in order to build and open them in a container, e.g.:

cd ~/
mkdir -p ws_[project]/src

Now create a .devcontainer folder in the root of your workspace and add a devcontainer.json and Dockerfile to this .devcontainer folder. Additionally, you need to create a cache folder in which you can cache the build, install and log folders for different ROS 2 distros. The workspace structure should look like this:

ws_[project]
├── cache
|   ├── [ROS2_DISTRO]
|   |   ├── build
|   |   ├── install
|   |   └── log
|   └── ...
|
├── src
    ├── .devcontainer
    │   ├── devcontainer.json
    │   └── Dockerfile
    ├── package1
    └── package2

Edit devcontainer.json

For the Dev Container to function properly, we have to build it with the correct user. Therefore add the following to .devcontainer/devcontainer.json:

{
  "name": "ROS 2 Development Container",
  "privileged": true,
  "remoteUser": "YOUR_USERNAME",
  "build": {
    "dockerfile": "Dockerfile",
    "args": {
      "USERNAME": "YOUR_USERNAME"
    }
  },
  "workspaceFolder": "/home/ws",
  "workspaceMount": "source=${localWorkspaceFolder},target=/home/ws/src,type=bind",
  "customizations": {
    "vscode": {
      "extensions":[
        "ms-vscode.cpptools",
        "ms-vscode.cpptools-themes",
        "twxs.cmake",
        "donjayamanne.python-extension-pack",
        "eamodio.gitlens",
        "ms-iot.vscode-ros"
      ]
    }
  },
  "containerEnv": {
    "DISPLAY": "unix:0",
    "ROS_AUTOMATIC_DISCOVERY_RANGE": "LOCALHOST",
    "ROS_DOMAIN_ID": "42"
  },
  "runArgs": [
    "--net=host",
    "-e", "DISPLAY=host.docker.internal:0"
  ],
  "mounts": [
    "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached",
    "source=${localWorkspaceFolder}/../cache/ROS_DISTRO/build,target=/home/ws/build,type=bind",
    "source=${localWorkspaceFolder}/../cache/ROS_DISTRO/install,target=/home/ws/install,type=bind",
    "source=${localWorkspaceFolder}/../cache/ROS_DISTRO/log,target=/home/ws/log,type=bind"
  ],
  "postCreateCommand": "sudo rosdep update && sudo rosdep install --from-paths src --ignore-src -y && sudo chown -R YOUR_USERNAME /home/ws/"
}

Open the file in your editor, search for YOUR_USERNAME and replace it with your username. If you do not know your username, you can find it by running echo $USER in the terminal. Also replace ROS_DISTRO, with the ROS 2 distribution that you want to use and added to the cache previously, for example, humble or rolling.

Edit Dockerfile

Open the Dockerfile and add the following contents:

FROM ros:ROS_DISTRO
ARG USERNAME=YOUR_USERNAME
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# Create the user
RUN groupadd --gid $USER_GID $USERNAME
RUN useradd --uid $USER_UID --gid $USER_GID -m $USERNAME
#
# [Optional] Add sudo support. Omit if you don't need to install software after connecting.
RUN apt-get -y update
RUN apt-get install -y sudo
RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME
RUN chmod 0440 /etc/sudoers.d/$USERNAME
RUN apt-get update && apt-get upgrade -y

# Install a few important dev dependencies
RUN apt-get install -y \
    ament-cmake \
    ccls \
    python3-colcon-common-extensions \
    python3-pip \
    vim \
    clang \
    clang-format \
    clang-tidy

RUN echo "source /opt/ros/ROS_DISTRO/setup.bash" >> /home/$USERNAME/.bashrc
ENV SHELL /bin/bash

# ********************************************************
# * Anything else you want to do like clean up goes here *
# ********************************************************

# [Optional] Set the default user. Omit if you want to keep the default as root.
USER $USERNAME
CMD ["/bin/bash"]

Search here also for the YOUR_USERNAME and replace it with your username and the ROS_DISTRO with the ROS 2 distribution you wish to use and added to the cache previously.

Open and Build Devcontainer

To build and start the devcontainer, we need GitHub - devcontainers/cli. Install it with:

brew install devcontainer

Afterwards, execute the following commands to build and start the devcontainer:

devcontainer up --workspace-folder ~/ws_[project]/src

Build the ROS 2 workspace

To build the ROS 2 workspace, run

devcontainer exec --workspace-folder ~/ws_[project]/src colcon build --cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

Edit files with emacs

Open the file you want to edit with helm-find-files, type /docker: and press TAB to auto-complete the docker container name. Then you can find the file you want to edit in the container with path like:

/docker:container_name:/home/ws/src/package1/src/file.cpp

Once the file is opened, you can edit it as usual. ccls or clangd will automatically index the file if

  • it is configured in your emacs configuration,
  • the compile_commands.json is present in the build folder of the ROS 2 workspace,
  • and the executable ccls or clangd is installed in the container.

Test ROS 2 CLI

Here we will follow the turtlesim, ros2, and rqt — ROS 2 Tutorial.

Install turtlesim

First, we need to install the turtlesim package. Open a new terminal and run:

devcontainer exec --workspace-folder ~/ws_[project]/src bash

which gives you a bash shell in the container. Then in the bash shell, run:

sudo apt install ros-ROS_DISTRO-turtlesim

Now check if the installation was successful by running:

ros2 pkg executables turtlesim

Start turtlesim

To start turtlesim, you can either run:

  • ros2 run turtlesim turtlesim_node in the bash shell of the container, or
  • devcontainer exec --workspace-folder ~/ws_[project]/src ros2 run turtlesim turtlesim_node in a new terminal.

Now you may encounter an error saying

Error: Can't open display: host.docker.internal:0

To solve it you need to follow the steps here:

  • Install XQuartz with brew install --cask xquartz.
  • Open XQuartz and go to Preferences -> Security and check Allow connections from network clients.
  • Reboot the computer.
  • Run xhost +localhost in a terminal to allow connections to the MacOS host.

Now you would be able to start turtlesim and see a “TurtleSim” window pop up.

Use TurtleSim

To control the turtle, you can either run:

  • ros2 run turtlesim turtle_teleop_key in the bash shell of the container, or
  • devcontainer exec --workspace-folder ~/ws_[project]/src ros2 run turtlesim turtle_teleop_key in a new terminal.

Now you can use the arrow keys to control the turtle in the “TurtleSim” window. And you should be able to continue the tutorial to learn more about ROS 2 CLI.

References