ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

q2c实战:从qmake到CMake的Qt工程迁移指南

q2c实战:从qmake到CMake的Qt工程迁移指南 简介面向需要在 qmake 与 CMake 之间切换的 Qt/C 开发者q2c 以轻量命令行工具的形式实现 .pro 文件与 CMakeLists.txt 的双向转换避免手工维护两套构建配置的重复劳动。压缩包共 13 个文件以 6 个 C 源文件和 5 个头文件为主另有工程文件与说明文档整体仅 14KB源码涉及终端参数解析、配置读取、日志记录和核心转换逻辑工程文件可直接用于 Qt 环境构建说明文档则提供简明使用指引便于编译使用也适合研究构建系统转换原理。已有 3204 人学习/下载。资源在说明 qmake 与 CMake 差异的同时展示了 q2c 的实现思路与转换注意事项帮助读者理解两种构建系统的对应关系对迁移期 Qt 项目或想学习构建工具原理的开发者能明显减少改写和排错时间也可作为团队迁移构建系统的参考模板。1. q2c把 qmake 工程批量搬到 CMake省的不只是改两行配置q2c 是一个 qmake 与 CMake 之间的转换工具它解决的是 Qt 工程迁移里最枯燥的那段机械劳动几十个 .pro 文件重写成 CMakeLists.txt。手工写不是不行但面对 QT widgets、TARGET、SOURCES 这种固定套路写十遍以后谁都烦。q2c 把 .pro 解析一遍直接吐出一份可编译的 CMakeLists.txt省下的时间够你把遗留工程的 Q_ASSERT 都梳理一遍。它适合维护老旧 Qt 工程、准备切 CMake 的 C 开发者也适合刚接到一批 qmake 代码、需要快速评估工作量的同学。机器能干的活交给机器剩下的手工活才值得你花时间。2. 为什么迁 CMakeqmake 退场背后的现实以及 q2c 的转换原理2.1 Qt 6 不再宠 qmake迁移动机比你想的更实际Qt 6 发布以后官方把 CMake 定为推荐构建方式新模块的文档、示例、CI 模板全是 CMake 工程。qmake 还在但更新基本停滞Qt 6 里能用的新特性如果你还指望 qmake 自动帮你处理大概率落空。我见过太多团队是被这两件事逼着迁移的一是 CI 的构建脚本要统一成 CMake 工具链二是第三方库只提供 CMake 的 find_package 支持qmake 这边要手写一堆 LIBS 和 INCLUDEPATH 才能对上。Makefile 和 CMake 的区别本质是生成器与依赖关系的差别CMake 的跨平台配置能力在 Qt 这套体系里已经成了事实标准。cmake 使用教程里都在强调同一个观点不要直接改构建目录里生成的 Makefile要让 CMake 替你管理依赖和生成规则。这个观点在迁移语境下同样成立。qmake 工程对 CMake 来说是个黑匣子迁移的价值之一就是把隐式的路径和变量暴露成显式的 target_include_directories 和 target_compile_definitions让后续维护的人不用靠猜。转换工具的价值也在这里——它把你已知的 .pro 内容翻译成 CMake 能看懂的声明翻译完你仍然要读一遍但至少不用从零起草。2.2 转换原理qmake 关键字到 CMake 语句的映射q2c 的工作原理并不复杂把 .pro 文件按行拆分识别变量赋值和配置关键字再按一张映射表生成 CMake 语句。qmake 的语法是类 Makefile 的脚本语言变量即字符串条件块、函数调用都能出现在任意位置所以转换器做的其实是词法扫描加规则重写并不是执行整个 qmake 脚本。你在 .pro 里写什么它看到什么它不理解的会原样留下或者丢弃。下面这张表是 q2c 的核心映射逻辑也是你手工复查时该对照的清单qmake 写法CMake 对应说明TEMPLATE appadd_executable()生成可执行文件TEMPLATE libadd_library()静态库要加 STATIC动态库加 SHAREDTARGET demoproject(demo ...)工程名也用于目标名QT widgetsfind_package(Qt6 COMPONENTS Widgets)按 Qt 主版本映射组件CONFIG c17set(CMAKE_CXX_STANDARD 17)语言标准DEFINES FOO1target_compile_definitions(... FOO1)预定义宏INCLUDEPATH pathtarget_include_directories(... path)头文件目录LIBS -lfootarget_link_libraries(... foo)链接库SOURCES / HEADERSadd_executable 的源文件参数直接展开RESOURCES app.qrcAUTORCC 或 qt_add_resources资源文件注意看 RESOURCES 那一行。qmake 里 qrc 文件列出来就行CMake 下如果你开着 CMAKE_AUTORCC直接把它放进源文件列表也能编如果关掉 AUTORCC就得用 qt_add_resources 或者 qt6_add_resources 手动生成。q2c 通常按“把 qrc 加进源文件”处理这依赖 CMake 侧开了 AUTORCC后面避坑章节我会再提。另外 QT 是追加语义一行可以追加多个模块q2c 处理时会合并成 find_package 的一行列表。这里有个容易忽略的点同一个 .pro 里可能多处出现QT xxx转换器要按顺序收集完再统一生成如果它只处理了第一次出现的 QT 行后续追加的模块就会丢。拿到转换结果后逐个模块核对是基本功。2.3 转换边界哪些 .pro 写法 q2c 帮不上忙qmake 的脚本能力比很多人以为的强条件块和自定义函数是重灾区。比如项目里常见的这种写法greaterThan(QT_MAJOR_VERSION, 5) { QT quick3d } else { QT quick } CONFIG(debug, debug|release) { DEFINES ENABLE_DEBUG_LOG }这种结构 q2c 一般处理不了可能原样把条件文本带进 CMakeLists.txt或者直接忽略。原因在于转换器只做了关键字映射没有执行 qmake 的求值逻辑。我的建议是转换前先把 .pro 里明显的条件结构手动化简或者转换后在生成的 CMakeLists.txt 里用 ifQt 版本判断重写等价逻辑。$$PWD、$$TARGET、自定义函数 message() 这类运行期展开q2c 能识别的有限。$$PWD 在 qmake 里指向 .pro 所在目录CMake 里等价的是 CMAKE_CURRENT_SOURCE_DIR但 q2c 有时候会把变量展开成绝对路径硬编码进去检查阶段要重点看路径有没有被写死。转换边界这事我的态度很明确把工具当翻译不当决策者。它能覆盖八成常规语法剩下的两成是留给你的工作。3. 编译安装 q2c从源码包到命令行工具3.1 环境准备Qt 开发环境与编译器q2c 自己是 Qt 程序所以你得先有一个能用的 Qt 开发环境。我这边在 Linux 上用 Qt 6.5 编译的Windows 上用 Qt 5.15 MinGW 也编过老一点的 Qt 5.12 应该也可以没遇到接口上的硬依赖。前提是 qmake 要在 PATH 里编译器和 Qt 的套件要匹配——用 MinGW 的 Qt 就得配 MinGW 的编译器用 MSVC 的 Qt 就配 MSVC 命令行环境。很多人卡在第一步不是因为源码编不过而是 Qt 和编译器混搭报错信息看不懂。可以先确认一下环境qmake -v g --version cmake --version三条命令都正常输出版本号再往下走。Linux 上 cmake 如果没装用系统包管理器装一下就行发行版仓库里的版本基本都够用。Windows 上如果 qmake 不在 PATH把 Qt 安装目录下的 bin 文件夹加进系统 PATH注意别加错版本——机器上同时装了 Qt5 和 Qt6 的情况很常见编译 q2c 之前先确认你打算用哪个 Qt。q2c 自身对 Qt 版本要求不高但用哪个 qmake 编出来的程序运行时就需要哪个 Qt 的库。如果系统里同时有 Qt5 和 Qt6而你希望 q2c 在 Qt6 下跑编译前一定先qmake -v确认当前 PATH 里的是不是 Qt6 的 qmake。这个检查十秒钟能省掉后面一连串的 libQt5 和 libQt6 冲突。3.2 编译qmake 构建一个转 qmake 的工具解压 q2c 的源码包里面是标准的 qmake 工程根目录有 .pro 文件。有意思的是一个用来迁移 qmake 工程的工具自己还是用 qmake 构建的这正好说明 qmake 在存量代码里的渗透率有多深。Linux/macOS 上用这条路径cd q2c-src qmake q2c.pro make -j$(nproc)qmake 这一行负责把 .pro 转成 Makefile-j 参数让 make 并行编译nproc 是 CPU 核心数直接写 make -j8 也行。如果源码包同时带了 CMakeLists.txt也可以直接用 cmake 编但我个人更推荐 qmake 这条路——它本来就是处理 qmake 工程的用它的原生构建系统编它自己路径和依赖关系最不容易出错。Windows 下如果用的 MinGW 套件把 make 换成 mingw32-make。MSVC 环境下要先用 VS 的 “x64 Native Tools Command Prompt for VS” 打开命令行再执行 qmake 和 nmake否则 cl.exe 不在 PATH 里编译会报找不到编译器。Windows 上如果 qmake 生成的 Makefile 用的是 nmake 规则直接敲 nmake 就行不用 make。编译产物是当前目录下的 q2c 可执行文件Windows 下是 q2c.exe。这一步走完工具本体就已经能跑了。3.3 验证安装先跑 --help 再看退出码编译完先别急着扔给真实工程先跑一下帮助命令./q2c --help正常情况会打印用法说明包括输入 .pro 路径、输出 CMakeLists.txt 的位置、是否允许覆盖已有文件等参数。这一步有两个作用一是确认工具真的能跑二是拿到当前版本的参数名。不同小版本的参数缩写可能不一样后面写自动化脚本时要以实际的 --help 输出为准别拿网上搜到的老参数硬套。看退出码更直接./q2c --help echo $?退出码 0 说明正常非 0 说明某个环节断了。Windows 下对应的检查是echo %ERRORLEVEL%。如果在 Windows 下双击运行没反应多半是缺 Qt 的 DLL把 Qt 的 bin 目录加到 PATH 再跑。Linux 下如果提示找不到 libQt6Core.so是运行库路径没指到。有一个解法是编译时在 .pro 里加 QMAKE_RPATHDIR不过我一般直接设置 LD_LIBRARY_PATH 指向 Qt 安装目录的 lib 文件夹。这一步过了工具侧就没问题了可以进入实战环节。4. 实战把一个带资源文件和第三方库的 .pro 工程转成 CMake4.1 构造一个典型的 .pro 工程为了说清楚转换的输入输出我拿一个带 Qt 模块、宏定义、外部头文件、外部库和 qrc 资源的工程做例子。这种工程基本覆盖了转换器八成的工作场景比 hello world 有参考价值QT core gui widgets TARGET demoapp TEMPLATE app CONFIG c17 DEFINES APP_VERSION1.2.0 INCLUDEPATH /opt/3rdparty/include LIBS -L/opt/3rdparty/lib -lfoobar SOURCES main.cpp mainwindow.cpp HEADERS mainwindow.h RESOURCES app.qrc实际老工程可能比这个复杂得多有自定义函数、条件块、多个 subdirs但先用这个把流程走通再看边界。转换命令很简单./q2c demoapp.pro默认会在 .pro 同目录生成 CMakeLists.txt。如果想指定输出位置看 --help 里的参数常见做法是用 -o 指定输出路径覆盖已有文件一般有独立的开关我习惯先备份原来的 CMakeLists.txt 再开覆盖。命令行工具的通用习惯是 -f 强制覆盖但具体到 q2c 的哪个版本以你本机的帮助输出为准。4.2 生成的 CMakeLists.txt 长什么样转换完成后打开生成的 CMakeLists.txt内容大致是下面这样。注意不同版本输出格式有差异但核心结构基本一致cmake_minimum_required(VERSION 3.16) project(demoapp VERSION 1.2.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) add_executable(demoapp main.cpp mainwindow.cpp mainwindow.h app.qrc ) target_include_directories(demoapp PRIVATE /opt/3rdparty/include) target_compile_definitions(demoapp PRIVATE APP_VERSION1.2.0) target_link_libraries(demoapp PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets foobar)逐段看逻辑。find_package 那行对应 .pro 里的 QT 如果你的机器上只装了 Qt5这里报错很正常——把 Qt6 改成 Qt5 就能过或者通过 CMAKE_PREFIX_PATH 指定 Qt6 安装目录。add_executable 把 SOURCES 和 HEADERS 一起列进源文件这是 CMake 的习惯头文件不需要编译但会出现在 IDE 的目录树里。CMAKE_AUTOMOC 是关键项Qt 类的 Q_OBJECT 宏需要 moc 处理没有它链接时大概率报未定义的虚表错误。q2c 生成的这份文件里不一定有这两行 AUTOMOC/AUTORCC取决于版本缺了就手动加。target_include_directories 对应 INCLUDEPATH这里原样保留了绝对路径.pro 里写的是 /opt换机器肯定坏建议后面改成相对路径。target_link_libraries 里最后一个 foobar 对应 LIBS -lfoobarCMake 会去找名为 foobar 的库Windows 上的查找规则和 Linux 不一样这个放到避坑章节细说。4.3 用 CMake 构建验证转换结果生成的 CMakeLists.txt 能不能编是检验转换质量的唯一标准。构建命令cmake -S . -B build -DCMAKE_PREFIX_PATH/opt/Qt/6.5.3/gcc_64 cmake --build build -j$(nproc) ./build/demoappcmake -S 指定源码目录-B 指定构建目录这是 CMake 3.13 以后推荐的写法比 cd build cmake .. 干净。CMAKE_PREFIX_PATH 指向 Qt 安装目录find_package 会顺着这个路径找 Qt6Config.cmake。如果报CMake Error at .../CMakeDetermineCompilerId.cmake:9这是 CMake 探测编译器失败跟转换结果无关先查编译器环境。如果报找不到 Qt5Config.cmake 之类是 CMAKE_PREFIX_PATH 指错了或者指到了 Qt5 上。如果你习惯用 cmake-gui打开 build 目录也能看到同样的配置项但命令行这步已经足够。用 VSCode 打开转好的目录装好 C/C 和 CMake Tools 插件之后编辑并保存 CMakeLists.txt底部状态栏会出现 Kit 选择和 Configure 按钮。点 Configure 就等于执行了 cmake 配置阶段。状态栏没有 Configure 按钮多半是 CMakeLists.txt 还没被插件识别——先保存文件再等几秒插件一般会自动刷新。这不算什么高深问题但确实是新手问得最多的一个点。5. 避坑q2c 转换中最容易翻车的四个地方5.1 find_package 缺组件或多组件现象转换后执行 cmake 配置报CMake Error at .../lib/cmake/Qt6/Qt6Config.cmake找不到指定组件或者提示找不到 Qt6::Widgets 目标。原因q2c 对 QT 的模块名做的是简单字符串映射而 Qt 的模块名在不同主版本之间有分合。Qt6 里 Widgets、Network、Sql 这些组件都在但 Quick3D、Multimedia 这类模块如果 .pro 里写的是旧名字映射不到正确的组件名上。另一个常见情况是 .pro 里用了QT multimediawidgets这种 Qt5 风格写法Qt6 里被拆到不同组件里find_package 少列一个就找不到。解决打开生成的 CMakeLists.txt核对 find_package(Qt6 REQUIRED COMPONENTS ...) 那行的组件列表按目标机器上的 Qt 版本补齐。多列一两个组件不会出错但少列了链接阶段必然报错。如果机器上同时有 Qt5 和 Qt6 的包还要确认 CMAKE_PREFIX_PATH 指到了正确的那份。5.2 条件块和自定义函数变成裸文本现象生成的 CMakeLists.txt 里出现一行greaterThan(QT_MAJOR_VERSION, 5) {或者一个莫名其妙的FOO barCMake 解析直接报语法错误。原因q2c 不执行 qmake 的条件求值和函数展开它只认纯赋值语句。contains()、CONFIG(debug, debug|release)、自定义函数、message() 打印这些都可能原样输出或者被直接丢弃。这几乎是 qmake 转 CMake 最玄学的一类问题——工具本身没报错但生成的文件里留着半截 qmake 语法。解决转换前先把 .pro 里这类结构手动算出结果写死或者转换后用 CMake 的 if/endif 重写等价逻辑。比如原 .pro 里的版本分支在 CMakeLists.txt 里写成if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.21) target_link_libraries(demoapp PRIVATE Qt6::Quick3D) else() target_link_libraries(demoapp PRIVATE Qt6::Quick) endif()如果 .pro 里条件分支太多我的做法是先在高版本 Qt 下用 qmake -d 跑一遍。qmake -d 会把条件求值后的实际配置打印出来相当于给你一份展开结果照着那个改 CMakeLists.txt比逐行猜条件省心得多。这是我从一次转换大工程里总结出来的血泪经验。5.3 $$PWD 和绝对路径被硬编码现象生成的 target_include_directories 里出现/home/user/project/../include这种绝对路径换一台机器或者换用户目录就编译失败。原因qmake 的 $$PWD 指向 .pro 所在目录转换器有时候会把变量展开成绝对路径写死而不是保留相对关系。更隐蔽的是第三方库路径.pro 里写LIBS -L$$PWD/lib -lfoo的时候-L 后面的路径也可能被固化成绝对路径。源码放在 git 里但生成的 CMakeLists.txt 带着个人路径同事拉下来一编就炸。解决把 include 路径和链接库路径改成相对 CMAKE_CURRENT_SOURCE_DIR 的写法target_include_directories(demoapp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/include)库路径同理用 find_library 先定位再加到 target_link_libraries 里。检查时我一般对着 .pro 里所有 $$PWD 逐行搜凡是生成文件里出现绝对路径的地方全部标记出来改掉。这个检查不能省它决定了换机器之后工程还能不能编。5.4 链接库在 Windows 上的 import library 差异现象Linux 下转完能编能跑Windows 下报 LNK 2019 无法解析的外部符号或者直接找不到 .lib 文件。原因qmake 里LIBS -lfoobar在 Linux 环境下对应的是 libfoobar.soWindows 下对应的是 foobar.lib 或者 foobar.dll 的导入库。q2c 生成 target_link_libraries 时只是把库名 foobar 放进去CMake 在 Windows 上查找库的规则和 Linux 不一样路径没指清楚就找不到。这不是工具本身的问题是 qmake 和 CMake 对库处理方式的天然差异但确实卡人。解决Windows 上显式写库的全路径或者用 find_library 让 CMake 自己定位find_library(FOOBAR_LIB foobar HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/lib) target_link_libraries(demoapp PRIVATE ${FOOBAR_LIB})把库搜索细节交给 CMake 处理路径变了改一处就行。遇到 LNK 2019 的时候先确认库文件本身存在再确认位数和运行时库设置匹配——这个和转换工具无关是 Windows 链接的老问题。6. 收尾技巧一份 CMakePresets.json 和一页验证清单6.1 用 CMakePresets 固化 Qt 路径和构建参数转换完成只是开始。真实团队里每个人机器上的 Qt 路径不一样如果让每个人手动敲-DCMAKE_PREFIX_PATH...一定会有人敲错。把参数写进 CMakePresets.json 是好习惯这也是 CMake 官方推荐的工程配置方式{ version: 6, configurePresets: [ { name: dev, generator: Ninja, binaryDir: ${sourceDir}/build-${presetName}, cacheVariables: { CMAKE_PREFIX_PATH: /opt/Qt/6.5.3/gcc_64, CMAKE_BUILD_TYPE: Debug } } ], buildPresets: [ { name: dev, configurePreset: dev } ] }用了 preset 之后构建命令会短一大截cmake --preset dev cmake --build --preset devbinaryDir 里的${presetName}会自动展开成 dev不同 preset 的构建目录不互相污染。CMAKE_PREFIX_PATH 指到 Qt 安装目录find_package 就顺着这个路径去找 Qt6Config.cmake。团队里有人用 Qt5 有人用 Qt6就在 presets 里多定义两个配置切换成本比改命令行参数低很多。6.2 转换结果的快速验证清单每转完一个工程我在提交之前都会过一遍这张表检查项看什么不对怎么办find_package 组件列表是否覆盖 .pro 里全部 QT 模块按目标 Qt 版本补组件AUTOMOC / AUTORCC有无 Q_OBJECT 宏、qrc 文件在 CMakeLists 顶部打开两个开关头文件与库路径是不是绝对路径或 $$PWD 残留改为相对 CMAKE_CURRENT_SOURCE_DIR条件块有无 qmake 语法残留用 CMake 的 if 重写空构建目录一次通过从零配置到链接完成按上面几项逐个排除说个我的习惯以前有一次转完没开 AUTOMOC链接报错追了一下午才发现是 Q_OBJECT 没处理。从那以后每个工程转完我都先打开生成的 CMakeLists.txt把 AUTOMOC、AUTORCC、find_package 这几行扫一遍确认无误才交出去。工具能帮你省掉重复劳动但最后一道检查还是得人来。希望帮到你。本文还有配套的精品资源点击获取
返回列表