Евгений Легоцкой30 июля 2016 г. 12: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 хостинг.
Поддержать автора Donate

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

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

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

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

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

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

r

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

r

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

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

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

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

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

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

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

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

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

k

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

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

<RequiresAdminRights>true</RequiresAdminRights>

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

Комментарии

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

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг
VD

C++ - Тест 001. Первая программа и типы данных

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

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

  • Результат:64баллов,
  • Очки рейтинга-1
o

C++ - Тест 001. Первая программа и типы данных

  • Результат:86баллов,
  • Очки рейтинга6
Последние комментарии
D:

QML - Урок 016. База данных SQLite и работа с ней в QML Qt

Добрый день, пытаюсь разобраться и подргнать пример под себя. Есть бд с огромным количеством полей. В приложении на виджетах при использовании QTableView все работает и путем простого sql запрос…

Django - Урок 039. Добавление личных сообщений и чатов на сайте - Часть 2 (Счётчик диалогов и чатов с непрочитанными сообщениями)

Добавляйте поле файла в модель сообщения. И в форме сообщения указывайте, что поле с файлом.
s

Django - Урок 023. Like Dislike система с помощью GenericForeignKey

все, я со всем разобрался!) Извините!)
s

Django - Урок 023. Like Dislike система с помощью GenericForeignKey

Доброго времени суток!) Я случайно набрел на вашу статью, и она помогла мне решить некоторые мои трудности, я прошел за вами по шагам, в попытках адаптировать это под себя, и возник вопрос. У ва…
Сейчас обсуждают на форуме
  • BlinCT
  • 7 августа 2020 г. 9:05

Динамическое заполнение StackLayout в qml

Всем привет. Пытаюсь решить такую задачку, есть TabBar и его кнопки. StackLayout{ currentIndex: tabBar.currentIndex A {id: tabA} B {id: tabB} C {id: tabC} D {id: ta…

Наследование QWidget

Добрый день В addWidget нужно ещё указывать номер строки и колонки, куда добаляется виджет. И в вашем случае лучше Диалоговое окно не наследовать сразу от QDialog и W, а наследовать …
М

QML: изменение стиля при наведении и при нажатии на кнопку

enabled = false перестанет быть активной и не будет ни на что реагировать) Хм.. по-моему пробовал такое. Проверю ещё раз после работы. Ура, спасибо большо…
U

Динамическое наполнение StackView QML

Во затупил))) Спасибо за все))) StackView.push("ModuleTip1.qml") ну или в сложной иерархии StackView.push("qrc:/folder/ModuleTip1.qml") и всего делов... Не пойму, почему сра…

QEventLoop тормозит при удалении экземпляра

Думаю, что нет. Лучше вообще без исключений, но не всегда возможно.
О нас
Услуги
© EVILEG 2015-2020
Рекомендует хостинг TIMEWEB