version: 2.1 orbs: android: circleci/android@0.2.1 sonarcloud: sonarsource/sonarcloud@1.0.2 ####################### # Commands section # For code reuse. ####################### commands: install-ndk: android/install-ndk restore-android-build-cache: android/restore-build-cache save-android-build-cache: android/save-build-cache scan-sonar: sonarcloud/scan restore-gradle-cache: description: "Restore gradle caches" steps: - restore_cache: key: gradle-v1-{{ arch }}-{{ checksum "build.gradle" }}-{{ checksum "Corona-Warn-App/build.gradle" }}-{{ checksum "Server-Protocol-Buffer/build.gradle" }} save-gradle-cache: description: "Save gradle caches" steps: - save_cache: paths: - ~/.gradle key: gradle-v1-{{ arch }}-{{ checksum "build.gradle" }}-{{ checksum "Corona-Warn-App/build.gradle" }}-{{ checksum "Server-Protocol-Buffer/build.gradle" }} restore-android-build-cache-macos: description: "Restore Android build caches on macOS" steps: - restore_cache: key: android-buildcache-v1-{{ arch }} save-android-build-cache-macos: description: "Save Android build caches on macOS" steps: - save_cache: paths: - ~/.android/build-cache - ~/.android/cache key: android-buildcache-v1-{{ arch }}-{{ epoch }} run-gradle-cmd: description: "Running gradle command with environment options" parameters: desc: type: string default: "Running gradle command" cmd: type: string steps: - run: name: << parameters.desc >> command: > ./gradlew -PdisablePreDex << parameters.cmd >> no_output_timeout: 30m environment: JVM_OPTS: -Xmx4096m GRADLE_OPTS: > -Xmx1536m -XX:+HeapDumpOnOutOfMemoryError -Dorg.gradle.caching=true -Dorg.gradle.configureondemand=true -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false run-gradle-cmd-test-splitting: description: "Running gradle command with environment options and test splitting" parameters: desc: type: string default: "Running gradle command" cmd: type: string steps: - run: name: Test splitting output command: circleci tests glob "**/test*/**/*.kt" | circleci tests split | xargs -n 1 echo - run: name: << parameters.desc >> command: > ./gradlew -PdisablePreDex << parameters.cmd >> -i -PtestFilter="`circleci tests glob "**/test*/**/*.kt" | circleci tests split`" environment: JVM_OPTS: -Xmx4096m GRADLE_OPTS: > -Xmx1536m -XX:+HeapDumpOnOutOfMemoryError -Dorg.gradle.caching=true -Dorg.gradle.configureondemand=true -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false skip-for-external-pull-requests: description: "Skip for external pull requests due to missing access to secrets." steps: - run: name: Early return if this build is from a forked PR command: | if [ -n "$CIRCLE_PR_NUMBER" ]; then echo "Nothing to do for forked PRs, so marking this step successful" circleci step halt fi setup-android-macos: description: "Setup Android environment on macOS executor" steps: - restore_cache: key: android-sdk-v1-{{ arch }}-{{ checksum ".circleci/install-android-sdk.sh" }} - run: name: Set ANDROID_SDK_ROOT environment variable command: echo 'export ANDROID_SDK_ROOT=$HOME/android-sdk' >> $BASH_ENV - run: name: Install Android SDK command: | sh .circleci/install-android-sdk.sh echo 'export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH' >> $BASH_ENV echo 'export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest:$PATH' >> $BASH_ENV echo 'export PATH=$ANDROID_SDK_ROOT/platform-tools:$PATH' >> $BASH_ENV echo 'export PATH=$ANDROID_SDK_ROOT/emulator:$PATH' >> $BASH_ENV echo 'export PATH=$ANDROID_SDK_ROOT/build-tools/29.0.3:$PATH' >> $BASH_ENV source $BASH_ENV sdkmanager --list - save_cache: key: android-sdk-v1-{{ arch }}-{{ checksum ".circleci/install-android-sdk.sh" }} paths: - /Users/distiller/android-sdk run-emulator-for-api: parameters: apilevel: type: integer description: "Setup and start emulator for API<< parameters.apilevel >>" steps: - run: name: Create emulator AVD command: > echo "no" | avdmanager --verbose create avd --force --name "emulator_API<< parameters.apilevel >>" --package "system-images;android-<< parameters.apilevel >>;google_apis;x86_64" - run: name: Configure emulator settings command: | cd $HOME/.android/avd/emulator_API<< parameters.apilevel >>.avd sed -i '' -e 's/hw.lcd.density=[0-9]*/hw.lcd.density=560/g' config.ini sed -i '' -e 's/hw.lcd.height=[0-9]*/hw.lcd.height=2880/g' config.ini sed -i '' -e 's/hw.lcd.width=[0-9]*/hw.lcd.width=1440/g' config.ini sed -i '' -e 's/hw.ramSize=[0-9]*/hw.ramSize=1536/g' config.ini - run: name: Check host state command: | emulator -accel-check emulator -version - run: name: Start emulator AVD command: > emulator @emulator_API<< parameters.apilevel >> -no-window -no-audio -no-boot-anim -memory 2048 -nojni background: true no_output_timeout: 60m - run: name: Wait for emulator command: | adb wait-for-device shell 'while [[ -z $(getprop dev.bootcomplete) ]]; do sleep 1; done;' adb devices sleep 5 - run: name: Disable animations command: | adb shell settings put global window_animation_scale 0 adb shell settings put global transition_animation_scale 0 adb shell settings put global animator_duration_scale 0 kill-all-emulators: description: "Kill all emulators" steps: - run: name: Kill all emulators command: | adb devices | grep emulator | cut -f1 | while read line; do adb -s $line emu kill; done sleep 2 adb kill-server sleep 2 compress-path: parameters: input: type: string output: type: string description: "Compress << parameters.input >> to << parameters.output >>" steps: - run: name: Compress files command: > zip -r << parameters.output >> << parameters.input >> setup-google-cloud: description: "Setup Google Cloud access." steps: - run: name: Store Google Service Account command: echo $GCLOUD_SERVICE_KEY_BASE64 | base64 -di > ${HOME}/gcloud-service-key.json - run: name: Authorize gcloud and set config defaults command: | sudo gcloud auth activate-service-account --key-file=${HOME}/gcloud-service-key.json sudo gcloud --quiet config set project ${GOOGLE_PROJECT_ID} ####################### # Jobs section # Tasks that get executed ####################### jobs: detekt: executor: android/android resource_class: large working_directory: ~/project steps: - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: desc: Detekt check cmd: ":Corona-Warn-App:detekt" - store_artifacts: path: Corona-Warn-App/build/reports destination: reports quick_build_device_release_no_tests: executor: android/android resource_class: xlarge working_directory: ~/project steps: - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: desc: Quick Build cmd: "assembleDeviceRelease" - save-gradle-cache - save-android-build-cache - store_artifacts: path: Corona-Warn-App/build/reports destination: reports quick_build_device_for_testers_release_no_tests: executor: android/android resource_class: xlarge working_directory: ~/project steps: - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: desc: Quick Build cmd: ":Corona-Warn-App:assembleDeviceForTestersRelease" - save-gradle-cache - save-android-build-cache - store_artifacts: path: Corona-Warn-App/build/reports destination: reports unit_tests_device_release: executor: android/android resource_class: xlarge working_directory: ~/project parallelism: 3 steps: - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd-test-splitting: desc: Unit tests cmd: ":Corona-Warn-App:testDeviceReleaseUnitTest -i" - save-gradle-cache - save-android-build-cache - store_test_results: path: Corona-Warn-App/build/test-results - persist_to_workspace: root: /home/circleci paths: - ./project - compress-path: input: ./Corona-Warn-App/build/reports output: /tmp/unit_tests_device_release.zip - store_artifacts: path: /tmp/unit_tests_device_release.zip destination: zips/unit_tests_device_release.zip unit_tests_device_for_testers_release: executor: android/android resource_class: xlarge working_directory: ~/project parallelism: 3 steps: - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd-test-splitting: desc: Unit tests with splitting cmd: ":Corona-Warn-App:testDeviceForTestersReleaseUnitTest" - save-gradle-cache - save-android-build-cache - store_test_results: path: Corona-Warn-App/build/test-results - compress-path: input: ./Corona-Warn-App/build/reports output: /tmp/unit_tests_device_for_testers_release.zip - store_artifacts: path: /tmp/unit_tests_device_for_testers_release.zip destination: zips/unit_tests_device_for_testers_release.zip lint_device_release_check: executor: android/android resource_class: xlarge working_directory: ~/project steps: - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: desc: Lint check deviceRelease cmd: ":Corona-Warn-App:lintDeviceRelease -i" - store_artifacts: path: Corona-Warn-App/build/reports destination: reports lint_device_for_testers_release_check: executor: android/android resource_class: xlarge working_directory: ~/project steps: - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: desc: Lint check deviceForTestersRelease cmd: ":Corona-Warn-App:lintDeviceForTestersRelease -i" - store_artifacts: path: Corona-Warn-App/build/reports destination: reports ktlint_check: executor: android/android resource_class: medium working_directory: ~/project steps: - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: desc: Ktlint check cmd: ":Corona-Warn-App:ktlintCheck" - store_artifacts: path: Corona-Warn-App/build/reports destination: reports run_sonar: executor: android/android resource_class: xlarge working_directory: ~/project steps: - skip-for-external-pull-requests - attach_workspace: at: /home/circleci - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: desc: JaCoCo report cmd: ":Corona-Warn-App:jacocoTestReportDeviceRelease -i" - scan-sonar quick_build_device_for_testers_signed: executor: android/android resource_class: xlarge working_directory: ~/project steps: - checkout - restore-gradle-cache - restore-android-build-cache - run: name: Download Keystore command: | curl --header "Authorization: token $keystore_download_token" --header "Accept: application/vnd.github.v3.raw" --remote-name --location "$keystore_download_url$keystore_download_filename" - run: name: Download Environment Properties command: | curl --header "Authorization: token $keystore_download_token" --header "Accept: application/vnd.github.v3.raw" --remote-name --location "$environments_download_url$env_prop_download_filename" - run: name: Decrypt Keystore command: openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 -in $keystore_download_filename -out $keystore_filename -k $keystore_encrypt_key - run: name: Prepare commit hash command: | commit_hash=$(echo $CIRCLE_SHA1 | cut -c1-5) echo $commit_hash echo "" >> "./gradle.properties" echo "commit_hash=$commit_hash" >> "./gradle.properties" - run: name: Prepare keystore properties for Signing command: | echo "" >> "./keystore.properties" echo "deviceForTestersRelease.storePath=../$keystore_filename" >> "./keystore.properties" echo "deviceForTestersRelease.storePassword=$keystore_password" >> "./keystore.properties" echo "deviceForTestersRelease.keyAlias=$key_alias" >> "./keystore.properties" echo "deviceForTestersRelease.keyPassword=$key_password" >> "./keystore.properties" - run-gradle-cmd: desc: Quick Build cmd: ":Corona-Warn-App:assembleDeviceForTestersRelease" - save-gradle-cache - save-android-build-cache - store_artifacts: path: Corona-Warn-App/build/outputs/apk destination: apk - store_artifacts: path: Corona-Warn-App/build/reports destination: reports - run: name: Send to T-System command: | fileName=$(find Corona-Warn-App/build/outputs/apk/deviceForTesters/release -name '*Corona-Warn-App*.apk') curl --location --request POST $tsystems_upload_url \ --header "Authorization: Bearer $tsystems_upload_bearer" \ --form "file=@${fileName}" \ instrumentation_tests_device: resource_class: xlarge docker: - image: circleci/android:api-28 steps: - skip-for-external-pull-requests - setup-google-cloud - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: desc: Build app APK cmd: ":Corona-Warn-App:assembleDeviceForTestersDebug" - run-gradle-cmd: desc: Build instrumentation test APK cmd: ":Corona-Warn-App:assembleDeviceForTestersDebugAndroidTest" - run: name: Setup Testlab environment command: | echo "export BUCKETDIR=\"`date "+%Y-%m-%d-%H:%M:%S:%3N"`-${RANDOM}\"" >> $BASH_ENV source $BASH_ENV echo "$BUCKETDIR is setup." - run: name: Test with Firebase Test Lab command: | echo "Using bucketdir $BUCKETDIR" sudo gcloud firebase test android run \ --type instrumentation \ --app Corona-Warn-App/build/outputs/apk/deviceForTesters/debug/*.apk \ --test Corona-Warn-App/build/outputs/apk/androidTest/deviceForTesters/debug/*.apk \ --results-dir ${BUCKETDIR} \ --results-bucket ${GOOGLE_PROJECT_ID}-circleci-android \ --environment-variables clearPackageData=true \ --test-targets "notAnnotation testhelpers.Screenshot" \ --timeout 20m \ --device-ids flame \ --os-version-ids 29 \ --locales de_DE \ --orientations portrait \ --no-record-video no_output_timeout: 30m - run: name: Create directory to store test results command: mkdir firebase-results when: always - run: name: Install gsutil dependency and copy test results data command: | sudo pip install -U crcmod sudo gsutil -m cp -R -U gs://${GOOGLE_PROJECT_ID}-circleci-android/${BUCKETDIR}/flame* firebase-results when: always - store_test_results: path: ./firebase-results/flame-29-de_DE-portrait - compress-path: input: ./firebase-results/flame-29-de_DE-portrait/*.xml output: /tmp/instrumentation_tests_device.zip - store_artifacts: path: /tmp/instrumentation_tests_device.zip destination: zips/instrumentation_tests_device.zip # Keep it until Firebase Test Lab proves reliability device_screenshots: macos: xcode: "12.3.0" resource_class: large steps: - checkout - restore_cache: key: gem-cache-v1-{{ arch }}-{{ checksum "Gemfile.lock" }} - run: bundle check || bundle install --path vendor/bundle - save_cache: key: gem-cache-v1-{{ arch }}-{{ checksum "Gemfile.lock" }} paths: - vendor/bundle - setup-android-macos - restore-gradle-cache - restore-android-build-cache-macos - run-gradle-cmd: desc: Build APKs for screenshots cmd: > :Corona-Warn-App:assembleDebug :Corona-Warn-App:assembleAndroidTest - save-gradle-cache - save-android-build-cache-macos - run-emulator-for-api: apilevel: 29 - run: name: Run fastlane screengrab command: | for i in {1..5}; do bundle exec fastlane screengrab && break || sleep 15; done no_output_timeout: 30m - kill-all-emulators - store_artifacts: path: fastlane/metadata/android destination: screenshots - compress-path: input: ./fastlane/metadata/android output: /tmp/device_screenshots.zip - store_artifacts: path: /tmp/device_screenshots.zip destination: zips/device_screenshots.zip firebase_screenshots: resource_class: xlarge docker: - image: circleci/android:api-28 steps: - skip-for-external-pull-requests - setup-google-cloud - checkout - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: desc: Build APKs for screenshots cmd: > :Corona-Warn-App:assembleDebug :Corona-Warn-App:assembleAndroidTest - run: name: Setup Testlab environment command: | echo "export BUCKETDIR=\"`date "+%Y-%m-%d-%H:%M:%S:%3N"`-${RANDOM}\"" >> $BASH_ENV source $BASH_ENV echo "$BUCKETDIR is setup." - run: name: Test with Firebase Test Lab command: | echo "Using bucketdir $BUCKETDIR" sudo gcloud firebase test android run \ --type instrumentation \ --app Corona-Warn-App/build/outputs/apk/deviceForTesters/debug/*.apk \ --test Corona-Warn-App/build/outputs/apk/androidTest/deviceForTesters/debug/*.apk \ --results-dir ${BUCKETDIR} \ --results-bucket ${GOOGLE_PROJECT_ID}-circleci-android \ --environment-variables clearPackageData=true \ --test-targets "annotation testhelpers.Screenshot" \ --timeout 20m \ --device-ids flame \ --os-version-ids 29 \ --locales de_DE,en_US \ --orientations portrait \ --no-record-video no_output_timeout: 30m - run: name: Create directory to store test results command: mkdir firebase-screenshots when: always - run: name: Install gsutil dependency and copy test results data command: | sudo pip install -U crcmod sudo gsutil -m cp -R -U gs://${GOOGLE_PROJECT_ID}-circleci-android/${BUCKETDIR}/flame* firebase-screenshots sudo chown -R circleci:circleci firebase-screenshots when: always - store_test_results: path: ./firebase-screenshots - run: name: Clean up pulled bucket command: | rm -rf firebase-screenshots/*/artifacts/sdcard rm -rf firebase-screenshots/*/test_cases rm -f firebase-screenshots/*/instrumentation.results rm -f firebase-screenshots/*/logcat rm -f firebase-screenshots/*/test_result_1.xml mkdir /tmp/screenshots - compress-path: input: ./firebase-screenshots output: /tmp/screenshots/CWA-Android-Screenshots-${CIRCLE_SHA1}.zip - store_artifacts: path: /tmp/screenshots destination: zips/ ####################### # Workflow section # Job execution orders ####################### workflows: version: 2 check_buildtype_device: jobs: - detekt - lint_device_release_check - ktlint_check - quick_build_device_release_no_tests - unit_tests_device_release - run_sonar: requires: - unit_tests_device_release - instrumentation_tests_device: requires: - ktlint_check - detekt check_buildtype_device_for_testers: jobs: - detekt - lint_device_for_testers_release_check - ktlint_check - quick_build_device_for_testers_release_no_tests - unit_tests_device_for_testers_release signed_build: jobs: - quick_build_device_for_testers_signed: filters: tags: only: - /^v.*/ branches: ignore: /.*/ - firebase_screenshots: filters: tags: only: - /^v.*/ branches: ignore: /.*/