]>
Commit | Line | Data |
---|---|---|
d4ad3054 FM |
1 | PowerDNS Development Environment |
2 | -------------------------------- | |
3 | ||
4 | Thank you for you interest to contribute to the PowerDNS project. | |
5 | This document will explain one way to set up a development environment based on the language server protocol (LSP) when working on PowerDNS. | |
6 | ||
7 | # Introduction | |
8 | ||
9 | The environment will consist of setting up the [`clangd`](https://clangd.llvm.org/) C/C++ language server to enable fancy IDE features for development. | |
10 | [`ccls`](https://github.com/MaskRay/ccls) can also be used in place of `clangd`. | |
11 | ||
12 | Furthermore, additional [on-the-fly checks using `clang-tidy`](#on-the-fly-clang-tidy) can be easily enabled. | |
13 | ||
14 | On some systems, `clangd` and `clang-tidy` are available as packages separate from the `clang` package. | |
15 | Ensure that you have all three binaries available and running on your system. | |
16 | ||
17 | # Compilation Database | |
18 | ||
19 | For projects with non-trivial build systems, like PowerDNS, `clangd` requires a [compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html). | |
20 | ||
77f8022f | 21 | Since PowerDNS' autotools-based build system does not have native support for generating such a database, an external tool like [Bear (the Build EAR)](https://github.com/rizsotto/Bear) or [compiledb](https://pypi.org/project/compiledb) can be used. |
d4ad3054 | 22 | |
77f8022f | 23 | ## Using Bear |
d4ad3054 FM |
24 | Once you have `bear` installed, configure a build of your choice (either the PowerDNS `auth`, `recursor` or `dnsdist`) using `clang` and `clang++`: |
25 | ||
26 | ```sh | |
27 | make distclean # Ensure we rebuild all files so that bear can pick them up. | |
28 | CC=clang CXX=clang++ ./configure --with-modules=gsqlite3 --disable-lua-records | |
29 | ``` | |
30 | ||
31 | We can now build PowerDNS using `bear` and `make` which produces a compilation database file named `compile_commands.json`: | |
32 | ||
33 | ```sh | |
34 | bear --append -- make -j 8 | |
35 | ``` | |
36 | ||
77f8022f OM |
37 | ## Using compiledb |
38 | Once you have `compiledb` installed, configure the build and run compiledb: | |
39 | ||
40 | ```sh | |
41 | make distclean # Ensure we rebuild all files so that bear can pick them up. | |
42 | CC=clang CXX=clang++ ./configure ... | |
43 | make -nwk | /path/to/compiledb -o- > compile_commands.json | |
44 | ``` | |
45 | ||
46 | to generate the compilation database. | |
47 | For the authoritative server, the configure command is run in the top level directory, while the compiledb command should be run in the `pdns` subdirectory. | |
48 | ||
49 | # Seting up the LSP client | |
50 | ||
d4ad3054 FM |
51 | Once the compilation database is generated, you can now move onto setting up an LSP client in your editor or IDE. |
52 | ||
53 | Note that the process of generating the compilation database file only needs to be run when a file is added to the project or when build flags change (e.g. dependencies are added). | |
54 | ||
55 | # Editors | |
56 | ||
57 | ## Emacs | |
58 | ||
59 | This section explains how to set up [Emacs](https://www.gnu.org/software/emacs/) with [LSP Mode](https://emacs-lsp.github.io/) for C/C++ development using `clangd` which supports [on-the-fly checking](https://www.flycheck.org/en/latest/) and [auto-completion](https://company-mode.github.io/), among many other features. | |
60 | ||
b4c7e352 FM |
61 | Instructions for an alternative, more minimal, setup using [Eglot](https://github.com/joaotavora/eglot) [are also available](#minimal-emacs). |
62 | ||
d4ad3054 FM |
63 | Code snippets below should be added to your Emacs init file (e.g. `~/.config/emacs/init`). |
64 | ||
65 | We'll start by enabling Emacs package repositories and declaring which packages we would like to have installed: | |
66 | ||
67 | ```elisp | |
68 | (with-eval-after-load 'package | |
69 | (setq package-archives | |
70 | '(("gnu" . "https://elpa.gnu.org/packages/") | |
71 | ("melpa" . "https://melpa.org/packages/"))) | |
72 | (push 'company package-selected-packages) | |
73 | (push 'flycheck package-selected-packages) | |
74 | (push 'lsp-mode package-selected-packages) | |
75 | (push 'lsp-ui package-selected-packages) | |
76 | (push 'lsp-treemacs package-selected-packages)) | |
77 | ``` | |
78 | ||
79 | To avoid restarting Emacs, you can evaluate that previous s-expression by pointing at the last parenthesis and using `C-x C-e` or selecting the block and using `M-x eval-region`. | |
80 | ||
81 | Once done, run `M-x package-refresh-contents`, then `M-x package-install-selected-packages`. | |
82 | This should install some packages for you. | |
83 | ||
84 | Now let's set up our common programming mode, this enables the following features: | |
85 | ||
86 | * Highlighting the current line. | |
87 | * Displaying line numbers. | |
88 | * Auto-inserting indentation. | |
89 | * Auto-inserting closing parenthesis, bracket, etc... | |
90 | * Auto-completion. | |
91 | * On-the-fly code checking. | |
92 | * On-the-fly spell checking. | |
93 | * Highlighting matching parentheses, brackets, etc... | |
94 | * Auto-displaying documentation briefs in the echo area. | |
95 | ||
96 | ```elisp | |
97 | (with-eval-after-load 'prog-mode | |
98 | (add-hook 'prog-mode-hook #'hl-line-mode) | |
99 | (add-hook 'prog-mode-hook #'display-line-numbers-mode) | |
100 | (add-hook 'prog-mode-hook #'electric-layout-mode) | |
101 | (add-hook 'prog-mode-hook #'electric-pair-mode) | |
102 | (add-hook 'prog-mode-hook #'company-mode) | |
103 | (add-hook 'prog-mode-hook #'flycheck-mode) | |
104 | (add-hook 'prog-mode-hook #'flyspell-prog-mode) | |
105 | (add-hook 'prog-mode-hook #'show-paren-mode) | |
106 | (add-hook 'prog-mode-hook #'eldoc-mode)) | |
107 | ``` | |
108 | ||
109 | Now let's set up `flycheck` for on-the-fly code checking, this adds the following key bindings: | |
110 | ||
111 | * `M-n` to jump to the next error. | |
112 | * `M-p` to jump to the previous error. | |
113 | ||
114 | ```elisp | |
115 | (with-eval-after-load 'flycheck | |
116 | (define-key flycheck-mode-map (kbd "M-n") #'flycheck-next-error) | |
117 | (define-key flycheck-mode-map (kbd "M-p") #'flycheck-previous-error) | |
118 | (setq flycheck-checker-error-threshold nil) | |
119 | (setq flycheck-check-syntax-automatically | |
120 | '(idle-change new-line mode-enabled idle-buffer-switch)) | |
121 | (setq flycheck-idle-change-delay 0.25) | |
122 | (setq flycheck-idle-buffer-switch-delay 0.25)) | |
123 | ``` | |
124 | ||
125 | And set up `company-mode` for auto-completion: | |
126 | ||
127 | ```elisp | |
128 | (with-eval-after-load 'company | |
129 | (setq company-backends '((company-capf company-files company-keywords))) | |
130 | (setq completion-ignore-case t) | |
131 | (setq company-minimum-prefix-length 1) | |
132 | (setq company-selection-wrap-around t) | |
133 | (define-key company-mode-map (kbd "<tab>") #'company-indent-or-complete-common)) | |
134 | ``` | |
135 | ||
136 | Then set up `lsp-mode` to integrate everything together, which enables the following additional features: | |
137 | ||
138 | * Header breadcrumbs showing the path to the current item under point. | |
139 | * Semantic syntax highlighting as opposed to regex-based ones. | |
140 | ||
141 | And adds the following key bindings: | |
142 | ||
143 | * `F2` to switch between implementation and header file. | |
144 | * `C-c f` to format the current file according to `clang-format`. | |
145 | * `C-c g` to format the selected region according to `clang-format`. | |
146 | * `C-c r` to reliably rename the item under point based on `clangd`. | |
147 | * `C-c h` to show code documentation about the item under point. | |
148 | * `C-c =` to expand selection outwards from the item under point. | |
149 | * `M-RET` to list and run available language server code actions. | |
150 | * `C-c x` to navigate to any symbol in the project. | |
151 | * `C-c e` to show a navigation list of errors/warnings in the project. | |
152 | * `C-c s` to show a navigation list of symbols in the project. | |
153 | * `C-c c` to show the call hierarchy of a method or function. | |
154 | * `C-c t` to show the type/inheritance hierarchy of a type. | |
155 | ||
156 | ```elisp | |
157 | (with-eval-after-load 'lsp-mode | |
158 | (define-key lsp-mode-map (kbd "C-c f") #'lsp-format-buffer) | |
159 | (define-key lsp-mode-map (kbd "C-c g") #'lsp-format-region) | |
160 | (define-key lsp-mode-map (kbd "C-c r") #'lsp-rename) | |
161 | (define-key lsp-mode-map (kbd "C-c h") #'lsp-describe-thing-at-point) | |
162 | (define-key lsp-mode-map (kbd "C-=" ) #'lsp-extend-selection) | |
163 | (define-key lsp-mode-map (kbd "M-RET") #'lsp-execute-code-action) | |
164 | (define-key lsp-mode-map (kbd "C-c e") #'lsp-treemacs-errors-list) | |
165 | (define-key lsp-mode-map (kbd "C-c s") #'lsp-treemacs-symbols) | |
166 | (define-key lsp-mode-map (kbd "C-c c") #'lsp-treemacs-call-hierarchy) | |
167 | (define-key lsp-mode-map (kbd "C-c t") #'lsp-treemacs-type-hierarchy) | |
168 | (add-hook 'lsp-mode-hook #'lsp-treemacs-sync-mode) | |
169 | (setq lsp-progress-prefix " Progress: ") | |
170 | (setq lsp-completion-provider :none) ; Company-capf is already set | |
171 | (setq lsp-headerline-breadcrumb-enable t) | |
172 | (setq lsp-restart 'auto-restart) | |
173 | (setq lsp-enable-snippet nil) | |
174 | (setq lsp-keymap-prefix "C-c") | |
175 | (setq lsp-idle-delay 0.1) | |
176 | (setq lsp-file-watch-threshold nil) | |
177 | (setq lsp-enable-semantic-highlighting t) | |
178 | (setq lsp-enable-indentation t) | |
179 | (setq lsp-enable-on-type-formatting t) | |
180 | (setq lsp-before-save-edits nil) | |
181 | (setq lsp-auto-configure t) | |
182 | (setq lsp-signature-render-documentation t) | |
183 | (setq lsp-modeline-code-actions-enable nil) | |
184 | (setq lsp-log-io nil) | |
185 | (setq lsp-enable-imenu nil)) | |
186 | ||
187 | (with-eval-after-load 'lsp-headerline | |
188 | (setq lsp-headerline-breadcrumb-icons-enable nil)) | |
189 | ||
190 | (with-eval-after-load 'lsp-semantic-tokens | |
191 | (setq lsp-semantic-tokens-apply-modifiers t)) | |
192 | ||
193 | (with-eval-after-load 'lsp-clangd | |
194 | (setq lsp-clients-clangd-args | |
195 | '("--header-insertion-decorators" | |
196 | "--all-scopes-completion" | |
197 | "--clang-tidy" | |
198 | "--completion-style=detailed" | |
199 | "--header-insertion=never" | |
200 | "--inlay-hints" | |
201 | "--limit-results=1000" | |
202 | "-j=4" | |
203 | "--malloc-trim" | |
204 | "--pch-storage=memory")) | |
205 | (with-eval-after-load 'cc-mode | |
206 | (define-key c-mode-base-map (kbd "<f2>") #'lsp-clangd-find-other-file))) | |
207 | ||
208 | (with-eval-after-load 'treemacs-interface | |
209 | (global-set-key (kbd "<f12>") #'treemacs-delete-other-windows)) | |
210 | ||
211 | (with-eval-after-load 'treemacs-customization | |
212 | (setq treemacs-width 70)) | |
213 | ||
214 | (with-eval-after-load 'treemacs-mode | |
215 | (add-hook 'treemacs-mode-hook #'toggle-truncate-lines)) | |
216 | ``` | |
217 | ||
218 | And now we set up `lsp-ui-mode` to provide a few more features and key bindings: | |
219 | ||
220 | * `C-c d` to show rendered documentation. | |
221 | * `M-.` to peek at the definition of the item under point. | |
222 | * `M-?` to peek at references to the item under point. | |
223 | * `M-I` to peek at implementations of virtual methods. | |
224 | * `M-,` to jump back. | |
225 | ||
226 | ```elisp | |
227 | (with-eval-after-load 'lsp-ui-flycheck | |
228 | (setq lsp-ui-flycheck-enable t)) | |
229 | ||
230 | (with-eval-after-load 'lsp-ui-doc | |
231 | ; Disable on-the-fly showing of rendered documentation. | |
232 | (setq lsp-ui-doc-enable nil) | |
233 | (setq lsp-ui-doc-alignment 'frame) | |
234 | (setq lsp-ui-doc-header t) | |
235 | (setq lsp-ui-doc-include-signature t) | |
236 | (setq lsp-ui-doc-max-height 30) | |
237 | (setq lsp-ui-doc-use-webkit t)) | |
238 | ||
239 | (with-eval-after-load 'lsp-ui-peek | |
240 | (setq lsp-ui-peek-list-width 30) | |
241 | (setq lsp-ui-peek-always-show t)) | |
242 | ||
243 | (with-eval-after-load 'lsp-ui-sideline | |
244 | (setq lsp-ui-sideline-enable nil)) | |
245 | ||
246 | (with-eval-after-load 'lsp-ui | |
247 | (define-key lsp-ui-mode-map (kbd "M-." ) #'lsp-ui-peek-find-definitions) | |
248 | (define-key lsp-ui-mode-map (kbd "M-?" ) #'lsp-ui-peek-find-references) | |
249 | (define-key lsp-ui-mode-map (kbd "M-I" ) #'lsp-ui-peek-find-implementation) | |
250 | (define-key lsp-ui-mode-map (kbd "C-c d" ) #'lsp-ui-doc-show) | |
251 | (define-key lsp-ui-mode-map (kbd "C-c ! l") #'lsp-ui-flycheck-list)) | |
252 | ``` | |
253 | ||
254 | And finally, set up the C/C++ programming mode with a few settings: | |
255 | ||
256 | * Indentation of 2 spaces. | |
257 | * Simple auto-detection of coding style. | |
258 | * Marking of badly-styled comments. | |
259 | * Running the LSP client. | |
260 | ||
261 | ```elisp | |
262 | (defmacro set-up-c-style-comments () | |
263 | "Set up C-style /* ... */ comments." | |
264 | `(with-eval-after-load 'newcomment | |
265 | (setq-local comment-style 'extra-line))) | |
266 | ||
267 | (with-eval-after-load 'cc-mode | |
268 | (add-hook 'c-mode-common-hook #'lsp)) | |
269 | ||
270 | (with-eval-after-load 'cc-vars | |
271 | (setq c-mark-wrong-style-of-comment t) | |
272 | (setq c-default-style '((other . "user"))) | |
273 | (setq c-basic-offset 2) | |
274 | (add-hook 'c-mode-common-hook (lambda nil (progn (set-up-c-style-comments))))) | |
275 | ``` | |
276 | ||
277 | ### TODO Items | |
278 | ||
279 | * Whitespace cleanup on save. | |
280 | * Snippet support with `yasnippet`. | |
281 | * Add `which-key` support. | |
282 | * Add `ivy` support. | |
283 | * Add `company-prescient` for auto-completion ranking. | |
284 | ||
b4c7e352 FM |
285 | ## Minimal Emacs |
286 | ||
287 | Code snippets below should be added to your Emacs init file (e.g. `~/.config/emacs/init`). | |
288 | ||
289 | We'll start by enabling Emacs package repositories and declaring which packages we would like to have installed: | |
290 | ||
291 | ```elisp | |
292 | (with-eval-after-load 'package | |
293 | (setq package-archives | |
294 | '(("gnu" . "https://elpa.gnu.org/packages/") | |
295 | ("melpa" . "https://melpa.org/packages/"))) | |
296 | (push 'company package-selected-packages) | |
297 | (push 'eglot package-selected-packages)) | |
298 | ``` | |
299 | ||
300 | To avoid restarting Emacs, you can evaluate that previous s-expression by pointing at the last parenthesis and using `C-x C-e` or selecting the block and using `M-x eval-region`. | |
301 | ||
302 | Once done, run `M-x package-refresh-contents`, then `M-x package-install-selected-packages`. | |
303 | This should install some packages for you. | |
304 | ||
305 | Now, let's set up Eglot and Company: | |
306 | ||
307 | ```elisp | |
308 | (require 'eglot) | |
309 | (add-to-list 'eglot-server-programs '((c++-mode c-mode) "clangd")) | |
310 | (add-hook 'c-mode-hook 'eglot-ensure) | |
311 | (add-hook 'c++-mode-hook 'eglot-ensure) | |
312 | ||
313 | (with-eval-after-load 'prog-mode | |
314 | (add-hook 'prog-mode-hook #'company-mode)) | |
315 | ``` | |
316 | ||
317 | That's it. | |
318 | ||
d4ad3054 FM |
319 | # Code Checkers |
320 | ||
321 | ## On-the-fly `clang-tidy` | |
322 | ||
323 | `clangd` automatically integrates with `clang-tidy` if a `.clang-tidy` configuration file is available. | |
324 | See [the "`clang-tidy`" section of the CONTRIBUTING document](CONTRIBUTING.md#clang-tidy) on how to set up `clang-tidy`. |