TAGS, Etags and system headers


Let's say that you are using Emacs's Xref features and you are hooking it up to your project through TAGS files. Let's also say you are using the etags program that ships with a standard GNU Emacs installation. You probably noticed that etags by default will create TAGS files only for the project, leaving out any external library.

When you just need to jump around functions or classes made by you or your team this is fine and all, but when you need to, say, autocomplete the name of a function or class from a third-party, then the TAGS files become useless.

Why would someone use Xref?

Considering this article was written in the year 2023 A.D., it is natural to ask why would Xref even be considered at all, given the abundance of tools that “just work” out of the box and that provide a broader experience.

Xref does not have any inherent advantage over the rest, but here are a couple reason why people might want to check it out:

  • Both Xref and etags are provided built-in with a standard installation, something that might be of interest to those seeking a minimalistic experience;
  • You can use it without an internet connection, if you really find yourself in dire need;
  • You just want to have fun! You do not have to always do ‘the right thing’. It is perfectly acceptable to take your time, explore, maybe try out things you never considered simply because they were ‘inferior’… that sort of stuff.

Telling etags about external declarations

Unlike more recent tools, etags does not have an option to scan a directory for interesting files, but requires the user to specify each file as an argument.

While this simplifies the interface a lot, especially since the tool already has a bunch of complex flags, things get a lot messy when the external declarations are placed in multiple files.

A popular example of this kind of situation is SDL 2, the library used to make games for various platforms. The rest of the article will use this library for its code snippets.

The article will also use Automake for a couple of things, mainly because support for etags is provided out of the box and configuring it is literally two lines of code.

Before we can tell etags about our files, we need to physically locate them. When a library is installed system-wide, it is common to find its headers in /usr/include, but that is not always the case. Certain systems might have it in /usr/local/include or even in weirder places like /opt/2.18/usr/include.

To ensure the compiler and the linker are always able to find everything, tools like pkg-config have been created. By running the following:

pkg-config sdl2 --cflags

we can see where the headers are located, e.g. you will see -I/usr/include/SDL2.

Of course the output would rarely be just the path to the headers (that would've been too easy) and it will have other values to ensure the compiler does the right thing, but which are useless to us.

Stripping them out isn't too hard and there are many tools that can perform this task. Getting the actual files, however, is not as easy.

If you are using something based on Make, be it Automake or something else, you can use the following incantation, which is explained later:

$(wildcard $(join $(subst -I,,$(filter -I%SDL2,$(SDL2_CFLAGS))),/SDL*.h))

First, SDL2_CFLAGS is just a variable containing the output of the pkg-config call above. With Autotools it can be generated thanks to PKG_CHECK_MODULES.

The filter function will remove from the second argument (in this case the variable) anything not matching the pattern, i.e. in this case the resulting string would be something like -I/usr/local/include/SDL2.

Through the subst function we can remove the -I part by providing an empty string as its second argument. This will give us the actual path, e.g. /system/headers/include/SDL2.

With the join function we can concatenate the resulting string with the files we are interested in. Since we need to scan multiple files, we specify a pattern to expand, i.e. we now have something like /home/foo/.local/include/SDL2/SDL*.h.

Etags is not able to expand this pattern on its own, therefore we have to do it ourselves before giving the list of files to the tool. The wildcard function will do it for us, generating a list like /usr/include/SDL2/SDL.h /usr/include/SDL2/SDL_main.h etc.

Now that we have our files, we can finally tell etags to scan them; thus, using Automake:

TAGS_DEPENDENCIES = $(wildcard ...)

The declarations flag is important: normally etags will look at full definitions, where you specify everything about the entity, be it a function or a class, but headers only have declarations like function prototypes; the flag will tell etags your files do not have definitions.


The Make spell has to be written ad-hoc for each external package and will likely require a lot of trial and error. Other build systems might or might not make things easier or more complex.