CI/CD для Unity проектов

Unity - кроссплатформенный движок, позволяющий собирать проект под разные платформы - Веб, Десктоп, Мобильные, VR-устройства и другие. Чтобы подготовить сборку под каждую платформу, нужно поменять настройки проекта, вычислить и указать версию сборки, собрать билд и выложить его на нужную площадку. Это может отнимать у разработчика много времени, а также есть риск человеческой ошибки.

Поэтому мы разработали собственные инструменты, позволяющие по “одному клику” собирать и выкладывать приложения под разные платформы.

Версионирование

Зачем нам нужна версия приложения?

  1. Необходимо отличать один билд от другого;
  2. Записать дополнительные мета-данные в версию сборки;

Кроме того, есть требования к версии приложения:

  • Версия должна быть уникальная и относится только к одному билду
  • Она должна хранить метаданные: с какой ветки была собрана версия и какого коммита
  • Она должна быть читаемой и понятной

Наше решение

За основу мы решили взять семантическое версионирование https://semver.org/

Для генерации и подсчета версии мы используем GitVersion

GitVersion - это инструмент автоматического вычисления номера версии, который основывается на истории коммитов в Git. Он вычисляет версию по определённым правилам семантического версионирования.

Версия приложения зависит от того, в каком состоянии сейчас находится репозиторий - какие ветки и теги присутствуют, а также какой активный главный указатель HEAD.
В качестве результата работы GitVersion создает JSON следующего вида:

{
  "GitVersion": {
    "Major": 0,
    "Minor": 3,
    "Patch": 0,
    "PreReleaseTag": "alpha. 192",
    "PreReleaseTagWithDash": "-alpha.192",
    "PreReleaseLabel": "alpha",
    "PreReleaseLabelWithDash": "-alpha",
    "PreReleaseNumber": 192,
    "WeightedPreReleaseNumber": 192,
    "BuildMetaData": 0,
    "BuildMetaDataPadded": "",
    "FullBuildMetaData": "Branch.develop.Sha.6ae2c8705e46bef322909b751ae75dd30fc78f17",
    "MajorMinorPatch": "0.3.0",
    "SemVer": "0.3.0-alpha. 192",
    "LegacySemVer": "0.3.0-alpha192",
    "LegacySemVerPadded": "0.3.0-alpha0192",
    "AssemblySemVer": "0.3.0.192",
    "AssemblySemFileVer": "0.3.0.192",
    "FullSemVer": "0.3.0-alpha.192",
    "InformationalVersion": "0.3.0-alpha.192+Branch.develop.Sha.6ae2c8705e46bef322909b751ae75dd30fc78f17",
    "BranchName": "develop",
    "EscapedBranchName": "develop",
    "Sha": "6ae2c8705e46bef322909b751ae75dd30fc78f17",
    "ShortSha": "6ae2c87",
    "NuGetVersionV2": "0.3.0-alpha0192",
    "NuGetVersion": "0.3.0-alpha0192",
    "NuGetPreReleaseTagV2": "alpha0192",
    "NuGetPreReleaseTag": "alpha0192",
    "VersionSourceSha": "875b285778db9215786e05860261f53d2b96ac29",
    "CommitsSinceVersionSource": 192,
    "CommitsSinceVersionSourcePadded": "0192",
    "UncommittedChanges": 0,
    "CommitDate": "2026-01-13"
  }
}

Он содержит различную информацию о текущей версии проекта, например текущую ветку, дату коммита, а самое главное, номер версии. Нас интересует FullSemVer, который содержит строку версии в специальном формате. Примеры:

  • 0.4.0-alpha.5
  • 0.4.0.beta.1
  • 0.4.0

Основной номер состоит из MAJOR.MINOR.PATCH. По стандартным правилам, эти значения изменяются когда:

  1. MAJOR, когда новая версия проекта несовместима с предыдущей.
  2. MINOR, когда добавлен новый функционал с обратной совместимостью с предыдущей версией
  3. PATCH, когда добавлены баг-фиксы, хотфиксы. Используется только в ветке main

Также в версии могут присутствовать теги pre-release и build metadata, они добавляется в конец версии после MAJOR.MINOR.PATCH. Например 0.1.1.alpha-3 или 1.0.0+20240331

Наличие и значение тега pre-release зависит от того, с какой ветки собирается текущий билд.

Ниже показана диаграмма, которая отображает пример структуры репозитория проекта и соответствующую версию билда на каждом коммите:

Структура репозитория с ветками, коммитами и версиями

