Как стать автором
Обновить
47.36

Как настроить Gitlab CI/CD для Android и Firebase App Distribution для доставки сборок

Время на прочтение9 мин
Количество просмотров8.9K

Привет, меня зовут Антон Рябых, я технический директор компании Doubletapp, и я расскажу, как реализовать сборку Android‑приложений на Gitlab CI/CD с последующей загрузкой в Firebase App Distribution для удобной доставки.

Это позволит нам:

  • автоматически собирать сборки на каждый пуш или Merge request;

  • прогонять тесты на сборках и не допускать мерджа веток, которые не прошли тесты;

  • доставлять сборки заинтересованным лицам (тестирование, менеджеры, клиенты, другие разработчики, и т. д.).

Данная статья будет полезна как людям с опытом в CI/CD, так и Android‑разработчикам — новичкам в CI/CD и DevOps теме в целом. Поэтому, кроме непосредственного описания настройки Gitlab CI и Firebase App Distribution, мы также поговорим о том, что такое CI/CD, и о том, что такое Docker. Статья подразумевает, что ваш репозиторий находится в Gitlab. В качестве вычислительных мощностей, собирающих сборки, будет использован сам Gitlab, дающий 2000 бесплатных минут для сборок в месяц (большее количество минут можно докупать, но для небольшой команды бесплатного лимита может быть достаточно. Также можно легко настроить запуск сборок на своих мощностях).

Что такое CI/CD?

По-умному:

  • CI/CD (Continuous Integration, Continuous Delivery — непрерывная интеграция и доставка) — это технология автоматизации тестирования и доставки новых модулей разрабатываемого проекта заинтересованным сторонам (разработчики, аналитики, инженеры качества, конечные пользователи и др.)

По-простому:

  • CI: вы делаете пуш в удаленный репозиторий → срабатывает триггер и удаленно собирается приложение, прогоняются тесты, создается апк — это непрерывная интеграция, Continuous integration.

  • CD: после предыдущего шага сборка где-то лежит — лениво и долго за ней идти, искать, качать, объяснять другим, где она, поэтому она автоматом отправляется туда, откуда ее легко забрать — это непрерывная доставка, Continuous Delivery.

Как настроить Gitlab CI/CD?

Сборки будут происходить на ресурсах Gitlab, используем бесплатный лимит (2000 минут).

Чтобы начать собирать, нужно в корень репозитория положить специальный файл, .gitlab-ci.yml. Про него поговорим позже.

Как происходит сборка — для общего понимания:

  1. Сервер CI видит, что нужно что-то собрать (произошел пуш или сработал иной триггер, например, запустили руками) и стартует pipeline. Pipeline — это верхнеуровневый компонент, который может состоять из нескольких работ — job-ов. Например, сборка дев билда — один job, сборка prod билда — второй, деплой — третий;

  2. Сервер CI берет первую джобу из пайплайна и ставит в очередь на выполнение;

  3. Свободный агент берет джобу, выкачивает репозиторий;

  4. Агент стартует Docker:

    • Docker по-простому — это легковесная виртуальная машина. Она разворачивает образы на основе linux, например, может развернуть Убунту. В эту виртуалку CI система сразу закинет и наш репозиторий. После развертывания виртуалка может пойти по написанному заранее скрипту (его можно описать в .gitlab-ci.yml). Например, в этом скрипте можно написать набор команд, который скачает нужные пакеты, Android-sdk, command-line-tools и т.д. Скачав их, можно выполнить обычный ./gradlew assembleDebug, т.е. сборку, и получить нужный АПК.

    • Однако существует важный аспект! Можно не скачивать при каждом старте Android-sdk, command-line-tools и все остальное, а сразу зашить их в образ. Т.е. взять образ Убунты и на основе создать новый образ, та же Убунта + все нужные зависимости уже установлены. Это называется сбилдить образ. После можно запушить этот образ в docker hub — вроде гитхаба для образов. Оттуда его кто угодно сможет скачать. И тогда агент будет разворачивать этот образ, в котором уже есть все зависимости, и в нем стартовать сборку.

    • Какой использовать образ, какие нужны первоначальные команды и какие команды для сборок — это все описывается в .gitlab-ci.yml.

  5. В докере собирается сборка — результат работы CI. Он называется артефакт;

  6. После выполнится скрипт, который отправит сборку в Firebase App Distribution — систему, которая позволит выкачать apk тестировщикам из приложения, уже установленного у них (иначе бы им пришлось выкачивать сборку с сайта gitlab). Скрипт этот выполнится в отдельном образе, об этом позже.

Как сконфигурировать .gitlab-ci.yml

Для начала разберем основные термины.

Как уже было сказано, когда CI понимает, что нужно что-то собрать, он стартует pipeline. Pipeline — это его верхнеуровневый компонент, который состоит из job-ов и stage-ов. Job-ы мы уже разобрали. Stage — это этапы, когда происходят джобы. Например, может быть 3 стейджа: build, test, deploy. Несколько джобов на первом стейдже (build) сделают сборки, несколько — прогонят тесты на стейдже test, и несколько джобов загрузят сборки на стейдже deploy.

