Сборка iOS на Kotlin 2.4.10: как развернуть удалённый Mac CI в 2026

Руководство для команд, которые разрабатывают Kotlin Multiplatform на Windows или Linux, но передают Apple-специфические этапы настоящему Mac. Вы разберёте границы ответственности, сборку Framework и XCFramework, тестирование, подпись, Archive, кэширование и восстановление CI-узла.

Сборка iOS на Kotlin 2.4.10: как развернуть удалённый Mac CI в 2026

Содержание

Сборку iOS на Kotlin 2.4.10 следует вынести на отдельный удалённый Mac CI: Windows или Linux оставьте для общего кода и проверок Gradle, а Framework, симулятор, Xcode-тесты, подпись и Archive выполняйте на настоящем Mac с установленным Xcode. На этой неделе сначала создайте независимый узел, затем проверьте его чистым клонированием репозитория и только после успешного Archive подключайте автоматический запуск.

Этот материал предназначен разработчикам, которые ведут Kotlin Multiplatform-проект с Windows или Linux, мобильным DevOps-инженерам, подключающим Kotlin/Native к CI, и руководителям платформ, отвечающим за общие Mac, секреты, кэш и восстановление после сбоя.

Последнее обновление: 27 августа 2026 года. Стабильный Kotlin 2.4.10 и дата его выпуска — 14 июля 2026 года — сверены по официальному списку релизов Kotlin. Kotlin 2.4.20-RC остаётся предварительной версией, поэтому её поведение нельзя использовать как доказательство возможностей стабильной ветки 2.4.10.

Границы удалённого Mac CI

Главная ошибка в такой схеме — считать Mac только «удалённым рабочим столом». Для CI это должен быть контролируемый Apple-узел с понятными входами, выходами и условиями остановки.

На Windows или Linux разумно оставить:

На Mac должны уходить:

Это не вопрос удобства интерфейса. Kotlin/Native компилирует отдельные Apple-цели, а структура Framework зависит от настроек проекта и выбранной архитектуры. Документация Kotlin по целевым платформам нужна как исходная точка для проверки поддерживаемых целей, но она не заменяет проверку именно вашего репозитория.

Потоки данных лучше зафиксировать до настройки Runner:

  1. Репозиторий и параметры сборки поступают на Mac из CI.
  2. Gradle загружает зависимости и создаёт Kotlin/Native-бинарии.
  3. Framework или XCFramework передаётся в Xcode-проект.
  4. Xcode получает схему, настройки подписи и исходники приложения.
  5. На выходе появляются логи, тестовый результат, Archive и экспортированный пакет.

Граница считается пройденной, если новый рабочий каталог после клонирования способен без ручного открытия Xcode дойти до Apple-этапа. Если сборка работает только после выбора Scheme мышью, исправления локального пути или добавления файла в рабочем каталоге, узел ещё не готов для CI.

Есть и скрытые ограничения. Общий Mac может получить конкуренцию за Derived Data, симулятор и ключи подписи. SSH-сессия может оборваться во время длительной задачи, а интерактивное окно авторизации превратит автоматический процесс в ручной. Кроме того, кэш, созданный одной веткой или версией инструментов, способен маскировать ошибку чистой сборки. Поэтому в отчёт нужно включать не только код возврата, но и точные входы, выбранную схему, путь к артефактам и версию инструментов.

Framework и XCFramework для Apple-целей

В Kotlin Multiplatform нельзя считать доставку завершённой после успешной сборки одной цели. iosArm64 относится к физическому устройству, а iosSimulatorArm64 — к симулятору на Mac с Apple Silicon; итоговое решение зависит от архитектуры узла, проекта и способа подключения. Не следует переносить название цели в конфигурацию без проверки задач Gradle: конкретный набор задач определяется вашим проектом.

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

