Добрый день всем! Хочется поделиться со всеми своим опытом создания системы для генерации патчей (да простит меня читатель за использование этого слова). Про wix довольно много было написано здесь и я предполагаю, что читатель немного знаком с ним, а вот проблему создания патчей как-то обошли. В нашей же компании они нашли широкое применение, в основном из-за своего размера, а также из-за возможности отката.
Для начала я опишу задачу, с которой я столкнулся, затем опишу базовую информацию для создания патчей. Также сделаю 2-ю часть с конкретным примером, утилитами для упрощения этого процесса и исходниками.
Задача:
1. Есть узкоспециализированный desktop продукт, который устанавливается по многим субъектам РФ в многие филиалы инстанции, связанной с лесным хозяйством. Давайте для удобство дадим имя этому условному продукту, допустим Ясень.
2. Для каждого субъекта собирается своя версия инсталляции, которые не отличаются бинарниками, а отличаются контент файлами, которые не меняются (карты, шаблоны отчетов, базовая БД и т.д.).
3. В «горячий» сезон обновления выпускаются раз в 1-2 недели (всем резко становится что-то нужно и часто что-то новое). При этом версия продукта меняется (либо major, minor или build)
4. В любой момент любой клиент может попросить как обновление, так и полную версию. Надо выйти из положения минимальными затратами.
5. ClickOnce не подходит, так как некоторые госструктуры, как ни странно, не имеют прямого выхода в интернет, либо он очень плох.
6. Сделать все на Wix, так как он бесплатен.
В этих условиях (пункт 4!), чтобы экономить трафик и не слать каждый раз по 100 Мб полной версии много раз лучше делать патчи.
Есть и пара прихотей разработчиков:
1. А также хочется свободы в пропусках патчей, то есть можно, например, обновляться через 1 или 2 обновления.
2. На схеме возможных обновлений эта прихоть выглядит так:
То есть, разрешены все варианты последовательности:
• Последовательно получать все патчи, потом получить полную msi (сплошная линия).
• Пропустить 2 патча, поставить 3-й, а потом поную msi (пунктирная).
• Поставить 1 патч, 1 пропустить, следующий поставить и получить полную версию msi.
• и т.д.
Давайте начнем с начала.
Разберемся версиями и с патчами
Как известно, версия в Wix состоит (=учитывает) из 3-х чисел: x.y.z, x — Major version, y — Minor version, z — Build number. Принцип изменения этих значений, на мой взгляд, определяется компанией, и какого-то явного правила нет, только — рекомендации.
В то же время, существует 3 типа обновлений:
- Major update (правильнее — Major upgrade, но чтобы было единство давайте отставим major update):
Major update это комплексное обновление продукта, которое затрагивает структуру устанавливаемых фич, меняет состав и название компонент и т.д. Major update удаляет предыдущую версию приложения и устанавливает новую. - Minor update (правильнее — Minor upgrade, но чтобы было единство давайте отставим minor update):
Minor update затрагивает многие ресурсы инсталляции, однако ни одно из них не требует изменения ProductCode (об этом чуть ниже). Minor update может добавлять новые фичи или компоненты, но не может реорганизовывать дерево фич и компонентов, то есть не может удалять фичи и компоненты и перемещать компоненты из одной фичи в другую. - Small update:
Small update это маленькое обновление которое затрагивает как правило несколько файлов, изменяя их содержимое.
«База» для обновлений в Wix
Реализации перечисленных вариантов обновления в Wix базируется на 2-х переменных:
1. Атрибут UpgradeCode элемента Product.
2. Атрибут ProductCode элемента Product.
Про Package.Id мы не говорим, так как он меняется почти всегда. Подробно о том, когда менять UpgradeCode и ProductCode, написано тут и тут.
Вкратце это так:
- UpgradeCode продукта одного поколения обычно не меняется. Как только система переписана полностью, коренным образом, например, изменены применяемые технологии, то UpgradeCode меняют, отсекая все ранее сделанные пакеты обновления.
- ProductCode меняется, если изменены имя msi пакета, удалены или изменены Component’ы. Если в новой версии просто поменялись уже существующие файлы или добавился новый Component, то ProductCode не меняют.
Пример
Пусть есть уже установленное приложение версии 1.0, мы создаем следующую версию инсталляции. Мы можем в ней поменять UpgradeCode и ProductCode на новое значение относительно предыдущей версии.
Давайте посмотрим, что получится, если мы поменяемоставим старым значения этих атрибутов. Наш успех при попытке (1) создать пакет и (2) установить его отражен в таблице ниже:
Мы получим: * — minor update, ** — major update, *** — small update
Общий подход создания патча
Сама процедура создания патча в общем выглядит очень просто: есть одна или несколько «базовых» сборок и одна «конечная». Утилита генерации патчей создает пакет, который умеет обновлять продукт с версий, которые содержатся в «базовых» сборках, до версии, которая содержится в «конечной».
Как известно, Wix поддерживает 2 технологии создания патчей: используя PatchWiz.dll и сам Wix. Я не буду глубоко залезать в разборы всех достоинств и недостатков этих вариантов. Это не является целью статьи. Скажу лишь, что в результате проведенных опытов мы остановились на первом варианте (т.к. только на нем смогли получить удовлетворяющий нас результат).
Создание патча на Wix при помощи PatchWiz.dll
Для создания патча с использованием PatchWiz (будем использовать утилиту msimsp.exe) необходимо минимум 2 инсталляционных пакета (точнее 2 msi файла) и описатель патча (обычно файл называют Patch.wxs). Я не буду подробно описывать все возможности, которые им предусмотрены, иначе статья получится слишком большой, но основные моменты затрону. (Подробности можно накопать тут)
1. Сначала создается Patch.wxs, а в нем элемент PatchCreation.
<PatchCreation
Id="{42D7EE3B-A712-4AD4-9B23-A8710FC486FA}"
Codepage="1251"
CleanWorkingFolder="yes"
OutputPath="patch.pcp"
WholeFilesOnly="yes">
Здесь:
Id – уникальный Id патча, всегда новый.
Codepage – кодовая страница для промежуточного (для нас) файла с расширение PCP.
CleanWorkingFolder – очищать временную папку после создания патча.
OutputPath – путь и имя промежуточного файла.
WholeFilesOnly – в патч будем включать изменившиеся файлы целиком, а не только изменившиеся блоки в них.
Внутрь PatchCreation записываются элементы с информацией о патче, здесь, я думаю, все понятно (PatchMetadata – не обязательный элемент).
<PatchInformation
Description="Обновление Ясень"
Comments="Обновление Ясень до 1.1"
Manufacturer="Рога и копыта"/>
<PatchMetadata
AllowRemoval="yes"
Description="Обновление Ясень"
ManufacturerName="Рога и копыта"
TargetProductName="Ясень"
MoreInfoURL="http://рогаикопыта.рф/"
Classification="Update"
DisplayName="Обновление Ясень до версии 1.1"/>
И, пожалуй, главное: указываем пути, где лежат базовые сборки и конечная.
<Family DiskId="2" Name="Yasen" SequenceStart="5000">
<UpgradeImage SourceFile="C:WorkYasenv1.1Setup.msi" Id="NewPackage">
<TargetImage SourceFile="C:WorkYasenv1.0.8Setup.msi" Order="2" Id="BasePackage1"/>
<TargetImage SourceFile="C:WorkYasenv1.0Setup.msi" Order="3" Id="BasePackage2"/>
</UpgradeImage>
</Family>
Здесь:
DiskId – номер новой записи в таблице Media, не должен совпадать с Media Id базового установщика.
Name – имя линейки обновлений. Обновлять один продукт обновлениями с разными Family Name я не пробовал.
SequenceStart – номер для записи в таблице InstallExecuteSequence, не должен совпадать с существующими записями. В большинстве примеров, которые я видел, стоит 5000. Это значение не конфликтует со стандартными значениями Wix, в нашем пакете тоже это значение не используется. (Подробно про это есть в статьи про wix, перечисленные выше)
Элемент UpgradeImage – описывает конечную сборку.
SourсeFile – путь к конечной сборке (на самом деле не совсем на нее, а на ее «распакованную» версию, об этом написано ниже)
Id – идентификатор сборки.
Элемент TargetImage – описывает те сборки, которые могут быть обновлены текущим обновлением.
SourceFile – путь к базовой сборке.
Order – порядок базовых сборок (так и не понял, зачем это надо).
Id – идентификатор базовых сборок.
И в конце PatchCreation вставляется элемент PatchSequence
<PatchSequence
PatchFamily= "Yasen"
Sequence="1.1.0.0"
Supersede="yes"
ProductCode="{7381ABA7-774B-4D44-BD7B-0A90BBCF2B0A}"
/>
Здесь:
PatchFamily – указывает к какой линейке относится этот патч (где-то мы это видели уже?)
Sequence – указывает версию патча, чтобы отличать в каком порядке патчи были выпущены. Указывается в формате x.x.x.x.
Supersede – указывает, может ли этот патч отменять все предыдущие патчи (накопительный патч?)
ProductCode – код продукта (видимо, чтобы не ошибиться).
Сборка и установка
После того, как инсталляции и описатель патча готовы, надо сделать следующее:
1. Выполнить административную установку всех инсталляций в отдельные папки, например, так:
msiexec.exe /a 1.0product.msi /qb TARGETDIR=C:sample1.0admin
msiexec.exe /a 1.1product.msi /qb TARGETDIR=C:sample1.1admin
Обратите внимание, что путь к инсталляциям в PatchCreation должен указывать на эти «распакованные» версии, например, C:sample1.0adminproduct.msi
2. Компилируем Wix файлы:
candle.exe patch.wxs
light.exe patch.wixobj -out patch.pcp
3. Используем утилиту от PatchWiz:
msimsp.exe -s patch.pcp -p patch.msp -l patch.log
И мы, наконец, получили желаемое!
Итог
Мы получили желаемый патч, однако для решения поставленной задачи нужно нечто большее:
- Не хочется создавать каждый раз файл-описатель патча.
- Эта система не очень эффективно работает, когда надо разрешить пропуск патчей. Причина в том, что мы указываем какие версии может патчить этот пакет, если мы хотим чтобы он мог патчить 5 версий, то объем может возрасти в 5 раз! Если мы указываем в качестве инсталяций для патча только предыдущую версию – экономим объем, но теряем возможность пропускать патчи.
- Нужно автоматизировать этот процесс, чтобы не тратить много времени на сбор патчей.
Соответственно для решения этих неудобств я напишу вторую часть и опишу все, что обещал в начале поста.
Ссылки:
Wix tutorial (очень хорошее руковдоство)
Wix — Creating patches
MSDN — Patching and Upgrades
Автор: neisbut