Configure build with GitHub Actions

This commit is contained in:
Carter Jernigan 2022-01-26 15:13:19 -05:00 committed by GitHub
parent 40950a0377
commit 52b6382d47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 578 additions and 18 deletions

View File

@ -11,12 +11,11 @@ For a Gradle dependency:
1. Update the dependency version in the root `gradle.properties`
1. Update the dependency locks
1. For Gradle plugins: `./gradlew dependencies --write-locks`
1. For Gradle dependencies: `./gradlew resolveAndLockAll --write-locks`
1. For Gradle dependencies: `./gradlew resolveAll --write-locks`
1. Verify no unexpected entries appear in the lockfiles. _A supply chain attack could occur during this stage. The lockfile narrows the supply chain attack window to this very moment (as opposed to every time a build occurs)_
1. Are there any new APIs or possible migrations for this dependency?
For Gradle itself:
1. Run `./gradle wrapper --gradle-version $X`
1. Add `distributionSha256Sum=` in `gradle/wrapper/gradle-wrapper.properties`, referencing [Gradle Release Checksums](https://gradle.org/release-checksums/)
1. Update the continuous integration server environment variables with the updated SHA for the Gradle wrapper, referencing [Gradle Release Checksums](https://gradle.org/release-checksums/). _Note: Bitrise builds for other branches may temporarily fail since only a single checksum at a time is currently supported. The wrapper is not updated with every Gradle version so in practice this problem should occur infrequently._
1. Are there any new APIs or possible migrations?

44
.github/actions/setup/action.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: 'Setup Java and Dependency Cache'
description: "Configures the build environment and caches Gradle and dependencies."
runs:
using: "composite"
steps:
- name: Set Env
shell: bash
run: |
echo "home=${HOME}" >> "$GITHUB_ENV"
- name: Set up Java
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: 17
- name: Disable Gradle Daemon
shell: bash
run: |
mkdir ~/.gradle
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
- name: Gradle Wrapper Cache
id: gradle-wrapper-cache
uses: actions/cache@v2.1.7
with:
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles(format('{0}{1}', github.workspace, '/gradle/wrapper/gradle-wrapper.properties')) }}
- name: Gradle Dependency Cache
id: gradle-dependency-cache
uses: actions/cache@v2.1.7
with:
path: ~/.gradle/caches/modules-2
key: ${{ runner.os }}-gradle-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/gradle.properties')) }}
restore-keys: |
${{ runner.os }}-gradle-deps
- name: Download Gradle
if: steps.gradle-wrapper-cache.outputs.cache-hit != 'true'
shell: bash
run: |
./gradlew --version
- name: Download Dependencies
if: steps.gradle-dependency-cache.outputs.cache-hit != 'true'
shell: bash
run: |
./gradlew resolveAll

