运行 Qt 项目时,Qt Creator 首先通过 ssh 将项目部署到远程嵌入式 Linux 设备上,然后在远程设备上运行可执行文件。这个特性几乎可以即时反馈 Qt 应用程序如何在嵌入式设备上工作。
部署工作可以很好地使用 qmake 的 INSTALLS 变量 来实现。不过 CMake 不具备类似 qmake 的 INSTALLS
变量的功能。幸运的是,Qt 提供了一个解决方案。下面将通过一个示例 CMakeLists.txt
文件来演示这个解决方案。
我们希望在嵌入式设备的 /opt/mycompany/bin
中安装 Qt 项目的可执行文件,在 /opt/mycompany/lib
中存放需要的三方库,而 /opt/mycompany/cad
是包含 3D CAD 文件的目录。CMakeLists.txt 的安装部分(位于 ${CMAKE_SOURCE_DIR}/src
中)与此类似。
set(CMAKE_INSTALL_PREFIX "/opt/mycompany")
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
)
install(FILES ./lib/other/libMagic.so
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
)
install(DIRECTORY ./cad/
DESTINATION ${CMAKE_INSTALL_PREFIX}/cad
)
上述设置本地部署到 Linux PC 与 make install
都工作良好。但是,嵌入式 Linux 设备的远程部署不能正常工作。
Qt Creator 在 项目>运行设置>部署>Files to deploy
设置中显示了从本地文件路径到远程文件路径的映射。对于上面的安装功能,Qt Creator 将只显示一个条目。可执行应用程序从其构建位置 ${CMAKE_BINARY_DIR}/src/app
映射到 src
。这显然是不对的。
解决这个问题的思想 是让 CMake 将从本地文件路径到远程文件路径的映射写入一个被命名为 QtCreatorDeployment.txt
的文件中。正确定义的 QtCreatorDeployment.txt
将包含以下映射。
/opt/mycompany
src/../../build-app-Remote_Qt_5_12_1-Release/src/app:bin
src/lib/other/libMagic.so:lib
src/cad/machine1/part1.step:cad/machine1
src/cad/machine1/part3.step:cad/machine1
src/cad/machine2/part6.step:cad/machine2
...
第一行给出的是复制文件到远程计算机上的(绝对)路径前缀。第二行和后面所有行的格式是。
relative/local/file:relative/remote/dir
本地目录相对于 ${CMAKE_SOURCE_DIR}
目录,而远程目录相对于第一行中给出的安装前缀 /opt/mycompany
目录。因此,上面这一行在概念上等同于下面的远程复制命令:
scp ${CMAKE_SOURCE_DIR}/relative/local/file \
developer@192.168.100.100:/opt/mycompany/relative/remote/dir/file
与往常一样,Qt 让我们的远程部署工作变得轻松了一些,它提供了两个 CMake 宏来向 QtCreatorDeployment.txt
添加文件和目录树。第一个宏 add_deployment_file
如下所示:
macro(add_deployment_file SRC DEST)
file(RELATIVE_PATH path ${CMAKE_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR})
file(APPEND "${CMAKE_SOURCE_DIR}/QtCreatorDeployment.txt"
"${path}/${SRC}:${DEST}\n")
endmacro()
第一个文件命令计算从源文件的根目录 ${CMAKE_SOURCE_DIR}
到当前处理的源目录 ${CMAKE_CURRENT_SOURCE_DIR}
的相对路径。第二个文件命令将行“${path}/${SRC}:${DEST}\n
”追加到文件 QtCreatorDeployment.txt
。
示例,调用:
add_deployment_file("lib/other/libMagic.so" "lib")
被解析的结果类似于:
SRC = "lib/other/libMagic.so", "DEST = lib"
path = "src"
Append: "src/lib/other/libMagic.so:lib\n"
宏将映射文件 QtCreatorDeployment.txt
写到目录 ${CMAKE_SOURCE_DIR}
,该目录是源树的根目录。但是,在构建过程中生成的所有文件都应该写入到构建树中。幸运的是,Qt Creator 不仅在 ${CMAKE_SOURCE_DIR}
目录(源码根目录,例如:/home/toby/test/TestEmptyCMakeProject
)中查找映射文件,而且在构建树的根目录(构建目录,例如:/home/toby/test/build-TestEmptyCMakeProject-target_qt5_12_5_syberos_5_0-Debug
) {CMAKE_BINARY_DIR}
中查找映射文件。因此,我们也可以将第一个宏中的第二条 file 命令更改为:
file(APPEND "${CMAKE_BINARY_DIR}/QtCreatorDeployment.txt"
"${path}/${SRC}:${DEST}\n")
第二个宏 add_deployment_directory
如下所示:
macro(add_deployment_directory SRC DEST)
file(GLOB_RECURSE files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
"${SRC}/*")
foreach(filename ${files})
get_filename_component(path ${filename} PATH)
add_deployment_file("${filename}" "${DEST}/${path}")
endforeach(filename)
endmacro()
file 命令行遍历目录 ${CMAKE_CURRENT_SOURCE_DIR}
处的树,并在 files
变量中存储与通配符表达式 ${SRC}/*
匹配的所有文件。
foreach 循环遍历 file 命令找到的所有文件。对于每个文件,它使用相对本地文件路径和远程目录路径 ${DEST}/${path}
调用 add_deployment_file 宏,其中 ${path}
是本地文件的相对目录路径。
示例,调用:
add_deployment_directory("cad" ".")
的解析结果示例如下:
SRC = "cad", DEST = "."
files = all file paths relative to "${CMAKE_CURRENT_SOURCE_DIR}" and
matching the pattern "cad/*"
files = "cad/machine1/part1.step" "cad/machine1/part3.step"
"cad/machine2/part6.step"
foreach loop:
add_deployment_file("cad/machine1/part1.step" "./cad/machine1")
Append: "src/cad/machine1/part1.step:cad/machine1\n"
add_deployment_file("cad/machine1/part3.step" "./cad/machine1")
Append: "src/cad/machine1/part3.step:cad/machine1\n"
add_deployment_file("cad/machine2/part6.step" "./cad/machine2")
Append: "src/cad/machine2/part6.step:cad/machine2\n"
现在,我们可以将所有部分放在一起并重写 CMakeLists.txt 文件的安装部分,如下所示:
macro(add_deployment_file SRC DEST)
file(RELATIVE_PATH path ${CMAKE_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR})
file(APPEND "${CMAKE_BINARY_DIR}/QtCreatorDeployment.txt"
"${path}/${SRC}:${DEST}\n")
endmacro()
macro(add_deployment_directory SRC DEST)
file(GLOB_RECURSE files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
"${SRC}/*")
foreach(filename ${files})
get_filename_component(path ${filename} PATH)
add_deployment_file("${filename}" "${DEST}/${path}")
endforeach(filename)
endmacro()
set(CMAKE_INSTALL_PREFIX "/opt/mycompany")
if(DEPLOYED_REMOTELY)
# Write base installation path as first line.
file(WRITE "${CMAKE_BINARY_DIR}/QtCreatorDeployment.txt"
"${CMAKE_INSTALL_PREFIX}\n")
# Append mapping for executable.
file(RELATIVE_PATH relative_exe_path
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}")
add_deployment_file(relative_exe_path bin)
# Append mapping for single library file.
add_deployment_file("lib/other/libMagic.so" "lib")
# Append all 3D CAD files from local directory "cad".
add_deployment_directory("cad" ".")
else()
# The original install commands go here for local deployment.
...
endif()
上述代码中 DEPLOYED_REMOTELY 是一个 CMake 选项,其定义为:
option(DEPLOYED_REMOTELY "Turn on for remote deployment" OFF)
Qt Creator 在 项目>构建设置> CMake
设置页面中将此选项显示为复选框。如果希望远程部署应用程序,只需勾选复选框并按下 Apply Configuration Changes
按钮。
当我们使用 Ctrl+R
运行应用程序时,Qt Creator 将根据 QtCreatorDeployment.txt
中的映射将文件复制到嵌入式设备,并在嵌入式设备上运行应用程序。
本文非原创,原文链接:《
Deploying Qt Projects to Embedded Devices with CMake》