From f738666cd2f00f98dd9c687d588d5dbec9290af7 Mon Sep 17 00:00:00 2001 From: Carter Jernigan Date: Thu, 2 Dec 2021 15:18:17 -0500 Subject: [PATCH] [#42] Add app deployment process This enables deployment to Google Play internal testing. Note these changes cannot be fully tested until the signing and API keys are in place. Non-deployed builds are continuing to work as expected (e.g. `./gradlew assemble`) --- app/build.gradle.kts | 53 ++++++++++++++++++++++++++++++++++++++++++-- docs/Deployment.md | 40 +++++++++++++++++++++++++++++++++ gradle.properties | 14 ++++++++++++ settings.gradle.kts | 2 ++ 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 docs/Deployment.md diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 64d0967b..1150b65e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("kotlin-parcelize") id("androidx.navigation.safeargs") id("zcash.android-build-conventions") + id("com.github.triplet.play") } val packageName = "cash.z.ecc" @@ -11,8 +12,11 @@ val packageName = "cash.z.ecc" android { defaultConfig { applicationId = packageName - versionCode = 1 - versionName = "1.0" + + // If Google Play deployment is triggered, then these are placeholders which are overwritten + // when the deployment runs + versionCode = project.property("ZCASH_VERSION_CODE").toString().toInt() + versionName = project.property("ZCASH_VERSION_NAME").toString() } compileOptions { @@ -100,3 +104,48 @@ dependencies { } } } + +val googlePlayServiceKeyFilePath = project.property("ZCASH_GOOGLE_PLAY_SERVICE_KEY_FILE_PATH").toString() +if (googlePlayServiceKeyFilePath.isNotEmpty()) { + // Update the versionName to reflect bumps in versionCode + androidComponents { + val versionCodeOffset = 0 // Change this to zero the final digit of the versionName + onVariants { variant -> + for (output in variant.outputs) { + val processedVersionCode = output.versionCode.map { playVersionCode -> + val defaultVersionName = project.property("ZCASH_VERSION_NAME").toString() + // Version names will look like `myCustomVersionName.123` + playVersionCode?.let { + val delta = it - versionCodeOffset + if (delta < 0) { + defaultVersionName + } else { + "$defaultVersionName.$delta" + } + } ?: defaultVersionName + } + + output.versionName.set(processedVersionCode) + } + } + } + + configure { + serviceAccountCredentials.set(File(googlePlayServiceKeyFilePath)) + + // For safety, only allow deployment to internal testing track + track.set("internal") + + // Automatically manage version incrementing + resolutionStrategy.set(com.github.triplet.gradle.androidpublisher.ResolutionStrategy.AUTO) + + val deployMode = project.property("ZCASH_GOOGLE_PLAY_DEPLOY_MODE").toString() + if ("build" == deployMode) { + releaseStatus.set(com.github.triplet.gradle.androidpublisher.ReleaseStatus.DRAFT) + // Prevent upload; only generates a build with the correct version number + commit.set(false) + } else if ("deploy" == deployMode) { + releaseStatus.set(com.github.triplet.gradle.androidpublisher.ReleaseStatus.COMPLETED) + } + } +} diff --git a/docs/Deployment.md b/docs/Deployment.md new file mode 100644 index 00000000..e997b8a5 --- /dev/null +++ b/docs/Deployment.md @@ -0,0 +1,40 @@ +# Signing +To create a release build, signing must be configured. The following Gradle properties must be set: +1. `ZCASH_RELEASE_KEYSTORE_PATH` +1. `ZCASH_RELEASE_KEYSTORE_PASSWORD` +1. `ZCASH_RELEASE_KEY_ALIAS` +1. `ZCASH_RELEASE_KEY_ALIAS_PASSWORD` +1. Run `./gradlew :app:assembleRelease` to create a signed release APK, which can be tested and easily installed on an emulator or test device. _Note that this APK cannot be deployed, because Google Play requires deployment in AAB format. APK, however, is easier to manage for manually creating a build for testing._ + +Note that although these are called "release" keys, they may actually be the "upload" key if Google Play Signing is being used. + +# Deployment +After signing is configured, it is possible to then configure deployment to Google Play. + +## Automated Deployment +Automated deployment to Google Play configured with the [Gradle Play Publisher plugin](https://github.com/Triple-T/gradle-play-publisher). +To perform a deployment: +1. Configure a Google Cloud service API key with the correct permissions +1. Configure Gradle properties + 1. `ZCASH_GOOGLE_PLAY_SERVICE_KEY_FILE_PATH` - Set to the path of the service key in JSON format + 1. `ZCASH_GOOGLE_PLAY_DEPLOY_MODE` - Set to `deploy` +1. Run the Gradle task `./gradlew :app:publishBundle` + +To generate a build with a correct version that can be deployed manually later: +1. Configure a Google Cloud service API key with the correct permissions +1. Configure Gradle properties + 1. `ZCASH_GOOGLE_PLAY_SERVICE_KEY_FILE_PATH` - Set to the path of the service key in JSON format + 1. `ZCASH_GOOGLE_PLAY_DEPLOY_MODE` - Set to `build` (this is the default value) +1. Run the Gradle tasks `./gradlew :app:processReleaseVersionCodes :app:bundleRelease` + +Note that the above instructions are for repeat deployments. If you do not yet have an app listing, you'll need to create that manually. + +Note that the artifacts can be manually saved from their output directory under the app/build directory + +## Manual Deployment +To manually deploy a build of the app +1. Configure Gradle properties + 1. `ZCASH_VERSION_CODE` - Set to the integer version code of the app. A simple monotonically increasing number is recommended.1 + 1. `ZCASH_VERSION_NAME` - Set to a human-readable version number, such as 1.0.1. +1. Run the Gradle task `./gradlew :app:bundleRelease` +1. Collect the build artifacts under `app/build` and manually deploy them through the Google Play web interface \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 6b0c1564..3bd80717 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,6 +28,12 @@ IS_USE_TEST_ORCHESTRATOR=true # Optionally disable minification IS_MINIFY_ENABLED=true +# If ZCASH_GOOGLE_PLAY_SERVICE_KEY_FILE_PATH is set and the deployment task is triggered, then +# VERSION_CODE is effectively ignored VERSION_NAME is suffixed with the version code. +# If not using automated Google Play deployment, then these serve as the actual version numbers. +ZCASH_VERSION_CODE=1 +ZCASH_VERSION_NAME=0.1 + # Set keystore details to enable build signing. Typically these # are overridden via ~/.gradle/gradle.properties to allow secure injection. # Debug keystore is useful if using Google Maps or Firebase, which require API keys to be linked @@ -39,6 +45,13 @@ ZCASH_RELEASE_KEYSTORE_PASSWORD= ZCASH_RELEASE_KEY_ALIAS= ZCASH_RELEASE_KEY_ALIAS_PASSWORD= +# Optionally set the Google Play Service Key path to enable deployment +ZCASH_GOOGLE_PLAY_SERVICE_KEY_FILE_PATH= +# Can be one of {build, deploy}. +# Build can be used to generate a version number for the next release, but does not ultimately create a release on Google Play. +# Deploy commits the build on Google Play, creating a new release +ZCASH_GOOGLE_PLAY_DEPLOY_MODE=build + # Versions ANDROID_MIN_SDK_VERSION=24 ANDROID_TARGET_SDK_VERSION=31 @@ -51,6 +64,7 @@ DETEKT_VERSION=1.19.0 GRADLE_VERSIONS_PLUGIN_VERSION=0.39.0 KTLINT_VERSION=0.43.1 JGIT_VERSION=5.12.0.202106070339-r +PLAY_PUBLISHER_PLUGIN_VERSION_MATCHER=3.6.0 ANDROIDX_ACTIVITY_VERSION=1.4.0 ANDROIDX_ANNOTATION_VERSION=1.3.0 diff --git a/settings.gradle.kts b/settings.gradle.kts index eb62eca2..31aad872 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,10 +6,12 @@ pluginManagement { val detektVersion = extra["DETEKT_VERSION"].toString() val gradleVersionsPluginVersion = extra["GRADLE_VERSIONS_PLUGIN_VERSION"].toString() val kotlinVersion = extra["KOTLIN_VERSION"].toString() + val playPublisherVersion = extra["PLAY_PUBLISHER_PLUGIN_VERSION_MATCHER"].toString() kotlin("jvm") version (kotlinVersion) kotlin("multiplatform") version (kotlinVersion) id("com.github.ben-manes.versions") version (gradleVersionsPluginVersion) apply (false) + id("com.github.triplet.play") version (playPublisherVersion) apply (false) id("io.gitlab.arturbosch.detekt") version (detektVersion) apply (false) } }