Этап первый: настройка для автоматической подписи готового apk
Первая проблема с которой я столкнулся, это сделать универсальную настройку, позволяющую собирать релизные варианты как локально, так и глобально.
Я использую вариант с использование файла
keystore.properties
, который позволяет нам добавить ключ разработчика в папку проекта, не светя при этом паролями от него, делается это так:apply plugin: ...
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
...
signingConfigs {
release {
storeFile file("../MyKey.jks")
storePassword keystoreProperties['RELEASE_STORE_PASSWORD']
keyAlias keystoreProperties['RELEASE_KEY_ALIAS']
keyPassword keystoreProperties['RELEASE_KEY_PASSWORD']
}
debug {
storeFile file('../debug.keystore')
}
}
buildTypes {
release {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
minifyEnabled false
signingConfig signingConfigs.release
buildConfigField "String", "PIN_ALIAS", keystoreProperties['PIN_ALIAS']
buildConfigField "String", "DB_PASS_ALIAS", keystoreProperties['DB_PASS_ALIAS']
}
debug {
minifyEnabled false
signingConfig signingConfigs.debug
buildConfigField "String", "PIN_ALIAS", keystoreProperties['PIN_ALIAS']
buildConfigField "String", "DB_PASS_ALIAS", keystoreProperties['DB_PASS_ALIAS']
}
}
}
dependencies {
...
}
И тут возникла проблема, как сделать так, что бы мы могли взять ключи из
${{ secrets.MY_KEY }}
и при этом градл понимал, если у нас есть keystore.properties, то берём из него, если нет то берём из секретов? Решение нашлось на одном из гайдов для флаттера, где для этого они использую окружения (Кстати, здесь классный подход, чтобы не светить нашим ключём разработчика), но проблему это не решило. Перепробовав несколько вариантов с введением дополнительных файлов и т.п., остановился на самом простом: мы вводим дополнительно несколько переменных(в зависимости от нужного нам количества), и проверяем наличие файла keystore.properties:def release_store_password
def release_key_password
def release_key_alias
def pin_alias
def db_pass_alias
def keystoreProperties = new Properties()
if (rootProject.file("keystore.properties").exists()) {
keystoreProperties.load(new FileInputStream(rootProject.file("keystore.properties")))
release_store_password = keystoreProperties['RELEASE_STORE_PASSWORD']
release_key_password = keystoreProperties['RELEASE_KEY_PASSWORD']
release_key_alias = keystoreProperties['RELEASE_KEY_ALIAS']
pin_alias = keystoreProperties['PIN_ALIAS']
db_pass_alias = keystoreProperties['DB_PASS_ALIAS']
} else {
release_store_password = System.env.RELEASE_STORE_PASSWORD
release_key_password = System.env.RELEASE_KEY_PASSWORD
release_key_alias = System.env.RELEASE_KEY_ALIAS
pin_alias = System.env.PIN_ALIAS
db_pass_alias = System.env.DB_PASS_ALIAS
}
android {
signingConfigs {
release {
storeFile file("../my_key.jks")
storePassword = release_store_password
keyAlias = release_key_alias
keyPassword = release_key_password
}
buildType{
release {
buildConfigField "String", "PIN_ALIAS", "\"$pin_alias\"" //если вам нужно ввести некоторые
buildConfigField "String", "DB_PASS_ALIAS", "\"$db_pass_alias\"" // дополнительные данны.
}
}
}
Итак, теперь наш сборщик умеет собирать и сразу подписывать наш apk.
Этап второй: версия сборки.
Тут нет ничего сверх естественного, хотелось получить какой-то, достаточно универсальный вариант, минимальной сложности. Погуглив, присмотрелся, сколько разработчиков — столько и вариантов и каждый извращается как может. Мне какие-то сверх сложные подходы не нужны и я уже хотел было использовать
BUILD_NUMBER
, но тут я наткнулся на параметр у для GitHub actions: ${{ github.run_number }}.По этому взвесив все за и против имеем следующее решение:
def versionPropsFile = rootProject.file('version.properties')
Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def verCode = versionProps['VERSION_CODE'].toInteger()
android {
defaultConfig {
versionCode verCode
versionName "1.1.$verCode"
}
}
//version.properties файл
VERSION_CODE=1
В рабочем процессе делаем так:
- name: Output version code
run: echo VERSION_CODE=${{ github.run_number }} > ./version.properties
Этап третий: развертывание (deploy)
На данный момент я нашел два готовых решения: Gradle Play Publisher и Upload Android release to the Play Store
Первый вариант отпал по причине: использование гаубицы при стрельбе по воробьям. По этому я выбрал второй. Ничего вроде сложного в нём нет, а тут есть подробная инструкция: Тут.
- name: Upload to PlayMarket
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.guralnya.notification_tracker
releaseFile: app/build/outputs/apk/release/notification_tracker.release.apk
track: beta
userFraction: 0.33
whatsNewDirectory: distribution/whatsnew
Но некоторые моменты у меня всё же возникли:
- serviceAccountJson и serviceAccountJsonPlainText — с первым я так и не разобрался в каком виде его нужно положить в секреты, второй же просто берём содержимое файла и кладём в наш секрет.
- releaseFile — использовал самый простой подход, когда мы берём готовый файл из папки с проектом, но вариант со звёздочкой не прокатил:
notification_tracker.release.*.apk
, где у меня стоит время сборки. Хотя в другом экшене, который у меня используется для загрузки файла (actions/upload-artifact@v2), такой подход работал отлично. - whatsNewDirectory — внимательнее к языковым кодам. Если английский я взял из гугл-консоли при добавлении новой версии (en-IN), а Русский как (ru-RU), то логично предположить что все языки работают по том же принципу, но нет — Украинский я не доглядел, а он там помечен как (uk), потому если не хотите лишний раз комититься и видеть красный крестик, лучше свериться с той же консолью.
Есть ситуация, когда у вас может по какой-то причине не деплоиться, это когда вы ещё не опубликовали приложение (встречать не приходилось, но в одной статье было описано). Так что начинайте с того, что сначала опубликуйте приложение в ручную если вы ещё этого не сделали.
Итоговый рабочий процесс — будет оптимизироваться и улучшаться вместе с файлом градла
Android CI.yaml:
name: Android_CI
on:
push:
branches:
- beta_release
jobs:
build:
runs-on: ubuntu-latest
name: Build release-apk and deploy to PlayMarket
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
# Without NDK not compile and not normal error message. NDK is required
- name: Install NDK
run: echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;20.0.5594570" --sdk_root=${ANDROID_SDK_ROOT}
# Some times is have problems with permissions for ./gradle file. Then uncommit it code
# - name: Make gradlew executable
# run: chmod +x ./gradlew
- name: Output version code
run: echo VERSION_CODE=${{ github.run_number }} > ./version.properties
- name: Build with Gradle
run: ./gradlew assemble
env:
RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
PIN_ALIAS: ${{ secrets.PIN_ALIAS }}
DB_PASS_ALIAS: ${{ secrets.DB_PASS_ALIAS }}
- name: Upload APK
uses: actions/upload-artifact@v2
with:
name: notification_tracker
path: app/build/outputs/apk/release/notification_tracker.release.apk
- name: Upload to PlayMarket
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.guralnya.notification_tracker
releaseFile: app/build/outputs/apk/release/notification_tracker.release.apk
track: beta
userFraction: 0.33
whatsNewDirectory: distribution/whatsnew
Важный момент
— необходимость NDK. Без установленного NDK у вас не соберётся проект, по крайней мере релизный. Можно долго гадать в чём проблема и искать решение, так как нормального сообщения ошибки нет. Иногда можно отловить вот это:
Task :app:stripDebugDebugSymbols FAILED
. После гуглинга и экспериментов, оказалось что нет NDK. Делаем так:- name: Install NDK
run: echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;20.0.5594570" --sdk_root=${ANDROID_SDK_ROOT}
Или лучше добавить в Gradle (спасибо пользователю anegin):
defaultConfig {
ndkVersion '21.2.6472646'
}
P.S. Для Gradle использовал подсветку кода от Kotlin. Для YAML — от JSON. Конечно немного не то, но лучше мне найти не удалось, если есть лучшие варианты, сообщите мне пожалуйста и я исправлю.
P.S.S. Может быть у кого есть лучшее решение или предложения по улучшения, напишите их в комментариях, так как по первому этапу вопрос провисел на StackOverflow больше 10-ти дней, но ответа так и не последовало.
anegin
Кажется стоит добавить кэширование gradle-зависимостей, иначе они скачиваются заново при каждом билде.
Проблема с необходимостью скачивать NDK, даже если она не используется, появилась с Gradle Android Plugin 3.6.
В build.gradle нужно явно указать версию NDK, например
Если ее не указывать, то будет использоваться версия по-умолчанию (20.0.5594570).
В образе ubuntu-latest на данный момент предустановлена версия NDK 21.2.6472646
Если не лень, то можно собрать и использовать свой docker-image с нужной версией NDK.
Leeeeerich Автор
Спасибо, добавил про NDK. Да, я смотрел по поводу кеширования, но много жалоб на стабильность его работы, потому я не стал настраивать кеширование. Как по мне готовый docker-image использовать будет лучше, выделю время и изучу данный вопрос, спасибо.