CMake: [deprecated] target_link_libraries(PUBLIC/PRIVATE/INTERFACE) の実践的な解説

# CMAKE_OPTIMIZE_DEPENDENCIES


(31-Oct-2021 追記) 

[CMake-3.19](https://cmake.org/cmake/help/v3.19/release/3.19.html#variables) から [変数 CMAKE_OPTIMIZE_DEPENDENCIES およびターゲットプロパティ OPTIMIZE_DEPENDENCIES](https://cmake.org/cmake/help/v3.19/prop_tgt/OPTIMIZE_DEPENDENCIES.html) が導入されました。

たいていの場合はこれを有効にしておけば、target_link_libraries(PRIVATE) でもライブラリ間の依存を断ち切ることができます。つまりたいていの用途において __キーワードは PRIVATE 一択でじゅうぶん__ となります。

```
set(CMAKE_OPTIMIZE_DEPENDENCIES ON)
```

しかし PRIVATE による依存は add_custom_command() への依存を伝播してしまいます。そのような伝播をどうしても断ち切りたいのであれば INTERFACE を用いることになります。

:::note info
以下の記事は CMAKE_OPTIMIZE_DEPENDENCIES 導入前に書かれたものです。
:::

---

私は CMake を愛しています。どれくらい愛してるかっつーと、たとえ刺し違えてもトドメを刺したいくらいに愛してるぞっと。この世に CMake 好きなんて存在するのか?!

閑話休題。

`target_link_libraries()` そのものの説明については https://cmake.org/cmake/help/v3.9/command/target_link_libraries.html をご覧ください。

# 前提--キレイな依存関係を書きましょう

`target_link_libraries()` に指定するライブラリは "当該ターゲットで実際に用いられている" ものを指定します。何も考えずに `target_link_libraries(foo <このプロジェクトで生成されるすべてのライブラリ>)` などとするのは愚かなことです。詳しくは共有ライブラリ(`PRIVATE`)の節にて説明します。

本稿では例として以下のファイルで構成にて説明します。各ファイル lib*.c は対応するインタフェイスヘッダ lib*.h を持っているものとします。

- `lib1.c`
- `lib2.c` `#include "lib1.h"`
- `lib3.c` `#include "lib2.h"`
- `foo.c` `#include "lib1.h"` `#include "lib3.h"`

この例でのライブラリ依存関係は概ね以下のようになります。

- `lib1` は依存ナシ
- `lib2` は `lib1` に依存
- `lib3` は `lib2` に依存
- `foo` は `lib1` `lib3` に依存

# `STATIC` -- スタティックライブラリ(*.a, *.lib)

`INTERFACE` を用います。

```
# STATIC は省略可能
add_library(lib1 STATIC lib1.c)

add_library(lib2 STATIC lib2.c)
target_link_libraries(lib2 INTERFACE lib1)

add_library(lib3 STATIC lib3.c)
target_link_libraries(lib3 INTERFACE lib2)

add_executable(foo foo.c)
# キーワードは省略可
# あえて指定するなら PRIVATE もしくは PUBLIC
# ただし INTERFACE はここではダメ
target_link_libraries(foo lib1 lib3)
```

`lib3.c` をコンパイルする際、キーワード `INTERFACE` がない場合(`PUBLIC` と同様の振る舞い)には `lib3.o` コンパイル前に `lib1` `lib2` のビルド(コンパイルおよびアーカイヴ)完了を待つ依存が発生します。`lib2.c` においても同様です。つまり、以下のビルドは**すべて直列化されます**。

1. `lib1.o: lib1.c`
2. `lib1.a: lib1.o`
3. `lib2.o: lib2.c`
4. `lib2.a: lib2.o`
5. `lib3.o: lib3.c`
6. `lib3.a: lib3.o`
7. `foo.o: foo.c`
8. `foo: lib3.a lib2.a lib1.a` (最終実行形式)

`INTERFACE` が指定されていれば、ターゲット `lib3` のビルドは `lib1` `lib2` に影響されなくなります。もちろん `lib2`
 も `lib1` に影響されなくなります。つまり **`lib1` `lib2` `lib3` のビルドは並列化されます。**

残念ながら `foo.c` のコンパイルは(コンパイル自体が `lib*.a` に依存していないにもかかわらず) `lib*` の完了に依存してしまいます。(ただし CMake-3.9 -GNinja では改良されています。そのうち記事書きます。)

`INTERFACE` は被依存ターゲットに対して[推移的(transitive)](https://ja.wikipedia.org/wiki/%E6%8E%A8%E7%A7%BB%E9%96%A2%E4%BF%82)に働きます。`foo` に対しては `lib3` が `lib2` `lib1` を引き連れて来るため、以下のように依存ライブラリが列挙されます。
```
-o foo lib3.a lib2.a lib1.a
```
依存関係が DAG である限りは被依存順に列挙されます。ただし CMake の実装の都合上、実際の列挙は `-o foo lib1.a lib3.a lib2.a lib1.a` のように `lib1` が重複してしまうかもしれません(さいきんは未確認)。

## `INTERFACE` のまとめ

- INTERFACE なライブラリどうしに依存は発生しないので並列度高まる。
- 非INTERFACE なターゲット(上記の例では `foo`)には `INTERFACE` ライブラリが連鎖してリンクされるのでリンク順で試行錯誤することはなくなる。

# `SHARED` -- 共有ライブラリ(*.so, *.dll)

できる限り `PRIVATE` を用います。

```
add_library(lib1 SHARED lib1.c)

add_library(lib2 SHARED lib2.c)
target_link_libraries(lib2 PRIVATE lib1)

add_library(lib3 SHARED lib3.c)
target_link_libraries(lib3 PRIVATE lib2)

add_executable(foo foo.c)
# キーワードは省略可もしくはPRIVATE/PUBLIC
# foo は lib1 にも依存しているのでここで明示
target_link_libraries(foo lib1 lib3)
```

`PRIVATE` は推移的に作用しません。上記の例では、`lib3` ダイナミックリンク時に列挙されるライブラリは `lib2` のみとなります。つまり `lib2` は依存ライブラリである `lib1` を伝播しません。同様に `foo` には `lib2` が列挙されず `lib1` `lib3` のみがリンクされます。

なお Win32 DLL の場合、依存 DLL の孫 DLL がエクスポートしているシンボルは解決されません。上記の例でいうと `foo.exe` からは `lib3.dll` が依存している `lib2.dll` がエクスポートしているシンボルは不可視です(ニホンゴムズカシイ)。また、`lib3`が依存している `lib2` が依存している `lib1` のシンボルは `foo.exe` から不可視のため、`lib1` への依存を明示しないと `foo.exe` のリンクに失敗します(ニホンゴサイアクニムズカシイ)。この挙動は CMake の `target_link_libraries(PRIVATE)` ととても相性がいいのです。

ニホンゴムズカシイのでコマンドライン例で書きますが、以下のリンクは成功します。
```
i686-w64-mingw32-gcc -o foo.exe foo.o lib1.dll lib3.dll
```
しかし `lib1` を指定しなかった場合 `lib1` `lib2` が列挙されないため、以下のようになりリンクは失敗します。
```
i686-w64-mingw32-gcc -o foo.exe foo.o lib3.dll
```

私の経験上、Win32 DLL(例:mingw)で `PRIVATE` を用いつつキレイに依存関係を書くことができれば、他のプラットフォームでもクリーンな依存関係となります。ライブラリ依存関係は Win32 DLL が最も厳しくなります。もし `PRIVATE` でなく `PUBLIC` を用いていると、動きはする(*just works*)ものの依存関係が壊れたときに発見が遅くなります。

余談(というワケではない)ですが、私が `i686-w64-mingw32-gcc` を用いて LLVM を `BUILD_SHARED_LIBS=ON` にてビルドしているのは、依存関係を常にクリーンに保つためという理由があります。

## `PRIVATE` のまとめ

- 自分の依存ライブラリを子に伝播しない。
- Win32 DLL(mingw) でこそ活用すべきだ!
- もちろん *.so でも有用。
- 依存関係をクリーンに保つために積極的に起用せよ!

# ところで `PUBLIC` の使い途は…?

**要らない子。** 以下、イラナイコの例です。

https://reviews.llvm.org/rL310712

Michał の名誉のために言っておきますが、彼が好き好んで `PUBLIC` を導入したのではなく、**言うことを聞かない外部プロジェクト**のために仕方なくこうしているのです。察しろ。

## `PUBLIC` のまとめ

- **要らない子**

# さいごに--キレイな依存関係を書きましょう

キレイな依存関係を保つことにより、多人数が参加するプロジェクトにおいて発生しがちな突発的なリンクエラーの発見・修復が速やかに行えるようになります。私が LLVM にて libdeps ポリスをやってるのは決して宗教的な理由ではなかったのです!

あ、マルチコア時代なんだからできるだけ依存関係を断ち切りまくってなるべくビルドは並列化しような! (Ryzen challenge には非参加でした…)