XCFramework предпочтительнее, когда общий модуль должен передаваться между независимыми частями проекта или использоваться как распространяемый бинарный пакет. В нём объединяются варианты для разных Apple-назначений, но сам факт наличия файла не доказывает, что он пригоден для устройства и симулятора. Руководство Kotlin по сборке Native-бинарей описывает параметры и задачи, которые следует сопоставить с вашим Gradle-конфигом.

Для каждой сборки сохраняйте четыре вида доказательств:

Проверка сборки с чистого каталога

В CI используйте условные обозначения вместо реальных данных:

export PROJECT_DIR="$CI_PROJECT_DIR"
export IOS_SCHEME="<IOS_SCHEME>"
export CONFIGURATION="<CONFIGURATION>"

./gradlew :shared:<FRAMEWORK_TASK> \
  -PbuildConfiguration="$CONFIGURATION" \
  --stacktrace

xcodebuild \
  -workspace "<WORKSPACE_PATH>" \
  -scheme "$IOS_SCHEME" \
  -configuration "$CONFIGURATION" \
  -destination 'generic/platform=iOS' \
  -showBuildSettings

Название <FRAMEWORK_TASK> должно быть взято из вывода Gradle для вашего проекта, а не скопировано из чужой статьи. После генерации проверьте, что Xcode действительно использует новый бинарь. Удобное доказательство — изменить публичный символ общего модуля, выполнить сборку, а затем убедиться, что iOS-код видит это изменение. Если импорт продолжает работать со старым API, CI, вероятно, подставляет устаревший путь.

Интеграция Kotlin Multiplatform с Xcode

Для интеграции есть три организационные модели.

При прямой интеграции Gradle вызывается из Xcode Build Phase, а Framework создаётся рядом с рабочей копией приложения. Это удобно, когда единый репозиторий и одна команда одновременно меняют Kotlin и Swift-код. Цена удобства — более строгие требования к путям, переменным окружения и порядку фаз.

CocoaPods уместен, если проект уже строится вокруг этой модели зависимостей. Тогда нужно проверять не только Gradle-задачу, но и сгенерированные pod-настройки, конфигурации и вызов из Xcode. Любой путь к домашнему каталогу разработчика должен считаться дефектом CI.

Удалённый бинарный вариант отделяет выпуск XCFramework от сборки приложения. Он лучше подходит командам с независимым циклом публикации общего модуля, однако добавляет управление версиями, хранилищем и совместимостью интерфейса. Для Swift Package Manager Kotlin также предоставляет отдельный механизм экспорта; его ограничения и параметры указаны в официальном описании экспорта в SPM.

Проверяйте связку в следующем порядке:

  1. Build Phase вызывает ожидаемую Gradle-задачу, а не локальный скрипт из домашнего каталога.
  2. Конфигурация Debug и Release использует предсказуемые пути к Framework.
  3. Scheme доступна в неинтерактивной сессии xcodebuild.
  4. Переменные окружения передаются через CI, а не через незакоммиченный файл.
  5. Изменение общего кода приводит к новому Framework и изменению результата Xcode-сборки.

Последний пункт важнее зелёного статуса отдельной задачи. Он доказывает полный цикл «исходник — Kotlin-бинарь — Xcode — приложение», а не только корректность одного инструмента.

Внимание: не храните Team ID, сертификаты, provisioning profile и закрытые ключи в репозитории или обычном логе сборки. Секреты должны передаваться из защищённого хранилища CI, а учётная запись публикации — быть отделена от повседневной разработки.

Тестирование симулятора и Xcode

Для Kotlin Multiplatform разделяйте как минимум три класса проверок.

Общие тесты оценивают код, не зависящий от Apple-рантайма. Их можно запускать на Windows или Linux, если проект и используемые библиотеки это допускают. Ошибка здесь обычно относится к общому модулю, его зависимостям или логике приложения.

Тесты Kotlin/Native требуют Apple-цели и должны выполняться на Mac. Они выявляют проблемы interop, памяти, платформенных API и компиляции Native-кода. Переносить такой сбой на сторону Xcode без отдельного лога Gradle затрудняет диагностику.

