20、CPack生成器详解(NSIS):Windows安装包制作、NSIS模板定制、创建桌面快捷方式与开始菜单项
说到Windows平台的安装包,NSIS(Nullsoft Scriptable Install System)绝对是个绕不开的名字。我最早接触它是在十年前,那时候还在用老版本的CMake,NSIS的集成远没有现在这么顺手。如今CPack对NSIS的支持已经相当成熟了,说白了,你只需要写几行CMake代码,就能生成一个像模像样的Windows安装程序。
这一章,我们就来聊聊怎么用CPack + NSIS,把我们的C++项目打包成一个专业的Windows安装包。包括桌面快捷方式、开始菜单项,还有模板定制这些实战中一定会遇到的需求。
20.1 NSIS生成器的基础配置
先说说怎么让CPack知道你要用NSIS。在CMakeLists.txt里,你只需要设置一个生成器名称:
set(CPACK_GENERATOR "NSIS")
嗯,就这么简单。但光有这一行还不够,你得告诉NSIS一些基本信息。我个人习惯把这些配置集中放在一个单独的 CPackConfig.cmake 文件里,这样主CMakeLists.txt不会太乱。
下面是一组最常用的NSIS相关变量:
| 变量名 | 说明 | 示例值 |
|---|---|---|
| CPACK_NSIS_DISPLAY_NAME | 安装程序中显示的应用名称 | "MyApp 1.0" |
| CPACK_NSIS_INSTALL_ROOT | 默认安装根目录(通常是 $PROGRAMFILES) | "$PROGRAMFILES64" |
| CPACK_NSIS_MUI_ICON | 安装程序的图标文件(.ico) | "${CMAKE_SOURCE_DIR}/icon.ico" |
| CPACK_NSIS_MUI_UNIICON | 卸载程序的图标文件 | "${CMAKE_SOURCE_DIR}/uninstall.ico" |
| CPACK_NSIS_INSTALL_DIRECTORY | 安装目录名称(在安装根目录下) | "MyApp" |
| CPACK_NSIS_URL_INFO_ABOUT | 应用主页URL(显示在控制面板中) | "https://myapp.com" |
| CPACK_NSIS_CONTACT | 开发者联系信息 | "support@myapp.com" |
举个例子,一个完整的配置大概长这样:
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.2.3")
set(CPACK_PACKAGE_VENDOR "MyCompany")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "MyApp")
set(CPACK_NSIS_DISPLAY_NAME "MyApp ${CPACK_PACKAGE_VERSION}")
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/resources/install.ico")
set(CPACK_NSIS_MUI_UNIICON "${CMAKE_SOURCE_DIR}/resources/uninstall.ico")
set(CPACK_NSIS_URL_INFO_ABOUT "https://mycompany.com/myapp")
set(CPACK_NSIS_CONTACT "support@mycompany.com")
include(CPack)
CPACK_NSIS_INSTALL_ROOT 设为 $PROGRAMFILES64,否则默认会装到32位的Program Files目录下。我曾经因为这个被测试同事追着问了好几次,后来就长记性了。
20.2 创建桌面快捷方式与开始菜单项
这是打包过程中最常被问到的需求。用户装完软件,总得有个地方点开它吧?
CPack提供了一个非常方便的变量:CPACK_CREATE_DESKTOP_LINKS。你只需要列出那些需要创建桌面快捷方式的可执行文件名称:
set(CPACK_CREATE_DESKTOP_LINKS "myapp" "myapp_tool")
这里有个坑——myapp 和 myapp_tool 必须是你在 install() 命令中安装过的目标名称,而且不能带路径。CPack会自动找到它们并生成对应的快捷方式。
但如果你想要更精细的控制,比如在开始菜单里创建文件夹、添加卸载快捷方式,那就得用 CPACK_NSIS_CREATE_ICONS_EXTRA 了。这个变量允许你直接写NSIS脚本片段:
set(CPACK_NSIS_CREATE_ICONS_EXTRA
"CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\MyApp.lnk' '$INSTDIR\\\\bin\\\\myapp.exe'"
"CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Uninstall.lnk' '$INSTDIR\\\\Uninstall.exe'"
)
\\\\。另外,$SMPROGRAMS 和 $STARTMENU_FOLDER 是NSIS内置变量,分别代表「所有用户的开始菜单目录」和「当前应用的开始菜单文件夹」。
我个人习惯的做法是:桌面只放主程序的快捷方式,开始菜单里放一个完整的文件夹,包含主程序、文档链接和卸载程序。这样用户找起来方便,卸载也直观。
20.3 NSIS模板定制
有时候,CPack提供的那些变量不够用。比如你想在安装过程中弹出一个自定义的许可协议页面,或者想在安装完成后自动启动程序。这时候就需要动NSIS模板了。
CPack在生成NSIS脚本时,会使用一个内置的模板文件。你可以通过 CPACK_NSIS_TEMPLATE 变量指定自己的模板:
set(CPACK_NSIS_TEMPLATE "${CMAKE_SOURCE_DIR}/cmake/MyNSISTemplate.nsi.in")
这个模板文件是一个 .nsi.in 文件,里面可以包含CMake变量替换。比如:
!define PRODUCT_NAME "@CPACK_NSIS_DISPLAY_NAME@"
!define PRODUCT_VERSION "@CPACK_PACKAGE_VERSION@"
!define INSTALL_DIR "@CPACK_NSIS_INSTALL_ROOT@\\@CPACK_NSIS_INSTALL_DIRECTORY@"
Section "MainSection" SEC01
SetOutPath "$INSTDIR"
File /r "${SOURCE_DIR}\\*.*"
SectionEnd
Section "StartMenu"
CreateDirectory "$SMPROGRAMS\\${PRODUCT_NAME}"
CreateShortCut "$SMPROGRAMS\\${PRODUCT_NAME}\\MyApp.lnk" "$INSTDIR\\bin\\myapp.exe"
CreateShortCut "$SMPROGRAMS\\${PRODUCT_NAME}\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"
SectionEnd
如果你不想从头写模板,也可以用 CPACK_NSIS_EXTRA_INSTALL_COMMANDS 和 CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS 来追加自定义的NSIS命令。这两个变量会在安装和卸载脚本的末尾插入你指定的代码:
set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
"ExecWait '$INSTDIR\\\\bin\\\\myapp.exe --first-run'"
)
上面这行代码会在安装完成后自动运行一次程序,并带上 --first-run 参数。我在做某个工具链打包时用过这个技巧,用来初始化用户配置文件,效果还不错。
20.4 实战:一个完整的NSIS打包示例
说了这么多,我们来拼一个完整的例子。假设我有一个叫 SuperTool 的项目,包含一个主程序和一个命令行工具:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(SuperTool VERSION 2.0.0 LANGUAGES CXX)
add_executable(supertool main.cpp)
add_executable(supertool_cli cli.cpp)
install(TARGETS supertool supertool_cli
RUNTIME DESTINATION bin
)
# CPack 配置
set(CPACK_GENERATOR "NSIS")
set(CPACK_PACKAGE_NAME "SuperTool")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_VENDOR "SuperCompany")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "SuperTool")
set(CPACK_NSIS_DISPLAY_NAME "SuperTool ${CPACK_PACKAGE_VERSION}")
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/icon.ico")
set(CPACK_NSIS_MUI_UNIICON "${CMAKE_SOURCE_DIR}/icon.ico")
set(CPACK_NSIS_URL_INFO_ABOUT "https://supercompany.com")
set(CPACK_NSIS_CONTACT "dev@supercompany.com")
# 桌面快捷方式:只放主程序
set(CPACK_CREATE_DESKTOP_LINKS "supertool")
# 开始菜单:主程序 + 命令行帮助 + 卸载
set(CPACK_NSIS_CREATE_ICONS_EXTRA
"CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\SuperTool.lnk' '$INSTDIR\\\\bin\\\\supertool.exe'"
"CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\CommandLineHelp.lnk' '$INSTDIR\\\\bin\\\\supertool_cli.exe --help'"
"CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Uninstall.lnk' '$INSTDIR\\\\Uninstall.exe'"
)
include(CPack)
然后执行:
mkdir build && cd build
cmake ..
cpack -G NSIS
你就会得到一个 SuperTool-2.0.0-win64.exe 安装包。双击运行,安装完成后桌面上会出现 SuperTool 的快捷方式,开始菜单里也有一个完整的 SuperTool 文件夹。
build/_CPack_Packages/win64/NSIS/ 目录下找到 CPack 生成的 NSIS.nsi 脚本。直接看这个脚本,就能知道 CPack 到底生成了什么。我每次遇到奇怪的问题,第一件事就是去翻这个文件。
嗯,NSIS打包其实没那么神秘。你只要掌握了那几个核心变量,再配合一点NSIS脚本知识,就能做出很专业的Windows安装包。记住,CPack帮你处理了90%的脏活累活,剩下的10%——比如快捷方式位置、自定义页面——才是你需要花心思的地方。