Tips for remote development

Keywords: #ros2 #emacs #tramp #ssh

I’ve been developing on robots with Emacs for a few years now, and I thought it may be beneficial for me to do a write-up of some of the tricks I’ve learned. The usefulness of the following won’t be limited to robotics, nor only to those who use Emacs, as I’m sure the frustrations of developing on a remote machine, inside a container are transcendent. Given that I have wound up revisiting the ROS and GDB page a good number of times to jog my memory, I suspect that this topic will be as useful. The sections are ordered on a roughly chronological order of operations that I tend to follow when setting up a remote machine.

Give the machine a hostname

It’s the first entry in this list for a reason, and is always one of the first things I do. If you can avoid it, don’t waste your time with an $ nmap -sn 192.168.1.* every time your router assigns a new IP. Just configure the robot to have a hostname, and never worry about whether it’s IP has changed again. On Ubuntu, this can be done by:

$ hostnamectl set-hostname new-hostname

Now, when you need to address the machine, you can use the hostname appended with *.local instead of the IP:

$ ssh new-hostname.local

and it will connect to your remote machine. From here on, examples will use the remote name that I use for KIWI, instead of the usual ipv4 address you may be used to.

Give the machine your keys

This one is for all of you who still type a password every time you attempt to work on your robot. There is a command to copy over your public ssh key, and have it authenticate using your key as opposed to a password. Your editor will also use this key when connecting to a remote machine, which will dramatically reduce the setup time to development each time you power on the robot.

$ ssh-copy-id kiwi.local

Create a compilation script

Between us, I believe colcon is objectively a more powerful and robust build system, however a lot of accessibility general pleasantness was not carried over from catkin. Some examples are Colour output, and not doing any checks for whether it’s about to make a workspace inside of a workspace. This caught me a couple of times when I was trying out ROS2 with QUAIL, and led me to very quickly getting into the habit of creating a compile script for all of my projects. They’re almost always located in the *_ws folder, and have a form similar to:

#!/bin/bash

cd ~/kiwi_ws
if [ $# -eq 0 ]
then
      colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=ON --no-warn-unused-cli  
else
      colcon build --packages-select $* --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=ON --no-warn-unused-cli
fi

You can call this from anywhere, defaults to compiling with debug flags, and supports wildcard package selection. Not to mention exporting compile commands allowing your C++ LSP to work with your workspace. To compile a set of packages, instead of the whole workspace, append with the desired packages, for example: ./compile.sh pkg1 pkg2.

Give Emacs a function to streamline connecting to your machine

If you’re using god’s editor, place the following in your config.el, run with M-x tramp_kiwi. It will open a buffer to your remote machine. You’ll need to modify the file it opens.

(defun tramp_kiwi() "Open a session to KIWI"
       (interactive) (let ((ip-address "kiwi.local")) 
        (find-file (concat "/ssh:" ip-address ":~/kiwi_ws/README.md"))))

Some people may try something fancy, such as opening a find-file buffer on connect, but I like to keep it simple

Develop inside of your container

If you’re working in a containerised environment (As you should be!), Tramp works well inside of a remote container:

(defun tramp_kiwi_docker() "Open a session to the kiwi remote docker container"
       (interactive) (let ((ip-address "kiwi.local") 
        (docker-container-id "kiwi_docker") 
        (file-path "~/kiwi_ws/README.md")) 
        (find-file (concat "/ssh:" ip-address "|docker:" docker-container-id ":" file-path))))

With a bit of effort, LSP servers such as clangd/pyls can run in the container if you have them installed. Obviously this won’t work if the container isn’t currently running, it would be interesting to extend this function to run the container if it is currently halted.

Fin

I’ve been meaning to give ros.el a try. I’d like to make a part 2 post of this style covering more of the Emacs side of things, if it ends up getting integrated with my workflow.