Qt Build and Deploy


  • 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              # synthesize a .pro from sources (rarely used outside one-offs)
qmake mywidget.pro          # generate Makefile (or Ninja, MSBuild project, etc.)
make -j                     # build via the generated tool
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>.
<!-- app.qrc -->
<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");   // loaded from the binary, no disk I/O
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.
# Windows: from Qt's bin/ directory
windeployqt.exe --release --no-translations build/release/myapp.exe

# macOS
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