Rationale
It is unreasonable for developers to expect either of lsp-mode
or flymake-mode
Elisp modes to predict all of the arcane C/C++ compiler options necessary for basic checks such as location of #include
-ed header files that are defined on a per-application basis with -I<directory>
options. Thus, when those Elisp modes attempt to do so without a compile_commands.json
file (compilation database in LSP parlance), they will get it "wrong", leading to flymake errors and failure of lsp-mode to xref to definitions and references to symbols in the language (i.e., here the focus is C/C++).
I found that two things were required in my self-answer here. I'm not satisfied with this answer as I'm expecting a rebuttal from someone who can simplify this sledge-hammered solution:
- Disabling of
lsp-mode
when there is no dominating compile_commands.json
file relative to the source file being opened, and,
- Disabling of
flycheck-mode
under the same conditions as above for lsp-mode
.
Conditional lsp-mode
;; There is nothing in lsp-mode that defines the
;; bg-clangd-compilation-database-file constant. Perhaps it is only known by the
;; LSP protocol and hidden from client view by the clangd implementation:
(defvar bg-clangd-compilation-database-file "compile_commands.json"
"Basename of the compilation database used by clangd.")
(defun bg-clangd-is-compilation-database-file-available ()
"Return t when there is a compilation database file in a dominating directory."
(and (derived-mode-p 'c++-mode)
(locate-dominating-file default-directory bg-clangd-compilation-database-file)))
(defun bg-lsp-deferred-when-compilation-database-file-available ()
"Only call lsp-deferred if there is a compilation database file in a dominating directory.
There is no need to start lsp using default clangd server if
there is no compilation database file in a dominating directory.
This is because there is always a need to have clangd see the
_exact_ set of compilation options as used in building the
application, and the defaults are useless, and flood the buffer
with error markers (due to flymake, dealt with separately than
this function here), all due to a missing compilation database
file (`bg-clangd-compilation-database-file', which see)."
(when (derived-mode-p 'c++-mode)
(if (not (bg-clangd-is-compilation-database-file-available))
(message "No compilation database file found relative to %S which is needed for lsp mode configuration." default-directory)
;; For lsp-deffered, see
;; https://emacs-lsp.github.io/lsp-mode/page/installation/#:~:text=which%2Dkey%2Dmode))-,To%20defer%20LSP%20server,-startup%20(and%20DidOpen
(lsp-deferred))))
Then add the hook:
(use-package lsp-mode
:ensure t
:hook ((c++-mode . bg-lsp-deferred-when-compilation-database-file-available))
:commands (lsp lsp-deferred))
Conditional Flycheck
Conditionally disable the flycheck on those C/C++ files that do not have a dominating compile_commands.json
file (i.e., the file does not exist in the current working directory of the source file, nor in any parent directory):
(defun bg-flycheck-conditionally-enable-cxx-checkers ()
"Only enable flycheck on C/C++ files if there is a compilation database given by `bg-clangd-compilation-database-file'.
Similar rationale as given in `bg-lsp-deferred-when-compilation-database-file-available', which see."
(unless (bg-clangd-is-compilation-database-file-available)
(mapc (lambda (elem) (add-to-list 'flycheck-disabled-checkers elem))
'(c/c++-clang c/c++-gcc))))
Then add the hook:
(use-package flycheck
:ensure t
:config
;; Only enable flycheck on C/C++ files under certain conditions
(add-hook 'c++-mode-hook 'bg-flycheck-conditionally-enable-cxx-checkers)
:init
(global-flycheck-mode))