Над сервисом для построения CI/CD даже не задумывался, есть GitLab, что еще нужно? Первым шагом нужно создать файл .gitlab-ci.yaml в корне проекта. Далее я подробно разберу его содержимое, а для тех кто хочет посмотреть сразу все, в конце статьи есть ссылки на репозиторий. Тем кто хоть немного знаком с синтаксисом .gitlab-ci.yml должно быть все понятно.

Объявляем переменную для всего файла, она будет доступна как в самом файле, так и в переменных среды в контейнере.

variables:  
	PROJECT_NAME: TestFirebaseAll

Указываем шаги сборки, по дефолту есть build, test и deploy, которые выполняется в соответствующем порядке. Можно определить свои и вставить в нужные позиции.

stages:  
  - build  
  - deploy

Далее идет шаблон задачи (Job) для сборки, это всего лишь шаблон и как его использовать будет показано ниже. Cамое главное необходимо понять, что он не включается в шаги pipeline. Далее по строкам:

  • stage - определяем на каком шаге сборки нужно запускать задачу (у нас это build);

  • image - docker образ где все будет запускаться, туда же (в определенную папку, она и будет рабочей директорией) будет скопирован наш репозиторий;

  • variables - еще раз определяем переменную, но уже на уровне задачи, это нужно для переиспользования шаблона в будущем;

  • artifacts - наши собранные aad файлы нужно будет где-то хранить, наиболее простым решением будет использование артефактов, Указываем директорию, которую нужно будет сохранить в виде архива, он будет доступен для скачивания;

  • before_script - команды которые нужно выполнить перед началом основных задач (еще существует after_script), у нас это создание файла ключа для подписи aab файла (здесь используем еще одну переменную среды KEYSTORE_FILE, ее мы определили сами, как именно - далее);

  • script - собственно сама сборка проекта, здесь ничего интересного нет, команды взяты прямиком из Qt.

Здесь мне очень сильно помог репозиторий пользователя rabits, там лежат Dockerfile для сборки образов, где установлен Qt, есть как под desktop, так и android, естественно все есть на docker hub. Также есть замечательный репозиторий qtci, содержащий различные скрипты для использования qt в ci-системах.

.build_template:
  stage: build
  image: rabits/qt:5.15-android
  variables:
    ANDROID_ABIS: ""
  artifacts:
    name: aab-${ANDROID_ABIS}
    paths:
      - ./dist/build/outputs/bundle/
    expire_in: 1 week  
  before_script:
    - echo ${KEYSTORE_FILE} | base64 -d > ./release.keystore
  script:    
    - qmake ./app/${PROJECT_NAME}.pro -spec android-clang "CONFIG+=qtquickcompiler" ANDROID_ABIS="${ANDROID_ABIS}"
    - make
    - make INSTALL_ROOT=./dist install
    - sudo chown -R $(whoami) $ANDROID_HOME
    - androiddeployqt --input ./android-${PROJECT_NAME}-deployment-settings.json --output ./dist
      --aab --deployment bundled --gradle --jarsigner --sign ./release.keystore ${KEY_ALIAS} --storepass ${KEY_PASSWORD}INSTALL_ROOT=./dist install    - sudo chown -R $(whoami) $ANDROID_HOME    - androiddeployqt --input ./android-${PROJECT_NAME}-deployment-settings.json --output ./dist      --aab --deployment bundled --gradle --jarsigner --sign ./release.keystore ${KEY_ALIAS} --storepass ${KEY_PASSWORD}

И вот первая выполняемая задача, созданная на основе предыдущего шаблона. С помощью переопределенной переменой мы указываем под какую платформу будем собирать, у нас их две arm64-v8a и armeabi-v7a. Все файлы будут сохранены в артефактах и доступны для скачивания в интерфейсе GitLab.

build-v8a:
  extends: .build_template
  variables:
    ANDROID_ABIS: "arm64-v8a"
    
build-v7a:
  extends: .build_template
  variables:
    ANDROID_ABIS: "armeabi-v7a"

Далее идет еще один шаблон, для деплоя в Google Market. Для этого мы используем утилиту под названием fastlane. Это довольно мощная программа, позволяющая делать скриншоты программы, загружать их в Google Market, деплоить во все стадии разработки (внутреннее тестирование, закрытое тестирование, открытое тестирование и релиз) приложение и это все для Android и iOS!

