公開日: 2024年3月27日

CMake 覚え書き (2): プロジェクトに静的ライブラリを加えてみる

前回は、main 関数のみからなるごく単純なプロジェクトを作って、CMake の挙動を観察しました。今回は前回のプロジェクトを少し発展させ、プロジェクトにソースファイルを 1 個だけ追加して、静的ライブラリとしてリンクしてみます。そして前回と同様に、どんな Makefile ができるのか?を確認してみたいと思います。なお、本稿での確認に用いた CMake のバージョンは 3.26.1 です。

今回試すプロジェクト

前回作った myapp ディレクトリの下に lib ディレクトリを作り、その中に lib1.c と lib1.h を作成します。lib1.c には、たいして意味のない関数を 1 個だけ作っておき、main 関数から呼び出します。また、myapp ディレクトリと lib ディレクトリにはそれぞれ CMakeLists.txt を置く必要があります。その中身は次の節に掲載します。

% cd ~/cmake/myapp
% tree
.
├── CMakeLists.txt
├── lib
│     ├── CMakeLists.txt
│     ├── lib1.c
│     └── lib1.h
└── myapp.c
~/cmake/myapp/myapp.c
#include <stdio.h>
#include "lib1.h"

int main(int argc, char* argv[])
{
    printf("func1 returns: %d\n", func1());
    return 0;
}
~/cmake/myapp/lib1.c
int func1()
{
    return 1;
}
~/cmake/myapp/lib1.h
#ifndef _LIB1_H_
#define _LIB1_H_

int func1();

#endif

CMakeLists.txt を書く

ルートの CMakeLists.txt

ソースツリーのルートには、前回作った CMakeLists.txt がすでにあります。そこに下記の 3 行を追加します。

~/cmake/myapp/CMakeLists.txt (差分)
@@ -1,3 +1,7 @@
 cmake_minimum_required(VERSION 3.10)
 project(myapp)
+add_subdirectory(lib)
 add_executable(myapp myapp.c)
+
+target_link_libraries(myapp PRIVATE lib1)
+target_include_directories(myapp PRIVATE "${PROJECT_SOURCE_DIR}/lib")

4 行目のadd_subdirectory()は、プロジェクトにサブディレクトリを追加するコマンドです。このコマンドを実行すると、指定したサブディレクトリにある CMakeLists.txt が、その時点ですぐに読み込まれて実行されます。

7 行目のtarget_link_libraries()は、myapp に lib1 をリンクするためのコマンドで、引数を 3 つとります [1]。第 1 引数はリンクのターゲットであり、add_executable()コマンドまたはadd_library()コマンドでプロジェクトに追加したターゲットである必要があります。第 3 引数にはリンクしたいライブラリを指定します。第 2 引数には Usage Requirements のスコープを指定します。値としてはPUBLICPRIVATE、またはINTERFACEを指定できますが、詳しくは次回検討することにして、ここは取りあえずPRIVATEとしておきます。

8 行目のtarget_include_directories()コマンドで、lib ディレクトリをインクルード・パスに追加しています。これで myapp.c が lib1.h をインクルードすることができるようになります。

lib/CMakeLists.txt

lib ディレクトリの下にも CMakeLists.txt を新規作成します。こちらのファイルは、ルートの CMakeLists.txt のadd_subdirectory(lib) コマンドにより読み込まれます。中身は次のように、たった 1 行です。(お察しのとおり) このコマンドにより lib1.c から静的ライブラリ liblib1.a が生成されます。

~/cmake/myapp/lib/CMakeLists.txt
add_library(lib1 STATIC lib1.c)

それでは、前回と同じようにビルドして、Makefile を眺めてみましょう。

% cd ~/cmake/
% cmake -S myapp -B build
% cmake --build build

生成された Makefile を眺める

ビルドが成功したあとの build ディレクトリの中を見てみます。誌面の都合で (?) tree コマンドの出力を一部カットしていますが、実際にはもっとたくさんのファイルがあります。

% tree build
build
├── CMakeFiles
│     ├── Makefile2
│     └── myapp.dir
│            ├── build.make
│            ├── compiler_depend.ts
│            ├── flags.make
│            ├── link.txt
│            ├── myapp.c.o
│            └── progress.make
├── lib
│     ├── CMakeFiles
│     │     ├── lib1.dir
│     │     │     ├── build.make
│     │     │     ├── compiler_depend.ts
│     │     │     ├── flags.make
│     │     │     ├── lib1.c.o
│     │     │     ├── link.txt
│     │     │     └── progress.make
│     │     └── progress.marks
│     ├── liblib1.a
│     └── Makefile
├── Makefile
└── myapp

前回見たように、ビルドツリーのルートには CMakeFiles、その下に「なんちゃら.dir」、さらにその下に build.make やコンパイルされたオブジェクトファイルがありました。それと同じように、今回は新たに lib ディレクトリが追加され、その下にも同様のディレクトリ構造が出来上がっています。ソースツリーのほうにサブディレクトリを作っていって、それらをadd_subdirectory()すると、ビルドツリーのほうもそれに対応して、このようなフラクタルな感じの構造が形成されていくようです。前回との比較のために、今回もターゲット間の依存関係を図にしてみました (図 1)。

図 1
図 1

それでは、各ファイルを個別に見ていきます。

Makefile

おおもとの Makefile は、allターゲットに関しては、前回とまったく同じでした。前回を参照してください。

CMakeFiles/Makefile2

CMakeFiles/Makefile2 では、CMakeFiles/myapp.dir/allターゲットの前提条件としてlib/CMakeFiles/lib1.dir/allが追加されました (下記差分の 14 行目)。CMakeFiles/myapp.dir/allCMakeFiles/myapp.dir/build.makeCMakeFiles/myapp.dir/buildターゲットを実行する (同 16 行目) のと似た感じで、lib/CMakeFiles/lib1.dir/alllib/CMakeFiles/lib1.dir/build.makelib/CMakeFiles/lib1.dir/buildターゲットを実行します (同 27 行目)。

CMakeFiles/Makefile2 (差分、抜粋)
 # The main recursive "all" target.
 all: CMakeFiles/myapp.dir/all
+all: lib/all
 .PHONY : all
...
+# Directory level rules for directory lib
+
+# Recursive "all" directory target.
+lib/all: lib/CMakeFiles/lib1.dir/all
+.PHONY : lib/all
...
 # All Build rule for target.
-CMakeFiles/myapp.dir/all:
+CMakeFiles/myapp.dir/all: lib/CMakeFiles/lib1.dir/all
 	$(MAKE) $(MAKESILENT) -f CMakeFiles/myapp.dir/build.make CMakeFiles/myapp.dir/depend
 	$(MAKE) $(MAKESILENT) -f CMakeFiles/myapp.dir/build.make CMakeFiles/myapp.dir/build
-	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/mijinco/cmake/build/CMakeFiles --progress-num=1,2 "Built target myapp"
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/mijinco/cmake/build/CMakeFiles --progress-num=3,4 "Built target myapp"
 .PHONY : CMakeFiles/myapp.dir/all
...
+#=============================================================================
+# Target rules for target lib/CMakeFiles/lib1.dir
+
+# All Build rule for target.
+lib/CMakeFiles/lib1.dir/all:
+	$(MAKE) $(MAKESILENT) -f lib/CMakeFiles/lib1.dir/build.make lib/CMakeFiles/lib1.dir/depend
+	$(MAKE) $(MAKESILENT) -f lib/CMakeFiles/lib1.dir/build.make lib/CMakeFiles/lib1.dir/build
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/mijinco/cmake/build/CMakeFiles --progress-num=1,2 "Built target lib1"
+.PHONY : lib/CMakeFiles/lib1.dir/all
...

CMakeFiles/myapp.dir/build.make

myapp.dir の build.make は、前回から 1 行だけ追加されました。

CMakeFiles/myapp.dir/build.make (差分)
 myapp: CMakeFiles/myapp.dir/myapp.c.o
 myapp: CMakeFiles/myapp.dir/build.make
