LoginSignup
20
12

More than 3 years have passed since last update.

emacsのC++開発環境を整理する

Last updated at Posted at 2019-08-30

emacsでC++のプログラムを書くのに、companyとかironyとかlspとかrtagsとかいろいろ入れていったら、いったいどれが何の機能を提供していて、どの設定を読むのかわからなくなったので、一から設定して整理してみる。

要約

  • emacs のコード補完とかエラーチェックのパッケージいっぱいあるけど、何をするものか理解しないで適当に人の設定を持ってくるとうまく動かないよ。
  • コードチェックは flymake か flycheck のどちらかを選択し、両方を有効にしないこと。
  • コード補完に company を使う場合は、backend に irony か lsp のどちらか(あるいは他の何か)を選択し、両方を有効にしないこと。
  • 公式ドキュメントが一番わかりやすいよ。
  • emacsにこだわりがなければ、VisualStudioCodeとか使った方が良いかもよ。

準備

今回、実験のためにまっさらな.emacs.d/init.el からスタートして、パッケージを一つ一つ追加して構築していく。

この記事の過程は、githubに上げてある。

まずは、以下の内容で ~/.emacs.d/init.el を作成する。

init.el
;;; package
(require 'package)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(package-initialize)

;;; use-package
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))


;;; 個別環境用設定の読み込み
(condition-case err
    (load-file "$HOME/.emacs.d/init-local.el"))

;;; custom-file
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (file-exists-p custom-file)
  (load custom-file))

さらに、touch ~/.emacs.d/custom.el して空のcustom.el を作成する。
この状態でemacsを再起動するか、init.el を M-x eval-bufferすると、use-package がインストールされ、custom.el が以下のような内容になる。

custom.el
(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(package-selected-packages (quote (use-package))))
(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 )

最初の部分はpackageを使うという設定。デフォルトでは gnu と言う設定だけが存在して、そこにmelpaとかmarmaladeとか好みに合わせて追加する。

次の部分は、use-packageを使う設定。インストールされていなければインストールまでしてしまう。

3番目の部分は、~/.emacs.d/init-local.el があったらそれを読み込むと言う設定。(と言うか、読んでみてエラーを握りつぶす)

最後のcustom.el を追い出す方法については、Out of Dimension: Emacs25 の package-selected-packages を何とかするを参考にした。
Caskからpackage.elに戻ってきた - YAMAGUCHI::weblogには、M-x package-install-selected-packagesでpackage-selected-packagesにあるパッケージをインストールしてくれることが書いてあった。

この段階は github の init ブランチで見られる。

flymake

最初に試すのは、emacsにbuilt-in で入っている flymake
ソースを修正すると、勝手に裏でビルドして、エラーを教えてくれるもの。
この記事を書いているときには、emacs26.2にbuilt-inされているのがflymake 0.3で、gnu にあるのが1.0.8らしい。

設定は、init.elに以下のような内容を追加する。

init.el
;;; flymake
(use-package flymake
  :init
  (add-hook 'c-mode-common-hook 'flymake-mode)
  :commands flymake-mode
  )

use-packageの使い方はこの記事の主題ではないので詳しくは別で調べて欲しいが、cc-mode系のモード(c-modeとかc++-modeとか)になるとflymake-modeを有効にする設定になる。
cc-mode系のモードのバッファが開かれるまではflymakeはロードされない。
(use-packageには:hookと言うのもあるのだが、c-mode-common-hookにあたるモード指定がわからなかった)

flymakeが有効になるためには、check-syntaxターゲットを持ったMakefileが必要らしい。
また、デフォルトではキー割り当てがされていないし、見た目もとてもわかりにくいので、いろいろ設定したり追加のパッケージを入れる必要がありそうだ。
この記事では、flymakeの代わりに別のパッケージを使うので、flymakeについてはここでおしまい。

この段階は、githubの flymake ブランチで見られる。

flycheck

flycheckはemacsにはbuilt-inされていないが、flymakeよりも進化しているんじゃないかと言うパッケージ。
package-installでさくっとインストールして、init.el に以下を足す。

init.el
;; flycheck
(use-package flycheck
  :config
  (global-flycheck-mode t)
  )

個別にモード設定しても良いのだが、なんでも卒なくこなしてくれそうなので、global-flycheck-modeでいつでも有効にしてしまう。(これを書いてて気が付いたんだけど、init.elに対して警告がたくさん出る・・・)
flycheckのチェッカーは言語(と言うかモード)によって違うので、設定もさまざまなのだが、上記のシンプル設定で c++ のソースを開くと、c/c++-clangと言うチェッカーが選ばれる。(flycheck--automatically-enabled-checkersで確認。clangがインストールされていない環境だったら、c/c++-gccが選ばれるかもしれない)
サポートされている言語とチェッカーの設定については、公式サイトの説明を見るのが良いだろう。
c/c++-clangの場合、最低限設定が必要そうなのは、flycheck-clang-language-standard(C++標準の設定)とflycheck-clang-include-path(インクルードディレクトリの設定)だろう。
しかし、これらはプロジェクトによって違うものであり、init.elで決め打ちにしてしまうのは問題があるように思われる。
そこでやり方を検索すると、.dir-locals.el を使ったものがひっかかる。
プロジェクトのトップディレクトリに以下の内容で .dir-locals.el を置く。

