qmake / jom / 影子构建:把编译管起来

Day 01 解决了"编译器能不能被找到"。这一篇讲"找到之后,怎么把编译这件事管起来",而不是每次都在工程目录里糊一团中间文件。

我刚接手那个老项目时,所有人都是直接在 Qt Creator 里点"构建"。问题来了:源码目录里塞满了 *.obj、moc_*.cpp、ui_*.h,git 一拉全红;想同时留一份 Debug 和一份 Release 根本不可能,因为都往同一个目录写;换台机器或者 CI 上一跑,构建脚本没有,全靠人手点。这就是没把编译"管起来"的代价。

先搞清楚 qmake 在干嘛

很多人以为 qmake 是编译器,不是。它干的事是:把你的 .pro 翻译成 Makefile(或者 .vcxproj),并且自动把 moc、uic、rcc 这几个 Qt 特有的代码生成步骤串进去。

你写的 Q_OBJECT 类,编译器不认识。得先靠 moc 生成 moc_xxx.cpp,再一起编。手写这个流程能累死人,qmake(以及后来的 CMake)就是干这个的。所以别纠结"能不能不用 qmake",在 Qt 5.12 这套里,它就是你和构建系统之间的翻译层。

最小 .pro 长这样:

pro 复制代码
QT += core gui widgets
TARGET = myapp
SOURCES += main.cpp mainwindow.cpp
HEADERS += mainwindow.h
FORMS   += mainwindow.ui

qmake 读完它会生成 Makefile,jom 拿着 Makefile 去编译。

为什么是 jom 不是 nmake

Day 01 说过,Qt 官方推荐 jom。它语法兼容 nmake,但支持 -j 多核并行。nmake 没有并行能力,大项目编起来慢得让人想睡一觉。

命令行构建的标准两步:

bat 复制代码
qmake -r
jom -j 8

-r 是递归处理子目录(你的工程如果用了 SUBDIRS 模板,不加 -r 子项目不会被处理)。jom -j 8 开 8 个并行任务。

还有个事:Qt Creator 那个"构建"按钮背后其实就是 qmake && jom(或 nmake)。点按钮很方便,但脚本化才是工程化的开始------你总不希望 CI 也靠人手动点。

影子构建(shadow build)

这是我最想安利的一点。默认情况下 qmake 在 .pro 所在目录生成 Makefile 和所有中间文件,源码和垃圾混在一起。影子构建的意思是:在一个单独的目录里生成构建产物,源码目录保持干净。

命令行这么做:

bat 复制代码
mkdir build && cd build
qmake ../src/myapp.pro
jom -j 8

生成的全在 build/ 里,src/ 干干净净。好处三条:

  1. 源码目录不污染,git 不用天天 ignore 一堆东西;
  2. 可以同时存在 build-debug 和 build-release,互不干扰;
  3. 想重来?直接 rm -rf build 完事,不影响源码。

Qt Creator 里那个 "Shadow build" 勾选框就是干这个的,默认其实是开着的。但自己写命令行脚本时,得手动建目录,不然又回到原地构建了。

一个容易踩的坑:改了 .pro 不重新 qmake

命令行构建有个和 Creator 不同的地方:Creator 在构建前会自动检测 .pro 变化并重跑 qmake;你自己敲命令不会。如果你加了源文件、改了 LIBS,只敲 jom 是没用的,Makefile 还是旧的。

所以脚本里永远把 qmake -r 放在 jom 前面,或者至少改了 .pro 之后手动重跑一次 qmake。我见过有人困惑"我明明加了文件怎么链接报 unresolved",查了半小时,最后发现是没重跑 qmake。

想要 Visual Studio 工程?

有些同事就是习惯在 VS 里干活。qmake 能直接生成 .vcxproj:

bat 复制代码
qmake -tp vc ../src/myapp.pro

生成的 .vcxproj 双击就能在 VS2017 里打开,moc/uic 步骤也都接好了。注意 -tp vc 生成的是"用 qmake 管理"的项目,改了 .pro 还是得重跑这条命令来更新。

小结

把编译管起来的最小闭环就是:

bat 复制代码
mkdir build && cd build
qmake -r ../src/myapp.pro
jom -j 8

源码干净、可并行、可脚本化、可重复。下一篇讲 .pro / .pri 本身------这套语法坑比你想的多,尤其是 = 和 +=、还有条件作用域。


本篇属于「Qt 工程化实战」专栏 DAY 02。

相关推荐
devilnumber12 小时前
Lambda 表达式(Java)优缺点清晰分析
java·开发语言·python
开开心心就好13 小时前
电脑小说阅读器,支持朗读和自定义皮肤
服务器·开发语言·javascript·算法·pdf·c#·音视频
Smoothcloud润云13 小时前
GPU远程纳管节点频繁离线?心跳、DNS与时钟排障实战
运维·服务器·开发语言·人工智能·php·gpu算力
class心平气和13 小时前
ADB 工具函数 Python 使用教程
开发语言·python·adb
闻缺陷则喜何志丹13 小时前
【c++20】协程与屏障
c++·算法·c++20·携程·屏障
会编程的小孩13 小时前
C++ 内存分区、堆与栈、static、union 完整修正版文档
c++
滕州市燕猫虎计算机科技工作室个体工商户13 小时前
Java,Java NIO,Java NIO2,Java AIO,AsynchronousServerSocketChannel
java·开发语言·nio·aio·nio2
古城小栈13 小时前
Rust 学习路线,供初学者参考
开发语言·rust
喵了几个咪14 小时前
GoWind CMS 拆解:给 NL2SQL 上四把锁——智能问数的安全边界
开发语言·后端·安全·微服务·golang·gowind
多弗朗皮卡丘14 小时前
C++的类型转换
开发语言·c++