Установщик - удобная штука, которая часто оказывается даже более предпочтительным вариантом для компактного переноса данных, чем zip-архив, и абсолютно превалирующим в тех случаях, когда подразумевается распространение своего продукта вовне.
В этой статье процесс создания установщика я буду рассматривать с оглядкой на OneScript: в качестве примера выступит установщик OInt CLI, а отдельное внимание будет уделено особенностям установки именно CLI приложений - в принципе единственного на сегодняшний день адекватного варианта приложения на OS. Потому что выпускать CLI приложение без установщика или даже, как это часто бывает, без exe файла в релизе это не круто - не делайте так
Работать мы будем с программным пакетом Inno Setup - с его рассмотрения и начнем
Inno Setup - это система создания Windows-установщиков с открытым исходным кодом. Она написана на Pascal и Pascal же (с оговорками) используется в ней для описания установочных скриптов. В этих скриптах можно:
- Описать информацию о приложении, версии, авторе и пр.
- Определить набор файлов: исполняемых, вспомогательных, файл лицензии, иконки и пр.
- Определить пред- и постустановочные действия, команды системы и изменения записей в реестре
- Описать некоторую логику (функции) на языке Pascal
Все это делается либо просто в текстовом редакторе, либо в редакторе скриптов Inno Setup Compiler, где есть кнопки для быстрой сборки и даже отладчик с точками останова
Скачать Inno Setup: https://jrsoftware.org/isdl.php
Сам скрипт делится на секции, по которым мы сейчас и пройдемся
Начало документа
До начала именных блоков, в iss-скрипте мы можем определить глобальные переменные. Это удобно по отношению к тем данным, которые могут неоднократно использоваться в процессе описания установщика
Делается это при помощи ключевого слова #define:
#define MyAppName "OInt"
#define MyAppVersion "1.9.0"
#define MyAppPublisher "bayselonarrend"
#define MyAppURL "https://github.com/Bayselonarrend/OpenIntegrations"
#define MyAppExeName "oint.exe"
#define OtherFiles "D:\GD"
#define Repo "D:\Repos\OpenIntegrations"
В дальнейшем мы можем использовать эти переменные через конструкцию {#ИмяПеременной}
Блок Setup
В блоке Setup определяются основные настройки нашего установщика. Делается это через присвоение значений параметрам-переменным, коих очень много, но для себя я пришел к определенному их набору, который скорее всего подойдет для решения большинства задач. Вот он:
[Setup]
DisableWelcomePage=no
WizardImageFile={#Repo}\Media\WizardImage.bmp
AppId={{E1D44D44-2C84-4112-80AA-2DC406D85A11}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
LicenseFile={#Repo}\LICENSE
OutputDir="{#OtherFiles}\Релизы\{#MyAppVersion}"
Compression=lzma
SolidCompression=yes
WizardStyle=modern
OutputBaseFilename=oint_{#MyAppVersion}_installer
Рассмотри всё по-порядку:
- DisableWelcomePage - параметр, отвечающий за показ страницы приветствия. Так как на этой странице отображается некоторая информация о приложении, а еще там можно показать какую-нибудь красивую картинку, было решено её включить
- WizardImageFile - картинка формата bmp, которая отображается сбоку на странице приветствия и завершения установки. Добавляет индивидуальность установщику - это важно. Подходящие размеры для картинки это 410x797, 355x700, 328x604. Минимальный размер 164x314
- AppId - это UID, который необходим для определения связи между разными установщиками и установленной программой. Например, если при выходе новой версии вашей программы вы заново скомпилируете установщик, то при совпадающих AppId этот установщик перезапишет данные прошлой версии, а если нет - установит её как абсолютно новый продукт. В частности это видно в разделе "Программы и компоненты" Windows
- AppName - имя приложения, которое будет отображаться в установщике, а также в меню Пуск после установки
- AppVersion - версия программы. Будет видна в установщике, а также в разделе программ и компонентов
- AppVerName - вариант отображения в тех местах, где имя и версия находятся в одной строке. Необязательный пункт
- AppPublisher - информация (в частности имя) об издателе приложения. Если не указать - будет вылазить окно с красным щитом о недостоверности издателя
- AppPublisherURL, AppSupportURL, AppUpdatesURL - различные URL издателя. Их также можно найти в программах и компонентах
- DefaultDirName - путь, который будет отображаться по умолчанию в строке выбора места установки. {autopf} здесь - служебная константа, автоматически определяющая путь к Program Files
- DefaultGroupName - имя, которое будет использовано для папки программы в меню Пуск по умолчанию
- DisableProgramGroupPage - признак отображения страницы выбора имени группы. Мне функционал выбора имени папки программы в меню Пуск кажется излишним, но это субъективно. При отключенной странице будет просто использован DefaultGroupName
- LicenseFile - путь к текстовому файлу лицензионного соглашения. Его содержимое будет показано перед началом установки
- OutputDir - каталог для сохранения готового установщика
- Compression - способ сжатия файлов в установщике
- SolidCompression - уменьшает размер установщика, но при этом и скорость распаковки
- WizardStyle - определяет стиль установщика: modern (как на скриншотах) или classic. Хотя разница, на самом деле, не велика - серый цвет и чуть меньшее окно
- OutputBaseFilename - имя выходного файла установщика
Блок Languages
Тут все просто: язык установщика по умолчанию - английский. Если же мы хотим, чтобы наш установщик был на русском, то необходимо добавить следующий блок:
[Languages]
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
Языков можно добавлять несколько. Их список определяется файлам в папке Languages каталога программы. Английский язык добавляется как Default.isl
[Languages]
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl"
Name: "english"; MessagesFile: "compiler:Default.isl"
Блок Files
Блок Files определяет список файлов, которые будут использованы для создания установщика. Каждый файл может быть описан полями Source - путь к файлу на диске, DesDir - путь, по которому файл будет распакован при установке и Flags - различные отметки об необходимости особой обработки файла
[Files]
Source: "C:\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\start.bat"; DestDir: "{app}"
В качестве DestDir тут выступает {app} - путь установки приложения, выбранный пользователем. Если мы хотим, чтобы файл распаковался в какую-нибудь подпапку, то можно отталкиваться от него: {app}/Папка2. Также доступны абсолютные пути и другие переменные
Флаг ignoreversion у исполняемого файла означает, что при установке поверх прошлой версии файл будет перезаписан в любом случае, даже если вы сначала установите более новую версию программы, а после - более старую
Блок Tasks
Блок Tasks позволяет выводить чекбоксы пользователю перед началом установки. Например, вопрос о создании ярлыка на рабочем столе
[Tasks]
Name: desktopicon; Description: "Создать ярлык на рабочем столе";
Далее мы можем использовать сделанный пользователем выбор в качестве условия для выполнения каких-либо действий
Блок Icons
Блок Icons отвечает за создание иконок, в частности на рабочем столе и в меню пуск
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\start.bat"; IconFilename: "C:\Media\ex.ico"
Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\start.bat"; IconFilename: "C:\Media\ex.ico"; Tasks: desktopicon
Name: "{group}\Удалить OInt"; Filename: "{uninstallexe}"; IconFilename: "C:\Media\wizard.ico"
Name: "{group}\Web-документация"; Filename: "https://www.openintegrations.dev/"
Что мы здесь видим?
- В поле Name указывается целевой путь для размещения иконки с её именем в конце. Здесь он начинается либо с {group} - папка программы в меню Пуск, либо с {userdesktop} - переменной, хранящей путь к рабочему столу пользователя
- В Filename определяется путь к файлу или URL, на который буде ссылаться ярлык. Пути определяются как DestDir в Files, а URL должен иметь протокол и www.
- Для каждого ярлыка можно установить свой файл иконки при помощи параметра IconFilename. Иконки должны быть в формате ico
- Параметр Tasks определяет проверки на чекбоксы, о которых мы говорили в прошлом блоке. Здесь для второй иконки определено Tasks: desktopicon - по имени таски, которую мы добавили для чекбокса создания ярлыка на рабочем столе
Блок Run
Блок Run отвечает за запуск команд в разные моменты работы нашего установщика. Нас этот блок интересует как способ создать предложение о запуске чего либо по завершении установки
[Run]
Filename: "{cmd}"; Parameters: "/k ""cd ""{app}"" && {#MyAppExeName}"""; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall
Filename: "https://openintegrations.dev/docs/Nachalo-raboty/Rabota-s-CLI-versiei"; Flags: shellexec runasoriginaluser postinstall; Description: "Посетить страницу документации openintegrations.dev"
Также, как и в случае с иконками, мы можем указывать в качестве целевого объекта файлы и URL, однако, помимо этого, нам еще становятся доступны консольные команды
В первой строке идет запуск одной из них: как я уже говорил, наш текущий пример нацелен на запуск CLI приложения. Для того, чтобы окно командной строки не закрылось тут же после запуска, нам необходимо сначала запустить cmd, а только потом, внутри него, командой запустить само приложение. На данный момент еще не будет прописан Path, так что мы сначала делаем cd в каталог приложения, а потом вызываем exe
Далее в Description определяется действие по замене заголовка окна консоли на имя нашей программы. Флаги nowait и postinstall означают, что при наличии нескольких выбранных пользователем действий после установки, они выполнятся без ожидания завершения работы текущего: у нас это будет "Посетить страницу документации", так вот, если не указать postinstall, то страница не откроется, пока не будет закрыт cmd с нашей программой
Второе действие - с переходом по ссылке на страницу документации - во многом схоже, только тут еще добавляются флаги shellexec - замена вызову команды через cmd, если сам cmd не нужен, а также runasoriginaluser - выполнение команды от имени текущего пользователя
Блоки Registry и Code
Несмотря на всю гибкость блока Code - буквально компилятора, который способен выполнять код на Pascal, я не нашел особо другого его применения, кроме как для написания функции проверки данных в реестре, по-этому объединил их в один пункт
Registry нужен нам для добавления каталога программы в Path, что позволит вызывать наше CLI приложение из любого места просто по имени. Если у вас не CLI приложение, то данный пункт можно пропустить.
Способ добавления Path через Registry - не самый простой, но зато самый надежный. Дело в том, что при выполнении этого действия через командную строку, есть вероятность переполнения Path - при прямом доступе он ограничен по длине, что может привести не только к некорректному добавлению вашего пути, но и к урезанию уже существующих там каталогов, если они были забиты через реестр
Единственный минус: Path через реестр обновляется только после перезагрузки компьютера. Но ничего не поделаешь
[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; \
Check: NeedsAddPath(ExpandConstant('{app}'))
Тут мы обращаемся к HKLM в переменные CurrentControlSet и перезаписываем Path, соединяя {olddata} - прошлое значение переменной и {app} - каталог нашего приложения через точку с запятой. Однако делается это только после прохождения Check - функции, описанной в блоке Code
[Code]
function NeedsAddPath(Param: string): boolean;
var
OrigPath: string;
Element: string;
begin
if not RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
'Path', OrigPath)
then begin
Result := True;
exit;
end;
Element:= ';' + Param + ';';
Result := Pos(Element, ';' + OrigPath + ';') = 0;
end;
Эта функция - NeedsAddPath - проверяет Path на наличие добавляемого пути. Если данного пути в Path нет, то возвращается Истина и путь добавляется. В противном случае, если путь уже есть в переменной, то Check не проходит и второй раз наш каталог в Path не попадет. Это позволяет не плодить копии пути к папке приложения при обновлении программы или если программы была удалена и установлена заново
В заключение
Установщик - отличный способ распространения файлов, а создать его - разве что чуть сложнее, чем архив. Сформированный один раз .iss скрипт можно использовать в дальнейшем сколько угодно раз, в том числе и из командной строки
"C:\Program Files (x86)\Inno Setup 6\Compil32.exe" /cc "C:\myscript.iss"
Пример скрипта из статьи можно найти в репозитории Открытого пакета интеграций. Там же можно посмотреть и готовый установщик
Спасибо за внимание!
Мой GitHub: https://gitub.com/Bayselonarrend Лицензия MIT: https://mit-license.org