dir-locals.el
((nil .
      ((eval .
             (set (make-local-variable 'project-directory)
                  (file-name-directory (locate-dominating-file default-directory ".dir-locals.el"))
                  )
             )
       (eval .
             (set (make-local-variable 'include-directories)
                  (list
                   (expand-file-name
                    (concat project-directory "include"))
                   )
                  )
             )
       (eval setq flycheck-clang-include-path include-directories)
       (eval setq flycheck-clang-language-standard "c++17")
       )
      ))

この状態でソースファイルを開くと、以下のような警告が表示される。

The local variables list in /home/emacs/git/emacs-init/simpleproject/
contains values that may not be safe (*).

Do you want to apply it?  You can type
y  -- to apply the local variables list.
n  -- to ignore the local variables list.
!  -- to apply the local variables list, and permanently mark these
      values (*) as safe (in the future, they will be set automatically.)

  * eval : (set (make-local-variable (quote project-directory)) (file-name-dire\
ctory (locate-dominating-file default-directory ".dir-locals.el")))
  * eval : (set (make-local-variable (quote include-directories)) (list (expand\
-file-name (concat project-directory "include"))))
  * eval : (setq flycheck-clang-include-path include-directories)
  * eval : (setq flycheck-clang-language-standard "c++17")

yを押すと有効になるがファイルを開くたびに同じことを聞かれる。!を押すとcustom.el に記録され、次からは聞かれない。
これ、他にも副作用があって、project-directoryとinclude-directoriesっていう二つのバッファローカル変数を作ってるんだけど、companyとかと組み合わせると他のエラーが出る。
同名のグローバル変数を作ってごまかしたんだけど、もっと良い方法はないのかな・・・。

flycheckもデフォルトのままだとかなり寂しい見た目になる。

この段階は、githubの flycheck ブランチで見られる。

irony

irony は、libclang を使ってコード補完とシンタックスチェックをやってくれるものです。flycheck-ironyパッケージを入れるとflycheckのチェッカーとして働きます。(つまり、前項の.dir-locals.el は不要と言うことに・・・)

まずは、package-installでironyとflycheck-ironyをインストールします。
そして、init.el を以下のように。

init.el
;; flycheck
(use-package flycheck
  :config
  (when (locate-library "flycheck-irony")
    (flycheck-irony-setup))
  (global-flycheck-mode t)
  )

;; irony
(use-package irony
  :commands irony-mode
  :config
  (custom-set-variables '(irony-additional-clang-options '("-std=c++17")))
  (add-hook 'irony-mode-hook 'irony-cdb-autosetup-compile-options)
  (add-hook 'c-mode-common-hook 'irony-mode)
  )

irony は irony-server と言うプログラムとやりとりするので、初回はM-x irony-install-server でirony-serverをビルドする。(成功すると、~/.emacs.d/irony/bin/ に入る)
このirony-serverのビルドが結構失敗するので、エラーで検索するといろいろ引っかかる。
私が試したUbuntuの場合は、llvm, clang, libclang-dev, makeのパッケージが必要だった。(clangだけインストールされた状態で試していた)
Mac の場合も素直にはビルドできなくて、手動でllvmの場所を教えてあげる必要があるようだ。(参考)

ビルドに成功したら、プロジェクトごとの設定。
ironyの場合、cmake等が出力する compile_commands.json か、.clang_complete のどちらかを見る。

ドキュメントによると、.clang_complete の場合はプロジェクトのトップディレクトリに置くことになっているが、compile_commands.json の場合はどこに置くかの情報がない。
今試したところ、プロジェクトトップディレクトリの中にbuild ディレクトリを作ってそこにおいても、プロジェクトのトップディレクトリと同じ階層にbuildディレクトリを置いても、どちらも認識した(ように見える)。
.clang_complete は、コンパイラのコマンドラインオプションを並べたもの。

irony 自体は、後述のcompanyなどと組み合わせない場合は、標準のM-x completion-at-point くらいしか使い方がない。(あとは、flycheckのcheckerとして動いてくれる)

この段階は、githubの irony ブランチで見られる。
simpleproject が .clang_complete のサンプル、cmakeproject が compile_commands.json のサンプルになっている。cmakeprojectの方は、以下のように compile_commands.json を生成する。

cd cmakeproject
mkdir build && cd build
cmake ..

lsp-mode

lsp-modeは、emacs から Language Server Protocol を使うためのものである。Language Server Protocolが何かは是非調べてみてもらいたい。

lsp-modeも、自身はlsp server とのやり取りをするためのものなので、lsp-ui, company-lsp, lsp-treemacs, helm-lsp, dap-mode とかと組み合わせて使えと書いてある。

まずは最低限の機能を見ていきたいので、package-install で lsp-mode と lsp-ui をインストールする。
lsp-ui は flycheck と連携して高度なUIを提供するものだそうだ。
用途から考えても、irony と lsp-mode は同時に使うものではない。

次に、言語毎のLanguage Server が必要になる。サポートしている言語とLanguage ServerはSupported languagesに一覧になっている。
例えば、C++の場合はccls, clangd, cquery の3種類が選べる。
ccls も試してみたが、いまいち動きが悪かったので、ここではclangdで説明する。
Ubuntuの場合はclang-tools-x みたいなパッケージで入る。(私の環境では clang-tools-6.0が入っていたのでそのまま使っているが、clang-tools-8 の方が幸せになれるかもしれない→うん、clangd-8を入れたら、lspの表示がいろいろ豪華になった。見やすいかは別にして。)
Macの場合は、brew install llvmとかで入るが、/usr/local/opt/llvm とか言うちょっと変な場所に入る。clangd は /usr/local/opt/llvm/bin/clangd だ。

次に、init.el に以下を追加する。(irony と flycheck-ironyは無効にしたうえで)

init.el
;; lsp-mode
(use-package lsp-mode
  :hook ((python-mode c++-mode) . lsp)
  :commands lsp
  :config
  (setq lsp-prefer-flymake nil)
  (setq lsp-clients-clangd-executable "clangd-6.0")
  )
(use-package lsp-ui :commands lsp-ui-mode)

デフォルトのままだと、lsp-uiがflycheckとflymakeを両方使おうとして設定も複雑になるし誰がエラーを出しているかわからなくなるので、flycheckを使う場合は lsp-prefer-flymake を nil にして flymake を無効にする。(これをしないと、include地獄に陥る)
lsp-clients-clangd-executable は、clangd が clangd と言う名前で PATH の通ったところにある場合は設定不要である。PATHが通っていない場合や、コマンド名が違う場合はここで設定する。

この状態で、初めてC++のソースを開こうとすると以下のように聞かれる。

In this buffer, type RET to select the completion near point.

Possible completions are:
Do not ask more for the current project(add "~/git/emacs-init/" to lsp-session-\
folder-blacklist)
Do not ask more for the current project(select ignore path interactively).
Do nothing and ask me again when opening other files from the folder.
Import project by selecting root directory interactively.
Import project root ~/git/emacs-init/

Import project by selecting root directory interactively. を選び、プロジェクトのトップディレクトリを指定すると、ファイルが開く。
このときの情報は、~/.emacs.d/.lsp-session-v1 に保存される。

clangd が読んでくれる設定ファイルは、cmopile_commands.json と compile_flags.txt。
compile_flags.txt の書式は .clang_completeと同じく行ごとにコンパイルオプションを並べる。
irony と違って、compile_commands.json をうまく探してくれないので、out-of-sourceビルドをしたい場合はビルドディレクトリにあるcompile_commands.json からプロジェクトのトップディレクトリにシンボリックリンクを張る。
あるいは、compile_flags.txtを書いた方が早いかもしれない。

この段階は、githubの lsp-mode ブランチで見られる。
compile.flags.txtのサンプルがsimpleproject、compile_commands.jsonがcmakeproject。
cmakeの方は先ほどと同じように compile_commands.jsonを生成した後、cmakeproject直下にシンボリックリンクを張る。

本当はここからetags, gtags, rtagsと続くつもりだったのだが、長くなりすぎるのでtags系は分割する。
ここまででだいたいバックグラウンドになるパッケージを紹介したので、いよいよ companyの登場である。

company-mode

company-modeは、emacsのテキスト補完のフレームワークである。
company-backends と言うリストになんでもかんでもつっこめば、いろいろ補完してくれる(はず)。

今回紹介した中だと、irony か lsp から選択することになると思うが、ここではlsp を使う想定で行く。

まずは、package-install で company, company-lsp をインストールする。

そして、init.el に以下を追加する。

init.el
;; company
(use-package company
  :config
  (global-company-mode t)
  (global-set-key (kbd "<C-tab>") 'company-complete)
  (bind-keys :map company-active-map
             ("C-n" . company-select-next)
             ("C-p" . company-select-previous))
  (bind-keys :map company-search-map
             ("C-n" . company-select-next)
             ("C-p" . company-select-previous))
  (bind-keys :map company-active-map
             ("<tab>" . company-complete-selection))
  )
(use-package company-lsp :commands company-lsp)

この設定で、Ctrl + TABを押すと候補一覧が表示され、C-n/C-pで選んでTABまたはEnterで確定する。
company-lsp をロードすると、勝手に company-backends に入るので、手動で追加する必要はない。

この段階は、githubの company-mode ブランチで見られる。

設定ばかり書いて使い方を書いてないけど、設定ができればきっと使える。
と言うことでTAGS編に続きます。

20
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
12