Evgenii Legotckoi
Evgenii Legotckoi30 июля 2016 г. 11:03

Qt WinAPI - Урок 004. QtIFW - Автоматизация WinDeployQt и сборки инсталляторов с Qt Installer Framework

Правильный программист - это ленивый программист. Если есть возможность что-то автоматизировать, то обязательно нужно это сделать. Например, можно сделать автоматическое выполнение windeployqt со сборкой онлайн и оффлайн инсталляторов, а также подготовкой репозитория приложения с помощью Qt Installer Framework и чёрной магии QMake .

Для того, чтобы сделать это наиболее красивым образом, необходимо, чтобы проект был организован со структурой subdirs , то есть состоял из нескольких проектов. Нам же не нужно при каждой сборке проекта создавать инсталляторы. Поэтому проект инсталлятора будет отдельным.

Но автоматизацию windeployqt оставим в основном проекте.

Структура проекта

Итак, имеем проект, с двумя подпроектами:

  1. Hello.pro - проект приложения
  2. Installer.pro -  проект инсталлятора

Основной проект представляет собой самый обычный "Hello world", но самый большой интерес заключается в скрипте QMake, который отвечает за windeployqt приложения.

Второй проект имеет минимальную структуру для сборки оффлайн и онлайн инсталляторов, а также сборки репозитория. Скрипт QMake в профайле проекта также содержит необходимую информацию для автоматизации сборки инсталляторов.


Автоматизация WinDeployQt

HelloInstaller.pro

Данный файл имеет традиционное содержание для subdirs проекта.

TEMPLATE = subdirs

CONFIG += ordered

SUBDIRS += \
    Hello \
    Installer

Hello.pro

Помимо обычного указания директорий сборки исполняемого файла, а также других выходных файлов проекта, в данном QMake скрипте выполняется запуск утилиты windeployqt .

QT += core
QT -= gui

CONFIG += c++11

TARGET = Hello
CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

# Выбираем директорию сборки исполняемого файла
# в зависимости от режима сборки проекта
CONFIG(debug, debug|release) {
    DESTDIR = $$OUT_PWD/../../HelloDebug
} else {
    DESTDIR = $$OUT_PWD/../../HelloRelease
}
# разделяем по директориям все выходные файлы проекта
MOC_DIR = ../common/build/moc
RCC_DIR = ../common/build/rcc
UI_DIR = ../common/build/ui
unix:OBJECTS_DIR = ../common/build/o/unix
win32:OBJECTS_DIR = ../common/build/o/win32
macx:OBJECTS_DIR = ../common/build/o/mac

# в зависимости от режима сборки проекта
# запускаем win deploy приложения в целевой директории, то есть собираем все dll
CONFIG(debug, debug|release) {
    QMAKE_POST_LINK = $$(QTDIR)/bin/windeployqt $$OUT_PWD/../../HelloDebug
} else {
    QMAKE_POST_LINK = $$(QTDIR)/bin/windeployqt $$OUT_PWD/../../HelloRelease
}

SOURCES += main.cpp

Если с указанием сборки папки исполняемого файла всё ясно, то разберёмся с тем, что же происходит при выполнении windeployqt.

QMAKE_POST_LINK - это директива, указывающая действия после завершения сборки проекта. То есть то, что мы выполняем после того, как был собран исполняемый файл.

$$(QTDIR) - переменная, которая содержит путь к комплекту сборки Qt. В моём случае это D:\Qt\5.7\mingw53_32.

Для выполнения windeployqt не обязательно указывать исполняемый файл, достаточно указать в качестве аргумента папку, где этот файл находится. В результате, по завершении сборки проекта, будет выполнена автоматическая сборка необходимых для приложения dll , и они будут помещены рядом с исполняемым файлом.

Сборка инсталляторов и репозитория

Сборка инсталляторов и онлайн репозитория проекта производится с помощью Qt Installer Framework, который содержит в своём составе утилиты, как для сборки самих инсталляторов, так и для подготовки репозитория приложения.

Для сборки будут использоваться две утилиты из этого фреймворка:

  • binarycreator - утилита для сборки инсталлятора;
  • repogen - утилита для подготовки онлайн репозитория.

