CMakeでsrcとappを分けるときに設定しておくべきこと
CMakeで小さいサンプルを書くくらいなら、ルートの CMakeLists.txt に全部並べてもそこまで困りません。
ただ、main.cpp の横に共通処理が増えてきたり、あとからテストを足したくなったりすると、だんだん見通しが悪くなります。src と app を分けたくなるのはこのあたりから。
調べた範囲では、以下のように役割を分けておく形が扱いやすいようです。
src側でライブラリを作るapp側で実行ファイルを作る- ルートから
add_subdirectory()で両方を読む appからsrcのライブラリへtarget_link_libraries()する
先に構成だけ見ると、こんな形です。
ディレクトリ構成
my_project/
├── CMakeLists.txt
├── app/
│ ├── CMakeLists.txt
│ └── main.cpp
└── src/
├── CMakeLists.txt
├── hello.cpp
└── hello.hpp
src は共通処理の置き場。app は実行ファイルの置き場。
ここで src/hello.cpp を app から直接参照するのではなく、src 側を一度 hello_lib というライブラリにしてからリンクします。
app/main.cpp
-> hello_lib
-> src/hello.cpp
-> src/hello.hpp
この形にしておくと、あとで test や別の実行ファイルを足すときに hello_lib を使い回せます。
関連するCMake記事は CMakeシリーズ にまとめていきます。
ルートのCMakeLists.txt
ルートの CMakeLists.txt は、全体設定とディレクトリ追加だけにします。
add_subdirectory() は、指定したディレクトリにある CMakeLists.txt をビルドに追加するコマンドです。
https://cmake.org/cmake/help/latest/command/add_subdirectory.html
cmake_minimum_required(VERSION 3.20)
project(MyProject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(src)
add_subdirectory(app)
順番は src が先です。
app 側で hello_lib をリンクするので、add_subdirectory(app) の時点で hello_lib がすでに定義されている必要があります。ここを逆にすると、hello_lib が見つからない状態になります。
src側でライブラリを作る
src/CMakeLists.txt では、共通処理をライブラリとして定義します。
add_library() はライブラリターゲットを作るためのコマンドです。
https://cmake.org/cmake/help/latest/command/add_library.html
add_library(hello_lib
hello.cpp
)
target_include_directories(hello_lib
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
hello_lib はこのプロジェクト内で使うライブラリ名です。名前は何でもよいですが、app 側からも参照するので、何のライブラリか分かる名前にしておくと後で追いやすくなります。
ここで target_include_directories() に PUBLIC を付けています。target_include_directories() は、ターゲットごとのincludeパスを設定するコマンドです。
https://cmake.org/cmake/help/latest/command/target_include_directories.html
hello.hpp は hello_lib の中でも使い、hello_lib を使う側の app/main.cpp からも include します。そのため、ここでは PRIVATE ではなく PUBLIC としています。
ざっくり使い分けるなら、このくらいで見ています。
| 指定 | 使う場面 |
|---|---|
PRIVATE |
そのターゲットの中だけで必要 |
PUBLIC |
そのターゲット自身にも、使う側にも必要 |
INTERFACE |
使う側にだけ伝えたい |
最初は、「リンクした相手にも include パスを渡したいなら PUBLIC」くらいで見ておくと整理しやすいです。
app側で実行ファイルを作る
app/CMakeLists.txt は、実行ファイルを作って hello_lib をリンクします。
add_executable(my_app
main.cpp
)
target_link_libraries(my_app
PRIVATE
hello_lib
)
target_link_libraries() は、ターゲット同士の依存関係を設定するコマンドです。
https://cmake.org/cmake/help/latest/command/target_link_libraries.html
my_app から見ると、必要なのは hello_lib だけです。
hello_lib 側で include パスを PUBLIC にしてあるので、app/main.cpp ではそのまま hello.hpp を include できます。
#include "hello.hpp"
int main() {
hello();
}
ここで app 側に include_directories(../src) のような設定を書かなくてよいのがポイントです。依存関係は target_link_libraries() に寄せておく方が追いやすくなります。
サンプルコード
src/hello.hpp:
#pragma once
void hello();
src/hello.cpp:
#include "hello.hpp"
#include <iostream>
void hello() {
std::cout << "Hello from hello_lib" << std::endl;
}
app/main.cpp:
#include "hello.hpp"
int main() {
hello();
}
ビルドはルートディレクトリで行います。
cmake -S . -B build
cmake --build build
この例では、実行ファイルは次の場所に生成されます。
./build/app/my_app
避けたい書き方
appを先にadd_subdirectoryする
これは順番の問題です。
add_subdirectory(app)
add_subdirectory(src)
app の中で hello_lib を使っているなら、この順番だとまだ hello_lib が存在しません。
基本はこう。
add_subdirectory(src)
add_subdirectory(app)
include_directoriesを全体に効かせる
昔のサンプルだと、こういう書き方を見かけます。
include_directories(src)
小さいうちは動きます。ただ、どのターゲットがどの include パスに依存しているのか分からなくなりやすくなります。
新しく書くなら、まずは target_include_directories() を使う形に寄せます。
appからsrcのcppを直接足す
これも動きます。
add_executable(my_app
main.cpp
../src/hello.cpp
)
ただ、src を分けたのに app 側から中身を直接拾っていて、構成としては少し苦しくなります。
あとからテスト用の実行ファイルを作ると、また ../src/hello.cpp を足すことになります。src 側をライブラリにしておいて、必要なところからリンクする方が整理しやすいです。
この構成にする目安
全部のプロジェクトで最初から分ける必要はありません。
1ファイルの動作確認なら、ルートの CMakeLists.txt に add_executable() だけでも十分です。逆に、次のどれかに当てはまるなら src と app を分ける目安になります。
- 実行ファイルと共通処理を分けたい
- テストターゲットを足す予定がある
- 同じ処理を複数の実行ファイルから使いたい
- includeパスをターゲット単位で管理したい
「あとからもう1つターゲットが増えそう」という段階なら、分けておく判断になります。
まとめ
src と app を分けるときは、ディレクトリを分けるだけでなく、CMake上でもターゲットを分けておくと扱いやすくなります。
- ルートで
add_subdirectory(src)とadd_subdirectory(app)を呼ぶ src側でadd_library()を使うapp側でtarget_link_libraries()する- includeパスは
target_include_directories()でターゲットに持たせる
ファイルパスで無理につなぐより、ターゲット同士をリンクする。CMakeではこの形にしておく方が、あとから構成を変えやすいです。