112
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,112 @@
# Expected secrets
# GOOGLE_PLAY_CLOUD_PROJECT - Google Cloud project associated with Google Play
# GOOGLE_PLAY_SERVICE_ACCOUNT - Email address of service account
# GOOGLE_PLAY_WORKLOAD_IDENTITY_PROVIDER - Workload identity provider to generate temporary service account key
# SIGNING_KEYSTORE_BASE_64 - The signing key for the app
# SIGNING_KEYSTORE_PASSWORD - The password for SIGNING_KEYSTORE_BASE_64
# SIGNING_KEY_ALIAS - The key alias inside SIGNING_KEYSTORE_BASE_64
# SIGNING_KEY_ALIAS_PASSWORD - The password for the key alias
name: Deploy
on:
workflow_dispatch:
push:
branches:
- main
paths-ignore:
- '.github/ISSUE_TEMPLATE/*'
- '.github/PULL_REQUEST_TEMPLATE.md'
- 'LICENSE'
- 'README.md'
- 'docs/**'
concurrency: deploy
jobs:
validate_gradle_wrapper:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
# Gradle Wrapper validation can be flaky
# https://github.com/gradle/wrapper-validation-action/issues/40
- name: Gradle Wrapper Validation
timeout-minutes: 1
uses: gradle/wrapper-validation-action@v1.0.4
check_secrets:
runs-on: ubuntu-latest
outputs:
has-secrets: ${{ steps.check_secrets.outputs.defined }}
steps:
- id: check_secrets
env:
GOOGLE_PLAY_CLOUD_PROJECT: ${{ secrets.GOOGLE_PLAY_CLOUD_PROJECT }}
GOOGLE_PLAY_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
GOOGLE_PLAY_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GOOGLE_PLAY_WORKLOAD_IDENTITY_PROVIDER }}
if: "${{ env.GOOGLE_PLAY_CLOUD_PROJECT != '' && env.GOOGLE_PLAY_SERVICE_ACCOUNT != '' && env.GOOGLE_PLAY_WORKLOAD_IDENTITY_PROVIDER != '' }}"
run: echo "::set-output name=defined::true"
build_and_deploy:
if: needs.check_secrets.outputs.has-secrets == 'true'
needs: [validate_gradle_wrapper, check_secrets]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
- name: Setup
id: setup
timeout-minutes: 8
uses: ./.github/actions/setup
- name: Authenticate to Google Cloud for Google Play
id: auth_google_play
uses: google-github-actions/auth@v0.5.0
with:
create_credentials_file: true
project_id: ${{ secrets.GOOGLE_PLAY_CLOUD_PROJECT }}
service_account: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
workload_identity_provider: ${{ secrets.GOOGLE_PLAY_WORKLOAD_IDENTITY_PROVIDER }}
access_token_lifetime: '1500s'
- name: Export Signing Key
env:
SIGNING_KEYSTORE_BASE_64: ${{ secrets.SIGNING_KEYSTORE_BASE_64 }}
SIGNING_KEY_PATH: ${{ format('{0}/release.jks', env.home) }}
shell: bash
run: |
echo ${SIGNING_KEYSTORE_BASE_64} | base64 --decode > ${SIGNING_KEY_PATH}
- name: Upload to Play Store
timeout-minutes: 25
env:
ZCASH_GOOGLE_PLAY_SERVICE_KEY_FILE_PATH: ${{ steps.auth_google_play.outputs.credentials_file_path }}
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PATH: ${{ format('{0}/release.jks', env.home) }}
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: ${{ secrets.SIGNING_KEY_ALIAS_PASSWORD }}
# Change to deploy once we've finished setting up the build scripts
ORG_GRADLE_PROJECT_ZCASH_GOOGLE_PLAY_DEPLOY_MODE: build
run: |
./gradlew :app:publishBundle
- name: Collect Artifacts
timeout-minutes: 1
env:
ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }}
BINARIES_ZIP_PATH: ${{ format('{0}/artifacts/binaries.zip', env.home) }}
MAPPINGS_ZIP_PATH: ${{ format('{0}/artifacts/mappings.zip', env.home) }}
run: |
mkdir ${ARTIFACTS_DIR_PATH}
zip -r ${BINARIES_ZIP_PATH} . -i *app/build/outputs/apk/*/release/*.apk *app/build/outputs/bundle/*/release/*.aab
zip -r ${MAPPINGS_ZIP_PATH} . -i *app/build/outputs/mapping/*/mapping.txt
- name: Upload Artifacts
uses: actions/upload-artifact@v2
timeout-minutes: 1
with:
name: Release binaries
path: ~/artifacts

336
.github/workflows/pull-request.yml vendored Normal file
View File