.deploy_google:
  stage: deploy
  image: zl0i/fastlane-ci:latest
  before_script:
    - echo ${GOOGLE_SERVICE} | base64 -d > ./google_play_api_key.json

Для работы этой утилиты нужно создать Gemfile в корне проекта, а также папку fastlane с файлами Appfile и Fastfile.

#Gemfile
source "https://rubygems.org"
gem "fastlane"

Appfile содержит путь к json-файлу с ключами сервисного аккаунта для деплоя в Google Market, а также название вашего пакета приложения (package name). Сервисный аккаунт Google дает нам права производить различные операции в Google Console, как создать такой аккаунт написано здесь. В итоге у нас должен появится json файл, скачиваем его к себе (но не в корень проекта, он не должен быть в гите).

#Appfile
json_key_file("./google_play_api_key.json") 
package_name("com.zloi.firebase.test") #Здесь нужно указать свой package

Fastfile содержит описание задач, более подробно можно посмотреть в документации, но вкратце desc - то что будет выводиться в консоль после успешного завершения задачи, lane - ключевое слово, то что идет после двоеточия название задачи (необходимо для запуска), ключевое слово do и дальше описание что нужно сделать. Стоить отметить, что fastlane также умеет собирать проекты и инкриминировать номер версии. В данном случае мы загружаем наш собранный aab файл в стадию внутреннего тестирования. Остальные задачи толкают с внутреннего тестирования на открытое и так далее, вплоть до релиза.

#Fastfile
default_platform(:android)

platform :android do

  desc "Submit a new Internal Build to Play Store"
  lane :internal do
    upload_to_play_store(track: 'internal', aab: './dist/build/outputs/bundle/release/dist-release.aab')
  end

  desc "Promote Internal to Alpha"
  lane :promote_internal_to_alpha do
    upload_to_play_store(track: 'internal', track_promote_to: 'alpha')
  end

  desc "Promote Alpha to Beta"
  lane :promote_alpha_to_beta do
    upload_to_play_store(track: 'alpha', track_promote_to: 'beta')
  end

  desc "Promote Beta to Production"
  lane :promote_beta_to_production do
    upload_to_play_store(track: 'beta', track_promote_to: 'production')
  end
end

А вот сами задачи GitLab CI на основе предыдущего шаблона, автоматически выполняется только публикация на внутреннее тестирование, остальные в ручном режиме (в интерфейсе GitLab). Стоит отметить, что артефакты автоматически загружаются в рабочую директорию перед запуском.

deploy_android_internal:
  extends: .deploy_google
  script:
    - bundle exec fastlane internal

deploy_android_alfa:
  extends: .deploy_google
  script:
    - bundle exec fastlane promote_internal_to_alpha
  when: manual

deploy_android_beta:
  extends: .deploy_google
  script:
    - bundle exec fastlane promote_alpha_to_beta
  when: manual

deploy_android_production:
  extends: .deploy_google
  script:
    - bundle exec fastlane promote_beta_to_production
  when: manual

Теперь нужно настроить наш репозиторий, мы должны добавить 4 ключа в переменные GitLab CI, причем google_play_api_key.json и release.keystore нужно преобразовать в base64 строку. В итоге у нас должно появится что-то подобное. При создании переменных не забудте снять отметки с Protect variable и поставить на Mask variable.

Вот и все! Можно пушить в наш репозиторий и наслаждаться автоматической сборкой и размещением в Google Market. Естественно можно улучшить pipeline и добавить прохождение тестов, в том числе на Firebase Test Lab и много чего еще. Стоит отметить, что сама сборка занимает не мало времени (около 12 минут), docker-образ с Qt и нужными ndk/sdk весит 1,67 ГБ, также артефакты обычно весят не мало (а бесплатное место на GitLab ограничено), поэтому лучше сделать запуск pipeline по присвоению тэгу.

Весь код целиком можно посмотреть по ссылкам ниже, это небольшой проект, созданный для изучения функций Firebase и интеграции их в Qt Android.

GitHub - основной репозиторий.

GitLab - настроен автоматический импорт для запуска pipeline.

Очень буду рад если подскажете как внедрить сборку под iOS. Конструктивная критика всегда приветствуется.

Комментарии (2)


  1. lepota
    02.01.2022 09:33

    "...pipelane..."


    1. zl0i Автор
      02.01.2022 09:33

      Спасибо за замечание, уже исправил)