Также отмечу, что в данном примере мы хотим запускать CI/CD не на каждый пуш, а только на пуши в ветки, которые находятся в прке или когда создана merge request. А также на все пуши (и мерджи) в мастер и в релиз-ветки.

Посмотрим пример .gitlab-ci.yml файла (напомню, его нужно положить в корень вашего проекта). В этом файле будут сборки debug и release и собирается 2 флейвора — dev для дебага и prod для релиза. Если у вас есть только один флейвор, просто уберите из команд testDevDebug, assembleDevDebug, testProdRelease, assembleProdRelease части про флейвор (dev, prod). Release сборка будет подписана. После сборки будет деплой в Firebase App Distribution.

# Тут опишем правила запуска пайплайна: автоматическая сборка ветки master, 
# веток release/* (например, release/2.1.0), merge request-ов и пушей 
# в существующие merge request.
workflow: 
  rules:
    # Правило: если ветка, в которой произошел коммит, называется master 
    # или release*, то запускаем пайплайн всегда, дальше условия не смотрятся
    - if: '$CI_COMMIT_BRANCH =~ /release*/ || $CI_COMMIT_BRANCH == "master"' 
      when: always
    # Правило: если произошел пуш, то ничего не запускаем 
    # (если мы на прошлом шаге решили запускать, то это условие уже не важно)
    - if: '$CI_PIPELINE_SOURCE == "push"' 
      when: never
    # Если пред условия неактуальны, то любой другой триггер запустит сборку. 
    # Например, если сделали merge request - будет запущен pipeline. 
    - when: always 

stages: # определяем 2 стейджа - build и deploy
  - build
  - deploy

assembleDevDebug: # описываем джобу для дебаг сборки с dev флейвором
  # Будем использовать образ из моего аккаунта на docker hub - в этот образ 
  # вшиты android-sdk для 29 версии и нужные тулзы 
  image: lenant255/android:latest
  stage: build # эта джоба выполнится на build stage
  script: 
    # Cобственно команда тестирования и сборки. 
    # Напишите нужную вам, подставьте верный flavor.
    - ./gradlew testDevDebug assembleDevDebug 
  artifacts:
    paths:
      # Путь внутри docker контейнера, куда попадут apk файлы
      - app/build/outputs/ 

# Описываем джобу для release сборки с prod флейвором - тут все то же самое, 
# но есть дополнительные шаги для подписания
assembleProdRelease:  
  image: lenant255/android:latest
  stage: build
  script:
    - echo $KEYSTORE_FILE | base64 -d > my.keystore
    # Для подписания передадим некоторые параметры в gradlew, они 
    # определяются в интферейсе gitlab ci, о них позже
    - ./gradlew testProdRelease assembleProdRelease 
      -Pandroid.injected.signing.store.file=$(pwd)/my.keystore
      -Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD
      -Pandroid.injected.signing.key.alias=$KEY_ALIAS
      -Pandroid.injected.signing.key.password=$KEY_PASSWORD
  artifacts:
    paths:
      - app/build/outputs/

deployDev: # джоба для деплоя дев сборки
  # Нам нужен npm для установки firebase-tools, так что используем образ с node 
  image: node:latest 
  stage: deploy
  # Опишем, что с предыдущего стейджа нам нужно только чтобы завершилась 
  # джоба assembleDevDebug. Это позволит не дожидаться завершения 
  # assembleProdRelease, кроме того у нас в этой джобе будут только 
  # артефакты из assembleDevDebug
  needs: [assembleDevDebug] 
  script:
    # Установим firebase-tools
    - npm install -g firebase-tools 
    # Найдем апк файл и запишем в переменную apkfile
    - apkfile=$(find . -name "*.apk") 
    # Выполним команду, которая отправит нашу сборку в firebase
    - firebase appdistribution:distribute $apkfile --app $FIREBASE_APP_DEV_ID 
      --release-notes "$CI_COMMIT_MESSAGE" # в качестве release-notes будем использовать сообщение из коммита
      --groups "your-app-testers" # укажите группу тестировщиков, которые получат сборку, о настройке группы ниже
      --token "$FIREBASE_TOKEN"

# Джоба для деплоя прод сборки, тут все как в предыдущей джобе, 
# только другой id приложения и другая джоба указана в needs
deployProd: 
  image: node:latest
  stage: deploy
  needs: [assembleProdRelease]
  script:
    - npm install -g firebase-tools
    - apkfile=$(find . -name "*.apk")
    - firebase appdistribution:distribute $apkfile --app $FIREBASE_APP_PROD_ID
      --release-notes "$CI_COMMIT_MESSAGE" 
      --groups "your-app-testers"
      --token "$FIREBASE_TOKEN"

Комментарии в файле объясняют, что есть что.

Посмотрим, как завести переменные окружения KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD, KEYSTORE_FILE, FIREBASE_APP_DEV_ID, FIREBASE_APP_PROD_ID и FIREBASE_TOKEN.

Как добавить переменные окружения для подписания продовой сборки

Значение переменных KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD понятно из названия: пароль от keystore, alias ключа и пароль от ключа.

