Introduction
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 ...) ETAGS_ARGS = --declarations $(TAGS_DEPENDENCIES)
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.
Conclusion
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.