Правильный программист - это ленивый программист. Если есть возможность что-то автоматизировать, то обязательно нужно это сделать. Например, можно сделать автоматическое выполнение windeployqt со сборкой онлайн и оффлайн инсталляторов, а также подготовкой репозитория приложения с помощью Qt Installer Framework и чёрной магии QMake .
Для того, чтобы сделать это наиболее красивым образом, необходимо, чтобы проект был организован со структурой subdirs , то есть состоял из нескольких проектов. Нам же не нужно при каждой сборке проекта создавать инсталляторы. Поэтому проект инсталлятора будет отдельным.
Но автоматизацию windeployqt оставим в основном проекте.
Структура проекта
Итак, имеем проект, с двумя подпроектами:
- Hello.pro - проект приложения
- 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
Очень полезная статья. Спасибо большое! Но у меня осталась пара вопросов. Я новичок в QT и не все понятно. Поэтому вопросы будут дилетантские. У Вас уже был создан проект с поддиректориями (это правильно?) включающий в себя проект программы и проект инсталлятора. Для проекта программы, как я понимаю, может при создании быть выбран любой шаблон. А вот, чтобы создать проект инсталлятора, какой должен шаблон необходимо выбрать? Второй вопрос. Вы рассказали про структуру файлов в проекте. Это все понятно. А вот какова должна быть структура файлов на диске. Есть корневой проект HelloInstaller, есть папка HelloInstaller. Есть два подпроекта: Hello и Installer. Так вот вопрос, папки этих проектов должны быть расположены внутри HelloInstaller, рядом с ним, или в любом другом месте? Подозреваю, что можно по разному. Но, на Ваш взгляд, как правильно? Заранее спасибо за ответ!
Да. За основу был взят проект с поддиректориями.
Я не нашёл подходящего шаблона проекта для инсталлятора, поэтому взял пустой проект qmake и уже переписал его для инсталлятора.
Вот как выглядит структура проекта, так и располагаются файлы и папки на диске. То есть есть общая папка HelloInstaller, а в ней уже папки проектов Hello и Installer, а также HelloInstaller.pro. Да, можно, конечно, по-разному. Но не вижу смысла раскидывать подпроекты повсюду, если есть возможность держать всё в одной папке. Будете использовать git-репозиторий, то заметите, что это удобно. Конечно, третьесторонние библиотеки могут располагаться и в другом месте, особенно если они используются в другом проекте, никак не связанном с текущим проектом. Но по-максимуму всё располагайте в одном месте.
Огромное спасибо за быстрый ответ.
Пожалуйста. Будут ещё вопросы, спрашивайте. Только задавайте их на форуме , чтобы были отдельные ветки обсуждений.
Певевести бы это на cmake
А что сделать чтоб qt не тащило весь свой гигабайт в инсталлер?
Конечно, хорошо бы это дело и на cmake сделать, но если честно, в задачах у меня такого нет, а делать специально только это сейчас времени тоже не хватает. увы.
А что касается гигабайта Qt, то обычно QtIFW тянет только необходимое, что в зависимостях и может затащить что-то из QML, если QML не используется, то я бы порылся в документации, скорее всего можно вырубить это. Просто сами все библиотеки, такие как Qt Core имеют вес. Тут никуда не денешься. Но как вариант есть ещё две возможности:
С cmake разобрался. Всетаки писать на нем сильно проще. С размером dll тоже разобрался. windeployqt надо давать флаг --release (тогда библиотеки в 20 раз меньше)
я подумал насчёт debug/release, но мне показалось - это сильно очевидным ))))
Насчёт cmake согласен - он лучше
Если возможность автоматически ставить версию сборки?
Нет, не сталкивался с такой возможностью. Скорее всего только с помощью скриптов это можно решить.
Здравствуйте. У меня вопрос: как добавить файл ресурсов (.rc) к установщику Hello.offline (в этом файле ресурсов - команда включения файла манифеста и информация, которая должна выводиться при нажатии на свойства файла -> подробно) ? У binarycreator есть флаг -r, после которого можно указать ресурсы. Но файлы таким образом просто включаются в исходник, но не обрабатываются! Пробовал также через утилиту windows sdk mt.exe, но она режит размер установщика. Если нет возможности подключить файл ресурсов, то хотя бы нужно, чтобы установщик мог запускаться только под админом.
Насколько помню, то для получения прав администратора нужно добавить в package.xml следующее
Помимо прочего, при написании скриптов установщика можно запрашивать права через функцию gainAdminRights() , но подробного кейса по применению я уже не помню.
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.