恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
q2c:Qt工程中.pro与CMakeLists.txt互转的构建迁移指南
首页
资讯中心
/
q2c:Qt工程中.pro与CMakeLists.txt互转的构建迁移指南
q2c:Qt工程中.pro与CMakeLists.txt互转的构建迁移指南
发布时间:2026/9/29 17:14:45
简介q2c是一款面向Qt开发者的构建系统转换工具能够在qmake的.pro项目文件与cmake的CMakeLists.txt之间进行双向转换有效解决工程体系切换时反复编写构建配置的痛点特别适合需要维护多构建系统的中高级开发者。资源包共收录13个文件以C源码为主体包含6个实现文件、5个头文件另有1个Qt工程文件与1份README说明整体压缩后仅14KB代码量精简便于快速阅读与二次开发。目前该资源已有3204人学习下载覆盖从qmake迁移到cmake、或从cmake回迁qmake的常见场景。通过研读源码可以了解项目文件解析流程、关键字映射逻辑、配置生成策略以及日志输出等模块设计也能直观对比qmake与cmake在语法简洁性、跨平台扩展性和社区生态上的差异为自研构建辅助工具或工程自动化迁移提供有价值的参考。1. q2c 是什么给每天改 .pro 的 Qt 开发者一颗后悔药当你接手一份五年没人动的 Qt 代码打开目录发现里面躺着一堆 .pro / .pri而 CI 和新同事只认 CMake 时第一个念头绝对是全网找一个能把 .pro 直接翻译成 CMakeLists.txt 的工具。q2c 就是干这件事的它读 qmake 项目按变量展开成 CMake target再补上 find_package 和 qt_standard_project_setup 这类样板能省掉大半手动重写。反向它也能做把 CMake 目标还原成 qmake 变量适合需要同时维护两套构建的过渡期。这篇笔记适合已经把 Qt 工程跑起来、但没系统看过 CMake 写法的人也适合准备批量迁移多目录工程的人。2. 先把 CMake 和 qmake 的差异讲明白q2c 的地基在哪很多人拿 q2c 转换完发现“编译不过”不是工具不行而是根本没搞清楚 .pro 和 CMakeLists.txt 是两套不同的心智模型。qmake 靠全局变量带条件分支来拼 MakefileCMake 靠 target 和属性来组织构建系统。q2c 夹在中间并不是逐行翻译而是先理解 .pro 里每个变量最后会作用到哪个目标再重新生成 CMake 语法。所以你得先看明白 qmake 和 CMake 各自在描述什么不然连报错都不知道去哪找。2.1 qmake 的 .pro 在描述什么变量和时间点一个典型 Qt Widgets 工程的 .pro 只有十几行但每一行都有特殊的时间点语义。下面是我做迁移常会看到的单文件项目QT core gui greaterThan(QT_MAJOR_VERSION, 4): QT widgets TEMPLATE app TARGET myapp CONFIG c17 SOURCES main.cpp mainwindow.cpp HEADERS mainwindow.h INCLUDEPATH $$PWD/3rdparty/include LIBS -L$$PWD/3rdparty/lib -lmy3rd RESOURCES app.qrcqmake 的处理逻辑是从上往下跑脚本QT变量控制调用 Qt 的哪个模块TEMPLATE决定生成 app 还是 libCONFIG里面塞编译选项INCLUDEPATH和LIBS会在生成编译器命令行时被展开。这里最容易被 q2c 搞混的是greaterThan这一行它是条件赋值q2c 不能像读普通赋值一样直接忽略。qmake 里的$$PWD会在解析期被替换成当前 .pro 所在目录所以INCLUDEPATH拿到的是绝对路径而 CMake 里更推荐用${CMAKE_CURRENT_SOURCE_DIR}这种.source 目录变量。q2c 干的第一个重要工作就是把$$PWD、$$_PRO_FILE_PWD_这类内置变量全部映射到 CMake 的目录变量否则转出来的 CMakeLists.txt 在别的机器上就是一堆坏路径。2.2 CMake 的 target 与作用域q2c 真正要生成的东西CMakeLists.txt 和 .pro 最本质的区别是CMake 以 target 为中心一个 target 拥有自己的源文件、头文件目录、编译选项和链接库而 qmake 中这些信息大多是全局的写在一个变量里就所有源文件都吃到。q2c 要做的不是机械地s/INCLUDEPATH/target_include_directories/g而是把全局变量归拢到某一个 target 名下。qmake 概念变量/写法CMake 对应物产物类型TEMPLATE appadd_executable / add_libraryQt 模块QT core guifind_package target_link_libraries头文件路径INCLUDEPATHtarget_include_directories宏定义DEFINEStarget_compile_definitions编译选项QMAKE_CXXFLAGStarget_compile_options资源RESOURCESqt_add_resources子项目SUBDIRSadd_subdirectory理解这张表后你会明白 q2c 转换出来的 CMakeLists.txt 里为什么大量的target_link_libraries后面跟着PRIVATE。因为 qmake 的LIBS在传统写法中是全局终端链接选项但 CMake 中最好放到 target 的私有关联否则写进接口会让所有依赖这个库的子项目也继承无意义的链接项。另一个容易踩的点是作用域。qmake 里INCLUDEPATH xxx写在子目录的 .pro 里只影响当前子目录而 CMake 中如果写成include_directories(xxx)会影响之后所有 target。q2c 一般会尽量写成target_include_directories但在处理缺失 target 的边界情况时也会退化成目录级命令这就是后续需要人工检查的地方。2.3 q2c 的转换模型它做的是“语义映射”不是文本翻译如果 q2c 只是把 .pro 的每一行翻译成 CMake 语法那它根本没法用因为 qmake 的QT widgets在 CMake 里要变成两步先find_package(Qt6 COMPONENTS Widgets REQUIRED)再target_link_libraries(... Qt6::Widgets)。这种一对多映射只有解析了变量语义才做得到。更极端的例子是CONFIG console。在 qmake 中它表示 Windows 下生成的 exe 要带控制台子系统但 CMake 里对应的是set_target_properties(... PROPERTIES WIN32_EXECUTABLE FALSE)如果手写这行几乎不会有人记得。q2c 会把这类常见配置归类成一张小表转换时直接替换成目标属性。但 q2c 也不是万灵药。它看到system()、message()、custom target这类带副作用的 qmake 函数时只能原样输出为注释或警告。所以你转换出来的 CMakeLists.txt 第一眼往往很干净但那些被工具“吞掉”的条件脚本才是后面翻车的根源。把 q2c 当成帮你搭主框架的脚手架而不是替你写全部构建逻辑的 AI使用的心态才正确。3. 用 q2c 把 .pro 转成 CMakeLists.txt最小可复现流程单看项目介绍容易陷入黑匣子焦虑直接把一个 .pro 丢进去看输出才是最直接的。这章从安装到跑通给一条可以照着敲的命令链路。我用的是 q2c 最普通的命令行入口如果你手上的版本带了图形界面命令名以q2c --help里写的为准。3.1 拿 q2c 可执行文件先解决“没有包”的问题q2c 不是 Qt 官方开箱随附的工具常见发行形态是源码仓库加一个 pip 入口。我在新机器上一般先试 pippip install q2c q2c --help如果 pip 源里没有就拉源码自己跑git clone 仓库地址 q2c cd q2c python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install -r requirements.txt python -m q2c --help这里的关键是不要直接用系统 Python 硬装依赖。q2c 的依赖里常见有lxml或pyyaml这类底层库在系统环境里装容易和别的工具冲突。我第一次就是图省事直接pip install --user结果跑起来报依赖找不到后来老老实实开 venv 就好了。参数方面--help主要看入口是q2c还是python -m q2c以及输入输出参数名。我会顺手跑一遍q2c --version确认安装目录避免后面在 CI 里调了个空壳命令。拿到 help 后下一步就可以拿一个小 .pro 做冒烟测试。3.2 从单 .pro 生成 CMakeLists命令、参数和生成结果单文件工程是最好验证路径的。我建议先用一个只有两三个源文件的小工程试而不要一上来就整个 SUBDIRS 工程不然输出几百行 CMake你根本看不出问题在哪。q2c convert --pro app.pro --cmake CMakeLists.txt这条命令的意思是读取app.pro把解析出来的语义映射成CMakeLists.txt并写到当前目录。如果输出文件已存在有些版本会直接覆盖所以建议提前备份原文件也可以看--help里有无--preserve-existing或--diff这类开关。落地后的 CMakeLists.txt 大概长这样cmake_minimum_required(VERSION 3.16) project(myapp VERSION 1.0 LANGUAGES CXX) qt_standard_project_setup() find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED) qt_add_executable(myapp main.cpp mainwindow.cpp mainwindow.h ) target_include_directories(myapp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/include ) target_link_libraries(myapp PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets ) qt_add_resources(myapp app PREFIX / FILES app.qrc )逻辑说明qt_standard_project_setup()会设置 Qt 项目常见默认属性比如自动处理 moc、uic 和 rccfind_package负责定位 Qt 模块qt_add_executable创建的 target 名来自TARGET myapp那个myapp就是后续 include/link 都绑定的目标名qt_add_resources后面的app是资源集合名取自 .pro 的同名资源文件。字段参数里有个坑q2c 会把RESOURCES app.qrc展开成里面所有文件如果你原来在 .pro 里用通配符形式写资源输出会变成一大串文件列表这不算错但会导致 CMake 部分改动后需要重新放进 qrc 文件的新资源。3.3 转完立刻要做的三个检查点target 名称、Qt 组件、路径变量第一步先查 target 名。qmake 的TARGET经常写小写字母而 CMake 的 target 名对大小写敏感但这只是风格问题真正要命的是 target 名里带空格或.在target_link_libraries里需要加引号。我看到输出里的 target 名和 exe 文件名不一致时会加一行set_target_properties(myapp PROPERTIES OUTPUT_NAME MyApp)第二步查 Qt 组件。很多老项目的 .pro 里写的QT qml quick实际用了 Quick、Qml 两个模块q2c 如果只按空格拆有时会漏掉widgets的补全条件。检查方法是看find_package行和项目里 include 的 Qt 头文件是否对应宁可多列一个组件也不要少列导致链接期翻车。第三步查路径变量。重点看INCLUDEPATH、DEPENDPATH、LIBS里的$$PWD和$$OUT_PWD是否被替换成了 CMake 变量。有些版本在遇到绝对路径时会保留原样这没问题真正危险的是 qmake 里LIBS -L$$PWD/lib被转成target_link_directories但路径少了一层或者更糟路径直接丢掉了。我把这一步戏称“路径三查”查完再交给 CMake后面就能少看一半报错。如果你在 VSCode 里用 CMake Tools转完记得重跑一次CMake: Delete Cache and Reconfigure。q2c 生成的 CMakeLists.txt 和原来手写的缓存不兼容底部状态栏的 Configure 按钮如果在切换 kit 后一直不出现多半是CMakePresets.json里的名称和当前工具链对不上不是 q2c 生成错了。4. 反过来走CMake 工程转回 qmake 的边界与偏方q2c 的标题里有个双向箭头所以反向转换不能当作赠品功能看待。实际场景里往往是老库还维护在 CMake但某个新分支需要塞进一个只能编译 .pro 的陈旧构建环境里这时候反向生成的 .pro 至少能给你一个能改的起点。4.1 反向模式能复刻什么target、sources、include 映射反向转换的命令和正向类似只是输入输出交换q2c convert --cmake CMakeLists.txt --pro migrated.pro对一个简单的 CMake 目标比如add_executable(console_app main.cpp) target_compile_features(console_app PRIVATE cxx_std_17) target_include_directories(console_app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/lib)q2c 生成出的 .pro 会近似这样TEMPLATE app TARGET console_app CONFIG cxx17 SOURCES main.cpp INCLUDEPATH $${PWD}/lib逻辑说明add_executable映射到TEMPLATE apptarget_compile_features里cxx_std_17映射成CONFIG cxx17这是 qmake 比较新的写法老版本 Qt 可能需要翻译成QMAKE_CXXFLAGS -stdc17target_include_directories里的${CMAKE_CURRENT_SOURCE_DIR}被还原成 qmake 的$$PWD。如果你手写的 CMake 里有target_link_libraries(console_app PRIVATE Qt6::Core)q2c 会试着生成QT core但这里常常出偏差Qt6::Core的模块名在 qmake 里是core可Qt6::Quick在 qmake 里要写成QT quick这个映射表相对可靠。真正不可靠的是Qt6::Widgets在老 Qt5 工程里还要补greaterThan(QT_MAJOR_VERSION, 4): QT widgetsq2c 不会帮你补这种历史条件。4.2 CMake 的 add_library 与自定义函数q2c 处理不了的语法堡垒CMake 里大量使用add_library(foo STATIC ...)反向生成TEMPLATE lib和CONFIG staticlib是常规操作。但如果你碰到 Qt 的插件库比如add_library(plugin MODULE ...)qmake 里对应的是TEMPLATE lib加CONFIG plugin不少 q2c 版本会只生成TEMPLATE lib导致后面加载插件时找不到入口。自定义函数是更大的坑function(qt_auto_moc target) qt6_generate_moc(${target}) endfunction() qt_auto_moc(myapp)q2c 看到function()和自定义函数调用时一般会直接把整块拷贝进 .pro 旁边的一个注释文件或者原地输出一个message(WARNING: function qt_auto_moc not translated)。这不是工具偷懒而是 qmake 根本没有“函数”这个一等公民它只有defineReplace/defineTest这种宿主脚本式的结构无法一对一转换。所以反向转换前你最好先把 CMakeLists.txt 里的自定义函数手动展开成普通命令再喂给 q2c输出会干净很多。4.3 手动补 .pro 的固定套路条件与 CONFIG反向生成的 .pro 里最常见的遗漏是条件逻辑尤其是if(WIN32)和if(APPLE)这类平台判断。qmake 里对应的写法是win32: { LIBS -lws2_32 } macx: { LIBS -framework Security }除此之外CMake 里的add_compile_definitions(WIN32_LEAN_AND_MEAN)要手动改成DEFINES WIN32_LEAN_AND_MEANadd_compile_options(-Wall)要改到QMAKE_CXXFLAGS -Wall下set_target_properties(... OUTPUT_NAME ...)则要改写成TARGET 目标名再去设置DESTDIR。这些不是我背下来的而是每次反向生成后直接 CtrlF 查add_和set_开头的那几行一个个手工对应。我个人的偏方是反向 .pro 只用来恢复目录结构和源文件清单编译选项全部重写。因为 qmake 对CONFIG的解析和 CMake 的编译选项本来就差一层硬去救那几行过渡代码不如推倒重写来得快。5. 转换现场避坑指南我从 .pro 迁到 CMake 的 5 次翻车q2c 的优势是省事劣势是你容易把它的输出当成成品。接下来这五条“现象 → 原因 → 解决”是我在真实项目里踩过的坑每一条都经过了 q2c 转换和后续人工修复写成血泪经验给你当参考。5.1 现象转完编译直接报 Unknown component: qtquick有一次把 Qt Quick 工程的 .pro 转成 CMakeLists.txt配置阶段报CMake Error at ...: Unknown component: qtquick原因.pro 里写的是QT quick qml但 q2c 的 Qt 组件映射表是按模块名硬编码的它把quick识别成Quick、把qml识别成Qml后生成的find_package是COMPONENTS Core Gui Quick Qml但某个老版本还在用Qt5::Quick这种命名。报错其实来自 CMake 的find_package里塞进了不存在的组件名而不是 Qt 真没装。解决排查find_package行把组件名修正成当前 Qt 版本的官方写法。Qt6 中是COMPONENTS Core Gui Qml QuickQt5 里则通常用find_package(Qt5 COMPONENTS Core Gui Qml Quick)。另外确认 .pro 里QT widgets在有Q_OBJECT的窗口头文件时没被漏掉漏掉不会报这个错但会少链接Qt5::Widgets。5.2 现象INCLUDEPATH 变成了相对路径第三方头文件找不到转换后 CMake 配置成功但编译时大量xxxx.h: No such file or directory。打开 CMakeLists.txt 看到target_include_directories(myapp PRIVATE 3rdparty/include)原因qmake 的INCLUDEPATH $$PWD/3rdparty/include在转换时$$PWD被替换成${CMAKE_CURRENT_SOURCE_DIR}但生成的路径直接用了3rdparty/include如果 CMake 运行目录不是 .pro 所在目录就会变成相对路径出错。这是我在cmake -B build分离构建时最常见的翻车点。解决把该行手动改成${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/include或者用target_include_directories的BEFORE选项确保顺序。更稳妥的做法是在转完后的全文件搜索里搜include_directories和target_include_directories凡是没有${CMAKE_CURRENT_SOURCE_DIR}开头的相对路径全部补全。q2c 不是给你背锅的它只负责映射路径规范化得人工管。5.3 现象SUBDIRS 多目录工程转出来只剩一个 target多目录工程结构是常见的main.pro加subdir1/subdir1.pro、subdir2/subdir2.pro用SUBDIRS聚合。q2c 转换后CMakeLists.txt 里只有一个add_executable子目录里全是空文件子目录或者干脆没生成。原因q2c 对SUBDIRS的处理一般会要求main.pro里已经写明条件比如SUBDIRS subdir1 subdir2但很多老工程会在main.pro里用.depends或者FORMS 让子目录交叉引用q2c 解析不了这种隐式依赖就只按第一个子目录生成。解决先手动把SUBDIRS里的每个子目录拿出来单独跑一次单项转换再在顶层 CMakeLists.txt 里用add_subdirectory(subdir1)、add_subdirectory(subdir2)串联。生成后检查每个子目录的 CMakeLists 是否包含自己的target_name再用add_dependencies(main subdir1)补依赖。这个过程没有捷径q2c 对“两个子目录互相链接”的场景只能给你留下两个独立 target依赖图还得手补。5.4 现象qrc 被重复 add_executable链接时报 duplicate symbol资源文件本来应该只被一个 target 引用但转换后 CMake 里出现了两次qt_add_executable(myapp ...) qt_add_resources(myapp app PREFIX / FILES main.qml) qt_add_resources(myapp app PREFIX / FILES main.qml)原因qmake 的RESOURCES app.qrc写一遍q2c 把它解析成文件列表后可能同时出现在qt_add_executable的源文件列表和qt_add_resources里等于是同一个 qrc 内容被 rcc 编译了两遍。链接时qrc_app.cpp里的符号重复CMake 会报重复定义而且这跟你之前用CONFIG resources_big还是小资源没关系。解决删除qt_add_executable里的.qrc或资源文件条目只保留qt_add_resources一处。如果 .pro 里做的是RESOURCES a.qrc b.qrc转成 CMake 后我习惯合并成一次qt_add_resources把两个集合用不同PREFIX隔离避免文件冲突。检查方法是用grep -n add_resources CMakeLists.txt一个 qrc 文件只能出现一次。5.5 现象CONFIG console 被吞掉Windows 下双击程序弹不出控制台控制台程序在 qmake 里写CONFIG console转换后的 CMakeLists.txt 里找不到任何对应项。生成出来的 exe 在 Windows 下双击不弹控制台窗口但输出内容会直接消失其实程序跑了只是没有 stdout 给你看。原因q2c 对CONFIG的处理倾向于忽略“不影响构建正确性”的项目console在 qmake 里影响的是链接器子系统而 CMake 里对应的是WIN32_EXECUTABLE属性。q2c 没有把它映射到set_target_properties所以转换结果就漏了。解决手动补一行set_target_properties(myapp PROPERTIES WIN32_EXECUTABLE FALSE)其中FALSE表示以控制台子系统方式链接和 qmake 的CONFIG console等价。如果你用 MSVC也可以直接target_link_options(myapp PRIVATE /SUBSYSTEM:CONSOLE)但属性写法更通用。这个坑的难点在于 q2c 不会报任何警告只能靠最后验证阶段真跑一次 exe 才能发现。6. 转换后的收尾技巧用 q2c 做持续迁移的验证脚本迁移不是把 CMakeLists.txt 生成出来就结束了真正的工地习惯是每次改动要么对比构建产物、要么对比运行行为。我会在项目根目录放一个migrate_check.sh把 qmake 构建和 CMake 构建放在隔离目录里各跑一遍再比较产物差异。#!/bin/bash set -euo pipefail rm -rf build-qmake build-cmake mkdir build-qmake build-cmake cd build-qmake qmake ../app.pro make -j$(nproc) mv myapp ../myapp-qmake.bin 2/dev/null || true cd .. cd build-cmake cmake .. -G Ninja -DCMAKE_BUILD_TYPERelease cmake --build . mv myapp ../myapp-cmake.bin 2/dev/null || true cd .. if cmp -s myapp-qmake.bin myapp-cmake.bin; then echo binary identical else echo binary differs, check the following: ls -l myapp-qmake.bin myapp-cmake.bin fi逻辑说明这个脚本会在两个干净目录里分别用 qmake 和 CMake 构建然后把可执行文件复制到根目录做二进制比对。绝大多数项目不可能二进制完全一致所以cmp失败是常态我要看的是“差多少”如果两个 bin 大小差好几倍说明 CMake 的 Release 标志没生效或调试信息被带进来了。脚本里的2/dev/null || true是为了防止某些平台下可执行文件名后缀不同导致脚本直接退出改成真正的检查逻辑要看自己目标平台。跑完脚本后我还会建一张表记录关键差异作为后续迁移状态卡检查项qmake 构建CMake 构建是否通过可执行文件能否启动是是通过--version输出v1.2.0v1.2.0通过加载第三方插件数量33通过链接库依赖liba.so, libb.soliba.so, libb.so通过这个脚本最大的价值不在第一天而在三周后你改了一个源文件qmake 构建被历史原因废掉了只有 CMake 还在跑这时候你能立刻知道是代码回归还是构建脚本差异。我现在每接一个新项目都会先把这套验证脚本塞进pre-commit里哪怕暂时还没切换构建系统也能通过对比发现 .pro 和 CMakeLists 是不是真的在描述同一个工程。q2c 帮我把重复劳动省了但最后一道检查还是得人来做希望这个流程能帮你也少踩几个坑。本文还有配套的精品资源点击获取