Значение для переменной KEYSTORE_FILE можно получить так:

base64 -i ваш_jks_файл

После выполнения этой команды нужное значение будет отображено в терминале.

Для добавления переменных в ваш репозиторий, зайдите в Settings → CI/CD → Variables. Введите нужные переменные. 

Безопасно ли хранить здесь эти переменные? Нет, лучше сделать подписание на отдельном сервере, где будут лежать все нужные секреты. Но для примера это место подходит.

Теперь получим токены для Firebase App Distribution

Получить FIREBASE_TOKEN можно способом, описанным тут.

Чтобы получить FIREBASE_APP_DEV_ID и FIREBASE_APP_PROD_ID, зайдите в настройки проекта в Firebase и посмотрите в поле App id у ваших приложений:

Далее на странице App Distribution нажмите «Начать».

Обязательно нужно добавить тестеров и создать для них группу. Это нужно сделать на странице «Тестировщики и группы».

Добавьте переменные в интерфейс Gitlab по аналогии с переменными для подписания продовой сборки.

Готово! Теперь пушьте в отдельной ветке .gitlab-ci.yml и делайте merge request. Должна начаться сборка. Это станет понятно по появившемуся полю pipeline в merge request.

Перейдя в pipeline, можно смотреть за работой джобов.

После успешной сборки на почту пользователям, добавленным в группу тестирования, придет ссылка на скачивание приложения App Tester, в котором будут сборки.

Чтобы вам было проще идентифицировать сборки, можно давать им уникальные versionCode из CI. Можно при этом брать уникальные на пайплайн, чтобы все сборки из этого пайплайна были с этим номером (и продовая, и тестовая, и любая другая). Для этого в build.gradle добавьте:

def versionCodeFromCi = System.getenv('CI_PIPELINE_IID') as Integer ?: 999

android {
   defaultConfig {
       versionCode versionCodeFromCi
       ...
   }
}

Отлично! Теперь вы можете собирать сборки, прогонять на них тесты и получать/доставлять их через App Tester. Перед тем, как закончить, разберем, что делать, если вам нужны иные версии android-sdk и build-tools для сборки.

Как создать свой Docker-образ для сборки

Для начала поймем, зачем его создавать. Мой образ, на который есть ссылка, использует android-sdk и build-tools 29-й версии. Вам может потребоваться другая версия. Некоторые можно найти, если погуглить. Но в каких-то ситуациях этого может быть недостаточно.

Чтобы создать свой образ, установите Docker на свою систему (https://www.docker.com/get-started) и зарегистрируйтесь в Docker hub. После создайте файл с названием Dockerfile в любом месте (но не в вашем репозитории, там он как раз не нужен).

Пример Dockerfile для 29-й версии:

FROM openjdk:8-jdk

ENV ANDROID_COMPILE_SDK="29" \
  ANDROID_BUILD_TOOLS="29.0.3" \
  ANDROID_SDK_TOOLS="6858069" \
  ANDROID_SDK_ROOT="/android-sdk"

RUN apt-get --quiet update --yes
RUN apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1 file
RUN wget --quiet --output-document=command-line-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
RUN unzip -d command-line-tools command-line-tools.zip
RUN mkdir ${ANDROID_SDK_ROOT}
RUN mkdir ${ANDROID_SDK_ROOT}/cmdline-tools
RUN mv command-line-tools/cmdline-tools/ ${ANDROID_SDK_ROOT}/cmdline-tools/tools
RUN echo y | ${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null
RUN echo y | ${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin/sdkmanager "platform-tools" >/dev/null
RUN echo y | ${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null
RUN export PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/cmdline-tools/tools/bin
RUN yes | ${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin/sdkmanager --licenses

Тут берется в качестве базового образа linux дистрибутив с установленной jdk-8. Далее задаются переменные окружения и выполняются нужные команды. Имейте в виду, что для более старых версий вместо command line tools нужно было качать android build tools, так что нужно поменять ссылки и номер.

После создания Dockerfile выполните в папке, где он лежит

docker build -t название_вашего_образа

Сбилдится ваш образ. После ему нужно указать тэг. Посмотрите IMAGE ID вашего образа — его можно увидеть, выполнив команду

docker image ls

И после выполните команду

docker tag yourimageid yourhubusername/yourimage:tag

yourimageid — это id вашего образа из предыдущего шага, yourhubusername — ваше имя в docker hub, yourimage:tag — название и тэг вашего образа, по которому его смогут найти в docker hub.

После выполните

docker push yourhubusername/yourimage

Теперь вы можете в .gitlab-ci.yml использовать ваш образ.

Заключение

Использование CI/CD во многом улучшает процесс разработки: вы всегда можете быть уверены, что ваши сборки прошли автоматическое тестирование и были доставлены QA-отделу. Разработчикам не нужно думать о том, чтобы руками кидать APK файлы QA или менеджерам или запускать автотесты — все происходит автоматически.

Теги:
Хабы:
+7
Комментарии6

Публикации

Информация

Сайт
doubletapp.ai
Дата регистрации
Дата основания
Численность
51–100 человек
Местоположение
Россия