- Description: Qt project structure (qmake
.pro/.pri/.prf/.prl, CMake), the code generators (moc, uic, rcc, lrelease), build with qmake/cmake, the app-icon and resource pipeline, dynamic vs static linking, and deployment with windeployqt/macdeployqt
- My Notion Note ID: K2A-B3-1
- Created: 2018-03-04
- Updated: 2026-05-18
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
1. Build Systems for Qt
- Three options in practice:
- qmake — Qt's historical build tool. Still works in Qt 6, but Qt itself moved to CMake. New projects shouldn't pick it.
- CMake — official for Qt 6.
find_package(Qt6 ...), qt_add_executable, qt_add_qml_module, qt_standard_project_setup(). What the Qt docs use for everything in Qt 6.
- Bazel — community rules (
rules_qt, bazel_rules_qt). Works but you're outside the supported path; expect to wire moc/uic/rcc yourself via the rule set.
- Pick CMake for anything new (Qt 5.15+ supports it, Qt 6 mandates it for first-class features). Keep qmake notes around because legacy
.pro files are everywhere.
2. Code Generators (moc, uic, rcc, lrelease)
- Qt's "magic" lives in C++ source transformed at build time. Build systems hide the steps; understanding what runs when explains every weird build error.
| Tool |
Input |
Output |
Purpose |
moc |
header with Q_OBJECT |
moc_<name>.cpp |
Meta-Object code: signals/slots wiring, QObject::metaObject(), qobject_cast, properties |
uic |
mywindow.ui (Designer XML) |
ui_mywindow.h |
Generated Ui::MyWindow struct that lays out child widgets |
rcc |
app.qrc |
qrc_app.cpp |
Bakes resource files into the binary; accessible as :/path/file |
lrelease |
*.ts (translation source) |
*.qm (compiled) |
Loaded by QTranslator at runtime |
moc is invoked on every header that contains Q_OBJECT — even nested classes. If you split a class with signals out of its header into a .cpp, you need #include "myclass.moc" at the bottom so moc finds it.
- Common build error: "undefined reference to
vtable for MyClass" → Q_OBJECT macro present but moc didn't run on this header. With CMake: set(CMAKE_AUTOMOC ON). With qmake: header listed in HEADERS = ....
3. qmake Project Files
3.1 .pro, .pri, .prf, .prl
| Extension |
Role |
.pro |
Project file. One per binary or library. Top-level configuration. |
.pri |
Project include. Snippets shared between .pro files via include(common.pri). |
.prf |
Feature file. Reusable build rules; loaded with CONFIG += <feature>. Lives in mkspecs/features/. |
.prl |
Generated metadata for a static library — link flags and dependencies. Consumers read it to link correctly. Not hand-edited. |
3.2 Common qmake Variables
QT += core gui widgets network
CONFIG += c++17
TARGET = myapp
TEMPLATE = app # or "lib", "subdirs"
SOURCES += main.cpp mainwindow.cpp
HEADERS += mainwindow.h
FORMS += mainwindow.ui
RESOURCES += app.qrc
TRANSLATIONS += i18n/app_zh.ts i18n/app_en.ts
INCLUDEPATH += $$PWD/include
LIBS += -L$$PWD/lib -lfoo
DEFINES += APP_VERSION=\\\"1.2.3\\\"
win32:RC_FILE = app.rc # Windows icon (see §7)
macx:ICON = app.icns # macOS icon
QT is the module list. core is implicit; gui is implicit unless you QT -= gui. widgets must be added explicitly in Qt 5/6 (it was split off from gui).
CONFIG is a feature-flag bag — release, debug, c++17, console, staticlib, etc.
TEMPLATE = subdirs for multi-project trees: list child projects with SUBDIRS = libs apps.
$$PWD = directory of the current .pro / .pri. $$OUT_PWD = build directory.
3.3 Building with qmake
qmake -project
qmake mywidget.pro
make -j
make install
- Out-of-source build:
mkdir build && cd build && qmake ../mywidget.pro && make -j. Same pattern as CMake.
qmake regenerates the Makefile when any .pro/.pri changes — but it does not rerun moc/uic on source-tree restructures. Stale moc_*.cpp is a common cause of mysterious link errors after big refactors; make distclean && qmake && make fixes it.
4. CMake with Qt
- Modern, recommended path for Qt 6. Qt 5.15 also supports it.
cmake_minimum_required(VERSION 3.21)
project(myapp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON) # run moc automatically
set(CMAKE_AUTOUIC ON) # run uic on .ui files
set(CMAKE_AUTORCC ON) # run rcc on .qrc files
find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Network)
qt_standard_project_setup() # Qt 6.3+: sane defaults (C++ std, output dirs, i18n)
qt_add_executable(myapp
main.cpp
mainwindow.cpp mainwindow.h mainwindow.ui
app.qrc
)
target_link_libraries(myapp PRIVATE Qt6::Widgets Qt6::Network)
# Translations
qt_add_translations(myapp TS_FILES i18n/app_zh.ts i18n/app_en.ts)
qt_add_executable (not plain add_executable) — wraps the target with extra Qt 6 bits: deployment hooks, finalization for static builds, QML imports.
find_package(Qt6 COMPONENTS ...) — each Qt module is its own component. Common ones: Core, Gui, Widgets, Network, Qml, Quick, Sql, Test, Concurrent, OpenGL, OpenGLWidgets, WebEngineWidgets.
- Link against the namespaced target
Qt6::Widgets, never raw ${Qt6Widgets_LIBRARIES}. The namespaced target carries include dirs and the AUTOMOC settings.
- For libraries built with Qt 5 + 6 compatibility, use
find_package(QT NAMES Qt6 Qt5) then refer to Qt::Widgets (un-versioned alias).
5. Bazel with Qt
- No official rules. Two community sets:
- Pattern: declare a
qt_cc_library rule that runs moc on Q_OBJECT headers behind the scenes, exposes the result as a normal cc_library. Mainline rules_cc then links and propagates as usual.
- Use only if Bazel is mandated by an existing repo. Otherwise CMake is far less friction.
6. Resources (.qrc)
- XML file describing files baked into the binary; read at runtime by path
:/<prefix>/<file>.
<RCC>
<qresource prefix="/icons">
<file>open.png</file>
<file>save.png</file>
</qresource>
<qresource prefix="/i18n">
<file>app_zh.qm</file>
</qresource>
</RCC>
QIcon openIcon(":/icons/open.png");
QTranslator t;
t.load(":/i18n/app_zh.qm");
QCoreApplication::installTranslator(&t);
rcc runs at build time, compiles the listed files into qrc_app.cpp. Trade-off: binary size grows, but the app has no missing-asset failure mode.
- Prefer resources for tiny static assets (icons, default translations, shaders). Large media (videos, big datasets) should stay on disk.
7. Application Icon
- Windows:
.rc file references a .ico, the .rc is compiled and linked.
IDI_ICON1 ICON DISCARDABLE "app.ico"
win32:RC_FILE = app.rc
- macOS: a single
.icns set via ICON = app.icns (qmake) or MACOSX_BUNDLE_ICON_FILE (CMake), copied into Contents/Resources/.
- Linux: typically install a
.desktop file plus PNG icons under share/icons/hicolor/<size>/apps/; the binary itself carries no icon.
8. Deployment
8.1 Dynamic Linking (Default)
- Qt LGPL builds are dynamic. The app binary depends on
Qt6Core.dll / libQt6Core.so.6 / QtCore.framework etc. You ship the Qt libs alongside, or rely on the system having them (Linux distros only).
- Output: build directory has just the executable; copying it to another machine fails — "this application failed to start because no Qt platform plugin could be initialized" is the canonical first error.
8.2 windeployqt / macdeployqt
- Qt ships per-platform helper tools that walk the binary and copy every needed Qt dependency next to it.
windeployqt.exe --release --no-translations build/release/myapp.exe
macdeployqt build/myapp.app -dmg
windeployqt resolves DLLs, copies platforms/qwindows.dll, image-format plugins, and any QML imports it found.
- Linux has no official equivalent; community tools are
linuxdeployqt and the AppImage / Flatpak / Snap pipelines.
8.3 Plugins You Must Ship
- Even with dynamic linking, certain plugins are loaded by filename at runtime — they won't show up in dependency walkers, and the app crashes / refuses to start without them.
| Plugin dir |
When you need it |
platforms/ |
Always. qwindows.dll / libqxcb.so / libqcocoa.dylib. The "no platform plugin" error means this is missing. |
imageformats/ |
Anything beyond PNG. JPG, GIF, ICO, WEBP, SVG all live here. |
sqldrivers/ |
QSqlDatabase backends — SQLite, MySQL, PostgreSQL, ODBC. |
tls/ |
TLS backend (OpenSSL or schannel). Needed for any HTTPS / QSslSocket. |
styles/ |
Optional widget styles (Fusion is built-in; "windowsvista" lives here). |
- Set
QT_DEBUG_PLUGINS=1 and re-run a failing app to get a verbose log of what was searched and why each candidate was rejected. Single best diagnostic for deployment problems.
8.4 Static Linking
- Search the Qt docs for "Deploying Qt Applications" and "Deploying an Application on Windows" for the canonical steps. Short version:
- Need a statically built Qt (
configure -static) — Qt's binary installer does not ship this; you build Qt yourself.
- The LGPL allows static linking only if you also ship object files or a relinking mechanism so users can swap Qt versions. Commercial license has no such restriction.
- In the
.pro: CONFIG += static. In CMake: build against a static Qt; qt_add_executable handles the Q_IMPORT_PLUGIN calls automatically when the target is finalized.
- Smaller deploy footprint, but you give up the ability to upgrade Qt without rebuilding the whole app. For most desktop apps, dynamic +
windeployqt is far less pain.
9. References