Using Emacs And RTags With Autools C++ Projects

Update [2019-04-22]: I'm keeping this article up for posterity, but I now recommend using ccls instead of Rtags. Stay tuned for a future blog post about how I use ccls.


Today I got RTags properly indexing a large autotools (autoconf/automake) C++ project, and I'm a very happy camper. Getting this set up properly was a lot of work, and took me a few hours. To me, properly working means:

I made a short asciinema recording demonstrating what it looks like. (You'll have to ignore the weird terminal colors, I don't have them set up properly because I normally use a graphical Emacs client.)

Why RTags?

I'm a really strong believer that at a minimum, everyone should have jump-to-definition working in their text editor. I don't care what text editor you're using, but if you can't quickly navigate your source code this way, you're going to waste a lot of time trying to navigate the code you're working on. Ideally this also means that you also have a working "find references" command, meaning that you can quickly and accurately find other parts of the code that call a given method (or use a variable).

Most autotools projects will automatically have a TAGS make target, which builds an etags (or ctags) database of the source code. This works OK, and I've used this for years without too many issues. Recently though I've been getting involved with Bitcoin Core development. Bitcoin is a fairly large C++ code base, and etags does not scale well to large code bases. It's not very accurate, often bringing up a large list of possible definitions. But that actually doesn't bother me too much. What really drives me crazy is how slow etags is in large projects. If something is going to be slow, it should at least work correctly. Etags satisfies neither of those requirements.

I've previously seen some videos of people with RTags integration in Emacs, and it's really slick. And it's instantaneous, which cannot be said of etags or ctags. RTags doesn't have to guess about context, because it hooks into the compilation phase of your project. It has real knowledge of things like preprocessor macro definitions, and which methods are used in complex contexts. This is what people with traditional IDEs (CLion, Visual Studio, etc.) are used to. It's something I find makes me a lot more productive.

What You Need To Do

First you'll need to actually install RTags. I use Fedora, and a recent version of RTags is already packaged for Fedora. However, I have not been able to get the Fedora version working (at least not with the GCC wrapper script). If your distro has RTags packaged you might as well try it out, but I had to build from source to get a working version.

Building RTags

To build RTags, I needed to install clang-devel and llvm-devel. This is specific to Fedora, but you need:

# Install clang-devel and llvm-devel.
sudo dnf install {clang,llvm}-devel

Afterwards you should be able to build and install RTags:

# Clone the git repo.
git clone https://github.com/Andersbakken/rtags
cd rtags

# Get the submodules
git submodule init
git submodule update

# I want to install to ~/.local, meaning the binaries go in ~/.local/bin.
./configure --prefix $HOME/.local
make
make install

I always install packages using the prefix ~/.local, which means the binaries end up in ~/.local/bin. I already have my shell $PATH set up to search this location. Modify the above commands as necessary (including updating your $PATH).

Afterwards you should have rc in your path:

$ which rc
/home/evan/.local/bin/rc

Configuring The GCC Wrapper

By default RTags expects to work with CMake projects that use Clang, which isn't much use to me. Thankfully, the RTags developers have a wrapper script to provide compatibility with GCC and other build systems, including autotools projects. The previous make install command should have put gcc-tags-wrapper.sh in your path. To actually use this, you need to set up symlinks for the various names that GCC can be invoked with.

The ~/.local/bin prefix I used earlier is already at the front of my $PATH, so I just need to put symlinks there. In my case that looks like this:

cd ~/.local/bin
for c in cc c++ gcc g++; do
  ln -s ./gcc-rtags-wrapper.sh "$c"
done

Check That Ccache Works

I also have ccache enabled, because development without it is very painful. The RTags docs don't say this explicitly, but I believe you want a search path for a command like g++ to run in order:

  1. gcc-wrapper.sh
  2. ccache
  3. /usr/bin/g++

This ordering ensures that rc will see the file even if ccache already has the compilation result cached. I'm not 100% sure that this is right though, it might make more sense to invoke ccache first and clear your existing cache using ccache -c.

If you just installed symlinks above, first run hash -r to make sure your shell clears its cache of where it thinks g++ is. Then you can verify that things are set up correctly like this:

$ which -a g++
/home/evan/.local/bin/g++
/usr/lib64/ccache/g++
/usr/bin/g++

This shows the search path from higher priority to lower priority. If this doesn't look right, you need to rearrange things in your $PATH.

Setting Up An RDM Socket Server

The RTags database server is called rdm, and it needs to be running when you compile code. The rc command submits new indexing requests to rdm over an AF_UNIX socket. You can run this however you want, but if you're on Linux I recommend using a Systemd user socket service. Here's how this configuration works:

You aren't required to run rdm this way, but I like it. The instructions for setting this up are in the rtags README, I won't repeat them here.

Indexing Your First Project

You should just have to do a make clean and then a make in your project. That's really it. If things are working correctly, once you start compiling code you'll start seeing ~/.cache/rtags start to grow in size.

One thing that is really cool about how rc works is that it submits work to rdm asynchronously. This means that your compilation doesn't block while rdm indexes your code. This means that you might have to wait a minute or so after indexing your first project before you get complete coverage, even if the compilation phase already completed.

Setting Up Emacs/Spacemacs

RTags provides an rtags Emacs package which provides basic RTags integration. If this is working, at a minimum you should have a command called rtags-find-symbol-at-point which jumps to the definition of the symbol at the current point. If you have a traditional Emacs setup, you can read the RTags docs for more information about the rtags package.

I am personally a big fan of Spacemacs, which is an Emacs distribution focused around evil mode (which lets me use vi keybindings). Spacemacs has full RTags integration, but not in a stable release. To use the new RTags layer, you have a few options:

I like to live dangerously, so I just switched to develop. It looks like they recently changed how their GitHub remotes are configured to make this more difficult, presumably to make it difficult to do what I'm about to show you to do.

On my laptop (which has an older git clone) I was able to just do:

cd ~/.emacs.d
git fetch
git checkout -t origin/develop

On my desktop (which has a newer git clone) the origin remote is configured to just show the master branch by default. There I had to do the following:

cd ~/.emacs.d
git fetch --all
git checkout -t checkversion/develop

Once you do the above (or copy the develop layer locally) you can enable RTags integration by setting c-c++-enable-rtags-support to t in your Spacemacs layers. The example in the docs is like this:

  (setq-default dotspacemacs-configuration-layers
    '((c-c++ :variables c-c++-enable-rtags-support t)))

I have a bunch of other Spacemacs things set up for my c-c++ layer; you may or may not be interested in some of these, the actual value I have in my config looks like this:

 (c-c++ :variables
        c-c++-default-mode-for-headers 'c++-mode
        c-c++-enable-clang-support t
        c-c++-enable-google-newline t
        c-c++-enable-google-style t
        c-c++-enable-rtags-support t)

Once this is all configured you should be able to use SPC m g . to invoke rtags-find-symbol-at-point. You can also use SPC m g , to invoke rtags-find-references-at-point, which does the inverse (shows you what code calls where you are now).