QMake Deployment

Для автоматической сборки Qt приложений под Windows в папку назначения вместе со всеми нужными библиотеками и необходимыми файлами в QtCreator’е можно использовать qmake-команды в .pro-файле.

Для начала переносим релизный .exe в новую папку:

CONFIG(release, debug|release) {
DESTDIR = $$PWD/../AppName
}

И наполняем эту папку библиотеками и файлами, которые нужны для выполнения.

Задаём перенос строки aka переменная разделения команд RETURN, и затем наполняем QMAKE_POST_LINK необходимыми командами:

RETURN = $$escape_expand(\n\t)
QMAKE_POST_LINK += $$RETURN command1
QMAKE_POST_LINK += $$RETURN command2
QMAKE_POST_LINK += $$RETURN command3
# export(QMAKE_POST_LINK) # if inside function (exports from the local context to the global.

Но лучше для каждой операции ввести отдельную функцию, все функции вынести в отдельный .pri-файл, а в .pro-файле оставить только вызовы.

# functions.pri
defineTest(func1) {
    ARG1 = $$1
    ARG2 = $$2
    # commands
}
defineTest(func2) {
    ARGS = $$1
    # commands
}

# ...

# main.pro
include("functions.pri")
func1(arg1, arg2)
func2(arg1)

Команда сборки windeployqt в qmake будет выглядеть так (сначала используя содержимое переменной QT берём модули, которые точно нужны, потом пишем модули которые окажутся лишними):

# windeployqt in $$DESTDIR with some ARGS
defineTest(windeployqtInDESTDIR) {
    ARGS = $$1
    RETURN = $$escape_expand(\n\t)
    QMAKE_POST_LINK += $$RETURN windeployqt $$ARGS $$quote($$shell_path($$DESTDIR))
    export(QMAKE_POST_LINK)
}

# ...

PACKAGES = "--compiler-runtime"
for(package,QT){
    PACKAGES += "--$${package} "
}
PACKAGES += --no-svg --no-system-d3d-compiler --no-translations --no-opengl-sw --no-angle
windeployqtInDESTDIR($$PACKAGES)

Копирование и удаление файлов

Функция для рекурсивного удаления папки:

# Recursive remove directory
defineTest(removeDirRecursive) {
    DIR_TO_DEL = $$shell_path($$1)
    RETURN = $$escape_expand(\n\t)
    QMAKE_POST_LINK += $$RETURN $$QMAKE_DEL_TREE $$quote($$DIR_TO_DEL)
    export(QMAKE_POST_LINK)
}

#....

removeDirRecursive($$DESTDIR/somedir)

Функция для удаления файлов из какой-то папки:

# Remove some files from directory (directory_path and file_names)
defineTest(removeFilesInDir) {
    PATH = $$shell_path($${1}) # full path to directory
    FILENAMES = $${2}          # filenames inside directory for remove
    RETURN = $$escape_expand(\n\t)
    for(FILENAME, FILENAMES){
        QMAKE_POST_LINK += $$RETURN $$QMAKE_DEL_FILE $$quote($${PATH}$${FILENAME})
    }
    export(QMAKE_POST_LINK)
}

# ...

FILENAMES = qlib1.dll qlib2.dll qlib3.dll qlib4.dll
removeFilesInDir($$DESTDIR/somedir/, $$FILENAMES)

Функция для удаления файлов (не обязательно из одной папки):

# Remove some files
defineTest(removeFiles) {
    FILES_TO_DEL = $$shell_path($$1) # full path (split spaces) or mask *
    RETURN = $$escape_expand(\n\t)
    for(FILE, FILES_TO_DEL){
        QMAKE_POST_LINK += $$RETURN $$QMAKE_DEL_FILE $$quote($$FILE)
    }
    export(QMAKE_POST_LINK)
}

# ...

FILEPATHES = /path/to/qlib1.dll /path/to/qlib2.dll /path/to/qlib3.dll /path/to/qlib4.dll
removeFiles($$FILENAMES)

Функция для создания папки и копирования в неё файлов:

# create directory if not exist, then copy some files to that directory
defineTest(copyFilesToDir) {
    FILES = $$shell_path($$1)  # full filepath (split spaces) or masks * ?
    DIR = $$shell_path($$2)    # directory path
    RETURN = $$escape_expand(\n\t)
    QMAKE_POST_LINK += $$RETURN $$sprintf($$QMAKE_MKDIR_CMD, $$DIR)
    for(FILE,FILES){
        QMAKE_POST_LINK += $$RETURN $$QMAKE_COPY $$quote($$FILE) $$quote($$DIR)
    }
    export(QMAKE_POST_LINK)
}

# ...

copyFilesToDir(some/*.dll, $$DESTDIR/other)
# or
copyFilesToDir(path/to/1.dll path/to/2.dll, $$DESTDIR/other)

Дерево файлов проекта

Также стоит вспомнить о команде, которая лечит не-баг-а-фичу дерева папок проекта под windows (вложенные дополнительные папки debug и release в каждой из build-Qt-Debug и build-Qt-Release):

project\
|__build-Qt_x_y_0_32bit_Debug\
|__|__debug\
|__|__|__...files
|__|__release\
|__|__|__...empty
|__build-Qt_x_y_0_32bit_Release\
|__|__debug\
|__|__|__...empty
|__|__release\
|__|__|__...files

Вот она:

CONFIG -= debug_and_release

После неё:

project\
|__build-Qt_x_y_0_32bit_Debug\
|__|__...files
|__build-Qt_x_y_0_32bit_Release\
|__|__...files

Можно пойти дальше, и аккуратно разложить файлы в build-Qt-Debug и build-Qt-Release:

OBJECTS_DIR = $$OUT_PWD/.obj
MOC_DIR     = $$OUT_PWD/.moc
RCC_DIR     = $$OUT_PWD/.qrc
UI_DIR      = $$OUT_PWD/.ui

Получим:

project\
|__build-Qt_x_y_0_32bit_Debug\
|__|__.obj\
|__|__.moc\
|__|__.qrc\
|__|__.ui\
|__build-Qt_x_y_0_32bit_Release\
|__|__.obj\
|__|__.moc\
|__|__.qrc\
|__|__.ui\

Использование переменных

Чтобы быстро включать и выключать копирование файлов в DEPLOY-сборку, и заодно, отключить в DEPLOY все отладочные сообщения, можно сделать так:

CONFIG += DEPLOY # comment/uncomment this for Usual_release/Deploy

# ...

CONFIG(release, debug|release) {
  DEPLOY {
    DEFINES += QT_NO_DEBUG_OUTPUT
    # ... all necessary commands
  }
}

Удобной может быть переменная времени сборки:

win32 {
    BUILD_TIME = $$system("echo %time:~0,8%")
} else {
    BUILD_TIME = $$system("time")
}

message("$$BUILD_TIME projectname.pro")