(Doom) Emacs tags for obscure embedded stuff

Why?

Today, everything seems to revolve around Language Server Protocols (LSPs). Despite Emacs sometimes slowing down when LSPs are active, their utility is too immense to abandon, at least until they no longer serve our needs.

In my experience, LSPs shine in everyday projects, like a Linux-based program one might develop. However, they often feel overly flashy to me with their constant code linting pop-ups and such. Nevertheless, having an effortless way to reference variables and functions remains crucial. Even in the realm of C/C++, where projects vary greatly in setups—some even employing obscure, handcrafted Makefiles—tools like ‚bear make‘ come in handy to generate ‚compile_commands.json.‘ This file aids clangd, a C Language Server Protocol, in locating header files. The process becomes even smoother when using modern build systems like CMake.

But what happens when you cross-compile that project? What if you aren’t using GCC, clang, or even sensible tools like Make, CMake, or Meson? Instead, you might find yourself dealing with a proprietary ‚IDE‘ on Windows, using its own XML format to manage header files and running an ARM compiler vastly different from GCC. Creating a ‚compile_commands.json‘ for this scenario becomes an ordeal.

However, I still want the ability to reference tags in my programs without switching from Emacs to that peculiar proprietary ‚IDE.‘

This is where ‚gtags‘ come into play.

What are gtags?

In the days of yore (before LSPs), tags were looked up using a file. While this sounds archaic by today’s standards, ‚ctags‘ does precisely that. Nevertheless, for sizable projects, storing tags in plain text within a file can become sluggish. That’s where ‚GNU Global‘ steps in—it performs a similar task but utilizes a database. Unfortunately, GNU Global doesn’t operate on Windows. Thankfully, with Windows Subsystem for Linux (WSL) available, this limitation is mitigated.

The Setup

GNU Global primarily constructs the database. Using it within Emacs involves additional tools. With Emacs offering numerous packages, each slightly different yet serving the same purpose, I opted for ‚ggtags‘ due to its popularity.

Here’s the process:

  1. Create a tag database for each project.
  2. Access this database using ggtags.
  3. Navigate between tags, their definitions, and references using Emacs‘ Xref, with ggtags as the backend.

Installation

Install GNU Global through your package manager. For instance:

sudo emerge -av dev-util/global

The installation process might vary for other distros.

In Emacs, install ggtags. There are multiple ways to do this; for instance, in Doom, add:

(package! ggtags)

to your ‚packages.el.‘ Then, run ‚doom sync‘ from the command line to fetch the packages.

Configuration

For other projects, occasionally using Clangd might still be necessary. Since Doom integrates lsp-mode into ‚c-mode,‘ ‚c++-mode,‘ etc., we need to remove this integration:

(remove-hook! '(c-mode-local-vars-hook
c++-mode-local-vars-hook
objc-mode-local-vars-hook
cmake-mode-local-vars-hook)
#'lsp!)

(remove-hook! '(c-mode)
#'lsp!)

Instead, add ‚ggtags-mode‘ to the hook:

(add-hook 'c-mode-common-hook
(lambda ()
(when (derived-mode-p 'c-mode 'c++-mode 'java-mode)
(ggtags-mode 1))))

If there are library sources to include in your project’s tags, add them to the ‚GTAGSLIBPATH‘ environment variable:

(setenv "GTAGSLIBPATH" "path/to/some/library/source:/path/to/some/other/library/source")

Usage

After these steps, evaluate the changes and open a source file in your project. Execute ‚M-x ggtags-create-tags.‘ It prompts you to select the project’s root directory. Gtags will traverse subdirectories and generate three tag files in the project’s root. (Alternatively, navigate to that directory in the command line and run ‚gtags‘ command).

The process will also inquire if you wish to use the ctags backend. Gtags can only create tags files for specific languages; for others, it relies on ctags to create files that it then parses to build the database. For C-related languages, declining this option suffices. Accidentally accepting this setting can be rectified by deleting the ‚GTAGS,‘ ‚GRTAGS,‘ and ‚GPATH‘ files from the project directory; Ggtags will prompt this choice again.

Perform the same process in every folder within ‚GTAGSLIBPATH.‘

Now, with the cursor on a variable in Emacs, it will underline it. Running ‚M-x xref-find-definitions‘ will navigate you to that variable’s definitions. As an ‚evil‘ user, I can utilize ‚C-o‘ (evil-jump-backward) to return to that variable. The same applies to ‚xref-find-references‘ and ‚xref-find-definitions-other-window.‘