Xcode-тесты проверяют iOS-приложение, его Scheme, ресурсы, Swift-слой и взаимодействие с созданным Framework. Apple описывает параметры сборки и запуска в документации Xcode по запуску приложения. Команда CI должна явно задавать workspace или project, Scheme, configuration и destination.

Симулятор требует отдельной проверки. Наличие удалённого Mac не означает, что любой узел подходит для интерактивного графического сеанса: могут отличаться доступная архитектура, зарегистрированные устройства, графическая сессия и состояние Simulator. Для автоматических тестов выбирайте заранее созданное устройство, очищайте его только по доказанной необходимости и сохраняйте журнал запуска. Воспроизводимость важнее возможности подключиться к окну через VNC.

Храните как минимум:

xcresult полезен тем, что позволяет повторно разобрать тестовый результат, а не восстанавливать причину по одной строке «exit code 1». Если тест падает только после отключения SSH, это уже не обычная ошибка приложения, а проблема способа запуска или жизненного цикла процесса.

Архивирование, подпись и публикация

Архивирование не следует смешивать с генерацией Framework в одну непрозрачную команду. Разделите конвейер на наблюдаемые этапы:

  1. Разрешение зависимостей и проверка исходного коммита.
  2. Сборка общего модуля для требуемых Apple-целей.
  3. Интеграция Framework или XCFramework в Xcode.
  4. Запуск тестов и сохранение xcresult.
  5. Создание Archive.
  6. Проверка Archive и экспорт пакета.
  7. Передача артефактов и удаление временных секретов.

Для каждого этапа задайте вход, выход и условие остановки. Например, этап Archive не должен запускаться после неуспешных тестов, а экспорт не должен считаться успешным только потому, что файл появился в каталоге. Руководство Apple по сборке приложения для распространения следует использовать при проверке параметров архивирования и публикации.

Команды оформляйте через placeholders:

xcodebuild archive \
  -workspace "<WORKSPACE_PATH>" \
  -scheme "<IOS_SCHEME>" \
  -configuration "<RELEASE_CONFIGURATION>" \
  -archivePath "$CI_ARTIFACTS/<APP_NAME>.xcarchive" \
  DEVELOPMENT_TEAM="<TEAM_ID>" \
  CODE_SIGN_STYLE="<SIGNING_STYLE>" \
  -allowProvisioningUpdates

Флаг, разрешающий обновление профилей, не должен автоматически применяться без понимания политики секретов и доступа учётной записи. В защищённую область относятся сертификат, provisioning profile, закрытый ключ, пароль связки ключей и токены публикации. Переменные окружения Apple позволяют централизовать часть параметров, но их значения нельзя выводить в лог; справочник Apple по переменным окружения Xcode помогает определить, какие настройки реально получает процесс.

Приёмка релизного узла выглядит так:

Для зарегистрированных устройств и внутренних тестовых каналов используйте отдельную процедуру проверки; соответствующие ограничения и действия Apple описывает в документации о распространении на зарегистрированные устройства.

Кэш, параллельность и восстановление узла

В Kotlin iOS CI нельзя воспринимать «кэш Gradle» как единый каталог. Разделяйте:

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

На общем удалённом Mac отдельная рабочая директория обязательна для каждой задачи. Нельзя позволять двум процессам одновременно менять один checkout, один Simulator или одну связку ключей. Для GitHub Actions используйте отдельную метку Runner, чтобы маршрутизировать только iOS-задачи; правила назначения меток описаны в документации GitHub Actions для self-hosted Runner.

Проверка восстановления должна имитировать реальные сбои:

  1. Запустите сборку из чистого каталога.
  2. Оборвите SSH-сеанс, не завершая саму задачу CI.
  3. Перезапустите Mac.
  4. Убедитесь, что Runner снова зарегистрирован и получает задания.
  5. Запустите новую задачу в отдельной директории.
  6. Сравните Framework, тестовый результат и Archive с предыдущей успешной сборкой.

