name: MobileGL APK on: push: branches: - dev - Feat/Backend-Direct-GLES - Feat/Backend-Direct-Vulkan workflow_dispatch: jobs: build: runs-on: ubuntu-latest permissions: actions: write contents: read env: CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_COMPRESS: "true" CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_MAXSIZE: 4G CCACHE_NOHASHDIR: "true" MOBILEGL_CMAKE_COMPILER_LAUNCHER: ccache steps: - name: Checkout repo uses: actions/checkout@v6 with: submodules: recursive - name: Set artifact metadata run: | echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV" - name: Set up JDK uses: actions/setup-java@v5 with: distribution: zulu java-version: '17' - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 with: gradle-version: 8.10.2 - name: Restore ccache uses: actions/cache/restore@v5 with: path: .ccache key: ${{ runner.os }}-apk-${{ github.job }}-ccache-v1 restore-keys: | ${{ runner.os }}-apk-${{ github.job }}-ccache- - name: Install ccache run: | sudo apt-get update sudo apt-get install -y ccache ccache --version - name: Setup Android SDK uses: android-actions/setup-android@v4 with: accept-android-sdk-licenses: false - name: Accept Android SDK licenses run: yes | sdkmanager --licenses >/dev/null - name: Install Android NDK run: | sdkmanager "ndk;27.3.13750724" echo "ndk.dir=$ANDROID_HOME/ndk/27.3.13750724" >> android-plugin/local.properties - name: Update glslang external sources working-directory: 3rdparty/glslang run: python update_glslang_sources.py - name: Build plugin APK run: gradle --no-daemon -p android-plugin :app:assemblePluginRelease -Pmobilegl.apkSuffix="${GITHUB_SHA}" -Pmobilegl.logLevel=MOBILEGL_LOG_LEVEL_INFO --parallel --max-workers "$(nproc)" env: SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} - name: Download ANGLE x86_64 libraries run: | angle_dir="android-plugin/app/src/trace/jniLibs/x86_64" rm -rf "${angle_dir}" mkdir -p "${angle_dir}" package_angle_variant() { variant="$1" commit="$2" egl_sha="$3" gles_sha="$4" source_dir="${RUNNER_TEMP}/mobilegl-angle-${variant}" base="https://raw.githubusercontent.com/FCL-Team/FoldCraftLauncher/${commit}/FCLauncher/src/main/jniLibs/x86_64" mkdir -p "${source_dir}" curl -L --fail --retry 3 -o "${source_dir}/libEGL_angle.so" "${base}/libEGL_angle.so" curl -L --fail --retry 3 -o "${source_dir}/libGLESv2_angle.so" "${base}/libGLESv2_angle.so" echo "${egl_sha} ${source_dir}/libEGL_angle.so" | sha256sum -c - echo "${gles_sha} ${source_dir}/libGLESv2_angle.so" | sha256sum -c - for library in libEGL_angle libGLESv2_angle; do filename="${library}_${variant}.so" cp "${source_dir}/${library}.so" "${angle_dir}/${filename}" done } package_angle_variant \ ec889e6ea831 \ f2a3d510dffd8f6540a52e1a7d0c5787d151075b \ c41828768d089899fa058ec0bee711a91be88347f29bdb935223da6be1149c40 \ e4f820d99f94365c66df868c7740fef142fe5c0cd7c941790a9e30638857ca4d package_angle_variant \ 90a62123d794 \ bdcc96ac11c79001018ae4375eb73cb54a9f682f \ d0f4298ccc770cc801fc52e21733521646161e8a4adb3bd0052d9a1b57ee0ca8 \ 66fdc867e552192d553d59095ea2e3cef4829de65c356f1fd826027b1905972e - name: Build retrace APK run: gradle --no-daemon -p android-plugin :app:assembleTraceRelease -Pmobilegl.apkSuffix="${GITHUB_SHA}" -Pmobilegl.abis=all -Pmobilegl.debuggableRelease=true -Pmobilegl.logLevel=MOBILEGL_LOG_LEVEL_INFO --parallel --max-workers "$(nproc)" env: SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} - name: Show ccache stats if: always() run: ccache --show-stats # Rewrite one rolling entry per job on the default branch. The upload stays # cumulative - it carries every object restored at the top of this run plus # the few TUs that actually changed - but Actions cache keys are immutable, # so the superseded blob has to be released before the same key can be # re-uploaded. Running after the build means a failed build leaves the # existing entry untouched. The other trigger branches restore this entry # rather than each writing a ~4 GB one of their own. - name: Release superseded ccache entry if: github.ref_name == github.event.repository.default_branch env: GH_TOKEN: ${{ github.token }} CACHE_KEY: ${{ runner.os }}-apk-${{ github.job }}-ccache-v1 run: gh cache delete "${CACHE_KEY}" || true - name: Save ccache if: github.ref_name == github.event.repository.default_branch continue-on-error: true uses: actions/cache/save@v5 with: path: .ccache key: ${{ runner.os }}-apk-${{ github.job }}-ccache-v1 - name: Verify APK metadata and packaging run: | AAPT2="$(find "$ANDROID_HOME/build-tools" -name aapt2 -type f | sort -V | tail -n 1)" plugin_apk="android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${GITHUB_SHA}.apk" trace_apk="android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk" test -f "${plugin_apk}" test -f "${trace_apk}" bash .github/scripts/validate-plugin-apks.sh "$AAPT2" "$plugin_apk" "$trace_apk" - name: Verify signed APKs run: | APKSIGNER="$(find "$ANDROID_HOME/build-tools" -name apksigner -type f | sort -V | tail -n 1)" mapfile -t APKS < <(printf '%s\n' \ "android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${GITHUB_SHA}.apk" \ "android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk") for APK in "${APKS[@]}"; do if [[ ! -f "$APK" ]]; then echo "::error::Expected release APK was not produced: $APK" exit 1 fi done for APK in "${APKS[@]}"; do if [[ "$APK" == *-unsigned.apk ]]; then echo "::error::Unsigned release APK produced: $APK" exit 1 fi "$APKSIGNER" verify --verbose "$APK" done - name: Upload plugin APK uses: actions/upload-artifact@v7 with: name: MobileGL-plugin-${{ env.date_today }}-${{ github.sha }} path: android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${{ github.sha }}.apk archive: false if-no-files-found: error - name: Upload retrace APK uses: actions/upload-artifact@v7 with: name: MobileGL-retrace-apk-${{ env.date_today }}-${{ github.sha }} path: android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${{ github.sha }}.apk archive: false if-no-files-found: error trace-cases: name: trace case matrix runs-on: ubuntu-latest needs: build outputs: android: ${{ steps.trace-cases.outputs.android }} names: ${{ steps.trace-cases.outputs.names }} steps: - name: Checkout repo uses: actions/checkout@v6 - name: Load trace cases id: trace-cases run: | echo "android=$(python3 tools/trace_replay/trace_cases.py --ci --format github-apk)" >> "$GITHUB_OUTPUT" echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT" trace-fixtures: name: trace fixture (${{ matrix.case }}) runs-on: ubuntu-latest needs: trace-cases strategy: fail-fast: false max-parallel: 4 matrix: case: ${{ fromJSON(needs.trace-cases.outputs.names) }} steps: - name: Checkout repo uses: actions/checkout@v6 - name: Derive trace fixture cache key id: fixture-key run: bash .github/scripts/trace-fixture-cache.sh key '${{ matrix.case }}' - name: Restore trace fixture cache id: fixture-cache if: steps.fixture-key.outputs.cacheable == 'true' uses: actions/cache/restore@v5 with: path: ${{ steps.fixture-key.outputs.paths }} key: ${{ steps.fixture-key.outputs.key }} - name: Verify restored trace fixture id: fixture-verify if: steps.fixture-cache.outputs.cache-hit == 'true' run: | if bash .github/scripts/trace-fixture-cache.sh verify '${{ matrix.case }}'; then echo "ok=true" >> "$GITHUB_OUTPUT" else echo "ok=false" >> "$GITHUB_OUTPUT" echo "::warning::Cached fixture for ${{ matrix.case }} failed verification; falling back to the download path" bash .github/scripts/trace-fixture-cache.sh reset '${{ matrix.case }}' fi - name: Fetch trace fixture if: steps.fixture-verify.outputs.ok != 'true' run: bash .github/scripts/fetch-trace-fixture-lfs.sh '${{ matrix.case }}' - name: Save trace fixture cache if: steps.fixture-key.outputs.cacheable == 'true' && steps.fixture-cache.outputs.cache-hit != 'true' uses: actions/cache/save@v5 with: path: ${{ steps.fixture-key.outputs.paths }} key: ${{ steps.fixture-key.outputs.key }} - name: Stage trace fixture run: | safe_case="$(printf '%s' '${{ matrix.case }}' | sed 's/[^A-Za-z0-9._-]/_/g')" stage_dir="trace-fixtures/${safe_case}" mkdir -p "${stage_dir}" python3 tools/trace_replay/trace_cases.py --format fixture-files --case '${{ matrix.case }}' | while IFS= read -r file; do cp "${file}" "${stage_dir}/" done - name: Upload trace fixture uses: actions/upload-artifact@v7 with: name: MobileGL-trace-fixture-${{ matrix.case }} path: trace-fixtures/** if-no-files-found: error android-avd: name: android avd image runs-on: ubuntu-latest env: AVD_NAME: mobilegl-ci ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd ANDROID_HOME: ${{ github.workspace }}/.android/sdk ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk steps: - name: Checkout repo uses: actions/checkout@v6 - name: Setup Android SDK uses: android-actions/setup-android@v4 with: accept-android-sdk-licenses: false - name: Accept Android SDK licenses run: yes | sdkmanager --licenses >/dev/null - name: Restore Android AVD cache id: android-avd-cache uses: actions/cache@v5 with: path: | ${{ env.ANDROID_AVD_HOME }} ${{ env.ANDROID_SDK_ROOT }}/emulator ${{ env.ANDROID_SDK_ROOT }}/platform-tools ${{ env.ANDROID_SDK_ROOT }}/platforms/android-35 ${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64 key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }} - name: Create AVD if: steps.android-avd-cache.outputs.cache-hit != 'true' run: | sh android-plugin/run-avd-ci.sh create \ --api-level 35 \ --target google_apis \ --arch x86_64 \ --profile pixel_6 \ --avd-name "${AVD_NAME}" retrace: name: retrace (${{ matrix.backend.name }}, ${{ matrix.case.name }}) runs-on: ubuntu-latest needs: - build - android-avd - trace-cases - trace-fixtures if: ${{ always() && needs.build.result == 'success' && needs.android-avd.result == 'success' && needs.trace-cases.result == 'success' }} timeout-minutes: 75 env: AVD_NAME: mobilegl-ci ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd ANDROID_HOME: ${{ github.workspace }}/.android/sdk ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk strategy: fail-fast: false max-parallel: 4 matrix: backend: - name: DirectGLES gpu: software - name: DirectVulkan gpu: lavapipe case: ${{ fromJSON(needs.trace-cases.outputs.android) }} steps: - name: Set Swap Space uses: pierotofy/set-swap-space@v1.0 with: swap-size-gb: 8 - name: Checkout repo uses: actions/checkout@v6 - name: Download trace fixture uses: actions/download-artifact@v8 with: name: MobileGL-trace-fixture-${{ matrix.case.name }} path: trace-fixture-download - name: Install trace fixture run: | mkdir -p tools/trace_replay/fixtures find trace-fixture-download -type f -exec cp {} tools/trace_replay/fixtures/ \; - name: Set artifact metadata run: | echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV" echo "EMULATOR_LOG=${RUNNER_TEMP}/mobilegl-emulator.log" >> "$GITHUB_ENV" echo "EMULATOR_PID_FILE=${RUNNER_TEMP}/mobilegl-emulator.pid" >> "$GITHUB_ENV" - name: Setup Android SDK uses: android-actions/setup-android@v4 with: accept-android-sdk-licenses: false - name: Accept Android SDK licenses run: yes | sdkmanager --licenses >/dev/null - name: Restore Android AVD cache id: android-avd-cache uses: actions/cache/restore@v5 with: path: | ${{ env.ANDROID_AVD_HOME }} ${{ env.ANDROID_SDK_ROOT }}/emulator ${{ env.ANDROID_SDK_ROOT }}/platform-tools ${{ env.ANDROID_SDK_ROOT }}/platforms/android-35 ${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64 key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }} - name: Download retrace APK uses: actions/download-artifact@v8 with: name: MobileGL-plugin-trace-release-${{ github.sha }}.apk path: android-retrace-apks - name: Enable KVM 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: Create AVD if: steps.android-avd-cache.outputs.cache-hit != 'true' run: | sh android-plugin/run-avd-ci.sh create \ --api-level 35 \ --target google_apis \ --arch x86_64 \ --profile pixel_6 \ --avd-name "${AVD_NAME}" - name: Launch Emulator run: | sh android-plugin/run-avd-ci.sh start \ --avd-name "${AVD_NAME}" \ --gpu "${{ matrix.backend.gpu }}" \ --emulator-log "${EMULATOR_LOG}" \ --pid-file "${EMULATOR_PID_FILE}" \ --boot-timeout 300 - name: Retrace and validate env: MOBILEGL_USE_ANGLE: ${{ matrix.backend.name == 'DirectGLES' && '1' || '0' }} MOBILEGL_TRACE_ANGLE_VARIANT: ${{ matrix.case.name == 'minecraft-1.21.4-fabric-iris-bliss-in-world' && '90a62123d794' || 'ec889e6ea831' }} MOBILEGL_MAGMA_R11G11B10F_FALLBACK: ${{ matrix.backend.name == 'DirectVulkan' && '1' || '0' }} run: | apk_file="android-retrace-apks/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk" test -f "${apk_file}" extra_retrace_args=() # Bliss needs the newer signed ANGLE variant plus sampler mipmap # min-filter downgrading on ANGLE llvmpipe. if [ "${{ matrix.backend.name }}" = "DirectGLES" ] && [ "${{ matrix.case.name }}" = "minecraft-1.21.4-fabric-iris-bliss-in-world" ]; then extra_retrace_args+=(--avoid-angle-llvmpipe-sampler-mipmap-min-filter) fi if [ "${{ matrix.backend.name }}" = "DirectGLES" ] && [ "${{ matrix.case.avoid_angle_llvmpipe_explicit_lod_bias || false }}" = "true" ]; then extra_retrace_args+=(--avoid-angle-llvmpipe-explicit-lod-bias) fi if [ "${{ matrix.case.coherent_as_flush || false }}" = "true" ]; then extra_retrace_args+=(--coherent-as-flush) fi run_retrace() { timeout "$(( ${{ matrix.case.timeout_seconds }} + 300 ))" sh android-plugin/trace-replay-ci.sh \ --apk-file "${apk_file}" \ --package top.mobilegl.plugin.trace \ --backend "${{ matrix.backend.name }}" \ --result-root android-retrace-result \ --fixture-root android-retrace-fixture \ --case "${{ matrix.case.name }}" \ --trace-archive "${{ matrix.case.trace_archive }}" \ --trace-file "${{ matrix.case.trace_file }}" \ --golden "${{ matrix.case.golden }}" \ --alternate-golden "${{ matrix.case.alternate_golden || '' }}" \ --target-call "${{ matrix.case.target_call }}" \ --width "${{ matrix.case.width }}" \ --height "${{ matrix.case.height }}" \ --ssim-threshold "${{ matrix.case.ssim_threshold || '0.99' }}" \ --crop-x "${{ matrix.case.crop_x }}" \ --crop-y "${{ matrix.case.crop_y }}" \ --crop-width "${{ matrix.case.crop_width }}" \ --crop-height "${{ matrix.case.crop_height }}" \ --timeout-seconds "${{ matrix.case.timeout_seconds }}" \ "${extra_retrace_args[@]}" } retrace_status=0 run_retrace || retrace_status=$? if [ "${retrace_status}" -eq 75 ]; then echo "::warning::Android emulator infrastructure failed; restarting it and retrying this retrace once." # Surface-lost is retried rather than failed, so it would otherwise # be invisible. Report it per job - a healthy run prints nothing and # a rate spike shows up as a row per affected case. reason_file="android-retrace-result/infrastructure-failure-reason.txt" surface_lost_retries=0 if [ -f "${reason_file}" ]; then surface_lost_retries="$(grep -c 'angle-surface-lost' "${reason_file}" || true)" fi if [ "${surface_lost_retries}" -gt 0 ]; then echo "surface-lost retries: ${surface_lost_retries} (${{ matrix.backend.name }}, ${{ matrix.case.name }})" \ >> "${GITHUB_STEP_SUMMARY}" fi # The restart truncates EMULATOR_LOG, and the attempt that lost the # emulator is the one worth reading - the retry usually only shows # the wreckage. Keep the first attempt's log before it is clobbered. if [ -f "${EMULATOR_LOG}" ]; then cp "${EMULATOR_LOG}" "${EMULATOR_LOG}.first-attempt" || true fi sh android-plugin/run-avd-ci.sh stop \ --avd-name "${AVD_NAME}" \ --emulator-log "${EMULATOR_LOG}" \ --pid-file "${EMULATOR_PID_FILE}" adb kill-server || true sleep 2 sh android-plugin/run-avd-ci.sh start \ --avd-name "${AVD_NAME}" \ --gpu "${{ matrix.backend.gpu }}" \ --emulator-log "${EMULATOR_LOG}" \ --pid-file "${EMULATOR_PID_FILE}" \ --boot-timeout 300 run_retrace elif [ "${retrace_status}" -ne 0 ]; then exit "${retrace_status}" fi - name: Collect retrace summary inputs if: always() run: | safe_case="$(printf '%s' '${{ matrix.case.name }}' | sed 's/[^A-Za-z0-9._-]/_/g')" result_dir="android-retrace-result/${safe_case}-${{ matrix.backend.name }}" mkdir -p "${result_dir}" if [ -s "${{ matrix.case.golden }}" ]; then cp "${{ matrix.case.golden }}" "${result_dir}/${safe_case}-${{ matrix.backend.name }}-golden.png" fi if [ -n "${{ matrix.case.alternate_golden || '' }}" ] && [ -s "${{ matrix.case.alternate_golden || '' }}" ]; then cp "${{ matrix.case.alternate_golden || '' }}" "${result_dir}/${safe_case}-${{ matrix.backend.name }}-alternate-golden.png" fi - name: Collect emulator diagnostics if: always() run: | mkdir -p android-retrace-result/diagnostics adb devices -l > android-retrace-result/diagnostics/adb-devices.txt || true timeout 30 adb logcat -d -t 1000 > android-retrace-result/diagnostics/logcat.txt || true if [ -f "${EMULATOR_LOG}" ]; then cp "${EMULATOR_LOG}" android-retrace-result/diagnostics/emulator.log fi if [ -f "${EMULATOR_LOG}.first-attempt" ]; then cp "${EMULATOR_LOG}.first-attempt" android-retrace-result/diagnostics/emulator-first-attempt.log fi # A vanished emulator looks identical whether the host OOM killer took # qemu or the renderer faulted. These two say which. free -h > android-retrace-result/diagnostics/host-memory.txt 2>&1 || true sudo dmesg -T 2>/dev/null | tail -300 > android-retrace-result/diagnostics/host-dmesg.txt || true - name: Stop Emulator if: always() run: | sh android-plugin/run-avd-ci.sh stop \ --avd-name "${AVD_NAME}" \ --emulator-log "${EMULATOR_LOG}" \ --pid-file "${EMULATOR_PID_FILE}" - name: Upload Android retrace result if: always() uses: actions/upload-artifact@v7 with: name: MobileGL-android-retrace-result-${{ env.date_today }}-${{ github.sha }}-${{ matrix.backend.name }}-${{ matrix.case.name }} path: android-retrace-result/** if-no-files-found: warn retrace-summary: name: retrace summary runs-on: ubuntu-latest needs: retrace if: always() steps: - name: Checkout repo uses: actions/checkout@v6 - name: Set artifact metadata run: | echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV" - name: Set up Node.js uses: actions/setup-node@v7 with: node-version: '22' - name: Download Android retrace results uses: actions/download-artifact@v8 with: pattern: MobileGL-android-retrace-result-* path: retrace-artifacts - name: Render retrace summary run: | node tools/trace_replay/render_retrace_summary.mjs \ --input retrace-artifacts \ --output-dir android-retrace-summary \ --title "MobileGL Android retrace overview" \ --group-label "Android Emulator" \ --html mobilegl-android-retrace-overview.html - name: Upload Android retrace summary uses: actions/upload-artifact@v7 with: path: android-retrace-summary/mobilegl-android-retrace-overview.html archive: false if-no-files-found: error remove-artifact-clutter: name: remove artifact clutter runs-on: ubuntu-latest needs: retrace-summary if: always() permissions: actions: write steps: - name: Delete intermediate Android retrace artifacts env: GH_TOKEN: ${{ github.token }} run: | declare -A failed_cases=() while IFS= read -r job_name; do case_name="${job_name#retrace (*, }" case_name="${case_name%)}" failed_cases["${case_name}"]=1 done < <( gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=100" \ --jq '.jobs[] | select(.name | startswith("retrace (")) | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name' ) if ((${#failed_cases[@]})); then echo "Retaining fixtures and results for failed retrace case(s):" printf ' %s\n' "${!failed_cases[@]}" else echo "All retrace jobs succeeded; nothing needs to be retained." fi deleted=0 retained=0 while IFS=$'\t' read -r artifact_id artifact_name; do keep=0 if [[ "${artifact_name}" == MobileGL-trace-fixture-* ]]; then case_name="${artifact_name#MobileGL-trace-fixture-}" if [[ -v "failed_cases[${case_name}]" ]]; then keep=1 fi elif [[ "${artifact_name}" == MobileGL-android-retrace-result-* ]]; then # The result artifact carries mobilegl.log, retrace.log, logcat, # the emulator log and the actual/diff images - the only record of # why a retrace failed. Its name ends in --, so a # suffix match on the case name keeps both backends' results for a # case that failed on either of them, which is what a comparison # needs. The match is anchored at the end, so a case name that is a # prefix of a longer one does not retain the longer one's results. for case_name in "${!failed_cases[@]}"; do if [[ "${artifact_name}" == *-"${case_name}" ]]; then keep=1 break fi done fi if ((keep)); then echo "Retaining ${artifact_name} (${artifact_id}) for failed retrace." ((retained += 1)) continue fi echo "Deleting ${artifact_name} (${artifact_id})" gh api --method DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${artifact_id}" ((deleted += 1)) done < <( gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \ --jq '.artifacts[] | select(.name | startswith("MobileGL-trace-fixture-") or startswith("MobileGL-android-retrace-result-") or startswith("trace-fixture-") or startswith("retrace-result-")) | [.id, .name] | @tsv' ) echo "Deleted ${deleted} intermediate Android artifact(s); retained ${retained} failed-retrace fixture(s)."