
在 Windows 上写 Qt6 代码最后要交付一个能在 Linux 上跑起来的可执行文件——这个组合我前后做过三个项目每一次踩的坑都不重样。有人在 Windows 上用 Qt Creator 调试得好好的一把代码丢到 Linux 上编译就报一堆找不到头文件也有人辛苦交叉编译出来的二进制拷到目标机器上第一句话就是GLIBC_2.38 not found。说到底Windows,Linux,Qt6,编译这四个词凑在一起本质上是问一个问题编译这个动作到底该放在哪台机器上执行。Qt6 本身是跨平台的源码逻辑也基本不需要改真正折磨人的是工具链、系统库、路径规则、行尾符这些和业务无关的东西。这篇东西适合三类人看第一类是在 Windows 上做 Qt6 桌面开发、需要往 Linux 交付的同学第二类是做嵌入式 Qt6、要在 Windows 主机上产出 Linux 或 ARM 目标程序的工程师第三类是刚接触跨平台构建、想搞清楚编译产物为什么不能随便搬的新手。下面我按路线选型、环境搭建、工程组织、Kit 配置、真交叉编译、报错排查六个部分把我实际跑通的方案和踩过的坑完整摊开讲。1. 把需求翻译成技术问题为什么 Linux 版不能随手就编出来1.1 编译产物的水土不服到底从哪来很多人第一次接触这个需求时的直觉是Qt6 不是跨平台框架吗我在 Windows 上编出来的 exe 改个后缀不就能在 Linux 上跑了这个误区必须先把原理讲清楚。C 编译出来的可执行文件并不是通用字节码它是直接绑定到操作系统 ABI 上的机器码加动态链接描述。Windows 用的是 PE/COFF 文件格式Linux 用的是 ELFWindows 的系统调用走 ntdll.dll 和 kernel32.dllLinux 走 glibc 封装的 syscall连 C 标准库的实现都不一样——Windows 上 MSVC 的std::string内存布局和 GCC 的 libstdc 就不兼容。所以哪怕 Qt6 源码只写一份最终产物也必须是在 Linux 上、用 Linux 工具链、链接 Linux 版 Qt 库编出来的。这就是为什么不能直接在 Windows 上双击编译出 Linux 版本也是后面所有方案分叉的根源。理解了这一点剩下的问题就变成一道很朴素的工程题我们要么把编译动作搬到 Linux 环境里要么在 Windows 上搭一套能生成 ELF 的交叉工具链。这两条大方向各自又派生出若干变种选哪条取决于你的目标机架构、交付频率、团队习惯和调试需求。我见过不少团队一开始图省事随手选了一条做到后期发现调试断点打不上、库依赖收不干净推倒重来代价比一开始多花两天搭环境大得多。1.2 四条主流路线横向对比下面这张表是我自己在几个项目里实际用过或评估过的四条路线把关键维度摊出来对比路线编译发生在哪目标架构支持首次搭建成本调试体验适合场景WSL2 内原生构建WSL2 里的 Linuxx86_64ARM 需额外配置低半天好可本地 gdb桌面应用交付 LinuxWindows Qt Creator 远程 Linux 设备Windows 上交叉编译取决于工具链高两三天中等需远程 gdb有独立 Linux 目标机虚拟机 共享目录虚拟机里的 Linuxx86_64中一天好需要完整桌面环境Docker 容器构建容器内 Linux多架构可配中一天差命令行为主CI 流水线、批量构建WSL2 路线的最大优势是编译环境和运行环境是同一个你在 WSL 里编出来的东西天然能在 WSL 里跑验证成本接近零。代价是 ARM 目标机需要额外装qemu-user-static或者用交叉工具链而且 WSL 的文件系统在/mnt/c下的性能很差工程必须放在 WSL 自己的 ext4 分区里。虚拟机路线和 WSL2 思路类似只是隔离更彻底好处是可以跑带完整桌面环境的发行版坏处是内存和磁盘占用明显更大共享文件夹的 I/O 性能同样是瓶颈。1.3 我按什么标准决定走哪条我的判断标准很简单按优先级排三条目标机和你开发机是不是同一架构、交付是不是要进流水线、你有没有远程调试的硬需求。如果目标就是普通的 x86_64 Linux 服务器或桌面开发机是 Windows那 WSL2 基本是唯一的最优解别纠结。如果你手上是一块 ARM 开发板Windows 主机要直接出 ARM 二进制那就只能走真交叉编译WSL2 帮不上忙但如果这块板子能连网、能跑 SSH我更推荐把构建放到一台 Linux 构建机上Windows 上装个 Qt Creator 连过去可维护性高得多。如果交付要进 CIDocker 是绕不开的本地开发用什么方案无所谓流水线里一定是一份 Dockerfile 定版本。这里有个容易被忽略的坑很多团队一开始用 Windows 上的 Qt 在线安装器装了 Qt6然后在 WSL 里又装了一份系统包管理器版本的 Qt6两边版本号差了一截写出来的 CMakeLists 里find_package(Qt6 6.5 REQUIRED)在 Linux 侧直接失败。所以定版本这件事要在动手之前就定死后面所有配置都围绕这个版本展开。2. 底座搭建Windows 侧与 Linux 侧各自要准备什么2.1 Windows 侧该装什么版本怎么对齐Windows 这边的角色是代码编辑和版本管理不是编译。需要装的东西其实不多Qt Creator用较新的版本Qt Creator 14 之后都比较稳它同时充当编辑器、CMake 前端和远程部署工具。如果你的路线是 WSL2 里跑 Linux 版 Qt Creator那 Windows 侧这个可以不装直接装 VS Code 加 Remote-WSL 扩展也够用。CMake从官网下 Windows 版安装包注意勾选Add to PATH。装完在 PowerShell 里cmake --version能出结果就行。版本建议 3.22 以上因为 Qt 6.8 明确要求 CMake 3.22。Git for Windows不只是版本控制它自带的 Git Bash 和 OpenSSH 客户端是后面配远程设备的关键。安装时记得选Use OpenSSH而不是Use bundled plink否则 Qt Creator 调用 ssh 时行为和命令行不一致。SSH 客户端Windows 10 1809 之后系统自带ssh.exe一般不用额外装。用ssh -V验证。版本对齐这块有个经验可以抄把 Windows 侧和 Linux 侧的 CMake 大版本保持一致比如都用 3.28。CMake 生成器在不同大版本之间偶尔会有行为差异尤其是涉及FetchContent和find_package的策略变化。我吃过一次亏Windows 上用 CMake 3.30 生成的构建树Linux 上用 3.16 去重新配置FetchContent的依赖解析直接炸了。2.2 WSL2 的安装与几个必须改的默认项WSL2 的安装现在一条命令搞定管理员权限的 PowerShell 里执行wsl --install -d Ubuntu-24.04装完重启第一次进系统会让你建用户名密码。接下来几个默认配置建议立刻改掉否则后面会反复踩第一是开启 systemd。Ubuntu 24.04 默认在 WSL 里已经开了但如果你用的是 22.04需要在/etc/wsl.conf里加[boot] systemdtrue [interop] appendWindowsPathfalseappendWindowsPathfalse这一条很关键。默认情况下 WSL 会把 Windows 的 PATH 拼进 Linux 的 PATH这导致你在 WSL 里敲cmake时实际执行的是 Windows 版 cmake.exe编出来的东西是 PE 格式。这个坑非常隐蔽表现是编译通过了但生成的是 .exe。关掉之后 WSL 里只会用 Linux 自己的工具链。第二是把工程目录放到 WSL 内部。/mnt/c/...走的是 9P 文件协议跨系统调用大工程 CMake 配置一次能等好几分钟编译时更慢。把代码放到~/projects/下面性能差别是数量级的。如果你习惯在 Windows 侧用 IDE 打开代码用 VS Code 的 Remote-WSL 连过去编辑文件实际还在 ext4 上。第三是确认 WSLg 可用。Windows 11 和升级过的 Windows 10 自带 WSLg也就是 GUI 应用能直接把窗口显示到 Windows 桌面上不需要额外装 X Server。验证方法是在 WSL 里装个x11-apps跑xeyes能弹窗就说明通了。这个能力对 Qt6 开发太重要了意味着你能在 WSL 里直接跑起带界面的程序看效果不用每改一次就部署到另一台机器上。2.3 Linux 侧 Qt6 的三种安装方式怎么选这是整个环境搭建里最需要动脑子的地方三条路差别很大方式命令或途径版本优点缺点发行版包管理器sudo apt install qt6-base-dev跟随发行版最快依赖自动解决版本偏旧官方在线安装器aqt install-qt linux desktop 6.8.0 gcc_64可指定任意版本版本可控和 Windows 侧对齐需下载几个 GB源码编译configurecmake --build任意可定制完全可控首次编译几小时包管理器这条路的版本情况Ubuntu 22.04 给的是 Qt 6.2.4Ubuntu 24.04 给的是 Qt 6.4.2。如果你的代码用到了 Qt 6.5 才引入的 API比如qt_standard_project_setup()之后的一些新宏那就必须走第二条路。包管理器的优点是依赖完整qt6-base-dev会把libgl1-mesa-dev、libxkbcommon-dev这些底层依赖一起装好你少操很多心。官方在线安装器这条路我推荐用aqtinstall这个 Python 工具比图形化安装器好用得多适合写进脚本python3 -m pip install aqtinstall # 先查询有哪些可用版本 aqt list-qt linux desktop --arch 6.8.0 # 安装 6.8.0 的 gcc_64 版本 aqt install-qt linux desktop 6.8.0 linux_gcc_64 -O /opt/Qt装完之后 Qt 在/opt/Qt/6.8.0/gcc_64qmake在bin/qmakeCMake 配置文件在lib/cmake。这个布局和 Windows 版在线安装器是一致的好处是你在 CMakeLists 里写CMAKE_PREFIX_PATH时两边写法完全一样心理负担小。源码编译这条路我一般只在两个场景用一是需要裁剪模块比如只要 Core 和 Gui不要 WebEngine二是要给特定架构做交叉编译。源码编译的 configure 参数后面第 5 节会详细讲。2.4 三分钟验证环境是否真的可用装完不要急着写代码先用最小工程验证一遍能省掉后面大量到底是环境问题还是代码问题的扯皮。建一个临时目录写一个最小的 CMakeListscmake_minimum_required(VERSION 3.22) project(SmokeTest LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) find_package(Qt6 REQUIRED COMPONENTS Core) add_executable(smoke main.cpp) target_link_libraries(smoke PRIVATE Qt6::Core)main.cpp里就打印一行qVersion()。然后cmake -S . -B build -DCMAKE_PREFIX_PATH/opt/Qt/6.8.0/gcc_64 cmake --build build ./build/smoke输出的版本号和你预期一致说明 Qt 库、CMake、编译器三件套都正常。这一步还有个额外好处它会生成一份CMakeCache.txt里面记录了这个环境下所有自动探测到的路径后面配置 Qt Creator 的 Kit 时可以直接对着这份缓存抄参数。3. 一套源码两处编译Qt6 工程的目录组织与 CMake 写法3.1 目录结构怎么设计才不别扭跨平台工程最容易犯的错是把平台相关代码散落在各处最后没人说得清哪个文件是给谁的。我的做法是固定一个结构让平台差异收拢到少数几个位置demo-app/ ├── CMakeLists.txt ├── cmake/ │ ├── toolchain-linux-x86_64.cmake │ └── toolchain-linux-aarch64.cmake ├── src/ │ ├── main.cpp │ ├── mainwindow.h │ ├── mainwindow.cpp │ └── platform/ │ ├── paths_win.cpp │ ├── paths_linux.cpp │ └── paths.h ├── resources/ │ └── app.qrc └── scripts/ ├── build-linux.sh └── deploy.sh关键在于src/platform/这个目录所有真正有平台差异的逻辑都放这里头文件paths.h里只声明统一接口两个.cpp各自实现。CMakeLists 里根据CMAKE_SYSTEM_NAME决定编译哪一个。这样代码主体部分完全不需要#ifdef可读性好很多。还有一个细节值得提前处理Qt6 的资源文件路径大小写。Windows 文件系统不区分大小写Linux 严格区分。resources/app.qrc里如果写成Images/logo.png而实际目录叫imagesWindows 上跑得好好的Linux 上就是一张白图。这个错误编译期不报运行期静默失败排查起来很费时间。我的习惯是所有资源引用一律小写文件名也全小写加下划线。3.2 CMakeLists 的关键片段与逐行解释下面这份是实际在用的骨架我逐块说明为什么这么写cmake_minimum_required(VERSION 3.22) project(DemoApp VERSION 1.0.0 DESCRIPTION Cross platform Qt6 demo LANGUAGES CXX ) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) find_package(Qt6 6.5 REQUIRED COMPONENTS Core Gui Widgets Network) qt_standard_project_setup() qt_add_executable(DemoApp src/main.cpp src/mainwindow.cpp src/mainwindow.h resources/app.qrc ) target_include_directories(DemoApp PRIVATE src) if(WIN32) target_sources(DemoApp PRIVATE src/platform/paths_win.cpp) set_target_properties(DemoApp PROPERTIES WIN32_EXECUTABLE ON) elseif(UNIX AND NOT APPLE) target_sources(DemoApp PRIVATE src/platform/paths_linux.cpp) set_target_properties(DemoApp PROPERTIES INSTALL_RPATH $ORIGIN/../lib ) endif() target_link_libraries(DemoApp PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network )几个点值得展开。cmake_minimum_required(VERSION 3.22)不是随便写的前面提过 Qt 6.8 的 CMake 集成脚本里用到了 3.22 引入的能力写低了在某些模块上会报策略警告甚至直接失败。qt_standard_project_setup()是 Qt 6.3 引入的宏它会自动设置CMAKE_AUTOMOC、CMAKE_AUTOUIC这些并且处理一些平台相关的默认值。用它可以少写五六行但要注意它是 6.3 之后才有的如果你的项目要兼容 6.2 LTS得手动写那几行set(CMAKE_AUTOMOC ON)。INSTALL_RPATH $ORIGIN/../lib这一句是给 Linux 部署用的。$ORIGIN是 ELF 里的一个特殊标记表示可执行文件自己所在的目录。这样设置之后程序启动时会自动在可执行文件同级的../lib目录下找动态库不需要用户手动设LD_LIBRARY_PATH。这个技巧在打包分发时非常实用Windows 上对应的概念是没有的Windows 默认就在 exe 同目录找 DLL所以很多人第一次做 Linux 分发时会卡在这儿。3.3 平台差异代码怎么写才干净paths.h只放接口#pragma once #include QString namespace platform { QString configDir(); QString logDir(); bool ensureDir(const QString path); }Linux 实现里用 XDG 规范#include paths.h #include QStandardPaths #include QDir namespace platform { QString configDir() { return QStandardPaths::writableLocation( QStandardPaths::AppConfigLocation); } QString logDir() { return QStandardPaths::writableLocation( QStandardPaths::AppDataLocation) /logs; } bool ensureDir(const QString path) { return QDir().mkpath(path); } }其实 Qt 的QStandardPaths本身就跨平台这个例子是为了演示结构真实项目里更多的是这些场景调用系统 API、读写注册表、处理路径分隔符、平台特有的进程管理。原则就是接口统一、实现分家、CMake 按平台选源文件。不要用#ifdef Q_OS_WIN把实现塞在一个文件里时间长了那个文件会变成没人敢改的怪物。3.4 构建脚本把重复劳动固化成一条命令跨平台开发最烦的就是每次都要记一堆参数。写个脚本固化下来效率提升非常明显#!/usr/bin/env bash set -euo pipefail QT_ROOT${QT_ROOT:-/opt/Qt/6.8.0/gcc_64} BUILD_DIR${BUILD_DIR:-build-linux} BUILD_TYPE${BUILD_TYPE:-Release} cmake -S . -B $BUILD_DIR \ -G Ninja \ -DCMAKE_BUILD_TYPE$BUILD_TYPE \ -DCMAKE_PREFIX_PATH$QT_ROOT \ -DCMAKE_EXPORT_COMPILE_COMMANDSON cmake --build $BUILD_DIR -j $(nproc) echo artifact: $BUILD_DIR/DemoAppCMAKE_EXPORT_COMPILE_COMMANDSON这个开关很值得加它会生成compile_commands.json里面记录了每个源文件完整的编译命令。VS Code 的 clangd 插件、Qt Creator 的代码模型、还有各种静态分析工具都靠这个文件提供准确的补全和跳转。没有它跨平台工程里的头文件路径解析经常是错的。4. Qt Creator 连接远程 LinuxKit 配置与部署实操4.1 SSH 免密与设备创建如果你选择Windows 上跑 Qt Creator编译和运行在另一台 Linux 机器这条路第一步是把 SSH 打通。在 Windows 的 PowerShell 里ssh-keygen -t ed25519 -C qt-remote-dev -f $env:USERPROFILE\.ssh\qt_remote # 把公钥推送到目标机 type $env:USERPROFILE\.ssh\qt_remote.pub | ssh user192.168.1.50 mkdir -p ~/.ssh cat ~/.ssh/authorized_keys推完之后测试一下ssh -i ~/.ssh/qt_remote user192.168.1.50是不是直接进不需要输密码。免密是必要的因为 Qt Creator 在部署和运行阶段会频繁建立连接每次弹密码框基本没法用。如果目标机是 WSL2有个小技巧WSL 里启动 sshd监听高位端口比如 2222Windows 侧因为 WSL2 的 localhost 转发机制直接ssh -p 2222 localhost就能连进去不需要在 Windows 上做额外的端口映射配置。具体做法是在 WSL 里改/etc/ssh/sshd_config把Port改成 2222PasswordAuthentication按需打开然后sudo systemctl enable --now ssh。设备配好之后Qt Creator 里走 工具 - 选项 - 设备 - 添加 - Generic Linux Device填 IP、端口、用户名、密钥文件。点下一步它会自动跑一轮设备测试测的是 SSH 连通性、SFTP 可用性、以及远程目录是否可写。三项全绿才能继续。4.2 Kit 的四个关键字段怎么填Kit 是 Qt Creator 里最容易被填错的地方核心就四个字段字段填什么常见错误Compiler能生成 Linux ELF 的编译器误选 Windows 的 MSVC 或 MinGWQt Version目标平台的 qmake 路径填了 Windows 版的 qmakeCMake Tool本地 CMake 可执行文件版本低于项目要求Device上一步创建的远程设备设备名填成 IP这里有个必须说清楚的前提Qt Creator 的编译动作永远发生在本机也就是 Windows远程设备只负责部署和运行。所以 Compiler 字段必须填一个能在 Windows 上执行、但输出 ELF 的交叉编译器。如果你没有这样的工具链那这条路走不通得改用 WSL 内跑 Linux 版 Qt Creator 的方案。这一点很多教程讲得含糊导致不少人卡在我明明配了远程设备为什么还报编译器找不到。Qt Version 字段填的 qmake 路径是 Linux 目标机上那份 Qt 的路径不是 Windows 的。因为 CMake 在配置阶段需要找到目标平台的Qt6Config.cmake这个文件里记录的是 Linux 版的库路径。如果 Qt Creator 是通过 sysroot 方式做交叉编译那这个路径应该是 sysroot 里的路径。4.3 部署方式rsync 还是 SFTPQt Creator 的部署配置里有两种传输方式。SFTP 兼容性最好什么环境都能用rsync 快得多因为它只传有变化的部分而且支持断点续传和压缩。用 rsync 的前提是目标机上装了 rsync大多数发行版默认有精简系统可能要单独装。配置的时候在部署步骤里把传输方式改成 rsync命令行参数加上-avz --delete。--delete的作用是删除目标目录里本地已经不存在的文件避免旧版本残留文件造成改了代码但行为没变的诡异现象。实测感受是一个 30MB 左右的 Qt 应用首次部署两种方式差不多都要十几秒但增量部署时 rsync 通常一两秒就完事SFTP 还是得重新传一遍全部内容。迭代频率高的话这个差距会明显影响开发节奏。还有一个绕不开的问题是 Qt 库的部署。你的程序依赖libQt6Core.so.6、libQt6Gui.so.6这一堆如果目标机的系统 Qt 版本和你编译时用的不一致程序会直接起不来。Qt Creator 有个在部署时同步 Qt 库的选项勾上之后它会自动分析依赖并把需要的库一起传过去目录结构也会按 rpath 的约定摆好。这个功能很省事但要注意它会把你 Qt 安装目录下的库文件一起复制如果 Qt 安装目录很大首次传输会比较慢。4.4 WSLg 路线把 Linux 版 Qt Creator 直接跑在 Windows 桌面上如果你的目标就是 x86_64 Linux 桌面程序我个人更推荐这条路配置量比远程设备少一半调试体验也更好。思路是完全在 WSL 里装 Linux 版 Qt6 和 Linux 版 Qt Creator通过 WSLg 把 Qt Creator 的窗口显示到 Windows 桌面上。操作步骤sudo apt update sudo apt install -y qt6-base-dev qt6-tools-dev qt6-declarative-dev \ qtcreator cmake ninja-build gdb装完之后直接在 WSL 终端敲qtcreator几秒后 Windows 桌面上会弹出 Qt Creator 窗口。编辑器、构建、调试全在 Linux 环境里完成没有任何跨平台配置。代码放哪是个要决策的点。放~/projects性能最好但 Windows 侧访问不便放/mnt/c/projects两边都能直接打开但 CMake 配置和增量编译会慢不少。我的折中方案是代码放 WSL 内部日常编辑用 VS Code 的 Remote-WSL 连过去需要跑调试的时候切到 Qt Creator。两边的代码模型都指向同一份compile_commands.json补全体验一致。界面显示这块WSLg 支持的是 Wayland 协议转发Qt6 程序会自动选择xcb或wayland平台插件。如果遇到窗口弹不出来或者渲染异常可以在启动时显式指定QT_QPA_PLATFORMxcb试试。这种问题在 WSLg 早期版本上出现过几次新版本已经稳定很多。5. 真交叉编译Windows 主机直接产出 Linux 二进制5.1 工具链和 sysroot 从哪来交叉编译的本质是在 A 平台上运行编译器生成能在 B 平台执行的代码。这里的关键不只是编译器能生成另一种格式的机器码还需要一份目标平台的系统头文件和库也就是 sysroot。编译器需要知道目标系统的libc长什么样、pthread的接口签名是什么、系统调用怎么封装。在 Windows 上搭 Linux 交叉编译环境工具链有几条获取途径一是装 LLVM/Clang 的 Windows 版然后用--targetx86_64-linux-gnu指定目标三元组。Clang 本身就支持多目标不需要专门编译一份交叉版的 Clang。链接器用 LLVM 自带的lld它也支持生成 ELF。这条路的好处是只需下载一个官方安装包坏处是 sysroot 得自己准备。二是找现成的 Windows 版 GNU 交叉工具链。这类工具链存在但版本普遍偏旧而且维护活跃度参差不齐选之前要先确认 glibc 版本是否满足你的目标系统要求。sysroot 的准备方式是在一台 Linux 机器上虚拟机或 WSL 都行把目标系统的开发包装齐然后把/usr/include、/usr/lib/x86_64-linux-gnu、/lib/x86_64-linux-gnu这些目录打包拷贝到 Windows 的某个路径下。这一步有个讲究最好在一台目标系统同版本或更旧版本的 Linux 上做因为 glibc 遵循向后兼容原则——在旧系统上编译的程序能在新系统跑反过来不行。5.2 工具链文件怎么写CMake 的工具链文件通过-DCMAKE_TOOLCHAIN_FILE传入它必须在project()之前生效所以不能写在主 CMakeLists 里。一份能用的样子# cmake/toolchain-linux-x86_64.cmake set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(SYSROOT D:/toolchains/sysroot-ubuntu2204) set(CMAKE_SYSROOT ${SYSROOT}) set(CMAKE_C_COMPILER clang) set(CMAKE_CXX_COMPILER clang) set(CMAKE_C_COMPILER_TARGET x86_64-linux-gnu) set(CMAKE_CXX_COMPILER_TARGET x86_64-linux-gnu) set(CMAKE_EXE_LINKER_FLAGS_INIT -fuse-ldlld) set(CMAKE_SHARED_LINKER_FLAGS_INIT -fuse-ldlld) set(CMAKE_FIND_ROOT_PATH ${SYSROOT} D:/Qt/6.8.0/linux-x64) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)逐条解释一下几个关键点。CMAKE_SYSTEM_NAME设成 Linux 是告诉 CMake 目标平台是 Linux这会让它启用 Unix 相关的默认规则比如生成.so而不是.dll。CMAKE_SYSROOT指向 sysroot 根目录。CMAKE_C_COMPILER_TARGET是 Clang 特有的告诉它生成哪个三元组的代码。CMAKE_FIND_ROOT_PATH_MODE_*这一组是最容易配错的地方。默认值会把 Windows 上的库也搜进来结果就是你链接到了错误的库文件。设成ONLY表示只在 sysroot 里找NEVER表示这类东西仍在宿主系统找比如可执行程序因为你得运行 Windows 版的工具。PROGRAM设成NEVER是因为构建过程中需要运行一些工具比如moc、rcc这些必须是能在 Windows 上执行的版本而LIBRARY、INCLUDE、PACKAGE设成ONLY是为了保证链接的库和头文件都来自目标平台。5.3 Qt6 库怎么来自编还是借用交叉编译找不到 Linux 版的 Qt6 库这是这条路最大的门槛。Qt 官方在线安装器提供的 Linux 版是给 Linux 主机本地编译用的库文件本身就是给 x86_64 Linux 准备的 ELF 动态库。理论上如果你的目标也是 x86_64 Linux这些库可以直接拿来链接——因为链接器只关心符号和格式不关心这个库是在哪台机器上编译的。这算是一条取巧但确实能用的路线用 aqtinstall 下载 Linux 版 Qt6 到 Windows然后让工具链文件里的CMAKE_FIND_ROOT_PATH指向那个目录。不过这条路有几个前提下载的 Qt 版本要在较老的发行版上构建过Qt 官方通常用较老的构建环境来保证兼容性你的目标系统 glibc 要不低于它另外链接的时候需要拿齐所有-dev包提供的.so符号链接因为 Qt 安装目录下有时只有版本化后的.so.6没有不带版本号的.so链接器找不到。更正经的做法是从源码交叉编译 Qt。基本流程是./configure \ -prefix /opt/qt6-linux-x64 \ -release \ -nomake examples \ -nomake tests \ -skip qtwebengine \ -qt-host-path /path/to/host/qt6 \ -device-option CROSS_COMPILEx86_64-linux-gnu- \ -sysroot /path/to/sysroot这里-qt-host-path是必须的。Qt6 的构建系统需要一些在构建过程中运行的工具比如moc、rcc、qmlcachegen这些必须是宿主平台Windows能执行的原生程序。所以你得先在 Windows 上有一份 Windows 版的 Qt6把这些工具提供出来。-skip qtwebengine这个参数强烈建议加上。WebEngine 模块基于 Chromium交叉编译它需要处理一大堆依赖编译时间以小时计而且经常报错。除非你确定项目里用到了 WebEngine否则跳过。5.4 这条路的边界在哪交叉编译看着很酷但它有明显的适用边界。我的经验是只在必须的场景下才走这条路。必须的场景包括目标机是 ARM 架构且没有可用的 Linux 构建机生产环境严格禁止把源码传到其他机器需要为多个架构同时产出二进制。除此之外用 WSL2 或者远程 Linux 构建机都更省事。边界之外还应该考虑维护成本。交叉编译环境一旦搭建完后续每次 Qt 升级、每次系统库更新、每次团队新人入职都要重走一遍流程。而 WSL2 方案里环境就是几条 apt 命令写进一个setup.sh文件新人克隆仓库执行一次就完事。这个差距在团队规模变大之后会非常明显。6. 踩坑实录编译期与运行期的高频报错排查6.1 编译期常见报错与定位思路编译期报错相对好处理因为编译器会告诉你具体哪一行、哪个文件。下面是几个我遇到频率最高的fatal error: QApplication: No such file or directory。这通常是因为find_package(Qt6 ...)找到了一个错误的 Qt 版本或者找到的是 Qt5。检查CMakeCache.txt里的Qt6_DIR变量看它指向哪。如果指向了系统里另一个 Qt 安装用-DQt6_DIR/path/to/correct/lib/cmake/Qt6强制覆盖。undefined reference to vtable for Xxx。这是 Q_OBJECT 宏没有被 moc 处理导致的。检查两件事类定义所在的头文件有没有加进qt_add_executable的源文件列表里Qt6 的 CMake API 会自动处理头文件的 moc 扫描以及CMAKE_AUTOMOC是不是被某个子项目关掉了。file not found但文件明明存在。这种大多是路径大小写的问题。Windows 上#include MainWindow.h能找到mainwindow.hLinux 上直接失败。解决办法是在 Windows 侧做代码检查时开启大小写敏感检查或者干脆统一用全小写加下划线命名文件。CMake Error: The current CMakeCache.txt directory is different。这是把构建目录从一台机器拷到另一台或者从 Windows 路径拷到 Linux 路径后出现的问题。CMake 缓存里记录了绝对路径换位置就失效。解决办法是删掉整个构建目录重新配置别想着改缓存文件。6.2 运行期报错与依赖分析运行期的报错往往更折磨人因为程序可能在启动瞬间就退出什么日志都没有。按下面的顺序排查效率最高。第一步永远是lddldd ./DemoApp | grep not found这条命令会列出所有动态库依赖和它们的解析状态。如果输出里有not found说明缺库。缺的通常是 Qt 自己的库或者某个底层依赖。解决方式是找到对应的.so文件放到 rpath 能覆盖到的目录下。第二步是看 glibc 版本./DemoApp # 报错version GLIBC_2.38 not found # 或者 objdump -T ./DemoApp | grep GLIBC | sort -u如果报 glibc 版本不满足说明你的编译环境比目标环境新。唯一的解决方式是在一个不高于目标系统版本的 Linux 上重新编译。这也是为什么做交付时我强烈建议在 CI 里用固定版本的容器镜像比如统一用 Ubuntu 20.04 作为构建基础镜像它的 glibc 是 2.31能覆盖绝大多数现代 Linux 发行版。第三步是看 Qt 平台插件QT_DEBUG_PLUGINS1 ./DemoApp 21 | head -50这个环境变量会让 Qt 打印插件的加载过程。如果报Could not load the Qt platform plugin xcb说明platforms/libqxcb.so找不到或者它依赖的某个系统库找不到。很多人在服务器上跑 Qt 程序会遇到这个根因通常是服务器没装图形相关的底层库比如libxkbcommon-x11-0、libxcb-cursor0。6.3 常见问题速查表把上面这些经验整理成一张表遇到问题先查这里现象大概率原因处理方式找不到 Qt6Config.cmakeCMAKE_PREFIX_PATH 未设或版本不符加 -DCMAKE_PREFIX_PATH检查所需版本号编译通过但生成 .exeWSL 里调用了 Windows 工具链关掉 appendWindowsPathvtable undefined referenceQ_OBJECT 未被 moc 处理头文件加入源列表检查 AUTOMOC启动报 libQt6Core.so.6 not found库路径未配置配 INSTALL_RPATH 或设 LD_LIBRARY_PATHGLIBC 版本不满足编译环境比目标环境新用旧版本系统或容器重新编译界面弹不出来平台插件缺失装 libxcb-cursor0用 QT_DEBUG_PLUGINS 排查部署后改了代码行为没变旧文件残留rsync 加 --delete资源图片不显示路径大小写不一致统一小写命名6.4 几条我踩过坑才总结出来的经验最后分享几条常规文档里不会写的东西。第一.sh脚本的行尾符一定要处理。Git 在 Windows 上默认会把文本文件的换行符转成 CRLF这导致你写好的部署脚本传到 Linux 上执行时报bad interpreter: /bin/bash^M。解决办法是在仓库根目录加一个.gitattributes*.sh text eollf *.cmake text eollf CMakeLists.txt text eollf这个配置一次配好全组受益比每次手动dos2unix靠谱得多。第二跨平台工程里的路径拼接不要用字符串加号。Windows 用反斜杠Linux 用正斜杠字符串硬拼迟早出问题。统一用QDir::cleanPath和QDir::separator或者干脆全用正斜杠——Qt 在 Windows 上也能正确处理正斜杠路径。第三调试符号的取舍。Debug 构建的二进制体积可能是 Release 的十倍以上部署到远程机器时传输很慢。我一般用 RelWithDebInfo 构建既有优化又保留符号出问题时用objdump或者gdb还能定位到具体行。如果最终要交付给客户再单独出一个剥离符号的 Release 版本。第四给每个 Qt 版本建一个独立目录。不要把所有版本装在一起然后靠环境变量切换那样迟早会出现今天跑得好好的明天就报错的情况。目录结构用/opt/Qt/6.8.0/gcc_64、/opt/Qt/6.5.3/gcc_64这种构建脚本里通过一个变量指定用哪个切换起来清晰可控。第五在 WSL 里开发时记得给 Qt Creator 的索引进程留足内存。WSL2 默认会占用宿主机最多一半的内存如果你的工程比较大代码模型索引加上编译过程容易把 WSL 的内存吃满表现是整个系统变卡。可以在 Windows 用户目录下的.wslconfig里显式限制[wsl2] memory12GB processors8 swap8GB具体数值按你机器的实际配置来定原则是给 Windows 留够别让 WSL 把内存全吃了。我自己做了几个项目之后现在的默认选择是桌面 Linux 交付一律走 WSL2 内原生构建加上 WSLg 显示代码放在 WSL 的 ext4 分区里用 VS Code Remote-WSL 编辑用 Bash 脚本做构建和部署只有在目标机是 ARM 且没法给它配一台 Linux 构建机的时候才去折腾交叉编译工具链而且尽量把交叉编译的环境做成一个可复现的脚本或者容器镜像而不是靠手工一步步敲命令。踩过的那些关于行尾符、大小写、glibc 版本的坑本质上都不是技术难题而是两个系统约定不同带来的认知税交一次就够了把它写进团队的环境搭建文档里下次直接抄。