Desktop UI

Пакування та розгортання Avalonia додатків

Підготовка Avalonia-додатку для розповсюдження: dotnet publish, self-contained vs framework-dependent, trimming, platform-specific packaging, auto-updates та CI/CD

Пакування та розгортання Avalonia додатків

Створення додатку — це лише половина справи. Щоб ваш додаток потрапив до користувачів, його потрібно правильно зібрати, запакувати та розповсюдити. Кожна платформа має свої конвенції та очікування щодо формату розповсюдження: Windows користувачі очікують MSI або MSIX інсталятори, macOS користувачі звикли до DMG образів з .app bundles, а Linux користувачі цінують AppImage, Flatpak або пакети для своїх дистрибутивів.

У цій статті ми детально розглянемо весь процес підготовки Avalonia-додатку до розповсюдження: від базової збірки через dotnet publish до створення platform-specific інсталяторів, від вибору між self-contained та framework-dependent deployment до оптимізації розміру через trimming та AOT compilation. Ми також обговоримо автоматичні оновлення, code signing для різних платформ, та налаштування CI/CD pipeline для автоматичної збірки на всіх підтримуваних платформах.

Правильне пакування та розгортання — це не просто технічна задача, а важлива частина user experience. Користувач повинен мати можливість легко встановити ваш додаток, отримувати автоматичні оновлення, та бути впевненим у безпеці (через code signing). Давайте розберемося, як це зробити правильно для кожної платформи.

Словник термінів
  • Deployment — розгортання, процес підготовки додатку для розповсюдження
  • Self-contained — автономний додаток, який включає .NET Runtime
  • Framework-dependent — додаток, який вимагає встановленого .NET Runtime
  • Trimming — видалення невикористаного коду для зменшення розміру
  • AOT (Ahead-of-Time) — компіляція у нативний код до запуску
  • Runtime Identifier (RID) — ідентифікатор платформи (win-x64, linux-x64, osx-arm64)
  • Code Signing — цифровий підпис додатку для підтвердження автентичності
  • Notarization — процес перевірки Apple для macOS додатків
  • CI/CD — Continuous Integration / Continuous Deployment, автоматизація збірки та розгортання

dotnet publish — базова збірка

Основний інструмент для збірки .NET додатків — команда dotnet publish. Вона компілює додаток, збирає всі залежності та готує його до розповсюдження.

Базова команда publish

dotnet publish -c Release

Ця команда:

  • Компілює проєкт у Release конфігурації (з оптимізаціями)
  • Збирає всі NuGet залежності
  • Копіює runtime assets (якщо потрібно)
  • Створює output у папці bin/Release/net8.0/publish/

Проте для production deployment потрібно більше налаштувань.

Runtime Identifier (RID) — вказівка платформи

Щоб зібрати додаток для конкретної платформи, використовуйте параметр -r (runtime):

# Windows 64-bit
dotnet publish -c Release -r win-x64

# Windows ARM64 (Surface Pro X, Windows on ARM)
dotnet publish -c Release -r win-arm64

# Linux 64-bit
dotnet publish -c Release -r linux-x64

# Linux ARM64 (Raspberry Pi 4, ARM servers)
dotnet publish -c Release -r linux-arm64

# macOS Intel (x64)
dotnet publish -c Release -r osx-x64

# macOS Apple Silicon (M1/M2/M3)
dotnet publish -c Release -r osx-arm64

Повний список RID доступний у .NET RID Catalog.

