Visual Studio 2022 で Webots の コントローラー用に CMake で参照できるライブラリを作成

Visual Studio 2022 を使用して C++ のプロジェクトを CMake で作成する方法を紹介しています。今回は、ロボットシミュレータ Webots ([https://cyberbotics.com/](https://cyberbotics.com/)) のコントローラをコンパイルする際に必要となる要件（ヘッダの場所・ソース・リンク情報）を一つにまとめてライブラリ化し、CMake の find_package() から参照できるようにする方法を報告します。自作のコントローラにさまざまなライブラリをリンクして開発するのが容易になります。

GitHub に公開しました。

https://github.com/kkoba705/WebotsCtrl

# ライブラリの要件
Webots User Guide の [Using Your IDE](https://cyberbotics.com/doc/guide/using-your-ide?tab-language=c++#configuration) にあるように、C++ でコントローラを作成するプロジェクトには
* %WEBOTS_HOME%/include/controller/c と %WEBOTS_HOME%/include/controller/cpp を Include Directories として登録する。
* %WEBOTS_HOME%/src/controller/cpp にあるすべての cpp ファイルを追加する。
* %WEBOTS_HOME%/lib/controller/Controller.dll を動的にリンクするために %WEBOTS_HOME%/lib/controller/Controller.lib を追加する。

ことが必要です。これらを target_link_libraries() だけで達成できるライブラリを作成します。

# 環境
* 開発環境として、Visual Studio 2022（Community）の「C++デスクトップ環境」がインストールされていること。
* CMake (最新版を推奨）がインストールされ、コマンドラインから cmake が実行できること。[^1]
* 環境変数の設定ができること。[^2]
* コマンドラインでの作業には「Developer Command Prompt for VS 2022」を使用すること。[^3]
[^1]: [https://cmake.org/](https://cmake.org/)  にある Latest Release の Windows x64 Installer を実行して 「Add CMake to the system PATH for all users」にチェックを入れてインストールする。
[^2]: 環境変数は、設定の「システム」＞「バージョン情報」＞「システムの詳細設定」から変更できます。```> set``` で環境変数の一覧が見れるので、設定が反映されていない場合には一旦 Windows からサインアウトしてサインインし直してください。
[^3]: スタートメニューの「Visual Studio 2022」フォルダ内にショートカットがあります。「ターミナル」の設定を変更し、「既定のプロファイル」に「Developer Command Prompt for VS 2022」を登録しておくと、エクスプローラーで作業するディレクトリを開き、コンテキストメニューから「ターミナルで開く」ことができて便利です。


# ディレクトリ構成
この記事では、ライブラリとそのソースは c:\dev にいれることにします。これは任意の場所に変更することができます。
```
dev/
├── webotsctrl
    ├── lib
    ├── bin
├── make
    ├── webotsctrl
        ├── src
        ├── build
```
* ライブラリを作るための CMakeLists.txt は c:\dev\make\webotsctrl に用意します。
* c:\dev\make\webotsctrl\src に %WEBOTS_HOME%/src/controller/cpp にあるソースファイル群をコピーします。
* c:\dev\webotsctrl にビルドしたライブラリがインストールされます。
* インストール後、 c:\dev\make\webotsctrl\build は削除できます。

# ライブラリ WebotsCtrl の作成
ディレクトリ ```C:\dev\make``` がなければ作成し、Developer Command Prompt for VS 2022 で以下を実行する。
```DOS
> cd \dev\make
> md webotsctrl
> cd webotsctrl
> md src
```
続けて、環境変数 WEBOTS_HOME の値を確認して、その下のディレクトリにある cpp ファイルを src にコピーする。（ユーザーインストールの場合は場所が異なります。）
```DOS
> echo %WEBOTS_HOME%
C:\Program Files\Webots
> copy "C:\Program Files\Webots\src\controller\cpp\*.cpp" src\
```
CMakeLists.txt を c:\dev\make\webotsctrl に次の内容で作成する。
```CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(webots_ctrl VERSION 0.1.0 
    DESCRIPTION "Webots controller" LANGUAGES CXX)

string(REPLACE \\ / webots_home $ENV{WEBOTS_HOME})
set(CMAKE_DEBUG_POSTFIX d)

file(GLOB cpp_src src/*.cpp)
add_library(CppController STATIC ${cpp_src})

target_include_directories(CppController PUBLIC
    ${webots_home}/include/controller/c
    ${webots_home}/include/controller/cpp
)

target_link_libraries(CppController INTERFACE
    ${webots_home}/lib/controller/Controller.lib
)

install(TARGETS CppController 
    EXPORT webotsctrl-config
    ARCHIVE DESTINATION lib
)

install(EXPORT webotsctrl-config
    NAMESPACE WebotsCtrl::
    DESTINATION lib/cmake/webotsctrl
) 

install(FILES
    ${webots_home}/lib/controller/Controller.dll
    DESTINATION bin
)
```
Developer Command Prompt for VS 2022 でライブラリのビルドとインストールを行う。
```DOS
> cd \dev\make\webotsctrl
> md build
> cd build
> cmake -DCMAKE_INSTALL_PREFIX="c:\dev\webotsctrl" ..
> cmake --build . --config Release --target INSTALL
> cmake --build . --config Debug --target INSTALL
```
作成したコントローラーの実行時には、パスの通ったディレクトリに Controller.dll が存在する必要がある。コントローラーの外部実行（デバッグ実行）を行う場合には、環境変数 ```Path``` の最後に ```c:\dev\webotsctrl\bin``` を追加するか、パスの通ったディレクトリ（c:\dev\bin など）に c:\dev\webotsctrl\bin に用意された Controller.dll を ```Controller.dll``` をコピーする。

# 環境変数の設定

次のうちいずれかを行う。上にある設定が優先される。

* 環境変数 ```WebotsCtrl_DIR``` に ```c:\dev\webotsctrl\lib\cmake\webotsctrl``` もしくは `c:\dev` を設定する。

* 環境変数 ```CMAKE_PREFIX_PATH``` に ```c:\dev``` を設定する。
* 環境変数 ```Path``` に ```c:\dev\bin``` を追加する。


# コントローラの要件

Webots User Guide の [The "controllers" Directory](https://cyberbotics.com/doc/guide/the-standard-file-hierarchy-of-a-project#the-controllers-directory) にあるように、たとえば "sample_controller" という名前のコントローラを作成するには、Webots のプロジェクトディレクトリの直下にある "controllers" というディレクトリ内に "sample_controller" というディレクトリを作成して、そこに "sample_controller.exe" を用意すればよい。

Webots のメインメニューからたどって "New Robot Controller..." を選び、コントローラ名として "sample_controller" と入力すると、 "controllers" の下に "sample_controller" というディレクトリが作成され、そこに "sample_controller.cpp" という雛形ファイル（何もしないコントローラのソース）が用意される。（同時に "Makefile" もしくは "sample_controller.sln" も作成される。）

# ライブラリの使用方法
コントローラのソースが一つのファイル "sample_controller.cpp" だけで構成される場合で使い方を説明する。"sample_controller" ディレクトリに、以下の内容で ```CMakeLists.txt``` を作成する。
```CMakeLists.txt
cmake_minimum_required(VERSION 3.15)

# The project name is defined to be the controller directory name.
get_filename_component(PROJECT ${CMAKE_SOURCE_DIR} NAME)
project(${PROJECT} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 14)
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

find_package(WebotsCtrl REQUIRED)

add_executable(${PROJECT})
target_link_libraries(${PROJECT} WebotsCtrl::CppController)
target_compile_definitions(${PROJECT} PRIVATE "_USE_MATH_DEFINES" "NOMINMAX")

target_sources(${PROJECT} PRIVATE
  ${PROJECT}.cpp
)

# Copy the target executable at the right location.
add_custom_command(TARGET ${PROJECT} POST_BUILD COMMAND ${CMAKE_COMMAND} -E
  copy $<TARGET_FILE:${PROJECT}> ${CMAKE_SOURCE_DIR}
)

# Specify the default startup project for Visual Studio
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} 
  PROPERTY VS_STARTUP_PROJECT ${PROJECT})
```
* プロジェクト名とソースファイル名には、CMakeLists.txt の存在するディレクトリ名  "sample_controller" ( = ${PROJECT}) が使われる。
* 複数のソースファイルや追加のヘッダファイルを使いたいときには、target_sources() 内にファイル名を追加する。もしくはサブディレクトリを作って add_subdirectory() を使う。
* find_package() で他のライブラリを探して、target_link_libraries() でプロジェクトに追加できる。
* ```M_PI``` を使いたいときには ```_USE_MATH_DEFINES``` を、```std::min()```,```std::max()``` を普通に使いたいときには ```NOMINMAX``` をソースファイルの先頭で #define する必要があるが、```target_compile_definitions``` で代行できる。
* 文字コードの選択は悩ましい問題だが、Webots でコンソール出力をするなら UTF-8 を使う。

"sample_controller" ディレクトリにおいて、Developer Command Prompt から
```
> md build
> cd build
> cmake ..
```
とすることで Visual Studio のプロジェクトである ```build\sample_controller.vcxproj``` とソリューションである ```build\sample_controller.sln``` が作成される。いずれかを Visual Studio で開くことで IDE を用いた開発ができる。(あるいは Visual Studio Code で Extensions が足りていれば ```sample_controller``` フォルダを開くことで、CMakeLists.txt が処理され、Intellisense が効いた状態でコーディングができる。) IDE を使わずに、コマンドラインから
```
> cmake --build . --config Release
```
としてビルドすることもできる。

add_custom_command() の設定によって、"build\Release(Debug)\sample_controller.exe" が作成された直後に、それは "sample_controller" ディレクトリへとコピーされる。既存の "sample_controller.exe" が実行中の場合、上書きに失敗するので、コントローラのビルドはシミュレーションが停止している状態で行うように注意する必要がある。

Webots User Guide の [Running Extern Robot Controllers](https://cyberbotics.com/doc/guide/running-extern-robot-controllers#usefulness) にあるように、ロボットのコントローラに `<extern>` を指定すると、IDE でコントローラのデバック実行ができる。

# 更新

いくつか新しい知識を得たので更新。
* コンパイル済みの lib を CMake のライブラリとしてインストールする場合には、IMPORTED library ではなく INTERFACE library を使うと良い。
* 環境変数のパス( \ 区切り）を CMake のパス( /区切り）に直すのは file(TO_CMAKE_PATH ) でできる。
* 外部のソースファイルはプロジェクト内にコピーしなくても参照できる。
* バージョンファイルを追加

```CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(webots_ctrl VERSION 0.2.0 
    DESCRIPTION "Webots controller" LANGUAGES CXX)

file(TO_CMAKE_PATH "$ENV{WEBOTS_HOME}" webots_home)

set(CMAKE_DEBUG_POSTFIX d)

add_library(CController INTERFACE)

target_include_directories(CController INTERFACE
    ${webots_home}/include/controller/c
)

target_link_libraries(CController INTERFACE
    ${webots_home}/lib/controller/Controller.lib
)

target_compile_definitions(CController INTERFACE
    "_USE_MATH_DEFINES"
)

add_library(CppController STATIC)

file(GLOB cpp_src  ${webots_home}/src/controller/cpp/*.cpp)
target_sources(CppController PRIVATE ${cpp_src})

target_include_directories(CppController PUBLIC
    ${webots_home}/include/controller/cpp
)

target_link_libraries(CppController PUBLIC CController)

target_compile_definitions(CppController INTERFACE
    "NOMINMAX"
)

install(TARGETS CController CppController 
    EXPORT webotsctrl-config
    ARCHIVE DESTINATION lib
)

install(EXPORT webotsctrl-config
    NAMESPACE WebotsCtrl::
    DESTINATION lib/cmake/webotsctrl
) 

install(FILES
    ${webots_home}/lib/controller/Controller.dll
    DESTINATION bin
)

include(CMakePackageConfigHelpers)

write_basic_package_version_file(
  ${CMAKE_CURRENT_BINARY_DIR}/webotsctrl-config-version.cmake
  VERSION 0.2.0
  COMPATIBILITY SameMajorVersion )

install(FILES 
    ${CMAKE_CURRENT_BINARY_DIR}/webotsctrl-config-version.cmake
    DESTINATION lib/cmake/webotsctrl
)
```
C++ ではなく、C でコントローラーを作成したい場合には
```
target_sources(${PROJECT} PRIVATE ${PROJECT}.c)
target_link_libraries(${PROJECT} WebotsCtrl::CController)
```
とする。