Этап первый: настройка для автоматической подписи готового 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_passworddef release_key_passworddef release_key_aliasdef pin_aliasdef db_pass_aliasdef 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_CIon: push: branches: - beta_releasejobs: 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-ти дней, но ответа так и не последовало.