Прежде, чем разбираться с QMake скриптом, изучим конфигурационные файлы и структуру директорий для сборки инсталляторов.

Структура директорий

Рассмотрим структуру проекта:

  • config - в которой находится конфигурационный файл инсталлятора:
  • config.xml - в данном файле будет содержаться информация об удалённом репозитории, версии, названии проекта и т.д.
  • packages - в котором будут находиться пакеты, из которых будут собираться инсталляторы, например:
  • ru.evileg.helloinstaller - директория первого пакета
  • meta - директория, которая содержит инсталляионные скрипты, которые будут описывать специфичный функционал, например, закрытие активного экземпляра приложения, если производится обновление приложения, или прописывание информации в реестр системы.
  • package.xml - в минимальном варианте требуется наличие данного файла, который будет  описываться версию и дату сборки проекта. Эта информация потребуется для формирования файла Updates.xml, который будет помещён в онлайн репозиторий.
  • data - в данную директорию необходимо будет поместить исполняемый файл и все необходимые dll, ресурсные файлы, файлы переводов и т.д. То есть всё, что необходимо для сборки рабочего приложения.
  • ru.evileg.ecolor -  а также, например, директория второго пакета с содержимым, которое сходно по структуре с предыдущим вариантов.

config.xml

Файл, в котором описана служебная информация об издателе, пути установки приложения, его версии и названии. А также приводится информация об адресе удалённого репозитория. Данная информация будет одинакова, как для онлайн инсталлятора, так и для оффлайн инсталлятора. Ведь нам нужно будет помимо прочего ещё и обновлять приложение, поэтому для обоих версий инсталляторов нужна информация об удалённом репозитории.

<?xml version="1.0" encoding="UTF-8"?>
<Installer>
    <Name>Hello Installer</Name>
    <Version>1.0.0</Version>
    <Title>1.0.0 Installer</Title>
    <Publisher>EVILEG</Publisher>
    <StartMenuDir>HelloInstaller</StartMenuDir>
    <TargetDir>@RootDir@Program Files (x86)\EVILEG\HelloInstaller</TargetDir>
        <AllowSpaceInPath>true</AllowSpaceInPath>
    <RemoteRepositories>
        <Repository>
            <Url>http://www.evileg.ru/software/helloinstaller/</Url>
        </Repository>
    </RemoteRepositories>
</Installer>
  • Installer - формируемый инсталлятор
  • Name - Наименование приложения
  • Version - Версия приложения
  • Title - Заглавие окна инсталлятора
  • Publisher - Издатель
  • StartMenuDir - указывает группу продуктов, к которой принадлежит приложение в стартовом меню Windows.
  • TargetDir - папка установки приложения; @RootDir@ - корневая папка. В данном случае C:\
  • AllowSpaceInPath - разрешить пробелы в пути к директории
  • RemoteRepositories - список репозиториев
  • Repository - один из онлайн репозиториев, их может быть несколько, либо они могут даже заменяться
  • Url - адрес репозитория

package.xml

Здесь уже указывается дополнительная информация о порядке установки приложения в систему, то есть наличие определённых форм, окон, установочных скриптов, например, для занесения информации в реестр. Также добавлена информация о дате выпуска приложения, что также будет помещено в файл Updates.xml.

Данный файл должен содержаться в каждом пакете. Инсталлятор может предоставлять возможность установке ряда компонентов, вспомните инсталлятор Qt, там много пунктов. Соответственно каждый такой пакет представляет свой пункт.

<?xml version="1.0" encoding="UTF-8"?>
<Package>
    <DisplayName>Hello Installer</DisplayName>
    <Description>The main component</Description>
    <Version>1.0.0</Version>
    <ReleaseDate>2016-07-30</ReleaseDate>
        <Default>true</Default>
    <Name>ru.evileg.helloinstaller</Name>
    <ForcedInstallation>true</ForcedInstallation>
    <RequiresAdminRights>true</RequiresAdminRights>
