TARGET_RUNTIME_DLLSで依存するDLLを簡単に配置する

※ Windowsでのみ有効な解決策であるため注意。

# 背景
CMakeプロジェクトにおいて、所望のターゲットに別環境でビルド済みのライブラリを動的リンクしたい場合、以下のように書いていた。

```cmake:CMakeLists.txt
add_executable(mytarget ${MY_PROJECT_SOURCES})
add_library(extLib SHARED IMPORTED)
set_target_properties(
  extLib
  PROPERTIES IMPORTED_LOCATION ${DYLIB_FILE_PATH}
             IMPORTED_IMPLIB ${STLIB_FILE_PATH}
             INTERFACE_INCLUDE_DIRECTORIES ${INCLUDE_DIR_PATH})
target_link_libraries(mytarget extLib)
```

これでビルドは上手く動作する。
しかしリンクした動的リンクライブラリがビルドディレクトリに配置されていないため、生成された実行ファイルをただ（PATHを追加せずに）実行しても動かない。
`configure_file` や `cmake -e copy` などのコマンドを使用し、依存するライブラリをコピーすることで回避できるが、依存ライブラリが増えるとコピー対象のファイルが増え記述が大変になることは目に見えている。

# 解決策
Generator Expression (ジェネレータ式?) の１つ、`$<TARGET_RUNTIME_DLLS:tgt>` を使用すると、あるターゲットが依存する全ての動的リンクライブラリのパスをリストとして取得できる。
※ 対象（依存するライブラリ）が１つもない場合空文字に変換され、以下のコマンドはConfigure時にエラーとなるので注意。
※ TARGET_RUNTIME_DLLS は CMake3.21以降で導入されている。


これを用いて、ビルドしたターゲット実行ファイルが存在するディレクトリに、そのターゲットが依存する全ての動的リンクライブラリファイルを配置するCMakeスクリプトは以下のように書ける。
```cmake
add_executable(mytarget ${MY_PROJECT_SOURCES})
#...
function(copy_dlls_to_target TARGET_NAME)
  add_custom_command(
    TARGET ${TARGET_NAME}
    POST_BUILD
    COMMAND
      ${CMAKE_COMMAND} -E copy_if_different
      $<TARGET_RUNTIME_DLLS:${TARGET_NAME}> $<TARGET_FILE_DIR:${TARGET_NAME}>
    COMMAND_EXPAND_LISTS) # COMMAND_EXPAND_LISTSを設定することでパスのリストが正しく展開される
endfunction()
copy_dlls_to_target(mytarget)
```

インストールについても、同様に `$<TARGET_RUNTIME_DLLS:tgt>` を用いて以下のように書ける。
```cmake
install(FILES $<TARGET_RUNTIME_DLLS:mytarget> TYPE BIN)
```


# 参考文献
+ TARGET_RUNTIME_DLLSの存在は、以下のstackoverflowの回答を見て知った。

https://stackoverflow.com/questions/10671916/how-to-copy-dll-files-into-the-same-folder-as-the-executable-using-cmake

https://stackoverflow.com/questions/62884439/how-to-use-cmake-file-get-runtime-dependencies-in-an-install-statement

+ `TARGET_RUNTIME_DLLS` の詳細については以下を参照した。

https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:TARGET_RUNTIME_DLLS

# 追記
前述したCMake関数`copy_dlls_to_target`について、以下の要件を追加したくなった。
+ `TARGET_RUNTIME_DLLS`が使用可能か事前チェックする
+ 対象のDLLファイルが存在しないと `cmake -e copy_if_different` が構文エラーとなるため、そうしたケースでもConfigureが失敗しないようにする。

これらを踏まえて修正した関数の実装が以下のようになる。
```cmake:copy_dlls_to_target.qmake
# targetが依存するDLLをコピーする関数
# Windowsでのみ使用可能
# 対象DLLが存在しない場合を考慮し、dummyファイルを作成する
function(copy_dlls_to_target TARGET_NAME)
  if(WIN32 AND (CMAKE_SHARED_LIBRARY_SUFFIX STREQUAL ".dll"))
    message(STATUS "copy_dlls_to_target: ${TARGET_NAME}")
    set(dummy ${PROJECT_BINARY_DIR}/.dummy)
    file(TOUCH ${dummy})
    add_custom_command(
      TARGET ${TARGET_NAME}
      POST_BUILD
      COMMAND
        ${CMAKE_COMMAND} -E copy_if_different
        $<IF:$<BOOL:$<TARGET_RUNTIME_DLLS:${TARGET_NAME}>>,$<TARGET_RUNTIME_DLLS:${TARGET_NAME}>,${dummy}>
        $<TARGET_FILE_DIR:${TARGET_NAME}>
      COMMAND_EXPAND_LISTS)
    cmake_path(GET dummy FILENAME dummy_filename)
    add_custom_command(
      TARGET ${TARGET_NAME}
      POST_BUILD
      COMMAND ${CMAKE_COMMAND} -E remove ${dummy}
              $<TARGET_FILE_DIR:${TARGET_NAME}>/${dummy_filename})
  else()
    # Linuxの場合TARGET_RUNTIME_DLLSは使用できない reference:
    # https://stackoverflow.com/questions/10671916/how-to-copy-dll-files-into-the-same-folder-as-the-executable-using-cmake
    message(
      WARNING "TARGET_RUNTIME_DLLS is not defined. DLLs will not be copied.")
  endif()
endfunction()
```
+ if文を使用している箇所は、CMake3.27以降であればジェネレータ式の `$<LIST:APPEND,list,item,...>` を用いて短縮可能と思われる。