Изображение 1 – Структура репозитория с ветками, коммитами и версиями

  • develop - основная ветка проекта, в которой ведется разработка посредством создания фича бранчей.
  • feature - ветка разработки отдельного функционала, после слияния в develop, увеличивает номер alpha на количество коммитов в смердженной ветке.
  • release - ветка подготовки релиза. Первый коммит ветки всегда 0.1.0-beta.0. Каждая новая ветка release увеличивает значение MINOR версии.
  • fix - ветка фиксов при подготовке к релизу, после слияния в release, увеличивает номер beta.
  • main - с этой ветки идёт основная сборка и публикация приложения. Когда проект готов к релизу, ветка release вливается в main, после чего вручную ставится тэг, начиная с 0.1.0.
  • hotfix - ветка срочных фиксов в релизную версию проекта, вливается в main. Каждая новая ветка hotfix увеличивает значение PATCH версии.

Процесс разработки выглядит следующим образом: после создания и инициализации репозитория, проект находится на первом коммите с ветками develop и main. Далее, разработчики выполняют задачи по проекту и добавляют новые функции, создавая ветки feature, после прохождения Code Review эти ветки вливаются в develop. Когда нам нужно сделать и выпустить новый релиз, создается ветка release от актуальной ветки develop. В ней происходит подготовка релиза, посредством создания веток fix и вливания в release. Когда релиз полностью готов, ветка release вливается в main и обратно develop, чтобы сохранить все фиксы этого релиза в проекте.

GitVersion использует конфиг-файл GitVersion.yml, который находится в корне репозитория. Мы настроили этот файл, чтобы наша версия вычислялась в соответствии с представленной выше схемой. Ниже настроенный конфиг-файл:

assembly-versioning-scheme: MajorMinorPatchTag
assembly-versioning-format: '{Major}.{Minor}.{Patch}.{PreReleaseNumber ?? 0}'
assembly-file-versioning-scheme: MajorMinorPatchTag
assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{PreReleaseNumber ?? 0}'
mode: MainLine
branches:
  develop:
    mode: ContinuousDeployment
    regex: ^(origin/)?develop$
  feature:
    mode: ContinuousDeployment
    increment: Minor
    tracks-release-branches: true
    regex: ^(origin/)?feature/
    source-branches:
    - develop
  release:
    mode: ContinuousDeployment
    regex: ^(origin/)?release/
    source-branches:
    - develop
  fix:
    mode: ContinuousDeployment
    regex: ^(origin/)?fix/
    tag: fix
    increment: None
    source-branches:
    - release
  hotfix:
    mode: ContinuousDeployment
    regex: ^(origin/)?hotfix/
    source-branches:
    - main
  main:
    mode: ContinuousDelivery
    regex: ^(origin/)?main$

Интеграция с Unity Editor

Одним из главных качеств внутренних инструментов является их юзабилити.

Для удобного использования GitVersion во время разработки мы создали набор инструментов, который позволяет использовать его прямо из редактора Unity. С их помощью мы можем в любой момент узнать текущую версию проекта прямо из окна движка.

Использование Editor инструмента для генерации GitVersion файла

Изображение 2 – Использование Editor инструмента для генерации GitVersion файла

Информация, полученная от GitVersion, сохраняется в специальный файл Юнити формата - ScriptableObject. Мы можем использовать его прямо во время выполнения приложения, например отображая текущую версию приложения на экране.

При подготовке к сборке билда, наши скрипты активируют GitVersion и используют результат его работы для заполнения настроек билда, таких как BundleVersion - это строка с текущей версией приложения.

Однако, для некоторых платформ, таких как iOS и macOS есть особенности, они не воспринимают строку формата FullSemVer, например 1.3.0-alpha.12. Поэтому для них мы берём из нашего файла только формат MajorMinorPatch - 1.3.0. Ниже представлена реализация метода GetBundleVersion:

private static string GetBundleVersion(bool needFullSemVer)
{
    return needFullSemVer
        ? GitVersionAsset.Instance.GitVersion.FullSemVer
        : GitVersionAsset.Instance.GitVersion.MajorMinorPatch;
}

Несколько слов о BuildNumber

Версия - это ещё не всё, что нам нужно вычислять. Чтобы платформа, на которую мы собираемся загрузить приложение могла понять, какой билд является более новым, а какой более старый, нужно определить номер билда, или BuildNumber. Обычно это целое число ограниченное по размеру.

Первое что приходит в голову, это вычислять номер билда, основываясь на версии MAJOR.MINOR.PATCH.

Проблема - может возникнуть следующая ситуация: мы опубликовали версию 0.1.0 с ветки main, далее мы продолжаем разработку, и, например, хотим загрузить на площадку версию 0.2.0-beta.0 прямо с ветки release, чтобы наши тестировщики могли протестировать готовящийся релиз по каналам внутреннего тестирования, тогда эта версия получит более высокий BuildNumber. Однако, если что-то случится с текущей стабильной версией 0.1.0, и нам нужно будет срочно загрузить хотфикс, например 0.1.1, тогда его номер версии будет уже ниже чем 0.2.0-beta.0, и площадка не сможет принять такую сборку на загрузку.

