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:
- RTags is set up to intercept calls to
gcc
andg++
- RTags passes the results down to ccache, which works as expected
- RTags works with autotools projects
- The
rdm
server automatically runs on demand in the background when needed - Proper integration in Emacs, which in my case means
evil
keybindings
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:
gcc-wrapper.sh
ccache
/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:
- Systemd binds to an
AF_UNIX
socket in your user run directory, which will be a location like/run/user/1000/rdm.socket
- When the
rc
command tries to talk tordm
, Systemd transparently starts up an instance that inherits the socket and serves the request - The
rdm
service is configured to terminate itself after 5 minutes of inactivity - If the
rdm.socket
file is used afterrdm
terminates itself, Systemd transparently starts it up again in the same manner as before
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:
- You can switch to the Spacemacs
develop
branch, which is the bleeding edge branch - You can copy the
c-c++
layer fromdevelop
to a stable Spacemacs release
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).