Нужно также проверить конкуренцию: параллельные задачи не должны использовать один путь, один набор секретов и одно состояние Simulator. Если узел не переживает перезапуск без ручного входа в систему, он подходит для эпизодической разработки, но не для постоянного Apple-выхода.

Сравнение вариантов размещения iOS-сборки

Перед заказом или выделением узла оцените не только доступ к macOS, но и степень контроля над окружением. В таблице ниже нет выдуманных цен и производительности: они зависят от конкретного предложения, проекта и политики команды.

Критерий Windows/Linux без Mac-узла Собственный Mac mini как CI Удалённый Mac CI через VPSMAC
Общий Kotlin-код и Gradle-проверки Подходит Подходит Подходит
Xcode, iOS Simulator и Archive Не выполняются нативно Выполняются локально Выполняются на реальном Mac
Начальные капитальные затраты Минимальные Покупка и обслуживание оборудования Нет необходимости сразу покупать отдельный Mac
Доступ вне рабочего места Зависит от локальной сети Требует настройки доступа и электропитания Доступ через SSH, VNC или консоль управления
Изоляция CI Нет Apple-среды Контролируется владельцем Требует раздельных каталогов, Runner и секретов
Восстановление после сбоя Не применимо к Apple-этапу Ответственность команды Проверяется перезапуском и повторной выдачей задачи
Оптимальный сценарий Разработка общего кода Длительная стабильная нагрузка Тестирование, миграция и запуск удалённого Apple-узла

Если вам нужен именно удалённый Mac, сначала сопоставьте задержку доступа, расположение команды и требуемый режим работы с доступными вариантами аренды Mac. Для международной команды отдельный регион может быть важен не только для VNC, но и для времени отклика при интерактивной диагностике; при этом автоматический CI обычно чувствительнее к стабильности соединения и восстановлению Runner, чем к графической задержке.

Приёмка перед постоянной эксплуатацией

Используйте следующую последовательность как технический барьер, а не как формальность:

  1. Зафиксируйте commit, параметры конфигурации, Scheme и выбранные Apple-цели.
  2. Выполните чистое клонирование на удалённый Mac без локальных файлов разработчика.
  3. Сгенерируйте Framework или XCFramework и проверьте его содержимое.
  4. Подключите результат к Xcode и подтвердите импорт изменённого публичного символа.
  5. Запустите общие тесты, Kotlin/Native-тесты и Xcode-тесты раздельно.
  6. Сохраните логи, xcresult и промежуточные артефакты.
  7. Создайте Archive и проверьте экспорт без ручного нажатия кнопок.
  8. Повторите задачу после очистки только рабочей директории.
  9. Проверьте остановку на намеренной ошибке подписи или теста.
  10. Оборвите SSH, перезапустите узел и повторите задание после автоматического восстановления Runner.

Результат должен быть бинарным: либо все контрольные точки подтверждены, либо iOS-маршрут остаётся экспериментальным. Особенно опасны частично зелёные сценарии, когда Framework собирается, но Xcode берёт старую копию, либо Archive создаётся, но экспорт использует личный профиль разработчика.

Если текущая схема — Windows или Linux с локальным запуском общего Kotlin-кода, её не нужно немедленно заменять. Её слабые места проявляются на Apple-этапе: нет нативного Xcode, невозможно надёжно проверить iOS Simulator, подпись зависит от ручных действий, а отдельный физический Mac приходится самостоятельно обслуживать и восстанавливать. Аренда Mac через VPSMAC даёт возможность сначала прогнать чистое клонирование, тесты и Archive на выделенном удалённом узле, а затем решить по фактической стабильности, нужен ли вам долгосрочный CI-сервер или достаточно временной среды для релизных задач. Начните с короткого проверочного цикла через удалённый Mac для сборки iOS: подключите один iOS-маршрут, зафиксируйте артефакты и не переводите весь проект на постоянную эксплуатацию, пока узел не пройдёт тест перезапуска.

Дополнительное чтение