简介q2c是一款面向Qt开发者的构建系统转换工具专注于在qmake与CMake之间自动互转项目配置解决Qt项目迁移与多构建系统维护的痛点。这份源码包仅14KB共含13个文件核心代码以C实现包括6个源程序文件与5个头文件另附1个qmake工程文件和1份说明文档结构小巧完整。包内代码按功能划分了项目解析、配置读取、终端参数处理与日志记录等模块便于跟踪q2c如何解析.pro文件、生成CMakeLists.txt也清晰展示反向转换时对参数与配置项的处理逻辑。整个资源可编译运行参考说明文档即可完成构建安装验证双向转换效果复杂项目中的特殊配置也可通过阅读源码进行手动调整。已有3205人浏览学习适合需要在Qt项目中灵活切换构建体系、或希望深入理解两种构建系统差异的开发者收藏使用。1. q2c是干嘛的qmake工程一键转CMake先看清值不值得用手头攒了十几个.pro文件的项目不在少数Qt6全面拥抱CMake之后老工程的构建脚本还停在qmake时代迁不迁都难受。手动重写CMakeLists.txt不是不行是漏一个头文件、丢一个链接参数就要排查半天。q2c就是干这个的转换工具——把qmake的.pro工程文件解析成CMakeLists.txt把QT模块、源文件、头文件、UI表单、资源文件、宏定义和include路径一次性映射过去。它不是万能的复杂的条件分支和自定义函数依然要手工处理但对常规的app、lib工程转换结果能直接进入构建流程省下最机械的那部分工作量。适合两类人一是手里有存量qmake工程、准备迁到CMake的Qt开发者二是新项目不想从零写CMakeLists先用qmake画草稿再转的人。下面所有步骤都按我拆过的工程来写照着走一遍就能落地。2. qmake和CMake的映射关系q2c的转换规则和三个关键配置2.1 .pro里到底有什么q2c真正要解析的东西qmake的.pro文件本质上是一个声明式构建脚本QT widgets声明模块依赖SOURCES main.cpp罗列编译单元CONFIG c17决定编译标准DESTDIR、TEMPLATE控制产物布局。q2c做的第一件事就是逐行扫描这份文本把每一类变量分拣出来再映射到CMake的target属性上。一个典型的.pro是这样QT core gui widgets TEMPLATE app TARGET myapp CONFIG c17 SOURCES main.cpp MainWindow.cpp HEADERS MainWindow.h FORMS MainWindow.ui RESOURCES resources.qrc DEFINES APP_NAME\myapp\ INCLUDEPATH include逐行拆解映射关系QT 决定CMake里find_package的内容和target_link_libraries的链接库TEMPLATE决定最终生成add_executable还是add_libraryTARGET对应CMake的target名字CONFIG c17对应CMAKE_CXX_STANDARDSOURCES、HEADERS、FORMS、RESOURCES直接进target的源码列表DEFINES变成target_compile_definitionsINCLUDEPATH变成target_include_directories。理解这一层之后你才具备逐一核对转换结果的能力。qmake变量是叠加的QT core和QT widgets会合并成core gui widgets。q2c在解析时会把同名字段的增量合并后再输出所以生成的CMakeLists里不会出现两份find_package调用。这个合并逻辑对SOURCES同样有效哪怕.pro里在不同作用域中追加源文件转换结果仍然是完整的源文件列表。2.2 模块映射QT 到底对应CMake的什么这是转换结果里最核心的对应关系它决定工程能不能在CMake世界里正确找到Qt的库和头文件。qmake模块名到CMake组件的映射关系如下这也是检查q2c输出是否正常的对照表qmake 模块find_package 组件链接目标coreCoreQt6::CoreguiGuiQt6::GuiwidgetsWidgetsQt6::WidgetsnetworkNetworkQt6::NetworksqlSqlQt6::SqlxmlXmlQt6::XmlmultimediaMultimediaQt6::MultimediachartsChartsQt6::ChartsquickQuickQt6::QuickqmlQmlQt6::QmluitoolsUiToolsQt6::UiToolstestlibTestQt6::Test以最常用的QT core gui widgets为例q2c生成的CMakeLists.txt里会包含find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED)后面跟着target_link_libraries(app PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)。有个容易忽视的点core和gui在qmake里是默认加载的模块很多老工程并不会显式写QT core但CMake没有默认模块所以q2c必须兜底补齐。我检查转换文件时第一件事就是看find_package那一行有没有把Core和Gui补上缺了后面链接必然报错。如果你不确定某个Qt库对应CMake的什么组件名有个比查文档更快的方法打开Qt安装目录下的lib/cmake文件夹里面的子目录名就是组件名比如Qt6Charts对应Charts。这个规则在Qt5和Qt6下都适用。如果.pro里还写了greaterThan(QT_MAJOR_VERSION, 5)这种版本判断q2c只能把它当作静态文本跳过不会执行条件。常见做法是转换前确定目标Qt大版本把版本分支删掉或统一再跑q2c否则find_package(Qt6 ...)和find_package(Qt5 ...)可能同时出现在生成文件里配置阶段直接乱套。2.3 三个自动化宏AUTOMOC、AUTOUIC、AUTORCCqmake时代有个隐藏的便利.ui文件自动生成ui_xxx.h带Q_OBJECT的头文件自动跑moc.qrc资源文件自动编进二进制。这三件事在qmake里是默认行为到了CMake却要显式开启否则工程能编译但链接期会报一堆undefined reference to vtable。q2c生成的CMakeLists.txt顶部一般会这样设置set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON)三个宏的分工很明确AUTOMOC扫描target里所有源文件对包含Q_OBJECT的头文件自动生成moc源文件并参与编译AUTOUIC负责把.ui文件转换成ui_*.h转换时使用target的include路径作为搜索目录AUTORCC处理.qrc里列出的资源资源内容会被编译进生成的qrc_*.cpp。这三个开关对应qmake里CONFIG automoc的旧写法但q2c的输出里不再写CONFIG直接交给CMake宏接管。一个容易被忽略的细节AUTOMOC只处理出现在target源文件列表里的文件。如果.pro里的HEADERS没写全某个头文件虽然被.cpp间接包含但没写进CMakeLists的target列表moc就不会触发结果就是那种“编译全过、链接报槽函数找不到”的玄学问题。如果你需要给moc额外传参数用set_property(TARGET demo PROPERTY AUTOMOC_MOC_OPTIONS -i)这些参数在qmake时代是写进.pro的QMAKE_MOC 或CONFIG里的转换后不会自动迁移需要手工补。3. q2c实操从.pro到CMakeLists.txt的完整复现流程3.1 拿到q2c源码编译与版本问题q2c是个命令行工具常见获取方式是从源码仓库克隆下来自己编译。它本身也是Qt工程所以机器上先得有一套能编译Qt项目的环境。编译方式不复杂用qmake或CMake都能构建。我一般习惯让构建q2c的Qt版本和后续转换目标版本保持一致避免工具自带规则和实际链接的Qt版本不匹配后面转换出来的CMakeLists还要二次修改。编译命令大致是这样的git clone 你找到的q2c仓库地址 cd q2c qmake q2c.pro make -j4编译完成后工程目录下会生成q2c可执行文件。如果机器上装了多个Qt版本编译前先跑qmake -v确认当前用的是哪一套。工具本身面向Qt5和Qt6都兼容但编译器版本和Qt版本混搭时容易出现标准库头文件冲突这是所有Qt源码项目编译的通用问题q2c也不例外。第一次跑的时候我建议先在临时目录里转一个能编译的小工程确认工具链没问题再去碰真工程。这样即使转换结果有问题排查范围也小得多。3.2 最小复现带窗口的.pro转成可构建的CMake从头复现一遍才能看清q2c的脾气。先写一个最常规的Qt Widgets工程目录结构如下demo/ ├── demo.pro ├── main.cpp ├── MainWindow.h ├── MainWindow.cpp ├── MainWindow.ui └── resources.qrcdemo.pro内容QT core gui widgets TARGET demo TEMPLATE app CONFIG c17 SOURCES main.cpp MainWindow.cpp HEADERS MainWindow.h FORMS MainWindow.ui RESOURCES resources.qrc在demo/目录下执行转换q2c demo.pro命令执行完当前目录下会生成一个CMakeLists.txt。不同版本的q2c在指定输出文件名上有差异有些支持第二个参数指定输出路径有些只生成默认文件名具体以q2c --help的输出为准。生成的CMakeLists.txt大致长这样cmake_minimum_required(VERSION 3.16) project(demo LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED) add_executable(demo main.cpp MainWindow.cpp MainWindow.h MainWindow.ui resources.qrc ) target_include_directories(demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(demo PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)这里解释几个转换细节。CONFIG c17被翻译成CMAKE_CXX_STANDARD 17这个宏从CMake 3.1开始支持所以cmake_minimum_required至少要设在3.10以上q2c默认给的3.16算是个保守且合理的值。MainWindow.ui和resources.qrc被原样写进target源码列表AUTOUIC和AUTORCC会在configure阶段自动生成对应的源文件参与编译CMakeLists里不需要手动写qt6_add_ui和qt6_add_resources。这是新手和老手写法的分水岭前者依赖自动化宏后者显式调用Qt6的CMake函数二者效果等价但AUTOUIC的写法更干净也更贴近qmake时代的行为。转换后的构建命令cmake -S . -B build -DCMAKE_PREFIX_PATH/path/to/Qt/6.5.0/gcc_64 cmake --build build -j4-DCMAKE_PREFIX_PATH是CMake寻找Qt库的关键参数。系统里装了多个Qt版本时这行参数决定你最终链接的是哪一套。不加这个参数CMake只会去默认路径找QtQt装在自定义目录的机器上会直接报Could not find a package configuration file provided by Qt6。从qmake环境迁过来的开发者最容易在这一步卡住qmake时代用QTDIR环境变量或qmake -queryCMake完全不读这一套它走自己的路径发现机制。迁移之后这个参数会陪伴你很久建议写进后续要讲的CMakePresets.json里。3.3 生成结果不急着用先对照检查六个位置q2c的输出不能无脑信任每次转换后我都会按固定顺序检查六个位置。第一个是find_package确认模块列表和.pro里的QT 一致第二个是target类型TEMPLATE app对应add_executableTEMPLATE lib对应add_library第三个是源文件列表核对SOURCES和HEADERS是否完整HEADERS缺失是q2c转换里比较常见的疏漏第四个是target_include_directories检查INCLUDEPATH里的路径有没有漏掉第五个是宏定义DEFINES里的内容必须转成target_compile_definitions字符串值要保留引号第六个是链接库LIBS里的-l参数和CMake的写法有区别后面会细说。六个位置和失败症状的对应关系检查位置对应.pro写法缺失时的典型报错find_packageQT modulesQt6 not foundtarget类型TEMPLATE产物类型不对源码列表SOURCES / HEADERSvtable未定义、符号找不到include路径INCLUDEPATH头文件No such file宏定义DEFINES行为不一致、条件编译分支不对链接库LIBScannot find -lxxx这六个位置是我第一次用q2c转换后踩坑踩出来的checklist。工具有一个通病它不告诉你哪些内容没有理解遇到解析不了的变量就跳过跳完之后照样生成一份CMakeLists.txt。所以转换后做这份对照检查比纠结工具本身生成的代码更值得花时间。等构建报错再回头改定位成本会翻好几倍。3.4 实际工程常遇到的两种模板lib和subdirs上面demo是TEMPLATE app单target最简单的情况。实际工程里还有两类很常见动态库和子目录工程。qmake里TEMPLATE lib通常配合CONFIG shared或CONFIG staticlib对应CMake的add_library及其STATIC/SHARED属性CONFIG plugin则对应MODULE。q2c能把TEMPLATE lib映射成add_library但STATIC和SHARED的判断经常不准常见做法是转换后手动确认一次。TEMPLATE lib TARGET core CONFIG shared QT - gui SOURCES core.cpp HEADERS core.h VERSION 1.2.0转换后需要手工修正的关键点add_library(core SHARED core.cpp core.h ) set_target_properties(core PROPERTIES VERSION 1.2.0 SOVERSION 1 )QT - gui在qmake里表示从默认模块中移除guiq2c对这种减法操作的处理也不总是可靠结果里可能仍然带着Qt6::Gui需要手动删掉。.pro里的VERSION也不会主动映射到CMake的VERSION和SOVERSION属性这些都要自己补。TEMPLATE subdirs的工程在q2c这里基本只能手动处理。父级.pro里的SUBDIRS core appq2c通常只会解析出子目录列表不会自动生成add_subdirectory。我一般的做法是每个子工程分别用q2c生成CMakeLists.txt父目录手工写一个最简CMakeListscmake_minimum_required(VERSION 3.16) project(workspace LANGUAGES CXX) add_subdirectory(core) add_subdirectory(app)子工程之间的依赖关系比如app依赖core需要在app的CMakeLists.txt里补target_link_libraries(app PRIVATE core)q2c不解析LIBS -lcore这种变量和target之间的对应关系。这一步没有捷径手工操作时注意链接顺序静态库依赖要放在后面。4. 必踩的五个转换坑条件语法、路径变量和AUTOMOC失效4.1 qmake作用域条件被静默丢弃现象转换后CMakeLists里所有源文件都被堆在一起原来在win32:{}、unix:{}里单独添加的文件、库要么全部丢失要么全被保留构建时还没有任何warning。原因qmake的作用域语法是预处理级别的条件判断win32:SOURCES winmain.cpp只有在Windows平台上才生效。q2c按静态文本解析看到win32:这个token但不会执行它背后的逻辑最终就把条件分支吞掉或合并错了。解决转换前先确定目标平台把对应平台的分支手动合并到主作用域。常见做法是复制一份临时的pro文件把条件分支里的SOURCES 、LIBS 挪到顶层再跑q2c。比如win32:LIBS user32.lib可以直接改成顶层LIBS user32.lib前提是确认工程不会在非Windows平台编译。这是转换工作的第一步预处理不是工具的错是静态解析工具的固有边界。4.2 $$PWD和系统调用展开不出真值现象生成的CMakeLists里出现$$PWD/../third_party这类字面字符串或者$$system(...)被原样保留include路径和库路径全部失效。原因$$PWD在qmake里展开为工程文件所在目录$$system()会调用外部命令并把输出拼进变量。q2c不执行系统命令也不展开这类变量只把看到的文本原样输出。CMake拿到一个$$PWD开头的路径自然找不到目录。解决转换前先把.pro里的$$PWD替换成实际相对路径。以INCLUDEPATH $$PWD/third_party/include为例交给q2c的输入应该写成INCLUDEPATH third_party/include或者转换后用${CMAKE_CURRENT_SOURCE_DIR}/third_party/include补进target_include_directories。$$system(...)没有通用解法建议先跑一遍qmake拿到实际展开的值替换成字面量后再转换。这是最花时间的一步但偷懒跳过的话后面每个路径错误都要单独排查。4.3 AUTOMOC失效头文件不在target列表里现象编译一切正常链接时报undefined reference to vtable for MainWindow或者编译日志里根本找不到moc_MainWindow.cpp。原因AUTOMOC只扫描target的源文件列表。.pro里如果HEADERS漏写了MainWindow.h或者只有SOURCES MainWindow.cpp却没有HEADERS MainWindow.hq2c按pro内容生成CMakeLists时自然不会包含这个头文件。但头文件里写了Q_OBJECTmoc不触发虚表就没有定义。这里有个qmake和CMake的隐性差异qmake时代即使头文件没列进HEADERS某些场景下还能靠include链碰巧生成moc文件AUTOMOC的规则更严格漏了就真的漏了。解决转换后检查add_executable或add_library的源码列表确认所有带Q_OBJECT的头文件都在列表里。我用一个固定命令快速排查grep -rl Q_OBJECT --include*.h . | sort把输出结果和CMakeLists里的源码列表对照漏一个补一个。从那之后我养成了习惯任何工具转换完先做这个grep对照再进入构建流程基本杜绝了这类vtable报错。4.4 UI文件路径和INCLUDEPATH不匹配现象构建时报uic: Cannot open file forms/MainWindow.ui或者uic能打开文件却报找不到ui_xxx.h内部引用的自定义头文件。原因qmake把FORMS forms/MainWindow.ui的相对路径基于.pro所在目录解析AUTOUIC的基本规则相同但uic在生成ui_MainWindow.h时如果.ui文件内部引用了自定义widget或头文件搜索路径来自target的include目录列表。q2c把INCLUDEPATH转成target_include_directories后如果.pro里原本用$$PWD拼接的路径没有正确展开uic的搜索目录就不完整。解决先按4.2节把$$PWD处理干净再确认CMakeLists里target_include_directories有没有包含UI文件所在目录的上一级。另一个容易漏的点是.ui里headercustomwidget.h/header这类自定义控件声明AUTOUIC的搜索目录必须包含该头文件所在路径。排查时打开生成的ui_MainWindow.h看它的#include语句指向哪个目录就知道缺了哪条路径。这个坑在qmake时代几乎不会暴露因为qmake会把pro文件所在目录自动加进搜索路径。4.5 静默跳过q2c不会提示它丢失了什么现象转换后构建报某个源文件不存在、某个宏没定义回到CMakeLists一查对应内容压根没被生成。原因这是q2c和所有静态转换工具的共性问题它不报告解析失败或跳过的地方只输出生成结果。比如.pro里写contains(QT_CONFIG, private) {}这种内置函数调用q2c解析不了就静默跳过CMakeLists生成后完全看不出这里曾经有过内容。相比编译器报错这种静默缺失的排查成本更大因为你不知道问题出在转换前还是转换后。解决转换前先手动清理.pro里的函数调用和模板变量把contains、message()、system()改成可转换的静态文本。更稳妥的流程是先用qmake -Wall跑一遍原工程确认.pro本身能正确解析再用qmake par或查看生成的Makefile拿到展开后的内容拿展开结果和q2c输出对照。这个对照工作量大但对复杂工程有必要尤其是准备删掉.pro正式切CMake之前。我自己的标准是转换结果能通过第5章的验证闭环才允许动删除的念头。5. 转换后的验证闭环编译对照、运行对照和双轨并行验证这步很多教程都不提但我觉得它是整个迁移里最不能省的事。CMake工程编译通过不等于行为和原工程一致。我自己的验证分三层编译层、运行层、配置一致性层。编译层验证最简单先确认机器上同时保留qmake和CMake两套构建产物分别编译同一份源码# 原qmake构建 mkdir build_qmake cd build_qmake qmake ../demo/demo.pro make -j4 cd .. # CMake构建 cmake -S demo -B build_cmake -DCMAKE_PREFIX_PATH/path/to/Qt/6.5.0/gcc_64 cmake --build build_cmake -j4两套二进制都出来后对比文件大小、动态链接依赖和运行结果。ldd build_qmake/demo和ldd build_cmake/demo输出里的Qt库路径应该完全一致这个检查能暴露CMAKE_PREFIX_PATH指错版本的问题。然后是运行层同时打开两个demo把窗口尺寸、资源图标、中文乱码、信号槽响应逐一对比。qmake和CMake对翻译文件、资源压缩格式的处理差异不大但Q_OBJECT类如果没正确触发moc运行期会表现成槽函数不响应这类问题静态编译阶段根本发现不了。配置一致性层有个实用技巧把两套构建的关键编译参数导出来对比。qmake构建用make V1能打印完整编译命令CMake侧在配置时加-DCMAKE_EXPORT_COMPILE_COMMANDSON会在build目录生成compile_commands.json把两边的-D参数、-I参数放一起比对能精确看出哪个宏定义或include路径在转换过程中丢了。这个对比我一般用脚本jq -r .[] | select(.file | contains(MainWindow)) | .command build_cmake/compile_commands.json把输出的编译命令和qmake打印的编译命令放在一起diff差异项就是转换的遗漏点。我第一回用q2c迁移一个带二十个源文件的中型工程时编译一次过跑起来主窗口的工具栏图标全部消失。排查到最后是.qrc里的资源别名在CMake侧解析不一致个别:/icon.png引用失效。从那以后我每次转换都强制走一遍“源码列表grep对照 → compile_commands.json对比 → 运行期资源检查”的流程再没翻过车。还有一个值得养成的习惯切CMake之后的两个星期内不要删qmake的.pro保持双轨并行。q2c只代表把构建脚本翻译了一遍不代表工程已经完成迁移。新需求到来时要加文件时先两边同步改一遍确认CMake侧行为一致再让.pro退休。希望帮到你。本文还有配套的精品资源点击获取