</Package>
  • DisplayName - наименование компонента. Обязательный тег.
  • Description - описание компонента. Обязательный тег.
  • Version - версия компонентаю. Обязательный тег.
  • ReleaseDate - дата выпуска. Обязательный тег.
  • Default - выбор компонента по умолчанию, то есть выбран компонент для установки, или нет.
  • Name - идентификатор пакета в виде доменного имени. Обязательный тег.
  • ForcedInstallation - указывает, что компонент обязателен к установке и пользователь не может его исключить из установки
  • RequiresAdminRights - указывает, что пакет должен устанавливаться с правами администратора. Поле необязательное. И самое интересное то, что в процессе работы инсталлятор может потерять данные права администратора. То есть для некоторых действий понадобится заново запрашивать у пользователя права администратора.

Installer.pro

Здесь представлен вариант со сборкой инсталляторов в отдельных папках для Release и Debug версий. А также подготовкой репозитория для Release версии.

Для сборки оффлайн инсталлятора воспользуемся утилитой binarycreator со следующими параметрами:

  • --offline-only - как видно из наименования, указывает, что собираем оффлайн инсталлятор
  • -c - указывает путь к конфигурационному файлу
  • -p -указывает путь к папке с пакетами, в процессе работы QtIFW сам разберётся с тем, как сформировать пакеты в инсталляторе с учётом их конфигурационных файлов и скриптов из папки meta

Примечание. Для онлайн версии инсталлятора всё тоже самое, только аргумент --offline-only заменяется на --online-only.

А для подготовки репозитория воспользуемся утилитой repogen , со следующими параметрами:

  • -p - указывает папку с пакетами
  • -i - указывает пакеты, которые необходимо включить в инсталлятор
  • -update - указывает, что необходимо обновить репозиторий по следующему пути. Если по этому пути нет репозитория, то будет создан новый.
# Before run make project, need to put executable file and dlls into
# $$PWD/installer/packages/ru.evileg.helloinstaller/data

TEMPLATE = aux

# В зависимости от режима сборки, определяем, куда именно будут собираться инсталляторы
CONFIG(debug, debug|release) {
    INSTALLER_OFFLINE = $$OUT_PWD/../../InstallerDebug/Hello.offline
    INSTALLER_ONLINE = $$OUT_PWD/../../InstallerDebug/Hello.online
    DESTDIR_WIN = $$PWD/packages/ru.evileg.helloinstaller/data
    DESTDIR_WIN ~= s,/,\\,g
    PWD_WIN = $$OUT_PWD/../../HelloDebug
    PWD_WIN ~= s,/,\\,g

    copydata.commands = $(COPY_DIR) $$PWD_WIN $$DESTDIR_WIN
    first.depends = $(first) copydata
    export(first.depends)
    export(copydata.commands)
    QMAKE_EXTRA_TARGETS += first copydata
} else {
    # Задаём переменные, которые будут содержать пути с названиями инсталляторов
    INSTALLER_OFFLINE = $$OUT_PWD/../../InstallerRelease/Hello.offline
    INSTALLER_ONLINE = $$OUT_PWD/../../InstallerRelease/Hello.online

    # Задаём переменную, которая должна содержать путь к папке с данными
    DESTDIR_WIN = $$PWD/packages/ru.evileg.helloinstaller/data
    DESTDIR_WIN ~= s,/,\\,g
    # Задаём путь откуда всё приложение с DLL-ками нужно будет скопировать
    PWD_WIN = $$OUT_PWD/../../HelloRelease
    PWD_WIN ~= s,/,\\,g

    # Прежде, чем выполнять сборку инсталляторов, необходимо скопировать файлы
    # из выходной папки проекта вместе со всеми DLL в папку data, которая относится
    # к собираемому пакету
    copydata.commands = $(COPY_DIR) $$PWD_WIN $$DESTDIR_WIN
    first.depends = $(first) copydata
    export(first.depends)
    export(copydata.commands)
    # задаём кастомную цель сборки, при которой сначала выполним компирование файлов
    # а потом уже и остальное, что следует по скрипту QMake
    QMAKE_EXTRA_TARGETS += first copydata
}