+myapp: lib/liblib1.a
 myapp: CMakeFiles/myapp.dir/link.txt
 	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=/home/mijinco/cmake/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking C executable myapp"
 	$(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/myapp.dir/link.txt --verbose=$(VERBOSE)

また、flags.make と link.txt も、lib1 の追加に伴って少し変更されていました。

CMakeFiles/myapp.dir/flags.make (差分)
 # compile C with /usr/bin/cc
 C_DEFINES =

-C_INCLUDES =
+C_INCLUDES = -I/home/mijinco/cmake/myapp/lib

 C_FLAGS =
CMakeFiles/myapp.dir/link.txt (差分)
-/usr/bin/cc CMakeFiles/myapp.dir/myapp.c.o -o myapp
+/usr/bin/cc CMakeFiles/myapp.dir/myapp.c.o -o myapp  lib/liblib1.a

lib/CMakeFiles/lib1.dir/build.make

lib1.dir の build.make は下記のようになっていました。出来上がるものが静的ライブラリか実行可能ファイルかの違いだけで、やっていることは myapp.dir の build.make とだいたい同じと考えてよさそうです。

lib/CMakeFiles/lib1.dir/build.make (抜粋)
...
lib/CMakeFiles/lib1.dir/lib1.c.o: lib/CMakeFiles/lib1.dir/flags.make
lib/CMakeFiles/lib1.dir/lib1.c.o: /home/mijinco/cmake/myapp/lib/lib1.c
lib/CMakeFiles/lib1.dir/lib1.c.o: lib/CMakeFiles/lib1.dir/compiler_depend.ts
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/home/mijinco/cmake/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building C object lib/CMakeFiles/lib1.dir/lib1.c.o"
	cd /home/mijinco/cmake/build/lib && /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -MD -MT lib/CMakeFiles/lib1.dir/lib1.c.o -MF CMakeFiles/lib1.dir/lib1.c.o.d -o CMakeFiles/lib1.dir/lib1.c.o -c /home/mijinco/cmake/myapp/lib/lib1.c
...
lib/liblib1.a: lib/CMakeFiles/lib1.dir/lib1.c.o
lib/liblib1.a: lib/CMakeFiles/lib1.dir/build.make
lib/liblib1.a: lib/CMakeFiles/lib1.dir/link.txt
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=/home/mijinco/cmake/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking C static library liblib1.a"
	cd /home/mijinco/cmake/build/lib && $(CMAKE_COMMAND) -P CMakeFiles/lib1.dir/cmake_clean_target.cmake
	cd /home/mijinco/cmake/build/lib && $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/lib1.dir/link.txt --verbose=$(VERBOSE)
...
lib/CMakeFiles/lib1.dir/build: lib/liblib1.a
.PHONY : lib/CMakeFiles/lib1.dir/build
...
lib/CMakeFiles/lib1.dir/depend:
	cd /home/mijinco/cmake/build && $(CMAKE_COMMAND) -E cmake_depends "Unix Makefiles" /home/mijinco/cmake/myapp /home/mijinco/cmake/myapp/lib /home/mijinco/cmake/build /home/mijinco/cmake/build/lib /home/mijinco/cmake/build/lib/CMakeFiles/lib1.dir/DependInfo.cmake --color=$(COLOR)
.PHONY : lib/CMakeFiles/lib1.dir/depend

ちなみに lib1.dir の flags.make には、フラグなどの指定はなにもありませんでした。link.txt には次のように書かれていました。lib1.dir/build.make 8 ~ 10 行目のlib/liblib1.a ターゲットが実行されると、lib1.c.o がアーカイブされ、静的ライブラリ liblib1.a が作成されるものと思われます。

lib/CMakeFiles/lib1.dir/link.txt
/usr/bin/llvm-ar qc liblib1.a CMakeFiles/lib1.dir/lib1.c.o
/usr/bin/llvm-ranlib liblib1.a

まとめ

前回作った簡単なプロジェクトを少しだけ発展させて、サブディレクトリとソースファイルを追加しました。それを CMake で静的ライブラリとしてリンクさせた結果、次のことがわかりました。

  • ソースツリーの下に hoge というサブディレクトリを作ると、ビルドツリーのほうにはそれに対応して hoge/CMakeFiles というディレクトリが作成される。
  • その中には、前回と同様に build.make や hoge.dir などのファイルやディレクトリが作成される。
  • hoge/CMakeFiles/build.make には、hoge ライブラリのビルドプロセスの本体が書かれている。これも前回と同様。
  • add_library()コマンドでライブラリ・ターゲットを作ると、link.txt でやることがリンクではなくアーカイブになる。

次回は、今回お茶を濁したtarget_link_libraries()コマンドの Usage Requirements について検討してみたいと思います。

参考資料

Raspberry Pi Pico 実験室