diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3bf98aea8..db0ba4674 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,20 +18,20 @@ jobs: with: java-version: 1.8 - - name: Build APK & Run small tests - run: android/build.sh build + - name: Build & Run small tests + run: ./build.sh build - name: Upload APK uses: actions/upload-artifact@v2 with: name: debug-apk - path: android/build/*apk + path: uhabits-android/build/*apk - name: Upload build outputs uses: actions/upload-artifact@v2 with: name: build - path: android/uhabits-android/build/outputs/ + path: uhabits-android/uhabits-android/build/outputs/ test: needs: build @@ -47,10 +47,10 @@ jobs: uses: actions/download-artifact@v2 with: name: build - path: android/uhabits-android/build/outputs/ + path: uhabits-android/uhabits-android/build/outputs/ - name: Run medium tests uses: ReactiveCircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} - script: android/build.sh medium-tests + script: ./build.sh medium-tests diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bfd0d0eeb..65ba841a7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,32 +11,40 @@ jobs: env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true steps: + - uses: actions/checkout@v1 + - name: Install GPG uses: olafurpg/setup-gpg@v2 + - name: Decrypt secrets env: GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} run: .secret/decrypt.sh + - name: Install Java Development Kit 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 + - name: Build APK & Run small tests env: RELEASE: 1 - run: android/build.sh build + run: ./build.sh build + - name: Run medium tests uses: ReactiveCircus/android-emulator-runner@v2.2.0 env: RELEASE: 1 with: api-level: 29 - script: android/build.sh medium-tests + script: ./build.sh medium-tests + - name: Upload build to GitHub uses: actions/upload-artifact@v1 with: name: Build - path: android/uhabits-android/build/outputs/ + path: uhabits-android/uhabits-android/build/outputs/ + - name: Upload APK to Google Play - run: cd android && ./gradlew publishReleaseApk + run: cd uhabits-android && ./gradlew publishReleaseApk diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..f8bbe1175 --- /dev/null +++ b/build.sh @@ -0,0 +1,311 @@ +#!/bin/bash +# Copyright (C) 2016-2021 Álinson Santos Xavier +# This file is part of Loop Habit Tracker. +# +# Loop Habit Tracker is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# Loop Habit Tracker is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . + +cd "$(dirname "$0")" + +ADB="${ANDROID_HOME}/platform-tools/adb" +EMULATOR="${ANDROID_HOME}/tools/emulator" +AVDMANAGER="${ANDROID_HOME}/tools/bin/avdmanager" +AVDNAME="uhabitsTest" +GRADLE="./gradlew --stacktrace --quiet" +PACKAGE_NAME=org.isoron.uhabits +ANDROID_OUTPUTS_DIR="uhabits-android/uhabits-android/build/outputs" +VERSION=$(cat uhabits-android/gradle.properties | grep VERSION_NAME | sed -e 's/.*=//g;s/ //g') + +if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then + echo "Error: ANDROID_HOME is not set correctly" + exit 1 +fi + + +# Logging +# ----------------------------------------------------------------------------- + +log_error() { + local COLOR='\033[1;31m' + local NC='\033[0m' + echo -e "$COLOR* $1 $NC" +} + +log_info() { + local COLOR='\033[1;32m' + local NC='\033[0m' + echo -e "$COLOR* $1 $NC" +} + +fail() { + log_error "BUILD FAILED" + exit 1 +} + + +# Core +# ----------------------------------------------------------------------------- + +ktlint() { + log_info "Running ktlint..." + cd uhabits-android + $GRADLE ktlintCheck || fail + cd .. +} + +build_core() { + log_info "Building uhabits-core..." + cd uhabits-core + $GRADLE build || fail + cd .. +} + + +# Android +# ----------------------------------------------------------------------------- + +run_adb_as_root() { + log_info "Running adb as root..." + $ADB root +} + +build_apk() { + cd uhabits-android + + if [ ! -z $RELEASE ]; then + log_info "Reading secret..." + source ../.secret/env || fail + fi + + log_info "Removing old APKs..." + rm -vf build/*.apk + + if [ ! -z $RELEASE ]; then + log_info "Building release APK..." + $GRADLE assembleRelease + cp -v \ + uhabits-android/build/outputs/apk/release/uhabits-android-release.apk \ + build/loop-$VERSION-release.apk + fi + + log_info "Building debug APK..." + $GRADLE assembleDebug --stacktrace || fail + cp -v \ + uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk \ + build/loop-$VERSION-debug.apk + + cd .. +} + +build_instrumentation_apk() { + log_info "Building instrumentation APK..." + cd uhabits-android + if [ ! -z $RELEASE ]; then + $GRADLE assembleAndroidTest \ + -Pandroid.injected.signing.store.file=$LOOP_KEY_STORE \ + -Pandroid.injected.signing.store.password=$LOOP_STORE_PASSWORD \ + -Pandroid.injected.signing.key.alias=$LOOP_KEY_ALIAS \ + -Pandroid.injected.signing.key.password=$LOOP_KEY_PASSWORD || fail + else + $GRADLE assembleAndroidTest || fail + fi + cd .. +} + +uninstall_apk() { + log_info "Uninstalling existing APK..." + $ADB uninstall ${PACKAGE_NAME} +} + +install_test_butler() { + log_info "Installing Test Butler..." + $ADB uninstall com.linkedin.android.testbutler + $ADB install uhabits-android/tools/test-butler-app-2.0.2.apk +} + +install_apk() { + log_info "Installing APK..." + if [ ! -z $RELEASE ]; then + $ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || fail + else + $ADB install -t -r ${ANDROID_OUTPUTS_DIR}/apk/debug/uhabits-android-debug.apk || fail + fi +} + +install_test_apk() { + log_info "Uninstalling existing test APK..." + $ADB uninstall ${PACKAGE_NAME}.test + + log_info "Installing test APK..." + $ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || fail +} + +run_instrumented_tests() { + SIZE=$1 + log_info "Running instrumented tests..." + $ADB shell am instrument \ + -r -e coverage true -e size $SIZE \ + -w ${PACKAGE_NAME}.test/androidx.test.runner.AndroidJUnitRunner \ + | tee ${ANDROID_OUTPUTS_DIR}/instrument.txt + + if grep "\(INSTRUMENTATION_STATUS_CODE.*-1\|FAILURES\)" $ANDROID_OUTPUTS_DIR/instrument.txt; then + log_error "Some instrumented tests failed" + fetch_logcat + exit 1 + fi +} + +fetch_logcat() { + log_info "Fetching logcat..." + $ADB logcat -d > ${ANDROID_OUTPUTS_DIR}/logcat.txt +} + +uninstall_test_apk() { + log_info "Uninstalling test APK..." + $ADB uninstall ${PACKAGE_NAME}.test +} + +remove_avd() { + log_info "Removing AVD..." + $AVDMANAGER delete avd --name $AVDNAME +} + +create_avd() { + API=$1 + log_info "Creating AVD..." + $AVDMANAGER create avd \ + --name $AVDNAME \ + --package "system-images;android-$API;default;x86_64" \ + --device pixel_xl || fail +} + +wait_for_device() { + log_info "Waiting for device..." + adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82' + sleep 15 +} + +run_avd() { + log_info "Launching emulator..." + $EMULATOR @$AVDNAME & + wait_for_device +} + +stop_avd() { + log_info "Stopping emulator..." + # https://stackoverflow.com/a/38652520 + adb devices | grep emulator | cut -f1 | while read line; do + adb -s $line emu kill + done + while [[ ! -z $(pgrep emulator) ]]; do sleep 1; done +} + +run_tests() { + SIZE=$1 + run_adb_as_root + install_test_butler + uninstall_apk + install_apk + install_test_apk + run_instrumented_tests $SIZE + fetch_logcat + uninstall_test_apk +} + +build_android() { + log_info "Building uhabits-android..." + build_apk + build_instrumentation_apk +} + +parse_opts() { + OPTS=`getopt -o r --long release -n 'build.sh' -- "$@"` + if [ $? != 0 ] ; then exit 1; fi + eval set -- "$OPTS" + + while true; do + case "$1" in + -r | --release ) RELEASE=1; shift ;; + * ) break ;; + esac + done +} + +remove_build_dir() { + rm -rfv uhabits-core/build + rm -rfv uhabits-web/node_modules/upath/build + rm -rfv uhabits-web/node_modules/core-js/build + rm -rfv uhabits-web/build + rm -rfv uhabits-core-legacy/build + rm -rfv uhabits-server/build + rm -rfv uhabits-android/build + rm -rfv uhabits-android/uhabits-android/build + rm -rfv uhabits-android/android-pickers/build + rm -rfv uhabits-web/node_modules +} + +main() { + case "$1" in + build) + shift; parse_opts $* + ktlint + build_core + build_android + ;; + + medium-tests) + shift; parse_opts $* + for attempt in {1..3}; do + (run_tests medium) && exit 0 + done + exit 1 + ;; + + large-tests) + shift; parse_opts $* + stop_avd + remove_avd + for api in 28; do + create_avd $api + run_avd + run_tests large + stop_avd + remove_avd + done + ;; + + clean) + remove_build_dir + ;; + + *) + cat < [options] +Builds and tests Loop Habit Tracker + +Commands: + build Build the app + clean Remove all build directories + large-tests Run large-sized tests on connected device + medium-tests Run medium-sized tests on connected device + +Options: + -r --release Build and test release version, instead of debug +END + exit 1 + ;; + esac +} + +main $* + diff --git a/uhabits-android/build.sh b/uhabits-android/build.sh deleted file mode 100755 index a6f23af76..000000000 --- a/uhabits-android/build.sh +++ /dev/null @@ -1,331 +0,0 @@ -#!/bin/bash -# Copyright (C) 2017 Álinson Santos Xavier -# This file is part of Loop Habit Tracker. -# -# Loop Habit Tracker is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# Loop Habit Tracker is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see . - -cd "$(dirname "$0")" - -ADB="${ANDROID_HOME}/platform-tools/adb" -EMULATOR="${ANDROID_HOME}/tools/emulator" -AVDMANAGER="${ANDROID_HOME}/tools/bin/avdmanager" -AVDNAME="uhabitsTest" -GRADLE="./gradlew --stacktrace" -PACKAGE_NAME=org.isoron.uhabits -OUTPUTS_DIR=uhabits-android/build/outputs -VERSION=$(cat gradle.properties | grep VERSION_NAME | sed -e 's/.*=//g;s/ //g') - -if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then - echo "Error: ANDROID_HOME is not set correctly" - exit 1 -fi - -log_error() { - if [ ! -z "$TEAMCITY_VERSION" ]; then - echo "###teamcity[progressMessage '$1']" - else - local COLOR='\033[1;31m' - local NC='\033[0m' - echo -e "$COLOR>>> $1 $NC" - fi -} - -log_info() { - if [ ! -z "$TEAMCITY_VERSION" ]; then - echo "###teamcity[progressMessage '$1']" - else - local COLOR='\033[1;32m' - local NC='\033[0m' - echo -e "$COLOR>>> $1 $NC" - fi -} - -fail() { - log_error "BUILD FAILED" - exit 1 -} - -if [ ! -z $RELEASE ]; then - log_info "Reading secret env variables from ../.secret/env" - source ../.secret/env || fail -fi - - -run_adb_as_root() { - log_info "Running adb as root" - $ADB root -} - -lint() { - log_info "Running ktlint..." - $GRADLE ktlintCheck || fail -} - -build_apk() { - log_info "Removing old APKs..." - rm -vf build/*.apk - - if [ ! -z $RELEASE ]; then - log_info "Building release APK" - $GRADLE assembleRelease - cp -v uhabits-android/build/outputs/apk/release/uhabits-android-release.apk build/loop-$VERSION-release.apk - fi - - log_info "Building debug APK" - $GRADLE assembleDebug --stacktrace || fail - cp -v uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk build/loop-$VERSION-debug.apk -} - -build_instrumentation_apk() { - log_info "Building instrumentation APK" - if [ ! -z $RELEASE ]; then - $GRADLE assembleAndroidTest \ - -Pandroid.injected.signing.store.file=$LOOP_KEY_STORE \ - -Pandroid.injected.signing.store.password=$LOOP_STORE_PASSWORD \ - -Pandroid.injected.signing.key.alias=$LOOP_KEY_ALIAS \ - -Pandroid.injected.signing.key.password=$LOOP_KEY_PASSWORD || fail - else - $GRADLE assembleAndroidTest || fail - fi -} - -uninstall_apk() { - log_info "Uninstalling existing APK" - $ADB uninstall ${PACKAGE_NAME} -} - -install_test_butler() { - log_info "Installing Test Butler" - $ADB uninstall com.linkedin.android.testbutler - $ADB install tools/test-butler-app-2.0.2.apk -} - -install_apk() { - log_info "Installing APK" - if [ ! -z $RELEASE ]; then - $ADB install -r ${OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || fail - else - $ADB install -t -r ${OUTPUTS_DIR}/apk/debug/uhabits-android-debug.apk || fail - fi -} - -install_test_apk() { - log_info "Uninstalling existing test APK" - $ADB uninstall ${PACKAGE_NAME}.test - - log_info "Installing test APK" - $ADB install -r ${OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || fail -} - -run_instrumented_tests() { - SIZE=$1 - log_info "Running instrumented tests" - $ADB shell am instrument \ - -r -e coverage true -e size $SIZE \ - -w ${PACKAGE_NAME}.test/androidx.test.runner.AndroidJUnitRunner \ - | tee ${OUTPUTS_DIR}/instrument.txt - - if grep "\(INSTRUMENTATION_STATUS_CODE.*-1\|FAILURES\)" $OUTPUTS_DIR/instrument.txt; then - log_error "Some instrumented tests failed" - fetch_images - fetch_logcat - exit 1 - fi - - #mkdir -p ${OUTPUTS_DIR}/code-coverage/connected/ - #$ADB pull /data/user/0/${PACKAGE_NAME}/files/coverage.ec \ - # ${OUTPUTS_DIR}/code-coverage/connected/ \ - # || log_error "COVERAGE REPORT NOT AVAILABLE" -} - -parse_instrumentation_results() { - log_info "Parsing instrumented test results" - java -jar tools/automator-log-converter-1.5.0.jar ${OUTPUTS_DIR}/instrument.txt || fail -} - -generate_coverage_badge() { - log_info "Generating code coverage badge" - CORE_REPORT=uhabits-core/build/reports/jacoco/test/jacocoTestReport.xml - rm -f ${OUTPUTS_DIR}/coverage-badge.svg - python3 tools/coverage-badge/badge.py -i $CORE_REPORT -o ${OUTPUTS_DIR}/coverage-badge -} - -fetch_logcat() { - log_info "Fetching logcat" - $ADB logcat -d > ${OUTPUTS_DIR}/logcat.txt -} - -run_jvm_tests() { - log_info "Running JVM tests" - if [ ! -z $RELEASE ]; then - $GRADLE testReleaseUnitTest :uhabits-core:check || fail - else - $GRADLE testDebugUnitTest :uhabits-core:check || fail - fi -} - -uninstall_test_apk() { - log_info "Uninstalling test APK" - $ADB uninstall ${PACKAGE_NAME}.test -} - -fetch_images() { - log_info "Fetching images" - rm -rf $OUTPUTS_DIR/test-screenshots - $ADB pull /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/ $OUTPUTS_DIR - $ADB shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/ -} - -accept_images() { - find $OUTPUTS_DIR/test-screenshots -name '*.expected*' -delete - rsync -av $OUTPUTS_DIR/test-screenshots/ uhabits-android/src/androidTest/assets/ -} - -run_tests() { - SIZE=$1 - run_adb_as_root - install_test_butler - uninstall_apk - install_apk - install_test_apk - run_instrumented_tests $SIZE - parse_instrumentation_results - fetch_logcat - uninstall_test_apk -} - -parse_opts() { - OPTS=`getopt -o r --long release -n 'build.sh' -- "$@"` - if [ $? != 0 ] ; then exit 1; fi - eval set -- "$OPTS" - - while true; do - case "$1" in - -r | --release ) RELEASE=1; shift ;; - * ) break ;; - esac - done -} - -remove_build_dir() { - rm -rfv .gradle - rm -rfv build - rm -rfv android-base/build - rm -rfv android-pickers/build - rm -rfv uhabits-android/build - rm -rfv uhabits-core/build -} - -remove_avd() { - log_info "Removing avd" - $AVDMANAGER delete avd --name $AVDNAME -} - -create_avd() { - API=$1 - log_info "Creating avd" - $AVDMANAGER create avd \ - --name $AVDNAME \ - --package "system-images;android-$API;default;x86_64" \ - --device pixel_xl || fail -} - -wait_for_device() { - log_info "Waiting for device..." - adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82' -} - -run_avd() { - log_info "Launching emulator" - $EMULATOR @$AVDNAME & - wait_for_device -} - -stop_avd() { - log_info "Stopping emulator..." - # https://stackoverflow.com/a/38652520 - adb devices | grep emulator | cut -f1 | while read line; do adb -s $line emu kill; done - while [[ ! -z $(pgrep emulator) ]]; do sleep 1; done -} - -case "$1" in - build) - shift; parse_opts $* - - lint - build_apk - build_instrumentation_apk - run_jvm_tests - #generate_coverage_badge - ;; - - medium-tests) - shift; parse_opts $* - for attempt in {1..3}; do - (run_tests medium) && exit 0 - done - exit 1 - ;; - - large-tests) - shift; parse_opts $* - stop_avd - remove_avd - for api in 28; do - create_avd $api - run_avd - run_tests large - stop_avd - remove_avd - done - ;; - - fetch-images) - fetch_images - ;; - - accept-images) - accept_images - ;; - - install) - shift; parse_opts $* - build_apk - install_apk - ;; - - clean) - remove_build_dir - ;; - - *) -cat < [options] -Builds, installs and tests Loop Habit Tracker - -Commands: - accept-images Copies fetched images to corresponding assets folder - build Build APK and run JVM tests - clean Remove build directory - fetch-images Fetches failed view test images from device - install Install app on connected device - large-tests Run large-sized tests on connected device - medium-tests Run medium-sized tests on connected device - -Options: - -r --release Build and test release APK, instead of debug -END - exit 1 - ;; -esac