# Создаём цель по сборке Оффлайн Инсталлятора
INPUT = $$PWD/config/config.xml $$PWD/packages
offlineInstaller.depends = copydata
offlineInstaller.input = INPUT
offlineInstaller.output = $$INSTALLER_OFFLINE
offlineInstaller.commands = $$(QTDIR)/../../QtIFW2.0.3/bin/binarycreator --offline-only -c $$PWD/config/config.xml -p $$PWD/packages ${QMAKE_FILE_OUT}
offlineInstaller.CONFIG += target_predeps no_link combine

QMAKE_EXTRA_COMPILERS += offlineInstaller

# Создаём цель по сборке Онлайн Инсталлятора
INPUT = $$PWD/config/config.xml $$PWD/packages
onlineInstaller.depends = copydata
onlineInstaller.input = INPUT
onlineInstaller.output = $$INSTALLER_ONLINE
onlineInstaller.commands = $$(QTDIR)/../../QtIFW2.0.3/bin/binarycreator --online-only -c $$PWD/config/config.xml -p $$PWD/packages ${QMAKE_FILE_OUT}
onlineInstaller.CONFIG += target_predeps no_link combine

QMAKE_EXTRA_COMPILERS += onlineInstaller

# репозиторий будем собирать только в случае режима release
CONFIG(release, debug|release) {
    # Сборку репозитория производим после того, как были собраны Инсталляторы
    # Для этого воспользуемся QMAKE_POST_LINK вместо QMAKE_EXTRA_COMPILERS
    # Поскольку он хорошо для этого подходит
    QMAKE_POST_LINK += $$(QTDIR)/../../QtIFW2.0.3/bin/repogen -p $$PWD/packages -i ru.evileg.helloinstaller --update $$OUT_PWD/../../repository
}

DISTFILES += \
    packages/ru.evileg.helloinstaller/meta/package.xml \
    config/config.xml


Скачать архив с проектом Qt Installer Framework example

Видеоурок

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

grig_p
  • 29 марта 2017 г. 8:34

Очень полезная статья. Спасибо большое! Но у меня осталась пара вопросов. Я новичок в QT и не все понятно. Поэтому вопросы будут дилетантские. У Вас уже был создан проект с поддиректориями (это правильно?) включающий в себя проект программы и проект инсталлятора. Для проекта программы, как я понимаю, может при создании быть выбран любой шаблон. А вот, чтобы создать проект инсталлятора, какой должен шаблон необходимо выбрать? Второй вопрос. Вы рассказали про структуру файлов в проекте. Это все понятно. А вот какова должна быть структура файлов на диске. Есть корневой проект HelloInstaller, есть папка HelloInstaller. Есть два подпроекта: Hello и Installer. Так вот вопрос, папки этих проектов должны быть расположены внутри HelloInstaller, рядом с ним, или в любом другом месте? Подозреваю, что можно по разному. Но, на Ваш взгляд, как правильно? Заранее спасибо за ответ!

Evgenii Legotckoi
  • 29 марта 2017 г. 23:19

Да. За основу был взят проект с поддиректориями.

Я не нашёл подходящего шаблона проекта для инсталлятора, поэтому взял пустой проект qmake и уже переписал его для инсталлятора.

Вот как выглядит структура проекта, так и располагаются файлы и папки на диске. То есть есть общая папка HelloInstaller, а в ней уже папки проектов Hello и Installer, а также HelloInstaller.pro. Да, можно, конечно, по-разному. Но не вижу смысла раскидывать подпроекты повсюду, если есть возможность держать всё в одной папке. Будете использовать git-репозиторий, то заметите, что это удобно. Конечно, третьесторонние библиотеки могут располагаться и в другом месте, особенно если они используются в другом проекте, никак не связанном с текущим проектом. Но по-максимуму всё располагайте в одном месте.

grig_p
  • 30 марта 2017 г. 0:20

Огромное спасибо за быстрый ответ.

Evgenii Legotckoi
  • 30 марта 2017 г. 5:25

Пожалуйста. Будут ещё вопросы, спрашивайте. Только задавайте их на форуме , чтобы были отдельные ветки обсуждений.

