MakefileをCMakeLists.txtに置き換える

MakefileでビルドしていたソースツリーをCMakeLists.txtに置き換えます。

1 MakefileとCMakeLists.txt

CMakeLists.txtはconfigure + Makefileとほぼ同等です。Unixの場合、cmakeコマンドでCMakeLists.txtからMakefileを生成します。このMakefileはインクルードファイルの更新時のソースコードの再ビルドや、makeの並列実行にも対応できています。その一報で記述量がMakefileと変わらないので導入を検討してみると良いでしょう。

2 Makefileのソースツリー

以下の様なソースツリーを使います。

$ tree
.
├── include
│   └── hello.h
├── lib
│   ├── hello.cpp
│   └── Makefile
├── Makefile
└── src
    ├── main.cpp
    └── Makefile

3 directories, 6 files

2.1 トップディレクトリのMakefile

トップディレクトリのMakefileは以下のとおりです。

  • ビルド処理はlibディレクトリとsrcディレクトリのMakefileに任せます。ただし、srcディレクトリでlibディレクトリのライブラリを参照するので、先にlibディレクトリが実行されるようにします。
LIBNAME  := libexample.a
PROGNAME := example

export LIBNAME
export PROGNAME

all: lib
        $(MAKE) -C src

lib:
        $(MAKE) -C lib

clean:
        $(MAKE) -C lib clean
        $(MAKE) -C src clean

.PHONY: lib

2.2 ライブラリを作成するMakefile

libディレクトリのMakefileは以下のとおりです。

  • .cppのソースコードからオブジェクトを割り出し、arコマンドでオブジェクトをlibexample.aにします。暗黙のルールでオブジェクトは自動的にコンパイルされます。
  • .dファイルにオブジェクトと.dファイルの依存関係を記述しておき、ヘッダファイルの更新等で自動的にビルドが実行されるようにします。ただし、cleanの時には.dファイルを生成しないようにします。
CXXFLAGS := -I../include -I.

SRC := $(wildcard *.cpp)
OBJ := $(patsubst %.cpp,%.o,$(SRC))
DEP := $(patsubst %.cpp,%.d,$(SRC))

all: $(LIBNAME)

clean:
        $(RM) $(DEP) $(OBJ) $(LIBNAME)

%.d: %.cpp
        $(CXX) -MM $(CXXFLAGS) $< | sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@

ifneq ($(filter clean,$(MAKECMDGOALS)),clean)
-include $(DEP)
endif

$(LIBNAME): $(OBJ)
        $(AR) rc $@ $(OBJ)

2.3 ライブラリを参照するMakefile

srcディレクトリのMakefileは以下のとおりです。

  • .cppのソースコードからオブジェクトを割り出し、c++コマンドでオブジェクトをexampleにコンパイルします。暗黙のルールでオブジェクトは自動的にコンパイルされます。
  • .dファイルについては先ほどと同様です。
CXXFLAGS = -I../include -I.

SRC := $(wildcard *.cpp)
DEP := $(patsubst %.cpp,%.d,$(SRC))
OBJ := $(patsubst %.cpp,%.o,$(SRC))

all: $(PROGNAME)

$(PROGNAME): $(OBJ) ../lib/$(LIBNAME)
        $(CXX) $(CXXFLAGS) $^ -o $@

%.d: %.cpp
        $(CXX) -MM $(CXXFLAGS) $< | sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@

ifneq ($(filter clean,$(MAKECMDGOALS)),clean)
-include $(DEP)
endif

clean:
        $(RM) $(DEP) $(OBJ) $(PROGNAME)

2.4 Makefileでビルド

makeを実行します。

$ make
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C lib
c++ -MM -I../include -I. hello.cpp | sed 's/\(hello\)\.o[ :]*/\1.o hello.d : /g' > hello.d
c++ -I../include -I.   -c -o hello.o hello.cpp
ar rc libexample.a hello.o
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C src
c++ -MM -I../include -I. main.cpp | sed 's/\(main\)\.o[ :]*/\1.o main.d : /g' > main.d
c++ -I../include -I.   -c -o main.o main.cpp
c++ -I../include -I. main.o ../lib/libexample.a -o example

3 CMakeLists.txtのソースツリー

以下の様なソースツリーを使います。

$ tree
.
├── CMakeLists.txt
├── include
│   └── hello.h
├── lib
│   ├── CMakeLists.txt
│   └── hello.cpp
└── src
    ├── CMakeLists.txt
    └── main.cpp

3 directories, 6 files

3.1 トップディレクトリのCMakeLists.txt

トップディレクトリのCMakeLists.txtは以下のとおりです。

  • cmakeはソースツリー外にビルド環境を整えることが可能です(様々なファイルが生成されるのでソースツリー外でcmakeを実行したほうが良い)。CMAKE_SOURCE_DIRとCMAKE_BINARY_DIRを比較して、ソースツリー内ではcmakeを実行できないようにします。
  • add_subdirectoryでlibディレクトリとsrcディレクトリを追加します。ディレクトリ間の依存関係は記述しません。srcディレクトリのCMakeLists.txtにてtarget_link_librariesを使うことで、ビルドの依存関係が自動的に決まります。
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
  message(FATAL_ERROR "DO NOT BUILD in-tree.")
endif()

cmake_minimum_required (VERSION 2.8.11)
project(example)

add_subdirectory(lib)
add_subdirectory(src)

3.2 ライブラリを作成するCMakeLists.txt

libディレクトリのCMakeLists.txtは以下のとおりです。

  • AUX_SOURCE_DIRECTORYでカレントソースディレクトリにあるソースコードをsourceという変数に格納します。
  • add_libraryでsource変数に格納されたソースコードからlibexampleという名前のライブラリを作成します。このままだとliblibexample.aというファイル名になってしまうので、set_target_propertiesでlibexample.aという名前に変更します。
  • target_include_directoriesで他から参照できるようにライブラリを公開します。
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} source)

# Use libexample for avoiding conflicts with add_executable(example).
# And rename liblibexample.a to libexample.a
add_library(libexample ${source})
set_target_properties(libexample PROPERTIES OUTPUT_NAME "example")

target_include_directories(libexample PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

3.3 ライブラリを参照するCMakeLists.txt

srcディレクトリのCMakeLists.txtは以下のとおりです。

  • AUX_SOURCE_DIRECTORYでカレントソースディレクトリにあるソースコードをsourceという変数に格納します。
  • add_executableでsource変数に格納されたソースコードからexampleという名前のバイナリを作成します。ファイル名はexampleです。
  • target_link_librariesでlibexampleという名前のライブラリを参照します(このlibexampleはファイル名ではなく、target_include_directoriesで指定した名前です)。
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} source)
add_executable(example ${source})
target_link_libraries(example LINK_PUBLIC libexample)

3.4 CMakeLists.txtでビルド

cmakeを実行してMakefileを作成してからmakeを実行します。ソースコードが変更された場合はmakeを実行すればソースコードがビルドされます。CMakeLists.txtが変更された場合はcmakeを再実行する必要があります。

$ ls cmake-tree
CMakeLists.txt  include  lib  src
$ mkdir cmake-tree.build
$ cd cmake-tree.build
$ cmake ../cmake-tree -G 'Unix Makefiles'
$ make
[ 50%] Building CXX object lib/CMakeFiles/libexample.dir/hello.cpp.o
Linking CXX static library libexample.a
[ 50%] Built target libexample
[100%] Building CXX object src/CMakeFiles/example.dir/main.cpp.o
Linking CXX executable example