TiagoMSSantos/MobileRT

View on GitHub
.github/workflows/reusable-android.yml

Summary

Maintainability
Test Coverage
name: Reusable Android workflow

on:
  workflow_call:
    inputs:
      host_os:
        type: string
        required: true
      android_api:
        type: string
        required: true
      type:
        type: string
        required: true

defaults:
  run:
    shell: sh
    working-directory: .

# Default environment variables.
env:
  GITHUB_STEP_TIMEOUT_SMALL: 4
  GITHUB_STEP_TIMEOUT_MEDIUM: 10
  GITHUB_STEP_TIMEOUT_LONG: 20

jobs:
  Build:
    runs-on: ${{ inputs.host_os }}
    name: Build ${{ inputs.android_api }} ${{ inputs.type }} (${{ inputs.host_os }})
    timeout-minutes: 360
    steps:
      - name: Checkout
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/checkout@v4

      - name: Set up JDK
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/setup-java@v4
        with:
          # Check available parameters in: https://github.com/actions/setup-java/blob/main/action.yml
          java-version: 21
          distribution: zulu
          java-package: jdk
          architecture: x64
          check-latest: false
          server-id: github
          server-username: GITHUB_ACTOR
          server-password: GITHUB_TOKEN
          settings-path: ~/.gradle
          overwrite-settings: true
          gpg-private-key: ''
          gpg-passphrase: GPG_PASSPHRASE
          cache: gradle
          cache-dependency-path: '**/build.gradle'

      - name: Add custom environment variables
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        working-directory: .
        run: |
          echo 'Available Android versions:';
          du -h -d 1 ${ANDROID_HOME}/ndk;
          du -h -d 1 ${ANDROID_HOME}/cmake || true;
          du -h -d 1 ${ANDROID_HOME}/build-tools;
          ls -lahp ${ANDROID_HOME}/platforms;
          ls -lahp ${HOME};
          echo "GRADLE_PATH=${HOME}/.gradle" >> "${GITHUB_ENV}";
          df -Pk -h ${HOME};
          env;

      # Useful step to avoid gradle having to download Gradle dependencies
      # Taken from: https://github.com/marketplace/actions/download-workflow-artifact
      - name: Download Gradle packages artifact
        id: download-gradle-artifact
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        uses: dawidd6/action-download-artifact@v7
        continue-on-error: true
        with:
          # Check available parameters in: https://github.com/dawidd6/action-download-artifact/blob/master/action.yml
          # Optional, GitHub token, a Personal Access Token with `public_repo` scope if needed
          # Required, if the artifact is from a different repo
          # Required, if the repo is private a Personal Access Token with `repo` scope is needed
          github_token: ${{ secrets.GITHUB_TOKEN }}
          # Optional, workflow file name or ID
          # If not specified, will be inferred from run_id (if run_id is specified), or will be the current workflow
          workflow: android.yml
          # Optional, the status or conclusion of a completed workflow to search for
          # Can be one of a workflow conclusion:
          #   "failure", "success", "neutral", "cancelled", "skipped", "timed_out", "action_required"
          # Or a workflow status:
          #   "completed", "in_progress", "queued"
          workflow_conclusion: ''
          # Optional, will get head commit SHA
          pr: ${{ github.event.pull_request.number }}
          # Optional, no need to specify if PR is
          commit: ${{ github.event.pull_request.head.sha }}
          # Optional, will use the branch
          # branch: master
          # Optional, defaults to all types
          # event: push
          # Optional, will use specified workflow run
          # run_id: 1122334455
          # Optional, run number from the workflow
          # run_number: 34
          # Optional, uploaded artifact name,
          # will download all artifacts if not specified
          # and extract them into respective subdirectories
          # https://github.com/actions/download-artifact#download-all-artifacts
          name: gradle-packages
          # Optional, a directory where to extract artifact(s), defaults to the current directory
          path: ${{ env.GRADLE_PATH }}
          # Optional, defaults to current repo
          repo: ${{ github.repository }}
          # Optional, check the workflow run to whether it has an artifact
          # then will get the last available artifact from the previous workflow
          # default false, just try to download from the last one
          check_artifacts:  true
          # Optional, search for the last workflow run whose stored an artifact named as in `name` input
          # default false
          search_artifacts: true
          # Optional, choose to skip unpacking the downloaded artifact(s)
          # default false
          skip_unpack: true
          # Optional, choose how to exit the action if no artifact is found
          # can be one of:
          #  "fail", "warn", "ignore"
          # default fail
          if_no_artifact_found: fail

      - name: Extract and check files from gradle artifact
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success() && steps.download-gradle-artifact.outcome == 'success'
        working-directory: .
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && extractFilesFromArtifact ${{ env.GRADLE_PATH }};
          set +e;
          find ${{ env.GRADLE_PATH }} -name "transforms" -exec rm -rf {} \;
          set -e;

      - name: Set Android CPU Architecture
        id: set-cpu-arch
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          set +eu;
          MAJOR_MAC_VERSION=$(sw_vers | grep ProductVersion | cut -d ':' -f2 | cut -d '.' -f1 | tr -d '[:space:]');
          if [ "${MAJOR_MAC_VERSION}" != '' ]; then
            echo "MacOS '${MAJOR_MAC_VERSION}' detected";
          fi
          set -e;
          if [ "${MAJOR_MAC_VERSION}" -gt 13 ]; then
              echo 'android_arch=\"arm64-v8a\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=arm64-v8a' >> "${GITHUB_ENV}";
          else
            if [ "${{ inputs.android_api }}" -gt 20 ]; then
              echo 'android_arch=\"x86_64\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=x86_64' >> "${GITHUB_ENV}";
            else
              echo 'android_arch=\"x86\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=x86' >> "${GITHUB_ENV}";
            fi
          fi
          set -u;

      - name: Download Android CMake
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        run: |
          ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install 'cmake;3.31.1';

      - name: Download Android dependencies
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          sh gradlew build --dry-run -Dorg.gradle.configuration-cache=true --parallel \
            -DtestType="${{ inputs.type }}" -DandroidApiVersion="${{ inputs.android_api }}" -DabiFilters="[${{ env.android_arch }}]" \
            --info --warning-mode all --stacktrace;

      - name: Build ${{ inputs.type }}
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_LONG) }}
        if: success()
        working-directory: .
        run: |
          echo "android_arch: ${{ env.android_arch }}";
          echo "android_emulator_arch: ${{ env.android_emulator_arch }}";
          sudo dmesg || true; # Print Kernel logs to know from which timestamp the new logs will appear.
          sh scripts/compile_android.sh -t ${{ inputs.type }} -r yes -a ${{ inputs.android_api }} -f ${{ env.android_arch }};

      - name: Print error logs
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: failure()
        working-directory: .
        run: |
          ls -lahp .;
          set +e;
          echo 'Print JVM errors if provided by Kernel, and just check the logs from the last line of the previous call:';
          sudo dmesg;
          echo 'Print native JVM stacktrace if possible:';
          cat hs_err_pid*.log;
          echo 'Print problems report if available:';
          cat build/reports/problems/problems-report.html;
          set -e;

      - name: Upload problems reports as artifact
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          # Check available parameters in: https://github.com/actions/upload-artifact/blob/main/action.yml
          name: problems-report
          path: build/reports/problems/problems-report.html
          if-no-files-found: error
          retention-days: 90

      - name: Check binaries' paths
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && checkPathExists app/build;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac/debug;
          . scripts/helper_functions.sh && checkPathExists app/build/tmp/kotlin-classes/debug;
          . scripts/helper_functions.sh && checkPathExists app/.cxx;
          . scripts/helper_functions.sh && checkPathExists app/third_party;

      - name: Upload generated binaries to cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: compiled_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: compiled_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          fail-on-cache-miss: false
          path: |
            app/build
            app/.cxx
            app/third_party

      - name: Zip Gradle packages
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        run: |
          set +e;
          du -h -d 1 ${{ env.GRADLE_PATH }};
          du -h -d 1 ${{ env.GRADLE_PATH }}/caches;
          du -h -d 1 ${{ env.GRADLE_PATH }}/wrapper/dists;
          du -h -d 1 ${{ env.GRADLE_PATH }}/daemon;
          du -h -d 1 ${{ env.GRADLE_PATH }}/notifications;
          du -h -d 1 ${{ env.GRADLE_PATH }}/jdks;
          du -h -d 1 ${{ env.GRADLE_PATH }}/kotlin-profile;
          du -h -d 1 ${{ env.GRADLE_PATH }}/workers;
          du -h -d 1 ${{ env.GRADLE_PATH }}/native;

          echo 'Deleting unnecessary cache.';
          rm -rf ${{ env.GRADLE_PATH }}/caches/build-cache-*;
          rm -rf ${{ env.GRADLE_PATH }}/caches/journal-*;
          rm -rf ${{ env.GRADLE_PATH }}/caches/transforms-4;
          rm -rf ${{ env.GRADLE_PATH }}/daemon;
          rm -rf ${{ env.GRADLE_PATH }}/jdks;
          rm -rf ${{ env.GRADLE_PATH }}/kotlin-profile;
          rm -rf ${{ env.GRADLE_PATH }}/workers;
          rm -rf ${{ env.GRADLE_PATH }}/native;
          rm -rf ${{ env.GRADLE_PATH }}/.tmp;
          rm -rf ${{ env.GRADLE_PATH }}/notifications;

          find ${{ env.GRADLE_PATH }}/wrapper/dists -not -iname '*gradle*.zip' -delete;
          find ${{ env.GRADLE_PATH }} -iname "docs" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.log*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.txt*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.md*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.doc*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.xls*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.htm*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.lock*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.lck*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.ok*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.png*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.jpg*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.jpeg*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -iname "*.gif*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -name "*README*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -name "*NOTICE*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -name "*LICENSE*" -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -name "transforms" -exec rm -rf {} \;

          find ${{ env.GRADLE_PATH }} -empty -exec rm -rf {} \;
          find ${{ env.GRADLE_PATH }} -size 0 -exec rm -rf {} \;
          set -e;

          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && zipFilesForArtifact ${{ env.GRADLE_PATH }} gradle-packages.zip;

      - name: Upload gradle packages to cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: gradle_packages_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: gradle_packages_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          fail-on-cache-miss: false
          path: |
            ${{ env.GRADLE_PATH }}/gradle-packages.zip

      - name: Upload Gradle packages as artifact
        timeout-minutes: 20
        if: success() && !startsWith(inputs.host_os, 'windows') && strategy.job-index == 0
        continue-on-error: true
        uses: actions/upload-artifact@v4
        with:
          # Check available parameters in: https://github.com/actions/upload-artifact/blob/main/action.yml
          name: gradle-packages
          path: ${{ env.GRADLE_PATH }}/gradle-packages.zip
          if-no-files-found: error
          retention-days: 90
          compression-level: 9
          overwrite: true


  AndroidTests:
    needs: [Build]
    name: Android tests ${{ inputs.android_api }} ${{ inputs.type }} (${{ inputs.host_os }})
    runs-on: ${{ inputs.host_os }}
    timeout-minutes: 360
    steps:
      - name: Checkout
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/checkout@v4

      - name: Set up JDK
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/setup-java@v4
        with:
          # Check available parameters in: https://github.com/actions/setup-java/blob/main/action.yml
          java-version: 21
          distribution: zulu
          java-package: jdk
          architecture: x64
          check-latest: false
          server-id: github
          server-username: GITHUB_ACTOR
          server-password: GITHUB_TOKEN
          settings-path: ~/.gradle
          overwrite-settings: true
          gpg-private-key: ''
          gpg-passphrase: GPG_PASSPHRASE
          cache: gradle
          cache-dependency-path: '**/build.gradle'

      - name: Add custom environment variables
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        working-directory: .
        run: |
          echo 'Available Android versions:';
          du -h -d 1 ${ANDROID_HOME}/ndk;
          du -h -d 1 ${ANDROID_HOME}/cmake || true;
          du -h -d 1 ${ANDROID_HOME}/build-tools;
          ls -lahp ${ANDROID_HOME}/platforms;
          ls -lahp ${HOME};
          echo "GRADLE_PATH=${HOME}/.gradle" >> "${GITHUB_ENV}";

      - name: Download generated binaries from cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: compiled_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: compiled_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          fail-on-cache-miss: true
          path: |
            app/build
            app/.cxx
            app/third_party

      - name: Download gradle packages from cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: gradle_packages_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: gradle_packages_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          fail-on-cache-miss: true
          path: |
            ${{ env.GRADLE_PATH }}/gradle-packages.zip

      - name: Extract and check files from gradle artifact
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        working-directory: .
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && extractFilesFromArtifact ${{ env.GRADLE_PATH }};

      - name: Check binaries' paths
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && checkPathExists app/build;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac/debug;
          . scripts/helper_functions.sh && checkPathExists app/build/tmp/kotlin-classes/debug;
          . scripts/helper_functions.sh && checkPathExists app/.cxx;
          . scripts/helper_functions.sh && checkPathExists app/third_party;

      - name: Enable KVM group permissions
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'ubuntu')
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules;
          sudo udevadm control --reload-rules;
          sudo udevadm trigger --name-match=kvm;

      - name: Set Android CPU Architecture
        id: set-cpu-arch
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          set +eu;
          MAJOR_MAC_VERSION=$(sw_vers | grep ProductVersion | cut -d ':' -f2 | cut -d '.' -f1 | tr -d '[:space:]');
          if [ "${MAJOR_MAC_VERSION}" != '' ]; then
            echo "MacOS '${MAJOR_MAC_VERSION}' detected";
          fi
          set -e;
          if [ "${MAJOR_MAC_VERSION}" -gt 13 ]; then
              echo 'android_arch=\"arm64-v8a\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=arm64-v8a' >> "${GITHUB_ENV}";
          else
            if [ "${{ inputs.android_api }}" -gt 20 ]; then
              echo 'android_arch=\"x86_64\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=x86_64' >> "${GITHUB_ENV}";
            else
              echo 'android_arch=\"x86\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=x86' >> "${GITHUB_ENV}";
            fi
          fi
          set -u;

      - name: Set CPU cores
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          echo "android_arch: ${{ env.android_arch }}";
          echo "android_emulator_arch: ${{ env.android_emulator_arch }}";
          . scripts/helper_functions.sh && parallelizeBuild && echo "NCPU_CORES=${NCPU_CORES}" >> "${GITHUB_ENV}";
          mkdir -p ~/.android/avd/Android_Emulator_API_34.avd;

      - name: Download Android dependencies
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          if [ "${{ inputs.type }}" = 'debug' ]; then
            gradle_command='jacocoTestReport';
          else
            gradle_command='connectedAndroidTest';
          fi
          sh gradlew "${gradle_command}" --dry-run -Dorg.gradle.configuration-cache=true --parallel \
            -DtestType="${{ inputs.type }}" -DandroidApiVersion="${{ inputs.android_api }}" -DabiFilters="[${{ env.android_arch }}]" \
            --info --warning-mode all --stacktrace;

      - name: Run Android tests
        timeout-minutes: 30
        if: success() && !startsWith(inputs.host_os, 'windows')
        uses: ReactiveCircus/android-emulator-runner@v2
        env:
          ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL: 1
          MEM_MB: 1024
        with:
          # Check available parameters in: https://github.com/ReactiveCircus/android-emulator-runner/blob/main/action.yml
          # Available CI host machines: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
          # Max api level for 32bit is: 29
          api-level: ${{ inputs.android_api }}
          cmake: 3.31.1
          target: default
          arch: ${{ env.android_emulator_arch }}
          disable-animations: true
          disable-spellchecker: true
          channel: stable
          emulator-options: -no-metrics -no-window -accel on -cores ${{ env.NCPU_CORES }} -memory ${{ env.MEM_MB }} -cache-size 512 -partition-size 800 -ranchu -fixed-scale -skip-adb-auth -gpu swiftshader_indirect -no-audio -no-snapshot -no-snapstorage -no-snapshot-update-time -no-snapshot-save -no-snapshot-load -no-boot-anim -camera-back none -camera-front none -netfast -wipe-data -no-sim -no-passive-gps -no-direct-adb -no-location-ui -no-hidpi-scaling -no-mouse-reposition -no-nested-warnings -verbose
          cores: ${{ env.NCPU_CORES }}
          ram-size: ${{ env.MEM_MB }}M
          heap-size: 512M
          disk-size: 10240M
          sdcard-path-or-size: 2048M
          avd-name: Android_Emulator_API_${{ inputs.android_api }}
          force-avd-creation: true
          disable-linux-hw-accel: auto
          enable-hw-keyboard: false
          emulator-boot-timeout: 400 # It can take more than 5 min to boot, and the tests can take more than 17 min to finish.
          script: |
            sh scripts/run_tests_android.sh -t ${{ inputs.type }} -r all -a ${{ inputs.android_api }} -k false -f ${{ env.android_arch }};
            echo "Run Android tests finished: ${?}";

      - name: Publish Android Test Report
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() || failure() # always run even if the previous step fails
        uses: mikepenz/action-junit-report@v5
        with:
          # Check available parameters in: https://github.com/mikepenz/action-junit-report/blob/main/action.yml
          check_name: 'Android Test Report - API ${{ inputs.android_api }} ${{ inputs.type }} (${{ inputs.host_os }})'
          report_paths: '**/build/**/*-results/**/TEST-*.xml'
          require_tests: true
          require_passed_tests: true
          include_passed: true
          check_retries: true
          fail_on_failure: true

      - name: Check report's paths
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'ubuntu') && inputs.type == 'debug'
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && checkPathExists app/build/reports/jacoco/jacocoTestReport jacocoTestReport.xml;

      - name: Upload reports to cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'ubuntu') && inputs.type == 'debug'
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: reports_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: reports_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          path: |
            app/build/reports

      - name: Add base64 key & Android Build Tools version to environment variables in order to sign APK
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        run: |
          echo "BASE64_KEY=$(openssl base64 < app/MobileRT.jks | tr -d '[:space:]')" >> "${GITHUB_ENV}";
          echo 'ANDROID_BUILD_TOOLS_VERSION=34.0.0' >> "${GITHUB_ENV}";

      - name: Sign APK
        id: sign-apk
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        uses: ilharp/sign-android-release@nightly
        with:
          # Check available parameters in: https://github.com/ilharp/sign-android-release/blob/master/action.yml
          releaseDir: app/build/outputs/apk/${{ inputs.type }}
          signingKey: ${{ env.BASE64_KEY }}
          keyAlias: ${{ secrets.KEY_ALIAS }}
          keyStorePassword: ${{ secrets.SIGNING_KEY }}
          keyPassword: ${{ secrets.SIGNING_KEY }}
          buildToolsVersion: ${{ env.ANDROID_BUILD_TOOLS_VERSION }}

      - name: Upload APK as artifact
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && (inputs.android_api == '15' || inputs.android_api == '21')
        uses: actions/upload-artifact@v4
        with:
          # Check available parameters in: https://github.com/actions/upload-artifact/blob/main/action.yml
          name: MobileRT_${{ inputs.type }}_android_api-${{ inputs.android_api }}_exp-apk
          path: ${{ steps.sign-apk.outputs.signedFile }}
          if-no-files-found: error
          retention-days: 90


  UnitTests:
    needs: [Build]
    runs-on: ${{ inputs.host_os }}
    timeout-minutes: 360
    name: Unit tests ${{ inputs.android_api }} ${{ inputs.type }} (${{ inputs.host_os }})
    steps:
      - name: Checkout
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/checkout@v4

      - name: Set up JDK
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/setup-java@v4
        with:
          # Check available parameters in: https://github.com/actions/setup-java/blob/main/action.yml
          java-version: 21
          distribution: zulu
          java-package: jdk
          architecture: x64
          check-latest: false
          server-id: github
          server-username: GITHUB_ACTOR
          server-password: GITHUB_TOKEN
          settings-path: ~/.gradle
          overwrite-settings: true
          gpg-private-key: ''
          gpg-passphrase: GPG_PASSPHRASE
          cache: gradle
          cache-dependency-path: '**/build.gradle'

      - name: Add custom environment variables
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        working-directory: .
        run: |
          echo 'Available Android versions:';
          du -h -d 1 ${ANDROID_HOME}/ndk;
          du -h -d 1 ${ANDROID_HOME}/cmake || true;
          du -h -d 1 ${ANDROID_HOME}/build-tools;
          ls -lahp ${ANDROID_HOME}/platforms;
          ls -lahp ${HOME};
          echo "GRADLE_PATH=${HOME}/.gradle" >> "${GITHUB_ENV}";

      - name: Download gradle packages from cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: gradle_packages_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: gradle_packages_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          fail-on-cache-miss: true
          path: |
            ${{ env.GRADLE_PATH }}/gradle-packages.zip

      - name: Extract and check files from gradle artifact
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        working-directory: .
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && extractFilesFromArtifact ${{ env.GRADLE_PATH }};

      - name: Download generated binaries from cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: compiled_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: compiled_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          fail-on-cache-miss: true
          path: |
            app/build
            app/.cxx
            app/third_party

      - name: Check binaries' paths
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && checkPathExists app/build;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac/debug;
          . scripts/helper_functions.sh && checkPathExists app/build/tmp/kotlin-classes/debug;
          . scripts/helper_functions.sh && checkPathExists app/.cxx;
          . scripts/helper_functions.sh && checkPathExists app/third_party;

      - name: Set Android CPU Architecture
        id: set-cpu-arch
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          set +eu;
          MAJOR_MAC_VERSION=$(sw_vers | grep ProductVersion | cut -d ':' -f2 | cut -d '.' -f1 | tr -d '[:space:]');
          if [ "${MAJOR_MAC_VERSION}" != '' ]; then
            echo "MacOS '${MAJOR_MAC_VERSION}' detected";
          fi
          set -e;
          if [ "${MAJOR_MAC_VERSION}" -gt 13 ]; then
              echo 'android_arch=\"arm64-v8a\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=arm64-v8a' >> "${GITHUB_ENV}";
          else
            if [ "${{ inputs.android_api }}" -gt 20 ]; then
              echo 'android_arch=\"x86_64\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=x86_64' >> "${GITHUB_ENV}";
            else
              echo 'android_arch=\"x86\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=x86' >> "${GITHUB_ENV}";
            fi
          fi
          set -u;

      - name: Download Android dependencies
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          sh gradlew test"${{ inputs.type }}"UnitTest lint --dry-run -Dorg.gradle.configuration-cache=true --parallel \
            -DtestType="${{ inputs.type }}" -DandroidApiVersion="${{ inputs.android_api }}" -DabiFilters="[${{ env.android_arch }}]" \
            --info --warning-mode all --stacktrace;

      - name: Run unit tests Java
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        working-directory: .
        run: |
          echo "android_arch: ${{ env.android_arch }}";
          echo "android_emulator_arch: ${{ env.android_emulator_arch }}";
          sh scripts/run_tests.sh -t ${{ inputs.type }} -a ${{ inputs.android_api }} -f ${{ env.android_arch }};

      - name: Run linter
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        working-directory: .
        run: |
          sh scripts/check_android.sh -a ${{ inputs.android_api }} -f ${{ env.android_arch }};

      - name: Publish Unit Test Report
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() || failure() # always run even if the previous step fails
        uses: mikepenz/action-junit-report@v5
        with:
          # Check available parameters in: https://github.com/mikepenz/action-junit-report/blob/main/action.yml
          check_name: 'Unit Test Report - API ${{ inputs.android_api }} ${{ inputs.type }} (${{ inputs.host_os }})'
          report_paths: '**/build/**/*-results/**/TEST-*.xml'
          require_tests: true
          require_passed_tests: true
          include_passed: true
          check_retries: true
          fail_on_failure: true


  Sonar:
    needs: [AndroidTests]
    if: inputs.type == 'debug'
    name: Code Coverage & Sonar ${{ inputs.type }} (${{ inputs.host_os }})
    runs-on: ${{ inputs.host_os }}
    timeout-minutes: 360
    steps:
      - name: Checkout
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/checkout@v4

      - name: Unshallow repository
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          git fetch --unshallow;

      - name: Set up JDK
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/setup-java@v4
        with:
          # Check available parameters in: https://github.com/actions/setup-java/blob/main/action.yml
          # Sonar recommended JVM: https://docs.sonarqube.org/latest/requirements/prerequisites-and-overview/
          java-version: 21
          distribution: zulu
          java-package: jdk
          architecture: x64
          check-latest: false
          server-id: github
          server-username: GITHUB_ACTOR
          server-password: GITHUB_TOKEN
          settings-path: ~/.gradle
          overwrite-settings: true
          gpg-private-key: ''
          gpg-passphrase: GPG_PASSPHRASE
          cache: gradle
          cache-dependency-path: '**/build.gradle'

      - name: Add custom environment variables
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        working-directory: .
        run: |
          echo 'Available Android versions:';
          du -h -d 1 ${ANDROID_HOME}/ndk;
          du -h -d 1 ${ANDROID_HOME}/cmake || true;
          du -h -d 1 ${ANDROID_HOME}/build-tools;
          ls -lahp ${ANDROID_HOME}/platforms;
          ls -lahp ${HOME};
          echo "GRADLE_PATH=${HOME}/.gradle" >> "${GITHUB_ENV}";

      - name: Download gradle packages from cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: gradle_packages_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: gradle_packages_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          fail-on-cache-miss: true
          path: |
            ${{ env.GRADLE_PATH }}/gradle-packages.zip

      - name: Extract and check files from gradle artifact
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success() && !startsWith(inputs.host_os, 'windows')
        working-directory: .
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && extractFilesFromArtifact ${{ env.GRADLE_PATH }};

      - name: Download generated binaries from cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: compiled_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: compiled_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          fail-on-cache-miss: true
          path: |
            app/build
            app/.cxx
            app/third_party

      - name: Check binaries' paths
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && checkPathExists app/build;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac/debug;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac/debugUnitTest;
          . scripts/helper_functions.sh && checkPathExists app/build/intermediates/javac/debugAndroidTest;
          . scripts/helper_functions.sh && checkPathExists app/build/tmp/kotlin-classes/debug;
          . scripts/helper_functions.sh && checkPathExists app/.cxx;
          . scripts/helper_functions.sh && checkPathExists app/third_party;

      - name: Create report's folders
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        run: |
          mkdir -p app/build/reports;

      - name: Download reports from cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'ubuntu')
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: reports_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          restore-keys: reports_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.android_api }}_${{ inputs.type }}_${{ inputs.host_os }}
          path: |
            app/build/reports

      - name: Check files from reports cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && checkPathExists app/build/reports/jacoco/jacocoTestReport jacocoTestReport.xml;

      - name: Send code climate report
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: app/src/main/java
        env:
          CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
        run: |
          # shellcheck disable=SC1091
          . ${{ github.workspace }}/scripts/helper_functions.sh && prepareBinaries ${{ github.workspace }};
          ../../../../test-reporter-latest-linux-amd64 format-coverage -t jacoco ../../../build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml;
          ../../../../test-reporter-latest-linux-amd64 upload-coverage;

      - name: Validate codecov report
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        run: |
          curl --retry 5 --retry-delay 2 --connect-timeout 2 --data-binary @codecov.yml https://codecov.io/validate;

      - name: Set Android CPU Architecture
        id: set-cpu-arch
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          set +eu;
          MAJOR_MAC_VERSION=$(sw_vers | grep ProductVersion | cut -d ':' -f2 | cut -d '.' -f1 | tr -d '[:space:]');
          if [ "${MAJOR_MAC_VERSION}" != '' ]; then
            echo "MacOS '${MAJOR_MAC_VERSION}' detected";
          fi
          set -e;
          if [ "${MAJOR_MAC_VERSION}" -gt 13 ]; then
              echo 'android_arch=\"arm64-v8a\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=arm64-v8a' >> "${GITHUB_ENV}";
          else
            if [ "${{ inputs.android_api }}" -gt 20 ]; then
              echo 'android_arch=\"x86_64\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=x86_64' >> "${GITHUB_ENV}";
            else
              echo 'android_arch=\"x86\"' >> "${GITHUB_ENV}";
              echo 'android_emulator_arch=x86' >> "${GITHUB_ENV}";
            fi
          fi
          set -u;

      - name: Download Android dependencies
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        run: |
          sh gradlew --dry-run -Dorg.gradle.configuration-cache=true --parallel --profile \
            -DabiFilters="[ ${{ env.android_arch }} ]" \
            -DandroidApiVersion=${{ inputs.android_api }} \
            --info --warning-mode all --stacktrace sonar;

      - name: Analyze Sonar
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
          # https://docs.sonarqube.org/latest/requirements/prerequisites-and-overview/
          GRADLE_OPTS: -Xms8G -Xmx8G -XX:ActiveProcessorCount=5
        run: |
          sh gradlew --offline --profile --parallel \
            -DabiFilters="[ ${{ env.android_arch }} ]" \
            -DandroidApiVersion=${{ inputs.android_api }} \
            --info --warning-mode all --stacktrace sonar;

      - name: Check sonar path
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && checkPathExists ~/.sonar;

      - name: Send codecov report
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
        run: |
          curl --retry 5 --retry-delay 2 --connect-timeout 2 -s https://codecov.io/bash | bash -s -- -c -F aFlag -f app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml -v;

      - name: Upload reports as artifact
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        uses: actions/upload-artifact@v4
        with:
          # Check available parameters in: https://github.com/actions/upload-artifact/blob/main/action.yml
          name: reports
          path: app/build/reports
          if-no-files-found: error
          retention-days: 90