Skip to content

Vehicle: reduce default MAV_CMD ACK timeout to 1200ms #2682

Vehicle: reduce default MAV_CMD ACK timeout to 1200ms

Vehicle: reduce default MAV_CMD ACK timeout to 1200ms #2682

Workflow file for this run

name: Android
on:
push:
branches:
- master
- 'Stable*'
tags:
- 'v*'
paths-ignore:
- 'docs/**'
pull_request:
paths:
- '.github/workflows/android.yml'
- '.github/actions/**'
- 'deploy/android/**'
- 'src/**'
- 'test/**'
- 'android/**'
- 'CMakeLists.txt'
- 'cmake/**'
- 'translations/*'
merge_group:
workflow_dispatch:
inputs:
build_type:
description: 'Build type'
required: false
default: 'Release'
type: choice
options:
- Release
- Debug
workflow_call:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
permissions:
contents: read
id-token: write
attestations: write
actions: read
jobs:
build:
name: Android (${{ matrix.host }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
include:
- host: linux
runner: ubuntu-latest
arch: linux_gcc_64
qt_host_path: gcc_64
shell: bash
primary: true
emulator: false
- host: mac
runner: macos-latest
arch: clang_64
qt_host_path: macos
shell: bash
primary: false
emulator: false
- host: windows
runner: windows-latest
arch: win64_msvc2022_64
qt_host_path: msvc2022_64
shell: pwsh
primary: false
emulator: false
- host: linux-emulator
qt_host: linux
runner: ubuntu-latest
arch: linux_gcc_64
qt_host_path: gcc_64
shell: bash
primary: false
emulator: true
defaults:
run:
shell: ${{ matrix.shell }}
env:
PACKAGE: QGroundControl
QT_ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/deploy/android/android_release.keystore
QT_ANDROID_KEYSTORE_ALIAS: QGCAndroidKeyStore
QT_ANDROID_KEYSTORE_STORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
QT_ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
QT_ANDROID_ABIS: ${{ matrix.emulator && 'x86_64' || matrix.primary && 'arm64-v8a;armeabi-v7a' || 'arm64-v8a' }}
HAS_PLAYSTORE_SECRET: ${{ secrets.GOOGLE_SERVICE_ACCOUNT != '' }}
steps:
- name: Harden Runner
if: runner.os == 'Linux'
uses: step-security/harden-runner@v2
with:
egress-policy: audit
- name: Checkout repo
uses: actions/checkout@v6
with:
fetch-depth: ${{ github.event_name == 'pull_request' && 1 || 0 }}
fetch-tags: ${{ github.event_name != 'pull_request' }}
- name: Get build config
id: config
uses: ./.github/actions/build-config
- name: Initial Setup
uses: ./.github/actions/common
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
with:
cache-read-only: ${{ github.event_name == 'pull_request' }}
- name: Install Qt for Android
uses: ./.github/actions/qt-android
with:
host: ${{ matrix.qt_host || matrix.host }}
arch: ${{ matrix.arch }}
version: ${{ steps.config.outputs.qt_version }}
modules: ${{ steps.config.outputs.qt_modules }}
abis: ${{ env.QT_ANDROID_ABIS }}
build-type: ${{ inputs.build_type || 'Release' }}
cpm-modules: ${{ runner.temp }}/build/cpm_modules
ndk-full-version: ${{ steps.config.outputs.ndk_full_version }}
java-version: ${{ steps.config.outputs.java_version }}
android-platform: ${{ steps.config.outputs.android_platform }}
android-cmdline-tools: ${{ steps.config.outputs.android_cmdline_tools }}
android-build-tools: ${{ steps.config.outputs.android_build_tools }}
save-cache: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
- name: Create Debug Keystore
if: ${{ env.QT_ANDROID_KEYSTORE_STORE_PASS == '' || !matrix.primary }}
shell: bash
run: |
keytool -genkey -v \
-keystore "${RUNNER_TEMP}/debug.keystore" \
-storepass android -alias androiddebugkey -keypass android \
-keyalg RSA -keysize 2048 -validity 10000 \
-dname "CN=Android Debug,O=Android,C=US"
{
echo "QT_ANDROID_KEYSTORE_PATH=${RUNNER_TEMP}/debug.keystore"
echo "QT_ANDROID_KEYSTORE_ALIAS=androiddebugkey"
echo "QT_ANDROID_KEYSTORE_STORE_PASS=android"
echo "QT_ANDROID_KEYSTORE_KEY_PASS=android"
} >> "$GITHUB_ENV"
- name: Configure
uses: ./.github/actions/cmake-configure
with:
build-dir: ${{ runner.temp }}/build
build-type: ${{ inputs.build_type || 'Release' }}
stable: ${{ (github.ref_type == 'tag' || contains(github.ref, 'Stable')) && 'true' || 'false' }}
extra-args: >-
-DCMAKE_WARN_DEPRECATED=FALSE
-DQT_ANDROID_ABIS=${{ env.QT_ANDROID_ABIS }}
-DQT_HOST_PATH=${{ env.QT_ROOT_DIR }}/../${{ matrix.qt_host_path }}
-DQT_ANDROID_SIGN_APK=ON
- name: Build
id: build
continue-on-error: ${{ matrix.emulator }}
uses: ./.github/actions/cmake-build
with:
build-dir: ${{ runner.temp }}/build
build-type: ${{ inputs.build_type || 'Release' }}
parallel-jobs: ${{ matrix.emulator && '2' || '' }}
output-file: ${{ matrix.emulator && 'qgc-build.log' || '' }}
- name: Retry Build (emulator JSON truncation recovery)
if: ${{ matrix.emulator && steps.build.outcome == 'failure' }}
shell: bash
env:
BUILD_DIR: ${{ runner.temp }}/build
BUILD_TYPE: ${{ inputs.build_type || 'Release' }}
run: |
set -euo pipefail
log_file="${BUILD_DIR}/qgc-build.log"
settings_json="${BUILD_DIR}/android-QGroundControl-deployment-settings.json"
retry_log="${BUILD_DIR}/qgc-build-retry.log"
if [[ ! -f "${log_file}" ]]; then
echo "::error::Build failed and '${log_file}' is missing."
exit 1
fi
# Retry only for known intermittent Qt deployment-settings truncation.
if ! grep -Eq "Invalid json file: .*android-QGroundControl-deployment-settings\\.json\\. Reason: unterminated object" "${log_file}"; then
echo "::error::Initial build failed for a non-retriable reason; skipping retry."
exit 1
fi
echo "::warning::Detected truncated android deployment settings JSON. Retrying build with --parallel 1."
if [[ -f "${settings_json}" ]]; then
python3 -m json.tool "${settings_json}" >/dev/null 2>&1 || true
rm -f "${settings_json}"
fi
cmake --build "${BUILD_DIR}" --target all --config "${BUILD_TYPE}" --parallel 1 2>&1 | tee "${retry_log}"
- name: Android Lint
if: ${{ matrix.host == 'linux' }}
shell: bash
working-directory: ${{ runner.temp }}/build/android-build
run: |
if [[ ! -x "./gradlew" ]]; then
echo "::error::Gradle wrapper not found in ${PWD}"
ls -la
exit 1
fi
./gradlew lintRelease --no-daemon
- name: Prepare Artifact
shell: bash
run: |
TEMP_DIR="${RUNNER_TEMP//\\//}" # Convert backslashes for Windows
APK_DIR="${TEMP_DIR}/build/android-build"
if ! ls "${APK_DIR}"/*.apk 1>/dev/null 2>&1; then
echo "::error::No APK files found in ${APK_DIR}"
ls -la "${APK_DIR}" 2>/dev/null || echo "Build directory does not exist"
exit 1
fi
cp "${APK_DIR}"/*.apk "${TEMP_DIR}/build/${{ env.PACKAGE }}.apk"
- name: Enable KVM
if: ${{ matrix.emulator }}
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: AVD Cache
if: ${{ matrix.emulator }}
id: avd-cache
uses: actions/cache@v5
with:
path: |
~/.android/avd/*
~/.android/adb*
key: >-
avd-${{ runner.os }}-${{ runner.arch }}-api${{ steps.config.outputs.android_platform }}
-x86_64-google_apis-pixel_6-qt${{ steps.config.outputs.qt_version }}
-ndk${{ steps.config.outputs.ndk_full_version }}-v2
- name: Create AVD Snapshot
id: avd-snapshot
if: ${{ matrix.emulator && steps.avd-cache.outputs.cache-hit != 'true' }}
timeout-minutes: 15
continue-on-error: true
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ steps.config.outputs.android_platform }}
system-image-api-level: ${{ steps.config.outputs.android_platform }}
arch: x86_64
target: google_apis
profile: pixel_6
cores: 4
ram-size: 4096M
heap-size: 1024M
avd-name: qgc-ci
force-avd-creation: false
emulator-boot-timeout: 420
disable-animations: true
disable-spellchecker: true
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim
script: echo "Generated AVD snapshot for cache."
- name: Kill Stale Emulator
if: ${{ matrix.emulator && steps.avd-snapshot.outcome == 'failure' }}
shell: bash
run: |
pkill -9 -f qemu-system || true
adb kill-server 2>/dev/null || true
sleep 5
- name: Emulator Boot Test
if: ${{ matrix.emulator }}
timeout-minutes: 20
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ steps.config.outputs.android_platform }}
system-image-api-level: ${{ steps.config.outputs.android_platform }}
arch: x86_64
target: google_apis
profile: pixel_6
cores: 4
ram-size: 4096M
heap-size: 1024M
avd-name: qgc-ci
force-avd-creation: false
emulator-boot-timeout: 420
disable-animations: true
disable-spellchecker: true
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -no-snapshot-save
script: |
timeout 20s adb start-server >/dev/null 2>&1 || true
timeout 180s adb wait-for-device || (echo "::error::adb wait-for-device timed out before boot test." && exit 1)
python3 .github/scripts/android_boot_test.py --apk "${{ runner.temp }}/build/${{ env.PACKAGE }}.apk" --package org.mavlink.qgroundcontrol --timeout 120 --stability-window 20 --adb-ready-timeout 180 --install-retries 4 --install-retry-delay 5 --launch-retries 2 --log-output /tmp/qgc_emulator_boot.log
- name: Collect Emulator Diagnostics
if: ${{ failure() && matrix.emulator }}
timeout-minutes: 5
shell: bash
run: |
OUT_DIR="${RUNNER_TEMP}/emulator-diagnostics"
BUILD_DIR="${RUNNER_TEMP}/build"
mkdir -p "${OUT_DIR}"
cp /tmp/qgc_emulator_boot.log "${OUT_DIR}/qgc_emulator_boot.log" 2>/dev/null || true
cp "${BUILD_DIR}/qgc-build.log" "${OUT_DIR}/qgc-build.log" 2>/dev/null || true
cp "${BUILD_DIR}/qgc-build-retry.log" "${OUT_DIR}/qgc-build-retry.log" 2>/dev/null || true
cp "${BUILD_DIR}/android-QGroundControl-deployment-settings.json" \
"${OUT_DIR}/android-QGroundControl-deployment-settings.json" 2>/dev/null || true
if [ -f "${OUT_DIR}/android-QGroundControl-deployment-settings.json" ]; then
python3 -m json.tool "${OUT_DIR}/android-QGroundControl-deployment-settings.json" \
> "${OUT_DIR}/android-QGroundControl-deployment-settings.pretty.json" \
2> "${OUT_DIR}/android-QGroundControl-deployment-settings.error.txt" || true
fi
if command -v adb >/dev/null 2>&1; then
timeout 20s adb start-server >/dev/null 2>&1 || true
timeout 20s adb devices > "${OUT_DIR}/adb-devices.txt" 2>&1 || true
if grep -Eq '^emulator-[0-9]+\s+device$' "${OUT_DIR}/adb-devices.txt"; then
timeout 30s adb -e logcat -d > "${OUT_DIR}/adb-logcat-full.txt" 2>/dev/null || true
timeout 30s adb -e shell dumpsys activity > "${OUT_DIR}/dumpsys-activity.txt" 2>/dev/null || true
timeout 30s adb -e shell getprop > "${OUT_DIR}/getprop.txt" 2>/dev/null || true
else
echo "No online emulator device found; skipping adb diagnostics." > "${OUT_DIR}/adb-skipped.txt"
fi
else
echo "adb not found; skipping adb diagnostics." > "${OUT_DIR}/adb-skipped.txt"
fi
if [ -d "${HOME}/.android/avd" ]; then
find "${HOME}/.android/avd" -maxdepth 3 -type f \( -name "*.log" -o -name "*.ini" \) \
-exec cp --parents {} "${OUT_DIR}" \; 2>/dev/null || true
fi
ls -la "${OUT_DIR}" || true
- name: Upload Emulator Diagnostics
if: ${{ failure() && matrix.emulator }}
uses: actions/upload-artifact@v6
with:
name: emulator-diagnostics-${{ matrix.host }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/emulator-diagnostics
if-no-files-found: warn
retention-days: 14
- name: Attest Build with SBOM
if: ${{ !matrix.emulator }}
uses: ./.github/actions/attest-sbom
with:
subject-path: ${{ runner.temp }}/build/${{ env.PACKAGE }}.apk
subject-name: ${{ env.PACKAGE }}-${{ matrix.host }}
scan-path: ${{ runner.temp }}/build
- name: Upload Build File
if: ${{ !matrix.emulator }}
uses: ./.github/actions/upload
with:
artifact_name: ${{ env.PACKAGE }}.apk
package_name: ${{ matrix.primary && env.PACKAGE || format('{0}-{1}', env.PACKAGE, matrix.host) }}
aws_role_arn: ${{ secrets.AWS_ROLE_ARN }}
aws_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws_distribution_id: ${{ secrets.AWS_DISTRIBUTION_ID }}
upload_aws: ${{ matrix.primary }}
- name: Deploy to Play Store
if: ${{ !matrix.emulator && matrix.primary && github.ref_type == 'tag' && env.HAS_PLAYSTORE_SECRET == 'true' }}
uses: ./.github/actions/playstore
with:
artifact: ${{ runner.temp }}/build/${{ env.PACKAGE }}.apk
service_account_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }}