Qt應用程式在運行時依賴於動態連結庫以及資源文件,在調試和生成Qt安裝包時都需要進行處理,否則經常出現本地運行可以,而在客戶電腦上不行的場景.
這時,保持開發者本地環境和真實運行環境一致是比較好的實踐.即:
不使用專門的IDE及插件(例如Qt Visual Studio Tools).
這種情況下CMake是比較好的選擇,這時就需要解決調試時部署Qt運行時的問題:
好在Qt在Windows上提供了windeployqt來幫助開發者.那麼如何整合到CMakeLists中呢?
工程構建結果的部署工程構建結果包含構造出來的exe、dll、qm以及其它資源文件.CMake默認會配置exe、dll等內容的輸出目錄,而資源文件是需要開發者自行處理的.這裡也分兩種場景來處理:
調整運行時輸出位置在CMake中 exe、dll等被成為運行時(runtime),通過指定CMAKE_RUNTIME_OUTPUT_DIRECTORY可以調整其輸出位置,假設要根據不同的配置(Debug、Release等)輸出到構建目錄,則在工程的主CMakeLists.txt中通過這種方式定義:
if(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIG>")
endif()
拷貝資源到輸出位置要執行這一操作,首先要獲取輸出位置,假設你為庫或者應用程式定義的target名稱為TARGET_NAME,通過如下指令可以獲取輸出位置:
$<TARGET_FILE_DIR:TARGET_NAME>qm文件的路徑存儲在TARGET_NAME_QM_FILE變量中,那麼可以以如下命令完成目錄創建、翻譯文件拷貝動作:
## 複製資源文件
add_custom_command(TARGET TARGET_NAME POST_BUILD
## 創建翻譯文件目錄
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:TARGET_NAME>/translations/
## 翻譯文件
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${TARGET_NAME_QM_FILE} $<TARGET_FILE_DIR:TARGET_NAME>/translations/
)拷貝資源文件也是相同的處理方式,假設在CMakeLists.txt同目錄下有data文件夾存儲了運行時資源,拷貝命令為:
## 複製資源文件
add_custom_command(TARGET TARGET_NAME POST_BUILD
## 複製數據
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/data/ $<TARGET_FILE_DIR:TARGET_NAME>/
)這裡建議你將庫、應用程式的name定義為變量,這樣碰見相同場景直接複製上述代碼即可,例如:
## 複製資源文件
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
## 複製數據
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/data/ $<TARGET_FILE_DIR:${TARGET_NAME}>/
## 創建翻譯文件目錄
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:${TARGET_NAME}>/translations/
## 翻譯文件
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${${TARGET_NAME}_QM_FILE} $<TARGET_FILE_DIR:${TARGET_NAME}>/translations/
)另外,TS以及QM文件均可以自動生成,CMake中寫法如下:
##查找Qt翻譯家
find_package(Qt5LinguistTools)
##創建TS文件並提供qm生成
qt5_create_translation(${TARGET_NAME}_QM_FILE
${${TARGET_NAME}_HEADER_FILES} #所有頭文件
${${TARGET_NAME}_SRC_FILES} #所有源文件
${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_zh.ts #生成的ts文件完整路徑
)
Qt運行時及資源部署經過上述操作,應用程式自身內容的部署就已經完成.而Qt的部署鑑於有windeployqt,可以實現得相對簡單且通用.
基本思路定義DeployQtRuntime.cmake,包含DeployQtRuntime函數,能夠處理各種Qt運行時及資源部署場景.該函數具備如下能力:
函數使用方式如下:
DeployQtRuntime(
TARGET YourTargetName
WebEngine #部署WebEngine
QmlFilesPath "工程qml文件路徑,供windeployqt掃描"
PLUGINS "xml;svg"
)在工程的主CMakeLists.txt中要將DeployQtRuntime.cmake所在路徑添加到CMAKE_MODULE_PATH中,例如:
## 配置本地cmake腳本路徑
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
include(DeployQtRuntime)
函數聲明include(CMakeParseArguments)
## 部署Qt運行時及插件
function(DeployQtRuntime)
set(options WebEngine)
set(oneValueArgs TARGET QmlFilesPath)
set(multiValueArgs PLUGINS)
cmake_parse_arguments(Gen "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
endfunction()CMakeParseArguments為CMake提供的參數解析輔助函數,上述聲明會將參數解析成為Gen_開頭的變量供後續使用.
查找windeployqtfind_package(Qt5 COMPONENTS Core CONFIG)
#加載Qt5::Core對應的庫配置文件,能夠找到Qt的bin目錄
if(NOT _qt5_install_prefix)
return()
endif()
## message(STATUS "Qt cmake模塊位於:${_qt5_install_prefix}")
## _qt5_install_prefix基本上在lib/cmake位置,需要定位到bin路徑下面找到部署程序
find_program(__qt5_deploy windeployqt PATHS "${_qt5_install_prefix}/../../bin")
## 獲取QTDIR
set(QTDIR "${_qt5_install_prefix}/../../")
插件參數格式處理Gen_PLUGINS要拆分成一個個插件,然後加上-前綴,並且在WINDOWS下還要調整以下參數樣式:
set(qt_plugins "")
foreach(__plugin ${Gen_PLUGINS})
string(APPEND qt_plugins " -${__plugin}")
endforeach()
separate_arguments(qt_plugins_list WINDOWS_COMMAND ${qt_plugins})
調用windeployqtqml默認掃描路逕取自target輸出路徑:
set(QmlFilesPath "$<TARGET_FILE_DIR:${Gen_TARGET}>")
if(Gen_QmlFilesPath)
#如果用戶設置了qml文件路徑則直接使用
set(QmlFilesPath "${Gen_QmlFilesPath}")
endif()
add_custom_command(TARGET ${Gen_TARGET} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E env QTDIR="${QTDIR}" "PATH=${QTDIR}/bin" windeployqt.exe --qmldir ${QmlFilesPath} $<TARGET_FILE:${Gen_TARGET}> ${qt_plugins_list}
COMMENT "deploy Qt runtime dependencies"
)注意上述命令在執行過程中,調整了環境變量QTDIR,這是因為用戶的環境下可能配置了QTDIR,會導致執行了環境變量配置中的windeployqt.exe,從而導致錯誤.
處理WebEngine運行時由於WebEngine運行需要一些特殊的資源文件以及QtWebEngineProcess,需要專門處理:
## Qt如果部署包含Qt WebEngine的應用,需要處理無法自動部署的資源
if(Gen_WebEngine)
add_custom_command(TARGET ${Gen_TARGET} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${_qt5_install_prefix}/../../resources/" ##拷貝 resources 目錄下內容
"$<TARGET_FILE_DIR:${Gen_TARGET}>"
COMMENT "deploy Qt WebEngine resources"
COMMAND ${CMAKE_COMMAND} -E copy "${_qt5_install_prefix}/../../bin/$<IF:$<CONFIG:DEBUG>,QtWebEngineProcessd.exe,QtWebEngineProcess.exe>"
"$<TARGET_FILE_DIR:${Gen_TARGET}>/$<IF:$<CONFIG:DEBUG>,QtWebEngineProcessd.exe,QtWebEngineProcess.exe>"
COMMENT "deploy Qt WebEngine process"
)
endif()