В данной руководстве мы расскажем как автоматизировать процесс релизов Android-приложений в Google Play и Huawei AppStore (пока что без RuStore). Вы навсегда забудете как это делать вручную и сможете потратить время на что-нибудь более полезное, экономя сотни часов в год.
Содержание
Структура Gradle-проекта
Для начала наведем порядок в самом Android проекте. Вbuild.gradle
проекта мы используем весьма стандартную матрицу из flavors x
buildTypes:
android {
// <REDACTED>
flavorDimensions 'default'
productFlavors {
google {
dimension "default"
versionName = android.defaultConfig.versionName + '-Google'
}
huawei {
dimension "default"
versionName = android.defaultConfig.versionName + '-Huawei'
}
}
// <REDACTED>
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-debug'
signingConfig signingConfigs.debug
// <REDACTED>
}
beta {
applicationIdSuffix '.beta'
versionNameSuffix '-beta'
signingConfig signingConfigs.release
// <REDACTED>
}
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
// <REDACTED>
}
}
Такая матрица дает комбинацию из GoogleDebug, GoogleBeta, GoogleRelease, HuaweiDebug, и т.д. В целом кастомизация по stores не обязательна, но полезна для управления специальными возможностями, таким как in-app. Настраивайте матрицу исходя из ваших потребностей.
Полезные ссылки:
Полный пример build.gradle
Android App Bundles (AAB) vs Android Packages (APK)
С самого момента зарождения Android все приложения распространялись в виде так называемых .apk
файлов, которые по сути представляют из себя ZIP архивы с скомпилированным кодом и ресурсами под различные архитектуры, локализации и API-levels. Вы компилируете свой код и ресурсы, собираете архив, подписываете его своей сигнатурой. А дальше можете распространять хоть через Google Play, хоть через RuStore, хоть просто на сайте выкладывать.
Ситуация поменялась в 2021 году. Google решил, что неплохо бы отправлять клиентами Google Play только те ресурсы, которые соответствуют API-level и архитектуре приложения. И действительно, зачем отправлять библиотеки armeabi-v7a
для устройств arm64-v8a
? Зачем устройствам с API level = 31 нужны compatibility файлы для API level = 21? Зачем локализации на 30 языках, если в системе всего один? В целом идея неплохая, но есть нюансы.
Чтобы всё это работало, Google придумал новый промежуточный формат Android App Bundles (.aab
), который аналогично .apk
вы собираете и подписываете своим ключом. Однако, .aab
файлы не могут быть непосредственно установлены на устройства. Google Play для каждого конкретного устройства создает из .aab файла .apk файл с необходимым ресурсами и отправляет его на девайс. Вместо, условно, 50 мегабайт .apk
файла вы качаете условные 20 мегабайт таргетированного .apk
файла специально под ваше устройство. Profit!
Важно! При использовании Android App Bundles, финальная сборка и подпись APK будет находится на стороне Google (и, теперь и Huawei). Каждый app store использует свои ключи для подписи. Google - свои, Huawei - свои. Android работает таким образом, что установка новой версии приложения, подписанного другим ключом, невозможна без удаления предыдущей версии приложения вмести со всеми данными. Если ваши пользователи установят bundle приложения из Google Play, то они не смогут обновить его через Huawei.
Классический подход с APK не имеет данной проблемы. Однако, начиная с августа 2021 у вас больше нет выбора и все новые приложения надо загружать только в формате .aab
, отдавая подпись на сторону Google Play. Теперь также можно забить на все эти split APK, а также APK expansion files и прочие костыли. Пользователи не будут качать ненужные мегабайты через скоростной EDGE^W5G Интернет. Больше свободного места на устройстве! И, в конце концов, практически полное отсутствие возможности перейти с Google Play на альтернативные сторы для таких приложений. Win-win-win.
Полезные ссылки:
Генерация номеров версий
Первая и одна из самых важных задач, которую необходимо решить для реализации continuous deployment хоть для онлайн сервиса, хоть для package software - это обеспечения детерминированного алгоритма генерации версий. Требование простое: для каждого git commit в релизной ветке должен существовать уникальный номер версии. Перестаньте уже делать "bump versions" (1.0.2 -> 1.0.3 -> 1.0.4 и т.д.) каждый раз вручную. Нужна система, где номер версии (хотя бы последняя его .patch-level часть) будет генерироваться автоматически.
Есть два неплохих подхода, которые позволяют закрыть данный вопрос раз и навсегда полностью.
Подход 1. Использование git describe
Особенность Android (и Google Play и AppGallery соответственно) в том, что downgrade версий приложений невозможен. Код номера версии всегда должен возрастать. Но ведь точно также и всегда растёт git история релизной ветки, если же, конечно, вы не хулиганите с rewrite истории и force-pushes.
Крайне логичным является использовать число git commit в ветке для нумерации. Можно, например, сделать так, что вы периодически ставите annotated tag вида v$major.$minor
(например, v1.0, v2.0), тогда как третий компонент (patch) генерируется как число коммитов от предыдущего тега.
Для реализации удобнее всего использовать команду git describe --long
:
> git tag -a v1.0 -m "New minor release"
> git describe --long
v1.0-0-gbc2d1a8
> touch test2.txt
> git add .
> git commit -m "Add some changes"
> git describe --long
v1.0-1-gaa7263d
...
> touch test3.txt
> git add .
> git commit -m "Add some more changes"
> git describe --long
v1.0-2-g6a48c84
Данный подход дает вполне себе semver v1.0.0, v1.0.1, v1.0.2 и т.д, где последняя цифра версии (patch) назначается автоматически, а major.minor контролируется вами. Для каждого git commit у вас будет уникальный номер версии. Найти git коммит по тегу и числу коммитов от него также не представляется сложным. Просто и практично.
Подход 2. Использование даты
Более радикальным подходом является использование даты git коммита в качестве версии. Например, 2023.04.23-2. А как же semver, спросите вы? Ну а вот а зачем он вам в мире Android приложений, если у вас всё равно идет непрерывное обновление приложений из Google Play и нет никакого способа откатиться назад или обеспечить доступность нескольких версий? Можете жарко поспорить об этом в комментариях.
В git есть две даты:
Author Date - то, что указал автор и то, что вы можете видеть в git log и git show. Может быть насколько угодно в прошлом, особенно если вы мержите какой-нибудь PR годовалой давности.
Commit Date - дата, когда коммит был добавлен в бранч. Обычно соответствует реальному времени, когда merge произошел в бранч. Время всегда идет вперед и Commit Date не исключение, если же, конечно, вы не будете намеренно экспериментировать с показанием ваших часов.
Мы будем использовать Commit Date, которую можно получить через %cd
форматинг в git log:
TZ=UTC0 git log --max-count=128 --pretty=format:%cd --date=iso-local
2023-04-23 10:37:01 +0000
2023-04-23 09:02:18 +0000
2023-04-19 14:43:21 +0000
2023-04-19 14:43:21 +0000
2023-04-19 01:39:03 +0000<REDACTED>
Одной даты недостаточно, если же, конечно, вы не хотите себя ограничить одним коммитом в день. В качестве дополнительной цифры будем считать число коммитов с начала дня. Получим что-то вроде 2023.04.23-1, 2023.04.23-3, 2023.04.23-4 и т.д. Также можно взять в качестве последней цифры час x 60 + минуты или даже просто час (up to you). Но нам вариант с числом коммитов с начала дня показался вполне рабочим.
Не забывайте, в конечном итоге всё это надо запихать в т.н. versionCode
, который ограничен пространством 0 - 2_100_000_000 (чуть меньше 31 бита). Version Code - это именно то, с чем работает Android и stores для версионирования, тогда как Version Name это просто строка с текстом где может быть всё, что вы пожелаете показать пользователю.
Для генерации версии будем использовать следующий код в Gradle (смотрите внимательно комментарии):
def getVersion() {
def time = Integer.parseInt(run(['git', 'log', '-1', '--format=%ct']).trim())
def cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ENGLISH)
cal.setTimeInMillis((long) time * 1000)
def year = cal.get(Calendar.YEAR)
def month = cal.get(Calendar.MONTH) + 1
def day = cal.get(Calendar.DAY_OF_MONTH)
def date_of_last_commit = String.format("%04d-%02d-%02d", year, month, day)
def build = Integer.parseInt(run(['git', 'rev-list', '--count', '--after="' + date_of_last_commit + 'T00:00:00Z"', 'HEAD']).trim())
// Use the last git commit date to generate the version code:
// RR_yy_MM_dd_CC
// - RR - reserved to identify special markets, max value is 21.
// - yy - year
// - MM - month
// - dd - day
// - CC - the number of commits from the current day
// 21_00_00_00_00 is the the greatest value Google Play allows for versionCode.
// See https://developer.android.com/studio/publish/versioning for details.
def versionCode = (year - 2000) * 1_00_00_00 + month * 1_00_00 + day * 1_00 + build
// Use the current date to generate the version name:
// 2021.04.11-12-Google (-Google added by flavor)
def versionName = String.format("%04d.%02d.%02d-%d", year, month, day, build)
return new Tuple2(versionCode, versionName)
}
Дальше устанавливаете versionCode
и versionName
из результата вызова getVersion()
:
android {
// <REDACTED>
defaultConfig {
def ver = getVersion()
versionCode = ver.V1
versionName = ver.V2
println('Version: ' + versionName)
println('VersionCode: ' + versionCode)
// <REDACTED>
}
// <REDACTED>
}
Можете убедиться, что каждый запуск gradle будет генерировать уникальный номер версии для каждого git коммита. Данный подход закрывает вопрос версионирования навсегда и вы не думаете про это.
Получение ключей доступа от Google Play
Чтобы загружать что-либо в Google Play автоматизированно, надо для начала получить ключи доступа в API. Кажется, что Google Play весьма плотно интегрировался с IAM Google Cloud и вам нужно создать новый Service Account с необходимыми правами и получить .json
файл с credentials. Но давайте по порядку.
Шаг 1. Связь Google Play Developer account Google Cloud project
Вроде как новые Google Play Developer Accounts должны связываться с Google Cloud как-то полумагически, особенно если вдруг вы использовали Firebase.
Идете в Google Play Console -> Setup -> API Access. Если Linked Project уже есть, то у вас всё хорошо и можно переходить к следующему шагу. Если нет, то проверьте в Google Cloud Console какие у вас уже есть проекты и, при необходимости, создайте новый. Firebase неявно создает Google Cloud проект и вы можете использовать его же.
Шаг 2. Создание Google Service Account
Service Account это такая сущность для "роботов", которая имеет credentials и на которую можно назначать роли и права доступа. Рекомендуется использовать robot Service Account, а не свой human Google Account.
Кликайте на "View in Google Cloud Platform" или же просто переходите в Google Cloud Console и выбирайте нужный проект в списке. Переходите в раздел "Service Accounts". Кликайте "Create Service Account".
Введите имя для Service Account, например, Google Play Automatic Upload. Введите ID (email) для Service Account, например google-play-uploaded
. Жмите "CREATE AND CONTINUE".
Добавьте роль Viewer, без этого Service Account не добавляется в Google Play Console. Жмите "DONE", пропуская третий шаг ( Grant users access to this service account (optional)).
Шаг 3. Получение ключа доступа
Кликайте по только что созданному Service Account. Переходите на вкладку "Keys". Нажимайте кнопку "ADD KEY" -> "Create new" -> JSON.
После клика на "CREATE" будет скачан JSON файл с приватным ключом Service Account:
{
"type": "service_account",
"project_id": "<YOUR GCLOUD PROJECT>",
"private_key_id": "<REDACTED>",
"private_key": "-----BEGIN PRIVATE KEY-----\n<REDACTED>\n-----END PRIVATE KEY-----\n",
"client_email": "google-play-uploaded@<YOUR GCLOUD PROJECT>.iam.gserviceaccount.com",
"client_id": "<SOME BIG NUMBER>",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/google-play%40<YOUR GCLOUD PROJECT>.iam.gserviceaccount.com"
}
Храните этот ключ в надежном секретном месте!
Шаг 4. Добавление Google Cloud Service Account в Google Play Console
Последний шаг - дать права на загрузку приложений для нового Service Account. Кажется, эта часть пока еще не интегрирована до конца с Google Cloud и права необходимо выдавать в Google Play Console.
Переходите в Google Play Console, раздел "Users and permissions". Жмете на меню с трёмя точками, дальше "Invite new users".
Вводите google-play-uploaded@<YOUR GCLOUD PROJECT>.iam.gserviceaccount.com
Необходимо дать следущие права
View app information and download bulk reports (read only)
View all app information, including any associated Play Games services projects – but not financial data. Users with this permission can also download bulk reports, and will be able to view any new apps that you add to Play Console in the future.Create, edit and delete draft apps
This permission does not allow users to publish apps on Google PlayRelease to production, exclude devices and use Play app signing
Create, edit and roll out releases to production, unpublish and republish apps, exclude devices in the device catalogue, and use app signing by Google Play to sign APKs. Users with this permission can publish apps to users on Google Play.Release apps to testing tracks
Upload draft apps; create, edit and rollout releases to testing tracks; unpublish and republish apps that have already been published to a testing track; upload and modify .obb files; edit release notes for apps which are not active in production; and upload app bundles for internal sharing. This permission does not allow users to publish apps to production on Google Play.
Готово. В результате выполнения инструкций в данном разделе у вас должен появиться .json
файл с credentials доступа к Google Play, позволяющие загружать приложения.
Полезные ссылки:
Загрузка сборок в Google Play
Загрузку в Google Play будем осуществлять через Triple T Gradle Play Publisher плагин для Gradle. Альтернативой является всем известный Fastlane, который мы тоже используем, но только для iOS. Triple-T Play Publisher субъективно кажется более удобным как минимум за счет за счет интеграции с Gradle и отсутствия необходимости возиться с набором портянок на Ruby.
Добавление библиотеки
Добавим в build.gradle
Gradle плагин com.github.triplet.gradle:play-publisher:
buildscript {
repositories {
google()
mavenCentral()
}
// <REDACTED>
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
// <REDACTED>
classpath("com.github.triplet.gradle:play-publisher:3.8.1")
}
}
Версия в статье быстро устареет. Пожалуйста, всегда используйте последнюю доступную версию.
Добавление вызова плагина
Добавьте вызов плагина com.github.triplet.play
в build.gradle
сразу после секции `android {}`:
android {
// <REDACTED>
}
apply plugin: 'com.github.triplet.play'
Настройка плагина
Плагин настраивается через глобальную секцию конфигурации play {}
, а также дополнительно в каждом flavor:
android {
defaultConfig {
// <REDACTED>
} // defaultConfig
// <REDACTED>
playConfigs {
googleRelease {
enabled.set(true)
}
} // playConfigs
} // android
play {
enabled.set(false)
track.set("production")
userFraction.set(Double.valueOf(0.10)) // 10%
defaultToAppBundles.set(true)
releaseStatus.set(ReleaseStatus.DRAFT)
serviceAccountCredentials.set(file("google-play.json"))
}
track
- говорим в какой track заливать. Конечно же, сразу в продакшен!userFraction
- включаем stage rollout на 10% чтобы не быть совсем сумашедшими.defaultToAppBundles
- включаем Android App Bundles (AAB) вместо Android Packages (APK).releaseStatus.set(ReleaseStatus.DRAFT)
- создаем черновик, но пока сразу не отправляем на ревью. После тестирования можно будет поменять наReleaseStatus.IN_PROGRESS
.
А вы сейчас делаете stage rollout? Расскажите, пожалуйста, об этом в комментариях.
Тестирование загрузки в Google Play
Запускаем таск bundleGoogleRelease
для сборки релизной версии aab
приложения для последующей загрузки:
gradle bundleGoogleRelease
Проверяем, что у нас реально что-то собралось:
ls build/outputs/bundle/googleRelease
OrganicMaps-23043001-google-release.aab
Запускаем таску publishGoogleReleaseBundle
для загрузки cобравшегося aab
в Google Play:
gradle publishGoogleReleaseBundle
> Task :publishGoogleReleaseBundle
Starting App Bundle upload: OrganicMaps-23043001-google-release.aab
Uploading App Bundle: 10% complete
Uploading App Bundle: 19% complete
Uploading App Bundle: 29% complete
Uploading App Bundle: 39% complete
Uploading App Bundle: 49% complete
Uploading App Bundle: 58% complete
Uploading App Bundle: 68% complete
Uploading App Bundle: 78% complete
Uploading App Bundle: 88% complete
Uploading App Bundle: 97% complete
App Bundle upload complete
Updating [completed, inProgress] release (app.organicmaps:[23030505, 23040207]) in track 'production'
В целом часть с заливкой готова и вы сможете увидеть сможете увидеть черновик релиза Google Play Console и отправить его на review.
Если всё соответствует ожиданиями, то дальше можно поменять releaseStatus.set(ReleaseStatus.DRAFT)
на releaseStatus.set(ReleaseStatus.IN_PROGRESS)
чтобы лишний раз не бегать в Google Play Console для нажатия кнопки. Кнопка должна быть где-то в вашем CI/CD, чтобы вы там не использовали.
Полезные ссылки:
Управление метаданными Google Play
Следующий шаг - это научиться также обновлять метаданные (описание приложения, скриншоты и т.п.) в Google Play. После добавления данной автоматизации станет возможным обновлять метаданные в git репозитории вместо Google Play. Работу по обновлению в Google Play сделает плагин.
Скачивание метаданных из Google Play
Для начала необходимо скачать уже имеющиеся в Google Play метаданные в репозиторий. Запускаем задачу `bootstrapGoogleReleaseListing`:
gradlew bootstrapGoogleReleaseListing
Пошуршав диском, gradle выкачает все текущие метаданные в репозиторий.
> Configure project :
Building with Google Mobile Services
Building without Google Firebase Services
Version: 2023.04.23-2
VersionCode: 23042302
Building for [armeabi-v7a, arm64-v8a, x86_64] archs.
Create separate apks: false
> Task :bootstrapGoogleReleaseListing
Downloading app details
Downloading listings
Downloading in-app products
Downloading release notes
Downloading en-US listing
Downloading tr-TR listing
<REDACTED>
Downloading en-US listing graphics for type 'featureGraphic'
Downloading en-US listing graphics for type 'icon'
Downloading en-US listing graphics for type 'phoneScreenshots'
Downloading en-US listing graphics for type 'tenInchScreenshots'
Downloading en-US listing graphics for type 'sevenInchScreenshots'
Downloading tr-TR listing graphics for type 'sevenInchScreenshots'
Downloading tr-TR listing graphics for type 'phoneScreenshots'
<-<----<
BUILD SUCCESSFUL in 44s
1 actionable task: 1 executed
Новые файлы появятся в src/<FLAVOR>/play
:
contact-email.txt
contact-website.txt
default-language.txt
listings/
en-US
full-description.txt
graphics/
icon/
1.png
phone-screenshots/
1.jpg
2.jpg
3.jpg
4.jpg
release-notes.txt
short-description.txt
title.txt
video-url.txt
release-notes/
en-US/
default.txt
Редактирование данных
После скачивания данных из Google Play добавьте их в git и далее используйте git как первоисточник, избегая ручных действий в Google Play Console. Это даёт огромное число плюсов, начиная от понятного версионирования, до возможности добавления инструментов для переводов. Например, мы используем Weblate для перевода описаний Google Play на другие языки. Данные Google Play также будут переиспользованы далее для Huawei AppGallery.
Загрузка метаданных в Google Play
Для загрузки обновленных метаданных обратно в Google Play существует задача publishGoogleReleaseListing
(где GoogleRelease - ваш flavor):
./gradlew publishGoogleReleaseListing
<REDACTED>
> Configure project :
Building with Google Mobile Services
Building without Google Firebase Services
Version: 2023.05.01-1
VersionCode: 23050101
Building for [armeabi-v7a, arm64-v8a, x86_64] archs.
Create separate apks: false
> Task :publishGoogleReleaseListing
Uploading app details
Uploading ar listing
<REDACTED>
Uploading zh-TW listing
Важно. Обновление метаданных в Google Play не привязано к выпуску релизов. Вы можете обновлять описания и скриншоты приложений в любой момент, вне зависимости от текущего состояния review сборки приложения. Review изменения метаданных происходит отдельно!
Мы используем данный подход чтобы обновлять данные на всех 59 языках, поддерживаемых Google Play Console на момент написания статьи.
Полезные ссылки:
Получение ключей доступа от Huawei AppGallery
Чтобы загружать что-то в Huawei AppGallery нам необходимо сначала получить ключи доступа к Huawei AppGallery Connect. Интерфейс Huawei AppGallery Connect, скажем так, местами весьма запутан. Но надо разобраться один раз чтобы в дальнейшем туда вообще не заходить.
Заходите в AppGallery Connect и нажимайте Users and permissions. Переходите к API key > Connect API и нажимайте Create:
Можете ввести любое название. Укажите следующие роли:
Development
Operations
Customer service (будет выбрано автоматически)
После этого вы сможете скачать .json
ключ доступа к Huawei AppGallery Connect:
{
"type":"team_client_id",
"developer_id":"<BIG NUMBER>",
"client_id":"<ANOTHER BIG NUMBER>",
"client_secret":"<REDACATED>",
"configuration_version":"3.0"
}
Готово. Данный файл понадобится на следущем этапе для автоматизации загрузки.
Полезные ссылки:
Загрузка сборок и release notes в Huawei AppGallery
Для автоматизации загрузки в Huawei AppGallery будем использовать Gradle plugin ru.cian:huawei-publish-gradle-plugin, разрабатываемый Aleksandr Mirko из Омска. Скажем спасибо Александру за столь прекрасный безальтернативный плагин для загрузки в Huawei AppGallery без которого пришлось бы страдать.
Добавление плагина в Gradle
Добавляем Gradle-plugin ru.cian:huawei-publish-gradle-plugin в build.gradle
:
buildscript {
repositories {
google()
mavenCentral()
}
// <REDACTED>
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
// <REDACTED>
classpath("ru.cian:huawei-publish-gradle-plugin:1.3.6")
}
}
Включение плагина
Включаем плагин ru.cian.huawei-publish-gradle-plugin
в build.gradle
после секции android {}
:
apply plugin: 'ru.cian.huawei-publish-gradle-plugin'
Настройка плагина
Плагин имеет отдельную секцию для конфигурции huaweiPublish {}
. Добавляем новую секцию с настройками в конец build.gradle
:
huaweiPublish {
instances {
huaweiRelease {
credentialsPath = "$rootDir/huawei-appgallery.json"
buildFormat = 'aab'
deployType = 'draft' // confirm manually
releaseNotes = []
def localeOverride = [
'am' : 'am-ET',
'gu': 'gu_IN',
'iw-IL': 'he_IL',
'kn-IN': 'kn_IN',
'ml-IN': 'ml_IN',
'mn-MN': 'mn_MN',
'mr-IN': 'mr_IN',
'ta-IN': 'ta_IN',
'te-IN': 'te_IN',
]
def files = fileTree(dir: "$projectDir/src/google/play/listings",
include: '**/release-notes.txt')
files.each { File file ->
def path = file.getPath()
def locale = file.parentFile.getName()
locale = localeOverride.get(locale, locale)
releaseNotes.add(new ru.cian.huawei.publish.ReleaseNote(locale, path))
}
}
}
}
credentialsPath
- задает путь к файлу с ключами от AppGallery ConnectdeployType = 'draft'
- говорим плагину создавать черновик релиза, но не отправлять сразу на review. После тестирования и обкатки можно будет поменять наpublish
.buildFormat = 'aab'
- также как и в Google Play используем для Huawei AppGallery Android App Bundles (AAB) вместо Android Packages (APK).localeOverride
- здесь и ниже делается делается магия для переиспользования release notes из плагина для Google Play. Есть небольшие неудобства из-за того, что небольшая группа кодов локалей для Huawei AppGallery почему-то пишется с_
(underscore) вместо-
(dash) как Google Play. Остальные совпадают.
Подробнее по параметрам настройки можно посмотреть в README плагина на GitHub.
Тестирование загрузки
Запускаем таск bundleHuaweiRelease
для сборки релизной версии aab
приложения для последующей загрузки:
gradle bundleHuaweiRelease
Проверяем, что у нас реально что-то собралось:
ls build/outputs/bundle/huaweiRelease
OrganicMaps-23043001-huawei-release.aab
Запускаем таску publishHuaweiAppGalleryHuaweiRelease
для загрузки cобравшегося aab
в Huawei AppGalery Connect:
gradle publishHuaweiAppGalleryHuaweiRelease
Внимание: задача publish
почему-то не имеет явной зависимости на bundle, поэтому надо не забывать запускать bundle
задачу каждый раз, можно обе команды сразу в одном запуске gradle:
gradle bundleHuaweiRelease publishHuaweiAppGalleryHuaweiRelease
Смотрим внимательно, что пишет gradle в процессе загрузки:
> Task :publishHuaweiAppGalleryHuaweiRelease
Huawei AppGallery Publishing API: Prepare input config
Huawei AppGallery Publishing API: Found build file: `OrganicMaps-23043001-huawei-release.aab`
Huawei AppGallery Publishing API: Get Access Token
Huawei AppGallery Publishing API: Get App ID
Huawei AppGallery Publishing API: Get Upload Url
Huawei AppGallery Publishing API: Upload build file '/home/runner/work/organicmaps/organicmaps/android/build/outputs/bundle/huaweiRelease/OrganicMaps-23040207-huawei-release.aab'
Huawei AppGallery Publishing API: Upload release notes: 1/59, lang=et
Huawei AppGallery Publishing API: Upload release notes: 2/59, lang=kk
[REDACTED]
Huawei AppGallery Publishing API: Upload release notes: 59/59, lang=ar
Huawei AppGallery Publishing API: Update App File Info
Huawei AppGallery Publishing API: Upload build file draft without submit on users - Successfully Done!
BUILD SUCCESSFUL in 1m 36s
1 actionable task: 1 executed
В случае каких-либо проблем с credentials в файле huawei-appgallery.json
плагин зафейлится еще на "Get Access Token".
Также возможны ситуации, когда число символов в тексте release notes превращает лимиты, что приводит к созданию draft без полного обновления release notes. В таком случае лучше исправить проблему, удалить draft релиза вручную в AppGallery Connect и попробовать еще раз. В данном примере мы загружаем release notes почти для всех доступных локализаций, хотя в большинстве случаев текст просто совпадает с английским.
Готово. После этого шага заливка в Huawei AppGallery также возможна через Gradle.
Полезные ссылки:
Добавление CI/CD
Последний, но важный шаг - добавить всё вышеописанное в вашу систему CI/CD. Здесь нет особых сложностей, так как вся автоматизация уже сделана в gradle и вам надо лишь запускать соответствующие задачи.
Мы используем GitHub Actions, поэтому расскажем кратко про них.
В первую очередь надо будет добавить файлы с ключами google-play.json
иhuawei-appgallery.json
в GitHub Actions Secrets. Также вам понадобятся ключи для подписи, которые можно сохранить в base64. Переходите в Settings -> Secrets and variables -> Actions и добавляйте две новых переменных для каждого файла:
Дальше создаем новый workflow файл .github/workflows/android-release.yaml
:
name: Android Release
on:
workflow_dispatch: # Manual trigger
Добавляем одну задачу с матрицу из нескольких вариантов (google, huawei, web):
jobs:
android-release:
name: Android Release
runs-on: ubuntu-latest
environment: production
needs: tag
strategy:
fail-fast: false
matrix:
include:
- flavor: google
- flavor: huawei
Качаем исходники:
steps:
- name: Checkout sources
uses: actions/checkout@v3
with:
fetch-depth: 200 # enough to get all commits for the current day
В случае shared GitHub Runners все зависимости вроде Android SDK уже установлены, но вы можете установить что-нибудь еще, нужное для вашего проекта:
- name: Install build tools and dependencies
shell: bash
run: |
sudo apt-get update -y
sudo apt-get install -y ninja-build
Создаем файлы google-play.json
иhuawei-appgallery.json
из GitHub Secrets:
- name: Get credenials
shell: bash
run: |
echo "${{ secrets.GOOGLE_PLAY_JSON }}" > google-play.json
echo "${{ secrets.HUAWEI_APPGALLERY_JSON }}" > huawei-appgallery.json
Также аналогично надо будет добавить ключи для подписи приложений и другие ключи. Можно вместо работы с индивидуальными секретами сделать отдельный репозиторий и применять его поверх основного в момент сборки приложения.
Добавляем задачи для сборки и заливки в Google Play и Huawei AppGallery используя инструкции, описанные ранее в данной статье:
- name: Compile and upload to Google Play
shell: bash
working-directory: android
run: |
gradle bundleGoogleRelease publishGoogleReleaseBundle
if: ${{ matrix.flavor == 'google' }}
- name: Compile and upload to Huawei AppGallery
shell: bash
working-directory: android
run: |
gradle bundleHuaweiRelease
gradle publishHuaweiAppGalleryHuaweiRelease
if: ${{ matrix.flavor == 'huawei' }}
В целом готово. При запуске будет выглядеть примерно так:
Также можно добавить задачу для обновления метаданных Google Play Store. Можно добавить задачу в тот же workflow файл или сделать отдельный файл, так как в Google Play данная операция может выполняться вне зависимости от текущего состояния релиза.
- name: Update Google Play Metadata
shell: bash
run: ./gradlew publishGoogleReleaseListing
working-directory: android
timeout-minutes: 5
Полезные ссылки:
Заключение
Выпуск новых релизов в сторах - это рутинная операция, которую приходиться делать на периодической основе - каждую неделю, каждый месяц, год от года. И на каждом шаге постоянно будет что-то забываться или делаться с ошибками. Релиз от релиза. Вы будете тратить время каждый раз.
Прежде чем добавлять описанную в статье автоматизацию, мы провели тщательную работу по документированию существующего процесса. Получилось больше 30 страниц текста со скриншотами... Настроенное CI/CD работает уже второй год и позволяет экономить сотни часов в год, которые можно потратить на более полезные вещи.