Flutter & GitLab CI/CD. Доставка мобильного приложения в Significa, TestFlight и Google Play
Привет! Меня зовут Данил Абдрафиков, я мобильный разработчик в компании TAGES. После успешной настройки сборки и подписания Flutter-приложений во второй части, остался последний, но не менее важный этап — дистрибуция приложения.
В современных условиях автоматизация деплоя на такие платформы, как Google Play, TestFlight и Significa, становится необходимостью. В этой статье мы разберем, как с помощью GitLab CI/CD настроить автоматическую отправку ваших приложений в магазины, чтобы вы могли сосредоточиться на разработке, а не на рутинных задачах.
Введение
Когда речь заходит о «доставке» мобильного приложения, большинство думает только о публикации в App Store или Google Play. На практике же доставка — это не одно действие, а набор процессов, которые зависят от разных задач: нужно ли выкатить сборку коллегам внутри компании или же отправить релиз миллионам пользователей. В первом случае важны скорость, простота и доступность сборок, во втором — изолированность, прозрачный процесс и контроль релизов.
Чтобы достичь этой цели, мы охватим следующие темы:
- Подготовка сервера дистрибуции Significa.
- Добавление скриптов деплоя в репозиторий с шаблонами.
- Деплой приложения в Significa.
- Деплой приложения в Google Play и TestFlight.
Все подробно разберем на примерах и готовых конфигурациях.
Подготовка сервера дистрибуции Significa
Significa App Distribution Server — это минималистичный сервер для внутренней дистрибуции мобильных сборок (.ipa и .apk). Его преимущество в том, что он решает одну конкретную задачу максимально просто: принимает готовые артефакты, хранит их и выдает простую ссылку/QR‑код для быстрой установки на устройстве.
Если хотите сильнее погрузиться в эту тему, то у проекта есть подробный материал с описанием данного инструмента и пошаговым разбором использования. Важно понимать, что это не замена дистрибуции приложения через TestFlight/Google Play, а легкий инструмент для оперативной доставки мобильных приложений из CI (подходит для Ad‑hoc/Enterprise сценариев).
В рамках статьи мы не будем углубляться в подробности настройки и развертывания самого сервера Significa — это хорошо описано в официальной документации. Вашим результатом на этом этапе должен стать запущенный и доступный экземпляр Significa, в который мы далее настроим автоматическую отправку сборок из GitLab CI.
Добавление скриптов деплоя в репозиторий с шаблонами
В прошлой части статьи мы уже создали децентрализованный репозиторий с общими скриптами сборки, следующий шаг — добавить скрипты для доставки артефактов в Significa, TestFlight и Google Play. Для этого создадим папку deploy и поместим туда следующие файлы:
.
│ └── deploy
│ └── google_play.gitlab-ci.yml
│ └── significa.gitlab-ci.yml
│ └── testflight.gitlab-ci.yml
.
./deploy/google_play.gitlab-ci.yml
Этот файл помогает автоматизировать отправку AAB-файла в Google Play, загружая его в указанную группу распространения.
include:
- local: '/common.gitlab-ci.yml'
.supply_track:
script:
- bundle exec fastlane run supply track:"$SUPPLY_TRACK" aab:"$BUILD_AAB_FILE_PATH"
.variables:deploy_google_play:
variables:
GOOGLE_PLAY_REQUIRED: true
.before_script:deploy_google_play:
before_script:
- !reference [ .clone_project, before_script ]
- !reference [ .configure_fastlane, before_script ]
.script:deploy_google_play:
script:
- !reference [ .supply_track, script ]
./deploy/significa.gitlab-ci.yml
Данный файл автоматизирует выгрузку артефакта на сервер Significa с целью внутренней дистрибуции и выдачи ссылки для установки:
include:
- local: '/common.gitlab-ci.yml'
.configure_significa:
before_script:
- export SIGNIFICA_ENABLED=$([ -n "$SIGNIFICA_SERVER_URL" ] && [ -n "$SIGNIFICA_SECRET" ] && echo "true" || echo "false")
- |
if ! $SIGNIFICA_ENABLED; then
echo "❌️ Error: App distribution server is disabled. Please specify the required variables: SIGNIFICA_SERVER_URL, SIGNIFICA_SECRET"
echo "Read more: https://github.com/significa/app-distribution-server"
exit 1
fi
.upload_to_significa:
script:
- echo -e "\e[0Ksection_start:`date +%s`:upload_to_significa_section\r\e[0KDistribute app via Significa"
- |
i=1
while [ $i -le "$SIGNIFICA_UPLOAD_MAX_RETRIES" ]; do
echo "ℹ️ Attempt $i to upload to Significa..."
BUILD=$(curl -X "POST" \
"$SIGNIFICA_SERVER_URL/api/upload" \
-H "Accept: application/json" \
-H "X-Auth-Token: $SIGNIFICA_SECRET" \
-H "Content-Type: multipart/form-data" \
-F "app_file=@$APP_FILE")
if echo "$BUILD" | jq empty >/dev/null 2>&1; then
BUNDLE_VERSION=$(echo "$BUILD" | jq -r '.bundle_version // empty')
UPLOAD_ID=$(echo "$BUILD" | jq -r '.upload_id // empty')
PLATFORM_NAME=$(echo "$BUILD" | jq -r '.platform // empty')
else
echo "⚠️ Warning: Received invalid JSON response or no response at all:"
echo "$BUILD"
fi
if [[ -n "$BUNDLE_VERSION" && -n "$UPLOAD_ID" && -n "$PLATFORM_NAME" ]]; then
echo "✅ Upload successful!"
break
else
echo "⚠️ Warning: Upload failed. Missing required fields. Retrying in $SIGNIFICA_UPLOAD_RETRY_DELAY seconds..."
sleep "$SIGNIFICA_UPLOAD_RETRY_DELAY"
fi
i=$((i + 1))
done
- |
if [ -z "$BUNDLE_VERSION" ] || [ -z "$UPLOAD_ID" ] || [ -z "$PLATFORM_NAME" ]; then
echo "❌ Error: All attempts to upload to Significa failed."
exit 1
fi
- |
echo "ℹ️ Info: Distribute app version $BUNDLE_VERSION ($UPLOAD_ID)"
- echo "UPLOAD_ID=$UPLOAD_ID" > "$DEPLOY_ENV_PATH"
- |
if [[ -n "$CI_PROJECT_TOKEN" ]]; then
if [ -z "${!SIGNIFICA_UPLOADS_VAR}" ]; then
export ${SIGNIFICA_UPLOADS_VAR}='{}'
fi
SIGNIFICA_UPLOADS_NEW_VALUE=$(echo "${!SIGNIFICA_UPLOADS_VAR}" | jq --arg branch "$CI_COMMIT_REF_SLUG" --arg upload_id "$UPLOAD_ID" '
.[$branch] = (.[$branch] // []) + [$upload_id]
')
eval "export $SIGNIFICA_UPLOADS_VAR='$SIGNIFICA_UPLOADS_NEW_VALUE'"
echo "✅ Updated ${SIGNIFICA_UPLOADS_VAR}: ${!SIGNIFICA_UPLOADS_VAR}"
curl --request POST --header "PRIVATE-TOKEN: $CI_PROJECT_TOKEN" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/variables" \
--form "key=$SIGNIFICA_UPLOADS_VAR" \
--form "value=${!SIGNIFICA_UPLOADS_VAR}" \
--form "description=These upload IDs are used to manage files, particularly for deletion or cleanup operations. Variable auto synced." \
--form "raw=true"
curl --request PUT --header "PRIVATE-TOKEN: $CI_PROJECT_TOKEN" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/variables/$SIGNIFICA_UPLOADS_VAR" \
--form "value=${!SIGNIFICA_UPLOADS_VAR}"
else
echo "⚠️ Warning: The `CI_PROJECT_TOKEN` variable is required to enable automatic cleanup of builds in Significa."
echo "If this variable is not set, builds will not be deleted automatically when environment stopped."
fi
- echo -e "\e[0Ksection_end:`date +%s`:upload_to_significa_section\r\e[0K"
.cleanup_significa:
script:
- echo -e "\e[0Ksection_start:`date +%s`:cleanup_significa_section\r\e[0KCleaning up Significa resources"
- |
if [[ -z "$CI_PROJECT_TOKEN" ]]; then
echo "⚠️ Warning: The `CI_PROJECT_TOKEN` variable is required to enable automatic cleanup of builds in Significa."
echo "If this variable is not set, builds will not be deleted automatically when environment stopped."
exit 1
fi
- |
if [ -z "${!SIGNIFICA_UPLOADS_VAR}" ]; then
export ${SIGNIFICA_UPLOADS_VAR}='{}'
fi
UPLOAD_IDS=$(echo "${!SIGNIFICA_UPLOADS_VAR}" | jq -r --arg branch "$CI_COMMIT_REF_SLUG" '.[$branch] // []')
- |
if [ "$(echo "$UPLOAD_IDS" | jq length)" -gt 0 ]; then
echo "ℹ️ Info: Found uploads to delete: $UPLOAD_IDS"
for UPLOAD_ID in $(echo "$UPLOAD_IDS" | jq -r '.[]'); do
echo "ℹ️ Info: Deleting upload $UPLOAD_ID"
curl -X "DELETE" \
"$SIGNIFICA_SERVER_URL/api/delete/$UPLOAD_ID" \
-H "Accept: application/json" \
-H "X-Auth-Token: $SIGNIFICA_SECRET"
done
else
echo "ℹ️ Info: No uploads found for this branch."
exit 0
fi
- |
eval "SIGNIFICA_UPLOADS_VALUE=\${$SIGNIFICA_UPLOADS_VAR}"
SIGNIFICA_UPLOADS_VALUE=$(echo "$SIGNIFICA_UPLOADS_VALUE" | jq --arg branch "$CI_COMMIT_REF_SLUG" 'del(.[$branch])')
if ! curl --request PUT --header "PRIVATE-TOKEN: $CI_PROJECT_TOKEN" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/variables/$SIGNIFICA_UPLOADS_VAR" \
--form "value=$SIGNIFICA_UPLOADS_VALUE"; then
echo "❌ Failed to update variable $SIGNIFICA_UPLOADS_VAR"
exit 1
fi
- echo -e "\e[0Ksection_end:`date +%s`:cleanup_significa_section\r\e[0K"
# Deploy android
.variables:deploy_android:
variables:
APP_FILE: $BUILD_APK_FILE_PATH
DEPENDENCIES_TO_INSTALL: "curl jq"
DEPLOY_ENV_PATH: "./deploy.env"
SIGNIFICA_UPLOAD_MAX_RETRIES: 3
SIGNIFICA_UPLOAD_RETRY_DELAY: 10
SIGNIFICA_UPLOADS_VAR: "SIGNIFICA_ANDROID_UPLOADS"
.variables:cleanup_android:
variables:
DEPENDENCIES_TO_INSTALL: "curl jq"
SIGNIFICA_UPLOADS_VAR: "SIGNIFICA_ANDROID_UPLOADS"
.before_script:deploy_android:
before_script:
- !reference [ .setup_environment, before_script ]
- !reference [ .configure_significa, before_script ]
.script:deploy_android:
script:
- !reference [ .upload_to_significa, script ]
.before_script:cleanup_android:
before_script:
- !reference [ .setup_environment, before_script ]
- !reference [ .configure_significa, before_script ]
.script:cleanup_android:
script:
- !reference [ .cleanup_significa, script ]
# Deploy iOS
.variables:deploy_ios:
variables:
APP_FILE: $BUILD_IPA_FILE_PATH
DEPENDENCIES_TO_INSTALL: "curl jq"
DEPLOY_ENV_PATH: "./deploy.env"
SIGNIFICA_UPLOAD_MAX_RETRIES: 3
SIGNIFICA_UPLOAD_RETRY_DELAY: 10
SIGNIFICA_UPLOADS_VAR: "SIGNIFICA_IOS_UPLOADS"
.variables:cleanup_ios:
variables:
DEPENDENCIES_TO_INSTALL: "curl jq"
SIGNIFICA_UPLOADS_VAR: "SIGNIFICA_IOS_UPLOADS"
.before_script:deploy_ios:
before_script:
- !reference [ .setup_environment, before_script ]
- !reference [ .configure_significa, before_script ]
.script:deploy_ios:
script:
- !reference [ .upload_to_significa, script ]
.before_script:cleanup_ios:
before_script:
- !reference [ .setup_environment, before_script ]
- !reference [ .configure_significa, before_script ]
.script:cleanup_ios:
script:
- !reference [ .cleanup_significa, script ]
./deploy/testflight.gitlab-ci.yml
Этот файл автоматизирует загрузку iOS‑сборки в App Store Connect и ее публикацию в TestFlight:
include:
- local: '/common.gitlab-ci.yml'
.upload_to_testflight:
script:
- |
bundle exec fastlane run upload_to_testflight \
api_key_path:"$APP_STORE_CONNECT_API_KEY_PATH" \
ipa:"$BUILD_IPA_FILE_PATH" \
skip_submission:true \
skip_waiting_for_build_processing:true
.variables:deploy_testflight:
variables:
APP_STORE_CONNECT_REQUIRED: true
.before_script:deploy_testflight:
before_script:
- !reference [ .clone_project, before_script ]
- !reference [ .configure_fastlane, before_script ]
.script:deploy_testflight:
script:
- !reference [ .upload_to_testflight, script ]
После этого коммитим, создаем новый тег и пушим изменения в удаленный репозиторий:
git commit -a -m "Добавить шаблоны деплоя для Android и iOS"
git tag v1.1.0
git push origin main v1.1.0
Деплой приложения в Significa
Мы уже на полпути к полной автоматизации! Осталось научить наш CI/CD-пайплайн не только собирать приложение, но и автоматически загружать его на наш сервер дистрибуции Significa. Для этого нужно выполнить всего два ключевых шага.
Шаг 1. Добавить секреты в GitLab
Чтобы GitLab Runner мог безопасно общаться с нашим сервером Significa, нам нужно добавить в проект три переменные окружения. Сделать это можно в настройках проекта: Settings → CI/CD → Variables.
CI_PROJECT_TOKEN (Переменная) – Токен проекта GitLab с правами API (Settings → Access tokens → Add new token). Нужен для управления сборками после загрузки.
SIGNIFICA_SECRET (Переменная) – Секретный ключ для доступа к Significa API.
SIGNIFICA_SERVER_URL (Переменная) – Базовый адрес сервера Significa (например, https://example.com).
Зачем нужен CI_PROJECT_TOKEN?
Это «умная» часть автоматизации. После успешной загрузки сборки наш скрипт сохранит ее уникальный ID из Significa в переменную проекта GitLab. Это позволит позже, при закрытии Merge Request или удалении ветки, автоматически удалить устаревшую сборку с сервера дистрибуции.
Шаг 2. Подключить новые шаги деплоя в .gitlab-ci.yml
Теперь нужно расширить конфигурацию, использовав новые шаблоны для деплоя. Обновляем файл .gitlab-ci.yml нашего Flutter-проекта:
include:
- project: 'project-group/flutter-ci-templates'
ref: v1.1.0
file:
- '/build/android.gitlab-ci.yml'
- '/build/ios.gitlab-ci.yml'
- '/deploy/significa.gitlab-ci.yml'
- '/common.gitlab-ci.yml'
deploy_android:
image: node:$NODE_VERSION
stage: deploy
needs:
- build_android
<<: *common_rules
artifacts:
reports:
dotenv: $DEPLOY_ENV_PATH
extends:
- .before_script:deploy_android
- .script:deploy_android
- .variables:deploy_android
environment:
name: android/$CI_COMMIT_REF_SLUG
url: $SIGNIFICA_SERVER_URL/get/$UPLOAD_ID
on_stop: cleanup_android
auto_stop_in: 1 month
tags:
- $MACOS_RUNNER_TAG
cleanup_android:
image: node:$NODE_VERSION
stage: cleanup
needs: [ ]
rules:
- if: |
$CI_PROJECT_TOKEN != "" &&
($CI_MERGE_REQUEST_IID || $CI_COMMIT_BRANCH =~ $CI_BRANCHES)
when: manual
allow_failure: true
- when: never
extends:
- .before_script:cleanup_android
- .script:cleanup_android
- .variables:cleanup_android
environment:
name: android/$CI_COMMIT_REF_SLUG
action: stop
tags:
- $MACOS_RUNNER_TAG
deploy_ios:
image: node:$NODE_VERSION
stage: deploy
needs:
- build_ios
rules:
- if: $CI_MERGE_REQUEST_IID || $CI_COMMIT_BRANCH =~ $CI_BRANCHES
when: on_success
- when: never
extends:
- .before_script:deploy_ios
- .script:deploy_ios
- .variables:deploy_ios
artifacts:
reports:
dotenv: $DEPLOY_ENV_PATH
environment:
name: ios/$CI_COMMIT_REF_SLUG
url: $SIGNIFICA_SERVER_URL/get/$UPLOAD_ID
on_stop: cleanup_ios
auto_stop_in: 1 month
tags:
- $MACOS_RUNNER_TAG
cleanup_ios:
image: node:$NODE_VERSION
stage: cleanup
needs: [ ]
rules:
- if: |
$CI_PROJECT_TOKEN != "" &&
($CI_MERGE_REQUEST_IID || $CI_COMMIT_BRANCH =~ $CI_BRANCHES)
when: manual
allow_failure: true
- when: never
extends:
- .before_script:cleanup_ios
- .script:cleanup_ios
- .variables:cleanup_ios
environment:
name: ios/$CI_COMMIT_REF_SLUG
action: stop
tags:
- $MACOS_RUNNER_TAG
Далее коммитим и пушим новые изменения в удаленный репозиторий, создаем Merge Request и смотрим на результат выполненной работы.
Шаг 3. Проверить интеграцию
Автоматический деплой после успешной сборки
В пайплайне появятся две новые ключевые задачи: deploy_android и deploy_ios. Они будут автоматически запускаться после успешной сборки, загружать артефакт (.apk/.ipa) на сервер Significa и выдавать вам готовую ссылку для установки.
Наглядный пример: у запущенного для Merge Request пайплайна видна стадия deploy, которая автоматически выполняется после стадии build.
Flutter project. Merge Request (deploy stage).
Автоматическая очистка и управление артефактами
Чтобы не захламлять сервер устаревшими сборками, мы также реализовали стадию cleanup. Она срабатывает автоматически в одном из двух сценариев:
- При вливании Merge Request. Сборки, созданные для целей код-ревью и тестирования этой ветки, становятся неактуальными и удаляются.
- При удалении ветки. Если ветка была удалена вручную, то все связанные с ней артефакты также очищаются.
Наглядный пример: стадия cleanup, которую можно выполнить вручную или дождаться когда сработает автоматический триггер для ее запуска.
Flutter project. Merge Request (cleanup stage).
Следовательно, сервер всегда будет содержать только актуальные версии приложения, что экономит дисковое пространство и снижает риск скачать устаревшую версию.
Централизованное управление окружениями в GitLab
Теперь каждое выполнение пайплайна, связанное с деплоем, автоматически создает или обновляет запись в соответствующем окружении, используя встроенные возможности GitLab Environments:
- Все сборки, их статус (active, stopped), коммит и ссылка для скачивания доступны в одном месте.
- Не нужно искать ссылки в логах пайплайна или чатах. Достаточно перейти в раздел Environments в вашем проекте GitLab.
- Можно вручную остановить старую среду, чтобы удалить все сборки, связанные с ней.
- В интерфейсе Merge Request отображается ссылка на развернутую сборку, что ускоряет процесс проверки.
Наглядный пример: список всех активных окружений в проекте с прямыми ссылками на последние развернутые сборки.
Flutter project. Environments.
Таким образом, интеграция с Significa сокращает ваше время от коммита до получения сборки с часов до минут, минимизирует человеческие ошибки и предоставляет команде удобный инструмент для проверки промежуточных версий приложения.
Деплой приложения в Google Play и App Store
На этом этапе мы автоматизируем процесс выкладки приложения в официальные магазины. Для обеспечения чистоты и безопасности производственного процесса мы создадим отдельный репозиторий, который будет отвечать исключительно за публикацию и управление релизами.
Шаг 1. Создать новый репозиторий
Создайте новый проект (например, flutter-infra) в GitLab и в корне этого репозитория поместите файл .gitlab-ci.yml со следующим содержимым:
stages:
- build
- deploy
include:
- project: 'project-group/flutter-ci-templates'
ref: v1.1.0
file:
- '/build/android.gitlab-ci.yml'
- '/build/ios.gitlab-ci.yml'
- '/deploy/google_play.gitlab-ci.yml'
- '/deploy/testflight.gitlab-ci.yml'
- '/common.gitlab-ci.yml'
variables:
BUILD_AAB_ARCHIVE: true
BUILD_AAB_FILE_NAME: "app-$BUILD_TYPE.aab"
BUILD_AAB_FILE_PATH: "$CI_PROJECT_DIR/build/$BUILD_AAB_FILE_NAME"
BUILD_APK_FILE_NAME: "app-$BUILD_TYPE.apk"
BUILD_APK_FILE_PATH: "$CI_PROJECT_DIR/build/$BUILD_APK_FILE_NAME"
BUILD_IPA_FILE_NAME: "Runner.ipa"
BUILD_IPA_FILE_PATH: "$CI_PROJECT_DIR/build/$BUILD_IPA_FILE_NAME"
BUILD_TYPE: release
DART_DEFINE_JSON_REQUIRED_KEYS: '[
"ENV_EXAMPLE_1",
"ENV_EXAMPLE_2"
]'
DART_DEFINE_PATH: "./launch.json"
FASTLANE_TEAM_ID: "EXAMPLE_TEAM_ID"
FLUTTER_SDK_VERSION: 3.35.1
GIT_CLONE_PROJECT_PATH: "${CI_PROJECT_NAMESPACE}/flutter-example"
GIT_CLONE_REF_NAME: "v1.0.0"
KEYCHAIN_NAME: "${CI_PROJECT_NAME}-${CI_COMMIT_REF_SLUG}.keychain-db"
LINUX_RUNNER_TAG: linux-runner-tag-name
MACOS_RUNNER_TAG: macos-runner-tag-name
PROJECT_ENTRY_POINT: "./lib/main.dart"
SIGN_TYPE: "appstore"
SUPPLY_TRACK: "beta"
TARGET_BUNDLE_MAPPING: |
[
{
"bundle_id": "com.example.myapp",
"target_name": "Runner"
}
]
default:
interruptible: true
.build_project: &build_project
- echo -e "\e[0Ksection_start:`date +%s`:build_project_section[collapsed=true]\r\e[0KBuild project"
- flutter pub get
- dart run build_runner build --delete-conflicting-outputs
- echo -e "\e[0Ksection_end:`date +%s`:build_project_section\r\e[0K"
build_android:
image: ghcr.io/cirruslabs/flutter:$FLUTTER_SDK_VERSION
stage: build
needs: [ ]
script:
- echo -e "\e[0Ksection_start:`date +%s`:build_app_section\r\e[0KBuild & sign app in $BUILD_TYPE mode"
- *build_project
- !reference [ .build_android, script ]
- echo -e "\e[0Ksection_end:`date +%s`:build_app_section\r\e[0K"
when: manual
allow_failure: false
artifacts:
name: "android-$CI_COMMIT_REF_SLUG"
expire_in: 1 week
paths:
- $BUILD_APK_FILE_PATH
- $BUILD_AAB_FILE_PATH
extends:
- .before_script:build_android
- .variables:build_android
tags:
- $MACOS_RUNNER_TAG
build_ios:
stage: build
needs: [ ]
script:
- *build_project
- !reference [ .build_ios, script ]
when: manual
allow_failure: false
artifacts:
name: "ios-$CI_COMMIT_REF_SLUG"
expire_in: 1 week
paths:
- $BUILD_IPA_FILE_PATH
extends:
- .before_script:build_ios
- .after_script:build_ios
tags:
- $MACOS_RUNNER_TAG
deploy_testflight:
stage: deploy
needs:
- build_ios
when: manual
extends:
- .before_script:deploy_testflight
- .script:deploy_testflight
- .variables:deploy_testflight
tags:
- $MACOS_RUNNER_TAG
deploy_google_play:
stage: deploy
needs:
- build_android
when: manual
extends:
- .before_script:deploy_google_play
- .script:deploy_google_play
- .variables:deploy_google_play
tags:
- $MACOS_RUNNER_TAG
Важно: на этом этапе главное — создать структуру пайплайна. Конкретные команды сборки и переменные нужно настроить на следующем шаге.
Шаг 2. Задать необходимые переменные
Рассмотрим переменные окружения, которые непосредственно участвуют при сборке и дистрибуции приложения.
BUILD_AAB_ARCHIVE – Определяет, нужно ли собирать Android App Bundle (AAB) в дополнение к APK.
BUILD_AAB_FILE_NAME – Имя итогового AAB-файла (Android App Bundle) после сборки.
BUILD_AAB_FILE_PATH – Полный путь до папки с AAB-файлом.
BUILD_APK_FILE_NAME – Имя итогового APK-файла (Android Package) после сборки.
BUILD_APK_FILE_PATH – Полный путь до папки с APK-файлом.
BUILD_IPA_FILE_NAME – Имя итогового IPA-файла после сборки.
BUILD_IPA_FILE_PATH – Полный путь до папки с IPA-файлом.
BUILD_TYPE – Тип сборки проекта. Возможные значения: debug, release.
DART_DEFINE_JSON_REQUIRED_KEYS – Список ключей переменных окружений, которые обязательно должны быть переданы во время сборки приложения.
DART_DEFINE_PATH – Путь, по которому будет формироваться временный файл с переменными окружения из DART_DEFINE_JSON (см. подробнее в следующем разделе).
FASTLANE_TEAM_ID – Идентификатор команды разработчика на портале Apple Developer (в разделе Membership details).
FLUTTER_SDK_VERSION – Версия Flutter SDK, используемая для сборки.
GIT_CLONE_PROJECT_PATH – Полный путь в GitLab до репозитория с мобильным приложением для клонирования проекта.
GIT_CLONE_REF_NAME – Название тега, хэша коммита или ветки для сборки проекта из репозитория с мобильным приложением.
KEYCHAIN_NAME – Имя для создания временной связки ключей для импортирования iOSPKCS#12.
LINUX_RUNNER_TAG – Опционально. Имя тега для использования раннера на машине под управлением Linux. Можно использовать в build_android и других процессах, которые не требуют выполнения команд на macOS.
MACOS_RUNNER_TAG – Имя тега для использования раннера на машине под управлением macOS (как создать Gitlab Runner см. в предыдущей части статьи).
PROJECT_ENTRY_POINT – Путь к главному файлу для запуска приложения.
SIGN_TYPE – Режим подписи iOS-приложения: adhoc, development, appstore.
SUPPLY_TRACK – Группа дистрибуции приложения после публикации в Google Play Console. Доступные значения: production, beta, alpha, internal.
TARGET_BUNDLE_MAPPING – Сопоставление iOS Target проекта к его Bundle ID и Profile Name в формате JSON (подробнее см. в следующем шаге).
Ключевой момент: как можно заметить, некоторые переменные явно задаются в файле .gitlab-ci.yml, а не на уровне проекта GitLab. Это нужно для того, чтобы было проще отслеживать изменения в системе контроля версий.
Шаг 3. Настроить доступ к репозиторию приложения
Инфраструктура требует наличие доступа на уровне CI_JOB_TOKEN к репозиторию с Flutter-приложением. Для этого:
- Перейдите в основной репозиторий вашего приложения в GitLab.
- Откройте раздел Settings → CI/CD.
- В подразделе Job token permissions найдите таблицу CI/CD job token allowlist.
- Добавьте в нее путь к вашему репозиторию инфраструктуры в формате «группа/проект» (например, project-group/flutter-infra) и нажмите на кнопку Save changes.
Flutter Example. Job token permissions.
После этого пайплайн в flutter-infra сможет склонировать код вашего приложения с помощью CI_JOB_TOKEN.
Шаг 4. Загрузить секретные ключи в Secure Files
Для подписи приложения и публикации в магазинах нужны закрытые ключи. Загрузите их через интерфейс GitLab в разделе Settings → CI/CD → Secure Files (см. подробнее в предыдущей части).
Шаг 5. Настроить интеграции с магазинами приложений в GitLab
Для автоматической загрузки сборок в Google Play и App Store GitLab предоставляет встроенные интеграции. Их нужно активировать и настроить один раз в вашем проекте flutter-infra.
Общий принцип:
- Вы создаете сервисный аккаунт (для Google Play) или ключ API (для App Store Connect) в соответствующих консолях разработчика.
- Вы вводите данные этого аккаунта (файл JSON или ключ) в настройках проекта GitLab.
- GitLab CI/CD получает безопасный доступ к магазинам и может использовать команды (вроде fastlane supply) для публикации.
Google Play
Настроить интеграцию можно на уровне проекта GitLab в разделе: Settings → Integrations → Google Play, следуя официальной документации.
Flutter Infra. Google Play.
App Store Connect
Настроить интеграцию можно на уровне проекта GitLab в разделе: Settings → Integrations → Apple App Store Connect, следуя официальной документации.
Flutter Infra. Apple App Store Connect.
Шаг 6. Проверить работу пайплайна
Теперь протестируем настройку. Отправьте изменения в репозиторий GitLab и создайте новый Merge Request. При успешной настройке в интерфейсе GitLab отобразятся все запланированные этапы:
Flutter Infra. Этапы CI/CD-пайплайна
После успешного завершения стадий build_android или build_ios в пайплайне появятся две ручные стадии — deploy_google_play и deploy_testflight. Они отвечают за публикацию собранных артефактов (APK/IPA) в соответствующие магазины приложений. Запустить их нужно вручную через интерфейс GitLab, что позволяет контролировать момент выпуска новой версии.
Заключение
Вот и все! Вы прошли весь путь от настройки физической машины до полной автоматизации доставки мобильных Flutter-приложений. Теперь ваш процесс разработки стал быстрее, эффективнее и надежнее. Вы можете быть уверены, что каждая новая версия вашего приложения попадет к вашим пользователям без задержек и ошибок.
Надеюсь, инструкция была понятной и у вас все взлетело. Если в процессе настройки у вас возникли вопросы или нужны уточнения – задавайте их в комментариях, я буду рад помочь.
Удачи в разработке и пусть выпуск ваших релизов будет быстрым!
Источник: Хабр
Автор: Данил Абдрафиков (разработчик мобильных приложений TAGES)