Всем привет, на связи Александр Панов, техлид из Pixonic. В компании я отвечаю за межпроектные решения и околопроектную периферию и сегодня хочу поделиться своим опытом и наработками.
Платформы непрерывной разработки и интеграции, или CI/CD, сейчас используются повсеместно в тех отраслях, где решающую роль играет итеративность и отлаженность технических процессов. В этой статье речь пойдёт о CI/CD для реализации наших Unity-проектов для мобильной геймдев-разработки: с какими проблемами мы столкнулись, как их удалось решить, каких улучшений мы добились и как прописан наш пайплайн сборок билдов.
Сразу договоримся, что в качестве сервера CI мы используем TeamCity от JetBrains, в качестве хранилища Git-репозиториев ― GitHub, для хранения артефактов сборки ― Nexus.
Итак, перед нами возникли следующие проблемы:
- Отсутствие общего стандарта создания сборок: доступ к серверу TeamCity был практически у всех разработчиков, в результате чего скрипты сборок писались на разных языках программирования (BASH, PowerShell, Python), и логика часто дублировалась;
- Слабый парк машин: из-за того, что нам необходимо собирать билды для iOS, приходилось использовать парк машин из Mac mini. И поскольку в Unity практически вся сборка происходит в один поток, распараллелить сборки на одной машине оказывалось делом проблематичным;
- Мало отлаженная оперативность работы: из-за низкой производительности нашего технического обеспечения сборки происходили очень долго;
- Большие очереди в ожидании сборки при наличии достаточного количества агентов TeamCity;
- Отдельный пул агентов для каждого проекта: в связи с различным установленным окружением на устройствах, а также конфликтующими между проектами конфигурационными файлами (файлы настройки кэш-сервера и т.д.) невозможно было организовать общий пул.
Что в результате сделали?
- Репозиторий для сборочных скриптов
В первую очередь мы завели единый репозиторий скриптов для сборок на Python. Запуск производится в среде управления виртуальным окружением Pipenv с проксированием сторонних библиотек на своем сервере для быстрого обновления и контроля версий необходимых библиотек. Таким образом мы обеспечили единую точку входа для сборок всех существующих проектов. Переписали все скрипты на Python, унифицировали конфигурации, привели к общему стандарту, убрали дублирования кода.
- Новый парк машин
Необходимо было сократить время сборок, по возможности уменьшив количество используемых устройств.
Изначально у нас была ферма из 13 компьютеров Mac mini, но данное решение далеко от оптимального: ввиду особенности сборок на Unity около 80% времени сборки будут производиться только в одном потоке. Добавим к этому солидное количество обращений на запись к жёсткому диску и получим, что один Mac mini едва справляется с 1-2 одновременными сборками.
Результат ― необходимость пересмотра технического обеспечения.
В ходе поиска и сравнения альтернатив по Unity-сборкам мы заметили, что компьютеры на базе AMD Ryzen благодаря своей производительности позволяют собирать до 8-12 сборок одновременно без существенной потери производительности, в связи с чем было решено закупить четыре таких устройства с шестью SSD и установить по два агента на каждый жёсткий диск.
Сравнение того, как было и что стало, приведено в таблице.
Среднее время сборок:
Кроме того, мы организовали приоритезацию выбора агентов TeamCity для уменьшения времени нахождения сборки в очереди. Раньше у каждого нашего проекта был свой пул агентов, и ввиду мультиплатформенности игр проектно-зависимое окружение не позволяло создать общий пул. Теперь, после реорганизации работы системы, мы оставили ряд закрепленных за проектами агентов, которые служат для автоматических сборок, но смогли добавить и несколько общих агентов для всех проектов: они включаются в работу, когда заняты все агенты, привязанные к нужному проекту.
- Библиотека BuildPipeline для Unity
Завели небольшую библиотеку для Unity, которая даёт возможность задавать настройки сборки билдов в отдельном окне редактора Unity, а также обладает возможностью запускать сборку билдов в режиме batchmode. Из основного функционала библиотеки: она позволяет добавлять и удалять defines перед сборкой, отключать сторонние библиотеки или конкретные файлы, добавлять кастомные шаги пре- и пост-обработки, все её настройки хранятся в конфигурационных файлах, также есть возможность их наследования.
Окно defines в библиотеке BuildPipeline
Наш текущий пайплайн CI/CD
Сборка PullRequest. Для каждого коммита производится:
- запуск Unity для проверки на ошибки компиляции, обновления defines и генерации решений;
- запуск тестов;
- запуск статического анализатора: с его помощью производится инкрементальный анализ на файлы, затронутые в рамках текущего PullRequest;
- сообщение о результате проверки, которое сохраняется в GitHub.
Шаги сборки Unity-проекта:
1. Установка Pipenv и запуск скриптов для сборки на Python: обновление и установка сторонних библиотек Python с нашего сервера (проксирование хранилища pypi.org) и последующий запуск скрипта сборки.
2. Предварительная подготовка для сборки Unity:
- удаление папки Library, Bundles, выборочных ассетов (по маске и/или конкретных файлов), удаление solutions (файлов .sln) ― при необходимости;
- генерация файла с информацией о сборке: наименование ветки, номер сборки и проч. ― для дальнейшего использования в билде для отладки и тестирования;
- установка кэш-сервера Unity для проекта. У каждого проекта он свой. Также у каждого разработчика выставлен кэш-сервер для более быстрого наполнения: при добавлении разработчиком нового ассета он автоматически появится на кэш-сервере и на сервере сборки, ― таким образом импорт ассетов происходит гораздо быстрее.
3. Запуск Unity для проверки на ошибки компиляции, обновление defines и генерации решений.
4. Запуск тестов и выход из них при наличии ошибок ― при необходимости.
5. Запуск Unity BuildPipeline с указанием нужной конфигурации и дополнительных параметров проекта.
6. Для сборок Android/iOS ― запуск Gradle/Xcode:
- Gradle ― GradleWrapper;
- XCode ― архивируем XcodeProject, полученный после Unity, и копируем его на Mac mini. Отдельно устанавливаем и обновляем все необходимые сертификаты и файлы Provisioning Profile. Запускаем команды Clean, Archive, Export.
- На этапе экспорта есть возможность разделения подписи билда, разработчика и AppStore. В зависимости от того, что собираем, выбираем нужный plist, либо каждый по очереди. На выходе получаем два файла: Developer и Release ― для установки на тестовые девайсы и для заливки в AppStore соответственно.
7. Заливка собранных билдов и сопутствующих файлов (логи, результаты тестов, .obb, manifest для установки iOS приложений, dsym-файлы и т.д.) в хранилище артефактов, для standalone-сборок ― заливка в хранилище архива собранного билда.
8. Генерация страницы с QR-кодом для установки билда, добавление ссылок из хранилища и информации по билду в базу данных для дальнейшей работы с приложением PixLauncher ― о нем расскажем далее.
9. Сообщение в Slack о результате сборки: тому, кто запустил сборку, а также в необходимые каналы.
Такие сообщения приходят в Slack
Дальнейшие шаги
В качестве завершающего шага пайплана происходит распространение собранных билдов на устройства для дальнейшего тестирования и загрузки в сторы.
Для установки билдов на устройства мы написали небольшое приложение для Android и iOS ― PixLauncher. Его мы устанавливаем на каждое устройство, где есть возможность выбора билда из TeamCity. Для удобства в нём можно установить фильтры ― например, добавить конфигурацию в избранное и далее производить действия с ней в один клик. В случае билдов для Android при необходимости автоматически скачивается файл в разрешении .obb.
Кроме того, мы организовали возможность установки билда через push-уведомления: на сервер TeamCity мы добавили самописный плагин, который на странице с билдом позволяет выбрать MAC-адреса девайсов, подключенных к локальной сети. Затем на эти устройства поступает push-уведомление со ссылкой на установку ― таким образом она теперь осуществляется в одно нажатие.
Так, приложение позволило ускорить поиск нужного билда QA отделом и установку на девайсы для последующей проверки.
Внешний вид приложения PixLauncher для iOS
Наконец, загрузка билдов в сторы
После всех произведённых действий естественным образом образуется потребность в гарантированной заливке билда и метаинформации по приложению в сторы.
Изначально проблемы возникали по большей части с AppStore:
- заливка в стор производится только с устройства MacOS;
- необходимо загружать видеоролики, скриншоты и описания приложения более чем на 25 языках.
Это приводило к большим потерям времени на заливку и сбоям в загрузке файлов в админку стора. Поэтому мы задумались над автоматизацией процесса.
В результате имеем следующее:
- В Google Disk мы завели табличку с описанием приложения на всех языках;
- Видеоролики и скриншоты приложения разложили по папкам с определённым неймингом;
- В Teamcity для возможности выбора уже собранного билда сделали конфигурацию, зависимую от релизной сборки;
- Через API GooglePlay и iTMSTransporter для Apple заливаем билды и мета-информацию о приложении в стор для всех необходимых языков. При возникновении проблем (например, с сетью) ― производим несколько попыток и присылаем сообщение в Slack с текстом ошибок.
Так выглядит выгрузка билда в AppStore
В качестве итога ― немного цифр
- Теперь у нас производится около 400 сборок и до 60 установок билдов на девайсы в день;
- Существует 57 различных конфигураций сборок на TeamCity;
- Мы используем 22 агента TeamCity, при этом есть возможность расширения без существенной потери производительности до 48 агентов;
- Есть возможность горизонтального расширения парка машин.
Автор: Pixonic