【CMake】ヘッダファイル参照を自動で追加
※2020年に別ブログで書いた記事の移植版です。
includeディレクトリを洗い出そう
CMakeでコンパイラに対してinclude対象のディレクトリを設定するには、include_directories() だとか target_include_directories() がある。現在の推奨は後者らしいのだが、俺の場合わりと小規模なプロジェクトを作ることが多いし、引数多いと面倒なので前者を使ってたりする。
# 以降のターゲット全てにincludeディレクトリを適用
include_directories(${INCLUDE_DIRS})
# fuga.exe には上記のinlucdeパスが適用される
add_executable(fuga ${SOURCES})
んで、どちらのinclude某を使うにせよ、予め洗い出しておいたinclude対象パスの一覧をテキトーなリスト(上記で言うところのINCLUDE_DIRS)に入れて、add_executableなどに渡してやる感じになる。
しかし、色々なディレクトリに参照したいヘッダファイルが散乱してる場合、includeディレクトリを手動で洗い出すのが面倒だ。というか、プロジェクトのフォルダ構成が頻繁に変わるという特殊な事情も相まって、ヘッダファイルが存在するディレクトリを把握できない。
可能であれば、「ヘッダファイルが入ってるディレクトリ」を勝手に走査して、勝手にリストにぶちこんでほしい。でも洗い出し処理を外部スクリプトで書いて環境変数経由でCMakeに受け渡すのもなんか嫌だし、そこらへんの処理はCMakeLists.txtの中だけで完結させたい。
まずは拾ってくる
そんな、需要があるのか無いのかよく分からない願望をもとにググってみたところ、なんとかそれらしきものを見つけ出せた。完全に拾い物なのだが、下記のようなマクロを使えば実現できるようだ。
# ヘッダが入ってるディレクトリを洗い出すマクロ
MACRO(HEADER_DIRECTORIES return_list)
FILE(GLOB_RECURSE new_list *.h)
SET(dir_list "")
FOREACH(file_path ${new_list})
GET_FILENAME_COMPONENT(dir_path ${file_path} PATH)
SET(dir_list ${dir_list} ${dir_path})
ENDFOREACH()
LIST(REMOVE_DUPLICATES dir_list)
SET(${return_list} ${dir_list})
ENDMACRO()
# マクロ呼び出し
HEADER_DIRECTORIES(header_dir_list)
# 件数カウント
list(LENGTH header_dir_list header_dir_list_count)
message(STATUS "Count: ${header_dir_list_count}")
# リスト一覧出力
message(${header_dir_list})
# いつもの
include_directories(${header_dir_list})
add_executable(fuga hoge.cpp)
しかし、なんかコピペだけして「動いた!閉廷!以上!皆解散!」で終わらせるのはなんとなく気持ち悪いので、お勉強がてらマクロ部分を分解して、何をやってるかちょっと見てみよう。
細かく見てみる
# マクロ開始
MACRO(HEADER_DIRECTORIES return_list)
...
# マクロ終了
ENDMACRO()
# マクロ呼び出し
HEADER_DIRECTORIES(header_dir_list)
マクロの開始と終わり。プログラミング言語でいうところの { } みたいなもん。MACRO()には定義するマクロの名前に続けて、任意の引数を半角スペース区切りで渡すことができる。今回は、リスト「header_dir_list」にヘッダの一覧をぶちこみたいので、それをマクロ側で引数「return_list」として扱う。
FILE(GLOB_RECURSE new_list *.h)
作業フォルダ以下のヘッダファイル一覧を、new_listにぶちこむ。「GLOB_RECURSE」なので再帰的に潜って見てくれる。
SET(dir_list "")
変数dir_listを空で初期化しとく。
FOREACH(file_path ${new_list})
...
ENDFOREACH()
for~each的なやつ。new_listには、さっきFILEで洗い出したヘッダファイルの一覧が入っているので、そいつからfile_pathにひとつひとつ切り出して格納していく。
GET_FILENAME_COMPONENT(dir_path ${file_path} PATH)
GET_FILENAME_COMPONENT()を使えば、指定したファイル名を加工して変数に格納できるっぽい。でも最後のPATHってなんぞ?ってところで公式ドキュメント見てみる。
PATH = Legacy alias for DIRECTORY (use for CMake <= 2.8.11)
どうやら、「今はDIRECTORYだけど昔はPATHだったよ!」みたいな感じ。しかし俺が今使ってるバージョンは3.16だしなぁ。
DIRECTORY = Directory without file name
で、DIRECTORYの説明にはこう書いてあった。要は、ファイル名を抜いてディレクトリ名だけ持ってくるいうことやね。
SET(dir_list ${dir_list} ${dir_path})
その結果をdir_listに追加してると。なるほどなるほど。でも、これでやると同じ階層にヘッダファイルいっぱいあるわけじゃん?つまり、同じディレクトリ名が大量に「dir_list」に格納されない?そこんとこ大丈夫なの?
LIST(REMOVE_DUPLICATES dir_list)
SET(${return_list} ${dir_list})
そこで出てくるのがこの「REMOVE_DUPLICATES」。
公式ドキュメントの説明によると「Removes duplicated items in the list. 」とある。つまり、リストの中で重複してるやつはぶち殺しちゃうわけだな。天才かよ。結果として、マクロが終わる頃にはヘッダファイルが入っているフォルダの一覧が格納されたリストが完成する。
結局そのまま使わせてもらう
先人が作った車輪は、なんとかそのまま動く模様なので、実務でバリバリ使わせてもらってる。しかし、CMakeビギナーの俺には、マクロなんぞ自作できる気がしないなぁ。
この記事が気に入ったらサポートをしてみませんか?