WiX.Py — кроссплатформенная сборка MSI пакетов

в 17:33, , рубрики: devops, open source, python

Вступление

Начнем с терминов:

  • MSI — штатный формат для распространения ПО в MS Windows.
  • WiX — сокращенное название от WiX Toolset (Windows Installer XML).

На данный момент, WiX стал де-факто стандартным инструментом для создания MSI пакетов. К нему существует масса вспомогательных инструментов-оберток, решающих те или иные задачи (python-wix, go-msi, electron-wix-msi и пр.). Иными словами, изготовление MSI пакетов оформилось в небольшую отдельную отрасль со своими гуру, сертификациями и прочими сопутствующими атрибутами. И у Windows-программистов под руками широкий выбор инструментария для создания инсталляторов ПО. На Хабре уже не раз были статьи, посвященные WiX: 1, 2, 3, 4.

Но существующий подход WiX страдает двумя серьезными недостатками:

  1. Необходимые для создания MSI пакета WXS файлы чудовищно громоздки и весьма низкоуровневые — их написание требует специфических знаний Windows Registry, внутренней базы MSI пакета и прочих нюансов. Для крупных проектов и корпораций держать отдельных специалистов по изготовлению инсталляторов не проблема. Для средних и мелких проектов это становится достаточно высоким порогом вхождения.
  2. Сборка MSI пакетов с использованием WiX требует обязательно Windows сервер. Для разработчиков под MS Windows этот пункт покажется странным, но для тех, кто участвует в кросс-платформенных проектах, дополнительный "железный" Windows-сервер или Windows-инстанс в облаке, как пятое колесо в телеге: и в плане затрат и в плане интеграции.

Разработчики поневоле

Прим.: если нет желания читать про историю создания WiX.Py, можете пропустить этот раздел.

Собственно говоря, именно с этими проблемами наш проект кросс-платформенного редактора векторной графики и столкнулся пару месяцев назад. Сборка пакетов для различных дистрибутивов Linux была переведена на Docker-контейнеры, что соответствует современному положению вещей в Continuous Integration сервисах. Даже portable-версию для Windows смогли собирать в докере. И только сборка MSI пакетов вызывала несуразные затраты по ресурсам и времени.

Естественно, пламенного желания "написать все свое" не возникало — у проекта своя область деятельности, далекая от системного программирования на Windows, и всегда есть чем заняться, помимо изучения MSI. Попытка запустить WiX на wine в Docker-контейнере потерпела полное фиаско. Эмулятор wine оказался завязанным на оконную систему, WiX сегфолтился и не желал заводиться. Одним словом, сплошное разочарование.

Поиск в гугле привел к малоизвестному проекту wixl производства компании Red Hat, который отличался ну очень чрезмерной скромностью в плане документирования. Пробы показали — он создает работающие MSI пакеты на Linux без каких-либо эмуляторов на основе WXS-файлов для WiX! Воодушевленные такой удачей, мы быстро набросали на python модель WXS документа, т.к. программисты — люди ленивые и писать руками огромные XML-полотнища для них скучно и утомительно. Первые рабочие сборки отлично прошли, но когда начались вопросы по нюансам (как сделать валидным MSI пакет, как добавить переменную окружения при установке и т.п.), тут то и выяснилось, что wixl весьма и весьма недоделан. Многие вещи отсутствую принципиально и решаются только изменением исходного кода.

Изучение исходников показало, что wixl написан на языке программирования Vala, который транслируется в C и потом компилируется в бинарный код. Попытка собрать wixl из исходников, полученных из репозитория, на Ubuntu 16.04 успехом не увенчалась. Изучение пакета wixl с исходниками для Ubuntu 16.04 обнаружило запись: "Из Vala исходников не собирается, компилировать из сишных стабов". Честно говоря, так и не разобрались — то ли авторы плохо подготовили сборочные скрипты, то ли Red Hat намеренно выдает такой обфусцированный полуфабрикат, непригодный для модификации. Не владея хорошо ЯП, делать какие-либо выводы сложно.

