.github/workflows/reusable-android.yml
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