C++跨平台(十二):部署与发布
最后一公里从代码到用户手中跨平台开发最容易被低估的阶段是部署。编译成功、测试通过只是完成了工作的一半——如何将程序交付给最终用户并确保它在他们的机器上正常运行是另一项艰巨的工程任务。Windows、macOS和Linux在应用分发上有着截然不同的范式、工具链和用户期望。平台分发的范式差异Windows的用户习惯于下载.exe或.msi安装程序双击运行按向导点击下一步最后在开始菜单和桌面找到快捷方式。安装程序通常负责处理一切解压文件到Program Files、注册COM组件、写入注册表项、创建卸载入口。Windows用户对绿色软件无需安装直接运行接受度也较高。macOS的用户期望拖拽.app包到Applications文件夹。.app本身是一个目录Bundle包含可执行文件、资源、框架动态库、Info.plist元数据、代码签名等。分发的标准格式是.dmg磁盘镜像——打开后显示应用图标和Applications文件夹的快捷方式用户拖拽即可完成安装。macOS用户对安装向导容忍度较低除非是驱动级别的系统软件强制要求签名和公证Notarization。Linux的软件分发是碎片化最严重的领域。传统方式是通过发行版的包管理系统Debian/Ubuntu的apt.deb、Fedora/RHEL的dnf.rpm、Arch的pacman。近年来出现了通用包格式——AppImage“一次打包到处运行”、Flatpak沙箱化运行依赖独立运行时、SnapCanonical主导自动更新。每种格式各有理念、各有用户群。依赖管理策略应用程序依赖的C/C库如何随应用一起分发是部署决策的核心。有三种基本策略策略一静态链接全部依赖。将第三方库全部编译为静态库.a/.lib链接进最终的可执行文件。优点是分发简单单个可执行文件不依赖系统环境。缺点是编译时间长、二进制文件大、LGPL等许可证可能要求用户有能力替换库文件。Go语言的成功在很大程度上归功于默认的静态链接。策略二动态库跟随应用。将需要的.dll/.so/.dylib放在可执行文件旁边或子目录中。Windows默认支持这种方式DLL搜索路径包含exe所在目录Linux需要用$ORIGIN设置RPATHmacOS需要用loader_path设置install_name。Qt、Electron等框架默认采用此策略。策略三依赖系统包管理器。声明依赖关系如Debian的Depends:字段由系统包管理器负责安装。这是Linux发行版的标准做法也是最传统的但要求为每个目标发行版单独打包且用户可能安装过旧版本的依赖。推荐组合Windows和macOS上采用策略二动态库跟随应用Linux上同时提供策略二的AppImage/Flatpak面向最终用户和策略三的发行版包面向发行版仓库。各平台打包实战WindowsMSI/EXE 安装程序Windows上最流行的安装程序制作工具是WiX ToolsetWindows Installer XML和NSISNullsoft Scriptable Install System。WiX Toolset使用XML描述安装内容被编译为.msi文件。.msi是Windows Installer的标准格式Windows原生支持、企业IT部门可以通过组策略批量部署、支持事务性安装失败可以回滚。NSIS使用自定义脚本语言生成轻量级的.exe安装程序。NSIS的安装程序体积极小执行速度快且足够灵活——VLC、Winamp等知名软件都用它。CMake内置了CPack可以生成NSIS安装程序# CMakeLists.txt 中的 CPack 配置 set(CPACK_GENERATOR NSIS) set(CPACK_PACKAGE_NAME MyApp) set(CPACK_PACKAGE_VERSION 1.0.0) set(CPACK_PACKAGE_VENDOR MyCompany) set(CPACK_NSIS_INSTALL_ROOT $PROGRAMFILES64) set(CPACK_NSIS_CREATE_ICONS_EXTRA CreateShortCut $SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\MyApp.lnk $INSTDIR\\\\bin\\\\myapp.exe) include(CPack)# 构建完成后生成安装程序cmake--buildbuild--targetpackagemacOSBundle DMG 签名macOS应用的标准结构是BundleMyApp.app/ ├── Contents/ │ ├── Info.plist ← 应用元数据名称、版本、Bundle ID等 │ ├── MacOS/ │ │ └── myapp ← 可执行文件 │ ├── Resources/ │ │ ├── AppIcon.icns ← 应用图标 │ │ └── *.lproj/ ← 本地化资源 │ └── Frameworks/ │ ├── QtCore.framework ← 动态库跟随应用 │ └── ...CMake可以设置macOS Bundle属性set_target_properties(myapp PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_GUI_IDENTIFIER com.mycompany.myapp MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} )代码签名是macOS部署不可逾越的步骤。未签名的应用在macOS上会被Gatekeeper阻止运行用户需要在系统设置→隐私与安全性中手动允许。发布到互联网的应用还需要经过Apple的公证Notarization流程——将签名的DMG上传到Apple服务器进行恶意软件扫描。# 签名codesign--deep--force--verify--verbose\--signDeveloper ID Application: Your Name (TEAM_ID)\MyApp.app# 创建DMGhdiutil create-volnameMyApp-srcfolderMyApp.app\-ov-formatUDZO MyApp.dmg# 公证xcrun notarytool submit MyApp.dmg\--apple-idyouremail.com--team-idTEAM_ID\--passwordkeychain:AC_PASSWORD--wait# 装订公证票据离线验证用xcrun stapler staple MyApp.dmgLinux传统包 AppImage Flatpak传统DEB包可以使用CPack或dpkg-deb创建# 使用 CPack 生成 DEBcmake-DCPACK_GENERATORDEB..cmake--build.--targetpackage# 或手动构建mkdir-pmyapp_1.0.0_amd64/DEBIANmkdir-pmyapp_1.0.0_amd64/usr/bincpmyapp myapp_1.0.0_amd64/usr/bin/catmyapp_1.0.0_amd64/DEBIAN/controlEOF Package: myapp Version: 1.0.0 Section: utils Priority: optional Architecture: amd64 Depends: libc6 ( 2.31), libstdc6 ( 12) Maintainer: Your Name youremail.com Description: My cross-platform application EOFdpkg-deb--buildmyapp_1.0.0_amd64AppImage的理念是一次打包到处运行。它本质上是一个自解压的文件系统镜像——将应用和所有依赖打包成一个文件运行时挂载为临时文件系统。AppImage不要求安装不要求root权限适合直接下载分发给最终用户。# AppImage 基本创建流程mkdir-pAppDir/usr/bin AppDir/usr/libcpmyapp AppDir/usr/bin/cplib/*.so AppDir/usr/lib/cpmyapp.desktop AppDir/cpmyapp.png AppDir/# 使用 linuxdeploy 工具linuxdeploy--appdirAppDir--executableAppDir/usr/bin/myapp\--desktop-file AppDir/myapp.desktop\--icon-file AppDir/myapp.png\--outputappimageFlatpak关注沙箱化和分发。应用运行在受限的沙箱中通过Portal API访问系统资源文件打开对话框、打印、通知等。应用声明对运行时如org.kde.Platform或org.gnome.Platform的依赖这些运行时由Flatpak系统统一管理。自动更新跨平台自动更新是另一个复杂领域。各平台的期望和机制不同Windows没有系统级的自动更新API。应用通常自己实现更新检查下载新版本安装程序并静默运行或使用第三方框架如WinSparkle类似macOS的Sparkle。Microsoft Store版本可以使用商店的更新机制。macOSSparkle是事实标准——成千上万的macOS应用使用它进行自动更新包括VLC、Transmission、Cyberduck等知名开源软件。Sparkle使用RSS格式的应用强制类型Appcast来描述更新信息支持增量更新、代码签名验证、在应用内下载和安装。Linux传统上完全依赖发行版的包管理系统进行更新。AppImage使用AppImageUpdate工具可以差分更新Flatpak和Snap内置了更新系统类似于移动端的应用商店模型。对于跨平台应用建议三层策略Linux上完全交给包管理系统和内置的Flathub/Snap Store更新机制macOS上用SparkleWindows上实现简单的检测→下载→安装流程或使用WinSparkle。应用商店分发应用商店是另一种重要的分发渠道商店平台要求Microsoft StoreWindowsMSIX打包、代码签名、通过认证测试Mac App StoremacOSSandboxing、App Store配置文件签名、审核FlathubLinuxFlatpak打包、遵循元数据规范Snap StoreLinuxSnap打包、自动更新商店分发的优势是自动更新、用户信任、集中统计代价是需要遵守各商店的审核政策和打包限制尤其是沙箱化要求可能限制应用的功能。实际建议应用和依赖一起打包。不要依赖用户自己安装正确的运行时库版本。在干净的虚拟机中测试安装。在一台全新安装的Windows/Linux/macOS虚拟机上运行安装程序是发现缺少依赖问题的最可靠方式。自动化构建产物流程。在CI中自动完成编译、签名在安全的密钥管理下、打包、生成安装程序的全流程。为每个版本保留构建信息。将git hash、构建时间、构建环境信息嵌入到二进制中方便事后追溯问题的来源。了解目标平台的用户期望。Windows用户接受安装向导macOS用户期望拖拽安装Linux用户期望包管理器。迎合这些期望能减少用户摩擦。