diff --git a/.github/actions/build-gazebo-sitl/action.yml b/.github/actions/build-gazebo-sitl/action.yml
new file mode 100644
index 0000000000..aae5a9565a
--- /dev/null
+++ b/.github/actions/build-gazebo-sitl/action.yml
@@ -0,0 +1,21 @@
+name: Build Gazebo Classic SITL
+description: Build PX4 firmware and Gazebo Classic plugins with ccache stats
+
+runs:
+ using: composite
+ steps:
+ - name: Build - PX4 Firmware (SITL)
+ shell: bash
+ run: make px4_sitl_default
+
+ - name: Cache - Stats after PX4 Firmware
+ shell: bash
+ run: ccache -s
+
+ - name: Build - Gazebo Classic Plugins
+ shell: bash
+ run: make px4_sitl_default sitl_gazebo-classic
+
+ - name: Cache - Stats after Gazebo Plugins
+ shell: bash
+ run: ccache -s
diff --git a/.github/actions/save-ccache/action.yml b/.github/actions/save-ccache/action.yml
new file mode 100644
index 0000000000..c4db6b8b18
--- /dev/null
+++ b/.github/actions/save-ccache/action.yml
@@ -0,0 +1,22 @@
+name: Save ccache
+description: Print ccache stats and save to cache
+
+inputs:
+ cache-primary-key:
+ description: Primary cache key from setup-ccache output
+ required: true
+
+runs:
+ using: composite
+ steps:
+ - name: Cache - Stats
+ if: always()
+ shell: bash
+ run: ccache -s
+
+ - name: Cache - Save ccache
+ if: always()
+ uses: actions/cache/save@v4
+ with:
+ path: ~/.ccache
+ key: ${{ inputs.cache-primary-key }}
diff --git a/.github/actions/setup-ccache/action.yml b/.github/actions/setup-ccache/action.yml
new file mode 100644
index 0000000000..f542c50d97
--- /dev/null
+++ b/.github/actions/setup-ccache/action.yml
@@ -0,0 +1,56 @@
+name: Setup ccache
+description: Restore ccache from cache and configure ccache.conf
+
+inputs:
+ cache-key-prefix:
+ description: Cache key prefix (e.g. ccache-sitl)
+ required: true
+ max-size:
+ description: Max ccache size (e.g. 300M)
+ required: false
+ default: '300M'
+ base-dir:
+ description: ccache base_dir value
+ required: false
+ default: '${GITHUB_WORKSPACE}'
+ install-ccache:
+ description: Install ccache via apt before configuring
+ required: false
+ default: 'false'
+
+outputs:
+ cache-primary-key:
+ description: Primary cache key (pass to save-ccache)
+ value: ${{ steps.restore.outputs.cache-primary-key }}
+
+runs:
+ using: composite
+ steps:
+ - name: Cache - Install ccache
+ if: inputs.install-ccache == 'true'
+ shell: bash
+ run: apt-get update && apt-get install -y ccache
+
+ - name: Cache - Restore ccache
+ id: restore
+ uses: actions/cache/restore@v4
+ with:
+ path: ~/.ccache
+ key: ${{ inputs.cache-key-prefix }}-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: |
+ ${{ inputs.cache-key-prefix }}-${{ github.ref_name }}-
+ ${{ inputs.cache-key-prefix }}-${{ github.base_ref || 'main' }}-
+ ${{ inputs.cache-key-prefix }}-
+
+ - name: Cache - Configure ccache
+ shell: bash
+ run: |
+ mkdir -p ~/.ccache
+ echo "base_dir = ${{ inputs.base-dir }}" > ~/.ccache/ccache.conf
+ echo "compression = true" >> ~/.ccache/ccache.conf
+ echo "compression_level = 6" >> ~/.ccache/ccache.conf
+ echo "max_size = ${{ inputs.max-size }}" >> ~/.ccache/ccache.conf
+ echo "hash_dir = false" >> ~/.ccache/ccache.conf
+ echo "compiler_check = content" >> ~/.ccache/ccache.conf
+ ccache -s
+ ccache -z
diff --git a/.github/ci-config.yml.example b/.github/ci-config.yml.example
new file mode 100644
index 0000000000..e3c0314915
--- /dev/null
+++ b/.github/ci-config.yml.example
@@ -0,0 +1,67 @@
+# PX4 CI Configuration for Forks
+# Copy to .github/ci-config.yml and customize.
+# All settings are optional — omit any key to use the upstream default.
+#
+# This example is tuned for GitHub-hosted runners.
+# The upstream PX4 repo uses RunsOn/AWS for all jobs.
+
+# Infrastructure hint — set to true when running on GitHub-hosted runners.
+# The upstream PX4 repo uses RunsOn/AWS (is_gha: false by default).
+# Jobs may use this flag to skip steps that require RunsOn-specific
+# infrastructure (e.g. S3 cache access, spot instance features).
+# No job behavior is gated on this today — it is wired for future use.
+is_gha: true
+
+# Runner labels — one label per size tier.
+# For GitHub-hosted runners, all tiers can point to ubuntu-latest.
+# For self-hosted runners, map each tier to the appropriate pool.
+runners:
+ small: '["ubuntu-latest"]'
+ medium: '["ubuntu-latest"]'
+ large: '["ubuntu-latest"]'
+ utility: '["ubuntu-latest"]'
+
+# Job toggles — enable only what your fork needs.
+# Jobs marked false below are upstream-specific (hardware boards,
+# platform-specific builds) and rarely useful for feature forks.
+jobs:
+ # T1 — gate checks (disable only if you have a very good reason)
+ gate_checks: true # format, newlines, module config validation
+ shellcheck: true # shell script linting
+ mavsdk_checks: true # mypy + flake8 on MAVSDK Python scripts
+
+ # T2 — core checks (recommended for all forks)
+ sitl_build: true
+ clang_tidy: true
+
+ # T2 — upstream-specific, disable for most forks
+ gazebo_classic_build: false # only needed if running sitl_tests/mavros_tests
+ ubuntu_builds: false # verifies clean builds on bare Ubuntu images
+ macos_build: false # macOS platform verification
+ itcm_check: false # NuttX board ITCM placement, board-specific
+ flash_analysis: false # binary size diffing against PR base, repo-specific
+ failsafe_sim: false # Emscripten web simulation build
+
+ # T3 — integration tests (expensive, disable unless you need them)
+ sitl_tests: false # requires gazebo_classic_build
+ ros_integration: false # requires Galactic ROS container
+ mavros_tests: false # requires gazebo_classic_build
+ ros_translation_node: true # lightweight, runs on standard ROS images
+
+# Cache sizes — adjust if you hit your cache storage limit.
+# GitHub-hosted runners share a 10 GB per-repo native cache.
+# With the job set above (~60% of jobs disabled), total warm footprint
+# is roughly 15-20% of the 10 GB limit per concurrent PR.
+# If you re-enable more jobs, total footprint scales up proportionally.
+# Raise your repo's cache limit if needed:
+# Settings → Actions → Caches (up to 10 TB configurable)
+cache:
+ sitl: 300M # ~94% fill at current default — do not lower
+ sitl_gazebo_classic: 350M # only relevant if gazebo_classic_build: true
+ clang_tidy: 150M # ~40% fill, sized with headroom
+ ubuntu_builds: 200M # only relevant if ubuntu_builds: true
+ macos: 200M # only relevant if macos_build: true
+ itcm: 200M # only relevant if itcm_check: true, growing
+ flash: 200M # only relevant if flash_analysis: true
+ ros_integration: 400M # ~70% fill, trending up
+ ros_translation: 150M # ~35% fill, sized with headroom
diff --git a/.github/workflows/build_all_targets.yml b/.github/workflows/build_all_targets.yml
index 4c0bc0fcb9..db994f7cb3 100644
--- a/.github/workflows/build_all_targets.yml
+++ b/.github/workflows/build_all_targets.yml
@@ -37,21 +37,19 @@
name: Build all targets
on:
+ # Triggered by CI orchestrator for all branches/PRs (after all tiers pass)
+ workflow_run:
+ workflows: ["CI Pipeline (Orchestrator)"]
+ types: [completed]
+ branches: ['**']
+ # Direct trigger for tagged releases (orchestrator doesn't run on tags)
push:
tags:
- 'v*'
- branches:
- - 'main'
- - 'stable'
- - 'beta'
- - 'release/**'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
paths-ignore:
- 'docs/**'
+ # Manual trigger for debugging
+ workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -65,14 +63,27 @@ permissions:
jobs:
group_targets:
name: Scan for Board Targets
+ # Only run if:
+ # 1. Direct push to tag (independent trigger for releases), OR
+ # 2. Orchestrator workflow_run completed successfully (for all branches/PRs), OR
+ # 3. Manual workflow_dispatch
+ if: |
+ github.event_name == 'push' ||
+ github.event_name == 'workflow_dispatch' ||
+ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
# runs-on: ubuntu-latest
runs-on: [runs-on,runner=1cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}",spot=false]
+ permissions:
+ contents: read
+ actions: read
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
timestamp: ${{ steps.set-timestamp.outputs.timestamp }}
branchname: ${{ steps.set-branch.outputs.branchname }}
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
- name: Cache Python pip
uses: actions/cache@v4
@@ -102,12 +113,13 @@ jobs:
- id: set-branch
name: Save Current Branch Name
run: |
- echo "branchname=${{
- github.event_name == 'pull_request' &&
- format('pr-{0}', github.event.pull_request.number) ||
- github.head_ref ||
- github.ref_name
- }}" >> $GITHUB_OUTPUT
+ if [ "${{ github.event_name }}" = "workflow_run" ]; then
+ # For workflow_run events, get branch from the triggering workflow
+ echo "branchname=${{ github.event.workflow_run.head_branch }}" >> $GITHUB_OUTPUT
+ else
+ # For push/workflow_dispatch events
+ echo "branchname=${{ github.head_ref || github.ref_name }}" >> $GITHUB_OUTPUT
+ fi
- name: Debug Matrix Output
if: runner.debug == '1'
@@ -121,6 +133,9 @@ jobs:
# runs-on: ubuntu-latest
runs-on: [runs-on,"runner=8cpu-linux-${{ matrix.runner }}","image=ubuntu24-full-${{ matrix.runner }}","run-id=${{ github.run_id }}",spot=false]
needs: group_targets
+ permissions:
+ contents: read
+ packages: read
strategy:
matrix: ${{ fromJson(needs.group_targets.outputs.matrix) }}
fail-fast: false
@@ -133,6 +148,7 @@ jobs:
- uses: runs-on/action@v2
- uses: actions/checkout@v4
with:
+ ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
fetch-depth: 0
- name: Git ownership workaround
@@ -207,6 +223,8 @@ jobs:
runs-on: [runs-on,runner=1cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}",spot=false]
needs: [setup, group_targets]
if: startsWith(github.ref, 'refs/tags/v') || contains(fromJSON('["main","stable","beta"]'), needs.group_targets.outputs.branchname)
+ permissions:
+ contents: write
outputs:
uploadlocation: ${{ steps.upload-location.outputs.uploadlocation }}
steps:
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
deleted file mode 100644
index e43feee8b3..0000000000
--- a/.github/workflows/checks.yml
+++ /dev/null
@@ -1,60 +0,0 @@
-name: Checks
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- container:
- image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
-
- strategy:
- fail-fast: false
- matrix:
- check: [
- "check_format",
- "check_newlines",
- "tests",
- "tests_coverage",
- "px4_fmu-v2_default stack_check",
- "validate_module_configs",
- "shellcheck_all",
- "NO_NINJA_BUILD=1 px4_fmu-v5_default",
- "NO_NINJA_BUILD=1 px4_sitl_default",
- "px4_sitl_allyes",
- "module_documentation",
- ]
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Building [${{ matrix.check }}]
- run: |
- cd "$GITHUB_WORKSPACE"
- git config --global --add safe.directory "$GITHUB_WORKSPACE"
- make ${{ matrix.check }}
-
- - name: Uploading Coverage to Codecov.io
- if: contains(matrix.check, 'coverage')
- uses: codecov/codecov-action@v1
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- flags: unittests
- file: coverage/lcov.info
diff --git a/.github/workflows/ci-orchestrator.yml b/.github/workflows/ci-orchestrator.yml
new file mode 100644
index 0000000000..7d32166253
--- /dev/null
+++ b/.github/workflows/ci-orchestrator.yml
@@ -0,0 +1,1342 @@
+name: CI Pipeline (Orchestrator)
+
+on:
+ push:
+ branches:
+ - 'main'
+ - 'stable'
+ - 'beta'
+ - 'release/**'
+ paths-ignore:
+ - 'docs/**'
+ pull_request:
+ branches:
+ - '**'
+ paths-ignore:
+ - 'docs/**'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+ packages: read
+ pull-requests: write
+
+env:
+ MIN_FLASH_POS_DIFF_FOR_COMMENT: 50
+ MIN_FLASH_NEG_DIFF_FOR_COMMENT: -50
+
+jobs:
+ # ═══════════════════════════════════════════════════════════════════
+ # CONFIG: Load CI configuration (runner labels, toggles, cache sizes)
+ # Root of the DAG — all jobs depend on this. Forks override via .github/ci-config.yml.
+ # ═══════════════════════════════════════════════════════════════════
+
+ load-config:
+ name: "Load CI Configuration"
+ runs-on: ubuntu-latest
+ outputs:
+ # Runner labels (JSON arrays for runs-on)
+ # Upstream defaults: RunsOn/AWS. Forks override to github-hosted or self-hosted.
+ runner_small: ${{ steps.config.outputs.runner_small }}
+ runner_medium: ${{ steps.config.outputs.runner_medium }}
+ runner_large: ${{ steps.config.outputs.runner_large }}
+ runner_utility: ${{ steps.config.outputs.runner_utility }}
+ # Cache max sizes
+ cache_max_size_sitl: ${{ steps.config.outputs.cache_max_size_sitl }}
+ cache_max_size_sitl_gazebo: ${{ steps.config.outputs.cache_max_size_sitl_gazebo }}
+ cache_max_size_clang_tidy: ${{ steps.config.outputs.cache_max_size_clang_tidy }}
+ cache_max_size_ubuntu: ${{ steps.config.outputs.cache_max_size_ubuntu }}
+ cache_max_size_macos: ${{ steps.config.outputs.cache_max_size_macos }}
+ cache_max_size_itcm: ${{ steps.config.outputs.cache_max_size_itcm }}
+ cache_max_size_flash: ${{ steps.config.outputs.cache_max_size_flash }}
+ cache_max_size_ros_integration: ${{ steps.config.outputs.cache_max_size_ros_integration }}
+ cache_max_size_ros_translation: ${{ steps.config.outputs.cache_max_size_ros_translation }}
+ # Infrastructure hint
+ is_gha: ${{ steps.config.outputs.is_gha }}
+ # Job toggles
+ enable_gate_checks: ${{ steps.config.outputs.enable_gate_checks }}
+ enable_shellcheck: ${{ steps.config.outputs.enable_shellcheck }}
+ enable_mavsdk_checks: ${{ steps.config.outputs.enable_mavsdk_checks }}
+ enable_macos_build: ${{ steps.config.outputs.enable_macos_build }}
+ enable_clang_tidy: ${{ steps.config.outputs.enable_clang_tidy }}
+ enable_ubuntu_builds: ${{ steps.config.outputs.enable_ubuntu_builds }}
+ enable_itcm_check: ${{ steps.config.outputs.enable_itcm_check }}
+ enable_flash_analysis: ${{ steps.config.outputs.enable_flash_analysis }}
+ enable_failsafe_sim: ${{ steps.config.outputs.enable_failsafe_sim }}
+ enable_sitl_build: ${{ steps.config.outputs.enable_sitl_build }}
+ enable_gazebo_classic_build: ${{ steps.config.outputs.enable_gazebo_classic_build }}
+ enable_sitl_tests: ${{ steps.config.outputs.enable_sitl_tests }}
+ enable_ros_integration: ${{ steps.config.outputs.enable_ros_integration }}
+ enable_mavros_tests: ${{ steps.config.outputs.enable_mavros_tests }}
+ enable_ros_translation_node: ${{ steps.config.outputs.enable_ros_translation_node }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ sparse-checkout: .github/ci-config.yml
+ sparse-checkout-cone-mode: false
+ fetch-depth: 1
+
+ - name: Load CI Config
+ id: config
+ run: |
+ # Defaults — all enabled, RunsOn AWS runners
+ RUNNER_SMALL='["runs-on","runner=4cpu-linux-x64","image=ubuntu24-full-x64","run-id=${{ github.run_id }}","spot=false","extras=s3-cache"]'
+ RUNNER_MEDIUM='["runs-on","runner=8cpu-linux-x64","image=ubuntu24-full-x64","run-id=${{ github.run_id }}","spot=false","extras=s3-cache"]'
+ RUNNER_LARGE='["runs-on","runner=16cpu-linux-x64","image=ubuntu24-full-x64","run-id=${{ github.run_id }}","spot=false","extras=s3-cache"]'
+ RUNNER_UTILITY='["runs-on","runner=1cpu-linux-x64","image=ubuntu24-full-x64","run-id=${{ github.run_id }}"]'
+ IS_GHA=false
+ ENABLE_GATE_CHECKS=true
+ ENABLE_SHELLCHECK=true
+ ENABLE_MAVSDK_CHECKS=true
+ ENABLE_MACOS_BUILD=true
+ ENABLE_CLANG_TIDY=true
+ ENABLE_UBUNTU_BUILDS=true
+ ENABLE_ITCM_CHECK=true
+ ENABLE_FLASH_ANALYSIS=true
+ ENABLE_FAILSAFE_SIM=true
+ ENABLE_SITL_BUILD=true
+ ENABLE_GAZEBO_CLASSIC_BUILD=true
+ ENABLE_SITL_TESTS=true
+ ENABLE_ROS_INTEGRATION=true
+ ENABLE_MAVROS_TESTS=true
+ ENABLE_ROS_TRANSLATION_NODE=true
+ CACHE_SITL=300M
+ CACHE_SITL_GAZEBO=350M
+ CACHE_CLANG_TIDY=150M
+ CACHE_UBUNTU=200M
+ CACHE_MACOS=200M
+ CACHE_ITCM=200M
+ CACHE_FLASH=200M
+ CACHE_ROS_INTEGRATION=400M
+ CACHE_ROS_TRANSLATION=150M
+
+ # Override from .github/ci-config.yml if present
+ if [ -f .github/ci-config.yml ]; then
+ echo "Found .github/ci-config.yml — applying overrides"
+ # mikefarah/yq (Go) is pre-installed on GHA ubuntu runners;
+ # python-yq wraps jq and uses "// empty" — Go yq does not.
+ # With Go yq: eval '.key' returns "null" for missing keys,
+ # so we filter that out to get the same behavior.
+ get() {
+ local val
+ val=$(yq eval ".$1" .github/ci-config.yml 2>/dev/null)
+ if [ "$val" = "null" ] || [ -z "$val" ]; then
+ echo ""
+ else
+ echo "$val"
+ fi
+ }
+ v=$(get is_gha); [ -n "$v" ] && IS_GHA="$v"
+ v=$(get runners.small); [ -n "$v" ] && RUNNER_SMALL="$v"
+ v=$(get runners.medium); [ -n "$v" ] && RUNNER_MEDIUM="$v"
+ v=$(get runners.large); [ -n "$v" ] && RUNNER_LARGE="$v"
+ v=$(get runners.utility); [ -n "$v" ] && RUNNER_UTILITY="$v"
+ v=$(get jobs.gate_checks); [ -n "$v" ] && ENABLE_GATE_CHECKS="$v"
+ v=$(get jobs.shellcheck); [ -n "$v" ] && ENABLE_SHELLCHECK="$v"
+ v=$(get jobs.mavsdk_checks); [ -n "$v" ] && ENABLE_MAVSDK_CHECKS="$v"
+ v=$(get jobs.macos_build); [ -n "$v" ] && ENABLE_MACOS_BUILD="$v"
+ v=$(get jobs.clang_tidy); [ -n "$v" ] && ENABLE_CLANG_TIDY="$v"
+ v=$(get jobs.ubuntu_builds); [ -n "$v" ] && ENABLE_UBUNTU_BUILDS="$v"
+ v=$(get jobs.itcm_check); [ -n "$v" ] && ENABLE_ITCM_CHECK="$v"
+ v=$(get jobs.flash_analysis); [ -n "$v" ] && ENABLE_FLASH_ANALYSIS="$v"
+ v=$(get jobs.failsafe_sim); [ -n "$v" ] && ENABLE_FAILSAFE_SIM="$v"
+ v=$(get jobs.sitl_build); [ -n "$v" ] && ENABLE_SITL_BUILD="$v"
+ v=$(get jobs.gazebo_classic_build); [ -n "$v" ] && ENABLE_GAZEBO_CLASSIC_BUILD="$v"
+ v=$(get jobs.sitl_tests); [ -n "$v" ] && ENABLE_SITL_TESTS="$v"
+ v=$(get jobs.ros_integration); [ -n "$v" ] && ENABLE_ROS_INTEGRATION="$v"
+ v=$(get jobs.mavros_tests); [ -n "$v" ] && ENABLE_MAVROS_TESTS="$v"
+ v=$(get jobs.ros_translation_node); [ -n "$v" ] && ENABLE_ROS_TRANSLATION_NODE="$v"
+ v=$(get cache.sitl); [ -n "$v" ] && CACHE_SITL="$v"
+ v=$(get cache.sitl_gazebo_classic); [ -n "$v" ] && CACHE_SITL_GAZEBO="$v"
+ v=$(get cache.clang_tidy); [ -n "$v" ] && CACHE_CLANG_TIDY="$v"
+ v=$(get cache.ubuntu_builds); [ -n "$v" ] && CACHE_UBUNTU="$v"
+ v=$(get cache.macos); [ -n "$v" ] && CACHE_MACOS="$v"
+ v=$(get cache.itcm); [ -n "$v" ] && CACHE_ITCM="$v"
+ v=$(get cache.flash); [ -n "$v" ] && CACHE_FLASH="$v"
+ v=$(get cache.ros_integration); [ -n "$v" ] && CACHE_ROS_INTEGRATION="$v"
+ v=$(get cache.ros_translation); [ -n "$v" ] && CACHE_ROS_TRANSLATION="$v"
+ else
+ echo "No .github/ci-config.yml found — using defaults"
+ fi
+
+ echo "is_gha=$IS_GHA" >> $GITHUB_OUTPUT
+ echo "runner_small=$RUNNER_SMALL" >> $GITHUB_OUTPUT
+ echo "runner_medium=$RUNNER_MEDIUM" >> $GITHUB_OUTPUT
+ echo "runner_large=$RUNNER_LARGE" >> $GITHUB_OUTPUT
+ echo "runner_utility=$RUNNER_UTILITY" >> $GITHUB_OUTPUT
+ echo "enable_gate_checks=$ENABLE_GATE_CHECKS" >> $GITHUB_OUTPUT
+ echo "enable_shellcheck=$ENABLE_SHELLCHECK" >> $GITHUB_OUTPUT
+ echo "enable_mavsdk_checks=$ENABLE_MAVSDK_CHECKS" >> $GITHUB_OUTPUT
+ echo "enable_macos_build=$ENABLE_MACOS_BUILD" >> $GITHUB_OUTPUT
+ echo "enable_clang_tidy=$ENABLE_CLANG_TIDY" >> $GITHUB_OUTPUT
+ echo "enable_ubuntu_builds=$ENABLE_UBUNTU_BUILDS" >> $GITHUB_OUTPUT
+ echo "enable_itcm_check=$ENABLE_ITCM_CHECK" >> $GITHUB_OUTPUT
+ echo "enable_flash_analysis=$ENABLE_FLASH_ANALYSIS" >> $GITHUB_OUTPUT
+ echo "enable_failsafe_sim=$ENABLE_FAILSAFE_SIM" >> $GITHUB_OUTPUT
+ echo "enable_sitl_build=$ENABLE_SITL_BUILD" >> $GITHUB_OUTPUT
+ echo "enable_gazebo_classic_build=$ENABLE_GAZEBO_CLASSIC_BUILD" >> $GITHUB_OUTPUT
+ echo "enable_sitl_tests=$ENABLE_SITL_TESTS" >> $GITHUB_OUTPUT
+ echo "enable_ros_integration=$ENABLE_ROS_INTEGRATION" >> $GITHUB_OUTPUT
+ echo "enable_mavros_tests=$ENABLE_MAVROS_TESTS" >> $GITHUB_OUTPUT
+ echo "enable_ros_translation_node=$ENABLE_ROS_TRANSLATION_NODE" >> $GITHUB_OUTPUT
+ echo "cache_max_size_sitl=$CACHE_SITL" >> $GITHUB_OUTPUT
+ echo "cache_max_size_sitl_gazebo=$CACHE_SITL_GAZEBO" >> $GITHUB_OUTPUT
+ echo "cache_max_size_clang_tidy=$CACHE_CLANG_TIDY" >> $GITHUB_OUTPUT
+ echo "cache_max_size_ubuntu=$CACHE_UBUNTU" >> $GITHUB_OUTPUT
+ echo "cache_max_size_macos=$CACHE_MACOS" >> $GITHUB_OUTPUT
+ echo "cache_max_size_itcm=$CACHE_ITCM" >> $GITHUB_OUTPUT
+ echo "cache_max_size_flash=$CACHE_FLASH" >> $GITHUB_OUTPUT
+ echo "cache_max_size_ros_integration=$CACHE_ROS_INTEGRATION" >> $GITHUB_OUTPUT
+ echo "cache_max_size_ros_translation=$CACHE_ROS_TRANSLATION" >> $GITHUB_OUTPUT
+
+ # ═══════════════════════════════════════════════════════════════════
+ # TIER 1: Quick Gate Checks
+ # Fastest checks that catch most common errors. Failures stop all downstream tiers.
+ # ═══════════════════════════════════════════════════════════════════
+
+ gate-checks:
+ name: "T1: Gate Checks [${{ matrix.check }}]"
+ needs: [load-config]
+ if: needs.load-config.outputs.enable_gate_checks == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_utility) }}
+ permissions:
+ contents: read
+ packages: read
+ container:
+ image: ghcr.io/px4/px4-dev:v1.17.0-beta1
+ strategy:
+ fail-fast: true # Stop all jobs immediately on first failure
+ matrix:
+ check: [
+ "check_format",
+ "check_newlines",
+ "validate_module_configs"
+ ]
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - name: Test - Run ${{ matrix.check }}
+ run: make ${{ matrix.check }}
+
+ shellcheck:
+ name: "T1: Shellcheck"
+ needs: [load-config]
+ if: needs.load-config.outputs.enable_shellcheck == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_utility) }}
+ permissions:
+ contents: read
+ packages: read
+ container:
+ image: ghcr.io/px4/px4-dev:v1.17.0-beta1
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - name: Test - Run shellcheck_all
+ run: make shellcheck_all
+
+ mavsdk-python-checks:
+ name: "T1: MAVSDK Python [${{ matrix.check }}]"
+ needs: [load-config]
+ if: needs.load-config.outputs.enable_mavsdk_checks == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_utility) }}
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: true
+ matrix:
+ check: ["mypy", "flake8"]
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Install Python 3.10
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.10"
+
+ - name: Setup - Install Python Linters
+ run: pip install mypy types-requests flake8
+
+ - name: Test - Run mypy on MAVSDK Scripts
+ if: matrix.check == 'mypy'
+ run: mypy --strict test/mavsdk_tests/*.py
+
+ - name: Test - Run flake8 on MAVSDK Scripts
+ if: matrix.check == 'flake8'
+ run: flake8 test/mavsdk_tests/*.py
+
+ # ═══════════════════════════════════════════════════════════════════
+ # TIER 2: Builds, Analysis & Platform Checks
+ # Runs after Tier 1. Runner labels, cache sizes, and job toggles come from load-config.
+ # ═══════════════════════════════════════════════════════════════════
+
+ build-sitl:
+ name: "T2: Build SITL Cache Seed"
+ needs: [load-config, gate-checks, shellcheck, mavsdk-python-checks]
+ if: needs.load-config.outputs.enable_sitl_build == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_small) }}
+ permissions:
+ contents: read
+ container:
+ image: ghcr.io/px4/px4-dev:v1.17.0-beta1
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - uses: ./.github/actions/setup-ccache
+ id: ccache
+ with:
+ cache-key-prefix: ccache-sitl
+ max-size: ${{ needs.load-config.outputs.cache_max_size_sitl }}
+
+ - name: Build - px4_sitl_default
+ run: make px4_sitl_default
+
+ - uses: ./.github/actions/save-ccache
+ with:
+ cache-primary-key: ${{ steps.ccache.outputs.cache-primary-key }}
+
+ build-sitl-gazebo-classic:
+ name: "T2: Build SITL Gazebo Classic Cache Seed"
+ needs: [load-config, gate-checks, shellcheck, mavsdk-python-checks]
+ if: needs.load-config.outputs.enable_gazebo_classic_build == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_small) }}
+ permissions:
+ contents: read
+ container:
+ image: px4io/px4-dev-simulation-focal:2021-09-08
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - uses: ./.github/actions/setup-ccache
+ id: ccache
+ with:
+ cache-key-prefix: ccache-sitl-gazebo-classic
+ max-size: ${{ needs.load-config.outputs.cache_max_size_sitl_gazebo }}
+
+ - name: Build - px4_sitl_default
+ run: make px4_sitl_default
+
+ - name: Cache - Stats after PX4 Firmware
+ run: ccache -s
+
+ - name: Build - Gazebo Classic Plugins
+ run: make px4_sitl_default sitl_gazebo-classic
+
+ - uses: ./.github/actions/save-ccache
+ with:
+ cache-primary-key: ${{ steps.ccache.outputs.cache-primary-key }}
+
+ basic-tests:
+ name: "T2: Unit Tests"
+ needs: [load-config, gate-checks, shellcheck, mavsdk-python-checks, build-sitl]
+ if: needs.load-config.outputs.enable_sitl_build == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_small) }}
+ permissions:
+ contents: write # needed for auto-commit of EKF CSVs on push to protected branches
+ env:
+ GIT_COMMITTER_EMAIL: bot@px4.io
+ GIT_COMMITTER_NAME: PX4BuildBot
+ container:
+ image: ghcr.io/px4/px4-dev:v1.17.0-beta1
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - uses: ./.github/actions/setup-ccache
+ with:
+ cache-key-prefix: ccache-sitl
+ max-size: ${{ needs.load-config.outputs.cache_max_size_sitl }}
+
+ - name: Build - tests
+ run: make tests
+
+ - name: Commit - Auto-update EKF change indication baselines
+ if: github.event_name == 'push'
+ uses: stefanzweifel/git-auto-commit-action@v7
+ with:
+ file_pattern: 'src/modules/ekf2/test/change_indication/*.csv'
+ commit_user_name: ${{ env.GIT_COMMITTER_NAME }}
+ commit_user_email: ${{ env.GIT_COMMITTER_EMAIL }}
+ commit_message: |
+ [AUTO COMMIT] update EKF change indication
+
+ See .github/workflows/ci-orchestrator.yml for more details
+
+ - name: Validate - Check for EKF Functional Changes
+ run: git diff --exit-code
+ working-directory: src/modules/ekf2/test/change_indication
+
+ - name: Build - module_documentation
+ run: make module_documentation
+
+ - name: Cache - Stats
+ run: ccache -s
+
+ clang-tidy:
+ name: "T2: Clang-Tidy Static Analysis"
+ needs: [load-config, gate-checks, shellcheck, mavsdk-python-checks]
+ if: needs.load-config.outputs.enable_clang_tidy == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_large) }}
+ permissions:
+ contents: read
+ outputs:
+ has_findings: ${{ steps.clang_tidy.outputs.has_findings }}
+ container:
+ image: ghcr.io/px4/px4-dev:v1.17.0-beta1
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - uses: ./.github/actions/setup-ccache
+ id: ccache
+ with:
+ cache-key-prefix: ccache-clang-tidy
+ max-size: ${{ needs.load-config.outputs.cache_max_size_clang_tidy }}
+
+ - name: Build - px4_sitl_default (Clang)
+ run: make -j16 px4_sitl_default-clang
+
+ - name: Cache - Stats after Build
+ run: ccache -s
+
+ - name: Analysis - Run Clang-Tidy
+ id: clang_tidy
+ run: |
+ if [ "${{ github.event_name }}" != "pull_request" ]; then
+ make -j$(nproc) clang-tidy
+ echo "has_findings=false" >> $GITHUB_OUTPUT
+ else
+ output=$(python3 Tools/ci/run-clang-tidy-pr.py origin/${{ github.base_ref }} 2>&1)
+ exit_code=$?
+ echo "$output"
+ # Script prints this message on both early-exit paths (no changed files / no TUs)
+ if echo "$output" | grep -q "skipping clang-tidy"; then
+ echo "has_findings=false" >> $GITHUB_OUTPUT
+ else
+ echo "has_findings=true" >> $GITHUB_OUTPUT
+ fi
+ exit $exit_code
+ fi
+
+ - name: Upload - compile_commands.json
+ if: always() && github.event_name == 'pull_request'
+ uses: actions/upload-artifact@v4
+ with:
+ name: compile-commands
+ path: build/px4_sitl_default-clang/compile_commands.json
+ retention-days: 1
+
+ - uses: ./.github/actions/save-ccache
+ with:
+ cache-primary-key: ${{ steps.ccache.outputs.cache-primary-key }}
+
+ ubuntu-builds:
+ name: "T2: Ubuntu Build [${{ matrix.container }}]"
+ needs: [load-config, gate-checks, shellcheck, mavsdk-python-checks]
+ if: needs.load-config.outputs.enable_ubuntu_builds == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_small) }}
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ container: [
+ "ubuntu:22.04",
+ "ubuntu:24.04"
+ ]
+ container:
+ image: ${{ matrix.container }}
+ volumes:
+ - /github/workspace:/github/workspace
+ env:
+ RUNS_IN_DOCKER: true
+ steps:
+ - uses: runs-on/action@v2
+
+ - name: Setup - Install Git and Configure Safe Directory
+ run: |
+ apt update && apt install git -y
+ git config --global --add safe.directory $(realpath .)
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Install PX4 Dependencies
+ run: |
+ ./Tools/setup/ubuntu.sh --no-nuttx --no-sim-tools
+
+ - uses: ./.github/actions/setup-ccache
+ id: ccache
+ with:
+ cache-key-prefix: ccache-ubuntu-${{ matrix.container }}
+ max-size: ${{ needs.load-config.outputs.cache_max_size_ubuntu }}
+
+ - name: Build - px4_sitl_default
+ run: make px4_sitl_default
+
+ - uses: ./.github/actions/save-ccache
+ with:
+ cache-primary-key: ${{ steps.ccache.outputs.cache-primary-key }}
+
+ macos-build:
+ name: "T2: macOS Build"
+ needs: [load-config, gate-checks, shellcheck, mavsdk-python-checks]
+ if: needs.load-config.outputs.enable_macos_build == 'true'
+ runs-on: macos-latest
+ permissions:
+ contents: read
+ steps:
+ - name: Setup - Install Python 3.10
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.10"
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Cache - Restore Homebrew Packages
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/Library/Caches/Homebrew/downloads
+ key: macos-homebrew-${{ runner.arch }}-${{ hashFiles('Tools/setup/macos.sh') }}
+ restore-keys: |
+ macos-homebrew-${{ runner.arch }}-
+
+ - name: Cache - Restore pip Packages
+ uses: actions/cache@v4
+ with:
+ path: ~/Library/Caches/pip
+ key: macos-pip-${{ runner.arch }}-${{ hashFiles('Tools/setup/requirements.txt') }}
+ restore-keys: |
+ macos-pip-${{ runner.arch }}-
+
+ - name: Setup - Install macOS Dependencies
+ run: ./Tools/setup/macos.sh
+
+ - uses: ./.github/actions/setup-ccache
+ id: ccache
+ with:
+ cache-key-prefix: ccache-macos
+ max-size: ${{ needs.load-config.outputs.cache_max_size_macos }}
+
+ - name: Build - px4_sitl (macOS)
+ run: make px4_sitl
+
+ - name: Cache - Stats after SITL
+ run: ccache -s
+
+ - name: Build - px4_fmu-v5_default
+ run: make px4_fmu-v5_default
+
+ - uses: ./.github/actions/save-ccache
+ with:
+ cache-primary-key: ${{ steps.ccache.outputs.cache-primary-key }}
+
+ itcm-check:
+ name: "T2: ITCM Check [${{ matrix.target }}]"
+ needs: [load-config, gate-checks, shellcheck, mavsdk-python-checks]
+ if: needs.load-config.outputs.enable_itcm_check == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_small) }}
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - target: px4_fmu-v5x_default
+ scripts: >
+ boards/px4/fmu-v5x/nuttx-config/scripts/itcm_gen_functions.ld
+ boards/px4/fmu-v5x/nuttx-config/scripts/itcm_static_functions.ld
+ - target: px4_fmu-v6xrt_default
+ scripts: >
+ boards/px4/fmu-v6xrt/nuttx-config/scripts/itcm_functions_includes.ld
+ boards/px4/fmu-v6xrt/nuttx-config/scripts/itcm_static_functions.ld
+ - target: nxp_tropic-community_default
+ scripts: >
+ boards/nxp/tropic-community/nuttx-config/scripts/itcm_functions_includes.ld
+ boards/nxp/tropic-community/nuttx-config/scripts/itcm_static_functions.ld
+ - target: nxp_mr-tropic_default
+ scripts: >
+ boards/nxp/mr-tropic/nuttx-config/scripts/itcm_functions_includes.ld
+ boards/nxp/mr-tropic/nuttx-config/scripts/itcm_static_functions.ld
+ container:
+ image: ghcr.io/px4/px4-dev:v1.17.0-beta1
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - uses: ./.github/actions/setup-ccache
+ id: ccache
+ with:
+ cache-key-prefix: ccache-itcm-${{ matrix.target }}
+ max-size: ${{ needs.load-config.outputs.cache_max_size_itcm }}
+
+ - name: Build - ${{ matrix.target }}
+ run: make ${{ matrix.target }}
+
+ - name: Setup - Copy Built ELF Binary
+ run: cp ./build/**/*.elf ./built.elf
+
+ - name: Setup - Install ITCM Check Dependencies
+ run: pip3 install -r Tools/setup/optional-requirements.txt --break-system-packages
+
+ - name: Analyze - Run ITCM Placement Check
+ run: python3 Tools/itcm_check.py --elf-file built.elf --script-files ${{ matrix.scripts }}
+
+ - uses: ./.github/actions/save-ccache
+ with:
+ cache-primary-key: ${{ steps.ccache.outputs.cache-primary-key }}
+
+ flash-analysis:
+ name: "T2: Flash Analysis [${{ matrix.target }}]"
+ needs: [load-config, gate-checks, shellcheck, mavsdk-python-checks]
+ if: needs.load-config.outputs.enable_flash_analysis == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_small) }}
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ target: [px4_fmu-v5x, px4_fmu-v6x]
+ outputs:
+ px4_fmu-v5x-bloaty-output: ${{ steps.gen-output.outputs.px4_fmu-v5x-bloaty-output }}
+ px4_fmu-v5x-bloaty-summary-map: ${{ steps.gen-output.outputs.px4_fmu-v5x-bloaty-summary-map }}
+ px4_fmu-v6x-bloaty-output: ${{ steps.gen-output.outputs.px4_fmu-v6x-bloaty-output }}
+ px4_fmu-v6x-bloaty-summary-map: ${{ steps.gen-output.outputs.px4_fmu-v6x-bloaty-summary-map }}
+ container:
+ image: ghcr.io/px4/px4-dev:v1.17.0-beta1
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ # ── Current build (PR head) ──────────────────────────────────
+ - name: Cache - Restore ccache (current)
+ id: cache_current
+ uses: actions/cache/restore@v4
+ with:
+ path: ~/.ccache
+ key: ccache-flash-${{ matrix.target }}-current-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: |
+ ccache-flash-${{ matrix.target }}-current-${{ github.ref_name }}-
+ ccache-flash-${{ matrix.target }}-current-
+
+ - name: Cache - Configure ccache
+ run: |
+ mkdir -p ~/.ccache
+ echo "base_dir = ${GITHUB_WORKSPACE}" > ~/.ccache/ccache.conf
+ echo "compression = true" >> ~/.ccache/ccache.conf
+ echo "compression_level = 6" >> ~/.ccache/ccache.conf
+ echo "max_size = ${{ needs.load-config.outputs.cache_max_size_flash }}" >> ~/.ccache/ccache.conf
+ echo "hash_dir = false" >> ~/.ccache/ccache.conf
+ echo "compiler_check = content" >> ~/.ccache/ccache.conf
+ ccache -s
+ ccache -z
+
+ - name: Build - ${{ matrix.target }} (Current)
+ run: make ${{ matrix.target }}_flash-analysis
+
+ - name: Cache - Stats after Current Build
+ run: ccache -s
+
+ - name: Cache - Save ccache (current)
+ if: always()
+ uses: actions/cache/save@v4
+ with:
+ path: ~/.ccache
+ key: ${{ steps.cache_current.outputs.cache-primary-key }}
+
+ - name: Analyze - Save Current ELF Binary
+ run: cp ./build/**/*.elf ./with-change.elf
+
+ # ── Baseline build (merge target) ───────────────────────────
+ - name: Setup - Clean Workspace for Baseline
+ run: |
+ make clean
+ make distclean
+ make submodulesclean
+ ccache -C
+
+ - name: Setup - Checkout PR Base Branch
+ if: ${{ github.event.pull_request }}
+ run: git checkout ${{ github.event.pull_request.base.ref }}
+
+ - name: Setup - Checkout Previous Commit
+ if: github.event_name == 'push'
+ run: git checkout ${{ github.event.before }}
+
+ - name: Setup - Resolve Baseline SHA
+ id: baseline_sha
+ run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
+
+ - name: Cache - Restore ccache (baseline)
+ id: cache_baseline
+ uses: actions/cache/restore@v4
+ with:
+ path: ~/.ccache
+ key: ccache-flash-${{ matrix.target }}-baseline-${{ steps.baseline_sha.outputs.sha }}
+ restore-keys: |
+ ccache-flash-${{ matrix.target }}-baseline-
+
+ - name: Cache - Reset ccache stats
+ run: ccache -z
+
+ - name: Setup - Update Submodules for Baseline
+ run: make submodulesupdate
+
+ - name: Build - ${{ matrix.target }} (Baseline)
+ run: make ${{ matrix.target }}_flash-analysis
+
+ - name: Cache - Stats after Baseline Build
+ run: ccache -s
+
+ - name: Cache - Save ccache (baseline)
+ if: always()
+ uses: actions/cache/save@v4
+ with:
+ path: ~/.ccache
+ key: ${{ steps.cache_baseline.outputs.cache-primary-key }}
+
+ # ── Compare ─────────────────────────────────────────────────
+ - name: Analyze - Save Baseline ELF Binary
+ run: cp ./build/**/*.elf ./before-change.elf
+
+ - name: Analyze - Compare Flash Usage (Bloaty)
+ uses: PX4/bloaty-action@v1.0.0
+ id: bloaty-step
+ with:
+ bloaty-file-args: ./with-change.elf -- ./before-change.elf
+ bloaty-additional-args: -d sections,symbols -s vm -n 20
+ output-to-summary: true
+
+ - name: Analyze - Export Results for PR Comment
+ id: gen-output
+ run: |
+ EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
+ echo "${{ matrix.target }}-bloaty-output<<$EOF" >> $GITHUB_OUTPUT
+ echo "${{ steps.bloaty-step.outputs.bloaty-output-encoded }}" >> $GITHUB_OUTPUT
+ echo "$EOF" >> $GITHUB_OUTPUT
+ echo "${{ matrix.target }}-bloaty-summary-map<<$EOF" >> $GITHUB_OUTPUT
+ echo '${{ steps.bloaty-step.outputs.bloaty-summary-map }}' >> $GITHUB_OUTPUT
+ echo "$EOF" >> $GITHUB_OUTPUT
+
+ post-flash-comment:
+ name: "T2: Publish Flash Analysis Results"
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_utility) }}
+ needs: [load-config, flash-analysis]
+ permissions:
+ pull-requests: write
+ env:
+ V5X-SUMMARY-MAP-ABS: ${{ fromJSON(fromJSON(needs.flash-analysis.outputs.px4_fmu-v5x-bloaty-summary-map).vm-absolute) }}
+ V5X-SUMMARY-MAP-PERC: ${{ fromJSON(fromJSON(needs.flash-analysis.outputs.px4_fmu-v5x-bloaty-summary-map).vm-percentage) }}
+ V6X-SUMMARY-MAP-ABS: ${{ fromJSON(fromJSON(needs.flash-analysis.outputs.px4_fmu-v6x-bloaty-summary-map).vm-absolute) }}
+ V6X-SUMMARY-MAP-PERC: ${{ fromJSON(fromJSON(needs.flash-analysis.outputs.px4_fmu-v6x-bloaty-summary-map).vm-percentage) }}
+ if: >-
+ needs.load-config.outputs.enable_flash_analysis == 'true'
+ && github.event.pull_request
+ && github.event.pull_request.head.repo.full_name == github.repository
+ steps:
+ - uses: runs-on/action@v2
+
+ - name: Setup - Find Existing Flash Comment
+ uses: peter-evans/find-comment@v3
+ id: fc
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ comment-author: 'github-actions[bot]'
+ body-includes: FLASH Analysis
+
+ - name: Setup - Generate Comment Timestamp
+ id: bt
+ run: |
+ echo "timestamp=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_OUTPUT
+
+ - name: Upload - Post Flash Analysis to PR
+ if: |
+ steps.fc.outputs.comment-id != '' ||
+ env.V5X-SUMMARY-MAP-ABS >= fromJSON(env.MIN_FLASH_POS_DIFF_FOR_COMMENT) ||
+ env.V5X-SUMMARY-MAP-ABS <= fromJSON(env.MIN_FLASH_NEG_DIFF_FOR_COMMENT) ||
+ env.V6X-SUMMARY-MAP-ABS >= fromJSON(env.MIN_FLASH_POS_DIFF_FOR_COMMENT) ||
+ env.V6X-SUMMARY-MAP-ABS <= fromJSON(env.MIN_FLASH_NEG_DIFF_FOR_COMMENT)
+ uses: peter-evans/create-or-update-comment@v4
+ with:
+ comment-id: ${{ steps.fc.outputs.comment-id }}
+ issue-number: ${{ github.event.pull_request.number }}
+ body: |
+ ## 🔎 FLASH Analysis
+
+ px4_fmu-v5x [Total VM Diff: ${{ env.V5X-SUMMARY-MAP-ABS }} byte (${{ env.V5X-SUMMARY-MAP-PERC}} %)]
+
+ ```
+ ${{ needs.flash-analysis.outputs.px4_fmu-v5x-bloaty-output }}
+ ```
+
+
+
+ px4_fmu-v6x [Total VM Diff: ${{ env.V6X-SUMMARY-MAP-ABS }} byte (${{ env.V6X-SUMMARY-MAP-PERC }} %)]
+
+ ```
+ ${{ needs.flash-analysis.outputs.px4_fmu-v6x-bloaty-output }}
+ ```
+
+
+ **Updated: _${{ steps.bt.outputs.timestamp }}_**
+ edit-mode: replace
+
+ post-clang-tidy-comments:
+ name: "T2: Clang-Tidy PR Annotations"
+ needs: [clang-tidy]
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ contents: read
+ if: >-
+ always()
+ && github.event.pull_request
+ && github.event.pull_request.head.repo.full_name == github.repository
+ && (needs.clang-tidy.result == 'success' || needs.clang-tidy.result == 'failure')
+ && needs.clang-tidy.outputs.has_findings == 'true'
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup - Install clang-tools (for clang-tidy-diff)
+ run: sudo apt-get install -y clang-tools
+
+ - name: Download - compile_commands.json
+ uses: actions/download-artifact@v4
+ with:
+ name: compile-commands
+ path: build/px4_sitl_default-clang
+
+ - name: Analysis - Run clang-tidy-diff and export fixes
+ run: |
+ # WHY WE REWRITE compile_commands.json PATHS
+ #
+ # The clang-tidy gate job runs on a RunsOn/AWS runner where the workspace
+ # root is "/__w/PX4-Autopilot/PX4-Autopilot". All absolute paths baked
+ # into compile_commands.json (the "file" and "directory" fields, and every
+ # -I include path in "command") use that prefix.
+ #
+ # This annotation job runs on a GitHub-hosted runner where the workspace
+ # root is "/home/runner/work/PX4-Autopilot/PX4-Autopilot". When
+ # clang-tidy-diff invokes clang-tidy, it reads the "directory" field from
+ # compile_commands.json and calls chdir() on it. Since the AWS-style path
+ # does not exist here, clang-tidy crashes with:
+ # LLVM ERROR: Cannot chdir into "/__w/.../build/px4_sitl_default-clang"
+ # and silently produces an empty fixes.yml — so no annotations are posted.
+ #
+ # Fix: rewrite all occurrences of the AWS workspace prefix to the current
+ # runner workspace ($GITHUB_WORKSPACE) before invoking clang-tidy-diff.
+ # This is safe because compile_commands.json is a local scratch file we
+ # downloaded from the artifact; we are not modifying any source file.
+ sed -i "s|/__w/PX4-Autopilot/PX4-Autopilot|${GITHUB_WORKSPACE}|g" \
+ build/px4_sitl_default-clang/compile_commands.json
+
+ mkdir -p clang-tidy-result
+ git diff -U0 origin/${{ github.base_ref }}...HEAD \
+ | clang-tidy-diff-18.py -p1 \
+ -path build/px4_sitl_default-clang \
+ -export-fixes clang-tidy-result/fixes.yml \
+ -j0 || true
+
+ - name: Post - Annotate PR with clang-tidy findings
+ uses: platisd/clang-tidy-pr-comments@v1
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ clang_tidy_fixes: clang-tidy-result/fixes.yml
+ request_changes: false
+ suggestions_per_comment: 10
+
+ failsafe-sim:
+ name: "T2: Failsafe Web Simulator"
+ needs: [load-config, gate-checks, shellcheck, mavsdk-python-checks]
+ if: needs.load-config.outputs.enable_failsafe_sim == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_small) }}
+ permissions:
+ contents: read
+ container:
+ image: ghcr.io/px4/px4-dev:v1.17.0-beta1
+ steps:
+ - uses: runs-on/action@v2
+
+ - name: Setup - Install Node.js v20.18.0
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.18.0
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - name: Cache - Restore Emscripten SDK
+ id: cache-emsdk
+ uses: actions/cache@v4
+ with:
+ path: _emscripten_sdk
+ key: emsdk-4.0.15
+
+ - name: Setup - Install Emscripten
+ if: steps.cache-emsdk.outputs.cache-hit != 'true'
+ run: |
+ git clone https://github.com/emscripten-core/emsdk.git _emscripten_sdk
+ cd _emscripten_sdk
+ git checkout 4.0.15
+ ./emsdk install latest
+ ./emsdk activate latest
+
+ - name: Build - failsafe_web
+ shell: bash
+ run: |
+ source _emscripten_sdk/emsdk_env.sh
+ make failsafe_web
+
+ # ═══════════════════════════════════════════════════════════════════
+ # TIER 2 GATE: Validate all T2 jobs before allowing T3 to start
+ # Distinguishes intentional skips (toggled off) from unexpected failures
+ # ═══════════════════════════════════════════════════════════════════
+
+ tier2-gate:
+ name: "T2: Gate (All T2 checks passed)"
+ needs: [load-config, build-sitl, build-sitl-gazebo-classic, basic-tests, clang-tidy,
+ ubuntu-builds, macos-build, itcm-check, flash-analysis, failsafe-sim]
+ if: always()
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check T2 results
+ env:
+ R_SITL_BUILD: ${{ needs.build-sitl.result }}
+ R_GAZEBO_BUILD: ${{ needs.build-sitl-gazebo-classic.result }}
+ R_BASIC_TESTS: ${{ needs.basic-tests.result }}
+ R_CLANG_TIDY: ${{ needs.clang-tidy.result }}
+ R_UBUNTU: ${{ needs.ubuntu-builds.result }}
+ R_MACOS: ${{ needs.macos-build.result }}
+ R_ITCM: ${{ needs.itcm-check.result }}
+ R_FLASH: ${{ needs.flash-analysis.result }}
+ R_FAILSAFE: ${{ needs.failsafe-sim.result }}
+ E_SITL_BUILD: ${{ needs.load-config.outputs.enable_sitl_build }}
+ E_GAZEBO_BUILD: ${{ needs.load-config.outputs.enable_gazebo_classic_build }}
+ E_BASIC_TESTS: ${{ needs.load-config.outputs.enable_sitl_build }}
+ E_CLANG_TIDY: ${{ needs.load-config.outputs.enable_clang_tidy }}
+ E_UBUNTU: ${{ needs.load-config.outputs.enable_ubuntu_builds }}
+ E_MACOS: ${{ needs.load-config.outputs.enable_macos_build }}
+ E_ITCM: ${{ needs.load-config.outputs.enable_itcm_check }}
+ E_FLASH: ${{ needs.load-config.outputs.enable_flash_analysis }}
+ E_FAILSAFE: ${{ needs.load-config.outputs.enable_failsafe_sim }}
+ run: |
+ check() {
+ local job=$1 result=$2 enabled=$3
+ if [ "$enabled" == "true" ] && [ "$result" != "success" ]; then
+ echo "❌ $job required but result: $result"; exit 1
+ fi
+ echo "✅ $job: $result (enabled=$enabled)"
+ }
+ check "build-sitl" "$R_SITL_BUILD" "$E_SITL_BUILD"
+ check "build-sitl-gazebo-classic" "$R_GAZEBO_BUILD" "$E_GAZEBO_BUILD"
+ check "basic-tests" "$R_BASIC_TESTS" "$E_BASIC_TESTS"
+ check "clang-tidy" "$R_CLANG_TIDY" "$E_CLANG_TIDY"
+ check "ubuntu-builds" "$R_UBUNTU" "$E_UBUNTU"
+ check "macos-build" "$R_MACOS" "$E_MACOS"
+ check "itcm-check" "$R_ITCM" "$E_ITCM"
+ check "flash-analysis" "$R_FLASH" "$E_FLASH"
+ check "failsafe-sim" "$R_FAILSAFE" "$E_FAILSAFE"
+
+ # ═══════════════════════════════════════════════════════════════════
+ # TIER 3: Integration Tests
+ # Runs after tier2-gate succeeds. SITL simulations and ROS tests.
+ # ═══════════════════════════════════════════════════════════════════
+
+ sitl-tests:
+ name: "T3: SITL Tests [${{ matrix.config.model }}]"
+ needs: [load-config, tier2-gate, build-sitl-gazebo-classic]
+ if: >-
+ !failure() && !cancelled()
+ && needs.tier2-gate.result == 'success'
+ && needs.load-config.outputs.enable_sitl_tests == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_medium) }}
+ permissions:
+ contents: read
+ container:
+ image: px4io/px4-dev-simulation-focal:2021-09-08
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - {model: "iris", latitude: "59.617693", longitude: "-151.145316", altitude: "48"} # Alaska
+ # TODO: VTOL and tailsitter SITL tests disabled due to extreme flakiness.
+ # Overall SITL pass rate is 31.6%. VTOL accounts for 79% of all failures,
+ # tailsitter 37%, while iris (quadcopter) passes 99% of runs.
+ # Investigate root cause of flakiness (especially tailsitter RTL with Reverse Mission)
+ # and performance regression before re-enabling.
+ # - {model: "tailsitter", latitude: "29.660316", longitude: "-82.316658", altitude: "30"} # Florida
+ # - {model: "standard_vtol", latitude: "47.397742", longitude: "8.545594", altitude: "488"} # Zurich
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - uses: ./.github/actions/setup-ccache
+ with:
+ cache-key-prefix: ccache-sitl-gazebo-classic
+ max-size: ${{ needs.load-config.outputs.cache_max_size_sitl_gazebo }}
+
+ - uses: ./.github/actions/build-gazebo-sitl
+
+ - name: Setup - Download MAVSDK Library
+ run: wget "https://github.com/mavlink/MAVSDK/releases/download/v$(cat test/mavsdk_tests/MAVSDK_VERSION)/libmavsdk-dev_$(cat test/mavsdk_tests/MAVSDK_VERSION)_ubuntu20.04_amd64.deb"
+
+ - name: Setup - Install MAVSDK Library
+ run: dpkg -i "libmavsdk-dev_$(cat test/mavsdk_tests/MAVSDK_VERSION)_ubuntu20.04_amd64.deb"
+
+ - name: Setup - Verify Environment Variables
+ env:
+ PX4_HOME_LAT: ${{matrix.config.latitude}}
+ PX4_HOME_LON: ${{matrix.config.longitude}}
+ PX4_HOME_ALT: ${{matrix.config.altitude}}
+ run: |
+ export
+ ulimit -a
+
+ - name: Build - MAVSDK Integration Tests
+ env:
+ DONT_RUN: 1
+ run: make px4_sitl_default sitl_gazebo-classic mavsdk_tests
+
+ - name: Cache - Stats after MAVSDK Tests Build
+ run: ccache -s
+
+ - name: Test - Run SITL MAVSDK Integration Tests
+ env:
+ PX4_HOME_LAT: ${{matrix.config.latitude}}
+ PX4_HOME_LON: ${{matrix.config.longitude}}
+ PX4_HOME_ALT: ${{matrix.config.altitude}}
+ run: test/mavsdk_tests/mavsdk_test_runner.py --speed-factor 10 --model ${{matrix.config.model}} test/mavsdk_tests/configs/sitl.json --verbose --force-color
+ timeout-minutes: 45
+
+ - name: Upload - Failed Test Logs and Flight Logs
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: failed-${{matrix.config.model}}-logs.zip
+ path: |
+ logs/**/**/**/*.log
+ logs/**/**/**/*.ulg
+ build/px4_sitl_default/tmp_mavsdk_tests/rootfs/log/**/*.ulg
+
+ ros-integration-tests:
+ name: "T3: ROS Integration Tests"
+ needs: [load-config, tier2-gate]
+ if: >-
+ !failure() && !cancelled()
+ && needs.tier2-gate.result == 'success'
+ && needs.load-config.outputs.enable_ros_integration == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_medium) }}
+ permissions:
+ contents: read
+ container:
+ image: px4io/px4-dev-ros2-galactic:2021-09-08
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - name: Setup - Update ROS 2 Galactic Package Keys
+ run: |
+ sudo rm /etc/apt/sources.list.d/ros2.list && \
+ sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg && \
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
+
+ - name: Setup - Install Gazebo 11 and GStreamer Plugins
+ run: apt update && apt install -y gazebo11 libgazebo11-dev gstreamer1.0-plugins-bad gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly libgstreamer-plugins-base1.0-dev
+
+ - uses: ./.github/actions/setup-ccache
+ id: ccache
+ with:
+ cache-key-prefix: ccache-ros-integration
+ max-size: ${{ needs.load-config.outputs.cache_max_size_ros_integration }}
+
+ - name: Cache - Restore Micro-XRCE-DDS Agent
+ id: cache-xrce-agent
+ uses: actions/cache@v4
+ with:
+ path: /opt/Micro-XRCE-DDS-Agent
+ key: xrce-agent-v2.2.1-fastdds-2.8.2-galactic-2021-09-08
+
+ - name: Build - Micro-XRCE-DDS Agent (v2.2.1)
+ if: steps.cache-xrce-agent.outputs.cache-hit != 'true'
+ run: |
+ cd /opt
+ git clone --recursive https://github.com/eProsima/Micro-XRCE-DDS-Agent.git
+ cd Micro-XRCE-DDS-Agent
+ git checkout v2.2.1
+ sed -i 's/_fastdds_tag 2.8.x/_fastdds_tag 2.8.2/g' CMakeLists.txt
+ mkdir build
+ cd build
+ cmake ..
+ make -j2
+
+ - name: Cache - Restore PX4 ROS 2 Interface Library Workspace
+ id: cache-px4-ros2-ws
+ uses: actions/cache@v4
+ with:
+ path: /opt/px4_ws
+ # Bump 'v1' when updating px4-ros2-interface-lib (currently tracking default branch)
+ key: px4-ros2-ws-v1-galactic-2021-09-08-${{ hashFiles('msg/*.msg', 'msg/versioned/*.msg', 'srv/*.srv') }}
+
+ - name: Build - PX4 ROS 2 Interface Library
+ if: steps.cache-px4-ros2-ws.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ PX4_DIR="$(pwd)"
+ . /opt/ros/galactic/setup.bash
+ mkdir -p /opt/px4_ws/src
+ cd /opt/px4_ws/src
+ # Determine the interface lib branch to clone:
+ # - PRs targeting main or feature branches → default branch (omit --branch)
+ # - PRs targeting release/X.YZ → matching release/X.YZ branch
+ # - Push to release/X.YZ → same
+ TARGET_BRANCH="${{ github.base_ref || github.ref_name }}"
+ if [[ "$TARGET_BRANCH" == release/* ]]; then
+ INTERFACE_LIB_BRANCH="$TARGET_BRANCH"
+ else
+ INTERFACE_LIB_BRANCH=""
+ fi
+ if [[ -n "$INTERFACE_LIB_BRANCH" ]]; then
+ if ! git ls-remote --exit-code --heads https://github.com/Auterion/px4-ros2-interface-lib.git "$INTERFACE_LIB_BRANCH" > /dev/null 2>&1; then
+ echo "::warning::Branch '$INTERFACE_LIB_BRANCH' not found in px4-ros2-interface-lib — falling back to default branch. Once the release branch is created, update the cache key (bump 'v1') to force a rebuild against the correct branch."
+ INTERFACE_LIB_BRANCH=""
+ fi
+ fi
+ CLONE_ARGS="--recursive"
+ [[ -n "$INTERFACE_LIB_BRANCH" ]] && CLONE_ARGS="$CLONE_ARGS --branch $INTERFACE_LIB_BRANCH"
+ git clone $CLONE_ARGS https://github.com/Auterion/px4-ros2-interface-lib.git
+ # Ignore python packages due to compilation issue (can be enabled when updating ROS)
+ touch px4-ros2-interface-lib/px4_ros2_py/COLCON_IGNORE || true
+ touch px4-ros2-interface-lib/examples/python/COLCON_IGNORE || true
+ cd ..
+ "${PX4_DIR}/Tools/copy_to_ros_ws.sh" "$(pwd)"
+ rm -rf src/translation_node src/px4_msgs_old
+ colcon build --symlink-install
+
+ - name: Cache - Stats after ROS 2 Interface Library
+ run: ccache -s
+
+ - uses: ./.github/actions/build-gazebo-sitl
+
+ - name: Test - Run ROS 2 Integration Tests (Iris model)
+ shell: bash
+ run: |
+ . /opt/px4_ws/install/setup.bash
+ /opt/Micro-XRCE-DDS-Agent/build/MicroXRCEAgent udp4 localhost -p 8888 -v 0 &
+ test/ros_test_runner.py --verbose --model iris --upload --force-color
+ timeout-minutes: 45
+
+ - uses: ./.github/actions/save-ccache
+ with:
+ cache-primary-key: ${{ steps.ccache.outputs.cache-primary-key }}
+
+ - name: Upload - Failed Test Logs and Flight Logs
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: failed-ros-logs.zip
+ path: |
+ logs/**/**/**/*.log
+ logs/**/**/**/*.ulg
+ build/px4_sitl_default/tmp_ros_tests/rootfs/log/**/*.ulg
+
+ mavros-tests:
+ name: "T3: MAVROS Tests [${{ matrix.config.name }}]"
+ needs: [load-config, tier2-gate, build-sitl-gazebo-classic]
+ if: >-
+ !failure() && !cancelled()
+ && needs.tier2-gate.result == 'success'
+ && needs.load-config.outputs.enable_mavros_tests == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_small) }}
+ permissions:
+ contents: read
+ container:
+ image: px4io/px4-dev-ros-noetic:2021-09-08
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - {name: "Mission", test_file: "mavros_posix_test_mission.test", params: "mission:=MC_mission_box vehicle:=iris"}
+ - {name: "Offboard", test_file: "mavros_posix_tests_offboard_posctl.test", params: "vehicle:=iris"}
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - name: Setup - Install Python Test Dependencies
+ run: pip3 install -r Tools/setup/requirements.txt
+
+ - uses: ./.github/actions/setup-ccache
+ with:
+ cache-key-prefix: ccache-sitl-gazebo-classic
+ max-size: ${{ needs.load-config.outputs.cache_max_size_sitl_gazebo }}
+
+ - uses: ./.github/actions/build-gazebo-sitl
+
+ - name: Test - MAVROS ${{ matrix.config.name }}
+ run: |
+ ./test/rostest_px4_run.sh \
+ ${{ matrix.config.test_file }} \
+ ${{ matrix.config.params }}
+ timeout-minutes: 10
+
+ - name: Upload - Failed Test Logs
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: failed-mavros-${{ matrix.config.name }}-logs.zip
+ path: |
+ logs/**/**/**/*.log
+ logs/**/**/**/*.ulg
+
+ ros-translation-node:
+ name: "T3: ROS Translation Node [${{ matrix.config.ros_version }}]"
+ needs: [load-config, tier2-gate]
+ if: >-
+ !failure() && !cancelled()
+ && needs.tier2-gate.result == 'success'
+ && needs.load-config.outputs.enable_ros_translation_node == 'true'
+ runs-on: ${{ fromJSON(needs.load-config.outputs.runner_small) }}
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - {ros_version: "humble", ubuntu: "jammy"}
+ - {ros_version: "jazzy", ubuntu: "noble"}
+ container:
+ image: ros:${{ matrix.config.ros_version }}-ros-base-${{ matrix.config.ubuntu }}
+ steps:
+ - uses: runs-on/action@v2
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup - Configure Git Safe Directory
+ run: git config --system --add safe.directory '*'
+
+ - uses: ./.github/actions/setup-ccache
+ id: ccache
+ with:
+ cache-key-prefix: ccache-ros-translation-${{ matrix.config.ros_version }}
+ max-size: ${{ needs.load-config.outputs.cache_max_size_ros_translation }}
+ base-dir: /ros_ws
+ install-ccache: 'true'
+
+ - name: Validate - Check Message File Versioning
+ if: github.event_name == 'pull_request'
+ shell: bash
+ run: |
+ ./Tools/ci/check_msg_versioning.sh ${{ github.event.pull_request.base.sha }} ${{github.event.pull_request.head.sha}}
+
+ - name: Build - Translation Node
+ shell: bash
+ run: |
+ ros_ws=/ros_ws
+ mkdir -p $ros_ws/src
+ ./Tools/copy_to_ros_ws.sh $ros_ws
+ cd $ros_ws
+ source /opt/ros/${{ matrix.config.ros_version }}/setup.sh
+ colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache --symlink-install --event-handlers=console_cohesion+
+
+ - name: Test - Translation Node Unit Tests
+ shell: bash
+ run: |
+ source /ros_ws/install/setup.sh
+ /ros_ws/build/translation_node/translation_node_unit_tests
+
+ - uses: ./.github/actions/save-ccache
+ with:
+ cache-primary-key: ${{ steps.ccache.outputs.cache-primary-key }}
+
+ # ═══════════════════════════════════════════════════════════════════
+ # TIER 4: Full Build Matrix (~200 board targets)
+ # Triggered by build_all_targets.yml after this workflow completes, or on tagged releases.
+ # See: .github/workflows/build_all_targets.yml
diff --git a/.github/workflows/ci-simple.yml.example b/.github/workflows/ci-simple.yml.example
new file mode 100644
index 0000000000..331084a58c
--- /dev/null
+++ b/.github/workflows/ci-simple.yml.example
@@ -0,0 +1,41 @@
+name: Simple CI (Fork-Friendly)
+
+# Forks: This is a lightweight CI workflow for forks that don't need
+# the full orchestrator pipeline. It builds PX4 SITL + one hardware
+# target and runs unit tests + format checks.
+#
+# To use: rename this file from ci-simple.yml.example to ci-simple.yml
+# and enable GitHub Actions in your fork settings.
+# To customize: adjust the 'make' target or container image tag below.
+
+on:
+ push:
+ branches: ['**']
+ pull_request:
+ branches: ['**']
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ quick-check:
+ runs-on: ubuntu-latest
+ container:
+ image: ghcr.io/px4/px4-dev:v1.17.0-beta1
+ steps:
+ - name: Configure git
+ run: git config --system --add safe.directory '*'
+
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Quick Check (SITL + FMUv5 + tests + format)
+ run: make quick_check
+ env:
+ RUNS_IN_DOCKER: true
diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml
deleted file mode 100644
index fc3c5a46ea..0000000000
--- a/.github/workflows/clang-tidy.yml
+++ /dev/null
@@ -1,69 +0,0 @@
-name: Static Analysis
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-permissions:
- contents: read
-
-jobs:
- clang_tidy:
- name: Clang-Tidy
- runs-on: [runs-on, runner=16cpu-linux-x64, "run-id=${{ github.run_id }}", "extras=s3-cache"]
- container:
- image: px4io/px4-dev:v1.17.0-beta1
- steps:
- - uses: runs-on/action@v2
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- fetch-tags: true
-
- - name: Configure Git Safe Directory
- run: git config --system --add safe.directory '*'
-
- - name: Restore Compiler Cache
- id: cc_restore
- uses: actions/cache/restore@v4
- with:
- path: ~/.ccache
- key: ccache-clang-tidy-${{ github.head_ref || github.ref_name }}
- restore-keys: |
- ccache-clang-tidy-${{ github.head_ref || github.ref_name }}-
- ccache-clang-tidy-main-
- ccache-clang-tidy-
-
- - name: Configure Compiler Cache
- run: |
- mkdir -p ~/.ccache
- echo "base_dir = ${GITHUB_WORKSPACE}" > ~/.ccache/ccache.conf
- echo "compression = true" >> ~/.ccache/ccache.conf
- echo "compression_level = 6" >> ~/.ccache/ccache.conf
- echo "max_size = 120M" >> ~/.ccache/ccache.conf
- echo "hash_dir = false" >> ~/.ccache/ccache.conf
- echo "compiler_check = content" >> ~/.ccache/ccache.conf
- ccache -s
- ccache -z
-
- - name: Run Clang-Tidy Analysis
- run: make -j16 clang-tidy
-
- - name: Compiler Cache Stats
- if: always()
- run: ccache -s
-
- - name: Save Compiler Cache
- if: always()
- uses: actions/cache/save@v4
- with:
- path: ~/.ccache
- key: ${{ steps.cc_restore.outputs.cache-primary-key }}
diff --git a/.github/workflows/compile_macos.yml b/.github/workflows/compile_macos.yml
deleted file mode 100644
index 5e011a5bf6..0000000000
--- a/.github/workflows/compile_macos.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: MacOS build
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: macos-latest
- strategy:
- matrix:
- config: [
- px4_fmu-v5_default,
- px4_sitl
- ]
- steps:
- - name: install Python 3.10
- uses: actions/setup-python@v5
- with:
- python-version: "3.10"
-
- - uses: actions/checkout@v4
-
- - name: setup
- run: |
- ./Tools/setup/macos.sh
-
- - name: Prepare ccache timestamp
- id: ccache_cache_timestamp
- shell: cmake -P {0}
- run: |
- string(TIMESTAMP current_date "%Y-%m-%d-%H;%M;%S" UTC)
- file(APPEND "$ENV{GITHUB_OUTPUT}" "timestamp=${current_date}\n")
- - name: ccache cache files
- uses: actions/cache@v4
- with:
- path: ~/.ccache
- key: macos_${{matrix.config}}-ccache-${{steps.ccache_cache_timestamp.outputs.timestamp}}
- restore-keys: macos_${{matrix.config}}-ccache-
- - name: setup ccache
- run: |
- mkdir -p ~/.ccache
- echo "base_dir = ${GITHUB_WORKSPACE}" > ~/.ccache/ccache.conf
- echo "compression = true" >> ~/.ccache/ccache.conf
- echo "compression_level = 6" >> ~/.ccache/ccache.conf
- echo "max_size = 40M" >> ~/.ccache/ccache.conf
- echo "hash_dir = false" >> ~/.ccache/ccache.conf
- ccache -s
- ccache -z
-
- - name: make ${{matrix.config}}
- run: |
- ccache -z
- make ${{matrix.config}}
- ccache -s
diff --git a/.github/workflows/compile_ubuntu.yml b/.github/workflows/compile_ubuntu.yml
deleted file mode 100644
index 019081b9f6..0000000000
--- a/.github/workflows/compile_ubuntu.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-name: Ubuntu environment build
-
-on:
- push:
- branches:
- - 'main'
- - 'stable'
- - 'beta'
- - 'release/**'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-env:
- RUNS_IN_DOCKER: true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build_and_test:
- name: Build and Test
- strategy:
- fail-fast: false
- matrix:
- version: ['ubuntu:22.04', 'ubuntu:24.04']
- runs-on: [runs-on,runner=4cpu-linux-x64,"image=ubuntu24-full-x64","run-id=${{ github.run_id }}",spot=false]
- container:
- image: ${{ matrix.version }}
- volumes:
- - /github/workspace:/github/workspace
- steps:
-
- - name: Fix git in container
- run: |
- # we only need this because we are running the job in a container
- # when checkout pulls git it does it in a shared volume
- # and file ownership changes between steps
- # first we install git since its missing from the base image
- # then we mark the directory as safe for other instances
- # of git to use.
- apt update && apt install git -y
- git config --global --add safe.directory $(realpath .)
-
- - uses: actions/checkout@v4
-
- - name: Install Deps, Build, and Make Quick Check
- run: |
- # we need to install dependencies and build on the same step
- # given the stateless nature of docker images
- ./Tools/setup/ubuntu.sh
- make quick_check
diff --git a/.github/workflows/ekf_functional_change_indicator.yml b/.github/workflows/ekf_functional_change_indicator.yml
deleted file mode 100644
index 11c1970680..0000000000
--- a/.github/workflows/ekf_functional_change_indicator.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: EKF Change Indicator
-
-on:
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-# If two events are triggered within a short time in the same PR, cancel the run of the oldest event
-concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
- cancel-in-progress: true
-
-jobs:
- unit_tests:
- runs-on: ubuntu-latest
-
- container:
- image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: main test
- run: |
- cd "$GITHUB_WORKSPACE"
- git config --global --add safe.directory "$GITHUB_WORKSPACE"
- make tests TESTFILTER=EKF
-
- - name: Check if there is a functional change
- run: git diff --exit-code
- working-directory: src/modules/ekf2/test/change_indication
diff --git a/.github/workflows/ekf_update_change_indicator.yml b/.github/workflows/ekf_update_change_indicator.yml
deleted file mode 100644
index 6f6e1dde55..0000000000
--- a/.github/workflows/ekf_update_change_indicator.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: EKF Update Change Indicator
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
-
-jobs:
- unit_tests:
- runs-on: ubuntu-latest
-
- container:
- image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
-
- env:
- GIT_COMMITTER_EMAIL: bot@px4.io
- GIT_COMMITTER_NAME: PX4BuildBot
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: main test
- run: |
- cd "$GITHUB_WORKSPACE"
- git config --global --add safe.directory "$GITHUB_WORKSPACE"
- make tests TESTFILTER=EKF
-
- - name: Check if there exists diff and save result in variable
- id: diff-check
- working-directory: src/modules/ekf2/test/change_indication
- run: |
- if git diff --quiet; then
- echo "CHANGE_INDICATED=false" >> $GITHUB_OUTPUT
- else
- echo "CHANGE_INDICATED=true" >> $GITHUB_OUTPUT
- fi
-
- - name: auto-commit any changes to change indication
- if: steps.diff-check.outputs.CHANGE_INDICATED == 'true'
- uses: stefanzweifel/git-auto-commit-action@v4
- with:
- file_pattern: 'src/modules/ekf2/test/change_indication/*.csv'
- commit_user_name: ${{ env.GIT_COMMITTER_NAME }}
- commit_user_email: ${{ env.GIT_COMMITTER_EMAIL }}
- commit_message: |
- [AUTO COMMIT] update change indication
-
- See .github/workflows/ekf_update_change_indicator.yml for more details
-
- - name: if there is a functional change, fail check
- if: steps.diff-check.outputs.CHANGE_INDICATED == 'true'
- run: exit 1
diff --git a/.github/workflows/failsafe_sim.yml b/.github/workflows/failsafe_sim.yml
deleted file mode 100644
index 24cdb49550..0000000000
--- a/.github/workflows/failsafe_sim.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-name: Failsafe Simulator Build
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: ubuntu-latest
- defaults:
- run:
- shell: bash
- strategy:
- fail-fast: false
- matrix:
- check: [
- "failsafe_web",
- ]
- container:
- image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
- options: --privileged --ulimit core=-1 --security-opt seccomp=unconfined
- steps:
- - name: Install Node v20.18.0
- uses: actions/setup-node@v4
- with:
- node-version: 20.18.0
-
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Git ownership workaround
- run: git config --system --add safe.directory '*'
-
- - name: Install empscripten
- run: |
- git clone https://github.com/emscripten-core/emsdk.git _emscripten_sdk
- cd _emscripten_sdk
- git checkout 4.0.15
- ./emsdk install latest
- ./emsdk activate latest
-
- - name: Testing [${{ matrix.check }}]
- run: |
- . ./_emscripten_sdk/emsdk_env.sh
- make ${{ matrix.check }}
diff --git a/.github/workflows/flash_analysis.yml b/.github/workflows/flash_analysis.yml
deleted file mode 100644
index 7f126352ab..0000000000
--- a/.github/workflows/flash_analysis.yml
+++ /dev/null
@@ -1,154 +0,0 @@
-name: FLASH usage analysis
-
-permissions:
- contents: read
- pull-requests: write
- issues: write
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-env:
- MIN_FLASH_POS_DIFF_FOR_COMMENT: 50
- MIN_FLASH_NEG_DIFF_FOR_COMMENT: -50
-
-jobs:
- analyze_flash:
- name: Analyzing ${{ matrix.target }}
- runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}",spot=false]
- container:
- image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
- strategy:
- matrix:
- target: [px4_fmu-v5x, px4_fmu-v6x]
- outputs:
- px4_fmu-v5x-bloaty-output: ${{ steps.gen-output.outputs.px4_fmu-v5x-bloaty-output }}
- px4_fmu-v5x-bloaty-summary-map: ${{ steps.gen-output.outputs.px4_fmu-v5x-bloaty-summary-map }}
- px4_fmu-v6x-bloaty-output: ${{ steps.gen-output.outputs.px4_fmu-v6x-bloaty-output }}
- px4_fmu-v6x-bloaty-summary-map: ${{ steps.gen-output.outputs.px4_fmu-v6x-bloaty-summary-map }}
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- submodules: recursive
-
- - name: Git ownership workaround
- run: git config --system --add safe.directory '*'
-
- - name: Build Target
- run: make ${{ matrix.target }}_flash-analysis
-
- - name: Store the ELF with the change
- run: cp ./build/**/*.elf ./with-change.elf
-
- - name: Clean previous build
- run: |
- make clean
- make distclean
- make submodulesclean
-
- - name: If it's a PR checkout the base branch
- if: ${{ github.event.pull_request }}
- # As checkout creates a merge commit (merging the base branch into the PR branch), the base branch is the base for a diff of the PR changes.
- run: git checkout ${{ github.event.pull_request.base.ref }}
-
- - name: If it's a push checkout the previous commit
- if: github.event_name == 'push'
- run: git checkout ${{ github.event.before }}
-
- - name: Update submodules
- run: make submodulesupdate
-
- - name: Build
- run: make ${{ matrix.target }}_flash-analysis
-
- - name: Store the ELF before the change
- run: cp ./build/**/*.elf ./before-change.elf
-
- - name: bloaty-action
- uses: PX4/bloaty-action@v1.0.0
- id: bloaty-step
- with:
- bloaty-file-args: ./with-change.elf -- ./before-change.elf
- bloaty-additional-args: -d sections,symbols -s vm -n 20
- output-to-summary: true
-
- - name: Generate output
- id: gen-output
- run: |
- EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
- echo "${{ matrix.target }}-bloaty-output<<$EOF" >> $GITHUB_OUTPUT
- echo "${{ steps.bloaty-step.outputs.bloaty-output-encoded }}" >> $GITHUB_OUTPUT
- echo "$EOF" >> $GITHUB_OUTPUT
- echo "${{ matrix.target }}-bloaty-summary-map<<$EOF" >> $GITHUB_OUTPUT
- echo '${{ steps.bloaty-step.outputs.bloaty-summary-map }}' >> $GITHUB_OUTPUT
- echo "$EOF" >> $GITHUB_OUTPUT
-
- # TODO:
- # This part of the workflow is causing errors for forks. We should find a way to fix this and enable it again for forks.
- # Track this issue https://github.com/PX4/PX4-Autopilot/issues/24408
- post_pr_comment:
- name: Publish Results
- runs-on: [runs-on,runner=1cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}"]
- needs: [analyze_flash]
- env:
- V5X-SUMMARY-MAP-ABS: ${{ fromJSON(fromJSON(needs.analyze_flash.outputs.px4_fmu-v5x-bloaty-summary-map).vm-absolute) }}
- V5X-SUMMARY-MAP-PERC: ${{ fromJSON(fromJSON(needs.analyze_flash.outputs.px4_fmu-v5x-bloaty-summary-map).vm-percentage) }}
- V6X-SUMMARY-MAP-ABS: ${{ fromJSON(fromJSON(needs.analyze_flash.outputs.px4_fmu-v6x-bloaty-summary-map).vm-absolute) }}
- V6X-SUMMARY-MAP-PERC: ${{ fromJSON(fromJSON(needs.analyze_flash.outputs.px4_fmu-v6x-bloaty-summary-map).vm-percentage) }}
- if: github.event.pull_request && github.event.pull_request.head.repo.full_name == github.repository
- steps:
- - name: Find Comment
- uses: peter-evans/find-comment@v3
- id: fc
- with:
- issue-number: ${{ github.event.pull_request.number }}
- comment-author: 'github-actions[bot]'
- body-includes: FLASH Analysis
-
- - name: Set Build Time
- id: bt
- run: |
- echo "timestamp=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_OUTPUT
-
- - name: Create or update comment
- # This can't be moved to the job-level conditions, as GH actions don't allow a job-level if condition to access the env.
- if: |
- steps.fc.outputs.comment-id != '' ||
- env.V5X-SUMMARY-MAP-ABS >= fromJSON(env.MIN_FLASH_POS_DIFF_FOR_COMMENT) ||
- env.V5X-SUMMARY-MAP-ABS <= fromJSON(env.MIN_FLASH_NEG_DIFF_FOR_COMMENT) ||
- env.V6X-SUMMARY-MAP-ABS >= fromJSON(env.MIN_FLASH_POS_DIFF_FOR_COMMENT) ||
- env.V6X-SUMMARY-MAP-ABS <= fromJSON(env.MIN_FLASH_NEG_DIFF_FOR_COMMENT)
- uses: peter-evans/create-or-update-comment@v4
- with:
- comment-id: ${{ steps.fc.outputs.comment-id }}
- issue-number: ${{ github.event.pull_request.number }}
- body: |
- ## 🔎 FLASH Analysis
-
- px4_fmu-v5x [Total VM Diff: ${{ env.V5X-SUMMARY-MAP-ABS }} byte (${{ env.V5X-SUMMARY-MAP-PERC}} %)]
-
- ```
- ${{ needs.analyze_flash.outputs.px4_fmu-v5x-bloaty-output }}
- ```
-
-
-
- px4_fmu-v6x [Total VM Diff: ${{ env.V6X-SUMMARY-MAP-ABS }} byte (${{ env.V6X-SUMMARY-MAP-PERC }} %)]
-
- ```
- ${{ needs.analyze_flash.outputs.px4_fmu-v6x-bloaty-output }}
- ```
-
-
- **Updated: _${{ steps.bt.outputs.timestamp }}_**
- edit-mode: replace
diff --git a/.github/workflows/itcm_check.yml b/.github/workflows/itcm_check.yml
deleted file mode 100644
index 767f69aa7c..0000000000
--- a/.github/workflows/itcm_check.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: ITCM check
-
-permissions:
- contents: read
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- check_itcm:
- name: Checking ${{ matrix.target }}
- runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}",spot=false]
- container:
- image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
- strategy:
- fail-fast: false
- matrix:
- include:
- - target: px4_fmu-v5x
- scripts: >
- boards/px4/fmu-v5x/nuttx-config/scripts/itcm_gen_functions.ld
- boards/px4/fmu-v5x/nuttx-config/scripts/itcm_static_functions.ld
- - target: px4_fmu-v6xrt
- scripts: >
- boards/px4/fmu-v6xrt/nuttx-config/scripts/itcm_functions_includes.ld
- boards/px4/fmu-v6xrt/nuttx-config/scripts/itcm_static_functions.ld
- - target: nxp_tropic-community
- scripts: >
- boards/nxp/tropic-community/nuttx-config/scripts/itcm_functions_includes.ld
- boards/nxp/tropic-community/nuttx-config/scripts/itcm_static_functions.ld
- - target: nxp_mr-tropic
- scripts: >
- boards/nxp/mr-tropic/nuttx-config/scripts/itcm_functions_includes.ld
- boards/nxp/mr-tropic/nuttx-config/scripts/itcm_static_functions.ld
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- submodules: recursive
-
- - name: Git ownership workaround
- run: git config --system --add safe.directory '*'
-
- - name: Build Target
- run: make ${{ matrix.target }}
-
- - name: Copy built ELF
- run: cp ./build/**/*.elf ./built.elf
-
- - name: Install itcm-check dependencies
- run: pip3 install -r Tools/setup/optional-requirements.txt --break-system-packages
-
- - name: Execute the itcm-check
- run: python3 Tools/itcm_check.py --elf-file built.elf --script-files ${{ matrix.scripts }}
diff --git a/.github/workflows/mavros_mission_tests.yml b/.github/workflows/mavros_mission_tests.yml
deleted file mode 100644
index 0b1a84f7b3..0000000000
--- a/.github/workflows/mavros_mission_tests.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-name: MAVROS Mission Tests
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- strategy:
- fail-fast: false
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Build SITL and Run Tests (inside old ROS container)
- run: |
- docker run --rm \
- -v "${GITHUB_WORKSPACE}:/workspace" \
- -w /workspace \
- px4io/px4-dev-ros-melodic:2021-09-08 \
- bash -c '
- git config --global --add safe.directory /workspace
- make px4_sitl_default
- make px4_sitl_default sitl_gazebo-classic
- ./test/rostest_px4_run.sh \
- mavros_posix_test_mission.test \
- mission:=MC_mission_box \
- vehicle:=iris
- '
diff --git a/.github/workflows/mavros_offboard_tests.yml b/.github/workflows/mavros_offboard_tests.yml
deleted file mode 100644
index dd6b750812..0000000000
--- a/.github/workflows/mavros_offboard_tests.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-name: MAVROS Offboard Tests
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- strategy:
- fail-fast: false
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Build SITL and Run Tests (inside old ROS container)
- run: |
- docker run --rm \
- -v "${GITHUB_WORKSPACE}:/workspace" \
- -w /workspace \
- px4io/px4-dev-ros-melodic:2021-09-08 \
- bash -c '
- git config --global --add safe.directory /workspace
- make px4_sitl_default
- make px4_sitl_default sitl_gazebo-classic
- ./test/rostest_px4_run.sh \
- mavros_posix_tests_offboard_posctl.test \
- vehicle:=iris
- '
diff --git a/.github/workflows/nuttx_env_config.yml b/.github/workflows/nuttx_env_config.yml
deleted file mode 100644
index f05b456bb6..0000000000
--- a/.github/workflows/nuttx_env_config.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-name: Nuttx Target with extra env config
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- container:
- image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
-
- strategy:
- matrix:
- config:
- - px4_fmu-v5_default
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Build PX4 and Run Test [${{ matrix.config }}]
- run: |
- cd "$GITHUB_WORKSPACE"
- git config --global --add safe.directory "$GITHUB_WORKSPACE"
- export PX4_EXTRA_NUTTX_CONFIG='CONFIG_NSH_LOGIN_PASSWORD="test";CONFIG_NSH_CONSOLE_LOGIN=y'
- echo "PX4_EXTRA_NUTTX_CONFIG: $PX4_EXTRA_NUTTX_CONFIG"
-
- make ${{ matrix.config }} nuttx_context
-
- echo "Check that the config option is set"
- grep CONFIG_NSH_LOGIN_PASSWORD build/${{ matrix.config }}/NuttX/nuttx/.config
diff --git a/.github/workflows/python_checks.yml b/.github/workflows/python_checks.yml
deleted file mode 100644
index c0fdc73e8b..0000000000
--- a/.github/workflows/python_checks.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Python CI Checks
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-jobs:
- build:
- runs-on: ubuntu-24.04
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Install Python3
- run: sudo apt-get install python3 python3-setuptools python3-pip -y
-
- - name: Install tools
- run: python3 -m pip install mypy types-requests flake8 --break-system-packages
-
- - name: Check MAVSDK test scripts with mypy
- run: $HOME/.local/bin/mypy --strict test/mavsdk_tests/*.py
-
- - name: Check MAVSDK test scripts with flake8
- run: $HOME/.local/bin/flake8 test/mavsdk_tests/*.py
diff --git a/.github/workflows/ros_integration_tests.yml b/.github/workflows/ros_integration_tests.yml
deleted file mode 100644
index bbcce560d8..0000000000
--- a/.github/workflows/ros_integration_tests.yml
+++ /dev/null
@@ -1,134 +0,0 @@
-# NOTE: this workflow is now running on Dronecode / PX4 AWS account.
-# - If you want to keep the tests running in GitHub Actions you need to uncomment the "runs-on: ubuntu-latest" lines
-# and comment the "runs-on: [runs-on,runner=..." lines.
-# - If you would like to duplicate this setup try setting up "RunsOn" on your own AWS account try https://runs-on.com
-
-name: ROS Integration Tests
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu22-full-x64,"run-id=${{ github.run_id }}",spot=false]
- container:
- image: px4io/px4-dev-ros2-galactic:2021-09-08
- options: --privileged --ulimit core=-1 --security-opt seccomp=unconfined
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Git Ownership Workaround
- run: git config --system --add safe.directory '*'
-
- - name: Update ROS Keys
- run: |
- sudo rm /etc/apt/sources.list.d/ros2.list && \
- sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg && \
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
-
- - name: Install gazebo
- run: |
- apt update && apt install -y gazebo11 libgazebo11-dev gstreamer1.0-plugins-bad gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly libgstreamer-plugins-base1.0-dev
-
- - name: Prepare ccache timestamp
- id: ccache_cache_timestamp
- shell: cmake -P {0}
- run: |
- string(TIMESTAMP current_date "%Y-%m-%d-%H;%M;%S" UTC)
- message("::set-output name=timestamp::${current_date}")
- - name: ccache cache files
- uses: actions/cache@v4
- with:
- path: ~/.ccache
- key: ros_integration_tests-${{matrix.config.build_type}}-ccache-${{steps.ccache_cache_timestamp.outputs.timestamp}}
- restore-keys: ros_integration_tests-${{matrix.config.build_type}}-ccache-
- - name: setup ccache
- run: |
- mkdir -p ~/.ccache
- echo "base_dir = ${GITHUB_WORKSPACE}" > ~/.ccache/ccache.conf
- echo "compression = true" >> ~/.ccache/ccache.conf
- echo "compression_level = 6" >> ~/.ccache/ccache.conf
- echo "max_size = 300M" >> ~/.ccache/ccache.conf
- echo "hash_dir = false" >> ~/.ccache/ccache.conf
- ccache -s
- ccache -z
-
- - name: Get and build micro-xrce-dds-agent
- run: |
- cd /opt
- git clone --recursive https://github.com/eProsima/Micro-XRCE-DDS-Agent.git
- cd Micro-XRCE-DDS-Agent
- git checkout v2.2.1 # recent versions require cmake 3.22, but px4-dev-ros2-galactic:2021-09-08 is on 3.16
- sed -i 's/_fastdds_tag 2.8.x/_fastdds_tag 2.8.2/g' CMakeLists.txt
- mkdir build
- cd build
- cmake ..
- make -j2
- - name: ccache post-run micro-xrce-dds-agent
- run: ccache -s
-
- - name: Get and build the ros2 interface library
- shell: bash
- run: |
- PX4_DIR="$(pwd)"
- . /opt/ros/galactic/setup.bash
- mkdir -p /opt/px4_ws/src
- cd /opt/px4_ws/src
- git clone --recursive https://github.com/Auterion/px4-ros2-interface-lib.git
- # Ignore python packages due to compilation issue (can be enabled when updating ROS)
- touch px4-ros2-interface-lib/px4_ros2_py/COLCON_IGNORE || true
- touch px4-ros2-interface-lib/examples/python/COLCON_IGNORE || true
- cd ..
- # Copy messages to ROS workspace
- "${PX4_DIR}/Tools/copy_to_ros_ws.sh" "$(pwd)"
- rm -rf src/translation_node src/px4_msgs_old
- colcon build --symlink-install
- - name: ccache post-run ros workspace
- run: ccache -s
-
- - name: Build PX4
- run: make px4_sitl_default
- - name: ccache post-run px4/firmware
- run: ccache -s
- - name: Build SITL Gazebo
- run: make px4_sitl_default sitl_gazebo-classic
- - name: ccache post-run sitl_gazebo-classic
- run: ccache -s
-
- - name: Core dump settings
- run: |
- ulimit -c unlimited
- echo "`pwd`/%e.core" > /proc/sys/kernel/core_pattern
-
- - name: Run tests
- shell: bash
- run: |
- . /opt/px4_ws/install/setup.bash
- /opt/Micro-XRCE-DDS-Agent/build/MicroXRCEAgent udp4 localhost -p 8888 -v 0 &
- test/ros_test_runner.py --verbose --model iris --upload --force-color
- timeout-minutes: 45
-
- - name: Upload failed logs
- if: failure()
- uses: actions/upload-artifact@v4
- with:
- name: failed-logs.zip
- path: |
- logs/**/**/**/*.log
- logs/**/**/**/*.ulg
- build/px4_sitl_default/tmp_ros_tests/rootfs/log/**/*.ulg
diff --git a/.github/workflows/ros_translation_node.yml b/.github/workflows/ros_translation_node.yml
deleted file mode 100644
index 64bae13f83..0000000000
--- a/.github/workflows/ros_translation_node.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-name: ROS Translation Node Tests
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-defaults:
- run:
- shell: bash
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build_and_test:
- name: Build and test
- runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu24-full-x64,"run-id=${{ github.run_id }}",spot=false]
- strategy:
- fail-fast: false
- matrix:
- config:
- - {ros_version: "humble", ubuntu: "jammy"}
- - {ros_version: "jazzy", ubuntu: "noble"}
- container:
- image: rostooling/setup-ros-docker:ubuntu-${{ matrix.config.ubuntu }}-latest
- steps:
- - name: Setup ROS 2 (${{ matrix.config.ros_version }})
- uses: ros-tooling/setup-ros@v0.7
- with:
- required-ros-distributions: ${{ matrix.config.ros_version }}
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- # Workaround for https://github.com/actions/runner/issues/2033
- - name: ownership workaround
- run: git config --system --add safe.directory '*'
-
- - name: Check .msg file versioning
- if: github.event_name == 'pull_request'
- run: |
- ./Tools/ci/check_msg_versioning.sh ${{ github.event.pull_request.base.sha }} ${{github.event.pull_request.head.sha}}
-
- - name: Build and test
- run: |
- ros_ws=/ros_ws
- mkdir -p $ros_ws/src
- ./Tools/copy_to_ros_ws.sh $ros_ws
- cd $ros_ws
- source /opt/ros/${{ matrix.config.ros_version }}/setup.sh
- colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release --symlink-install --event-handlers=console_cohesion+
- source ./install/setup.sh
- ./build/translation_node/translation_node_unit_tests
diff --git a/.github/workflows/sitl_tests.yml b/.github/workflows/sitl_tests.yml
deleted file mode 100644
index c114b05cb3..0000000000
--- a/.github/workflows/sitl_tests.yml
+++ /dev/null
@@ -1,163 +0,0 @@
-# NOTE: this workflow is now running on Dronecode / PX4 AWS account.
-# - If you want to keep the tests running in GitHub Actions you need to uncomment the "runs-on: ubuntu-latest" lines
-# and comment the "runs-on: [runs-on,runner=..." lines.
-# - If you would like to duplicate this setup try setting up "RunsOn" on your own AWS account try https://runs-on.com
-
-name: SITL Tests
-
-on:
- push:
- branches:
- - 'main'
- paths-ignore:
- - 'docs/**'
- pull_request:
- branches:
- - '**'
- paths-ignore:
- - 'docs/**'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build:
- name: Testing PX4 ${{ matrix.config.model }}
- runs-on: [runs-on,runner=4cpu-linux-x64,image=ubuntu22-full-x64,"run-id=${{ github.run_id }}",spot=false]
- container:
- image: px4io/px4-dev-simulation-focal:2021-09-08
- options: --privileged --ulimit core=-1 --security-opt seccomp=unconfined
- strategy:
- fail-fast: false
- matrix:
- config:
- - {model: "iris", latitude: "59.617693", longitude: "-151.145316", altitude: "48", build_type: "RelWithDebInfo" } # Alaska
- # VTOL/tailsitter disabled: persistent flaky CI failures (timeouts, erratic
- # transitions). Re-enable once the test infrastructure is stabilized.
- # - {model: "tailsitter" , latitude: "29.660316", longitude: "-82.316658", altitude: "30", build_type: "RelWithDebInfo" } # Florida
- # - {model: "standard_vtol", latitude: "47.397742", longitude: "8.545594", altitude: "488", build_type: "Coverage" } # Zurich
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Git Ownership Workaround
- run: git config --system --add safe.directory '*'
-
- - id: set-timestamp
- name: Set timestamp for cache
- run: echo "::set-output name=timestamp::$(date +"%Y%m%d%H%M%S")"
-
- - name: Cache Key Config
- uses: actions/cache@v4
- with:
- path: ~/.ccache
- key: sitl-ccache-${{ steps.set-timestamp.outputs.timestamp }}
- restore-keys: sitl-ccache-${{ steps.set-timestamp.outputs.timestamp }}
-
- - name: Cache Conf Config
- run: |
- mkdir -p ~/.ccache
- echo "base_dir = ${GITHUB_WORKSPACE}" > ~/.ccache/ccache.conf
- echo "compression = true" >> ~/.ccache/ccache.conf
- echo "compression_level = 6" >> ~/.ccache/ccache.conf
- echo "max_size = 120M" >> ~/.ccache/ccache.conf
- echo "hash_dir = false" >> ~/.ccache/ccache.conf
- ccache -s
- ccache -z
-
- - name: Build PX4
- env:
- PX4_CMAKE_BUILD_TYPE: ${{matrix.config.build_type}}
- run: make px4_sitl_default
-
- - name: Cache Post-Run [px4_sitl_default]
- run: ccache -s
-
- - name: Build SITL Gazebo
- env:
- PX4_CMAKE_BUILD_TYPE: ${{matrix.config.build_type}}
- run: make px4_sitl_default sitl_gazebo-classic
-
- - name: Cache Post-Run [sitl_gazebo-classic]
- run: ccache -s
-
- - name: Download MAVSDK
- run: wget "https://github.com/mavlink/MAVSDK/releases/download/v$(cat test/mavsdk_tests/MAVSDK_VERSION)/libmavsdk-dev_$(cat test/mavsdk_tests/MAVSDK_VERSION)_ubuntu20.04_amd64.deb"
-
- - name: Install MAVSDK
- run: dpkg -i "libmavsdk-dev_$(cat test/mavsdk_tests/MAVSDK_VERSION)_ubuntu20.04_amd64.deb"
-
- - name: Check PX4 Environment Variables
- env:
- PX4_HOME_LAT: ${{matrix.config.latitude}}
- PX4_HOME_LON: ${{matrix.config.longitude}}
- PX4_HOME_ALT: ${{matrix.config.altitude}}
- PX4_CMAKE_BUILD_TYPE: ${{matrix.config.build_type}}
- run: |
- export
- ulimit -a
-
- - name: Build PX4 / MAVSDK tests
- env:
- PX4_CMAKE_BUILD_TYPE: ${{matrix.config.build_type}}
- DONT_RUN: 1
- run: make px4_sitl_default sitl_gazebo-classic mavsdk_tests
-
- - name: Cache Post-Run [px4_sitl_default sitl_gazebo-classic mavsdk_tests]
- run: ccache -s
-
- - name: Core Dump Settings
- run: |
- ulimit -c unlimited
- echo "`pwd`/%e.core" > /proc/sys/kernel/core_pattern
-
- - name: Run SITL / MAVSDK Tests
- env:
- PX4_HOME_LAT: ${{matrix.config.latitude}}
- PX4_HOME_LON: ${{matrix.config.longitude}}
- PX4_HOME_ALT: ${{matrix.config.altitude}}
- PX4_CMAKE_BUILD_TYPE: ${{matrix.config.build_type}}
- run: test/mavsdk_tests/mavsdk_test_runner.py --speed-factor 10 --abort-early --model ${{matrix.config.model}} test/mavsdk_tests/configs/sitl.json --verbose --force-color
- timeout-minutes: 45
-
- - name: Upload failed logs
- if: failure()
- uses: actions/upload-artifact@v4
- with:
- name: failed-${{matrix.config.model}}-logs.zip
- path: |
- logs/**/**/**/*.log
- logs/**/**/**/*.ulg
- build/px4_sitl_default/tmp_mavsdk_tests/rootfs/log/**/*.ulg
-
- - name: Look at Core files
- if: failure() && ${{ hashFiles('px4.core') != '' }}
- run: gdb build/px4_sitl_default/bin/px4 px4.core -ex "thread apply all bt" -ex "quit"
-
- - name: Upload PX4 coredump
- if: failure() && ${{ hashFiles('px4.core') != '' }}
- uses: actions/upload-artifact@v4
- with:
- name: coredump
- path: px4.core
-
- - name: Setup & Generate Coverage Report
- if: contains(matrix.config.build_type, 'Coverage')
- run: |
- git config --global credential.helper "" # disable the keychain credential helper
- git config --global --add credential.helper store # enable the local store credential helper
- echo "https://x-access-token:${{ secrets.ACCESS_TOKEN }}@github.com" >> ~/.git-credentials # add credential
- git config --global url."https://github.com/".insteadof git@github.com: # credentials add credential
- mkdir -p coverage
- lcov --directory build/px4_sitl_default --base-directory build/px4_sitl_default --gcov-tool gcov --capture -o coverage/lcov.info
-
- - name: Upload Coverage Information to Codecov
- if: contains(matrix.config.build_type, 'Coverage')
- uses: codecov/codecov-action@v4
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- flags: mavsdk
- file: coverage/lcov.info
diff --git a/Tools/ci/run-clang-tidy-pr.py b/Tools/ci/run-clang-tidy-pr.py
new file mode 100755
index 0000000000..226bdae2e3
--- /dev/null
+++ b/Tools/ci/run-clang-tidy-pr.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+"""
+Run clang-tidy incrementally on files changed in a PR.
+
+Usage: run-clang-tidy-pr.py
+ base-ref: e.g. origin/main
+
+Computes the set of translation units (TUs) affected by the PR diff,
+then invokes Tools/run-clang-tidy.py on that subset only.
+Exits 0 silently when no C++ files were changed.
+"""
+
+import argparse
+import json
+import os
+import subprocess
+import sys
+
+EXTENSIONS_CPP = {'.cpp', '.c'}
+EXTENSIONS_HDR = {'.hpp', '.h'}
+# Manual exclusions from Makefile:508
+EXCLUDE_EXTRA = '|'.join([
+ 'src/systemcmds/tests',
+ 'src/examples',
+ 'src/modules/gyro_fft/CMSIS_5',
+ 'src/lib/drivers/smbus',
+ 'src/drivers/gpio',
+ r'src/modules/commander/failsafe/emscripten',
+ r'failsafe_test\.dir',
+])
+
+
+def repo_root():
+ try:
+ return subprocess.check_output(
+ ['git', 'rev-parse', '--show-toplevel'], text=True).strip()
+ except subprocess.CalledProcessError:
+ print('error: not inside a git repository', file=sys.stderr)
+ sys.exit(1)
+
+
+def changed_files(base_ref, root):
+ try:
+ out = subprocess.check_output(
+ ['git', 'diff', '--name-only', f'{base_ref}...HEAD',
+ '--', '*.cpp', '*.hpp', '*.h', '*.c'],
+ text=True, cwd=root).strip()
+ return out.splitlines() if out else []
+ except subprocess.CalledProcessError:
+ print(f'error: could not diff against "{base_ref}" — '
+ 'is the ref valid and fetched?', file=sys.stderr)
+ sys.exit(1)
+
+
+def submodule_paths(root):
+ # Returns [] if .gitmodules is absent or has no paths — both valid
+ try:
+ out = subprocess.check_output(
+ ['git', 'config', '--file', '.gitmodules',
+ '--get-regexp', 'path'],
+ text=True, cwd=root).strip()
+ return [line.split()[1] for line in out.splitlines()]
+ except subprocess.CalledProcessError:
+ return []
+
+
+def build_exclude(root):
+ submodules = '|'.join(submodule_paths(root))
+ return f'{submodules}|{EXCLUDE_EXTRA}' if submodules else EXCLUDE_EXTRA
+
+
+def load_db(build_dir):
+ db_path = os.path.join(build_dir, 'compile_commands.json')
+ if not os.path.isfile(db_path):
+ print(f'error: {db_path} not found', file=sys.stderr)
+ print('Run "make px4_sitl_default-clang" first to generate '
+ 'the compilation database', file=sys.stderr)
+ sys.exit(1)
+ try:
+ with open(db_path) as f:
+ return json.load(f)
+ except json.JSONDecodeError as e:
+ print(f'error: compile_commands.json is malformed: {e}', file=sys.stderr)
+ sys.exit(1)
+
+
+def find_tus(changed, db, root):
+ db_files = {e['file'] for e in db}
+ result = set()
+ for f in changed:
+ abs_path = os.path.join(root, f)
+ ext = os.path.splitext(f)[1]
+ if ext in EXTENSIONS_CPP:
+ if abs_path in db_files:
+ result.add(abs_path)
+ elif ext in EXTENSIONS_HDR:
+ hdr = os.path.basename(f)
+ for e in db:
+ try:
+ if hdr in open(e['file']).read():
+ result.add(e['file'])
+ except OSError:
+ pass # file deleted in PR — skip
+ return sorted(result)
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument('base_ref',
+ help='Git ref to diff against, e.g. origin/main')
+ args = parser.parse_args()
+
+ root = repo_root()
+ build_dir = os.path.join(root, 'build', 'px4_sitl_default-clang')
+
+ run_tidy = os.path.join(root, 'Tools', 'run-clang-tidy.py')
+ if not os.path.isfile(run_tidy):
+ print(f'error: {run_tidy} not found', file=sys.stderr)
+ sys.exit(1)
+
+ changed = changed_files(args.base_ref, root)
+ if not changed:
+ print('No C++ files changed — skipping clang-tidy')
+ sys.exit(0)
+
+ db = load_db(build_dir)
+ tus = find_tus(changed, db, root)
+
+ if not tus:
+ print('No matching TUs in compile_commands.json — skipping clang-tidy')
+ sys.exit(0)
+
+ print(f'Running clang-tidy on {len(tus)} translation unit(s)')
+
+ result = subprocess.run(
+ [sys.executable, run_tidy,
+ '-header-filter=.*\\.hpp',
+ '-j0',
+ f'-exclude={build_exclude(root)}',
+ '-p', build_dir] + tus
+ )
+ sys.exit(result.returncode)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/docs/en/test_and_ci/continous_integration.md b/docs/en/test_and_ci/continous_integration.md
index 86033febbc..4b3a820bbf 100644
--- a/docs/en/test_and_ci/continous_integration.md
+++ b/docs/en/test_and_ci/continous_integration.md
@@ -2,6 +2,512 @@
PX4 uses GitHub Actions for continuous integration, with different workflows handling code builds, testing, and documentation.
+## Code CI
+
+PX4 builds and testing are performed using GitHub Actions with a waterfall/staged pipeline architecture to optimize costs and provide fast developer feedback.
+
+### CI Architecture Overview
+
+PX4 uses a **4-tier waterfall pipeline** that ensures expensive AWS-hosted integration tests only run after basic checks pass. This architecture prevents costly runs when simple issues like formatting errors are present.
+
+#### Pipeline Structure
+
+```
+┌─────────────────────────────────────────────┐
+│ TIER 1: Gate Checks (2-5 min) │
+│ • Format checks, shellcheck, Python linting │
+│ • GitHub-hosted runners (free/cheap) │
+│ • fail-fast: stops on first failure │
+└─────────────────┬───────────────────────────┘
+ │ ALL PASS
+ ▼
+┌─────────────────────────────────────────────┐
+│ TIER 2: Builds, Analysis & Platform Checks │
+│ (10-30 min) │
+│ • SITL build + cache seed (PX4 + Gazebo) │
+│ • Unit tests, static analysis, EKF checks │
+│ • Ubuntu/macOS builds, ITCM, flash analysis │
+│ • Failsafe web simulator (Emscripten) │
+│ • Mixed runners (AWS 4cpu + GitHub) │
+└─────────────────┬───────────────────────────┘
+ │ ALL PASS
+ ▼
+┌─────────────────────────────────────────────┐
+│ TIER 3: Integration Tests (30-45 min) │
+│ • SITL tests (Gazebo + MAVSDK, 20x speed) │
+│ • ROS2 integration, MAVROS tests │
+│ • ROS translation node (humble + jazzy) │
+│ • AWS Self-hosted 8cpu runners │
+└─────────────────┬───────────────────────────┘
+ │ ALL PASS
+ ▼
+┌─────────────────────────────────────────────┐
+│ TIER 4: Full Build Matrix (30-60 min) │
+│ • Build all board targets │
+│ • AWS 8cpu runners (most expensive) │
+│ • Only on main/stable/beta/release/tags │
+└─────────────────────────────────────────────┘
+```
+
+### Active Workflows
+
+#### Core CI
+
+**[ci-orchestrator.yml](https://github.com/PX4/PX4-Autopilot/blob/main/.github/workflows/ci-orchestrator.yml)** - Main CI Pipeline
+- Runs on all PRs and pushes
+- Implements Tiers 1-4 with cascading dependencies
+- Fails fast to save costs and provide quick feedback
+- Uses concurrency control to cancel outdated runs
+
+**[build_all_targets.yml](https://github.com/PX4/PX4-Autopilot/blob/main/.github/workflows/build_all_targets.yml)** - Full Board Build Matrix
+- Triggered by orchestrator completion on PRs
+- Runs independently on tagged releases
+- Builds all board configurations
+- Uploads artifacts to S3 and GitHub Releases
+
+#### Infrastructure Workflows
+
+- **dev_container.yml** - Docker container builds for development environment
+- **fuzzing.yml** - Daily fuzzing tests for security
+
+#### Maintenance Workflows
+
+- **ekf_update_change_indicator.yml** - Track EKF functional changes
+- **label.yml** - Auto-label PRs based on modified files
+- **stale.yml** - Mark and close stale issues/PRs
+- **sync_to_px4_msgs.yml** - Sync ROS message definitions to px4_msgs repo
+
+### CI Tier Details
+
+#### Tier 1: Gate Checks (2-5 minutes)
+
+**Purpose:** Catch common errors quickly before spinning up expensive resources.
+
+**Jobs:**
+- Format checks (`check_format`)
+- Newline checks (`check_newlines`)
+- Shellcheck for bash scripts
+- Python linting (mypy, flake8) for MAVSDK tests
+
+**Runner:** GitHub-hosted `ubuntu-latest`
+
+**Behavior:** `fail-fast: true` - stops all jobs immediately on first failure
+
+#### Tier 2: Builds, Analysis & Platform Checks (10-30 minutes)
+
+**Purpose:** Validate code compiles, passes unit tests, and builds on all platforms. This tier runs all build and analysis jobs in parallel to minimize wall-clock time.
+
+**Jobs:**
+- **Build SITL Cache Seed** - Builds `px4_sitl_default` and Gazebo Classic plugins, seeds the ccache for downstream SITL test jobs
+- **Basic Tests** - Unit tests, module configuration validation, module documentation checks, and EKF functional change detection (PRs only — checks `src/modules/ekf2/test/change_indication` for uncommitted diff after running EKF tests)
+- **Clang-tidy** static analysis (16cpu runner); incremental on PRs (changed files only), full scan on push to main/stable/beta/release
+- **Clang-tidy PR Annotations** - Posts inline line-level review comments with one-click fix suggestions via `platisd/clang-tidy-pr-comments`; same-repo PRs only, informational (does not block merge)
+- **Ubuntu builds** (22.04, 24.04) on AWS 4cpu runners
+- **macOS build** on GitHub macOS runners
+- **ITCM memory checks** for 4 NuttX targets (fmu-v5x, fmu-v6xrt, tropic variants)
+- **Flash analysis** using bloaty for 2 targets (fmu-v5x, fmu-v6x), with PR comment
+- **Failsafe web simulator** build (Emscripten, cached SDK)
+
+**Runners:** Mixed (AWS 4cpu-linux-x64, 16cpu for clang-tidy, 2cpu for clang-tidy annotations, GitHub macos-latest)
+
+**Behavior:** Only runs if Tier 1 passes completely; `fail-fast: false` to let all jobs attempt
+
+#### Tier 3: Integration Tests (30-45 minutes)
+
+**Purpose:** Run expensive simulation and integration tests.
+
+**Jobs:**
+- **SITL Tests** - Gazebo Classic simulation with MAVSDK test suite at 20x speed factor (iris, tailsitter, standard_vtol models)
+- **ROS2 Integration** - Build and test ROS2 interface library
+- **MAVROS Mission Tests** - Mission execution tests
+- **MAVROS Offboard Tests** - Offboard control tests
+- **ROS Translation Node** - Tests for humble and jazzy distributions
+
+**Runners:** AWS Self-hosted 8cpu-linux-x64
+
+**Behavior:** Only runs if all Tier 2 jobs pass; `fail-fast: false` to collect all test results
+
+**Timeout:** 45 minutes per job
+
+#### Tier 4: Full Build Matrix (30-60 minutes)
+
+**Purpose:** Build all board targets for firmware distribution.
+
+**Trigger:**
+- Automatically after orchestrator succeeds (for PRs)
+- Independently on main/stable/beta/release branches
+- Independently on version tags (v*)
+
+**Process:**
+1. Scan and group board targets by architecture
+2. Build targets in parallel on AWS 8cpu runners
+3. Package and upload artifacts
+
+**Artifacts:**
+- Uploaded to S3 bucket (px4-travis) for QGroundControl
+- Uploaded to GitHub Releases for version tags
+- Draft releases created for manual review before publishing
+
+**Runners:** AWS Self-hosted 8cpu-linux-x64 (most expensive)
+
+### Runner Types
+
+#### GitHub-Hosted Runners
+
+Used for Tier 1 gate checks and macOS builds.
+
+**Benefits:**
+- Fast startup (5-10 seconds)
+- Free for public repos (2,000 minutes/month)
+- Low cost when paying (~$0.008/min for Linux)
+- Reliable provisioning
+
+**Used For:**
+- Format/lint checks
+- Python linting
+- Shellcheck
+- macOS builds
+
+#### AWS Self-Hosted Runners (RunsOn)
+
+Used for Tiers 2-4 builds and tests.
+
+**Configuration:**
+- `1cpu-linux-x64` - Utility jobs (flash analysis comment publishing)
+- `2cpu-linux-x64` - Clang-tidy PR annotation posting
+- `4cpu-linux-x64` - SITL cache seed, basic tests, EKF checks, platform builds, flash analysis, ITCM checks, failsafe sim
+- `8cpu-linux-x64` - SITL integration tests, ROS integration tests, full build matrix
+- `16cpu-linux-x64` - Clang-tidy static analysis
+- `spot=false` - On-demand instances (can be changed to spot for 60-70% savings)
+
+**Used For:**
+- SITL simulation tests (8cpu for Gazebo physics at 20x speed)
+- ROS integration tests (8cpu for parallel compilation of xrce-dds and ROS2 libraries)
+- Full board compilation (8cpu)
+- Platform builds and analysis (4cpu)
+
+### Fork CI Configuration
+
+Forks can run CI without AWS self-hosted runners. There are two paths depending on how much of the pipeline you need.
+
+#### Option 1: Configure the Orchestrator
+
+Use the full orchestrator with settings tuned for your fork's infrastructure. This gives you access to all tiers, job toggles, and cache tuning.
+
+1. Copy the example config:
+
+ ```bash
+ cp .github/ci-config.yml.example .github/ci-config.yml
+ ```
+
+2. Edit `.github/ci-config.yml` to match your setup:
+ - **Runner labels** -- point all tiers to `ubuntu-latest` (or your own self-hosted labels)
+ - **Job toggles** -- disable hardware-specific jobs (ITCM, flash analysis, platform builds) that don't apply to your fork
+ - **Cache sizes** -- GitHub provides 10 GB per repo; the example config uses roughly 15-20% of that with most upstream-specific jobs disabled
+ - **`is_gha` flag** -- set to `true` when running on GitHub-hosted runners (wired for future infrastructure-aware behavior)
+
+3. Commit the file and push. The orchestrator reads it at runtime and applies your overrides.
+
+This configuration has been validated end-to-end on GitHub-hosted `ubuntu-latest` runners: [successful run](https://github.com/PX4/PX4-Autopilot/actions/runs/22746668606).
+
+#### Option 2: Use the Simple CI Workflow
+
+If you only need basic validation (SITL build, one hardware target, unit tests, format checks), use the single-job workflow instead of the full orchestrator.
+
+1. Rename the example file:
+
+ ```bash
+ mv .github/workflows/ci-simple.yml.example .github/workflows/ci-simple.yml
+ ```
+
+2. Optionally delete `ci-orchestrator.yml` to avoid running both workflows.
+
+3. Enable GitHub Actions in your fork settings if not already enabled.
+
+The simple workflow runs a single `quick-check` job on `ubuntu-latest` that builds PX4 SITL, FMU-v5, runs unit tests, and checks formatting. It typically completes in under 15 minutes and has no AWS or self-hosted runner dependencies.
+
+### Cost Optimization Features
+
+#### 1. Cascading Dependencies
+
+Each tier only executes if the previous tier passes completely. This prevents expensive AWS runners from spinning up when basic checks fail.
+
+**Example:**
+- Format error detected in 2 minutes (Tier 1)
+- Pipeline stops immediately
+- AWS runners never start
+- Cost: ~$0.01 vs ~$5 for full run
+
+#### 2. Fail-Fast Strategy
+
+Tier 1 uses `fail-fast: true` to stop all parallel jobs on the first failure, providing immediate feedback to developers.
+
+#### 3. Concurrency Control
+
+All workflows use `cancel-in-progress: true` to automatically stop outdated runs when developers push new commits.
+
+#### 4. Branch-Aware Execution
+
+The full build matrix (Tier 4) only runs on important branches:
+- main, stable, beta
+- release/* branches
+- Version tags (v*)
+
+Feature branch PRs skip the expensive full build after validation in Tiers 1-3.
+
+#### 5. Path-Based Filtering
+
+Most CI workflows ignore changes to `docs/**` paths to avoid unnecessary builds when only documentation is modified.
+
+### Developer Experience
+
+#### Quick Feedback Loop
+
+The waterfall architecture provides progressively detailed feedback:
+
+| Issue Type | Detection Time | Tier | Cost Impact |
+|------------|---------------|------|-------------|
+| Format error | 2 minutes | Tier 1 | ~$0.01 |
+| Unit test / build failure | 15 minutes | Tier 2 | ~$0.50 |
+| Integration test failure | 45 minutes | Tier 3 | ~$2.00 |
+| Board target failure | 90 minutes | Tier 4 | ~$5.00 |
+
+#### Before/After Comparison
+
+**Before (28 separate workflows):**
+- All workflows start simultaneously on every PR
+- Format error detected at 2 min, but expensive tests run for 45+ min anyway
+- Total wasted compute: ~43 minutes of AWS 4cpu + 8cpu runners
+- Cost per failed PR: ~$5-10
+
+**After (Orchestrator + Build All Targets):**
+- Format error detected at 2 min
+- Pipeline stops immediately after Tier 1
+- Total wasted compute: ~2 minutes of GitHub-hosted runners
+- Cost per failed PR: ~$0.01
+- **Savings: ~99% on early failures, ~40-60% overall**
+
+### Caching Strategy
+
+The CI orchestrator uses several caching mechanisms to avoid redundant work across jobs and runs.
+
+#### ccache (C++ compilation cache)
+
+ccache caches compiled object files so unchanged source files skip recompilation on subsequent runs.
+
+**Cache keys follow this fallback pattern:**
+
+```
+ccache-{scope}-{branch}-{sha} # exact match (never hits, since sha is unique)
+ccache-{scope}-{branch}- # same branch, most recent commit
+ccache-{scope}-{base_branch}- # base branch (e.g. main), for PR first runs
+ccache-{scope}- # any branch, last resort
+```
+
+The exact-match key (`{sha}`) is used as the **save** key. Since GitHub Actions cache is immutable (write-once), each commit saves a new cache entry. The restore step falls through to the most recent cache from the same branch, or the base branch.
+
+**Cache scopes and sizes:**
+
+| Scope | Key prefix | Max size | Contents | Saved by |
+|-------|-----------|----------|----------|----------|
+| `ccache-sitl` | `ccache-sitl-` | 400M | PX4 SITL firmware + Gazebo Classic plugins | `build-sitl` (cache seed job) |
+| `ccache-clang-tidy` | `ccache-clang-tidy-` | 250M | Clang-tidy analysis objects | `clang-tidy` |
+| `ccache-ubuntu` | `ccache-ubuntu-{container}-` | 250M | Ubuntu build objects (per container version) | `ubuntu-builds` |
+| `ccache-macos` | `ccache-macos-` | 400M | macOS build objects | `macos-build` |
+| `ccache-ros-integration` | `ccache-ros-integration-` | 500M | PX4 + xrce-dds + Gazebo + ROS2 libraries | `ros-integration-tests` |
+| `ccache-ros-translation-{ros}` | `ccache-ros-translation-{ros_version}-` | 250M | ROS translation node build objects (per ROS distro) | `ros-translation-node` |
+| `ccache-flash-{target}-current` | `ccache-flash-{target}-current-` | 250M | Flash analysis objects for PR HEAD (per board target) | `flash-analysis` |
+| `ccache-flash-{target}-baseline` | `ccache-flash-{target}-baseline-` | 250M | Flash analysis objects for baseline commit (per board target) | `flash-analysis` |
+| `px4-ros2-ws` | `px4-ros2-ws-v1-galactic-{image}-{msg-hash}` | — | PX4 ROS 2 Interface Library workspace at `/opt/px4_ws` (keyed on msg hash) | `ros-integration-tests` |
+
+**Cache seed pattern:** The `build-sitl` job acts as a cache seed for all downstream SITL-related jobs. It builds both `px4_sitl_default` and `sitl_gazebo-classic`, then saves the combined ccache. Downstream jobs (`basic-tests`, `ekf-functional-check`, `sitl-tests`) restore this cache using `actions/cache/restore` (read-only) and get near-100% hit rates without needing to save their own caches.
+
+**ccache configuration (all jobs):**
+
+| Setting | Value | Purpose |
+|---------|-------|---------|
+| `base_dir` | `${GITHUB_WORKSPACE}` | Normalize paths for cache portability |
+| `compression` | `true` | Reduce cache storage size |
+| `compression_level` | `6` | Balance compression ratio vs speed |
+| `hash_dir` | `false` | Ignore directory paths in hash (portability) |
+| `compiler_check` | `content` | Hash compiler binary content, not path/mtime |
+
+#### Emscripten SDK cache
+
+The failsafe web simulator job caches the Emscripten SDK directory to avoid re-cloning and installing it on every run.
+
+| Key | Path | Contents |
+|-----|------|----------|
+| `emsdk-4.0.15` | `_emscripten_sdk` | Full emsdk installation (pinned to version 4.0.15) |
+
+This is a simple version-pinned key. Updating the emsdk version in the workflow automatically invalidates the cache.
+
+#### macOS Homebrew and pip caches
+
+The macOS build job caches Homebrew packages and pip installations to avoid re-downloading dependencies.
+
+#### Why separate cache scopes?
+
+Different jobs use different compilers, flags, and build targets. Sharing a single ccache across all jobs would cause constant eviction as incompatible objects compete for space. Separate scopes ensure each job's cache stays warm with relevant objects.
+
+#### CI Status
+
+Check which tier failed by looking at the job names in the GitHub Actions UI:
+
+- Tier 1 failure (T1 prefix) - Gate checks (format, lint) - Fix formatting/style issues
+- Tier 2 failure (T2 prefix) - Builds/analysis (tests, static analysis, platform builds) - Fix compilation or test failures
+- Tier 3 failure (T3 prefix) - Integration tests - Fix SITL/ROS test failures
+- All tiers passed - Ready for merge (after approvals)
+
+### Manual Workflow Triggers
+
+All workflows support manual execution via GitHub Actions UI or CLI:
+
+**Via GitHub UI:**
+1. Go to Actions tab
+2. Select the workflow
+3. Click "Run workflow"
+4. Choose branch and click "Run"
+
+**Via GitHub CLI:**
+```bash
+gh workflow run ci-orchestrator.yml
+gh workflow run build_all_targets.yml
+```
+
+This is useful for:
+- Re-running specific workflows for debugging
+- Testing workflow changes
+- Manually triggering builds on branches
+
+### Troubleshooting
+
+#### Workflow Not Triggering
+
+**Possible causes:**
+- PR only modifies `docs/**` paths (ignored by most workflows)
+- Orchestrator must complete successfully for `build_all_targets` to trigger (Tier 4)
+- Workflow was canceled by a newer commit (concurrency control)
+
+**Solution:**
+- Check the Actions tab for workflow status
+- Verify files modified are not in ignored paths
+- Wait for orchestrator to complete before expecting Tier 4
+
+#### Unexpected CI Failures
+
+**Debugging steps:**
+1. Check which tier failed by looking at job name prefixes (T1, T2, T3)
+2. Review the specific failed job logs in that tier
+3. Ensure your branch is rebased on latest main
+4. Look for infrastructure issues (runner availability, network problems)
+5. Try re-running failed jobs using "Re-run failed jobs" button
+
+#### Common Issues
+
+**Clang-tidy failures:**
+- On PRs, only changed files are analyzed. Run `python3 Tools/ci/run-clang-tidy-pr.py origin/main` locally to reproduce the incremental check
+- For a full scan (matches push-to-main behavior): `make clang-tidy`
+- Fix any warnings or use `// NOLINT` comments for false positives
+- Inline fix suggestions are posted as PR review comments by the `post-clang-tidy-comments` job (same-repo PRs only)
+
+**SITL test timeouts:**
+- Tests have a 45-minute timeout
+- Check for deadlocks or infinite loops in simulation code
+- Review MAVSDK test logs in failed artifacts
+
+**AWS runner unavailable:**
+- RunsOn provisions runners on-demand
+- Rarely, provisioning can fail due to AWS capacity
+- Re-run the workflow or wait a few minutes and try again
+
+**Checkout errors in workflow_run:**
+- The `build_all_targets` workflow (Tier 4) uses `workflow_run` trigger
+- It automatically checks out the correct commit SHA
+- If issues persist, check GitHub Actions permissions
+
+### CI Best Practices for Contributors
+
+#### Before Pushing
+
+1. **Run format checks locally:**
+ ```bash
+ make check_format
+ make shellcheck_all
+ ```
+
+2. **Run unit tests:**
+ ```bash
+ make tests
+ ```
+
+3. **Build your target configuration:**
+ ```bash
+ make px4_fmu-v5_default # or your target
+ ```
+
+#### During PR Review
+
+- Monitor CI status in the GitHub UI
+- Address failures starting from the earliest tier
+- Don't push new commits while CI is running if possible (cancels previous run)
+- Use draft PRs to prevent expensive builds until ready
+
+#### When CI Fails
+
+1. Check which tier failed by looking at job name prefixes (T1, T2, T3)
+2. Read the failed job logs carefully
+3. Reproduce the issue locally if possible
+4. Fix the root cause, not just the symptom
+5. Consider if your changes affect other platforms/configurations
+
+### Future Optimization Opportunities
+
+#### 1. Enable Spot Instances
+
+Change `spot=false` to `spot=true` for PR testing to reduce costs by 60-70%. This requires accepting occasional (~5%) provisioning failures.
+
+#### 2. Path-Based Test Selection
+
+Only run ROS tests if ROS-related files (`msg/**`, `src/modules/uxrce_dds_client/**`) are modified.
+
+#### 3. Skip Tests on Draft PRs
+
+Add `if: github.event.pull_request.draft == false` to expensive jobs, allowing draft PRs to skip CI entirely.
+
+#### 4. Pre-built Container Images for ROS
+
+Bake xrce-dds, ROS2 libraries, and Gazebo into updated container images to eliminate build-from-source overhead in integration test jobs (currently ~5-8 minutes per job).
+
+### Migration History
+
+This waterfall architecture was introduced to replace 28 independent workflows that all ran simultaneously. The migration:
+
+- Reduced workflow files from 28 to 14
+- Eliminated 1,057 lines of redundant YAML
+- Decreased CI costs by an estimated 40-60%
+- Improved developer feedback time for common errors
+- Maintained full test coverage with smarter execution
+
+The previous workflows that were consolidated:
+- `checks.yml` -> Tiers 1 & 2
+- `python_checks.yml` -> Tier 1
+- `clang-tidy.yml` -> Tier 2
+- `compile_macos.yml` -> Tier 2
+- `compile_ubuntu.yml` -> Tier 2
+- `sitl_tests.yml` -> Tier 3
+- `ros_integration_tests.yml` -> Tier 3
+- `mavros_mission_tests.yml` -> Tier 3
+- `mavros_offboard_tests.yml` -> Tier 3
+- `ros_translation_node.yml` -> Tier 3
+- `itcm_check.yml` -> Tier 2
+- `flash_analysis.yml` -> Tier 2
+- `failsafe_sim.yml` -> Tier 2
+- `nuttx_env_config.yml` -> Tier 2
+- `ekf_functional_change_indicator.yml` -> Tier 2
+
+The original 5-tier design was subsequently optimized to 4 tiers by merging platform builds (old Tier 3) into Tier 2, since they had no data dependency on basic test results and could run in parallel. This reduced wall-clock time by ~20 minutes on successful runs.
+
## Documentation CI
The documentation pipeline handles building, deploying, and translating the PX4 User Guide.
@@ -18,12 +524,12 @@ Jobs are organized in tiers, where each tier depends on the previous one complet
| Tier | Job | PR | Push / Dispatch | Description |
| ---- | -------------- | ---------------------------- | --------------- | ------------------------------------------------------------- |
-| T1 | Detect Changes | Yes | — | Checks if source code files changed (triggers metadata regen) |
-| T2 | PR Metadata | Yes (conditional) | — | Builds PX4 SITL and regenerates all auto-generated docs |
-| T2 | Metadata Sync | — | Yes | Builds PX4 SITL, regenerates metadata, auto-commits |
-| T2 | Link Check | Yes | — | Checks for broken links in changed files, posts PR comment |
+| T1 | Detect Changes | Yes | - | Checks if source code files changed (triggers metadata regen) |
+| T2 | PR Metadata | Yes (conditional) | - | Builds PX4 SITL and regenerates all auto-generated docs |
+| T2 | Metadata Sync | - | Yes | Builds PX4 SITL, regenerates metadata, auto-commits |
+| T2 | Link Check | Yes | - | Checks for broken links in changed files, posts PR comment |
| T3 | Build Site | Yes (if docs/source changed) | Yes (after T2) | Builds the VitePress documentation site |
-| T4 | Deploy | — | Yes | Deploys to AWS S3 |
+| T4 | Deploy | - | Yes | Deploys to AWS S3 |
#### Pull Request Flow
@@ -31,15 +537,15 @@ When a PR modifies files in `docs/**` or the orchestrator workflow file itself,
```txt
PR Event
- │
- ▼
+ |
+ v
┌─────────────────────────────────────┐
│ T1: Detect Changes │
│ • Checks if src/msg/ROMFS changed │
└─────────────────┬───────────────────┘
│
┌───────┴───────┐
- ▼ ▼
+ v v
┌──────────────────┐ ┌─────────────────────────┐
│ T2: PR Metadata │ │ T2: Link Check (~30s) │
│ (conditional) │ │ • Detects changed .md │
@@ -50,16 +556,16 @@ PR Event
│ failsafe web │ │
└────────┬─────────┘ │
└───────────┬────────────┘
- ▼
+ v
┌─────────────────────────────────────┐
│ T3: Build Site (~7-10 min) │
│ (skipped if only workflow YAML │
-│ changed — no docs/source changes) │
+│ changed - no docs/source changes) │
│ • Builds VitePress site │
│ • Verifies no build errors │
└─────────────────┬───────────────────┘
│
- ▼
+ v
DONE
```
@@ -73,12 +579,12 @@ PR Event
#### Push / Dispatch Flow (main/release branches)
When changes are pushed to `main` or `release/**` branches (or a `workflow_dispatch` is triggered), the workflow regenerates metadata, builds, and deploys.
-Only `main` and `release/*` branches are accepted for deploy — other branches will fail with a clear error.
+Only `main` and `release/*` branches are accepted for deploy -- other branches will fail with a clear error.
```txt
Push / Dispatch Event
- │
- ▼
+ |
+ v
┌─────────────────────────────────────┐
│ T2: Metadata Sync (~10-15 min) │
│ • Builds px4_sitl_default │
@@ -90,14 +596,14 @@ Push / Dispatch Event
│ (with [skip ci]) │
└─────────────────┬───────────────────┘
│
- ▼
+ v
┌─────────────────────────────────────┐
│ T3: Build Site (~7-10 min) │
│ • Builds VitePress site │
│ • Uploads build artifact │
└─────────────────┬───────────────────┘
│
- ▼
+ v
┌─────────────────────────────────────┐
│ T4: Deploy (~3 min) │
│ • Syncs to AWS S3 │
@@ -192,3 +698,12 @@ Jobs run on [runs-on](https://runs-on.com/) self-hosted runners with S3 cache:
| T2: Link Check | ubuntu-latest |
| T3: Build Site | 4 CPU |
| T4: Deploy | ubuntu-latest |
+
+## Contact
+
+For CI-related questions or issues:
+- GitHub Issues: Tag the CI maintainer
+- Slack: #infrastructure channel
+- Dev Call: Bring up during weekly meeting
+
+All workflows can be found in [.github/workflows/](https://github.com/PX4/PX4-Autopilot/tree/main/.github/workflows).
diff --git a/integrationtests/python_src/px4_it/mavros/mavros_offboard_attctl_test.py b/integrationtests/python_src/px4_it/mavros/mavros_offboard_attctl_test.py
index 5e791be7b5..4432e466e7 100755
--- a/integrationtests/python_src/px4_it/mavros/mavros_offboard_attctl_test.py
+++ b/integrationtests/python_src/px4_it/mavros/mavros_offboard_attctl_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
#***************************************************************************
#
# Copyright (c) 2015 PX4 Development Team. All rights reserved.
@@ -35,9 +35,6 @@
#
# @author Andreas Antener
#
-# The shebang of this file is currently Python2 because some
-# dependencies such as pymavlink don't play well with Python3 yet.
-from __future__ import division
PKG = 'px4'
@@ -46,7 +43,6 @@ from geometry_msgs.msg import Quaternion, Vector3
from mavros_msgs.msg import AttitudeTarget
from mavros_test_common import MavrosTestCommon
from pymavlink import mavutil
-from six.moves import xrange
from std_msgs.msg import Header
from threading import Thread
from tf.transformations import quaternion_from_euler
@@ -124,7 +120,7 @@ class MavrosOffboardAttctlTest(MavrosTestCommon):
loop_freq = 2 # Hz
rate = rospy.Rate(loop_freq)
crossed = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if (self.local_position.pose.position.x > boundary_x and
self.local_position.pose.position.y > boundary_y and
self.local_position.pose.position.z > boundary_z):
diff --git a/integrationtests/python_src/px4_it/mavros/mavros_offboard_posctl_test.py b/integrationtests/python_src/px4_it/mavros/mavros_offboard_posctl_test.py
index 81c6835a2b..8e88bf608b 100755
--- a/integrationtests/python_src/px4_it/mavros/mavros_offboard_posctl_test.py
+++ b/integrationtests/python_src/px4_it/mavros/mavros_offboard_posctl_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
#***************************************************************************
#
# Copyright (c) 2015 PX4 Development Team. All rights reserved.
@@ -35,9 +35,6 @@
#
# @author Andreas Antener
#
-# The shebang of this file is currently Python2 because some
-# dependencies such as pymavlink don't play well with Python3 yet.
-from __future__ import division
PKG = 'px4'
@@ -48,7 +45,6 @@ from geometry_msgs.msg import PoseStamped, Quaternion
from mavros_msgs.msg import ParamValue
from mavros_test_common import MavrosTestCommon
from pymavlink import mavutil
-from six.moves import xrange
from std_msgs.msg import Header
from threading import Thread
from tf.transformations import quaternion_from_euler
@@ -132,7 +128,7 @@ class MavrosOffboardPosctlTest(MavrosTestCommon):
loop_freq = 2 # Hz
rate = rospy.Rate(loop_freq)
reached = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if self.is_at_position(self.pos.pose.position.x,
self.pos.pose.position.y,
self.pos.pose.position.z, self.radius):
@@ -174,7 +170,7 @@ class MavrosOffboardPosctlTest(MavrosTestCommon):
positions = ((0, 0, 0), (50, 50, 20), (50, -50, 20), (-50, -50, 20),
(0, 0, 20))
- for i in xrange(len(positions)):
+ for i in range(len(positions)):
self.reach_position(positions[i][0], positions[i][1],
positions[i][2], 30)
diff --git a/integrationtests/python_src/px4_it/mavros/mavros_offboard_yawrate_test.py b/integrationtests/python_src/px4_it/mavros/mavros_offboard_yawrate_test.py
index f40c45b17f..88b646a19b 100755
--- a/integrationtests/python_src/px4_it/mavros/mavros_offboard_yawrate_test.py
+++ b/integrationtests/python_src/px4_it/mavros/mavros_offboard_yawrate_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
#***************************************************************************
#
# Copyright (c) 2020 PX4 Development Team. All rights reserved.
@@ -35,8 +35,6 @@
# @author Pedro Roque
#
-from __future__ import division
-
PKG = 'px4'
import rospy
@@ -44,7 +42,6 @@ from geometry_msgs.msg import Quaternion, Vector3
from mavros_msgs.msg import AttitudeTarget
from mavros_test_common import MavrosTestCommon
from pymavlink import mavutil
-from six.moves import xrange
from std_msgs.msg import Header
from threading import Thread
from tf.transformations import quaternion_from_euler
@@ -134,7 +131,7 @@ class MavrosOffboardYawrateTest(MavrosTestCommon):
loop_freq = 2 # Hz
rate = rospy.Rate(loop_freq)
crossed = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if (self.local_position.pose.position.x < boundary_x and
self.local_position.pose.position.x > -boundary_x and
self.local_position.pose.position.y < boundary_y and
diff --git a/integrationtests/python_src/px4_it/mavros/mavros_test_common.py b/integrationtests/python_src/px4_it/mavros/mavros_test_common.py
index 642d669335..ee5a95d438 100644
--- a/integrationtests/python_src/px4_it/mavros/mavros_test_common.py
+++ b/integrationtests/python_src/px4_it/mavros/mavros_test_common.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-from __future__ import division
+#!/usr/bin/env python3
import unittest
import rospy
@@ -11,7 +10,6 @@ from mavros_msgs.srv import CommandBool, ParamGet, ParamSet, SetMode, SetModeReq
WaypointPush
from pymavlink import mavutil
from sensor_msgs.msg import NavSatFix, Imu
-from six.moves import xrange
class MavrosTestCommon(unittest.TestCase):
@@ -183,7 +181,7 @@ class MavrosTestCommon(unittest.TestCase):
loop_freq = 1 # Hz
rate = rospy.Rate(loop_freq)
arm_set = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if self.state.armed == arm:
arm_set = True
rospy.loginfo("set arm success | seconds: {0} of {1}".format(
@@ -213,7 +211,7 @@ class MavrosTestCommon(unittest.TestCase):
loop_freq = 1 # Hz
rate = rospy.Rate(loop_freq)
mode_set = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if self.state.mode == mode:
mode_set = True
rospy.loginfo("set mode success | seconds: {0} of {1}".format(
@@ -247,7 +245,7 @@ class MavrosTestCommon(unittest.TestCase):
loop_freq = 1 # Hz
rate = rospy.Rate(loop_freq)
param_set = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
try:
res = self.set_param_srv(param_id, param_value)
if res.success:
@@ -274,7 +272,7 @@ class MavrosTestCommon(unittest.TestCase):
loop_freq = 1 # Hz
rate = rospy.Rate(loop_freq)
simulation_ready = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if all(value for value in self.sub_topics_ready.values()):
simulation_ready = True
rospy.loginfo("simulation topics ready | seconds: {0} of {1}".
@@ -297,7 +295,7 @@ class MavrosTestCommon(unittest.TestCase):
loop_freq = 10 # Hz
rate = rospy.Rate(loop_freq)
landed_state_confirmed = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if self.extended_state.landed_state == desired_landed_state:
landed_state_confirmed = True
rospy.loginfo("landed state confirmed | seconds: {0} of {1}".
@@ -325,7 +323,7 @@ class MavrosTestCommon(unittest.TestCase):
loop_freq = 10 # Hz
rate = rospy.Rate(loop_freq)
transitioned = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if transition == self.extended_state.vtol_state:
rospy.loginfo("transitioned | seconds: {0} of {1}".format(
i / loop_freq, timeout))
@@ -348,7 +346,7 @@ class MavrosTestCommon(unittest.TestCase):
loop_freq = 1 # Hz
rate = rospy.Rate(loop_freq)
wps_cleared = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if not self.mission_wp.waypoints:
wps_cleared = True
rospy.loginfo("clear waypoints success | seconds: {0} of {1}".
@@ -381,7 +379,7 @@ class MavrosTestCommon(unittest.TestCase):
rate = rospy.Rate(loop_freq)
wps_sent = False
wps_verified = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
if not wps_sent:
try:
res = self.wp_push_srv(start_index=0, waypoints=waypoints)
@@ -417,7 +415,7 @@ class MavrosTestCommon(unittest.TestCase):
loop_freq = 1 # Hz
rate = rospy.Rate(loop_freq)
res = False
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
try:
res = self.get_param_srv('MAV_TYPE')
if res.success:
diff --git a/integrationtests/python_src/px4_it/mavros/mission_test.py b/integrationtests/python_src/px4_it/mavros/mission_test.py
index 0692f11ec1..c4134cff00 100755
--- a/integrationtests/python_src/px4_it/mavros/mission_test.py
+++ b/integrationtests/python_src/px4_it/mavros/mission_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
#***************************************************************************
#
# Copyright (c) 2015-2016 PX4 Development Team. All rights reserved.
@@ -36,10 +36,6 @@
# @author Andreas Antener
#
-# The shebang of this file is currently Python2 because some
-# dependencies such as pymavlink don't play well with Python3 yet.
-from __future__ import division
-
PKG = 'px4'
import rospy
@@ -47,13 +43,13 @@ import glob
import json
import math
import os
-from px4tools import ulog
+import numpy as np
+from pyulog import ULog
import sys
from mavros import mavlink
from mavros_msgs.msg import Mavlink, Waypoint, WaypointReached
from mavros_test_common import MavrosTestCommon
from pymavlink import mavutil
-from six.moves import xrange
from threading import Thread
@@ -70,6 +66,49 @@ def get_last_log():
return last_log
+def analyze_estimator_attitude(log_file):
+ """Compute attitude estimator error metrics from a ULog file."""
+ ulog = ULog(log_file)
+
+ att = ulog.get_dataset('vehicle_attitude').data
+ att_gt = ulog.get_dataset('vehicle_attitude_groundtruth').data
+
+ def quat_to_euler(q0, q1, q2, q3):
+ """Quaternion (w,x,y,z) to (roll, pitch, yaw) via ZYX Tait-Bryan."""
+ roll = np.arctan2(2 * (q0 * q1 + q2 * q3), 1 - 2 * (q1**2 + q2**2))
+ sinp = 2 * (q0 * q2 - q3 * q1)
+ pitch = np.where(np.abs(sinp) >= 1,
+ np.copysign(np.pi / 2, sinp), np.arcsin(sinp))
+ yaw = np.arctan2(2 * (q0 * q3 + q1 * q2), 1 - 2 * (q2**2 + q3**2))
+ return roll, pitch, yaw
+
+ roll, pitch, yaw = quat_to_euler(
+ att['q[0]'], att['q[1]'], att['q[2]'], att['q[3]'])
+ roll_gt, pitch_gt, yaw_gt = quat_to_euler(
+ att_gt['q[0]'], att_gt['q[1]'], att_gt['q[2]'], att_gt['q[3]'])
+
+ # interpolate groundtruth onto attitude timestamps
+ ts = att['timestamp']
+ ts_gt = att_gt['timestamp']
+ roll_gt = np.interp(ts, ts_gt, roll_gt)
+ pitch_gt = np.interp(ts, ts_gt, pitch_gt)
+ yaw_gt = np.interp(ts, ts_gt, yaw_gt)
+
+ wrap = lambda x: np.arcsin(np.sin(x))
+ e_roll = wrap(roll - roll_gt)
+ e_pitch = wrap(pitch - pitch_gt)
+ e_yaw = wrap(yaw - yaw_gt)
+
+ return {
+ 'roll_error_mean': np.rad2deg(np.mean(e_roll)),
+ 'pitch_error_mean': np.rad2deg(np.mean(e_pitch)),
+ 'yaw_error_mean': np.rad2deg(np.mean(e_yaw)),
+ 'roll_error_std': np.rad2deg(np.std(e_roll)),
+ 'pitch_error_std': np.rad2deg(np.std(e_pitch)),
+ 'yaw_error_std': np.rad2deg(np.std(e_yaw)),
+ }
+
+
def read_mission(mission_filename):
wps = []
with open(mission_filename, 'r') as f:
@@ -188,7 +227,7 @@ class MavrosMissionTest(MavrosTestCommon):
# does it reach the position in 'timeout' seconds?
loop_freq = 2 # Hz
rate = rospy.Rate(loop_freq)
- for i in xrange(timeout * loop_freq):
+ for i in range(timeout * loop_freq):
pos_xy_d, pos_z_d = self.distance_to_wp(lat, lon, alt)
# remember best distances
@@ -295,9 +334,7 @@ class MavrosMissionTest(MavrosTestCommon):
rospy.loginfo("mission done, calculating performance metrics")
last_log = get_last_log()
rospy.loginfo("log file {0}".format(last_log))
- data = ulog.read_ulog(last_log).concat(dt=0.1)
- data = ulog.compute_data(data)
- res = ulog.estimator_analysis(data, False)
+ res = analyze_estimator_attitude(last_log)
# enforce performance
self.assertTrue(abs(res['roll_error_mean']) < 5.0, str(res))