Настраиваем GitHub Actions для Android с последующим деплоем в PlayMarket

от автора

Привет, Хаброжители! На днях начал изучать GitHub Actions для Android. Ранее у меня была удачная попытка настройки данного функционала для проекта на Flutter, но без деплоя, для которого полно информации и гайдов как на англоязычных ресурсах, так и на русскоязычных, а вот с нативным андроидом не всё так прозаично. И решил записать основные проблемы и их решение.

Этап первый: настройка для автоматической подписи готового 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 }}.

${{ github.run_number }}

Уникальный номер для каждого запуска определенного рабочего процесса в хранилище. Это число начинается с 1 для первого запуска рабочего процесса и увеличивается с каждым новым запуском. Это число не изменится, если вы повторно запустите рабочий процесс. (Запуском здесь подразумевается — когда вы пушите в ветку).

По этому взвесив все за и против имеем следующее решение:

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}

P.S. Для Gradle использовал подсветку кода от Kotlin. Для YAML — от JSON. Конечно немного не то, но лучше мне найти не удалось, если есть лучшие варианты, сообщите мне пожалуйста и я исправлю.

P.S.S. Может быть у кого есть лучшее решение или предложения по улучшения, напишите их в комментариях, так как по первому этапу вопрос провисел на StackOverflow больше 10-ти дней, но ответа так и не последовало.

ссылка на оригинал статьи https://habr.com/ru/post/506700/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *