Правильний програміст - це лінивий програміст. Якщо є можливість автоматизувати щось, то обов'язково потрібно це зробити. Наприклад, можна зробити автоматичне виконання 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: 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
Очень полезная статья. Спасибо большое! Но у меня осталась пара вопросов. Я новичок в 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.