Як визначити потрібний RID?Для вашої поточної платформи:
dotnet --info
Шукайте рядок "RID" у виводі. Наприклад:
RID:         osx-arm64
Для production рекомендується збирати для всіх популярних платформ:
  • Windows: win-x64 (обов'язково), win-arm64 (опціонально)
  • Linux: linux-x64 (обов'язково), linux-arm64 (для ARM серверів/Raspberry Pi)
  • macOS: osx-x64 та osx-arm64 (обидва обов'язкові для підтримки Intel та Apple Silicon)

Output path — вказівка папки виводу

За замовчуванням dotnet publish створює output у bin/Release/net8.0/{RID}/publish/. Для зручності можна вказати власну папку:

dotnet publish -c Release -r win-x64 -o ./dist/windows
dotnet publish -c Release -r linux-x64 -o ./dist/linux
dotnet publish -c Release -r osx-arm64 -o ./dist/macos

Це зручно для CI/CD pipelines та для організації artifacts.

Self-contained vs Framework-dependent

Один з найважливіших виборів при deployment — це тип розгортання: self-contained або framework-dependent.

Framework-dependent deployment (FDD)

Що це: Додаток вимагає встановленого .NET Runtime на машині користувача.

Переваги:

  • Малий розмір (~3-10 MB для простого додатку)
  • Швидше завантажується та встановлюється
  • Автоматичні security patches через оновлення .NET Runtime
  • Спільний runtime для всіх .NET додатків (економія диску)

Недоліки:

  • Користувач повинен встановити .NET Runtime (додатковий крок)
  • Залежність від версії runtime (може не працювати з новішою/старішою версією)
  • Складніше для non-technical користувачів

Збірка:

dotnet publish -c Release -r win-x64 --self-contained false

Або у .csproj:

<PropertyGroup>
  <SelfContained>false</SelfContained>
</PropertyGroup>

Розмір: ~3-10 MB (залежно від кількості залежностей)

Self-contained deployment (SCD)

Що це: Додаток включає .NET Runtime, не вимагає встановленого runtime на машині користувача.

Переваги:

  • Працює "out of the box" — користувач просто запускає
  • Не залежить від встановленого runtime
  • Контроль над версією runtime (не зламається при оновленні системного runtime)
  • Краще для non-technical користувачів

Недоліки:

  • Великий розмір (~60-100 MB для простого додатку)
  • Повільніше завантажується та встановлюється
  • Потрібно вручну оновлювати для security patches
  • Кожен додаток має свою копію runtime (марнування диску)

Збірка:

dotnet publish -c Release -r win-x64 --self-contained true

Або у .csproj:

<PropertyGroup>
  <SelfContained>true</SelfContained>
</PropertyGroup>

Розмір: ~60-100 MB (залежно від платформи та trimming)

Порівняльна таблиця

ХарактеристикаFramework-dependentSelf-contained
Розмір3-10 MB60-100 MB
Вимоги.NET Runtime встановленийНемає
Складність установкиСередня (потрібен runtime)Низька (просто запустити)
Security updatesАвтоматичні (через runtime)Вручну (rebuild додатку)
Контроль версіїНизькийВисокий
Цільова аудиторіяРозробники, IT professionalsЗвичайні користувачі

Коли що обирати?

Framework-dependent — якщо:

  • Ваша аудиторія — розробники або IT professionals
  • Додаток розповсюджується через package managers (apt, brew, chocolatey)
  • Важливий малий розмір
  • Додаток часто оновлюється

Self-contained — якщо:

  • Ваша аудиторія — звичайні користувачі
  • Важлива простота установки
  • Потрібен контроль над версією runtime
  • Додаток рідко оновлюється

Рекомендація: Для більшості desktop-додатків рекомендується self-contained deployment. Користувачі очікують, що додаток "просто працює" без додаткових кроків.

Single-file deployment.NET підтримує single-file deployment — упакування всього додатку в один executable файл:
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true
Це зручно, але має обмеження:
  • Не всі бібліотеки підтримують single-file (особливо з native dependencies)
  • При запуску файл розпаковується у temp папку (повільніший старт)
  • Avalonia може мати проблеми з single-file через assets (XAML, images)
Для Avalonia рекомендується звичайний self-contained deployment без single-file.

Trimming — зменшення розміру

Self-contained deployment включає весь .NET Runtime, навіть якщо ваш додаток використовує тільки малу його частину. Trimming дозволяє видалити невикористаний код та зменшити розмір.

Увімкнення trimming

<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
  <TrimMode>link</TrimMode>
</PropertyGroup>

Або через CLI:

dotnet publish -c Release -r win-x64 --self-contained true -p:PublishTrimmed=true

Як працює trimming

Trimmer аналізує ваш код та визначає, які частини .NET Runtime та бібліотек використовуються. Невикористаний код видаляється з output.

Приклад:

  • Без trimming: 80 MB
  • З trimming: 40-50 MB (економія ~40-50%)

Trim modes

link (агресивний):

<TrimMode>link</TrimMode>

Видаляє невикористані типи та методи з усіх assemblies (включаючи ваш код та NuGet пакети).

copyused (консервативний):

<TrimMode>copyused</TrimMode>

Видаляє тільки повністю невикористані assemblies, але не видаляє код всередині assemblies.

Рекомендація: Використовуйте link для максимальної економії, але ретельно тестуйте додаток.

Проблеми з trimming

Trimming може зламати додаток, якщо код використовує reflection, dynamic loading або інші runtime features:

// Цей код може зламатися після trimming
var type = Type.GetType("MyNamespace.MyClass");
var instance = Activator.CreateInstance(type);

Trimmer не може визначити, що MyClass використовується, і може видалити його.

Рішення 1: Trim warnings

Увімкніть trim warnings:

<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
  <TrimmerSingleWarn>false</TrimmerSingleWarn>
  <EnableTrimAnalyzer>true</EnableTrimAnalyzer>
</PropertyGroup>

Компілятор покаже warnings для потенційно проблемного коду.

Рішення 2: DynamicallyAccessedMembers attribute

Вкажіть, які члени типу використовуються через reflection:

public void CreateInstance(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
    Type type)
{
    var instance = Activator.CreateInstance(type);
}

Рішення 3: Trim root assemblies

Вкажіть assemblies, які не повинні триматися:

<ItemGroup>
  <TrimmerRootAssembly Include="MyApp.Core" />
</ItemGroup>

Avalonia та trimming

Avalonia підтримує trimming, але потрібні додаткові налаштування:

<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
  <TrimMode>link</TrimMode>
  
  <!-- Avalonia-specific -->
  <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>

<ItemGroup>
  <!-- Не тримати Avalonia assemblies -->
  <TrimmableAssembly Include="Avalonia.Base" />
  <TrimmableAssembly Include="Avalonia.Controls" />
  <TrimmableAssembly Include="Avalonia.Markup.Xaml" />
</ItemGroup>

Важливо: Використовуйте Compiled Bindings замість Reflection Bindings для сумісності з trimming.

Тестування після trimmingЗавжди тестуйте додаток після trimming! Trimmer може видалити код, який використовується через reflection або dynamic loading.Рекомендований workflow:
  1. Зберіть з trimming: dotnet publish -p:PublishTrimmed=true
  2. Запустіть додаток та протестуйте всі features
  3. Перевірте trim warnings: dotnet publish -p:TrimmerSingleWarn=false
  4. Виправте проблеми через attributes або TrimmerRootAssembly
  5. Повторіть тестування

NativeAOT — компіляція у нативний код

NativeAOT (Ahead-of-Time compilation) компілює .NET код у нативний машинний код до запуску. Це дає кілька переваг:

Переваги:

  • Швидший старт (немає JIT compilation)
  • Менший розмір (немає JIT compiler у runtime)
  • Менше використання пам'яті
  • Складніше reverse-engineer (нативний код замість IL)

Недоліки:

  • Довша збірка
  • Не підтримує всі .NET features (reflection, dynamic code generation)
  • Експериментальна підтримка у Avalonia

Увімкнення NativeAOT

<PropertyGroup>
  <PublishAot>true</PublishAot>
</PropertyGroup>
dotnet publish -c Release -r win-x64 -p:PublishAot=true

Avalonia та NativeAOT

На момент написання статті (2024) Avalonia має експериментальну підтримку NativeAOT. Основні проблеми:

  • XAML parsing використовує reflection
  • Деякі Avalonia features не працюють з AOT
  • Потрібні додаткові налаштування

Рекомендація: Для production використовуйте trimming замість NativeAOT. NativeAOT для Avalonia ще не готовий для широкого використання.

Слідкуйте за Avalonia GitHub для оновлень щодо NativeAOT підтримки.