@ -0,0 +1,336 @@
# Expected secrets
# FIREBASE_TEST_LAB_PROJECT - Firebase Test Lab project name
# FIREBASE_TEST_LAB_SERVICE_ACCOUNT - Email address of Firebase Test Lab service account
# FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER - Workload identity provider to generate temporary service account key
name: Pull Request
on:
pull_request:
paths-ignore:
- '.github/ISSUE_TEMPLATE/*'
- '.github/PULL_REQUEST_TEMPLATE.md'
- 'LICENSE'
- 'README.md'
- 'docs/**'
jobs:
validate_gradle_wrapper:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
# Gradle Wrapper validation can be flaky
# https://github.com/gradle/wrapper-validation-action/issues/40
- name: Gradle Wrapper Validation
timeout-minutes: 1
uses: gradle/wrapper-validation-action@v1.0.4
prime_cache:
needs: validate_gradle_wrapper
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
- name: Setup
id: setup
timeout-minutes: 8
uses: ./.github/actions/setup
check_secrets:
runs-on: ubuntu-latest
outputs:
has-secrets: ${{ steps.check_secrets.outputs.defined }}
steps:
- id: check_secrets
env:
FIREBASE_TEST_LAB_PROJECT: ${{ secrets.FIREBASE_TEST_LAB_PROJECT }}
FIREBASE_TEST_LAB_SERVICE_ACCOUNT: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT }}
FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER }}
if: "${{ env.FIREBASE_TEST_LAB_PROJECT != '' && env.FIREBASE_TEST_LAB_SERVICE_ACCOUNT != '' && env.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER != '' }}"
run: echo "::set-output name=defined::true"
static_analysis_detekt:
needs: prime_cache
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
- name: Setup
id: setup
timeout-minutes: 5
uses: ./.github/actions/setup
- name: Detekt
timeout-minutes: 4
run: |
./gradlew detektAll
- name: Collect Artifacts
timeout-minutes: 1
if: ${{ always() }}
env:
ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }}
REPORTS_ZIP_PATH: ${{ format('{0}/artifacts/static_analysis_detekt.zip', env.home) }}
run: |
mkdir ${ARTIFACTS_DIR_PATH}
zip -r ${REPORTS_ZIP_PATH} . -i build/reports/detekt/*
- name: Upload Artifacts
if: ${{ always() }}
uses: actions/upload-artifact@v2
timeout-minutes: 1
with:
name: Detekt static analysis results
path: ~/artifacts
static_analysis_ktlint:
needs: prime_cache
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
- name: Setup
id: setup
timeout-minutes: 5
uses: ./.github/actions/setup
- name: Ktlint
timeout-minutes: 4
run: |
./gradlew ktlint
- name: Collect Artifacts
timeout-minutes: 1
if: ${{ always() }}
env:
ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }}
REPORTS_ZIP_PATH: ${{ format('{0}/artifacts/static_analysis_ktlint.zip', env.home) }}
run: |
mkdir ${ARTIFACTS_DIR_PATH}
zip -r ${REPORTS_ZIP_PATH} . -i build/reports/ktlint/*
- name: Upload Artifacts
if: ${{ always() }}
uses: actions/upload-artifact@v2
timeout-minutes: 1
with:
name: Ktlint static analysis results
path: ~/artifacts
static_analysis_android_lint:
needs: prime_cache
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
- name: Setup
id: setup
timeout-minutes: 5
uses: ./.github/actions/setup
- name: Android Lint
timeout-minutes: 15
env:
# Disable minify, since it makes lint run faster
ORG_GRADLE_PROJECT_IS_MINIFY_ENABLED: false
run: |
./gradlew :app:lintZcashmainnetRelease
- name: Collect Artifacts
timeout-minutes: 1
env:
ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }}
LINT_ZIP_PATH: ${{ format('{0}/artifacts/android_lint.zip', env.home) }}
run: |
mkdir ${ARTIFACTS_DIR_PATH}
zip -r ${LINT_ZIP_PATH} . -i *build/reports/*
- name: Upload Artifacts
uses: actions/upload-artifact@v2
timeout-minutes: 1
with:
name: Android Lint static analysis results
path: ~/artifacts
test_kotlin_modules:
needs: prime_cache
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
- name: Setup
id: setup
timeout-minutes: 5
uses: ./.github/actions/setup
- name: Test
timeout-minutes: 4
run: |
./gradlew ktlint
- name: Collect Artifacts
timeout-minutes: 1
env:
ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }}
RESULTS_ZIP_PATH: ${{ format('{0}/artifacts/test_kotlin.zip', env.home) }}
run: |
mkdir ${ARTIFACTS_DIR_PATH}
zip -r ${RESULTS_ZIP_PATH} . -i *build/reports/*
- name: Upload Artifacts
uses: actions/upload-artifact@v2
timeout-minutes: 1
with:
name: Test Kotlin modules results
path: ~/artifacts
test_android_modules:
if: needs.check_secrets.outputs.has-secrets == 'true'
needs: [prime_cache, check_secrets]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
- name: Setup
id: setup
timeout-minutes: 5
uses: ./.github/actions/setup
- name: Build
timeout-minutes: 20
run: |
./gradlew assembleDebug assembleAndroidTest
- name: Authenticate to Google Cloud for Firebase Test Lab
id: auth_test_lab
uses: google-github-actions/auth@v0.5.0
with:
create_credentials_file: true
project_id: ${{ secrets.FIREBASE_TEST_LAB_PROJECT }}
service_account: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT }}
workload_identity_provider: ${{ secrets.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER }}
access_token_lifetime: '1200s'
- name: Test
timeout-minutes: 20
env:
# This first environment variable is used by Flank, since the temporary token is missing the project name
GOOGLE_CLOUD_PROJECT: ${{ secrets.FIREBASE_TEST_LAB_PROJECT }}
ORG_GRADLE_PROJECT_ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH: ${{ steps.auth_test_lab.outputs.credentials_file_path }}
run: |
# NEED Firebase Test Lab API key
# ./gradlew runFlank --parallel
- name: Collect Artifacts
timeout-minutes: 1
env:
ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }}
TEST_RESULTS_ZIP_PATH: ${{ format('{0}/artifacts/test_results.zip', env.home) }}
run: |
mkdir ${ARTIFACTS_DIR_PATH}
zip -r ${TEST_RESULTS_ZIP_PATH} . -i *build/outputs/androidTest-results/*
- name: Upload Artifacts
uses: actions/upload-artifact@v2
timeout-minutes: 1
with:
name: Test Android modules results
path: ~/artifacts
release_build:
needs: prime_cache
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
- name: Setup
id: setup
timeout-minutes: 5
uses: ./.github/actions/setup
# A fake signing key to satisfy creating a "release" build
- name: Export Signing Key
env:
SIGNING_KEY_PATH: ${{ format('{0}/release.jks', env.home) }}
shell: bash
run: |
keytool -genkey -v -keystore $SIGNING_KEY_PATH -keypass android -storepass android -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 100000 -dname "CN=, OU=, O=Test, L=, S=, C=" -noprompt
- name: Build
timeout-minutes: 20
env:
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PATH: ${{ format('{0}/release.jks', env.home) }}
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PASSWORD: android
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: androiddebugkey
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: android
run: |
./gradlew :app:assembleRelease
- name: Collect Artifacts
timeout-minutes: 1
env:
ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }}
BINARIES_ZIP_PATH: ${{ format('{0}/artifacts/binaries.zip', env.home) }}
MAPPINGS_ZIP_PATH: ${{ format('{0}/artifacts/mappings.zip', env.home) }}
run: |
mkdir ${ARTIFACTS_DIR_PATH}
zip -r ${BINARIES_ZIP_PATH} . -i *app/build/outputs/apk/*/release/*.apk *app/build/outputs/bundle/*/release/*.aab
zip -r ${MAPPINGS_ZIP_PATH} . -i *app/build/outputs/mapping/*/mapping.txt
- name: Upload Artifacts
uses: actions/upload-artifact@v2
timeout-minutes: 1
with:
name: Release binaries
path: ~/artifacts
# Performs a button mash test on the release build of the app
# Note that we might need to help it get past the onboarding test with a script
test_robo:
if: needs.check_secrets.outputs.has-secrets == 'true'
needs: [release_build, check_secrets]
runs-on: ubuntu-latest
permissions:
packages: read
contents: read
id-token: write
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@v2.4.0
- name: Setup
id: setup
timeout-minutes: 5
uses: ./.github/actions/setup
- name: Authenticate to Google Cloud for Firebase Test Lab
id: auth_test_lab
uses: google-github-actions/auth@v0.5.0
with:
create_credentials_file: true
project_id: ${{ secrets.FIREBASE_TEST_LAB_PROJECT }}
service_account: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT }}
workload_identity_provider: ${{ secrets.FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER }}
access_token_lifetime: '900s'
- name: Download a single artifact
uses: actions/download-artifact@v2
with:
name: Release binaries
- name: Robo test
timeout-minutes: 15
env:
# Path depends on `release_build` job, plus path of `Download a single artifact` step
BINARIES_ZIP_PATH: binaries.zip
# This first environment variable is used by Flank, since the temporary token is missing the project name
GOOGLE_CLOUD_PROJECT: ${{ secrets.FIREBASE_TEST_LAB_PROJECT }}
ORG_GRADLE_PROJECT_ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH: ${{ steps.auth_test_lab.outputs.credentials_file_path }}
run: |
unzip ${BINARIES_ZIP_PATH}
./gradlew :app:runFlankSanityConfig

