
Вступление
Всем привет. Сегодня я бы хотел поговорить про полезные автоматизации в проекте. Есть такие вещи, которые реально приносят профит на все сто процентов. Их не так уж много, и самое интересное - распознать полезный кейс заранее почти невозможно. Обычно ты понимаешь его ценность только на практике.
И как раз про одну такую штуку я хочу рассказать - про автоматическую публикацию приложения в магазине.
Мы сейчас не будем рассматривать какое-то сложное продакшен-решение, хотя можно привести примеры, как это делается в CI/CD. Но, скорее всего, для нормального CI/CD там нужен немножко другой подход, хотя и тот, о котором я расскажу, в принципе тоже будет корректен. Например, публикацию в Google Play, как мне кажется, лучше делать через fastlane, который у вас, скорее всего, уже где-то подключен. Его будет рациональнее использовать.
Но если у вас небольшой проект и у вас нет CI/CD, это вообще не значит, что нельзя автоматизировать релиз. Почему то у меня это ассоциировалось именно с хороший пайплайном. Давайте как раз рассмотрим пример такой автоматизации, которая действительно помогает, действительно облегчает жизнь и, честно говоря, прям выручает в уставшие без сил вечера.
Что нужно для релиза?
Если очень коротко, давайте вспомним, что вообще нужно сделать разработчику, чтобы опубликовать приложение. Ты сидишь, программируешь, пишешь код, и вот тебе нужно:
- поднять версию
- собрать APK
- подписать ее нужным ключом
- выбрать нужный стор (а их может быть несколько)
- вспомнить интерфейс конкретного стора
- загрузить туда APK
- отправить на модерацию
- можно еще поставить галочку на автоматический выпуск
И вроде бы все хорошо. На бумаге это звучит как мелочь, ничего сложного: ну подумаешь, собрал, подписал, загрузил. Но на практике все это почему-то всегда сложнее, чем кажется. Особенно когда уже поздний вечер, голова забита задачами, и вообще не хочется этим заниматься.
Хочется просто нажать одну кнопку - и чтобы все улетело в релиз, аккуратно собрано и отправлено. Вот как раз про это я сегодня и хочу поговорить. Давайте посмотрим на двух примерах - публикации в RuStore и публикации в Google Play. Разберем, какие там есть нюансы, с чем можно столкнуться и как все это автоматизировать так, чтобы вы потратили минимум времени на добавление этой автоматизации в свой проект.
Версия
Для поднятия версии мы напишем gradle таску которая будет автоматически из build.gradle.kts в полях versionCode и versionName повышать версию.
Зарегистрируем в корневом build.gradle.kts вашего проекта две градл таски bumpVersion и tagVersion.
tasks.register("bumpVersion") {
group = "versioning"
description = "Bump VERSION_CODE/NAME. -Ppart=patch|minor|major or -PnewName=1.2.3"
doLast {
val propsFile = file("${rootDir}/gradle.properties")
val props = Properties().apply { propsFile.inputStream().use { load(it) } }
val code = (props.getProperty("VERSION_CODE") ?: "1").toInt()
val name = props.getProperty("VERSION_NAME") ?: "0.1.0"
val newName = (project.findProperty("newName") as String?) ?: bumpSemver(
name,
project.findProperty("part") as String?
)
props.setProperty("VERSION_CODE", (code + 1).toString())
props.setProperty("VERSION_NAME", newName)
propsFile.outputStream().use { props.store(it, null) }
println("Bumped: VERSION_CODE $code -> ${code + 1}, VERSION_NAME $name -> $newName")
}
}
tasks.register("tagVersion") {
group = "versioning"
dependsOn("bumpVersion")
doLast {
val props = Properties().apply {
file("${rootDir}/gradle.properties").inputStream().use { load(it) }
}
val v = props.getProperty("VERSION_NAME")
exec { commandLine("git", "add", "gradle.properties") }
exec { commandLine("git", "commit", "-m", "chore: bump version to $v") }
exec { commandLine("git", "tag", "-a", "v$v", "-m", "release v$v") }
exec { commandLine("git", "push", "origin", "HEAD") }
exec { commandLine("git", "push", "origin", "v$v") }
println("Tagged and pushed v$v")
}
}
В файле gradle.properties:
VERSION_CODE=27
VERSION_NAME=2.0.7
Получается так что bumpVersion делает:
- Читает текущие VERSION_CODE и VERSION_NAME из gradle.properties.
- Увеличивает их (например, patch/minor/major или просто +1).
- Записывает обратно обновлённые значения в gradle.properties.
А tagVersion:
- считывает VERSION_NAME или VERSION_CODE
- вызывает git команду, например:
git tag v2.0.8
git push origin v2.0.8
То есть она просто ставит в Git метку конкретного релиза. На этом все, вы можете просто вызывать команду:
./gradlew tagVersion -Ppart=patch
И версия повышена. Кажется тут все просто, с версией мы разобрались.
Rustore
Для публикации есть open-source проект, который, судя по всему, разрабатывают ребята из Cian. Не без трудностей, но у меня получилось его успешно заюзать. Ссылка на проект: https://github.com/cianru/rustore-publish-gradle-plugin
Инструкция в целом понятная, проект работает, так что можно пробовать. Но важно учитывать, что при работе на Windows могут возникнуть проблемы с публикацией.
У меня был заведен issue, который позже закрыли: https://github.com/cianru/rustore-publish-gradle-plugin/issues/23
Честно говоря, я пару вечеров мучался с этой ошибкой она оказалась связана с рассинхронизацией времени в Windows. Решение я описал в issue.
После фикса проблем больше не возникало - всё работает стабильно.
Google Play
После проблемы с временем в Windows, если честно, мотивация немного пропала и руки не доходили до google. Но как говорится: глаза боятся — руки делают. В итоге всё оказалось проще, чем думал.
У меня была мысль затащить fastlane и использовать upload_to_play_store: https://docs.fastlane.tools/actions/upload_to_play_store/
Обычно fastlane уже установлен на CI/CD, и его можно легко подключить. Но так как у нас история больше про локальную разработку, я нашёл другой вариант: https://github.com/Triple-T/gradle-play-publisher
Единственный нюанс получение ключа JSON. В Google Play напрямую его нет, хотя почему то я его искал именно там, поэтому нужно идти сюда: https://console.cloud.google.com/
В целом в инструкции это нормально описано, просто нужно не пропускать шаги.
После настройки я указал трек, track.set(“internal”). И на первый раз лучше действительно не рисковать: я отправил сборку во внутреннее тестирование, а потом уже из консоли перекинул её в релиз.
Результат
На этом всё. В качестве заключения оставлю инструкцию из README. Кликаете по каждому пункту - и шаг за шагом приближаетесь к релизу.
Релиз в Rustore и GooglePlay
1. Поднять версию. 🚧
./gradlew tagVersion -Ppart=patch
2. Собрать релизную версию RuStore. 🏗️
./gradlew assembleRustoreProdRelease
3. Опубликовать релизную версию RuStore. 🚀
./gradlew publishRustoreRustoreProdRelease --info --stacktrace
4. Проверить публикацию в RuStore. ✅
5. Опубликовать релизную версию Google Play. 🚀
./gradlew publishGoogleplayProdReleaseBundle --info --stacktrace
6. Проверить публикацию в Google Play. ✅
По большому счёту кажется, что сделано мало, но это не так. Были свои сложности. Зато теперь, после любых фиксов, коммитов или мелких багфиксов, можно хоть поздним вечером спокойно открыть README, выполнить нужные команды и без лишних трудозатрат отправить проект в релиз.