阅读代码时如果能够很方便地跳转到函数、类型定义处,会极大地提高效率。使用 grep 或 ctags 或 gtags 的问题在于给出的结果不够严谨,可能会给出很多候选,容易打断思路,另一方面 c++ 本身语法非常复杂,继承、重载等会让选择正确的跳转处变得更加困难, ctags 或 gtags 之类的静态代码分析工具对 c++ 的支持也相当有限。
clang 编译器较之 gcc 在进行代码分析方面提供了良好的支持,编译器给出的结果往往是最正确和最丰富的。
本文描述了使用如何将 emacs 打造成阅读 c++ 代码的利器。
编译数据库
编译数据库( compile_commands.json
)里面记录了每一个源代码文件对应的编译命令,有了编译数据库就可以从 clang 编译器获取最详尽的代码分析数据,让代码跳转、自动完成更加精确。
不同的构建工具可以使用相应的工具来生成编译数据库( compile_commands.json
)。
bazel
安装
使用
Bazel And Compile Commands
脚本可以很方便地为 Bazel 构建项目生成 compile_commands.jsongit clone https://github.com/vincent-picaud/Bazel_and_CompileCommands.git
生成
../Bazel_and_CompileCommands/setup_compile_commands.sh ../Bazel_and_CompileCommands/create_compile_commands.sh //...
make
Bear 是一个生成生成编译数据库的工具,其工作原理是监视编译工具(如:make)调用的编译命令,是一种很通用的方案。
安装
yaourt -S bear
生成
bear make
cmake
安装
yaourt -S cmake
生成
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .
rtags
RTags is a client/server application that indexes C/C++ code and keeps a persistent file-based database of references, declarations, definitions, symbolnames etc.
rtags 是基于 clang,根据编译器提供的信息可以实现精确的查找定义、引用及调用、列出派生类等。
启动后台服务
nohup rdm &
提交项目信息到后台服务
由于 bazel 会将代码通过软链接放到一个沙箱中进行编译,导致生成的编译数据库中代码目录与实际编辑的路径不一致,使用 rtags 查找定义时会报 "/xxx/xxx.cpp not indexed" 错误,可以通过提交项目信息时指定 --project-root
进行修正。
rc -J . --project-root $PWD
在 emacs 中整合以上工具
请参考 https://github.com/Andersbakken/rtags#elisp
以下是我的配置,首先需要安装这些 emacs 包: rtags
helm-rtags
company-rtags
flycheck-rtags
;;;; rtags (defun my-rtags-load-compile-commands-command () "rtags load compile_commands.json command" ;; compile_commands.json generate by https://github.com/vincent-picaud/Bazel_and_CompileCommands ;; will refer source code from bazel's sandbox, must use "--project-root" to fix it. (let ((project-root default-directory) (tmp-project-root "")) (while (and project-root (not (file-exists-p (concat project-root "compile_commands.json")))) (setq tmp-project-root (file-name-directory (directory-file-name project-root))) (message "tmp-project-root: %s, project-root: %s" tmp-project-root project-root) (if (equal tmp-project-root project-root) (setq project-root nil) (setq project-root tmp-project-root))) (unless project-root (message "RTags: compile_commands.json not exists") (setq project-root default-directory)) (message "RTags: %s" (concat project-root "compile_commands.json")) (format "rc -J %s --project-root %s" project-root project-root))) (defun my-rtags-run () "rtags startup with generated compile_commands.json" (interactive) (rtags-start-process-unless-running) (shell-command (my-rtags-load-compile-commands-command))) (defun my-rtags-build () "rtags startup use compile_commands.json generate from build tool" (interactive) (cond ((file-exists-p "BUILD") (my-rtags-bazel)) ((file-exists-p "CMakeLists.txt") (my-rtags-cmake)) ((file-exists-p "Makefile") (my-rtags-make)) (t (error "No build tool detected")))) (defun my-rtags-bazel () "rtags startup use compile_commands.json generate from bazel" (interactive) (let ((tool_dir "~/Opensource/Bazel_and_CompileCommands") (command "")) (setq command (format "%s/setup_compile_commands.sh; %s/create_compile_commands.sh //..." tool_dir tool_dir)) (setq command (read-string "Build bazel compile_commands.json: " command nil nil)) (unless command (error "Build compile_commands.json for bazel failed")) (rtags-start-process-unless-running) (async-shell-command (concat command " && " (my-rtags-load-compile-commands-command))))) (defun my-rtags-make () "build compile_commands.json for make" (interactive) (let ((command (read-string "Build make compile_commands.json: " "bear make" nil nil))) (unless command (error "Build compile_commands.json for make failed")) (rtags-start-process-unless-running) (async-shell-command (concat command " && " (my-rtags-load-compile-commands-command))))) (defun my-rtags-cmake () "build compile_commands.json for cmake" (interactive) (let ((command (read-string "Build cmake compile_commands.json: " "cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ." nil nil))) (rtags-start-process-unless-running) (async-shell-command (concat command " && " (my-rtags-load-compile-commands-command))))) (setq rtags-completions-enabled t) (eval-after-load 'company '(add-to-list 'company-backends 'company-rtags)) (setq rtags-autostart-diagnostics t) (rtags-enable-standard-keybindings) (require 'helm-rtags) (require 'flycheck-rtags) (define-key c-mode-map (kbd "C-c C-j") 'rtags-find-symbol) (define-key c++-mode-map (kbd "C-c C-j") 'rtags-find-symbol) (define-key c-mode-map (kbd "C-c C-b") 'rtags-location-stack-back) (define-key c++-mode-map (kbd "C-c C-b") 'rtags-location-stack-back) (define-key c-mode-map (kbd "C-c C-r") 'rtags-find-references) (define-key c++-mode-map (kbd "C-c C-r") 'rtags-find-references)
第一次需在项目根目录执行 M-x my-rtags-build
,它会先生成编译数据库( compile_commands.json
)再启动 rtags 后台服务。以后可直接运行 rtags 后台服务 M-x my-rtags-run
。
现在随便打开项目中的 c++ 源代码,将光标放到变量名上,然后按 C-c C-j
跳转到定义处,更多用法请参考 rtags 文档。