Сборка Android и iOS приложений в CI-пайплайне

У вас есть мобильное приложение, которое отлично собирается на ноутбуке. Вы пушите код в репозиторий, CI-пайплайн запускается, а через десять минут падает с ошибкой, которую вы раньше не видели. Версия SDK отличается. Зависимость разрешилась в чуть более новую версию. Сертификат подписи отсутствует. Такова реальность мобильных сборок в CI: то, что работает локально, часто ломается в пайплайне.

Первое, что нужно правильно настроить в мобильном CI-пайплайне — это сам процесс сборки. Не просто запуск компилятора, а получение консистентного, тестируемого и готового к распространению артефакта. Android и iOS подходят к этому по-разному, и у каждой платформы есть свои подводные камни.

Сборка Android с Gradle

Сборка Android выполняется через Gradle, который читает конфигурацию из файлов build.gradle. Здесь важны три настройки SDK: compileSdk, minSdk и targetSdk. Их легко перепутать, но у каждой своя цель.

compileSdk определяет, какой уровень API доступен во время компиляции. Если установить 34, можно использовать API, появившиеся в Android 14. minSdk — это минимальная версия Android, которую поддерживает ваше приложение. Если установить 26, устройства с Android 8.0 и старше не смогут установить приложение. targetSdk сообщает Android, на какой версии вы тестировали приложение. При нацеливании на более новый SDK Android может применять изменения в поведении, которые повлияют на ваше приложение.

Пайплайн должен использовать те же версии SDK, что и ваше локальное окружение. Несоответствие часто вызывает ошибки компиляции, которые проявляются только в CI. Явно зафиксируйте эти версии в файлах build.gradle и проверяйте их в конфигурации пайплайна.

Зависимости — ещё один частый источник проблем. Gradle тянет библиотеки из Maven Central, Google Maven или вашего внутреннего репозитория. Без кэширования пайплайн при каждой сборке скачивает все зависимости заново. Проект с двадцатью зависимостями может тратить на загрузку от десяти до пятнадцати минут. Кэшируйте директорию зависимостей Gradle между сборками. Большинство CI-платформ поддерживают это простой настройкой, что значительно сокращает время сборки.

Результат сборки Android — это APK или AAB. APK — старый формат, который можно установить напрямую на устройство. AAB — новый формат для загрузки в Google Play, который затем генерирует оптимизированные APK для каждой конфигурации устройства. Используйте APK для внутреннего тестирования и ручного распространения. Используйте AAB для официальных релизов через Play Store. Ваш пайплайн должен поддерживать оба формата, но выбор зависит от того, куда отправляется артефакт.

Вот минимальный пример GitHub Actions для сборки Android-приложения и сохранения APK как артефакта:

name: Android Build
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: Cache Gradle dependencies
        uses: actions/cache@v4
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
          restore-keys: |
            ${{ runner.os }}-gradle-
      - name: Build release APK
        run: ./gradlew assembleRelease
      - name: Upload APK
        uses: actions/upload-artifact@v4
        with:
          name: app-release
          path: app/build/outputs/apk/release/*.apk

Этот пример настраивает JDK, включает кэширование Gradle, запускает релизную сборку и загружает полученный APK. Подстройте версию Java и путь к Gradle wrapper под ваш проект.

Сборка iOS с Xcode

Сборка iOS использует Xcode и его систему сборки. Пайплайн запускает xcodebuild с параметрами, указывающими схему, конфигурацию (Debug или Release) и назначение (симулятор или физическое устройство). Файл проекта — .xcodeproj или .xcworkspace. Если проект использует CocoaPods, нужен workspace-файл.

Управление зависимостями — частая причина проблем в iOS-сборках. Многие iOS-проекты используют CocoaPods, Swift Package Manager или Carthage. Пайплайн должен выполнить pod install или swift package resolve перед сборкой. Если версии, разрешённые в CI, отличаются от тех, что использует ваша команда локально, вы получите ошибки, которые сложно отладить. Зафиксируйте версии зависимостей. CocoaPods генерирует файл Podfile.lock. Добавьте его в репозиторий и убедитесь, что пайплайн его использует.

Результат сборки iOS — это IPA-файл. В отличие от APK, IPA — это пакет, содержащий подписанное приложение. Пайплайны обычно создают два варианта: development IPA для внутреннего тестирования с development-сертификатами и distribution IPA для App Store. Оба варианта требуют правильной подписи, которую мы рассмотрим отдельно.

Хранение артефактов сборки

И Android, и iOS сборки создают артефакты, которые нужно где-то хранить. Самый простой подход — использовать встроенное хранилище артефактов вашей CI-платформы. Для больших команд или более сложных рабочих процессов загружайте артефакты в общее хранилище, например S3 или Google Cloud Storage.

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

flowchart TD A[Checkout Code] --> B[Android: Gradle Build] A --> C[iOS: Xcode Build] B --> D[APK / AAB] C --> E[IPA] D --> F[Artifact Storage] E --> F F --> G[Version & Build Number]

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

Практический чек-лист

Прежде чем объявить пайплайн сборки мобильного приложения готовым, пройдитесь по этому короткому чек-листу:

  • Версии SDK в build.gradle совпадают в локальном окружении и в CI
  • Кэширование зависимостей Gradle включено
  • Podfile.lock или его аналог добавлен в репозиторий и используется в CI
  • Схема и конфигурация Xcode явно указаны в пайплайне
  • Имена файлов артефактов содержат номер версии и номер сборки
  • Артефакты сборки сохранены и доступны после завершения пайплайна

Вывод

Пайплайн сборки мобильного приложения — это не просто запуск компилятора. Это воспроизведение одного и того же окружения, одних и тех же зависимостей и одной и той же конфигурации каждый раз. Когда локальная сборка работает, а пайплайн падает, проблема почти всегда в различии одного из этих трёх элементов. Зафиксируйте их, агрессивно кэшируйте и чётко именуйте артефакты. Остальная часть пайплайна зависит от того, насколько правильно выполнен этот первый шаг.