avatar Kisaragi Hiu

Trying to set up Emacs for C++

This is how I have set up Emacs for C++.

First, install the necessary packages.

  • Clangd
  • Bear
  • LSP Mode

    (straight-use-package 'lsp-mode)
    
    (when (executable-find "clangd")
      (add-hook 'c++-mode-hook #'lsp))

Then we need to run a build through Bear once.

As far as I can tell, the C++ ecosystem generally manages the dependency tree as part of the build system, rather than the compiler. This information has to be passed to Clangd so that it knows where to find the header files.

CMake has native support for this through the -DCMAKE_EXPORT_COMPILE_COMMANDS=1 flag. There is an extension for Bazel as well. For other build systems, there is Bear, which intercepts calls to the compiler to extract this information during a build and write it to compile_commands.json.

This is done like this:

bear -- make

I also like to set up a task runner. For other languages I'd use Make for this, but as Make is already being used as the build system, I resort to using npm scripts just for this purpose.

For instance, with a package.json like this:

{
  "scripts": {
    "autogen": "./autogen.sh",
    "wip": "bear -- make -j8 CPPFLAGS+='-DGTK_DISABLE_SINGLE_INCLUDES -DGDK_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED -DGSEAL_ENABLE -Wno-deprecated-declarations -Wno-parentheses'",
    "mk": "bear -- make -j8 CPPFLAGS+='-Wno-deprecated-declarations -Wno-parentheses'",
    "autogen+make": "./autogen.sh; npm run mk"
  }
}

This allows me to run ivy-taskrunner (which I've bound to SPC l l) and issue a rebuild quickly.

I have a custom version of ivy-taskrunner's entry point that will clear its cache (so that it indexes tasks and build targets again) if the command receives a universal argument (C-u), which makes it actually usable when I'm editing the targets.

(defun k/ivy-taskrunner (reset-cache)
  "Like `ivy-taskrunner' except reset cache when RESET-CACHE is non-nil."
  (interactive "P")
  (when reset-cache
    (taskrunner-invalidate-tasks-cache))
  (ivy-taskrunner))

The project looks like this in the end:

  • autogen.sh
  • package.json (see above, not tracked by Git)
  • compile_commands.json (generated by Bear)
  • configure.ac
  • configure (generated by autogen.sh)
  • Makefile (generated by configure)
  • src/…

To compile the project from Emacs, I run k/ivy-taskrunner then select, for instance, NPM wip (the “wip” npm script as defined above). If I changed Autotools settings, I just run autogen.sh again, then build again — this is what the autogen+mk task I defined above does.