事实上,CMake除了构建管理之外,还可以进行测试和打包安装程序。其中打包功能由CPack模块1支持,可以针对不同平台打包生成适用的安装程序。
1. Qt程序CMake配置
以之前编写的QML程序为例,编写CMakeLists.txt配置文件如下。末尾引入的packaging.cmake文件定义了打包配置相关信息。
cmake_minimum_required(VERSION 3.16)
set(PROJECT_NAME sample)
project(${PROJECT_NAME} VERSION 0.1 LANGUAGES CXX)
set(EXECUTABLE_NAME ${PROJECT_NAME}_app)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Quick)
if(Qt6_FOUND)
message(STATUS "Qt6 found: ${Qt6_DIR}")
else()
message(FATAL_ERROR "Qt6 not found!")
endif()
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(${EXECUTABLE_NAME}
main.cpp
)
qt_add_qml_module(${EXECUTABLE_NAME}
URI ${PROJECT_NAME}
VERSION 1.0
QML_FILES
Main.qml
)
# set static linking for MSVC
if(MSVC AND (${VCPKG_TARGET_TRIPLET} MATCHES "static"))
message(STATUS "Configuring for MSVC static linking")
set_property(TARGET ${EXECUTABLE_NAME} PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
set_target_properties(${EXECUTABLE_NAME} PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.${EXECUTABLE_NAME}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(${EXECUTABLE_NAME}
PRIVATE Qt6::Quick
)
include(GNUInstallDirs)
install(TARGETS ${EXECUTABLE_NAME}
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
if(MSVC AND (${VCPKG_TARGET_TRIPLET} MATCHES "static"))
message(STATUS "Linking static MSVC runtime, skipping install of MSVC runtime DLLs")
else()
message(STATUS "Configuring install of runtime dependencies")
set(deploy_tool_options_arg "")
if(APPLE)
set(deploy_tool_options_arg --hardened-runtime)
elseif(WIN32)
set(deploy_tool_options_arg --no-compiler-runtime)
endif()
qt_generate_deploy_qml_app_script(
TARGET ${EXECUTABLE_NAME}
OUTPUT_SCRIPT deploy_script
MACOS_BUNDLE_POST_BUILD
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
DEPLOY_TOOL_OPTIONS ${deploy_tool_options_arg}
)
install(SCRIPT ${deploy_script})
install(CODE [[
if("$ENV{VCPKG_TARGET_TRIPLET}" STREQUAL "")
find_program(QMAKE qmake6 HINTS $ENV{PATH})
if(NOT QMAKE)
message(FATAL_ERROR "qmake not found in PATH")
else()
message(STATUS "qmake found: ${QMAKE}")
cmake_path(GET QMAKE PARENT_PATH QT_BIN_DIR)
message(STATUS "qmake bin dir: ${QT_BIN_DIR}")
endif()
else()
set(QT_BIN_DIR "$ENV{VCPKG_ROOT}/installed/$ENV{VCPKG_TARGET_TRIPLET}/bin")
if(NOT EXISTS ${QT_BIN_DIR})
message(FATAL_ERROR "qmake bin dir not found: ${QT_BIN_DIR}")
endif()
endif()
file(GET_RUNTIME_DEPENDENCIES
RESOLVED_DEPENDENCIES_VAR RESOLVED_DEPS
UNRESOLVED_DEPENDENCIES_VAR UNRESOLVED_DEPS
# path to the executable files
EXECUTABLES $<TARGET_FILE:sample_app>
# directories to search for library files
DIRECTORIES ${QT_BIN_DIR}
PRE_EXCLUDE_REGEXES "system32"
PRE_EXCLUDE_REGEXES "api-ms-*"
POST_EXCLUDE_REGEXES "system32"
)
foreach(DEP_LIB ${RESOLVED_DEPS})
file(INSTALL ${DEP_LIB} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
endforeach()
]])
endif()
include(${CMAKE_CURRENT_SOURCE_DIR}/packaging.cmake)
在install定义了要安装什么内容之后,在cmake配置文件末尾增加打包相关内容2。
# --- packaging.cmake ---
include(InstallRequiredSystemLibraries)
set(CPACK_PACKAGE_NAME "qml_sample_install")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_CONTACT "author_email@example.com")
set(CPACK_PACKAGE_VENDOR "author_name")
set(CPACK_PACKAGE_EXECUTABLES "${EXECUTABLE_NAME}" "${EXECUTABLE_NAME}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A demo qt quick application with CMake packaging")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" CACHE FILEPATH "License file")
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md" CACHE FILEPATH "Readme file")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}")
set(CPACK_CREATE_DESKTOP_LINKS "bin/${EXECUTABLE_NAME}")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}")
include(CPack)
2. NSIS 打包
CPack本身不提供打包安装程序,需要借助其他程序打包成可执行的安装包。Windows平台下比较常用的有NSIS3和Inno Setup4等。
首先运行cmake命令配置构建脚本并编译。
cmake -B build .
cmake --build build --config Release
进入构建目录,运行CPack程序进行打包。-G "NSIS"表示打包生成器指定为NSIS5,注意要将NSIS安装目录添加到PATH里面。
$Env:PATH += ";D:\conda\envs\cpp_env\NSIS"
cd build
cpack -G "NSIS"
完成后在build/_CPack_Packages/win64/NSIS路径下可以找到生成的exe安装包。
用NSIS打包只适合Windows平台,而且打包的安装程序可以通过7z、WinRAR等压缩程序直接打开,安全性不高。
3. Inno Setup打包
CPack的生成器选择"INNOSETUP"6,同样需要先安装Inno Setup并将安装路径添加到PATH。
$Env:PATH += ";D:\opt\Inno Setup 6"
cd build
cpack -G "INNOSETUP"
打包完成后在build/_CPack_Packages/win64/INNOSETUP路径下生成安装文件。
Inno Setup同样只适合Windows平台,相比NSIS安全性有了一些提升,但还是可以通过专用工具解包。
4. WiX打包
WiX7是专门为Windows开发的打包工具,用于生成.msi格式的安装包。打包工具可以从WiX3发布页面 下载。
WiX的许可证文件只支持rtf格式文件,需要在CMake配置文件中定义CPACK_WIX_LICENSE_RTF来指定许可证文件,否则构建安装包时会报错。
# --- packaging.cmake ---
...
set(CPACK_WIX_LICENSE_RTF "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rtf" CACHE FILEPATH "License file in RTF format")
...
生成器选择"WIX"8。
$Env:PATH += ";D:\opt\wix314-binaries"
cd build
cpack -G "WIX"
打包完成后在build/_CPack_Packages/win64/WIX路径下查看生成的文件。
使用这种方式打包的程序安装之后不会在安装目录留下卸载程序,需要通过控制面板-程序或设置-应用界面卸载。
5. Qt Installer Framework打包
Qt Installer Framework9是Qt官方专门为Qt开发的打包安装程序,适合所有受Qt支持的平台。Qt Installer Framework可以从Qt官方发布网站下载10。
CPack打包命令,生成器选择"IFW"11。
$Env:PATH += ";D:\opt\Qt\QtIFW-4.8.1\bin"
cd build
cpack -G "IFW"
Windows下打包完成后在build/_CPack_Packages/win64/IFW路径下生成安装文件。
Linux下打包完成后直接在build目录下就能找到安装文件,默认添加.run后缀名。
Qt Installer Framework打包的优点是支持跨平台,而且不同平台的安装程序操作界面一致性较好,缺点是无法生成开始菜单启动项和桌面快捷方式,需要自己针对不同安装平台单独编写安装脚本。
6. 打包DEB文件
CPack支持打包Linux二进制文件。指定DEB生成器12用于生成DEB包,适合Ubuntu、Debian等系统。
cpack -G "DEB"
打包完成后直接在build目录下生成DEB包。
使用dpkg工具直接安装,也可以通过apt工具安装。
dpkg -i path-to-package.deb
7. 打包RPM文件
指定DEB生成器13用于生成RPM包,适合Fedora、Red Hat、SUSE等系统。
cpack -G "RPM"
在build目录下可以找到打包生成的RPM包。
使用rpm工具安装即可,也可以通过dnf、zypper等包管理工具安装。
rpm -ivh path-to-package.rpm
8. 总结
Windows下发布的安装包,推荐使用Inno Setup或者WiX打包。Linux则建议分别生成DEB包和RPM包以适应不同发行版要求。
如果不考虑打包之后的文件体积,可以使用Qt Installer Framework打包,跨平台而且一致性更好,只是需要自己编写脚本生成桌面图标和开始菜单启动项。