chore(ci): implement tiered GitHub Actions runner strategy (#4937)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-26 13:18:03 -05:00 committed by GitHub
parent e106badec7
commit 1c1c208d48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 94 additions and 33 deletions

View file

@ -137,6 +137,13 @@ Always run commands in the following order to ensure reliability. Do not attempt
- Pull request CI is main-only (`.github/workflows/pull-request.yml` targets `main` branch).
- Gradle cache writes are trusted on `main` and merge queue runs (`merge_group` / `gh-readonly-queue/*`); other refs use read-only cache mode in reusable CI.
- PR `check-changes` path filtering lives in `.github/workflows/pull-request.yml` and must include module dirs plus build/workflow entrypoints (`build-logic/**`, `gradle/**`, `.github/workflows/**`, `gradlew`, `settings.gradle.kts`, etc.) so CI is not skipped for infra-only changes.
- **Runner strategy (three tiers):**
- **`ubuntu-24.04-arm`** — Lightweight/utility jobs (status checks, labelers, triage, changelog, release metadata, stale, moderation). These run only shell scripts or GitHub API calls and benefit from ARM runners' shorter queue times.
- **`ubuntu-24.04`** — Gradle-heavy jobs (CI host-check, android-check, release builds, Dokka, CodeQL, publish, dependency-submission). Pinned for reproducibility; avoid `ubuntu-latest` to prevent breakage when GitHub rolls the alias forward.
- **Desktop release matrix**`[macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]` for cross-platform native packaging (DMG, MSI, deb/rpm/AppImage for x64 and ARM).
- **CI JVM tuning:** `gradle.properties` is tuned for local dev (8g heap, 4g Kotlin daemon). CI workflows override via `GRADLE_OPTS` env var to fit the 7GB RAM budget of standard runners: `-Xmx4g` Gradle heap, `-Xmx2g` Kotlin daemon, VFS watching disabled, workers capped at 4.
- **KMP Smoke Compile:** Use `./gradlew kmpSmokeCompile` instead of listing individual module compile tasks. The `kmpSmokeCompile` lifecycle task (registered in `RootConventionPlugin`) auto-discovers all KMP modules and depends on their `compileKotlinJvm` + `compileKotlinIosSimulatorArm64` tasks.
- **`mavenLocal()` gated:** The `mavenLocal()` repository is disabled by default to prevent CI cache poisoning. For local JitPack testing, pass `-PuseMavenLocal` to Gradle.
- **Terminal Pagers:** When running shell commands like `git diff` or `git log`, ALWAYS use `--no-pager` (e.g., `git --no-pager diff`) to prevent the agent from getting stuck in an interactive prompt.
- **Text Search:** Prefer using `rg` (ripgrep) over `grep` or `find` for fast text searching across the codebase.

View file

@ -28,7 +28,7 @@ jobs:
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-24.04' }}
if: github.repository == 'meshtastic/Meshtastic-Android'
permissions:
# required for all workflows

View file

@ -29,7 +29,7 @@ permissions:
jobs:
determine-tags:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
outputs:
tag_to_process: ${{ steps.calculate_tags.outputs.tag_to_process }}
release_name: ${{ steps.calculate_tags.outputs.release_name }}
@ -142,7 +142,7 @@ jobs:
cleanup-on-failure:
needs: [determine-tags, call-release-workflow]
if: ${{ (failure() || cancelled()) && !inputs.dry_run && inputs.channel == 'internal' }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout code
uses: actions/checkout@v6

View file

@ -10,7 +10,7 @@ permissions:
jobs:
dependency-submission:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
if: github.repository == 'meshtastic/Meshtastic-Android'
steps:

View file

@ -38,7 +38,7 @@ concurrency:
jobs:
build-docs:
if: github.repository == 'meshtastic/Meshtastic-Android'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v6
@ -70,7 +70,7 @@ jobs:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
needs: build-docs
steps:
- name: Deploy to GitHub Pages

View file

@ -16,7 +16,7 @@ concurrency:
jobs:
main-push-changelog:
name: Generate main push changelog
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout code
uses: actions/checkout@v6

View file

@ -25,7 +25,7 @@ jobs:
check-workflow-status:
name: Check Workflow Status
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
permissions: {}
needs:
- android-check

View file

@ -15,7 +15,7 @@ concurrency:
jobs:
triage:
if: ${{ github.repository == 'meshtastic/firmware' && github.event.issue.user.type != 'Bot' }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
# ─────────────────────────────────────────────────────────────────────────
# Step 1: Quality check (spam/AI-slop detection) - runs first, exits early if spam

View file

@ -16,7 +16,7 @@ concurrency:
jobs:
triage:
if: ${{ github.repository == 'meshtastic/firmware' && github.event.pull_request.user.type != 'Bot' }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
# ─────────────────────────────────────────────────────────────────────────
# Step 1: Check if PR already has automation/type labels (skip if so)

View file

@ -9,7 +9,7 @@ on:
jobs:
spam-detection:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
permissions:
issues: write
pull-requests: write

View file

@ -18,7 +18,7 @@ permissions:
jobs:
cleanup_prereleases:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
environment: Release
steps:
- name: Checkout code

View file

@ -10,7 +10,7 @@ permissions:
jobs:
check-label:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
- name: Check for PR labels
uses: actions/github-script@v8

View file

@ -65,7 +65,7 @@ permissions:
jobs:
prepare-build-info:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
outputs:
APP_VERSION_NAME: ${{ steps.prep_version.outputs.APP_VERSION_NAME }}
APP_VERSION_CODE: ${{ steps.calculate_version_code.outputs.versionCode }}
@ -102,7 +102,7 @@ jobs:
shell: bash
promote-release:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
environment: Release
needs: [ prepare-build-info ]
steps:
@ -116,7 +116,7 @@ jobs:
user-fraction: ${{ (inputs.channel == 'production' && '0.1') || (inputs.channel == 'open' && '0.5') || '1.0' }}
update-github-release:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
needs: [ prepare-build-info, promote-release ]
steps:
- name: Checkout code

View file

@ -12,7 +12,7 @@ on:
jobs:
publish:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write

View file

@ -9,7 +9,7 @@ jobs:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
- id: label-the-PR
uses: actions/labeler@v6

View file

@ -19,7 +19,7 @@ jobs:
# 1. CHANGE DETECTION: Prevents unnecessary builds
check-changes:
if: github.repository == 'meshtastic/Meshtastic-Android' && !( github.head_ref == 'scheduled-updates' || github.head_ref == 'l10n_main' )
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
outputs:
android: ${{ steps.filter.outputs.android }}
steps:
@ -57,7 +57,7 @@ jobs:
# 1b. FILTER DRIFT CHECK: Ensures check-changes stays aligned with module roots
verify-check-changes-filter:
if: github.repository == 'meshtastic/Meshtastic-Android' && !( github.head_ref == 'scheduled-updates' || github.head_ref == 'l10n_main' )
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v6
- name: Verify module roots are represented in check-changes filter
@ -115,7 +115,7 @@ jobs:
# 3. WORKFLOW STATUS: Ensures required checks are satisfied
check-workflow-status:
name: Check Workflow Status
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
permissions: {}
needs: [check-changes, verify-check-changes-filter, validate-and-build]
if: always()

View file

@ -61,7 +61,7 @@ permissions:
jobs:
prepare-build-info:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
outputs:
APP_VERSION_NAME: ${{ steps.prep_version.outputs.APP_VERSION_NAME }}
APP_VERSION_CODE: ${{ steps.calculate_version_code.outputs.versionCode }}
@ -101,13 +101,18 @@ jobs:
shell: bash
release-google:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs: [prepare-build-info]
environment: Release
env:
GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }}
GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }}
GRADLE_CACHE_PASSWORD: ${{ secrets.GRADLE_CACHE_PASSWORD }}
GRADLE_OPTS: >-
-Dorg.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8
-Dorg.gradle.vfs.watch=false
-Dorg.gradle.workers.max=4
-Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseParallelGC
steps:
- name: Checkout code
uses: actions/checkout@v6
@ -193,13 +198,18 @@ jobs:
subject-path: app/build/outputs/apk/google/release/*.apk
release-fdroid:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs: [prepare-build-info]
environment: Release
env:
GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }}
GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }}
GRADLE_CACHE_PASSWORD: ${{ secrets.GRADLE_CACHE_PASSWORD }}
GRADLE_OPTS: >-
-Dorg.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8
-Dorg.gradle.vfs.watch=false
-Dorg.gradle.workers.max=4
-Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseParallelGC
steps:
- name: Checkout code
uses: actions/checkout@v6
@ -266,7 +276,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-22.04, ubuntu-22.04-arm]
os: [macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]
env:
GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }}
GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }}
@ -324,7 +334,7 @@ jobs:
if-no-files-found: ignore
github-release:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
needs: [prepare-build-info, release-google, release-fdroid, release-desktop]
env:
INTERNAL_BUILDS_HOST: ${{ secrets.INTERNAL_BUILDS_HOST }}

View file

@ -44,10 +44,17 @@ env:
GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }}
GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }}
GRADLE_CACHE_PASSWORD: ${{ secrets.GRADLE_CACHE_PASSWORD }}
# CI JVM tuning: override gradle.properties values (8g heap + 4g Kotlin daemon)
# that exceed the 7GB RAM on ubuntu-24.04 standard runners.
GRADLE_OPTS: >-
-Dorg.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8
-Dorg.gradle.vfs.watch=false
-Dorg.gradle.workers.max=4
-Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseParallelGC
jobs:
host-check:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read
timeout-minutes: 60
@ -93,7 +100,7 @@ jobs:
run: ./gradlew test koverXmlReport app:koverXmlReportFdroidDebug app:koverXmlReportGoogleDebug core:api:koverXmlReportDebug core:barcode:koverXmlReportFdroidDebug core:barcode:koverXmlReportGoogleDebug mesh_service_example:koverXmlReportDebug desktop:koverXmlReport -Pci=true --continue --scan
- name: KMP Smoke Compile
run: ./gradlew :core:proto:compileKotlinJvm :core:common:compileKotlinJvm :core:model:compileKotlinJvm :core:repository:compileKotlinJvm :core:di:compileKotlinJvm :core:navigation:compileKotlinJvm :core:resources:compileKotlinJvm :core:datastore:compileKotlinJvm :core:database:compileKotlinJvm :core:domain:compileKotlinJvm :core:prefs:compileKotlinJvm :core:network:compileKotlinJvm :core:data:compileKotlinJvm :core:ble:compileKotlinJvm :core:nfc:compileKotlinJvm :core:service:compileKotlinJvm :core:testing:compileKotlinJvm :core:ui:compileKotlinJvm :feature:intro:compileKotlinJvm :feature:messaging:compileKotlinJvm :feature:connections:compileKotlinJvm :feature:map:compileKotlinJvm :feature:node:compileKotlinJvm :feature:settings:compileKotlinJvm :feature:firmware:compileKotlinJvm :core:proto:compileKotlinIosSimulatorArm64 :core:common:compileKotlinIosSimulatorArm64 :core:model:compileKotlinIosSimulatorArm64 :core:repository:compileKotlinIosSimulatorArm64 :core:di:compileKotlinIosSimulatorArm64 :core:navigation:compileKotlinIosSimulatorArm64 :core:resources:compileKotlinIosSimulatorArm64 :core:datastore:compileKotlinIosSimulatorArm64 :core:database:compileKotlinIosSimulatorArm64 :core:domain:compileKotlinIosSimulatorArm64 :core:prefs:compileKotlinIosSimulatorArm64 :core:network:compileKotlinIosSimulatorArm64 :core:data:compileKotlinIosSimulatorArm64 :core:ble:compileKotlinIosSimulatorArm64 :core:nfc:compileKotlinIosSimulatorArm64 :core:service:compileKotlinIosSimulatorArm64 :core:testing:compileKotlinIosSimulatorArm64 :core:ui:compileKotlinIosSimulatorArm64 :feature:intro:compileKotlinIosSimulatorArm64 :feature:messaging:compileKotlinIosSimulatorArm64 :feature:connections:compileKotlinIosSimulatorArm64 :feature:map:compileKotlinIosSimulatorArm64 :feature:node:compileKotlinIosSimulatorArm64 :feature:settings:compileKotlinIosSimulatorArm64 :feature:firmware:compileKotlinIosSimulatorArm64 -Pci=true --continue --scan
run: ./gradlew kmpSmokeCompile -Pci=true --continue --scan
- name: Upload coverage results to Codecov
if: ${{ !cancelled() && inputs.run_unit_tests }}
@ -127,7 +134,7 @@ jobs:
retention-days: 7
android-check:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read
timeout-minutes: 60

View file

@ -7,7 +7,7 @@ on:
jobs:
update_assets:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
if: github.repository == 'meshtastic/Meshtastic-Android'
permissions:
contents: write # To commit files and push branches
@ -142,7 +142,7 @@ jobs:
check-workflow-status:
name: Check Workflow Status
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
permissions: {}
needs:
- update_assets

View file

@ -12,7 +12,7 @@ permissions:
jobs:
stale_issues:
name: Close Stale Issues
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
if: github.repository == 'meshtastic/Meshtastic-Android'
steps: