Ранее в серии Qt мы видели, как объединить кроссплатформенную мощь Qt с инфраструктурой CI/CD Github Actions для создания двоичных файлов для Windows, MacOs и Linux.

В этом эпизоде ​​мы увидим, как создать установщик для доставки нашей программы клиентам. Это основной способ распространения в Windows, но его также можно сделать для MacOs и Linux.

Qt Installer Framework поддерживает два типа установщиков:

  • Offline: простой исполняемый файл, который устанавливает двоичный файл и его зависимости.
  • В сети: исполняемый файл, который вызывает веб-сервер, сравнивает версию программы с текущей и устанавливает соответствующую версию с веб-сервера. Эта версия позволяет выполнять простые обновления для пользователя.

В этом эпизоде ​​мы создадим простой установщик только с одним пакетом и настраиваемым компонентом. Установщик выполнит три задачи:

  1. Принятие лицензии.
  2. Установка программы в указанное место.
  3. Открытие программы и документации.

Архитектура

Во-первых, нам нужно создать необходимые папки и файлы конфигурации, которые будут следовать этой архитектуре (для нескольких пакетов инкапсулируйте каждый пакет в подпапку внутри каталога пакетов):

ProgramInstaller 
│
└───packages
│   │
│   └───Program
│       │
│       └───data
│       │   │ data.7z 
│       │        
│       └───meta
│           │ license.txt
│           │ installscript.qs
│           │ package.xml
│           │ readmecheckboxform.ui
│
└───config
    │   config.xml

Лицензия.txt

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

Установить.qss

Файл install.qss определяет компонент, отображаемый в процессе установки. В функции Component.prototype.createOperations мы определяем два ярлыка: один для исполняемого файла программы, второй для Qt MaintenanceTool, который появится в стартовом меню. Следующие две функции отобразят пользовательский компонент, который откроет программу и документацию, если пользователь установит флажки.

function Component()
{
    installer.installationFinished.connect(this, Component.prototype.installationFinishedPageIsShown);
    installer.finishButtonClicked.connect(this, Component.prototype.installationFinished);
}

Component.prototype.createOperations = function()
{
    component.createOperations();
    if (systemInfo.productType === "windows") {
        component.addOperation("CreateShortcut", "@TargetDir@/Program.exe", "@StartMenuDir@/Program.lnk",
            "workingDirectory=@TargetDir@","iconPath=@TargetDir@/icon.ico", "description=Program Icon");
        component.addOperation("CreateShortcut", "@TargetDir@/maintenancetool.exe", "@StartMenuDir@/ProgramUpdater.lnk",
            "workingDirectory=@TargetDir@", "description=Program Updater");
        installer.setDefaultPageVisible(QInstaller.LicenseCheck, false);
    }
}

Component.prototype.installationFinishedPageIsShown = function()
{
    try {
        if (installer.isInstaller() && installer.status == QInstaller.Success) {
            installer.addWizardPageItem( component, "ReadMeCheckBoxForm", QInstaller.InstallationFinished );
        }
    } catch(e) {
        console.log(e);
    }
}

Component.prototype.installationFinished = function()
{
    try {
        if (installer.isInstaller() && installer.status == QInstaller.Success) {
            var isReadMeCheckBoxChecked = component.userInterface( "ReadMeCheckBoxForm" ).readMeCheckBox.checked;
            if (isReadMeCheckBoxChecked) {
                QDesktopServices.openUrl("https://siteofdoc.html");
            }
            var isStartCheckBoxChecked = component.userInterface( "ReadMeCheckBoxForm" ).startCheckBox.checked;
            if (isStartCheckBoxChecked) {
                installer.executeDetached("@TargetDir@/Program.exe");
            }
        }
    } catch(e) {
        console.log(e);
    }
}

Пакет.xml

Файл package.xml — это файл конфигурации единственного пакета, содержащегося в нашем установщике.

<?xml version="1.0" encoding="UTF-8"?>
<Package>
    <DisplayName>Program package</DisplayName>
    <Description>Program package</Description>
    <Version>0.0.0</Version>
    <ReleaseDate></ReleaseDate>
    <Licenses>
    <License name="GNU GENERAL PUBLIC LICENSE" file="license.txt" />
    </Licenses>
    <Default>true</Default>
    <Script>installscript.qs</Script>
    <UserInterfaces>
        <UserInterface>readmecheckboxform.ui</UserInterface>
    </UserInterfaces>
</Package>

Readmecheckboxform.ui

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

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ReadMeCheckBoxForm</class>
 <widget class="QWidget" name="ReadMeCheckBoxForm">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>412</width>
    <height>179</height>
   </rect>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <property name="leftMargin">
    <number>0</number>
   </property>
   <property name="topMargin">
    <number>0</number>
   </property>
   <property name="rightMargin">
    <number>0</number>
   </property>
   <property name="bottomMargin">
    <number>0</number>
   </property>
   <item>
    <widget class="QCheckBox" name="readMeCheckBox">
     <property name="text">
      <string>Open the User manual</string>
     </property>
     <property name="checked">
      <bool>true</bool>
     </property>
     <property name="tristate">
      <bool>false</bool>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QCheckBox" name="startCheckBox">
     <property name="text">
      <string>Start Program</string>
     </property>
     <property name="checked">
      <bool>true</bool>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

Данные.7z

Архив data.7z будет содержать бинарник и зависимости пакета, который будет распакован в папку установки.

Конфиг.xml

config.xml — это файл конфигурации для установщика. Если вам нужен интерактивный установщик, необходимо добавить URL-адрес, доступный для установщика, в теге ‹Url›‹/Url›. На следующем шаге мы увидим, что добавить в конечную точку этого URL.

<?xml version="1.0" encoding="UTF-8"?>
<Installer>
    <Name>Program</Name>
    <Version>0.0.0</Version>
    <Title>Program Installer</Title>
    <Publisher>MyCompany</Publisher>
    <StartMenuDir>Program</StartMenuDir>
    <TargetDir>@HomeDir@/Program Files (x86)/Program</TargetDir>
    <RemoteRepositories>
    <Repository>
        <Url>url to remote repo</Url>
    </Repository>
</RemoteRepositories>
</Installer>

Действия на GitHub

Теперь мы автоматизируем создание этого установщика с помощью действий GitHub и повторного использования концепций, которые мы видели в эпизоде ​​1 серии Qt.

Как обычно, мы создаем новый файл .github/workflows/installer.yml.

│.github
│
└───workflows
│   │   installer.yml
│
ProgramInstaller 
│
└───packages
│   │
│   └───Program
│       │
│       └───data
│       │   │ data.7z 
│       │        
│       └───meta
│           │ license.txt
│           │ installscript.qs
│           │ package.xml
│           │ readmecheckboxform.ui
│
└───config
    │   config.xml

installer.yml:

name: Continous Builds

on:
  push:
    branches: [master]

jobs:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
        with:
          python-version: '3.8'
      - name: install qt6
        run: |
          pip install aqtinstall
          python3 -m aqt install-qt -O ${{ github.workspace }}/Qt/ windows desktop 6.2.0 win64_msvc2019_64
          python3 -m aqt install-tool -O ${{ github.workspace }}/Qt/ windows desktop tools_ifw
          echo "${{ github.workspace }}/Qt/6.2.0/msvc2019_64/bin/" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
          echo "${{ github.workspace }}/Qt/Tools/QtInstallerFramework/4.1/bin/" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
      - name: build
        shell: cmd
        run: |
          repogen.exe -p .\ProgramInstaller\packages OnlineRepository
          binarycreator.exe -p .\ProgramInstaller\packages -c .\Program\config\config.xml ProgramInstaller
      - name: Windows artefact
        uses: actions/upload-artifact@v1
        with:
          name: ProgramInstaller
          path: ./ProgramInstaller.exe
      - name: Windows artefact
        uses: actions/upload-artifact@v1
        with:
          name: OnlineRepository
          path: ./OnlineRepository

Первые шаги Действия аналогичны тому, что мы видели в S01E01. Дополнительным шагом является установка Qt Installer Framework с помощью aqtinstall с командой install-tool.

Мы используем repogen.exe для создания онлайн-репозитория, который мы должны разместить на веб-сервере в конечной точке URL, который мы указали в файле config.xml. .

Затем мы используем binarycreator для создания установщика. По умолчанию установщик будет содержать все компоненты и может получить доступ к онлайн-репозиторию, если URL-адрес был указан заранее. В результате его можно установить как с подключением к Интернету, так и без него. Можно создать автономный установщик, добавив параметр — только офлайн, и онлайн-установщик, который не содержит никаких компонентов и требует подключения к Интернету, добавив — параметр только онлайн.

Вывод

Автоматическое создание автономных и онлайн-установщиков с использованием Qt Installer Framework и GitHub Actions — это просто и упростит процесс доставки нашего приложения клиенту. В следующем эпизоде ​​мы подробно рассмотрим онлайн-репозиторий, который мы создали с помощью онлайн-установщика. Что с этим делать? Как им управлять? Как клиент может обновить Программу?