
打开 Qt Creator 跑起来一个能用的窗口界面调得也顺眼结果交付给同事的那一刻被问你这程序怎么是个默认的白色小方块。这类反馈我遇到过不止一次。给 Qt 应用程序设置 logo 这件事表面上看是加一行setWindowIcon实际动手才知道它牵扯到至少四个完全独立的显示位置窗口标题栏和任务栏、可执行文件在文件管理器里显示的那个图标、安装包和桌面快捷方式、以及 macOS 的 Dock 和 Linux 的启动器入口。这四个位置由四套不同的机制负责改了一处没改另一处就会出现我自己机器上明明是对的这种经典场面。下面这套内容我按真实施工顺序来写先把图标显示位置这件事掰清楚再分别处理 Windows 的资源文件、图标文件本身的多尺寸制作、代码里的 qrc 与 QIcon 适配、macOS 与 Linux 的收尾最后把我踩过的排查链路完整摊开。适合正在做 Qt 桌面端打包发布、被图标问题卡过一次的人也适合刚上手 Qt、想知道为什么加了图标还是不生效的新手。1. 先把图标的四个投放位置分清楚1.1 运行时窗口图标和任务栏图标这是大多数人第一反应想到的那个位置由QApplication::setWindowIcon()控制。它的实现方式是 Qt 在窗口创建后通过平台插件往窗口系统里写图标属性Windows 下走WM_SETICONX11 下写_NET_WM_ICON这个 propertymacOS 下则交给 Cocoa 设置NSApplication的图标。也就是说这一层只管程序跑起来之后界面上显示的图标跟文件系统里那个 exe 长什么样没有任何关系。这里有个细节值得单独说如果你只调了setWindowIcon没有往 exe 里嵌资源那么在 Windows 的任务栏上图标大概率是能正常显示的——因为任务栏的按钮图标来自窗口的WM_SETICON。但如果你把这个程序固定到任务栏固定后再看图标可能又变回默认样式了。原因是被固定的是快捷方式快捷方式的图标是另一套东西后面 1.3 节会讲。还有一个容易忽视的点setWindowIcon要放在主窗口创建之前调用。理论上 Qt 会把它应用到之后创建的所有没单独设过图标的窗口但顺序颠倒时某些平台插件在窗口已经映射之后再改图标会触发一次窗口重建视觉上就是任务栏图标闪一下再变。稳妥做法是紧跟QApplication构造之后立刻设置。1.2 可执行文件自身的图标这是在资源管理器/访达里看到的那个图标也是真正需要构建系统参与的一层。它不是在代码里设置的而是在编译期把一个图标资源塞进二进制文件的资源段。Windows 靠的是 PE 文件里的资源节macOS 靠的是 app bundle 里Contents/Resources/下的.icns加Info.plist里的CFBundleIconFile字段。为什么这一层不能用代码解决因为图标要显示在文件被双击之前此时操作系统还没加载程序不可能执行任何一行你的 C 代码。操作系统只能去读文件的静态元数据这就是资源段存在的意义。理解了这一点后面所有为什么我改了代码图标还是不变的困惑基本都能自解——你改的是运行时行为操作系统看的是静态数据两者之间没有任何通道。顺带一提很多人在 Qt Creator 里按 F5 运行发现任务栏图标是对的就以为大功告成结果直接把 exe 拷给别人对方看到的是空白图标。原因就是 Qt Creator 运行的是构建目录里的临时 exe那里面根本没嵌资源只是运行时窗口图标生效了而已。1.3 安装包、快捷方式与桌面入口第三层是分发环节和 Qt 关系不大但同样要让用户看到正确图标。Windows 上如果用 Inno Setup、NSIS 或者 WiX 做安装包安装包本身的图标、写入的开始菜单项图标、桌面快捷方式图标都是各自独立的配置项各有各的坑。快捷方式的图标路径需要指向一个安装后依然存在的文件常见错误是直接指向构建目录用户装完就变成白板。Linux 下这一层对应.desktop文件macOS 下对应 DMG 的卷图标和 bundle 图标。这三者的共同点是它们都不受你的 Qt 代码控制必须单独配。1.4 别指望一套配置打通所有平台把四层列成一张表思路会清楚很多投放位置生效机制由谁控制是否需重新编译窗口标题栏/任务栏运行时写窗口属性QApplication::setWindowIcon否exe 文件本身图标编译期嵌入资源段.rc文件 /RC_ICONS/.icns是安装包与快捷方式安装器脚本配置Inno/NSIS/desktop 文件否Dock / 启动器bundle 元数据 / desktop 文件Info.plist/.desktop视情况看到没有真正需要重新编译的只有第二行。所以调试图标问题时的第一个判断动作应该是问自己现在看到的是哪一层的图标不对。判断方法很简单把 exe 复制到一个全空的临时目录用系统文件管理器看它的图标再把 exe 双击跑起来看任务栏图标。两者一对比立刻就知道该往哪个方向查。2. Windows 上把图标焊进 exe 的两种接法2.1 qmake 里一行 RC_ICONS 到底做了什么如果你用的是 qmake 工程嵌入图标确实只需要一行QT widgets TARGET MyApp TEMPLATE app RC_ICONS $$PWD/icons/app.ico SOURCES main.cpp这行的原理是qmake 在生成 Makefile 时会在构建目录里自动生成一个.rc文件内容大致是IDI_ICON1 ICON DISCARDABLE 路径/app.ico然后把它作为源文件一起交给资源编译器MSVC 用rc.exeMinGW 用windres.exe。编译产物是一个.res目标文件链接阶段和其他.o一起被打进 PE 文件。所以你并没有绕开资源文件这一步只是 qmake 替你写了。注意RC_ICONS后面只能跟一个.ico文件。有人试图写成多个用空格分隔结果只有第一个生效甚至报错。多尺寸的问题不是靠这里解决而是靠.ico文件内部的多个图像帧见第 3 节。还有一个容易混淆的写法是RC_FILE app.rc这是让你自己提供完整的.rc文件。RC_ICONS和RC_FILE不要同时出现同时设置时行为取决于 qmake 版本可能其中一个被忽略排查起来很费时间。2.2 CMake 工程里手动喂 .rc 的正确姿势Qt 6 时代用 CMake 的人越来越多方式不同。首先要意识到 CMake 默认可能没有启用 RC 语言支持尤其是用 MinGW 工具链时cmake_minimum_required(VERSION 3.16) project(MyApp LANGUAGES CXX) if(WIN32) enable_language(RC) endif() find_package(Qt6 REQUIRED COMPONENTS Widgets) qt_standard_project_setup() qt_add_executable(MyApp main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/icons/app.rc ) if(WIN32) set_target_properties(MyApp PROPERTIES WIN32_EXECUTABLE ON) endif()配套的icons/app.rc内容只有一行IDI_ICON1 ICON DISCARDABLE app.ico关键点在IDI_ICON1这个标识符。Windows 约定俗成的习惯是用IDI_ICON1实际上是靠序号 1 来定位的也就是资源类型 ICON 下的第一个图标。这个数字不能乱改改成别的名字时部分工具链能编过但系统读不到结果就是图标不显示。2.3 那个害我查了两小时的相对路径坑app.rc里写相对路径app.ico时资源编译器到底从哪里找这个文件答案是取决于工具链和构建系统怎么调用它MSVC 的rc.exe通常以构建目录为当前目录MinGW 的windres.exe则常以.rc文件所在目录或构建目录为基准。同一份配置在 MSVC 下能过、在 MinGW 下报找不到文件就是这里出的问题。最稳的做法是不要在.rc里写相对路径。用 CMake 生成它configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/icons/app.rc.in ${CMAKE_CURRENT_BINARY_DIR}/app.rc ONLY )app.rc.in里写IDI_ICON1 ICON DISCARDABLE APP_ICON_PATH然后设置APP_ICON_PATH为app.ico的绝对路径注意把反斜杠转成正斜杠或者用双反斜杠否则会被当成转义字符。这样做虽然多了一个模板文件但从此跨工具链、跨机器都不会出问题。注意路径里带空格或者中文字符时MinGW 的windres在某些老版本上会直接崩掉。图标目录和设备名、用户名相关的构建路径尽量不要含非 ASCII 字符这是最省事的规避方式。2.4 顺手把文件属性也填了既然已经在处理.rc建议把 Windows 文件属性一起配置好。在 qmake 里是这几个变量VERSION 1.2.0.0 QMAKE_TARGET_COMPANY Example Studio QMAKE_TARGET_PRODUCT MyApp QMAKE_TARGET_DESCRIPTION MyApp desktop client QMAKE_TARGET_COPYRIGHT Copyright (C) 2024 Example Studio QMAKE_TARGET_ORIGINAL_FILENAME MyApp.exe QMAKE_TARGET_INTERNALNAME MyApp这些会写进 PE 的版本信息资源里决定文件属性对话框中显示的内容。很多人只配了图标用户右键看属性发现公司名是空的或者显示成TARGET变量拼出来的奇怪字符串观感上很掉价。做商业交付时这几项一定要补齐尤其是QMAKE_TARGET_PRODUCTWindows 在某些场景下会用它来识别应用。CMake 工程没有直接等价的变量需要自己在.rc里加VERSIONINFO块或者用qt_add_executable配合自定义的.rc模板。做法稍繁琐但一次写好之后就是复制粘贴的事。3. ico 文件本身的门道一次做成多尺寸3.1 为什么单尺寸图标必然糊.ico格式和.png最大的区别在于它是一个容器里面可以放多张不同分辨率的图像帧。系统在渲染时会根据当前显示尺寸挑最合适的一帧。会话任务栏在 100% 缩放下用的是 32×32150% 缩放用 48×48Windows 11 的圆角任务栏在大图标模式下会用到 64×64而超大图标视图下的资源管理器会用 256×256。如果.ico里只有一张 256×256 的图系统在需要 16×16 时就得现场做一次缩略运算。这个运算由外壳完成算法不见得比你自己处理得好结果就是小尺寸下边缘发虚、细线条消失。反过来如果只有一张 32×32 的图在大图标视图下被放大锯齿会非常明显。这就是为什么单尺寸的.ico看起来永远差一口气。建议的最小尺寸集合是16、24、32、48、64、128、256。24 这个尺寸容易被忽略它对应的是部分旧版列表视图和某些第三方外壳扩展加上不亏。3.2 用命令行批量生成别手动一个个导出ImageMagick 一条命令就能生成全套而且会按需要自动缩放magick app_1024.png -define icon:auto-resize256,128,64,48,32,24,16 app.ico源图建议用 1024×1024 的 PNG透明通道保留完整。低于 256 的源图不要拿来当母版放大放大出来的边缘是插值糊出来的视觉上一眼能看出来。macOS 那一侧需要的是.icns。从一张 1024 的 PNG 出发先生成一个.iconset目录文件名有严格要求mkdir app.iconset magick app_1024.png -resize 16x16 app.iconset/icon_16x16.png magick app_1024.png -resize 32x32 app.iconset/icon_16x162x.png magick app_1024.png -resize 32x32 app.iconset/icon_32x32.png magick app_1024.png -resize 64x64 app.iconset/icon_32x322x.png magick app_1024.png -resize 128x128 app.iconset/icon_128x128.png magick app_1024.png -resize 256x256 app.iconset/icon_128x1282x.png magick app_1024.png -resize 256x256 app.iconset/icon_256x256.png magick app_1024.png -resize 512x512 app.iconset/icon_256x2562x.png magick app_1024.png -resize 512x512 app.iconset/icon_512x512.png cp app_1024.png app.iconset/icon_512x5122x.png iconutil -c icns app.iconset -o app.icnsiconutil是 macOS 自带工具只能在 macOS 上跑。如果你的构建机器是 Linux 或者 Windows就得在 CI 里加一个 macOS runner 专门产出这个文件或者用跨平台的png2icns替代。文件名的2x后缀不是随便写的iconutil会按这个约定推导 DPI 元数据名字不对会直接报错。3.3 小尺寸下的可读性经验这一块是设计层面的事但作为工程师你至少要知道什么图能过、什么图不能过。我的经验是三条第一16×16 下不要指望有任何文字。哪怕源图里只有一个字母缩到 16 像素后基本就是一坨。品牌标识如果是纯字母建议额外做一个图形化的简化版本专门用于小尺寸。第二线条宽度在源图上至少占 3% 的画布宽度。1024 的画布上笔画不要细于 30 像素这样缩到 32×32 时才还剩大约 1 像素勉强能看清。低于这个值2 倍缩放下会因抗锯齿而变成灰雾。第三留出安全边距。macOS 的 Dock 图标视觉上会比其他图标显得大因为它周围有一圈透明留白。Windows 任务栏没有这个约定图标会被撑满。如果你的源图四周不留边距在 macOS 上会显得比邻居大一截。我一般会留 8% 到 10% 的边距跨平台观感比较均衡。4. 代码里怎么设qrc 与 QIcon 的配合4.1 setWindowIcon 的时机和作用范围运行时图标的设置代码就三行但位置很重要#include QApplication #include QIcon #include mainwindow.h int main(int argc, char *argv[]) { QApplication app(argc, argv); QApplication::setWindowIcon(QIcon(:/icons/app.png)); MainWindow w; w.show(); return app.exec(); }QApplication::setWindowIcon是应用级默认值。任何没有单独调用过QWidget::setWindowIcon的顶层窗口都会继承它。如果你有某些独立窗口需要不同图标——比如一个数据可视化窗口有自己的标识——就在那个窗口的构造函数里单独设置不设置就会继承。有一个例外要留意QMessageBox、QFileDialog这类原生对话框在部分平台上是系统绘制的它们显示的是系统默认图标而不是你的应用图标。这是正常行为不用去折腾。4.2 qrc 里放多张大图还是放一张 SVG两种方案各有适用场景。位图方案是往 qrc 里放多张不同尺寸的 PNGRCC qresource prefix/icons fileapp_16.png/file fileapp_24.png/file fileapp_32.png/file fileapp_48.png/file fileapp_256.png/file /qresource /RCC然后在代码里逐个登记尺寸QIcon icon; icon.addFile(:/icons/app_16.png, QSize(16, 16)); icon.addFile(:/icons/app_24.png, QSize(24, 24)); icon.addFile(:/icons/app_32.png, QSize(32, 32)); icon.addFile(:/icons/app_48.png, QSize(48, 48)); icon.addFile(:/icons/app_256.png, QSize(256, 256)); QApplication::setWindowIcon(icon);如果不显式登记尺寸QIcon只知道这张图的实际像素大小请求 48×48 时它会拿最接近的那张去缩放缩放质量取决于QIcon的内插策略通常不如你预先准备一张原生 48×48 来得锐利。SVG 方案更省事但需要QtSvg模块参与QApplication::setWindowIcon(QIcon(:/icons/app.svg));矢量图在任意尺寸下都锐利单一文件就能覆盖全部 DPI维护成本最低。代价是运行时会做矢量渲染图标特别复杂时首次渲染有一点点开销通常可以忽略另外极小尺寸下矢量图的细节会挤成一团反而不如手工调过的小尺寸位图。我的选择标准是几何化的标识用 SVG带纹理或渐变的复杂标识用位图多尺寸方案。4.3 高 DPI 下的 2x 命名约定Qt 从 5.6 开始支持一种很省事的约定只要资源里同时存在app.png和app2x.pngQIcon会在高 DPI 环境下自动选择后者。命名规则是basenamefactorx.extfactor 可以是 2、3、4。RCC qresource prefix/icons fileapp.png/file fileapp2x.png/file fileapp3x.png/file /qresource /RCC代码里只需要QIcon(:/icons/app.png)一句不用手动登记。这个机制的原理是QIcon在加载时会把同目录下的倍率变体一并读进来作为独立的 pixmap 帧带着devicePixelRatio元数据存储。Qt 5 里还需要显式打开高 DPI 支持必须放在QApplication构造之前QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication app(argc, argv);Qt 6 默认开启不需要这两行。但如果你的项目还停留在 Qt 5忘了加这两行在 4K 屏上会看到任务栏图标明显发虚且窗口内所有图标都糊这是很典型的一个排查起点。5. 跨平台收尾icns 与 desktop 入口5.1 macOS bundle 里到底放了什么qmake 工程这一侧很简单一行搞定macx: ICON $$PWD/icons/app.icnsqmake 会自动把.icns拷进MyApp.app/Contents/Resources/同时在Info.plist里写入CFBundleIconFile app不带扩展名。你可以直接打开 bundle 里的Info.plist确认字段有没有生成这是最快的验证方式。CMake 工程需要手动配置MACOSX_BUNDLE属性加上图标文件和它的安装位置set_target_properties(MyApp PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_ICON_FILE app.icns MACOSX_BUNDLE_BUNDLE_NAME MyApp MACOSX_BUNDLE_GUI_IDENTIFIER com.example.myapp ) set(APP_ICON_ICNS ${CMAKE_CURRENT_SOURCE_DIR}/icons/app.icns) set_source_files_properties(${APP_ICON_ICNS} PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) target_sources(MyApp PRIVATE ${APP_ICON_ICNS})MACOSX_PACKAGE_LOCATION Resources这行是关键它告诉 CMake 把这个文件放进 bundle 的Resources目录而不是可执行文件旁边。少了这行CFBundleIconFile会指向一个不存在的路径图标自然出不来。5.2 Linux 桌面文件的 WM_CLASS 匹配Linux 上运行时的窗口图标靠setWindowIcon就够了但启动器里显示的那个图标要靠.desktop文件[Desktop Entry] TypeApplication Version1.0 NameMyApp CommentMyApp desktop client Exec/opt/myapp/MyApp Icon/opt/myapp/icons/app.png Terminalfalse CategoriesUtility;Development; StartupWMClassMyApp两个位置需要理解。Icon字段可以直接写绝对路径也可以只写一个名字这时系统会在/usr/share/icons和~/.local/share/icons里按主题查找所以想用名字的话需要按 hicolor 主题的目录规范摆放图标文件。StartupWMClass是很多人漏掉的一行它决定了窗口和桌面入口能否被正确关联。窗口管理器拿到的是窗口的WM_CLASS默认情况下这个值来自可执行文件名。如果你的可执行文件叫myapp而桌面文件名是MyApp.desktop两者大小写不一致时就可能匹配不上表现为从菜单启动后任务栏上出现两个图标一个是你自己的一个是默认的。这时候把StartupWMClass显式写成可执行文件名对应的类名就能解决。5.3 缓存问题改了图标但系统不认三个平台都有自己的图标缓存改了图标之后系统不刷新是常态。Windows 上最难缠图标缓存数据库在%localappdata%\IconCache.db新版本可能拆成多个文件放在Explorer目录下。简单的清法是拷走 exe 换个目录名再看或者运行ie4uinit.exe -show新版 Windows 该命令是ie4uinit.exe -ClearIconCache。实在不行就重启资源管理器进程。macOS 用的是 LaunchServices 数据库。最省事的刷新方式是touch MyApp.app更新 bundle 的修改时间让系统重新扫描或者用lsregister -f MyApp.app强制重新注册。Linux 下 GTK 系桌面环境有图标主题缓存改完后运行gtk-update-icon-cache如果只是把文件换了个位置通常注销重新登录就能生效不用重启。6. 图标不生效时的排查链路6.1 我自己的五步定位法踩过几次坑之后我总结了一套固定顺序从改完没变化开始五分钟内基本能定位第一步先确认坏的是哪一层。把 exe 单独拷到空目录用文件管理器看一眼再双击运行看任务栏。两层分别判断不要混着猜。第二步如果是文件管理器里没图标检查.rc有没有真的参与编译。方法是在构建目录里搜.res或.o文件或者看链接命令里有没有windres或rc.exe。这一步能立刻区分配置写了但没生效和配置根本没被读到。第三步如果是任务栏图标不对检查setWindowIcon的路径能不能解析。QFile::exists(:/icons/app.png)打一行日志qrc 没被编译进程序时它返回 false这是最常见的低级错误。CMake 工程要确认CMAKE_AUTORCC打开了或者 qrc 文件被加进了源文件列表。第四步如果代码和数据都没问题那就是缓存。按 5.3 节处理。第五步如果以上都对但就是不对检查是不是 Debug 和 Release 混用了。这个问题我在多配置生成器Visual Studio上遇到过Release 目录里的 exe 是好的结果我一直运行的 Debug 版本而.rc只在 Release 配置下被包含。CMake 里加资源文件时要注意$$CONFIG:Release:...这类生成器表达式有没有意外把资源排除掉。6.2 手动验证 exe 里到底嵌了什么Windows 下有一条不需要额外工具的验证路子用 PowerShellAdd-Type -AssemblyName System.Drawing $ico [System.Drawing.Icon]::ExtractAssociatedIcon(C:\path\to\MyApp.exe) $ico.ToBitmap().Save(C:\temp\extracted.png)把提取出来的图存下来打开看一眼就知道资源段里到底是什么。如果这里提取出来是 Windows 默认图标说明嵌入根本没成功不用再怀疑缓存。另外还可以看 PE 结构用objdump之类工具列资源节不过对日常排查来说有点重知道有这条路就行。6.3 打包发布后图标消失的几个典型场景场景一用 windeployqt 之后 exe 变了但图标没了。这不会发生。windeployqt只拷贝依赖库不动资源节。真出现这种情况多半是你前后运行的不是同一个 exe。场景二安装包里的程序没有图标。检查安装脚本里的快捷方式图标配置IconFile指向的路径是否在安装后仍然有效。用相对路径时容易指向安装临时目录装完就失效。场景三Linux 上从 AppImage 启动没图标。AppImage 内的.desktop文件需要额外声明Icon指向 AppImage 内部的绝对路径同时 AppImage 的启动器集成需要桌面环境写入对应的文件。如果你的程序只在命令行运行没问题从文件管理器双击启动才丢图标基本就是这个集成环节没做。场景四macOS 上签名后图标正常但不显示。这通常不是图标问题而是 Gatekeeper 拦截了未签名或不匹配签名的 bundle表现上会很像图标问题。先确认应用能正常启动再说。提示排查图标问题时尽量不要在 IDE 的运行按钮上做最终验证。构建一个干净目录下的独立副本从命令行或文件管理器启动这个环境才和用户拿到的一致。7. 几个让交付质感提升的小细节7.1 启动闪屏与关于对话框如果你的程序启动需要初始化数据、加载配置用户点开后会有一两秒的空白期。这时候用QSplashScreen挂上 logo 是很划算的投入#include QSplashScreen #include QTimer int main(int argc, char *argv[]) { QApplication app(argc, argv); QPixmap logo(:/icons/splash.png); QSplashScreen splash(logo); splash.show(); app.processEvents(); MainWindow w; w.show(); splash.finish(w); return app.exec(); }splash.finish(w)会自动等主窗口显示后再关闭闪屏比手动QTimer::singleShot更省心。闪屏图建议单独做一张横向比例、带产品名的版本不要直接拿方形图标放大那样很别扭。关于对话框是另一个体现细节的地方用图标加版本号组成一个简洁的界面QMessageBox::about(this, tr(关于 MyApp), tr(h3MyApp 1.2.0/h3 p桌面客户端/p p构建于 Qt %1/p).arg(QT_VERSION_STR));加上QT_VERSION_STR是个好习惯用户报问题时你能立刻知道他在跑哪个 Qt 版本。7.2 运行时切换图标和深色模式适配图标不需要是静态的。如果你的应用有工作中/待机中这类状态可以运行时替换任务栏图标让用户在缩略状态下也能看出状态void MainWindow::setBusyState(bool busy) { QApplication::setWindowIcon( busy ? QIcon(:/icons/app_busy.png) : QIcon(:/icons/app.png)); }需要注意的是在部分平台上任务栏图标不会实时刷新可能需要给每个顶层窗口单独调一次setWindowIcon。我一般会遍历QApplication::topLevelWidgets()逐个设置兼容性最好。深色模式这块是最近两年绕不开的。如果你的 logo 主体颜色偏深在深色主题的任务栏上会糊成一块几乎看不见。解决办法有两个一是给图标加一圈浅色描边或浅色底衬让它在中性背景下都能辨认二是准备一套浅色主题专用的图标监听QStyleHints::colorSchemeChanged信号后切换。加描边这个做法成本最低绝大多数场景下够用也是我在时间紧张时的默认选择。7.3 一套配置写法在三个平台落地最后把三平台的配置合在一份 qmake 工程里作为可以直接抄的模板QT widgets TARGET MyApp TEMPLATE app VERSION 1.2.0.0 win32 { RC_ICONS $$PWD/icons/app.ico QMAKE_TARGET_COMPANY Example Studio QMAKE_TARGET_PRODUCT MyApp QMAKE_TARGET_DESCRIPTION MyApp desktop client QMAKE_TARGET_COPYRIGHT Copyright (C) 2024 Example Studio } macx { ICON $$PWD/icons/app.icns QMAKE_INFO_PLIST $$PWD/Info.plist } unix:!macx { target.path /opt/myapp desktop.files $$PWD/myapp.desktop desktop.path /usr/share/applications icons.files $$PWD/icons/app.png icons.path /opt/myapp/icons INSTALLS target desktop icons } RESOURCES app.qrc SOURCES main.cpp mainwindow.cpp HEADERS mainwindow.h注意unix:!macx这个作用域写法它把 Linux 和其他类 Unix 系统单独圈出来避免和 macOS 的配置互相干扰。另外INSTALLS里的顺序没有语义意义但把target放第一个是习惯make install时会先装可执行文件。跨平台项目里我最容易忘记的一件事是app.ico和app.icns这两个文件最好都提交到版本库即使你在某个平台上暂时用不到。CI 在多平台上跑构建时缺少对应平台的图标文件会导致构建失败或者产出没有图标的安装包等到打包环节才发现就晚了。8. 图标这件事反映出的工程习惯做了几个跨平台桌面项目之后我越来越觉得图标问题是个很好的流程健康度指示器。一个团队如果在图标上反复出问题通常说明构建产物的验证环节是缺位的——每次都是本地跑一下觉得没问题就交出去了没有走干净环境下的独立产物验证这一步。而这一步恰恰是桌面软件最容易翻车的地方因为它涉及的环节横跨编译、打包、安装、运行四个阶段每个阶段都有自己独立的配置来源。我现在的做法是在 CI 里加一条很简单的检查构建完成后用脚本检查产物里的图标资源是否存在。Windows 上用前面提到的 PowerShell 提取方法macOS 上检查 bundle 里.icns文件是否存在且Info.plist的CFBundleIconFile字段非空。这两条检查加起来不到二十行脚本但拦下过好几次因为路径变量改错导致的图标丢失。花半小时写一次比每次交付后被人问你这程序怎么没图标要划算得多。至于图标资源本身用 1024 的 PNG 当唯一母版、用脚本生成所有派生格式、把生成脚本也提交进版本库这三点做到之后图标就不会再成为一个需要反复讨论的话题了。