Тут опять встала дилемма — погрузится в изучение Vala (а это 2-3 месяца времени) или искать какой-либо обходной путь. Просмотр исходников показал, что часть кода wixl это разбор XML, другая часть это построение модели WXS документа (а мы модель к этому времени уже написали на python) и совсем небольшая часть — собственно сама генерация MSI пакета из построенной модели с использованием libmsi. После тяжелых раздумий, таки решили — будем пробовать генерировать MSI самостоятельно, жалко было бросать "непосильным трудом" созданную модель. Почти сразу же обнаружилось, что у libmsi есть gobject-introspection интерфейс. Это позволяло использовать библиотеку без написания отдельного нативного расширения для python. Используя код wixl, мы буквально за несколько дней перенесли всю логику на python, исправив кучу недостатков и добавив нужный функционал. Так и получился из простенькой обертки отдельный проект.

Параллельно обратили внимание на distutils-команду bdist_msi в python. Она есть только в дистрибутиве python для Windows и тоже в весьма сыром и практически недокументированном состоянии. Но интерес представлял не код этой команды, а нативное расширение _msi.pyd, которое идет в дистрибутиве python для Windows и служит привязкой к штатной системной msi.dll. Его интерфейсы оказались весьма похожими на libmsi, что не удивительно, т.к libmsi делалась по образцу и подобию msi.dll. Мы подключили его к проекту WiX.Py и это позволило нам сделать проект кросс-платформенным, не затрачивая серьезных усилий.

Формат исходных данных, в виде JSON-файла, получился сам собой. В процессе построения модели мы все исходные уникальные данные (имя приложения, версию и т.п.) сохраняли как питонский словарь. А его сериализация это и есть JSON. Поскольку уникальных данных для построения модели совсем немного, у нас получился сборщик MSI с весьма низким порогом вхождения — нет необходимости погружаться в бездну MSDN, чтобы создать MSI пакет для небольшого десктопного приложения. Заполнить по образцу JSON-файл под силу даже простому Windows-пользователю.

Как WiX.Py работает

Процесс создания MSI пакета упрощен до предела:

  1. В произвольной папке (напр.build) формируем содержимое для инсталляции в том виде, в котором оно будет на целевом компьютере.

  2. Заполняем небольшой JSON-файл. Например:

    {
    "Name": "MyApp",
    "UpgradeCode": "3AC4B4FF-10C4-4B8F-81AD-BAC3238BF690",
    "Version": "0.1",
    "Manufacturer": "My Company",
    "Description": "MyApp 0.1 Installer",
    "Comments": "Licensed under GPLv3+",
    "Keywords": "wxs, xml, build",
    "Win64": true,
    "_CheckX64": true,
    "_AppIcon": "resources/myapp.ico",
    "_ProgramMenuFolder": "My Company",
    "_Shortcuts": [
        {"Name": "MyApp",
        "Description": "MyApplication",
        "Target": "myapp.exe",
        "AddOnDesktop": true,
        "OpenWith": [".xml", ".wxs", ".yml"]
        }
    ],
    "_SourceDir": "build/",
    "_InstallDir": "myapp-0.1",
    "_OutputName": "myapp-0.1_win64.msi",
    "_OutputDir": "./"
    }

  3. Генерируем MSI пакет командой:

    wix.py <имя_файла>.json

Да, это полностью рабочий пример MSI пакета, который проверит перед установкой, что Windows 64bit, установит приложение в Program Files, добавит приложение в программное меню и на рабочий стол, и привяжет указанные форматы файлов к устанавливаемому приложению. Никакой магии и шаманства с регистром.

И сборку можно проводить как в Windows, так и в Linux, в т.ч. в Docker-контейнерах. Поскольку libmsi может быть собрана на многих других UNIX-системах (напр. macOS), формально WiX.Py можно и на них использовать, только практического смысла это не несет.

Описание различных нюансов вы можете найти в документации проекта.

Заключение

Если вам подходит WiX.Py для решения задач по созданию MSI пакетов, но не хватает какого-либо функционала, заходите на наш сайт https://wix.sk1project.net и создавайте запрос на расширение функционала. Тоже самое касается обнаруженных багов — сообщайте и мы будем с ними бороться. Там же на сайте найдете исходный код и готовые пакеты для разных платформ.

Автор: весёлый усач

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js