CMake事始め
はじめに
ビルドは自動化したいものです.
ではCMakeを使いましょう.
前提
環境
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
$ cmake --version
cmake version 3.22.1
知識
- コンパイル によってソースファイルからオブジェクトファイルを生成する
- リンク によってオブジェクトファイル・静的ライブラリから実行ファイルを生成する
- コンパイル・リンクによってソースファイルから実行ファイルを生成することを ビルド と言う
本記事の流れ
本記事はいくつかのステップにより,段階的に CMakeLists.txt のコマンドについて解説しています.
各ステップ (Step0 を除く) はサンプルプログラムをビルドするための
- コマンド
- CMakeLists.txt
を順に解説しており,コマンドと CMakeLists.txt とを対応付けて理解できるようになっています.
サンプルプログラムは各ステップの冒頭に折り畳んで示しています.
Step0
まず CMake を利用する動機について解説します.
ツールを利用するとき「何故そのツールを利用するのか」を意識することは重要です.
Step0 は本記事の趣旨からやや外れるため,読み飛ばせるよう折り畳んでいます.
読み飛ばして頂いても問題ありませんが,後のステップで登場する用語・操作の解説を含んでいるため,ご一読ください.
CMake を利用する動機
main.cpp
をビルドするとき,次のようにします.
g++ main.cpp
簡単ですね.
しかし多くの場合は次のようにするでしょう.
g++ -Wall -Wextra -Werror -c main.cpp
g++ -o main main.o
もうコマンドを入力したくなくなってきました.
では次のような構成の main.hpp
, main.cpp
, foo.cpp
, bar.cpp
から実行ファイル main
をビルドするなら?
.
├── main.hpp
├── main.cpp
├── foo.cpp
└── bar.cpp
考えたくもありません.
g++ -o main main.cpp foo.cpp bar.cpp
でできますが,ここではコンパイルとリンクとを別で実行することにします.
ではどうすれば良いでしょうか.
まず思いつくのは「シェルスクリプトを書く」でしょう.
コンパイル・リンクに必要なコマンドを纏めたシェルスクリプトを作成すればビルドを効率化できます.
シェルスクリプト
次の build.bash
は,前述の main.hpp
, main.cpp
, foo.cpp
, bar.cpp
をビルドするシェルスクリプトの一例です.
#!/bin/bash
srcs=("main.cpp" "foo.cpp" "bar.cpp")
rm -f main *.o *.d
g++ -g -Wall -Wextra -Werror -c ${srcs[@]}
g++ -o main ${srcs[@]/%.cpp/.o}
このようなシェルスクリプトを作成しておくと
./build.bash
のみでビルドを実行できます.
しかし,このシェルスクリプトには次のような問題があります.
- bash 以外のユーザを考慮していない
共同開発者が bash と互換性のないシェルのユーザであれば利用できません. - ワーキングディレクトリを考慮していない
プロジェクトのルートディレクトリ以外から実行できるため,予期せぬ問題が発生するかもしれません. - リビルドの実行時間を考慮していない
例えばfoo.cpp
のみを変更した場合でも,全てのソースファイルをリコンパイルします.大きなプロジェクトではコンパイルに数時間を要することがあり,変更のないソースファイルをリコンパイルするのは非効率的です.
これらの問題を解決するビルドシステムに make があります.
make
make は広く利用されているビルドシステムで,Makefile に記述したビルドルールに従ってビルドを実行します.
次の Makefile は,make によって前述の main.hpp
, main.cpp
, foo.cpp
, bar.cpp
をビルドするための Makefile の一例です.
CXX := g++
CXXFLAGS:= -g -Wall -Wextra -Werror
TARGET := main
SRCS := main.cpp foo.cpp bar.cpp
OBJS := $(SRCS:.cpp=.o)
INCDIR :=
LIBDIR :=
LIBS :=
.PHONY: all
all: clean $(TARGET)
$(TARGET): $(OBJS)
$(CXX) -o $@ $^ $(LIBDIR) $(LIBS)
$(OBJS): $(SRCS)
$(CXX) $(CXXFLAGS) $(INCDIR) -c $^
.PHONY: clean
clean:
rm -f $(TARGET) *.o *.d
Makefile を記述しておくと
make
のみでビルドを実行できます.
make によるビルドは Makefile を配置したディレクトリ以外から実行できず,またリビルドで変更のないソースファイルをコンパイルし直すこともありません.
make はファイルの更新の有無をタイムスタンプから確認しています.
しかし,この Makefile にも次のような問題があります.
- プラットフォームに依存する
make と同様に Makefile に従ってビルドを実行するビルドシステムに NMAKE があり,Unix 環境では一般に make が利用されますが,Windows 環境では NMAKE が利用されることがあります.両者の Makefile の文法には互換性がなく,共同開発者が互換性のないプラットフォームのユーザであれば利用できません. - 文法が独特
自由度は高いですが,可読性は高くありません.
NMAKE では
nmake
でビルドを実行します.
これらの問題を解決するのが CMake です.
CMake
CMake はビルドシステムのジェネレータで,CMakeLists.txt に記述した内容に従って Makefile を生成します.
このとき CMake はプラットフォームに応じた Makefile を生成するため,CMake を利用するとクロスプラットフォームでのビルドが容易になります.
CMake はプラットフォームに応じて異なるビルドシステムを利用するため「プラットフォームが利用するビルドシステムにおいて Makefile に相当するファイルを生成する」と表現する方が適切なのですが,簡単のため「Makefile を生成する」と表現しています.
次の CMakeLists.txt は,CMake によって前述の main.hpp
, main.cpp
, foo.cpp
, bar.cpp
をビルドするための Makefile を生成する CMakeLists.txt の一例です.
cmake_minimum_required(VERSION 3.22)
project(main
VERSION 1.0
LANGUAGES CXX
)
add_executable(${PROJECT_NAME}
main.cpp
foo.cpp
bar.cpp
)
CMakeLists.txt を記述して
cmake -S . -B build
を一度実行しておくと
cmake --build build
のみでビルドを実行できます.
cmake --build build
は内部で make
を実行し,事前に生成された Makefile に従って実行ファイルを出力します.
ここでも「各プラットフォームが利用するビルドシステムにおいて
make
に相当するコマンドを実行する」と表現する方が適切なのですが,やはり簡単のため「make
を実行する」と表現しています.
CMakeLists.txt は Makefile と同様に独自の文法を持っていますが,Makefile と比較して可読性が高く,新たに習得するのであれば Makefile より CMakeLists.txt の文法の方が容易でしょう.
Step1
Step1 では次のような構成を考えます.
.
├── main.hpp
├── main.cpp
├── foo.cpp
└── bar.cpp
サンプルプログラム
#pragma once
auto foo() -> void;
auto bar() -> void;
#include "main.hpp"
auto main() -> int {
foo();
bar();
}
#include "main.hpp"
#include <iostream>
auto foo() -> void {
std::cout << "foo" << std::endl;
}
#include "main.hpp"
#include <iostream>
auto bar() -> void {
std::cout << "bar" << std::endl;
}
-
command ver.
次のコマンドを実行します.
# main.cpp (ソースファイル) から main.o (オブジェクトファイル) を生成 g++ -c main.cpp foo.cpp bar.cpp # main.o (オブジェクトファイル) から main (実行ファイル) を生成 g++ -o main main.o foo.cpp bar.cpp
これで実行ファイル
main
が生成されます. -
CMake ver.
次の位置に CMake Lists.txt を作成します.. ├── main.hpp ├── main.cpp ├── foo.cpp ├── bar.cpp └── CMakeLists.txt
CMakeLists.txt を次のように記述します.
CMakeListe.txt# 各行のシャープ記号より後はコメントと認識されます cmake_minimum_required(VERSION 3.22) project(main VERSION 1.0 LANGUAGES CXX ) add_executable(main main.cpp foo.cpp bar.cpp )
この後に
.
で次のコマンドを実行します.cmake -S . -B build cmake --build build
これで
./build
に実行ファイルmain
が生成されます.後述しますが,
cmake -S . -B build
を毎回実行する必要はなくcmake --build build
のみでリビルドを実行できます.
以降のステップではCMakeLists.txt のみを掲載しますが,ビルド手順は同じです.
in-source ビルド・out-of-source ビルド
cmake -S . -B build
はソースディレクトリを .
に,ビルドディレクトリを build
にそれぞれ設定し,CMakeLists.txt に記述した内容に従ってMakefile 等のビルドに必要なファイルを build
ディレクトリに出力します.build
ディレクトリが存在しなければ同時に作成します.
このようにビルドディレクトリがソースディレクトリと異なるビルドを out-of-source ビルド と言います.
ソースファイルの存在するディレクトリをソースディレクトリ,
cmake
によって生成される Makefile 等のファイルやビルドによって生成される実行ファイルが出力されるディレクトリをビルドディレクトリと言います.
代えて
cmake .
はソースディレクトリ・ビルドディレクトリを共に .
に設定します.
このようにビルドディレクトリがソースディレクトリと同じであるビルドを in-souce ビルド と言います.
in-source ビルドと out-of-source ビルドとでは out-of-source ビルドをすべき です.
in-source ビルドではソースファイルと生成ファイルとが単一のディレクトリに混在するため,ファイル管理が困難です.
対して out-of-source ビルドは rm -r build
のみで全ての生成ファイルを削除できるため,クリーンビルドが容易です.
これらのコマンドは build
ディレクトリが作成されていない場合や CMakeLists.txt を更新した場合に実行します.
このステップで登場したコマンドは次の3つです.
cmake_minimum_required()
次のように最低要件を設定します.
cmake_minimum_required(
VERSION 3.22 # バージョン
)
バージョンは必須の項目で,設定したバージョンより古い CMake を利用した場合にエラーを発生させます.
cmake_minimum_required()
はトップレベルのCMakeLists.txt の先頭に記述します.
「トップレベルの」は「ソースディレクトリの」と同義です.
敢えてこのような表現をするのは,ソースディレクトリより下の階層に別の CMakeLists.txt を作成する場合があるためです.
project()
次のようにプロジェクトの情報を設定します.
project(cmake_tutorial # プロジェクト名
VERSION 1.0 # バージョン
DESCRIPTION "This project is a tutorial on CMake" # 説明文
HOMEPAGE_URL "example.com" # ホームページURL
LANGUAGES C CXX # 使用言語
)
プロジェクト名は必須の項目です.また多くの場合,バージョン・使用言語の項目を設定しておく必要があります.
使用言語は
- C なら
C
- C++ なら
CXX
のように設定します.
C
CXX
はデフォルトで設定されています.
project()
はトップレベルの CMakeLists.txt の cmake_minimum_required()
に続けて記述します.
add_executable()
executable は実行ファイルの意で,次のように生成する実行ファイルについての情報をビルドルールに追加します.
add_executable(main # 実行ファイル
main.cpp # ソースファイル1
foo.cpp # ソースファイル2
bar.cpp # ソースファイル3
)
これは
- 実行ファイル
main
を生成すること - ソースファイルが
main.cpp
,foo.cpp
,bar.cpp
であること
を設定しており,コマンドの
g++ -c main.cpp foo.cpp bar.cpp
g++ -o main main.o foo.cpp bar.cpp
の部分に相当します.
変数
変数について簡単に解説します.
CMakeLists.txt に登場するコマンドの多くは内部で変数の値を設定しており,例えば
-
cmake_minimum_required(VERSION 3.22)
は-
CMAKE_MINIMUM_REQUIRED_VERSION
という変数の値を3.22
に
-
-
project(cmake_tutorial VERSION 1.0)
は-
PROJECT_NAME
という変数の値をcmake_tutorial
に -
PROJECT_VERSION
およびcmake_tutorial_VERSION
という変数の値を1.0
に
-
それぞれ設定しています.
各変数の値は次のように ${変数名}
で取り出すことができます.
message("${CMAKE_MINIMUM_REQUIRED_VERSION}")
message("${PROJECT_NAME}")
message("${PROJECT_VERSIOIN}")
message("${${PROJECT_NAME}_VERSIOIN}")
message()
は CMake における ログ出力のようなもので,message("Hello, world!")
とすると実行ログにHello, world!
を出力します
これにより,生成する実行ファイル名がプロジェクト名と同じである場合,前述の add_executable()
を次のように記述できます.
add_executable(${PROJECT_NAME}
main.cpp
foo.cpp
bar.cpp
)
このステップで解説したコマンドの詳細は次のリンクから確認できます.
リンク先の内容はそれぞれ最新バージョンの CMake に準拠しているため,注意してください.
Discussion
cmake -S . -B build を毎回実行する必要はなく
cmake --build build
のみでリビルドを実行できます
が正解ですか?
仰る通りです.
ご指摘ありがとうございます🙏
修正しました.