Наше решение - использовать текущую дату и время для генерации BuildNumber:

private static string GetBuildNumberString()
{
    var totalMinutes = (int)(DateTime.UtcNow - DateTime.UnixEpoch).TotalMinutes;
    var buildNumber = totalMinutes + BuildNumberOffset;
    return buildNumber.ToString();
}

Идея в том, чтобы использовать количество минут, которые прошли с момента UnixEpoch. Это гарантирует нам, что как минимум через одну минуту, новый BuildNumber будет больше чем предыдущий.

Также мы добавили BuildNumberOffset, который позволяет перейти на новую систему вычисления BuildNumber на старых проектах, где предыдущие версии уже были рассчитаны по старому методу.

Система сборки и деплоя билдов

Чтобы собрать проект в Юнити под разные платформы, требуется переключать целевую платформу для сборки проекта, изменять определенные настройки под платформу, правильно указывать BundleVersion и так далее. Делать это каждый раз вручную - не наш подход.

Поэтому под каждую платформу мы подготовили Editor скрипт, который делает эту рутину каждый раз перед сборкой проекта. Всю эту логику мы также можем запускать прямо с окна эдитора, чтобы по одной кнопке собрать билд под нужную платформу и протестировать на девайсе.

Использование Unity Editor инструмента для сборки проекта под разные платформы

Изображение 3 – Использование Unity Editor инструмента для сборки проекта под разные платформы

Может возникнуть вопрос:

Что если я хочу собрать новый релиз сразу под несколько платформ? Каждый раз нажимать Build, ждать и загружать сборки на платформы вручную?

Ответ - нет, для этого даже не обязательно запускать Unity с проектом. Для CI/CD на Unity проектах мы используем сервис Unity Cloud Build - https://cloud.unity.com/, а также специальные .sh скрипты для деплоя билдов, которые находятся в Юнити проекте.

Как выглядит этот процесс:

  1. Мы создаем страничку проекта на UCB (Unity Cloud Build)
  2. Связываем UCB и наш репозиторий, чтобы сервис мог собирать наш билд на удаленных серверах.
  3. Создаем нужные конфиги под сборку на разные платформы, например iOS, Android, Meta, WebGL и так далее.
  4. В этих конфигах мы указываем, где в проекте находится Post Build Script - скрипт который нужно запустить после того, как билд успешно соберется. Это и есть наш .sh скрипт, который умеет заливать сборку на платформу.
  5. Готово, теперь на страничке проекта на UCB мы можем нажать и запустить одновременную сборку на нужные нам платформы, которая будет происходить на серверах UCB, а далее заливаться на нужные нам платформы с помощью наших скриптов

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

Здесь также есть особенности, например, для WebGL билдов, мы можем использовать GitHub Actions, которые ожидают завершения сборки билда на UCB, а далее активируют специальные скрипты в репозитории для деплоя сборки на хостинг.

Схема деплоя билдов на девайсы:

Поскольку большинство платформ дают возможность использовать релизные каналы, мы разработали специальную систему наименования каналов и соответствия веток в репозитории этим каналам:

Схема деплоя с веток Git в разные каналы

Изображение 4 – Схема деплоя с веток Git в разные каналы

  • ALPHA - релизный канал для билдов с ветки develop, билды в таком канале распространяются на тестовые девайсы компании, чтобы разработчики и тестировщики могли протестировать билд
  • ALPHA-RC и BETA - релизные каналы для билдов с ветки release, билды в таком канале доступны на внутренних девайсах, а также на тестовых девайсах клиента, чтобы можно было проверить и утвердить релизный билд
  • RC и PRODUCTION - релизные каналы для билдов с ветки main, перед публичным релизом на площадки билд проверяется на тестовых девайсах в канале RC (Release Candidate), когда билд готов к релизу, его можно продвигать в канал PRODUCTION, с которого он уже доступен для всех пользователей на площадке

Результаты и выводы

Разработка внутренних инструментов для команды зачастую может ускорить и упростить дальнейшую разработку.

Наша система версионирования, сборки и деплоя билдов под общим названием Unity Build Tools позволяет нам сэкономить множество часов разработчика, упростить и ускорить процесс деплоя, это удобно:

  • Для клиентов - релизы могут появиться стабильнее и чаще
  • Для тестировщиков - билд новой версии почти одновременно появляется на всех устройствах
  • Для разработчика, который четко и ясно понимает, как меняется версия проекта и почему, а также деплой занимает всего пару кликов.

Читайте также

Наверх