JUCEをリンクする中間ライブラリをCMakeで扱う

本記事は [JUCE Advent Calendar 2022](https://qiita.com/advent-calendar/2022/juce) の12月22日向けに投稿した記事です。

JUCEをCMakeからインテグレートする際に、通常用意されているJUCE CMake APIではすんなり出来なかったことに挑戦してみました。
注意：Windowsでのみ試したため、他のプラットフォームだと上手く動かない可能性あり。

# 対象バージョン
+ JUCE 7.0.3
+ Visual Studio 2022
+ CMake 3.15以上
+ Git 2.0以上

# 事前情報
## JUCEとは
JUCE (Jules' Utility Class Extensions)は、C++言語によるマルチメディア系アプリケーションの開発を支援するフレームワークです。
ライブラリの一部モジュールとして独自のGUIシステムを実装しており、開発者はそのAPIを利用することで、クロスプラットフォームなGUIアプリケーションを統一したコードベースで実装することができます。
[公式サイト](https://juce.com/)

## CMakeとは
CMakeは、主にC言語、C++言語などC系言語のビルドにおけるクロスプラットフォームビルド環境を提供するビルドマネジメントツールの一つです。ソフトウェアのビルドにおける一連の処理をCMakeLists.txtに記述し、CMakeプログラムを通じて各種プラットフォーム用のビルドツールに向けたプロジェクトを出力することができます。
[公式サイト](https://cmake.org/)

### JUCEライブラリ と CMake
2020年にリリースされたJUCE 6から、ビルドマネジメントツール『CMake』のサポートが追加されました。
JUCEがCMakeをサポートすることで以下のメリットを得ることができるようになりました。

+ ビルド設定をCMake用のフォーマットで記述することができる
+ CMakeのエコシステムの恩恵を受けることができる
+ CMakeが独自に提供する機能を利用することができる
+ CMakeに対応した他のライブラリとのインテグレーションが容易になる

# JUCEをCMakeからインテグレートする際のお作法

JUCEのCMake APIには、CMakeのターゲットを生成するAPIとして3種類のAPIが用意されています。

+ juce_add_gui_app: GUIアプリケーションのターゲットを作成する（実行可能ファイル）
+ juce_add_console_app: コンソールアプリケーションのターゲットを作成する（実行可能ファイル）
+ juce_add_plugin: オーディオプラグインのターゲットを作成する（動的ライブラリ）

上記のように、「メインとなるアプリケーション・モジュールコードにJUCEをリンクして、エントリポイント（main関数)はJUCE CMake APIが提供する」という考え方になっています。
これはこれでカジュアルに使いやすいというメリットはあるのですが、今回筆者が求めていた構成を実現するには利用できないという事情がありました。

## JUCE内のCMake APIドキュメントの抜粋
```JUCE/docs/CMAKE API.md
### Functions

#### `juce_add_<target>`

    juce_add_gui_app(<target> [KEY value]...)
    juce_add_console_app(<target> [KEY value]...)
    juce_add_plugin(<target> [KEY value]...)

`juce_add_gui_app` and `juce_add_console_app` add an executable target with name `<target>`.
`juce_add_plugin` adds a 'shared code' static library target with name `<target>`, along with extra
targets for each of the specified plugin formats. Each of these functions also takes a number of
optional arguments in the form of a `KEY` followed by one or more `value`s which can be used to set
additional attributes of the target. If these optional arguments aren't specified, their values will
fall back to sensible defaults.
```

# 以下の構成となるCMakeターゲットを作成したい！
+ JUCEをリンクする中間ライブラリ（Coreライブラリ)
+ 中間ライブラリをリンクするアプリケーション（App.exe）
+ 中間ライブラリをリンクするアプリケーションその２（App2.exe）
+ アプリケーションからJUCE APIを呼ぶことができる

## 今回作成したリポジトリ

上記の構成を実現したGitHubリポジトリです。

https://github.com/COx2/juce_layered_integrate

## CMakeList.txt

上記リポジトリに含まれる`CMakeLists.txt`ファイルの記述です。

```
cmake_minimum_required(VERSION 3.15)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

project(JUCE-LAYERED-INTEGRATE VERSION 0.0.1)

if(MSVC)
    # static linking in Windows
    set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()

# Adds all the module sources so they appear correctly in the IDE
set_property(GLOBAL PROPERTY USE_FOLDERS YES)
option(JUCE_ENABLE_MODULE_SOURCE_GROUPS "Enable Module Source Groups" ON)

# Add JUCE Package
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/External/JUCE)

# Add static library target
add_library(Core STATIC)

_juce_initialise_target(Core)
_juce_set_output_name(Core $<TARGET_PROPERTY:Core,JUCE_PRODUCT_NAME>)

target_link_libraries(Core
    PUBLIC
        juce::juce_analytics
        juce::juce_audio_basics
        juce::juce_audio_devices
        juce::juce_audio_formats
        juce::juce_audio_plugin_client
        juce::juce_audio_processors
        juce::juce_audio_utils
        juce::juce_box2d
        juce::juce_core
        juce::juce_cryptography
        juce::juce_data_structures
        juce::juce_dsp
        juce::juce_events
        juce::juce_graphics
        juce::juce_gui_basics
        juce::juce_gui_extra
        juce::juce_opengl
        juce::juce_osc
        juce::juce_product_unlocking
        juce::juce_video
)

target_sources(Core
    PRIVATE
        "Core/Source/Core.cpp"
)

# Add application target
add_executable(App)

target_sources(App
    PRIVATE
        "App/Source/Main.cpp"
)

target_include_directories(App
    PRIVATE
        "Core/Source"
)

target_link_libraries(App
    PRIVATE
        Core
)

# Add another application target
add_executable(App2)

target_sources(App2
    PRIVATE
        "App2/Source/Main.cpp"
)

target_include_directories(App2
    PRIVATE
        "Core/Source"
)

target_link_libraries(App2
    PRIVATE
        Core
)
```

### 抜粋

上記のスクリプトにおいて、JUCEをリンクする中間ライブラリを生成する処理は以下の部分になります。

```
# 静的ライブラリのターゲットを追加する
add_library(Core STATIC)

# JUCE CMake APIからJUCE特有のターゲット初期化処理を実行する
_juce_initialise_target(Core)
_juce_set_output_name(Core $<TARGET_PROPERTY:Core,JUCE_PRODUCT_NAME>)

# 生成する静的ライブラリにリンクするJUCEモジュールを列挙する
# このとき、"PUBLIC"オプションを選択するくことで、このCoreターゲットをリンクするAppターゲットに対してJUCEライブラリのAPIを公開することができる。
target_link_libraries(Core
    PUBLIC
        juce::juce_analytics
        juce::juce_audio_basics
        juce::juce_audio_devices
        juce::juce_audio_formats
        juce::juce_audio_plugin_client
        juce::juce_audio_processors
        juce::juce_audio_utils
        juce::juce_box2d
        juce::juce_core
        juce::juce_cryptography
        juce::juce_data_structures
        juce::juce_dsp
        juce::juce_events
        juce::juce_graphics
        juce::juce_gui_basics
        juce::juce_gui_extra
        juce::juce_opengl
        juce::juce_osc
        juce::juce_product_unlocking
        juce::juce_video
)

```


# 注意事項

JUCEに限らずC++のライブラリのリンクに関することではあるのですが、以下の点に注意しましょう。

+ プロジェクトごとに異なるモジュール・オプションを必要とする場合、同じJUCEオブジェクト・ファイルを複数のプロジェクトで再利用することは危険
+ JUCEの静的ライブラリをターゲット間で共有する場合、依存する全てのターゲットが全く同じ JUCEモジュールとモジュール設定フラグのセットを使用することを確認する必要がある

[参考: JUCEフォーラムの投稿その１](https://forum.juce.com/t/how-to-create-a-static-library-that-use-juce-in-official-cmake/50080/3)
[参考: JUCEフォーラムの投稿その２](https://forum.juce.com/t/using-cmake-flow-to-build-libraries-android-apps/40492/20)