View File

@ -28,6 +28,8 @@ If you plan to fork the project to create a new app of your own, please make the
1. ui-lib/src/main/res/common/ - All of the the ic_launcher assets
1. Change the package name
1. Under [app/build.gradle.kts](app/build.gradle.kts), change the package name of the application
1. Optional
1. Configure secrets for [Continuous Integration](docs/CI.md).
# Known Issues

View File

@ -2,7 +2,6 @@ dependencyLocking {
lockAllConfigurations()
}
// Just resolves dependencies
tasks {
register("resolveAll") {
doLast {
@ -12,17 +11,4 @@ tasks {
}.forEach { it.resolve() }
}
}
// Invoke with `./gradlew resolveAndLockAll --write-locks`
register("resolveAndLockAll") {
doFirst {
require(gradle.startParameter.isWriteDependencyLocks)
}
doLast {
configurations.filter {
// Add any custom filtering on the configurations to be resolved
it.isCanBeResolved
}.forEach { it.resolve() }
}
}
}

View File

@ -7,6 +7,7 @@ buildscript {
plugins {
id("com.github.ben-manes.versions")
id("com.osacky.fulladle")
id("io.gitlab.arturbosch.detekt")
id("zcash.ktlint-conventions")
}
@ -49,6 +50,34 @@ fun isNonStable(version: String): Boolean {
return unstableKeywords.any { versionLowerCase.contains(it) }
}
// Firebase Test Lab has min and max values that might differ from our project's
// These are determined by `gcloud firebase test android models list`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MIN_API = 23
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MAX_API = 30
val firebaseTestLabKeyPath = project.properties["ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH"].toString()
if (firebaseTestLabKeyPath.isNotBlank()) {
val minSdkVersion = run {
val buildMinSdk = project.properties["ANDROID_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(FIREBASE_TEST_LAB_MIN_API).toString()
}
val targetSdkVersion = run {
val buildTargetSdk = project.properties["ANDROID_TARGET_SDK_VERSION"].toString().toInt()
buildTargetSdk.coerceAtMost(FIREBASE_TEST_LAB_MAX_API).toString()
}
fladle {
serviceAccountCredentials.set(File(firebaseTestLabKeyPath))
devices.addAll(
mapOf("model" to "NexusLowRes", "version" to minSdkVersion),
mapOf("model" to "NexusLowRes", "version" to targetSdkVersion)
)
@Suppress("MagicNumber")
flakyTestAttempts.set(2)
}
}
// All of this should be refactored to build-conventions
subprojects {

View File

@ -68,6 +68,8 @@ com.google.protobuf:protobuf-java:3.10.0=classpath
com.google.testing.platform:core-proto:0.0.8-alpha07=classpath
com.googlecode.json-simple:json-simple:1.1=classpath
com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath
com.osacky.flank.gradle:fladle:0.17.3=classpath
com.osacky.fulladle:com.osacky.fulladle.gradle.plugin:0.17.3=classpath
com.squareup:javapoet:1.10.0=classpath
com.squareup:javawriter:2.5.0=classpath
com.sun.activation:javax.activation:1.2.0=classpath

44
docs/CI.md Normal file
View File

@ -0,0 +1,44 @@
# Continuous Integration
Continuous integration is set up with GitHub Actions. The workflows are defined in this repo under [/.github/workflows](../.github/workflows).
Workflows exist for:
* Pull request - On pull request, static analysis and testing is performed.
* Deploy - Work in progress. On merge to the main branch, a release build is automatically deployed. Concurrency limits are in place, to ensure that only one release deployment can happen at a time.
## Setup
When forking this repository, some secrets need to be defined to set up new continuous integration builds.
The secrets passed to GitHub Actions then map to Gradle properties set up within our build scripts. Necessary secrets are documented at the top of each GitHub workflow yml file, as well as reiterated here.
To enhance security, [OpenID Connect](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-google-cloud-platform) is used to generate temporary access tokens for each build.
### Pull request
* `FIREBASE_TEST_LAB_PROJECT` - Firebase Test Lab project name.
* `FIREBASE_TEST_LAB_SERVICE_ACCOUNT` - Email address of Firebase Test Lab service account.
* `FIREBASE_TEST_LAB_WORKLOAD_IDENTITY_PROVIDER` - Workload identity provider to generate temporary service account key.
To obtain the values for these, you'll need to enable the necessary Google Cloud APIs to enable automated access to Firebase Test Lab.
* Configure Firebase Test Lab. Google has [documentation for Jenkins](https://firebase.google.com/docs/test-lab/android/continuous). Although we're using GitHub Actions, the initial requirements are the same.
* Configure [workload identity federation](https://github.com/google-github-actions/auth#setting-up-workload-identity-federation)
Note that pull requests will create a "release" build with a temporary fake signing key. This simplifies configuration of CI for forks who simply want to run tests and not do release deployments. The limitations of this approach are:
- These builds cannot be used for testing of app upgrade compatibility (since signing key is different each time)
- Firebase, Google Play Services, and Google Maps won't work since they use the signing key to restrict API access. The app does not currently use these services, so this is a non-issue for now.
### Release deployment
* `GOOGLE_PLAY_CLOUD_PROJECT` - Google Cloud project associated with Google Play.
* `GOOGLE_PLAY_SERVICE_ACCOUNT` - Email address of service account.
* `GOOGLE_PLAY_WORKLOAD_IDENTITY_PROVIDER` - Workload identity provider to generate temporary service account key
* `SIGNING_KEYSTORE_BASE_64` — Base64 encoded upload keystore.
* `SIGNING_KEYSTORE_PASSWORD` — Password for upload keystore.
* `SIGNING_KEY_ALIAS` — Name of key inside upload keystore.
* `SIGNING_KEY_ALIAS_PASSWORD` — Password for key alias.
To obtain the values for the Google Play deployment, you'll need to
* Create a service account with access to your Google Play account. Recommended permissions are to "edit and delete draft apps" and "release apps to testing tracks".
* Configure [workload identity federation](https://github.com/google-github-actions/auth#setting-up-workload-identity-federation)
Note that security of release deployments is enhanced via two mechanisms:
- CI signs the app with the upload keystore and not the final release keystore. If the upload keystore is ever leaked, it can be rotated without impacting end user security.
- Deployment to Google Play can only be made to testing tracks. Release to production requires manual human login under a different account with greater permissions.

View File

@ -21,6 +21,9 @@ IS_COVERAGE_ENABLED=false
# It is disabled by default, because it causes tests to take about 2x longer to run.
IS_USE_TEST_ORCHESTRATOR=false
# Optional API key for Firebase Test Lab
ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH=
# Optionally disable minification
IS_MINIFY_ENABLED=true
@ -60,6 +63,7 @@ ANDROID_NDK_VERSION=23.0.7599858
ANDROID_GRADLE_PLUGIN_VERSION=7.1.0
DETEKT_VERSION=1.19.0
FULLADLE_VERSION_MATCHER=0.17.3
GRADLE_VERSIONS_PLUGIN_VERSION=0.39.0
KTLINT_VERSION=0.43.1
JGIT_VERSION=5.12.0.202106070339-r

View File

@ -34,6 +34,7 @@ pluginManagement {
plugins {
val androidGradlePluginVersion = extra["ANDROID_GRADLE_PLUGIN_VERSION"].toString()
val detektVersion = extra["DETEKT_VERSION"].toString()
val fulladleVersion = extra["FULLADLE_VERSION_MATCHER"].toString()
val gradleVersionsPluginVersion = extra["GRADLE_VERSIONS_PLUGIN_VERSION"].toString()
val kotlinVersion = extra["KOTLIN_VERSION"].toString()
val playPublisherVersion = extra["PLAY_PUBLISHER_PLUGIN_VERSION_MATCHER"].toString()
@ -42,6 +43,7 @@ pluginManagement {
id("com.android.library") version (androidGradlePluginVersion) apply (false)
id("com.github.ben-manes.versions") version (gradleVersionsPluginVersion) apply (false)
id("com.github.triplet.play") version (playPublisherVersion) apply (false)
id("com.osacky.fulladle") version (fulladleVersion) apply (false)
id("io.gitlab.arturbosch.detekt") version (detektVersion) apply (false)
kotlin("android") version (kotlinVersion) apply (false)
kotlin("jvm") version (kotlinVersion)
@ -54,7 +56,7 @@ dependencyResolutionManagement {
repositories {
val isRepoRestrictionEnabled = true
maven("https://dl.google.com/dl/android/maven2/") { // google()
google {
if (isRepoRestrictionEnabled) {
content {
includeGroup("android.arch.lifecycle")
@ -65,7 +67,7 @@ dependencyResolutionManagement {
}
}
}
maven("https://repo.maven.apache.org/maven2/") { // mavenCentral()
mavenCentral {
if (isRepoRestrictionEnabled) {
content {
excludeGroup("android.arch.lifecycle")