※ Windowsでのみ有効な解決策であるため注意。
背景
CMakeプロジェクトにおいて、所望のターゲットに別環境でビルド済みのライブラリを動的リンクしたい場合、以下のように書いていた。
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 (ジェネレータ式?) の1つ、$<TARGET_RUNTIME_DLLS:tgt>
を使用すると、あるターゲットが依存する全ての動的リンクライブラリのパスをリストとして取得できる。
※ 対象(依存するライブラリ)が1つもない場合空文字に変換され、以下のコマンドはConfigure時にエラーとなるので注意。
※ TARGET_RUNTIME_DLLS は CMake3.21以降で導入されている。
これを用いて、ビルドしたターゲット実行ファイルが存在するディレクトリに、そのターゲットが依存する全ての動的リンクライブラリファイルを配置する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>
を用いて以下のように書ける。
install(FILES $<TARGET_RUNTIME_DLLS:mytarget> TYPE BIN)
参考文献
- TARGET_RUNTIME_DLLSの存在は、以下のstackoverflowの回答を見て知った。
-
TARGET_RUNTIME_DLLS
の詳細については以下を参照した。
追記
前述したCMake関数copy_dlls_to_target
について、以下の要件を追加したくなった。
-
TARGET_RUNTIME_DLLS
が使用可能か事前チェックする - 対象のDLLファイルが存在しないと
cmake -e copy_if_different
が構文エラーとなるため、そうしたケースでもConfigureが失敗しないようにする。
これらを踏まえて修正した関数の実装が以下のようになる。
# 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,...>
を用いて短縮可能と思われる。