r
  • 6 декабря 2019 г. 4:48

Певевести бы это на cmake

r
  • 6 декабря 2019 г. 10:37

А что сделать чтоб qt не тащило весь свой гигабайт в инсталлер?

Evgenii Legotckoi
  • 6 декабря 2019 г. 11:40

Конечно, хорошо бы это дело и на cmake сделать, но если честно, в задачах у меня такого нет, а делать специально только это сейчас времени тоже не хватает. увы.

А что касается гигабайта Qt, то обычно QtIFW тянет только необходимое, что в зависимостях и может затащить что-то из QML, если QML не используется, то я бы порылся в документации, скорее всего можно вырубить это. Просто сами все библиотеки, такие как Qt Core имеют вес. Тут никуда не денешься. Но как вариант есть ещё две возможности:

  • Попытаться собрать программу статически, но нужно учитывать особенности лицензии тогда
  • Либо посмотреть в сторону Qt lite , завезли с версией 5,8, но точно не помню - является ли это коммерческой лицензией, а там сами знаете какая цена за Qt.
r
  • 6 декабря 2019 г. 11:46

С cmake разобрался. Всетаки писать на нем сильно проще. С размером dll тоже разобрался. windeployqt надо давать флаг --release (тогда библиотеки в 20 раз меньше)

Evgenii Legotckoi
  • 6 декабря 2019 г. 11:47
  • (ред.)

я подумал насчёт debug/release, но мне показалось - это сильно очевидным ))))

Насчёт cmake согласен - он лучше

Юрий
  • 21 декабря 2019 г. 10:21

Если возможность автоматически ставить версию сборки?

Evgenii Legotckoi
  • 9 января 2020 г. 15:15

Нет, не сталкивался с такой возможностью. Скорее всего только с помощью скриптов это можно решить.

k
  • 9 марта 2020 г. 15:22
  • (ред.)

Здравствуйте. У меня вопрос: как добавить файл ресурсов (.rc) к установщику Hello.offline (в этом файле ресурсов - команда включения файла манифеста и информация, которая должна выводиться при нажатии на свойства файла -> подробно) ? У binarycreator есть флаг -r, после которого можно указать ресурсы. Но файлы таким образом просто включаются в исходник, но не обрабатываются! Пробовал также через утилиту windows sdk mt.exe, но она режит размер установщика. Если нет возможности подключить файл ресурсов, то хотя бы нужно, чтобы установщик мог запускаться только под админом.

Evgenii Legotckoi
  • 10 марта 2020 г. 8:09

Насколько помню, то для получения прав администратора нужно добавить в package.xml следующее

<RequiresAdminRights>true</RequiresAdminRights>

Помимо прочего, при написании скриптов установщика можно запрашивать права через функцию gainAdminRights() , но подробного кейса по применению я уже не помню.

S
  • 18 января 2023 г. 7:03

Hello Evgenij,
regarding the online installer, I've tried many times to use web host for the created repo after repogen step.
I tried using github but I found people talking it is not possible to to be read by qt ifw and tried google cloud source and code berg but both couldn't be read by the installer.
Do you have a recomendation for a certain web host for the repo? I also want it to be private but I don't want to put a password for the account in the config.xml, so is there another way to give authorization for installer to reach the repo without writing password in plain text?
I know this post is very old but I couldn't find any recent reference online, thanks in advanced.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
m

C++ - Тест 003. Условия и циклы

  • Результат:85баллов,
  • Очки рейтинга6
в

C++ - Тест 003. Условия и циклы

  • Результат:50баллов,
  • Очки рейтинга-4
l

C++ - Тест 005. Структуры и Классы

  • Результат:91баллов,
  • Очки рейтинга8
Последние комментарии
k
kmssr9 февраля 2024 г. 5:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 12:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 21:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 19:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 8:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
P
Pisych27 февраля 2023 г. 15:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 января 2024 г. 22:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCT27 декабря 2023 г. 19:57
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
Дмитрий10 января 2024 г. 15:18
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii Legotckoi12 декабря 2023 г. 17:48
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Следите за нами в социальных сетях