Evgenii Legotckoi
Evgenii Legotckoi30 липня 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: Qt5.7mingw53_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 р. 09:34

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

Evgenii Legotckoi
  • 30 березня 2017 р. 00:19

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

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

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

grig_p
  • 30 березня 2017 р. 01:20

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

Evgenii Legotckoi
  • 30 березня 2017 р. 06:25

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

r
  • 06 грудня 2019 р. 05:48

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

r
  • 06 грудня 2019 р. 11:37

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

Evgenii Legotckoi
  • 06 грудня 2019 р. 12:40

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

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

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

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

Evgenii Legotckoi
  • 06 грудня 2019 р. 12:47
  • (відредаговано)

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

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

Юрий
  • 21 грудня 2019 р. 11:21

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

Evgenii Legotckoi
  • 09 січня 2020 р. 16:15

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

k
  • 09 березня 2020 р. 16:22
  • (відредаговано)

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

Evgenii Legotckoi
  • 10 березня 2020 р. 09:09

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

<RequiresAdminRights>true</RequiresAdminRights>

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

S
  • 18 січня 2023 р. 08: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.

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
Дмитрий

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:60бали,
  • Рейтинг балів-1
Дмитрий

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

  • Результат:92бали,
  • Рейтинг балів8
d
  • dsfs
  • 26 квітня 2024 р. 01:56

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
Останні коментарі
k
kmssr08 лютого 2024 р. 15:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко04 лютого 2024 р. 22:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 07:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 05:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 грудня 2023 р. 18:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
G
George1306 травня 2024 р. 21:27
добавить qlineseries в функции в функции: "GPlotter::addSeries(QString title, QVector &arr)" я вызываю метод setChart(...), я в конструктор передал адрес на QChartView элемент
BlinCT
BlinCT05 травня 2024 р. 02:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
PS
Peter Son03 травня 2024 р. 14:57
Best Indian Food Restaurant In Cincinnati OH Ready to embark on a gastronomic journey like no other? Join us at App india restaurant and discover why we're renowned as the Best Indian Food Restaurant In Cincinnati OH . Whether y…
Evgenii Legotckoi
Evgenii Legotckoi02 травня 2024 р. 11:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
IscanderChe
IscanderChe30 квітня 2024 р. 01:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…

Слідкуйте за нами в соціальних мережах