{"owner":"mavlink","repo":"qgroundcontrol","hasSkills":true,"totalSkillsCount":26,"totalTokensCount":24436,"categories":["copilot-instructions","plugin-manifest","root-instruction","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":[".github/copilot-instructions.md","src/QtLocationPlugin/qgc_maps_plugin.json","AGENTS.md","tools/analyzers/README.md","tools/ci/README.md","tools/common/README.md","tools/coding-style/README.md","tools/configs/yamlfmt.yaml","tools/configs/gersemirc.yaml","tools/configs/editorconfig-checker.json","tools/README.md","tools/qgc_tools/README.md","tools/simulation/README.md","tools/terrain/README.md","tools/translations/README.md","tools/debuggers/gdb-pretty-printers/README.md","tools/generators/config_qml/README.md","tools/generators/settings_qml/README.md","docs/en/qgc-dev-guide/tools/index.md","docs/en/qgc-dev-guide/tools/mock_link.md","docs/ko/qgc-dev-guide/tools/index.md","docs/ko/qgc-dev-guide/tools/mock_link.md","docs/tr/qgc-dev-guide/tools/index.md","docs/tr/qgc-dev-guide/tools/mock_link.md","docs/zh/qgc-dev-guide/tools/index.md","docs/zh/qgc-dev-guide/tools/mock_link.md"],"skills":{".github/copilot-instructions.md":"# QGroundControl AI Assistant Guide\n\nThe canonical AI agent guide is [AGENTS.md](../AGENTS.md) — **read it first.**\nIt lists the critical files, code structure, build/test commands, CI layout,\nand links to every topic-specific reference (CODING_STYLE, CONTRIBUTING,\ntools/README, test/README, .github/ci-overview).\n","src/QtLocationPlugin/qgc_maps_plugin.json":"{\n    \"Keys\": [\"qgc-maps\"],\n    \"Provider\": \"QGroundControl\",\n    \"Version\": 101,\n    \"Experimental\": false,\n    \"Features\": [\n        \"OnlineMappingFeature\",\n        \"OfflineMappingFeature\"\n    ]\n}\n","AGENTS.md":"# AGENTS.md\n\nInstructions for AI coding agents (Codex, Claude Code, etc.) working on QGroundControl.\n\n## Quick References\n\n- [CODING_STYLE.md](CODING_STYLE.md) — Naming, formatting, C++20 features, QML style, logging\n- [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) — Architecture patterns (Fact System, Multi-Vehicle, FirmwarePlugin)\n- [tools/README.md](tools/README.md) — Development scripts, tooling, and every `just` recipe\n- [test/README.md](test/README.md) — Test framework, base classes, CTest labels, MultiSignalSpy, sanitizers, coverage\n- [cmake/README.md](cmake/README.md) — CMake modules, presets, and test options\n- [.github/ci-overview.md](.github/ci-overview.md) — CI workflow/action/script layout and conventions\n- [.pre-commit-config.yaml](.pre-commit-config.yaml) — Lint hooks (`just lint` runs every hook not marked `manual`)\n\n## Required Preflight\n\nBefore modifying any repository file:\n\n1. Read [CODING_STYLE.md](CODING_STYLE.md) completely.\n2. Read the task-specific references listed in this file.\n3. Do not begin editing until those reads are complete.\n4. Before the first edit, state in a progress update which instruction files were read.\n\n## Golden Rules\n\nThese are the non-negotiables. The first four are QGC's core architecture patterns. The pre-commit hook named\nin a rule fails CI on a violation. Full list with code examples:\n[.github/CONTRIBUTING.md#architecture-patterns](.github/CONTRIBUTING.md#architecture-patterns) and\n[CODING_STYLE.md#common-pitfalls](CODING_STYLE.md#common-pitfalls).\n\n- **Fact System** — ALL vehicle parameters flow through Facts; never create custom parameter storage.\n- **Multi-Vehicle** — ALWAYS null-check `activeVehicle()` / `Vehicle*` before dereferencing (`vehicle-null-check`\n  flags unchecked `activeVehicle()` results).\n- **Firmware Plugin** — use `vehicle->firmwarePlugin()` for firmware-specific behavior, not `if (px4)` branches.\n- **QML Integration** — register types with `QML_ELEMENT`/`QML_SINGLETON`/`QML_UNCREATABLE`; expose state via `Q_PROPERTY`.\n- **No `Q_ASSERT` in production code** — use defensive checks with early returns (`check-no-qassert`).\n- **No `QTest::ignoreMessage`** in tests — use `expectLogMessage`/`ignoreLogMessage` (`check-no-qtest-ignore-message`).\n- **No fixed-delay `QTest::qWait(<n>)`** — use `QTRY_*_WITH_TIMEOUT` or `QSignalSpy::wait` (`check-no-fixed-qwait`).\n- **Comments** — prefer self-documenting code; comment only non-obvious intent, constraints, or workarounds.\n\n## Critical Files (Read First!)\n\n1. `src/FactSystem/Fact.h` — Parameter system foundation\n2. `src/Vehicle/Vehicle.h` — Core vehicle model\n3. `src/FirmwarePlugin/FirmwarePlugin.h` — Firmware abstraction\n\n## Code Structure\n\nKey modules (`src/` has about 30 subdirectories):\n\n```text\nsrc/\n├── Vehicle/          # Vehicle state/comms\n├── Comms/            # Link layer (serial, UDP, TCP, Bluetooth)\n├── FactSystem/       # Parameter management\n├── FirmwarePlugin/   # PX4/ArduPilot abstraction\n├── AutoPilotPlugins/ # Vehicle setup UI\n├── MissionManager/   # Mission planning\n├── MAVLink/          # Protocol handling\n├── VideoManager/     # Video pipeline (GStreamer)\n├── FlyView/          # In-flight UI\n├── PlanView/         # Mission planning UI\n├── QmlControls/      # Reusable QML components\n└── Settings/         # Persistent settings\n```\n\n## Build & Test Commands\n\nThe `just` recipes are the canonical workflow — see [tools/README.md](tools/README.md) for the full list. They\nconfigure and build `build/` with the `default*` preset for `BUILD_TYPE` (default `Debug`, tests enabled).\n\n```bash\njust configure          # CMake configure through the default preset\njust build              # incremental build; uses all cores (override with JOBS=N)\njust test               # ctest --preset default, LABELS=\"Unit|Integration\" EXCLUDE=\"Flaky|Network\"\njust lint               # prek run --all-files (the pre-commit hooks)\njust lint-docs          # only the docs hooks, on docs/en pages (pass --changed for branch changes)\njust test-python        # pytest for tools/tests (CI script tests in tools/tests/ci)\njust check              # lint + test-python + test\njust format-fix         # apply clang-format to changed lines\njust info               # print configured versions (Qt, CMake minimum, GStreamer) and build settings\n```\n\n- **Build incrementally** — rebuild every few file edits during multi-file C++/Qt work, not just at the end; fix build errors before continuing.\n- **Tight test loops** — iterate one test with `ctest --test-dir build -R <name>` or\n  `build/Debug/QGroundControl --unittest:<name>` (Linux path); run `just test` on the final pass.\n- **Match CI** — Linux CI runs the `Unit` and then the `Integration` label as separate CTest runs, excluding\n  `Flaky|Network`, in the `Linux-coverage` and `Linux-sanitizers` (ASan + UBSan; also excludes `NoSanitizer`)\n  presets. Reproduce a sanitizer failure with `cmake --workflow --preset Linux-sanitizers` (needs `QT_ROOT_DIR`)\n  and coverage with `just coverage`. CI lints only the files changed from the PR base, like\n  `uv run --frozen --project tools --group dev python tools/pre_commit.py --changed`.\n  See [.github/ci-overview.md](.github/ci-overview.md) for the rest.\n\n## Definition of Done\n\nBefore considering a change complete:\n\n1. `just build` succeeds.\n2. `just lint` passes.\n3. Relevant tests pass: `ctest --test-dir build -R <name>` for the touched area, `just test-python` for Python\n   tooling, and `just test` on the final pass.\n4. Commit message follows Conventional Commits (below).\n\n## Commit & Review Conventions\n\nCommit messages follow **Conventional Commits** — the type decides the release-notes section\n(`tools/configs/cliff.toml` → git-cliff). Use: `feat`, `fix`, `perf`, `revert` (listed in release notes);\n`docs`, `style`, `chore`, `refactor`, `test`, `build`, `ci` (omitted unless breaking). Example: `fix(Vehicle): guard null activeVehicle in telemetry handler`.\n`prek install` adds a `commit-msg` hook that checks this format; Renovate's `deps` type, `fixup!`/`squash!`,\nmerge, and revert messages also pass.\n\nKeep pull request history intentional and easy to review. Organize related work into coherent commits and\ncombine fixups or closely related incremental changes before publication. Avoid a trail of tiny work-in-progress,\ncleanup, or review-fix commits. Each commit should represent a clear, reviewable change without mixing unrelated work.\n\nYour output will be reviewed by another AI agent before being accepted. Keep changes focused and\nminimal, use clear naming, and leave explanatory commit messages. Avoid unrelated changes,\ncommented-out code, or ambiguous TODOs.\n\n---\n\n**Key Principle**: Match the style of code you're editing. See [CODING_STYLE.md](CODING_STYLE.md) for conventions and [CODING_STYLE.md#examples](CODING_STYLE.md#examples) for canonical Vehicle/Fact/QML snippets.\n","tools/analyzers/README.md":"# QGC Static Analyzers\n\nBackends for [`tools/analyze.py`](../analyze.py) and QGC-specific static analyzers for C++ code.\nFor `analyze.py` usage and compiler-analysis prerequisites, see\n[tools/README.md](../README.md#analyzepy); for CI planning, sharding, and result caching, see\n[ci-overview.md](../../.github/ci-overview.md).\n\n## Compiler Analysis Output\n\nCompiler analysis (clang-tidy, Clazy) writes these files to the build directory:\n\n- `<tool>-progress.jsonl` records each file's duration and status as it completes, so\n  interrupted CI jobs retain partial timing evidence.\n- `<tool>-timings.json` adds selection, elapsed and accumulated worker time, shard coverage,\n  result-cache statistics, and (when profiling) per-check costs.\n- `<tool>-diagnostics.json` lists each unique diagnostic once (line, column, severity, and check,\n  by file relative to the checkout, or `build/...` for generated files), so reports can count a\n  header's warnings once across translation units and shards. `commit` names the checkout whose\n  line numbers they use (`GITHUB_SHA`, which on a pull request is GitHub's merge commit rather\n  than the PR head, else `git rev-parse HEAD`; absent when neither is known). With `PR_HEAD_SHA`\n  set (Code Analysis on a pull request), `changed` repeats those on lines the pull request changed\n  (`common.git.changed_ranges` against the PR's diff base).\n- `<tool>-raw.txt` keeps every translation unit's full output; the console shows each identical\n  diagnostic block once.\n\n`--cache-dir` (`result_cache.py`) replays results whose tool, configuration, compile command, and\npreprocessed inputs are unchanged; tool failures, timeouts, and crashes always run again.\n\nPer-check profiling adds substantial overhead and is disabled by default. Use\n`python tools/analyze.py --tool clang-tidy --profile-checks src/LogManager/LogEntryTableModel.cc`\nor the manual workflow's `profile_checks` input with a narrow path and `analyze_all: false`.\nProfiled runs print the most expensive checks and archive the original profiles under\n`clang-tidy-profiles/`. Check costs are summed across translation units and overlap with parallel\nwork. Ordinary runs retain per-file timing and progress without instrumenting individual checks.\n\n## clang-tidy Fixes\n\n`python tools/analyze.py --tool clang-tidy --fix [PATH...]` runs clang-tidy with\n`--export-fixes` for each selected translation unit, writing `clang-tidy-fixes/*.yaml` in the build\ndirectory, then merges them with `clang-apply-replacements --ignore-insert-conflict`. Headers shared\nby several translation units therefore receive each fix once, which running `clang-tidy --fix` in\nparallel cannot guarantee. The `clang-apply-replacements` beside `clang-tidy` is preferred (both ship\nin LLVM packages and the pinned `clang-tidy` PyPI wheel), then `PATH`.\n\n- Fix runs bypass `--cache-dir`: a replayed result would export no fixes.\n- Header fixes are limited to this checkout's `src/`, `test/`, and `tools/`, because CPM sources and\n  generated headers in other `src/` directories also match `.clang-tidy`'s `HeaderFilterRegex`.\n- Fixes from translation units that failed to compile are discarded, as `clang-tidy --fix` does.\n- Fixed code is not reformatted; run `just format-fix`, review with `git diff`, and rerun without\n  `--fix` for findings that have no automatic fix.\n- `analyze.py` refuses `--tool clang-tidy --fix` when `CI` or `GITHUB_ACTIONS` is set; CI only\n  reports findings.\n\n## clang-format Hook\n\n`clang_format.py` also runs as the `clang-format` pre-commit hook\n(`python3 tools/analyzers/clang_format.py [--fix] FILE...`). It checks, or with `--fix` formats, only\nthe lines that differ from the PR merge base (pre-commit's `PRE_COMMIT_FROM_REF` and\n`PRE_COMMIT_TO_REF`) or, locally, from `HEAD`. New files are checked in full and deletion-only\nchanges are skipped, as in `analyze.py --tool clang-format`.\n\n## Vehicle Null-Check Analyzer\n\nDetects unsafe patterns where `activeVehicle()` results are used without null checks.\n\n### Usage\n\n```bash\n# Analyze specific files\npython3 tools/analyzers/vehicle_null_check.py src/Vehicle/*.cc\n\n# Analyze directory recursively\npython3 tools/analyzers/vehicle_null_check.py src/\n\n# JSON output for CI/editor integration\npython3 tools/analyzers/vehicle_null_check.py --json src/\n\n# Show help\npython3 tools/analyzers/vehicle_null_check.py --help\n```\n\n### Detected Patterns\n\n| Pattern | Risk | Example |\n| --- | --- | --- |\n| `unsafe_active_vehicle_direct` | High | `activeVehicle()->method()` |\n| `unsafe_active_vehicle_use` | High | Variable used after `activeVehicle()` without check |\n\n### Example Output\n\n```text\nsrc/Example.cc:42:15: warning: unsafe_active_vehicle_direct\n  activeVehicle()->parameterManager()->getParameter(...);\n  Suggestion: Add null check before using activeVehicle():\n  Vehicle *vehicle = MultiVehicleManager::instance()->activeVehicle();\n  if (!vehicle) return;\n```\n\n### JSON Output Format\n\n```json\n[\n  {\n    \"file\": \"src/Example.cc\",\n    \"line\": 42,\n    \"column\": 15,\n    \"pattern\": \"unsafe_active_vehicle_direct\",\n    \"code\": \"activeVehicle()->parameterManager()->...\",\n    \"suggestion\": \"Add null check before using activeVehicle():...\"\n  }\n]\n```\n\n### Pre-commit Integration\n\nAlready configured in `.pre-commit-config.yaml`. Runs automatically on C++ files.\n\n```bash\n# Run manually on all files\nprek run vehicle-null-check --all-files\n```\n\n### Suppressing False Positives\n\nThe analyzer looks back 10 lines for null checks. If you have a valid null check that isn't detected, restructure your code to have the check closer to the usage, or add a comment explaining why it's safe.\n\n```cpp\n// Safe: null check is visible to analyzer\nVehicle *vehicle = MultiVehicleManager::instance()->activeVehicle();\nif (!vehicle) {\n    return;\n}\nvehicle->doSomething();  // OK - analyzer sees the check above\n```\n\n## QT_TRANSLATE_NOOP Check\n\n`qt_translate_noop_check.py` reports `QT_TRANSLATE_NOOP`/`QT_TR_NOOP` results that are returned\nor used as runtime strings (for example in `.arg()` or `QString(...)`). These macros only mark\nstrings for extraction; translate at the call site with `QCoreApplication::translate()` or `tr()`.\nIt runs as the `qt-translate-noop-check` pre-commit hook and, like the null-check analyzer, takes\nfiles or directories and `--json`.\n\n## qmlformat Check\n\n`qmlformat.py` runs Qt's `qmlformat` (from `PATH`, like qmllint) on each file in stdout mode with the\nnearest `.qmlformat.ini` and prints a unified diff where the output differs; it never rewrites files.\nIt backs `analyze.py --tool qmlformat` and the manual-stage `qmlformat` hook\n(`prek run qmlformat --hook-stage manual --files <file.qml>`). Apply a result with `qmlformat -i`.\n\n## Shared Utilities\n\n`base.py` defines `AnalysisResult` and `AnalyzerBase` for `analyze.py` backends, and\n`run_line_check_cli()`, the shared command line (paths and `--json`) of the two line-based checks.\nFile discovery uses `file_traversal.py` from [`tools/common/`](../common/README.md).\n\n## Adding New Analyzers\n\n1. Create a new Python script in `tools/analyzers/` and bootstrap imports as\n   `vehicle_null_check.py` does (see [Bootstrapping Imports](../common/README.md#bootstrapping-imports))\n2. Use `find_cpp_files()` from `common.file_traversal`\n3. For an `analyze.py --tool`, subclass `analyzers.base.AnalyzerBase` and add one entry to\n   `ANALYZERS` in `tools/analyze.py`\n4. For a line-based check, fill `AnalysisResult.findings` and define\n   `main(argv)` as `run_line_check_cli(YourAnalyzer, argv)`\n5. Add tests under `tools/tests/`, and add to `.pre-commit-config.yaml` if appropriate, running it\n   through `uv run --frozen --project tools --group scripts python` like the other hooks\n","tools/ci/README.md":"# CI Scripts\n\n`tools/ci/` contains the Python entrypoints that GitHub Actions workflows and composite actions run\nby file path (`python3 \"${GITHUB_WORKSPACE}/tools/ci/<package>/<script>.py\"`). Each subpackage\ngroups one CI concern; put reusable helpers with multiple consumers in\n[`tools/common/`](../common/README.md) or [`tools/qgc_tools/`](../qgc_tools/README.md).\n\n## Packages\n\n| Package | Scope |\n| --- | --- |\n| `planning/` | Change detection and build-matrix planners |\n| `cache/` | Compiler, moc, CPM, and GitHub Actions cache planning and maintenance |\n| `runner/` | Runner host preparation and managed runner images |\n| `compile/` | CMake and Docker build drivers with timing and compiler-cache reporting |\n| `packaging/` | Locate, validate, and boot-test packaged build outputs |\n| `android/` | Android SDK setup, build retry, lint reporting, and emulator smoke tests |\n| `ios/` | iOS packaging and simulator smoke tests |\n| `publish/` | Attestation, publishing, release gating, draft release notes, and the GitHub Pages site |\n| `reporting/` | Build-results comments, job-summary Setup and test sections, size and coverage reports, and PR labels |\n| `gstreamer/` | GStreamer SDK packaging and mirroring |\n| `security/` | Dependency SBOM generation and vulnerability-scanner installation |\n\nSubpackage names avoid `build*` and `release`: `.gitignore` ignores directories with those names,\nand Ruff skips `build` directories.\n\n## Script Index\n\n| Script | Purpose |\n| --- | --- |\n| `planning/analysis_matrix.py` | Plan Code Analysis jobs (configure and analyzer arguments per tool family): PR shards and scan scope, one cache-seeding job for master pushes, none when nothing compiled changed |\n| `planning/android_matrix.py` | Library: the Android build legs (hosts, runners, ABIs) for `plan_platform.py` |\n| `planning/detect_changes.py` | Library: decide which platforms an event builds, including the release-tag gate |\n| `planning/plan_docker_builds.py` | Library: select the Docker variants an event builds, each with the image push target |\n| `planning/plan_platform.py` | `_detect-changes.yml`'s planner: change detection and the release-tag gate once, then the Android or Docker matrix |\n| `cache/build_cache_artifact.py` | Retain compiler/moc snapshots as short-lived workflow artifacts and restore them |\n| `cache/cache_backend.py` | Detect whether RunsOn's S3 proxy serves the Actions cache and export `QGC_ACTIONS_CACHE_BACKEND` for `setup-python` |\n| `cache/cache_plan.py` | Resolve the cache action's save policy, scope, keys, ccache budget, compiler/moc/CPM environment, and the sccache pilot's selection |\n| `cache/ccache_helper.py` | Install the pinned, checksum-verified ccache (and mold, plus sccache for the [sccache pilot](../../.github/ci-overview.md#sccache-pilot), on Linux) and plan `save-build-cache`: gate PR saves, pack the snapshot artifact, and choose the cache keys |\n| `cache/cpm_helper.py` | Fingerprint CPM dependencies and the GStreamer SDK config for `cache_plan.py`, and create and apply runner-image CPM seeds |\n| `cache/gh_cache_cleanup.py` | List and optionally delete GitHub Actions caches, taking the branch and mode from `cache-cleanup.yml`'s triggering event (`CLEANUP_*` variables override them) |\n| `runner/analysis_tools.py` | Install the clang-tidy wheel hash-pinned in `build-config.json`, verify `.clang-tidy`, and put LLVM, clang-tidy, and Clazy on `PATH` for `analysis.yml` |\n| `runner/apt_install_retry.py` | Install apt packages with bounded retries, RunsOn's in-region EC2 mirror, and a runner-owned download cache |\n| `runner/build_setup.py` | Validate `build-setup`'s inputs and resolve the values its steps read: Qt hosts, architectures and modules, cache target, Xcode and MSVC selections, Windows ARM64 cross builds' host Qt, Android ABIs and SDK environment, and the `build-config.json` tool versions |\n| `runner/enable_kvm.py` | Open `/dev/kvm` to every user on a nested-virtualization runner |\n| `runner/install_dependencies_helper.py` | `install-dependencies` entry points: plan the Linux apt cache key, install the apt packages or the macOS Homebrew packages and GStreamer (both without the tools CI pins elsewhere), and install Windows MSVC and NSIS (macOS and Windows in three timed attempts that kill a hung attempt's process tree) |\n| `runner/install_multipass.py` | Install Multipass and wait for daemon readiness; with `--allow-bridge-forwarding`, undo Docker's iptables `FORWARD` drop policy so guests reach the network |\n| `runner/runner_image.py` | Check `runner-images.yml`'s branch and AWS settings, then output, verify, promote, and retire QGC runner AMIs |\n| `runner/validate_runner_configs.py` | `packer-validate` and `runs-on-lint` hooks: validate the runner image Packer template and lint the RunsOn configuration with the Packer and RunsOn CLI pinned in `build-config.json` |\n| `compile/cmake_helper.py` | Run CI configure, build, and CTest steps with standard arguments and timing, tee configure and build output to logs, and write the job summary's Configure (result, duration, preset, generator, configure-check reuse) and Build (result, duration, toolchain, this build's compiler and moc cache hit rates, slowest build steps) sections; after a failure, annotate and summarize the first CMake error or the failed targets and first build errors |\n| `compile/docker_helper.py` | Run Docker builds and repository scripts in builder images, and report compiler identities, timings, and cache statistics |\n| `packaging/artifact_link.py` | Link an uploaded diagnostics artifact (upload-artifact's `artifact-url`) from the job summary |\n| `packaging/find_artifact.py` | Find exactly one build artifact by glob pattern (fails on none or several), optionally copying it |\n| `packaging/validate_native_package.py` | Validate native package identity, version, contents, and installed layout |\n| `packaging/verify_executable.py` | Boot-test a built QGroundControl executable, AppImage, or DMG; keep a failed boot's output, stacks, crash reports, and minidumps for the diagnostics artifact |\n| `android/android_boot_test.py` | Run the emulator boot smoke test |\n| `android/android_build_retry.py` | Retry an Android build after a known intermittent Qt deployment-settings JSON truncation |\n| `android/android_collect_diagnostics.py` | Collect emulator, ADB, AVD, and GStreamer diagnostics |\n| `android/android_lint.py` | Run `gradlew lintRelease` in the Gradle project androiddeployqt generated, annotate the first Error and Fatal issues from lint's XML report at their `android/` sources (else Gradle's errors), and write the job summary's Android Lint section (result, duration, error and warning counts, first errors); exits with Gradle's status |\n| `android/android_sdk_helper.py` | Install the `build-config.json` Android SDK/NDK packages with retries, configure tool paths, and create the PR debug keystore |\n| `ios/ios_boot_test.py` | Run the QGC smoke test in a disposable iOS simulator within a 25-minute budget; save each failed attempt's simulator and CoreSimulator logs and crash reports, annotate the failing phase and attempt, and write the Boot test summary |\n| `ios/ios_package.py` | Select signing profiles and validate/package iOS bundles |\n| `publish/attest_helper.py` | Prepare artifacts for attestation (checksum sidecar, CPM dependency SBOM, build manifest) and decide the AWS publish; verify or write standalone checksums |\n| `publish/aws_upload.py` | Publish artifacts to S3 `builds/<ref>/` (the runner's ref by default), plus `latest/` and one CloudFront invalidation for release tags |\n| `publish/deploy_docs.py` | Deploy generated documentation to an external Pages repository |\n| `publish/pages_history.py` | Library: the Pages dashboard's history of master push builds: fetch the deployed `data/history.json` (any failure but a 404 stops the build, and so does a 404 once a `pages.yml` run has succeeded), collect each new run's result, wall time, Build-step durations, package sizes, JUnit suites and failed tests, `src/` line coverage, and unique Code Analysis warnings, newest first until the 15-minute collection deadline, with two 120-second attempts per artifact, and merge by commit and workflow within the 1,000-commit and 365-day caps |\n| `publish/pages_render.py` | Library: the Pages site's self-contained HTML pages and inline SVG charts (no scripts or external assets), each chart followed by a table of its latest values and linking every run |\n| `publish/pages_site.py` | Build the GitHub Pages site for `pages.yml`: the CI health dashboard, `schemas/` (each `$id` must be its site URL), `downloads/` of the S3 master packages with their SHA-256 sidecars and provenance commits, and `api/` from the latest master Doxygen artifact or a link to api.qgroundcontrol.com (also when its download stalls); `--new-history` (`PAGES_NEW_HISTORY`) starts a new history when the deployed one is gone; checks its pages' internal links and writes the job summary's Pages site section |\n| `publish/release_branch_check.py` | Stop `release.yml` before it drafts a release for a tag that no `origin/Stable*` branch contains |\n| `publish/release_notes.py` | Draft a tag's release notes with git-cliff (`tools/configs/cliff.toml`) and create its draft GitHub Release unless one exists |\n| `reporting/collect_build_status.py` | Collect platform, pre-commit, Code Analysis, and every other workflow's status for a head SHA (with `--status-out`, each run's state, attempt, duration and total wall time, and failed-step links and lost runners from its jobs) and save the latest runs for `download_artifacts.py` |\n| `reporting/download_artifacts.py` | Download prefix-selected artifacts from matching completed workflow runs into one directory each, with a manifest naming each artifact's workflow and whether it downloaded, and record each workflow's latest run and its distributable artifacts (size, id, expiry) |\n| `reporting/generate_build_results_comment.py` | Render the consolidated PR build-results comment: verdict, per-workflow table, other checks, the PR base's builds, failures, and collapsed tests, coverage, artifact downloads, pre-commit, and Code Analysis details, compared with the base's own builds |\n| `reporting/gh_pr_size_label.py` | Apply a pull request's `size/*` label from its changed lines |\n| `reporting/junit_report.py` | Summarize JUnit results (counts, failures, slowest tests) in the job summary, annotate failed tests and, optionally, slow tests (parsed with `common/junit.py`, like the build-results comment and the Pages dashboard) |\n| `reporting/report_context.py` | Resolve a pull_request build's PR and base commit, and with `--comment` recheck that the build is current and create or update the PR's build-results comment |\n| `reporting/setup_summary.py` | Write the job summary's Setup section for `build-setup`, also after a failed setup step, which it names: runner, image, RunsOn instance, CPUs and RAM, Qt (version, installer, source), CMake, compiler, Xcode, JDK, Android NDK, Python groups, and each build cache's restore result |\n| `reporting/size_analysis.py` | Analyze and report binary-size changes |\n| `reporting/verify_coverage_thresholds.py` | Enforce line and branch coverage thresholds (read from `CMakeCache.txt` with `--build-dir`) |\n| `reporting/workflow_runs.py` | Library: read the triggering `workflow_run` payload and the configured platform workflows, which default the reporting scripts' run-query arguments; validate, select, and group workflow runs; load run snapshots |\n| `gstreamer/gstreamer_archive.py` | Package GStreamer builds and optionally upload them to S3 |\n| `gstreamer/mirror_gstreamer.py` | Verify and mirror upstream GStreamer release artifacts to S3 (`mirror-gstreamer.yml` sets its options through `MIRROR_*` variables) |\n| `security/generate_cpm_sbom.py` | Generate a CycloneDX or SPDX SBOM from CPM package metadata and the bundled GStreamer |\n| `security/install_grype.py` | Install checksum-verified Grype release assets into the scan action tool cache |\n\nThe script index and one-to-one production test coverage are checked by\n[`tools/tests/ci/contracts/test_scripts_docs.py`](../tests/ci/contracts/test_scripts_docs.py).\n\n## Shared Code and Bootstrapping\n\nEntrypoints bootstrap like `tools/setup/` scripts: insert the `tools/` directory into `sys.path`\nwith one bare statement, import `_bootstrap` for the CI debug hooks, then import from the module\nthat owns the symbol ([details](../common/README.md#bootstrapping-imports)). Scripts import each\nother as package modules:\n\n```python\nsys.path.insert(0, str(Path(__file__).resolve().parents[2]))\n\nimport _bootstrap as _bootstrap\nfrom common.gh_actions import write_github_output\nfrom qgc_tools.release_tag_check import check_release_tag\n\nfrom ci.planning.detect_changes import detect_builds\n```\n\n`ci` is the top import-linter layer: it may import the other `tools/` packages, and none of them may\nimport it. Its subpackages are independent of each other (only `publish/attest_helper.py` reuses\n`security/generate_cpm_sbom.py`); move code that several of them need into `common` or `qgc_tools`.\nKeep package `__init__.py` files import-free. Sparse-checkout jobs must include the\nentrypoint, `tools/_bootstrap.py`, imported `ci` modules with their package `__init__.py` files, and\ntransitive `common` and `qgc_tools` modules; `test_bootstrap_sparse_checkout.py` verifies that\nclosure.\n\n`cache/cache_backend.py`, `runner/apt_install_retry.py`, `runner/enable_kvm.py`,\n`runner/install_multipass.py`, `runner/runner_image.py`, `packaging/validate_native_package.py`,\nand `ios/ios_boot_test.py` are standalone: they import only the standard library and skip the\nbootstrap. `runner-images.yml` checks out `runner_image.py` by itself, so it must stay that way.\n\nScripts that run before `setup-python` use the runner's system interpreter and must import only the\nstandard library and stdlib-only `tools/` modules. `PRE_SETUP_PYTHON_SCRIPTS` in\n[`tools/tests/test_stdlib_only.py`](../tests/test_stdlib_only.py) lists them; the test imports each\none under `python -I -S` and fails when a job without `setup-python` runs a script missing from the\nlist. `setup-python` puts `tools/.venv` first on `PATH`; on Windows `install_python.py` also copies\nthe venv's `python.exe` to `python3.exe`, so later `python3` steps run in the venv on every OS.\nAs a fallback for a runner whose `PATH` still prefers its own interpreter, the test also holds every\nscript that a composite action runs as `python3` to the same rule; `_VENV_ACTION_SCRIPTS` exempts\nthe few that need third-party packages.\n\n## Tests\n\nAdd `tools/tests/ci/test_<script>.py` with every new production script; every test there must match\na script. Entry scripts define `main(argv: list[str] | None = None) -> int`\n(`tools/tests/test_bootstrap.py` checks the signature), so tests call `main([...])` instead of\npatching `sys.argv`. Tests of workflows, composite actions, presets, and deploy files go in\n`tools/tests/ci/contracts/`. Load repository YAML through `tools/tests/_helpers.py`\n(`load_workflow()`, `load_action()`, `load_yaml()`, `get_steps()`, `get_step()`): it parses each\nfile once with libyaml, keys workflow triggers under `\"on\"`, and returns a private copy;\n`workflow_names()` and `action_names()` enumerate them. Build temporary git repositories with the\n`git_repo` fixture, `_helpers.git()` and `commit_files()`, which ignore user and system git\nconfiguration. Point `GITHUB_OUTPUT`, `GITHUB_ENV`, `GITHUB_PATH` and `GITHUB_STEP_SUMMARY` at\nempty temporary files with the `gh_output`, `gh_env`, `gh_path` and `gh_summary` fixtures, and\nparse what a script wrote with `read_github_file()`, which also reads `name<<DELIMITER` blocks.\n`workflow_run_event(**fields)` points `GITHUB_EVENT_PATH` at a `workflow_run` payload.\nImport scripts outside the `tools/` packages with `load_script()`. A test reads the repository files\nit checks directly, without skipping when they are missing: a file absent from a CI sparse checkout\nfails the test. Mark the tests of each script that `windows.yml` runs on Windows, and of other\nWindows-specific code paths, with `pytestmark = pytest.mark.windows`; the Windows CI job runs\n`pytest -n 2 --durations=15 -m windows tools/tests` (two xdist workers are a trial; revert to\nserial if CI shows it slower).\nRun the same suite as CI (`pytest -q -n auto tools/tests` in the locked `tools/.venv`):\n\n```bash\njust test-python\n```\n\nTests run in parallel (pytest-xdist) and in random order (pytest-randomly, which also reseeds\n`random`), so a test must not depend on another test's side effects. The run ends with\n`Reproduce the test order with --randomly-seed=N`; replay that order serially with\n`just test-python -n 0 --randomly-seed=N`, or run in file order with `-p no:randomly`.\n`tools/tests/conftest.py` restores `os.environ` after each test, and for the whole session removes\nthe runner's context (`CI`, `GITHUB_*`, `RUNNER_*`, `ACTIONS_*`, `RUNS_ON_*`, `QGC_CI_DEBUG`,\n`QGC_ACTIONS_CACHE_BACKEND`), so tests behave the same in CI as locally and never write to the\njob's real output or summary files; a test that needs one of these variables sets it.\npytest-timeout fails any test that runs longer than 60 s. The `test` group also provides:\n\n- pytest-subprocess's `fp` fixture for sequences of faked commands (`fp.register()`); prefer it\n  over patching `subprocess.run` when the code under test runs real commands.\n- Hypothesis for property tests (`@given`); `conftest.py` disables its per-example deadline.\n  Hypothesis keeps failing examples in `.cache/hypothesis/` (set in `conftest.py`) and replays them\n  first on the next run.\n- syrupy's `snapshot` fixture for rendered output; snapshots live in `__snapshots__/*.ambr` next\n  to the test. After an intended change, refresh them with `--snapshot-update` and review the diff.\n","tools/common/README.md":"# QGC Shared Tooling\n\n`tools/common/` contains small, dependency-light helpers shared by QGC developer and CI scripts.\nImport from the module that defines a helper; `common/__init__.py` intentionally does not re-export\nsymbols. Explicit imports keep runtime and sparse-checkout dependencies visible.\n\n```python\nfrom common.file_traversal import find_repo_root\nfrom common.proc import run_captured\n```\n\n## Module Index\n\n| Module | Purpose |\n| ------ | ------- |\n| `analysis_diagnostics.py` | analyze.py's `*-diagnostics.json` reports: each tool's unique warnings across shards, and those on changed lines |\n| `aws.py` | Allowlisted public S3 object checks and uploads |\n| `build_config.py` | `.github/build-config.json` lookup, validation, and CI export |\n| `cmake.py` | CMake cache variable parsing |\n| `coverage_summary.py` | gcovr JSON summary line, function, and branch coverage metrics |\n| `file_traversal.py` | Repository-root discovery and filtered C++ file traversal |\n| `format.py` | Human-readable byte, size-delta, count, duration, percentage, and ratio formatting, and one-line clipped names (`clip`) |\n| `gh_actions.py` | GitHub CLI calls, read-only API retries and JSON reads, workflow artifact archive downloads, GitHub timestamps and durations, annotations (with optional `title`, `file`, `line`, and `col`), log groups, outputs, summaries, and CI detection |\n| `git.py` | Captured Git commands, default-branch discovery, pull request diff refs, diff bases, and changed line ranges |\n| `gstreamer.py` | GStreamer SDK package and mirror URLs and their pinned checksums |\n| `io.py` | JSON/TOML I/O, checksums, atomic writes, and safe archive extraction |\n| `junit.py` | CTest JUnit XML test cases (outcome, duration, failure excerpt) and suite wall time |\n| `logging.py` | Color-aware terminal logging |\n| `markdown.py` | GitHub-Flavored Markdown tables, code spans and blocks, `<details>` blocks, untrusted-text escaping, and the shared job-summary section layout (`summary_section`) |\n| `net.py` | Dependency-free atomic downloads and small fetches with optional SHA-256 verification; retries only transient failures (connection errors, timeouts, 5xx, 408, 429), so a 404 fails at once; `fetch_release_member` extracts one member from a pinned release archive |\n| `paths.py` | `TOOLS_DIR` and `REPO_ROOT`, resolved from the module location |\n| `platform.py` | OS and CPU architecture normalization |\n| `proc.py` | Captured, tee (through Bash, or a live-flushed Python pipe), and bounded-retry subprocess execution (optionally killing a timed-out attempt's whole process tree, `kill_process_tree`; the final failure logs the command and the end of its captured stderr), bytes-or-text output decoding, `require_tools` (`MissingToolError` names every tool missing from `PATH`), and deadline polling with `wait_until` |\n| `qt.py` | Qt installer-kit discovery for host tools such as `qt-cmake` and `lupdate` |\n| `tool_version.py` | External-tool and `uv.lock` version lookup |\n| `xml.py` | Safe XML parsing with entity-declaration rejection |\n\nAPI behavior and edge cases are covered by matching files under `tools/tests/`, such as\n`test_proc.py`, `test_io.py`, and `test_gh_actions.py`.\n\n## Bootstrapping Imports\n\nTop-level `tools/*.py` scripts run with `tools/` as `sys.path[0]` and import `common` directly.\nEntry scripts in subdirectories (`tools/setup/`, `tools/ci/<package>/`, ...) first insert `tools/`\nwith one bare statement, which Ruff's import-position rule (E402) accepts:\n\n```python\nsys.path.insert(0, str(Path(__file__).resolve().parents[2]))  # tools/ci/<package>/x.py\n\nimport _bootstrap as _bootstrap\nfrom common.proc import run_captured\n```\n\n`import _bootstrap as _bootstrap` installs the CI debug hooks (see\n[`tools/README.md`](../README.md#python-script-conventions)); the redundant alias marks the unused\nimport as intentional. Library modules without a `__main__` block do not touch `sys.path`.\n`tools/tests/test_bootstrap.py` enforces this shape.\n\nCI jobs that use sparse checkout must include the entrypoint, `tools/_bootstrap.py`, and transitive\n`ci`, `common`, and `qgc_tools` modules. `test_bootstrap_sparse_checkout.py` checks that closure\nautomatically.\n\n## Adding or Reusing Helpers\n\nBefore adding a helper, search this directory and its call sites. Add shared code only when it has\nmultiple consumers or centralizes a correctness boundary such as safe extraction, retries, GitHub\noutput encoding, or platform normalization.\n\nWhen a new helper is warranted:\n\n1. Put it in the narrowest existing module; create a module only for a distinct concern.\n2. Keep imports dependency-light and defer optional packages until the function that needs them.\n3. Export the supported surface through the module's `__all__` when it has one.\n4. Add focused tests under `tools/tests/`.\n5. Run `just test-python` and `just lint` (Ruff, Pyright, and import-linter run as pre-commit hooks).\n6. Update sparse-checkout lists if the automatic policy test reports a missing module.\n","tools/coding-style/README.md":"# QGroundControl Coding Style Examples\n\nReference files demonstrating QGroundControl's coding conventions. For the authoritative rules\n(naming, include order, C++20, QML, logging), see **[CODING_STYLE.md](../../CODING_STYLE.md)** — these\nfiles are worked examples of it.\n\n## Files\n\n| File | Demonstrates |\n| --- | --- |\n| `CodingStyle.h` | Header conventions: `#pragma once`, include order, forward declarations, `Q_DECLARE_LOGGING_CATEGORY`, QML macros, naming |\n| `CodingStyle.cc` | Implementation conventions: include order, `QGC_LOGGING_CATEGORY`, C++20 features, const correctness |\n| `CodingStyle.qml` | QML conventions: import/property order, naming, declarative bindings |\n\n## Related Documentation\n\n- **[CODING_STYLE.md](../../CODING_STYLE.md)** - Full coding style guide\n- **[.clang-format](../../.clang-format)** - Automatic C++ formatting rules\n- **[.clang-tidy](../../.clang-tidy)** - Static analysis configuration\n\n## Formatting & Static Analysis\n\n```bash\n./tools/analyze.py --tool clang-format --fix        # Format lines changed since the default branch\n./tools/analyze.py --tool clang-format              # Check changed lines (same rule as the pre-commit hook)\n./tools/analyze.py --tool clang-format --fix --all  # Reformat whole files (dedicated formatting change only)\n./tools/analyze.py                                   # clang-tidy on changed files\n./tools/analyze.py --all                             # clang-tidy on all files\n```\n\nOr use the `just` wrappers (`just format-fix`, `just analyze`) — see [tools/README.md](../README.md).\n","tools/configs/yamlfmt.yaml":"# yamlfmt trial: the manual-stage `yamlfmt` hook runs `yamlfmt -lint`; yamllint remains the\n# enforced YAML check. Settings follow .yamllint.yml: no document start marker (document-start is\n# disabled), two-space indented sequences, at most 200 columns, and comments padded by two spaces\n# (yamllint requires at least one).\nexclude:\n  # clang-format and clang-tidy read these as YAML, but their documented layout keeps `---`.\n  - .clang-format\n  - .clang-tidy\nformatter:\n  type: basic\n  max_line_length: 200\n  pad_line_comments: 2\n  retain_line_breaks_single: true\n  scan_folded_as_literal: true\n","tools/configs/gersemirc.yaml":"# gersemi trial: the manual-stage `gersemi` hook passes this file with --config; cmake-format and\n# cmake-lint remain the enforced CMake hooks. Settings approximate .cmake-format.yaml.\n# Files and directories defining QGC commands, so gersemi derives their keywords; gersemi resolves\n# them relative to this file. The remaining cmake/tests and cmake/GStreamer/tests files are left\n# out: their stubs redefine some of these commands.\ndefinitions:\n  - ../../cmake/CompilerWarnings.cmake\n  - ../../cmake/GStreamer/Components.cmake\n  - ../../cmake/GStreamer/Download.cmake\n  - ../../cmake/GStreamer/Install.cmake\n  - ../../cmake/GStreamer/Layout.cmake\n  - ../../cmake/GStreamer/Link.cmake\n  - ../../cmake/GStreamer/Notice.cmake\n  - ../../cmake/GStreamer/PkgConfig.cmake\n  - ../../cmake/GStreamer/Platform.cmake\n  - ../../cmake/GStreamer/PluginPolicy.cmake\n  - ../../cmake/GStreamer/Probe.cmake\n  - ../../cmake/GStreamer/SdkState.cmake\n  - ../../cmake/GStreamer/StaticInit.cmake\n  - ../../cmake/GStreamer/Targets.cmake\n  - ../../cmake/GStreamer/platform\n  - ../../cmake/GStreamer/tests/_assert.cmake\n  - ../../cmake/Helpers.cmake\n  - ../../cmake/PrintSummary.cmake\n  - ../../cmake/QGCTest.cmake\n  - ../../cmake/install\n  - ../../cmake/modules\n  - ../../cmake/platform\n  - ../../cmake/tests/CMakeLists.txt\n  - ../../cmake/tests/ExpectedFailureTest.cmake\n  - ../../src/VideoManager/VideoReceiver/GStreamer/HwBuffers/CMakeLists.txt\n  - ../../test/CMakeLists.txt\n  - ../../test/GPS/CMakeLists.txt\n  - ../../test/Portable/CMakeLists.txt\nindent: 4\nline_length: 120\n# Qt's commands (qt_add_*) and file-local test helpers are unknown; gersemi keeps their argument layout.\nwarn_about_unknown_commands: false\n","tools/configs/editorconfig-checker.json":"{\n  \"$schema\": \"https://raw.githubusercontent.com/editorconfig-checker/editorconfig-checker/v4.0.2/.editorconfig-checker.schema.json\",\n  \"Exclude\": [\n    \"\\\\.(keystore|lz4|mesh)$\",\n    \"\\\\.ambr$\",\n    \"^test/GPS/Core/Protocols/(corpus|fixtures)/\",\n    \"^translations/\",\n    \"^android/res/values/strings\\\\.xml$\",\n    \"^android/src/org/mavlink/qgroundcontrol/QGCActivity\\\\.java$\",\n    \"^src/AutoPilotPlugins/PX4/(ActuatorFact\\\\.qml|AirframeComponentController\\\\.cc|FlightModesComponentSummary\\\\.qml)$\",\n    \"^src/Comms/MockLink/MockLink\\\\.General\\\\.MetaData\\\\.json$\",\n    \"^src/FirmwarePlugin/PX4/px4_custom_mode\\\\.h$\",\n    \"^src/FlyView/FlightDisplayViewUVC\\\\.qml$\",\n    \"^src/Utilities/Geo/Formats/ogckml22\\\\.xsd$\",\n    \"^src/Vehicle/Actuators/(Actuators|Mixer)\\\\.cc$\",\n    \"^src/Vehicle/FTPManager\\\\.h$\",\n    \"^src/Vehicle/VehicleSetup/(Bootloader\\\\.h|FirmwareUpgradeController\\\\.(cc|h))$\"\n  ],\n  \"Disable\": {\n    \"IndentSize\": true,\n    \"MaxLineLength\": true\n  }\n}\n","tools/README.md":"# QGroundControl Tools\n\nDevelopment tools, scripts, and configuration files for QGroundControl. This is the full\nreference for every `just` recipe and standalone script; [AGENTS.md](../AGENTS.md) has the\ncondensed day-to-day workflow.\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Directory Structure](#directory-structure)\n- [Just Command Reference](#just-command-reference)\n- [Direct Script Usage](#direct-script-usage)\n- [Development Scripts](#development-scripts)\n- [Setup Scripts](#setup-scripts)\n- [Python Environment](#python-environment)\n- [Python Script Conventions](#python-script-conventions)\n- [Static Analyzers](#static-analyzers)\n- [Debugging Tools](#debugging-tools)\n- [Other Tool Areas](#other-tool-areas)\n- [Code Quality Tools](#code-quality-tools)\n- [VS Code Integration](#vs-code-integration)\n- [Centralized Configuration](#centralized-configuration)\n\n## Quick Start\n\nInstall uv, then the locked `dev` Python profile into `tools/.venv`; the profile includes `just`:\n\n```bash\n# Linux/macOS; on Windows install uv from https://docs.astral.sh/uv/getting-started/installation/\npython3 tools/setup/install_uv.py\nexport PATH=\"$HOME/.local/bin:$PATH\"\n\npython3 tools/setup/install_python.py dev   # Windows: python tools/setup/install_python.py dev\n. tools/.venv/bin/activate                  # PowerShell: . tools/.venv/Scripts/Activate.ps1\n```\n\nA separately installed `just` (`brew install just`, `cargo install just`) must be 1.48 or newer\nbecause the justfile uses `set lazy`; Ubuntu's `apt install just` ships 1.21, which is too old.\n\nEveryday loop:\n\n```bash\njust             # List all recipes\njust setup       # First-time: deps + configure + build\njust build       # Incremental build\njust check       # lint + Python tooling tests + application tests — run before declaring done\n```\n\n## Directory Structure\n\n```text\ntools/\n├── _bootstrap.py            # Import bootstrap for entry scripts (tools/ on sys.path, CI debug hooks)\n├── analyze.py               # Static analysis and formatting (clang-format, clang-tidy, clazy, qmllint, qmlformat)\n├── build_profile.py         # Summarize Ninja and Clang time-trace build hotspots\n├── check_deps.py            # Check for outdated dependencies\n├── clean.py                 # Clean build artifacts and caches\n├── configure.py             # CMake configuration wrapper\n├── coverage_report.py       # Code coverage reports\n├── moccache_launcher.py     # moccache entry point: the CMake AUTOMOC launcher and the stats/trim CLI\n├── pre_commit.py            # Pre-commit runner with CI summaries\n├── terrain_tiles.py         # Terrarium elevation tiles from Copernicus GLO-30 (GeoMap)\n├── update_px4_metadata.py   # Refresh the bundled PX4 airframe and parameter metadata\n├── validate_cmake_presets.py # CMake presets against the locked cmake package's schema (hook)\n├── validate_configs.py      # Cross-field checks for build-config.json and link exceptions\n├── pyproject.toml, uv.lock  # Locked Python tooling groups\n├── analyzers/               # analyze.py backends and QGC-specific analyzers\n├── ci/                      # GitHub Actions entrypoints, grouped by CI concern\n├── coding-style/            # Code style examples\n├── common/                  # Shared Python utilities\n├── configs/                 # Tool configuration files\n│   ├── ccache.conf          # ccache configuration\n│   ├── cliff.toml           # git-cliff release-notes sections (just release-notes)\n│   ├── Doxyfile             # Doxygen API docs, run from the repository root\n│   ├── editorconfig-checker.json # editorconfig-checker exclusions and relaxed checks\n│   ├── gersemirc.yaml       # gersemi CMake formatter trial (manual hook)\n│   ├── lychee.toml          # Link checker settings and dated URL exceptions\n│   ├── trufflehog-exclude.txt # Paths the trufflehog pre-commit hook skips\n│   └── yamlfmt.yaml         # yamlfmt YAML formatter trial (manual hook)\n├── debuggers/               # Debugger formatters, profiling, Android memory capture, Valgrind suppressions\n├── generators/              # Code generation (MAVLink enums, config/settings QML)\n├── moccache/                # Content-addressed cache for Qt moc (AUTOMOC wrapper)\n├── qgc_tools/               # Shared policy: Python environment, configure presets, Docker variants\n├── setup/                   # Environment setup scripts\n├── simulation/              # Vehicle simulators\n├── terrain/                 # Terrain tile generation package (see terrain_tiles.py)\n├── tests/                   # pytest suite for tools/ (CI script and contract tests in tests/ci/)\n└── translations/            # Translation tools (lupdate, JSON extraction, pseudo-localization)\n```\n\n## Just Command Reference\n\nAll recipes are defined in [`../justfile`](../justfile), grouped by the sections below; run `just`\nwith no arguments for the same list. Most recipes run Python through\n`uv run --frozen --project tools`. `BUILD_TYPE` (default `Debug`), `JOBS` (default: all cores),\nand `QT_DIR` override the build recipes; `just info` prints them with the Qt, CMake, and GStreamer\nversions from `.github/build-config.json` ([Centralized Configuration](#centralized-configuration)).\n\n### Setup\n\n| Recipe            | Description                                                    |\n| ----------------- | -------------------------------------------------------------- |\n| `just deps`       | Auto-detect and install system build dependencies              |\n| `just vscode`     | Install missing VS Code workspace files from tracked templates |\n\n### Build\n\n| Recipe              | Description                                                                                      |\n| ------------------- | ------------------------------------------------------------------------------------------------ |\n| `just configure`    | Configure through the matching `default*` CMake preset (Debug by default)                        |\n| `just build`        | Build the project (uses all cores by default; override with `JOBS=N`)                            |\n| `just release`      | Configure and build in Release mode (testing disabled)                                           |\n| `just clean [ARGS]` | Clean the build directory; forwards `ARGS` to `tools/clean.py` (`--cache`, `--all`, `--dry-run`) |\n| `just rebuild`      | `clean` + `configure` + `build`                                                                  |\n| `just setup`        | Full first-time setup: `deps` + `configure` + `build`                                            |\n\n### Quality\n\n| Recipe                  | Description                                                                                           |\n| ----------------------- | ----------------------------------------------------------------------------------------------------- |\n| `just test`             | Run application tests via CTest; defaults to labels `Unit`/`Integration`, excluding `Flaky`/`Network` |\n| `just test-python`      | Run the Python tooling tests (`tools/tests`, incl. `tools/tests/ci`) in parallel, in random order     |\n| `just lint [ARGS]`      | Run non-manual hooks on all files with prek (`prek run --all-files`); `ARGS` select hooks/stages      |\n| `just lint-docs [ARGS]` | Lint English docs with only the docs hooks (every page; `--changed` for branch changes)               |\n| `just format`           | Check clang-format on lines changed since the default branch (no changes)                             |\n| `just format-fix`       | Apply clang-format to lines changed since the default branch                                          |\n| `just analyze`          | Run static analysis (`tools/analyze.py`, default tool clang-tidy)                                     |\n| `just coverage`         | Build with coverage instrumentation, run tests, generate report                                       |\n| `just check`            | `lint` + `test-python` + `test` — run before declaring a task done                                    |\n\nOverride `test` label filters via environment variables or positional args:\n\n```bash\njust test                              # LABELS=Unit|Integration EXCLUDE=Flaky|Network (defaults)\nLABELS=\"Slow\" just test                # override via env var\njust test \"Slow\" \"Network\"             # override via positional args (labels, exclude)\n```\n\n### Run & Deploy\n\n| Recipe        | Description                                                                    |\n| ------------- | ------------------------------------------------------------------------------ |\n| `just run`    | Launch the built `QGroundControl` binary                                       |\n| `just docs`   | Build the VitePress documentation site (`npm run docs:build` in `docs/`)       |\n| `just docker` | Build the `ubuntu` Docker variant (`deploy/docker/run_docker.py build ubuntu`) |\n\n### Utilities\n\n| Recipe                   | Description                                                                                           |\n| ------------------------ | ----------------------------------------------------------------------------------------------------- |\n| `just info`              | Print resolved build configuration (Qt version/dir, CMake min, GStreamer, jobs, ...)                  |\n| `just check-deps`        | Check Qt, GStreamer, Python, and build tool versions (`tools/check_deps.py`)                          |\n| `just check-gstreamer`   | Check the latest GStreamer patch common to all SDK platforms                                          |\n| `just translations`      | Update translation sources (`tools/translations/qgc_lupdate.py`)                                      |\n| `just distclean`         | Clean build, caches, generated files, and `node_modules/`                                             |\n| `just validate-configs`  | Validate build settings and link exceptions ([Centralized Configuration](#centralized-configuration)) |\n| `just release-notes TAG` | Print draft release notes for a `vX.Y.Z` tag ([Release notes](#release-notes))                        |\n| `just lint-qml-build`    | Lint all QML against the build's generated type information ([analyze.py](#analyzepy))                |\n\n## Direct Script Usage\n\nPrefer `just` recipes; call the scripts below directly for flags a recipe doesn't expose. Run them\nfrom the repository root with `tools/.venv` activated ([Quick Start](#quick-start)), or prefix the\ncommand with `uv run --frozen --project tools --group dev` instead. The `dev` profile covers every\nscript here.\n\nRepository configure/build/test/workflow presets are rooted at [`../CMakePresets.json`](../CMakePresets.json).\nUse `CMakeUserPresets.json` for machine-local paths or derived presets; it is ignored by Git and\npreserved by `just clean`. `tools/configure.py` (behind `just configure`) requires the Qt kit's\n`qt-cmake`, found through `--qt-root` (`QT_DIR` for `just`), `QT_ROOT_DIR`, or the newest standard\nQt install for the host architecture. Use `--no-qt-cmake` only when supplying an explicit target\ntoolchain, as Android CI does. `configure.py` configures into the selected preset's `binaryDir`,\nwhere `cmake --build --preset` and `ctest --preset` look for the tree; `-B` overrides it.\n\n## Development Scripts\n\n### analyze.py\n\nRun static analysis and formatting on source code. Underlies `just format`, `just format-fix`,\n`just analyze`, and `just lint-qml-build`.\n\n```bash\npython tools/analyze.py                              # Analyze changed files (default tool: clang-tidy)\npython tools/analyze.py --all                        # Analyze all files\npython tools/analyze.py --tool clang-format --fix    # Format changed lines\npython tools/analyze.py --tool clang-format --all    # Check formatting (all files)\npython tools/analyze.py src/Vehicle/                 # Analyze specific directory\npython tools/analyze.py --tool clang-tidy --tool clazy --cache-dir .cache/analysis-results  # Both, one scan\npython tools/analyze.py --tool clang-tidy --all --shard 1 --shard-count 4  # One of four disjoint partitions\npython tools/analyze.py --tool clang-tidy --profile-checks src/Vehicle/Vehicle.cc  # Slower per-check profiling\npython tools/analyze.py --tool clang-tidy --fix src/Vehicle/  # Apply clang-tidy fixes (local only)\n```\n\nPaths are relative to the repository root; absolute paths inside the checkout also work.\n\nOther `--tool` choices: `clazy`, `qmllint`, `qmlformat` (reports what `qmlformat` would change,\nnever rewrites), `vehicle-null-check`, `qt-translate-noop-check`.\nWithout paths or `--all`, files changed since the merge base with the default branch (or\n`PR_BASE_SHA`; in GitHub's merge checkout of `PR_HEAD_SHA`, its first parent) are analyzed,\nincluding uncommitted and untracked files; without a usable base every file is. For clang-format the\nselection narrows to changed lines, matching the pre-commit hook: deletion-only changes are skipped\nand new or untracked files are checked in full. `--all` or explicit paths check (or `--fix`) whole\nfiles for intentional reformatting. `--advisory` keeps warnings informational; error-level\ndiagnostics and tool or compiler invocation errors still fail.\n\n**Compiler analysis** (clang-tidy, Clazy) reads `compile_commands.json` from `--build-dir`\n(default `build`) and needs generated headers, not a full build. Configure Ninja with\n`CMAKE_EXPORT_COMPILE_COMMANDS=ON`, `CMAKE_DISABLE_PRECOMPILE_HEADERS=ON`,\n`CMAKE_AUTOGEN_ORIGIN_DEPENDS=OFF`, `CMAKE_GLOBAL_AUTOGEN_TARGET=ON`, and `QGC_UNITY_BUILD=OFF`,\nthen build only `cmake --build <dir> --target qgc-analysis-headers` (`autogen` is needed only for\nIWYU). Repeating `--tool` shares one file selection and dependency scan; `--cache-dir <dir>`\nreplays unchanged results and needs `clang-scan-deps`. `tools/setup/build_clazy.py` builds the\nClazy that CI uses. `--tool clang-tidy --fix` applies the selected translation units' fixes with\n`clang-apply-replacements` ([analyzers/README.md](analyzers/README.md#clang-tidy-fixes)); CI never\napplies fixes. The clang-tidy and Clazy pre-commit hooks run only with `--hook-stage manual`;\nfast hooks remain the required PR gate. CI sharding and result caching are described in\n[ci-overview.md](../.github/ci-overview.md); output files and profiling in\n[analyzers/README.md](analyzers/README.md).\n\n**QML:** `--tool qmllint --qml-build` checks QML against the generated `<build-dir>/qml` type\ninformation after a build. Missing imports, properties, and types are errors; the fast\nsource-only hook keeps its limited checks. `just lint-qml-build` runs it on every QML file with\n`--advisory`.\n\n### pre_commit.py\n\nRuns the [`.pre-commit-config.yaml`](../.pre-commit-config.yaml) hooks with\n[prek](https://github.com/j178/prek) and CI-friendly summaries; the pre-commit workflow calls it\nwith `--ci --changed --results-dir`. A `--changed` run whose docs lint configuration changed also\nruns the docs hooks over every English page.\n\n```bash\npython tools/pre_commit.py --changed                             # Files changed vs PR_BASE_SHA or the default branch\npython tools/pre_commit.py --changed --hook ruff --hook pyright  # Only these hooks (repeatable)\npython tools/pre_commit.py --docs --changed                      # English docs with only the docs hooks (just lint-docs)\npython tools/pre_commit.py --changed --needs-qt                  # Whether the selection has QML (qmllint needs Qt)\npython tools/pre_commit.py --install                             # Install the pre-commit and commit-msg hooks\npython tools/pre_commit.py --prepare-cache                       # Install the hook environments CI caches, prune the rest\n```\n\nThe selected hooks run in one `prek run` call; its output streams to the console and, with\n`--results-dir`, into `pre-commit-output.txt`. `--ci` skips the local-only `trufflehog` hook unless\n`SKIP` is already set. `--prepare-cache` installs every hook environment except\n`CACHE_SKIPPED_HOOKS` (TruffleHog and the Renovate validator) and then runs `prek cache gc`; the\npre-commit workflow runs it on pushes that miss the prek cache, and saves the cache only when the\ninstall succeeded.\n\nThe `precommit` group pins prek, which also accepts pre-commit's commands and options\n(`prek run <id> --hook-stage manual --files <paths>`, `SKIP=<id>`). The configuration stays\ncompatible with stock pre-commit, which the group keeps as a fallback runner for one release.\n`editorconfig-checker` enforces [`.editorconfig`](../.editorconfig) with the exclusions and relaxed\nchecks in `configs/editorconfig-checker.json`; `cmake-presets-schema` validates `CMakePresets.json` and\n`cmake/presets/*.json` offline against the schema shipped in the locked `cmake` package.\nManual-stage hooks run only on request: `clang-tidy`, `clazy`, the CI validators that download pinned\ntools (`renovate-config-validator`, `packer-validate`, `runs-on-lint`, which `pre_commit.py --ci` also\nruns on the changed files), and\nthree formatter trials that report differences without rewriting files: `gersemi` (CMake,\n`configs/gersemirc.yaml`), `yamlfmt` (`configs/yamlfmt.yaml`), and `qmlformat` (`analyzers/qmlformat.py`,\n`.qmlformat.ini`). For example, `just lint gersemi --hook-stage manual` checks every CMake file.\n\n### clean.py\n\nClean build artifacts and caches. Underlies `just clean` and `just distclean`.\n\n```bash\npython tools/clean.py              # Clean build directory\npython tools/clean.py --all        # Clean everything (build, caches, node_modules/, Python bytecode outside venvs)\npython tools/clean.py --cache      # Clean only repo-local caches (.cache, incl. ccache and the clangd index, plus a legacy .ccache)\npython tools/clean.py --dry-run    # Show what would be removed\n```\n\n### build_profile.py\n\nSummarize build-time hotspots from Ninja logs and optional Clang time traces. The parsing and report\ncode lives in `qgc_tools/build_profile.py`, which `ci/compile/cmake_helper.py` and\n`ci/compile/docker_helper.py` also use.\n\n```bash\npython tools/build_profile.py -B build                 # Report slowest Ninja edges and rebuild churn\npython tools/build_profile.py -B build --limit 25      # Show more rows per section\npython tools/build_profile.py -B build --json          # Machine-readable output\npython tools/build_profile.py -B build --json-out r.json > r.txt  # Text and JSON from one scan\npython tools/build_profile.py -B build --output-dir build/profile  # Ninja-only snapshot\npython tools/build_profile.py -B build --step-summary --extra-report \"Title=other.txt\"  # Plus a job-summary section\npython tools/build_profile.py --install-clang-build-analyzer .cache/ClangBuildAnalyzer  # Pinned binary (Linux x86_64)\n```\n\nFor per-translation-unit trace details, configure with `-DQGC_TIME_TRACE=ON`, rebuild, then rerun\nthe report — it scans the build dir for Clang `-ftime-trace` JSON and highlights the slowest events.\n\nOrdinary CI builds upload Ninja-only snapshots with separate link and generated-code rankings.\nTask totals overlap because commands run in parallel; they are not elapsed build time. The manual\nBuild Profile workflow installs the ClangBuildAnalyzer release pinned (with its SHA-256) in\n`.github/build-config.json`'s `tools.clang_build_analyzer`, and `--step-summary` writes its job\nsummary: build type, commit, and compiler, with this report and each `--extra-report` collapsed.\n\n### moccache/\n\nContent-addressed cache for Qt's moc, wired in through `AUTOMOC_EXECUTABLE` while the\n`QGC_USE_MOCCACHE` CMake option is ON (the default). The generated launcher runs\n`moccache_launcher.py`, which imports the `moccache` package so its bytecode is cached in\n`tools/moccache/__pycache__`. A moc run loads only `core.py`; `trim.py` (LRU eviction) and `stats.py`\n(hit/miss reports) are imported only for their subcommands or a due auto-trim, keeping each cache\nhit's startup short. The stats and trim commands run through the launcher too\n(`python3 tools/moccache_launcher.py --show-stats ...`).\nMisses fall through to the real moc and never fail the build. The\n[dev guide Build Caching section](../docs/en/qgc-dev-guide/getting_started/index.md#build-caching)\ncovers the cache key, environment variables, and stats commands. Tests:\n`tools/tests/test_moccache.py`.\n\n### coverage_report.py\n\nGenerate code coverage reports through the CMake coverage targets. Underlies `just coverage`.\nConfigures the `Linux-coverage` preset's `build/Linux-coverage/` directory (override the directory\nwith `-b`/`--build-dir`). Requires `gcovr` (`coverage` group, included in `dev`).\nCoverage compiles pass `-fprofile-update=atomic`, which ccache caches only from 4.10; an older\ndistro ccache (Ubuntu 24.04 ships 4.9.1) gets no cache hits in coverage builds.\nReports are Cobertura `coverage.xml` (uploaded to Codecov), gcovr's JSON `coverage-summary.json`\n(read by the step summary, `verify_coverage_thresholds.py`, and the PR build-results comment), and\nHTML `coverage.html`. Reporting fails when the build directory holds no `.gcda` counters, so tests\nthat wrote no coverage data cannot publish a zero-coverage report.\n\n```bash\npython tools/coverage_report.py              # Build with coverage, run tests like CI, generate XML, JSON + HTML\npython tools/coverage_report.py --report     # Generate report only from existing data (CI adds --step-summary)\npython tools/coverage_report.py --open       # Full run, then open the HTML report\npython tools/coverage_report.py --clean      # Delete the coverage build directory\n```\n\n**Direct CMake usage:**\n\n```bash\ncmake -B build -DQGC_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug\ncmake --build build\nctest --test-dir build\ncmake --build build --target coverage-report  # XML, JSON summary + HTML from existing data\ncmake --build build --target coverage         # Run Unit tests + generate XML, JSON summary + HTML\ncmake --build build --target coverage-clean   # Remove coverage data and reports\n```\n\n`coverage_report.py` runs CI's two test phases through the `Linux-coverage` test preset, which\nexcludes `Flaky` and `Network`: Unit tests at one process per CPU, then Integration tests at `-j2`,\nbecause instrumented integration tests time out at full parallelism. The CMake `coverage` target\nruns only the Unit tests.\n\n### check_deps.py\n\nCheck for outdated dependencies. Underlies `just check-deps`.\n\n```bash\npython tools/check_deps.py              # Check all dependencies\npython tools/check_deps.py --qt         # Check Qt version\npython tools/check_deps.py --gstreamer  # Find latest patch common to all SDK platforms\n```\n\n### API documentation (Doxygen)\n\nRun Doxygen from the repository root, as the Doxygen workflow does; HTML goes to `docs/api/html/`.\nThe workflow builds and deploys weekly; pull requests run it only when they change the Doxyfile or\nthe workflow, because warnings do not fail the build. Master runs keep the HTML artifact for eight\ndays, and the Pages workflow publishes the latest under <https://mavlink.github.io/qgroundcontrol/api/>.\n\n```bash\ndoxygen tools/configs/Doxyfile\n```\n\nRequires `doxygen`; `graphviz` enables diagrams.\n\n### Release notes\n\nRelease tags are created and pushed by hand. `just release-notes vX.Y.Z` prints the notes that\n`release.yml` drafts for that tag: git-cliff (`release` group) lists the commits since the previous\n`vX.Y.Z` tag reachable from it, grouped by type as configured in `configs/cliff.toml`, as full\nConventional Commits subjects under `## Changes in vX.Y.Z`. For a tag that does not exist yet it\nlists the unreleased commits since the last release reachable from `HEAD`.\n\n```bash\njust release-notes v5.1.5                                # Notes for an existing tag\npython tools/ci/publish/release_notes.py v5.2.0          # Same script; v5.2.0 not tagged yet\n```\n\n### PX4 metadata\n\n`px4-metadata.yml` runs `update_px4_metadata.py` daily and opens a pull request when the bundled PX4\nairframe (`AirframeFactMetaData.xml`) or parameter (`PX4ParameterFactMetaData.json`) metadata\nchanges upstream. It retries both downloads from artifacts.px4.io and replaces neither file unless\nboth are valid: well-formed airframe XML without a DTD, and xz-compressed JSON that lists parameters.\n\n```bash\npython3 tools/update_px4_metadata.py\n```\n\n## Setup Scripts\n\nScripts in `setup/` configure development environments and read versions from\n`.github/build-config.json`. They need only the standard library, so they also run with the host\n`python3` before `tools/.venv` exists. `install_dependencies` is a package\n(`tools/setup/install_dependencies/`) run through its `__main__.py`; without `--platform` it\ndetects the host. `build_gstreamer.py` is the entry for the `setup/gstreamer/` package: `config`\n(build settings, Meson options and plugins), `host` (commands, host arch and jobs), `meson` (Linux,\nmacOS, Windows), `cerbero` (Android, iOS) and `cli` (arguments, CI defaults and arch/prefix\noutputs). The Build GStreamer workflow first runs `setup/gstreamer/ci_plan.py`, which rejects an\narchitecture the dispatch's runner cannot build and outputs the runner's Qt and apt category.\n\n| Script                                    | Platform              | Description                                                                            |\n| ----------------------------------------- | --------------------- | -------------------------------------------------------------------------------------- |\n| `install_dependencies --platform debian`  | Linux (Debian/Ubuntu) | Install build dependencies via apt                                                     |\n| `install_dependencies --platform fedora`  | Linux (Fedora/RHEL)   | Install build dependencies via dnf                                                     |\n| `install_dependencies --platform arch`    | Linux (Arch)          | Install build dependencies via pacman                                                  |\n| `install_dependencies --platform macos`   | macOS                 | Install dependencies via Homebrew + GStreamer                                          |\n| `install_dependencies --platform windows` | Windows               | Install GStreamer (optional `--vulkan`, `--msvc`, `--msvc-arm64`, `--nsis`)            |\n| `install_uv.py`                           | Linux, macOS          | Install the uv release named by `required-version` when `uv` is not on `PATH`          |\n| `install_python.py`                       | All                   | Install locked Python tool groups via uv ([Python Environment](#python-environment))   |\n| `install_qt.py`                           | All                   | Install Qt with [QGC's installer or aqtinstall](#qt-installer) (CI: `plan`/`ensure`)   |\n| `setup_vscode.py`                         | All                   | Install missing VS Code workspace files from tracked templates                         |\n| `read_config.py`                          | All                   | Read `.github/build-config.json`                                                       |\n| `build_gstreamer.py`                      | All                   | Build GStreamer from source (optional)                                                 |\n| `build_android_openssl.py`                | Android               | Cross-compile OpenSSL for Android; configure builds only the build type's variant      |\n| `build_clazy.py`                          | Linux                 | Build the pinned `clazy-standalone` against the analysis LLVM, as CI does              |\n\nLinux `--category` limits the install to `core`, `qt`, `gstreamer`, `sdl`, `audio` or `misc`.\nOn Debian, `android` is `core` plus the OpenJDK for `android.java_version` in `build-config.json`;\nthe Docker Android image uses it, while CI's Android jobs install `core` and get the JDK from\n`setup-java`. Debian's `gstreamer_build` and `gstreamer_build_android`, outside the full set, hold\nwhat `build_gstreamer.py` needs for a Linux source build and an Android (Cerbero) build; the Build\nGStreamer workflow installs them.\n`--list [--platform NAME]` lists each platform's packages by category and\n`--print-packages [--category NAME]` prints one list on a single line; neither installs anything.\nLocally, the Linux installers also sync the locked `build` group (CMake, Ninja, `just`, ...) into\n`tools/.venv` and print how to activate it; CI build jobs instead use the `build.cmake_version` and\n`build.ninja_version` pins in `build-config.json`, which `build-setup` installs.\n\nOn macOS, the GStreamer runtime and development packages come from the official server, falling\nback to QGroundControl's public S3 mirror. Both sources are verified against the SHA-256 checksums\npinned in `.github/build-config.json`; installation stops if either package cannot be verified.\n\n```bash\n# Linux: Install all dependencies\npython3 tools/setup/install_dependencies --platform debian\n\n# Linux: build tools plus Gradle's JDK only, for Android builds\npython3 tools/setup/install_dependencies --platform debian --category android\n\n# macOS: Install all dependencies\npython3 tools/setup/install_dependencies --platform macos\n\n# Windows (as Admin):\npython .\\tools\\setup\\install_dependencies --platform windows\n\n# Install Qt from the configured version and modules\npython3 tools/setup/install_qt.py install --from-config --host linux --arch linux_gcc_64\n\n# Build GStreamer from source (optional — CMake auto-downloads pre-built SDKs)\npython3 tools/setup/install_dependencies --platform debian --category gstreamer_build\npython3 tools/setup/build_gstreamer.py --platform linux --prefix /opt/gstreamer\n\n# Build Clazy like the Code Analysis workflow; then put .cache/clazy/bin on PATH\npython3 tools/setup/build_clazy.py --prefix .cache/clazy\n\n# Read build config values\npython3 tools/setup/read_config.py --get qt.version\n```\n\n### Qt installer\n\n`install_qt.py` installs Qt with one of two tools, chosen by `--installer` or `QGC_QT_INSTALLER`:\n\n- `qgc`, the default: the standard-library package `setup/qt_installer/`. It reads the\n  combination's `Updates.xml` from download.qt.io, checks it and every archive against the SHA-256\n  sums that download.qt.io publishes (archives may come from any mirror), extracts the archives in parallel with\n  `cmake -E tar`, and applies aqtinstall's post-install edits. CMake comes from `PATH`, else from\n  the tools environment's `build` group. The Qt root gets `.qgc-qt-manifest.json` (archives,\n  checksums, patched files) and, for full installs, the `.qgc-modules` marker that preinstalled-SDK\n  reuse reads.\n- `aqt`: aqtinstall from the locked `qt` group, which stays as the fallback.\n\nBoth produce the same tree apart from install paths in patched text files and pkg-config `prefix=`\nlines (aqt leaves them unpatched on `linux_arm64`); `install_qt.py compare` checks this for a\ncombination. Android built on a Windows host differs on purpose: `qgc` pairs it with the\n`msvc2022_64` host Qt, while aqt names a `mingw_64` one that QGC never installs; builds pass\n`QT_HOST_PATH` either way. Like aqt, `qgc` reports but does not install addon dependencies missing from\n`--modules`, such as `qtquick3d`'s `qtquicktimeline`. The `plan` cache key's digest includes a non-aqt\ninstaller, so neither tool restores the other's tree.\n\n## Python Environment\n\n`install_python.py` installs dependency groups defined in `tools/pyproject.toml`:\n`scripts`, `precommit`, `test`, `qt`, `coverage`, `build`, `dev` (default), `lint`, `terrain`,\n`simulation`, `release`, and `all`. Groups compose smaller profiles: `dev` includes the build, Qt,\nlint, test, and release (git-cliff) tools. `build` adds CMake, Ninja, Meson, `just`, prek (with\npre-commit), and gcovr; `simulation` adds pymavlink for the `simulation/` MAVLink tools. The Pyright\nhook also syncs `terrain` so it can type-check `tools/terrain`. `--list-groups` prints each group's packages. `[tool.uv] required-version` in\n`tools/pyproject.toml` is the single uv version: CI, `install_uv.py`, and the Docker image install\nexactly that floor, and older uv releases refuse the project (run `uv self update`).\n\nAll Python dependencies are resolved in `tools/uv.lock`. Setup requires uv and uses\n`uv sync --frozen --inexact` into `tools/.venv`, so repeating it with a smaller group preserves\ninstalled tools. Use `--replace` only to deliberately replace the profile; `--check` validates the\nrunning interpreter's installed versions and Python/platform markers without installing. CMake,\nVS Code, and CI use the same environment; CMake creates it with the `scripts` group when it is\nmissing (`QGC_AUTO_PYTHON_VENV`).\n\nFor an image-owned environment, set `QGC_TOOLS_PROJECT` to the directory containing\n`pyproject.toml` and `uv.lock`, and `QGC_PYTHON_ENV` to the environment directory.\nDocker uses `/opt/qgc-tools` and `/opt/qgc-venv`. Qt installs that fall back to aqtinstall use\nthe locked `qt` group ([Qt installer](#qt-installer)).\n\n`common/` contains low-level utilities ([module index](common/README.md)); `qgc_tools/` holds\nshared policy such as the Python environment, configure-preset selection, Docker variants, and\nrelease-tag checks ([qgc_tools/README.md](qgc_tools/README.md)). Command-line entrypoints are the\ntop-level `tools/*.py` scripts and those in `setup/`, `ci/`, and `deploy/`. Import-linter enforces\nthese dependency boundaries: `ci` is the top layer, and `setup` sits beside `analyzers`,\n`generators`, `terrain`, and `translations`, which must not import each other.\n\n## Python Script Conventions\n\nNew and edited scripts under `tools/` follow one shape, built on the [`common/`](common/README.md)\nhelpers:\n\n- **Entry**: scripts in subpackages first run the bare statement\n  `sys.path.insert(0, str(Path(__file__).resolve().parents[N]))` (N levels below `tools/`);\n  top-level scripts already have `tools/` on `sys.path`. Entry scripts then\n  `import _bootstrap as _bootstrap`, which installs the CI debug hooks; the redundant alias marks\n  the import as intentional. Library modules leave `sys.path` alone\n  ([details](common/README.md#bootstrapping-imports)).\n- **Main**: define `main(argv: list[str] | None = None) -> int` and end with\n  `raise SystemExit(main())` under `if __name__ == \"__main__\":`, so tests can call `main([...])`.\n- **Output**: log locally with `common.logging` (`log_info`, `log_warn`, `log_error`); in CI,\n  report problems as `common.gh_actions` annotations (`gh_error`, `gh_warning`) and write step\n  outputs and summaries with its `write_github_output` and `write_step_summary`. Build each\n  job-summary section with `common.markdown.summary_section` (H3 title, facts table, collapsed\n  details) and format numbers with `common.format` so every job reads the same way.\n- **Subprocesses and network**: run commands through `common.proc` (`run_captured`, `run_tee`,\n  `run_with_retry`), check required executables with its `require_tools` and poll with its\n  `wait_until`, download with `common.net`, and call GitHub through `common.gh_actions.gh()`.\n- **Files**: locate the checkout with `common.paths` (`REPO_ROOT`, `TOOLS_DIR`) rather than\n  `Path(__file__).resolve().parents[N]` outside the entry statement, and read and write JSON with\n  `common.io` (`read_json`, `write_json`).\n\n## Static Analyzers\n\n`analyzers/` holds the `analyze.py` backends, whose `clang_format.py` also runs as the\nchanged-region `clang-format` pre-commit hook and `qmlformat.py` as the manual-stage `qmlformat`\ncheck, and two QGC-specific checks that also run as\npre-commit hooks: `vehicle_null_check.py` detects `activeVehicle()` results dereferenced without a\nnull check, and `qt_translate_noop_check.py` detects `QT_TRANSLATE_NOOP`/`QT_TR_NOOP` results used\nas runtime strings. See [analyzers/README.md](analyzers/README.md) for usage and output formats.\n\n## Debugging Tools\n\n### debuggers/profile.py\n\nProfile QGC for performance and memory issues.\n\n```bash\npython tools/debuggers/profile.py                # CPU profiling with perf\npython tools/debuggers/profile.py --memcheck     # Memory leak detection (valgrind)\npython tools/debuggers/profile.py --callgrind    # CPU profiling (valgrind)\npython tools/debuggers/profile.py --massif       # Heap profiling (valgrind)\npython tools/debuggers/profile.py --heaptrack    # Heap profiling (heaptrack)\npython tools/debuggers/profile.py --sanitize     # Build with QGC_ENABLE_ASAN/UBSAN in build/sanitize/, then run\npython tools/debuggers/profile.py --config Debug -- --logging:full  # Forward application arguments\n```\n\n### debuggers/android_mem_capture.py\n\nSample an Android app's memory (`dumpsys meminfo` PSS breakdown plus system `MemAvailable`) over\nadb at a fixed interval and write a CSV, for soak testing and before/after comparisons. Handles\nprocess restarts/LMK kills; press Enter during capture to drop a phase marker (POSIX terminals\nonly). Defaults to the QGC package.\n\n```bash\npython tools/debuggers/android_mem_capture.py --label geomap --interval 10   # Capture until Ctrl+C\npython tools/debuggers/android_mem_capture.py --label soak --duration 30m    # Timed capture\npython tools/debuggers/android_mem_capture.py --compare run-a.csv run-b.csv  # Summarize/compare runs\n```\n\nThe compare summary reports start/peak/end and a second-half trend (kB/min) for total PSS,\ngraphics, native heap, and system available memory.\n\n### Debugger Formatters and Suppressions\n\n- `debuggers/gdb-pretty-printers/` — GDB pretty printers for Qt 6 types; see\n  [its README](debuggers/gdb-pretty-printers/README.md) for setup.\n- `debuggers/qt6.natvis` — Visual Studio debugger visualizers for Qt 6 types. Add it to your\n  project's Natvis files, or point `visualizerFile` at it in a VS Code `cppvsdbg` launch\n  configuration.\n- `debuggers/valgrind.supp` — Valgrind suppressions used by `profile.py` and CTest memcheck.\n\n## Other Tool Areas\n\nThese areas have their own READMEs:\n\n- [ci/](ci/README.md) — the GitHub Actions entrypoints, in subpackages such as `planning/`,\n  `cache/`, `compile/`, `publish/`, and `reporting/`; workflows run them by path, and their tests\n  live in `tests/ci/` (workflow and action contracts in `tests/ci/contracts/`).\n- [simulation/](simulation/README.md) — `mock_vehicle.py`, a lightweight MAVLink vehicle on\n  UDP 14550, `run_arducopter_sitl.py`, ArduCopter SITL in Docker or from a local build on\n  `tcp://localhost:5760`, `fly_plan.py`, which flies a `.plan` in ArduPilot or PX4 SITL and\n  records the track and images, `mock_ntrip_caster.py`, an offline NTRIP caster on\n  `127.0.0.1:2101` for testing RTK corrections, and `rc_transmitter.py`, a web page of RC channel\n  sliders for PX4 SITL.\n- [translations/](translations/README.md) — `just translations` runs `qgc_lupdate.py` (lupdate,\n  the JSON string extractor, and `pseudo_loc.py`); `check_placeholders.py` guards translation\n  placeholders.\n- [terrain/](terrain/README.md) — `terrain_tiles.py` builds, validates, and uploads the\n  terrarium elevation tiles GeoMap renders; it needs the `terrain` group.\n- [generators/config_qml/](generators/config_qml/README.md) and\n  [generators/settings_qml/](generators/settings_qml/README.md) — JSON schemas for the vehicle\n  config and application settings pages CMake generates at configure time.\n\n## Code Quality Tools\n\n### ccache.conf\n\nConfiguration for [ccache](https://ccache.dev/). CMake's compiler launcher points ccache at\n`tools/configs/ccache.conf` unless `CCACHE_CONFIGPATH` is already set; see the dev guide's\n[Build Caching](../docs/en/qgc-dev-guide/getting_started/index.md#build-caching) section.\n\n```bash\n# Manual use:\nexport CCACHE_CONFIGPATH=/path/to/qgroundcontrol/tools/configs/ccache.conf\n```\n\n### coding-style/\n\nWorked examples of [CODING_STYLE.md](../CODING_STYLE.md): `CodingStyle.h`, `CodingStyle.cc`, and\n`CodingStyle.qml` ([coding-style/README.md](coding-style/README.md)).\n\n## VS Code Integration\n\n`.vscode/` tracks `extensions.json` (recommended extensions) and templates for `settings.json`\n(editor and CMake integration), `tasks.json` (build, test, format tasks), and `launch.json` (debug\nconfigurations) as `*.default.json`. `just vscode` copies missing ones without overwriting local\nfiles. Open the repository in VS Code and install the recommended extensions.\n\n## Centralized Configuration\n\nVersion numbers and build settings are centralized in `.github/build-config.json`, grouped into\n`qt`, `android`, `apple`, `build`, `gstreamer`, and `analysis` objects (schema:\n`.github/build-config.schema.json`). Scripts read it so local development and CI use the same\nversions. Read a value with its dotted path, e.g. `read_config.py --get qt.version` or\n`--get android.ndk_full_version`; exported env vars and CI outputs derive from the path\n(`android.ndk_full_version` → `ANDROID_NDK_FULL_VERSION` / `android_ndk_full_version`).\n\nRun `just validate-configs` after changing build versions, schemas, or link exceptions.\nIt validates required fields, minimum versions, SDK ordering, checksum coverage, and exception expiry.\nThe pre-commit gate triggers on schema-only edits too. External link exceptions in `configs/lychee.toml`\nare exact URLs with a review deadline. Docs builds check English internal links without\nexceptions; the all-locale build allows a finite list of existing translation links in\n`docs/.vitepress/config.mjs`. CI's documentation checks are described in\n[ci-overview.md](../.github/ci-overview.md).\n","tools/qgc_tools/README.md":"# qgc_tools Documentation\n\nPath: `tools/qgc_tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/qgc_tools/README.md)","tools/simulation/README.md":"# simulation Documentation\n\nPath: `tools/simulation/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/simulation/README.md)","tools/terrain/README.md":"# terrain Documentation\n\nPath: `tools/terrain/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/terrain/README.md)","tools/translations/README.md":"# translations Documentation\n\nPath: `tools/translations/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/translations/README.md)","tools/debuggers/gdb-pretty-printers/README.md":"# gdb-pretty-printers Documentation\n\nPath: `tools/debuggers/gdb-pretty-printers/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/debuggers/gdb-pretty-printers/README.md)","tools/generators/config_qml/README.md":"# config_qml Documentation\n\nPath: `tools/generators/config_qml/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/config_qml/README.md)","tools/generators/settings_qml/README.md":"# settings_qml Documentation\n\nPath: `tools/generators/settings_qml/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/settings_qml/README.md)","docs/en/qgc-dev-guide/tools/index.md":"# Tools Skill\n\nPath: `docs/en/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/index.md)","docs/en/qgc-dev-guide/tools/mock_link.md":"# Tools Skill\n\nPath: `docs/en/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/mock_link.md)","docs/ko/qgc-dev-guide/tools/index.md":"# Tools Skill\n\nPath: `docs/ko/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/index.md)","docs/ko/qgc-dev-guide/tools/mock_link.md":"# Tools Skill\n\nPath: `docs/ko/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/mock_link.md)","docs/tr/qgc-dev-guide/tools/index.md":"# Tools Skill\n\nPath: `docs/tr/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/index.md)","docs/tr/qgc-dev-guide/tools/mock_link.md":"# Tools Skill\n\nPath: `docs/tr/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/mock_link.md)","docs/zh/qgc-dev-guide/tools/index.md":"# Tools Skill\n\nPath: `docs/zh/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/index.md)","docs/zh/qgc-dev-guide/tools/mock_link.md":"# Tools Skill\n\nPath: `docs/zh/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/mock_link.md)"},"files":{".github/copilot-instructions.md":"# QGroundControl AI Assistant Guide\n\nThe canonical AI agent guide is [AGENTS.md](../AGENTS.md) — **read it first.**\nIt lists the critical files, code structure, build/test commands, CI layout,\nand links to every topic-specific reference (CODING_STYLE, CONTRIBUTING,\ntools/README, test/README, .github/ci-overview).\n","src/QtLocationPlugin/qgc_maps_plugin.json":"{\n    \"Keys\": [\"qgc-maps\"],\n    \"Provider\": \"QGroundControl\",\n    \"Version\": 101,\n    \"Experimental\": false,\n    \"Features\": [\n        \"OnlineMappingFeature\",\n        \"OfflineMappingFeature\"\n    ]\n}\n","AGENTS.md":"# AGENTS.md\n\nInstructions for AI coding agents (Codex, Claude Code, etc.) working on QGroundControl.\n\n## Quick References\n\n- [CODING_STYLE.md](CODING_STYLE.md) — Naming, formatting, C++20 features, QML style, logging\n- [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) — Architecture patterns (Fact System, Multi-Vehicle, FirmwarePlugin)\n- [tools/README.md](tools/README.md) — Development scripts, tooling, and every `just` recipe\n- [test/README.md](test/README.md) — Test framework, base classes, CTest labels, MultiSignalSpy, sanitizers, coverage\n- [cmake/README.md](cmake/README.md) — CMake modules, presets, and test options\n- [.github/ci-overview.md](.github/ci-overview.md) — CI workflow/action/script layout and conventions\n- [.pre-commit-config.yaml](.pre-commit-config.yaml) — Lint hooks (`just lint` runs every hook not marked `manual`)\n\n## Required Preflight\n\nBefore modifying any repository file:\n\n1. Read [CODING_STYLE.md](CODING_STYLE.md) completely.\n2. Read the task-specific references listed in this file.\n3. Do not begin editing until those reads are complete.\n4. Before the first edit, state in a progress update which instruction files were read.\n\n## Golden Rules\n\nThese are the non-negotiables. The first four are QGC's core architecture patterns. The pre-commit hook named\nin a rule fails CI on a violation. Full list with code examples:\n[.github/CONTRIBUTING.md#architecture-patterns](.github/CONTRIBUTING.md#architecture-patterns) and\n[CODING_STYLE.md#common-pitfalls](CODING_STYLE.md#common-pitfalls).\n\n- **Fact System** — ALL vehicle parameters flow through Facts; never create custom parameter storage.\n- **Multi-Vehicle** — ALWAYS null-check `activeVehicle()` / `Vehicle*` before dereferencing (`vehicle-null-check`\n  flags unchecked `activeVehicle()` results).\n- **Firmware Plugin** — use `vehicle->firmwarePlugin()` for firmware-specific behavior, not `if (px4)` branches.\n- **QML Integration** — register types with `QML_ELEMENT`/`QML_SINGLETON`/`QML_UNCREATABLE`; expose state via `Q_PROPERTY`.\n- **No `Q_ASSERT` in production code** — use defensive checks with early returns (`check-no-qassert`).\n- **No `QTest::ignoreMessage`** in tests — use `expectLogMessage`/`ignoreLogMessage` (`check-no-qtest-ignore-message`).\n- **No fixed-delay `QTest::qWait(<n>)`** — use `QTRY_*_WITH_TIMEOUT` or `QSignalSpy::wait` (`check-no-fixed-qwait`).\n- **Comments** — prefer self-documenting code; comment only non-obvious intent, constraints, or workarounds.\n\n## Critical Files (Read First!)\n\n1. `src/FactSystem/Fact.h` — Parameter system foundation\n2. `src/Vehicle/Vehicle.h` — Core vehicle model\n3. `src/FirmwarePlugin/FirmwarePlugin.h` — Firmware abstraction\n\n## Code Structure\n\nKey modules (`src/` has about 30 subdirectories):\n\n```text\nsrc/\n├── Vehicle/          # Vehicle state/comms\n├── Comms/            # Link layer (serial, UDP, TCP, Bluetooth)\n├── FactSystem/       # Parameter management\n├── FirmwarePlugin/   # PX4/ArduPilot abstraction\n├── AutoPilotPlugins/ # Vehicle setup UI\n├── MissionManager/   # Mission planning\n├── MAVLink/          # Protocol handling\n├── VideoManager/     # Video pipeline (GStreamer)\n├── FlyView/          # In-flight UI\n├── PlanView/         # Mission planning UI\n├── QmlControls/      # Reusable QML components\n└── Settings/         # Persistent settings\n```\n\n## Build & Test Commands\n\nThe `just` recipes are the canonical workflow — see [tools/README.md](tools/README.md) for the full list. They\nconfigure and build `build/` with the `default*` preset for `BUILD_TYPE` (default `Debug`, tests enabled).\n\n```bash\njust configure          # CMake configure through the default preset\njust build              # incremental build; uses all cores (override with JOBS=N)\njust test               # ctest --preset default, LABELS=\"Unit|Integration\" EXCLUDE=\"Flaky|Network\"\njust lint               # prek run --all-files (the pre-commit hooks)\njust lint-docs          # only the docs hooks, on docs/en pages (pass --changed for branch changes)\njust test-python        # pytest for tools/tests (CI script tests in tools/tests/ci)\njust check              # lint + test-python + test\njust format-fix         # apply clang-format to changed lines\njust info               # print configured versions (Qt, CMake minimum, GStreamer) and build settings\n```\n\n- **Build incrementally** — rebuild every few file edits during multi-file C++/Qt work, not just at the end; fix build errors before continuing.\n- **Tight test loops** — iterate one test with `ctest --test-dir build -R <name>` or\n  `build/Debug/QGroundControl --unittest:<name>` (Linux path); run `just test` on the final pass.\n- **Match CI** — Linux CI runs the `Unit` and then the `Integration` label as separate CTest runs, excluding\n  `Flaky|Network`, in the `Linux-coverage` and `Linux-sanitizers` (ASan + UBSan; also excludes `NoSanitizer`)\n  presets. Reproduce a sanitizer failure with `cmake --workflow --preset Linux-sanitizers` (needs `QT_ROOT_DIR`)\n  and coverage with `just coverage`. CI lints only the files changed from the PR base, like\n  `uv run --frozen --project tools --group dev python tools/pre_commit.py --changed`.\n  See [.github/ci-overview.md](.github/ci-overview.md) for the rest.\n\n## Definition of Done\n\nBefore considering a change complete:\n\n1. `just build` succeeds.\n2. `just lint` passes.\n3. Relevant tests pass: `ctest --test-dir build -R <name>` for the touched area, `just test-python` for Python\n   tooling, and `just test` on the final pass.\n4. Commit message follows Conventional Commits (below).\n\n## Commit & Review Conventions\n\nCommit messages follow **Conventional Commits** — the type decides the release-notes section\n(`tools/configs/cliff.toml` → git-cliff). Use: `feat`, `fix`, `perf`, `revert` (listed in release notes);\n`docs`, `style`, `chore`, `refactor`, `test`, `build`, `ci` (omitted unless breaking). Example: `fix(Vehicle): guard null activeVehicle in telemetry handler`.\n`prek install` adds a `commit-msg` hook that checks this format; Renovate's `deps` type, `fixup!`/`squash!`,\nmerge, and revert messages also pass.\n\nKeep pull request history intentional and easy to review. Organize related work into coherent commits and\ncombine fixups or closely related incremental changes before publication. Avoid a trail of tiny work-in-progress,\ncleanup, or review-fix commits. Each commit should represent a clear, reviewable change without mixing unrelated work.\n\nYour output will be reviewed by another AI agent before being accepted. Keep changes focused and\nminimal, use clear naming, and leave explanatory commit messages. Avoid unrelated changes,\ncommented-out code, or ambiguous TODOs.\n\n---\n\n**Key Principle**: Match the style of code you're editing. See [CODING_STYLE.md](CODING_STYLE.md) for conventions and [CODING_STYLE.md#examples](CODING_STYLE.md#examples) for canonical Vehicle/Fact/QML snippets.\n","tools/analyzers/README.md":"# QGC Static Analyzers\n\nBackends for [`tools/analyze.py`](../analyze.py) and QGC-specific static analyzers for C++ code.\nFor `analyze.py` usage and compiler-analysis prerequisites, see\n[tools/README.md](../README.md#analyzepy); for CI planning, sharding, and result caching, see\n[ci-overview.md](../../.github/ci-overview.md).\n\n## Compiler Analysis Output\n\nCompiler analysis (clang-tidy, Clazy) writes these files to the build directory:\n\n- `<tool>-progress.jsonl` records each file's duration and status as it completes, so\n  interrupted CI jobs retain partial timing evidence.\n- `<tool>-timings.json` adds selection, elapsed and accumulated worker time, shard coverage,\n  result-cache statistics, and (when profiling) per-check costs.\n- `<tool>-diagnostics.json` lists each unique diagnostic once (line, column, severity, and check,\n  by file relative to the checkout, or `build/...` for generated files), so reports can count a\n  header's warnings once across translation units and shards. `commit` names the checkout whose\n  line numbers they use (`GITHUB_SHA`, which on a pull request is GitHub's merge commit rather\n  than the PR head, else `git rev-parse HEAD`; absent when neither is known). With `PR_HEAD_SHA`\n  set (Code Analysis on a pull request), `changed` repeats those on lines the pull request changed\n  (`common.git.changed_ranges` against the PR's diff base).\n- `<tool>-raw.txt` keeps every translation unit's full output; the console shows each identical\n  diagnostic block once.\n\n`--cache-dir` (`result_cache.py`) replays results whose tool, configuration, compile command, and\npreprocessed inputs are unchanged; tool failures, timeouts, and crashes always run again.\n\nPer-check profiling adds substantial overhead and is disabled by default. Use\n`python tools/analyze.py --tool clang-tidy --profile-checks src/LogManager/LogEntryTableModel.cc`\nor the manual workflow's `profile_checks` input with a narrow path and `analyze_all: false`.\nProfiled runs print the most expensive checks and archive the original profiles under\n`clang-tidy-profiles/`. Check costs are summed across translation units and overlap with parallel\nwork. Ordinary runs retain per-file timing and progress without instrumenting individual checks.\n\n## clang-tidy Fixes\n\n`python tools/analyze.py --tool clang-tidy --fix [PATH...]` runs clang-tidy with\n`--export-fixes` for each selected translation unit, writing `clang-tidy-fixes/*.yaml` in the build\ndirectory, then merges them with `clang-apply-replacements --ignore-insert-conflict`. Headers shared\nby several translation units therefore receive each fix once, which running `clang-tidy --fix` in\nparallel cannot guarantee. The `clang-apply-replacements` beside `clang-tidy` is preferred (both ship\nin LLVM packages and the pinned `clang-tidy` PyPI wheel), then `PATH`.\n\n- Fix runs bypass `--cache-dir`: a replayed result would export no fixes.\n- Header fixes are limited to this checkout's `src/`, `test/`, and `tools/`, because CPM sources and\n  generated headers in other `src/` directories also match `.clang-tidy`'s `HeaderFilterRegex`.\n- Fixes from translation units that failed to compile are discarded, as `clang-tidy --fix` does.\n- Fixed code is not reformatted; run `just format-fix`, review with `git diff`, and rerun without\n  `--fix` for findings that have no automatic fix.\n- `analyze.py` refuses `--tool clang-tidy --fix` when `CI` or `GITHUB_ACTIONS` is set; CI only\n  reports findings.\n\n## clang-format Hook\n\n`clang_format.py` also runs as the `clang-format` pre-commit hook\n(`python3 tools/analyzers/clang_format.py [--fix] FILE...`). It checks, or with `--fix` formats, only\nthe lines that differ from the PR merge base (pre-commit's `PRE_COMMIT_FROM_REF` and\n`PRE_COMMIT_TO_REF`) or, locally, from `HEAD`. New files are checked in full and deletion-only\nchanges are skipped, as in `analyze.py --tool clang-format`.\n\n## Vehicle Null-Check Analyzer\n\nDetects unsafe patterns where `activeVehicle()` results are used without null checks.\n\n### Usage\n\n```bash\n# Analyze specific files\npython3 tools/analyzers/vehicle_null_check.py src/Vehicle/*.cc\n\n# Analyze directory recursively\npython3 tools/analyzers/vehicle_null_check.py src/\n\n# JSON output for CI/editor integration\npython3 tools/analyzers/vehicle_null_check.py --json src/\n\n# Show help\npython3 tools/analyzers/vehicle_null_check.py --help\n```\n\n### Detected Patterns\n\n| Pattern | Risk | Example |\n| --- | --- | --- |\n| `unsafe_active_vehicle_direct` | High | `activeVehicle()->method()` |\n| `unsafe_active_vehicle_use` | High | Variable used after `activeVehicle()` without check |\n\n### Example Output\n\n```text\nsrc/Example.cc:42:15: warning: unsafe_active_vehicle_direct\n  activeVehicle()->parameterManager()->getParameter(...);\n  Suggestion: Add null check before using activeVehicle():\n  Vehicle *vehicle = MultiVehicleManager::instance()->activeVehicle();\n  if (!vehicle) return;\n```\n\n### JSON Output Format\n\n```json\n[\n  {\n    \"file\": \"src/Example.cc\",\n    \"line\": 42,\n    \"column\": 15,\n    \"pattern\": \"unsafe_active_vehicle_direct\",\n    \"code\": \"activeVehicle()->parameterManager()->...\",\n    \"suggestion\": \"Add null check before using activeVehicle():...\"\n  }\n]\n```\n\n### Pre-commit Integration\n\nAlready configured in `.pre-commit-config.yaml`. Runs automatically on C++ files.\n\n```bash\n# Run manually on all files\nprek run vehicle-null-check --all-files\n```\n\n### Suppressing False Positives\n\nThe analyzer looks back 10 lines for null checks. If you have a valid null check that isn't detected, restructure your code to have the check closer to the usage, or add a comment explaining why it's safe.\n\n```cpp\n// Safe: null check is visible to analyzer\nVehicle *vehicle = MultiVehicleManager::instance()->activeVehicle();\nif (!vehicle) {\n    return;\n}\nvehicle->doSomething();  // OK - analyzer sees the check above\n```\n\n## QT_TRANSLATE_NOOP Check\n\n`qt_translate_noop_check.py` reports `QT_TRANSLATE_NOOP`/`QT_TR_NOOP` results that are returned\nor used as runtime strings (for example in `.arg()` or `QString(...)`). These macros only mark\nstrings for extraction; translate at the call site with `QCoreApplication::translate()` or `tr()`.\nIt runs as the `qt-translate-noop-check` pre-commit hook and, like the null-check analyzer, takes\nfiles or directories and `--json`.\n\n## qmlformat Check\n\n`qmlformat.py` runs Qt's `qmlformat` (from `PATH`, like qmllint) on each file in stdout mode with the\nnearest `.qmlformat.ini` and prints a unified diff where the output differs; it never rewrites files.\nIt backs `analyze.py --tool qmlformat` and the manual-stage `qmlformat` hook\n(`prek run qmlformat --hook-stage manual --files <file.qml>`). Apply a result with `qmlformat -i`.\n\n## Shared Utilities\n\n`base.py` defines `AnalysisResult` and `AnalyzerBase` for `analyze.py` backends, and\n`run_line_check_cli()`, the shared command line (paths and `--json`) of the two line-based checks.\nFile discovery uses `file_traversal.py` from [`tools/common/`](../common/README.md).\n\n## Adding New Analyzers\n\n1. Create a new Python script in `tools/analyzers/` and bootstrap imports as\n   `vehicle_null_check.py` does (see [Bootstrapping Imports](../common/README.md#bootstrapping-imports))\n2. Use `find_cpp_files()` from `common.file_traversal`\n3. For an `analyze.py --tool`, subclass `analyzers.base.AnalyzerBase` and add one entry to\n   `ANALYZERS` in `tools/analyze.py`\n4. For a line-based check, fill `AnalysisResult.findings` and define\n   `main(argv)` as `run_line_check_cli(YourAnalyzer, argv)`\n5. Add tests under `tools/tests/`, and add to `.pre-commit-config.yaml` if appropriate, running it\n   through `uv run --frozen --project tools --group scripts python` like the other hooks\n","tools/ci/README.md":"# CI Scripts\n\n`tools/ci/` contains the Python entrypoints that GitHub Actions workflows and composite actions run\nby file path (`python3 \"${GITHUB_WORKSPACE}/tools/ci/<package>/<script>.py\"`). Each subpackage\ngroups one CI concern; put reusable helpers with multiple consumers in\n[`tools/common/`](../common/README.md) or [`tools/qgc_tools/`](../qgc_tools/README.md).\n\n## Packages\n\n| Package | Scope |\n| --- | --- |\n| `planning/` | Change detection and build-matrix planners |\n| `cache/` | Compiler, moc, CPM, and GitHub Actions cache planning and maintenance |\n| `runner/` | Runner host preparation and managed runner images |\n| `compile/` | CMake and Docker build drivers with timing and compiler-cache reporting |\n| `packaging/` | Locate, validate, and boot-test packaged build outputs |\n| `android/` | Android SDK setup, build retry, lint reporting, and emulator smoke tests |\n| `ios/` | iOS packaging and simulator smoke tests |\n| `publish/` | Attestation, publishing, release gating, draft release notes, and the GitHub Pages site |\n| `reporting/` | Build-results comments, job-summary Setup and test sections, size and coverage reports, and PR labels |\n| `gstreamer/` | GStreamer SDK packaging and mirroring |\n| `security/` | Dependency SBOM generation and vulnerability-scanner installation |\n\nSubpackage names avoid `build*` and `release`: `.gitignore` ignores directories with those names,\nand Ruff skips `build` directories.\n\n## Script Index\n\n| Script | Purpose |\n| --- | --- |\n| `planning/analysis_matrix.py` | Plan Code Analysis jobs (configure and analyzer arguments per tool family): PR shards and scan scope, one cache-seeding job for master pushes, none when nothing compiled changed |\n| `planning/android_matrix.py` | Library: the Android build legs (hosts, runners, ABIs) for `plan_platform.py` |\n| `planning/detect_changes.py` | Library: decide which platforms an event builds, including the release-tag gate |\n| `planning/plan_docker_builds.py` | Library: select the Docker variants an event builds, each with the image push target |\n| `planning/plan_platform.py` | `_detect-changes.yml`'s planner: change detection and the release-tag gate once, then the Android or Docker matrix |\n| `cache/build_cache_artifact.py` | Retain compiler/moc snapshots as short-lived workflow artifacts and restore them |\n| `cache/cache_backend.py` | Detect whether RunsOn's S3 proxy serves the Actions cache and export `QGC_ACTIONS_CACHE_BACKEND` for `setup-python` |\n| `cache/cache_plan.py` | Resolve the cache action's save policy, scope, keys, ccache budget, compiler/moc/CPM environment, and the sccache pilot's selection |\n| `cache/ccache_helper.py` | Install the pinned, checksum-verified ccache (and mold, plus sccache for the [sccache pilot](../../.github/ci-overview.md#sccache-pilot), on Linux) and plan `save-build-cache`: gate PR saves, pack the snapshot artifact, and choose the cache keys |\n| `cache/cpm_helper.py` | Fingerprint CPM dependencies and the GStreamer SDK config for `cache_plan.py`, and create and apply runner-image CPM seeds |\n| `cache/gh_cache_cleanup.py` | List and optionally delete GitHub Actions caches, taking the branch and mode from `cache-cleanup.yml`'s triggering event (`CLEANUP_*` variables override them) |\n| `runner/analysis_tools.py` | Install the clang-tidy wheel hash-pinned in `build-config.json`, verify `.clang-tidy`, and put LLVM, clang-tidy, and Clazy on `PATH` for `analysis.yml` |\n| `runner/apt_install_retry.py` | Install apt packages with bounded retries, RunsOn's in-region EC2 mirror, and a runner-owned download cache |\n| `runner/build_setup.py` | Validate `build-setup`'s inputs and resolve the values its steps read: Qt hosts, architectures and modules, cache target, Xcode and MSVC selections, Windows ARM64 cross builds' host Qt, Android ABIs and SDK environment, and the `build-config.json` tool versions |\n| `runner/enable_kvm.py` | Open `/dev/kvm` to every user on a nested-virtualization runner |\n| `runner/install_dependencies_helper.py` | `install-dependencies` entry points: plan the Linux apt cache key, install the apt packages or the macOS Homebrew packages and GStreamer (both without the tools CI pins elsewhere), and install Windows MSVC and NSIS (macOS and Windows in three timed attempts that kill a hung attempt's process tree) |\n| `runner/install_multipass.py` | Install Multipass and wait for daemon readiness; with `--allow-bridge-forwarding`, undo Docker's iptables `FORWARD` drop policy so guests reach the network |\n| `runner/runner_image.py` | Check `runner-images.yml`'s branch and AWS settings, then output, verify, promote, and retire QGC runner AMIs |\n| `runner/validate_runner_configs.py` | `packer-validate` and `runs-on-lint` hooks: validate the runner image Packer template and lint the RunsOn configuration with the Packer and RunsOn CLI pinned in `build-config.json` |\n| `compile/cmake_helper.py` | Run CI configure, build, and CTest steps with standard arguments and timing, tee configure and build output to logs, and write the job summary's Configure (result, duration, preset, generator, configure-check reuse) and Build (result, duration, toolchain, this build's compiler and moc cache hit rates, slowest build steps) sections; after a failure, annotate and summarize the first CMake error or the failed targets and first build errors |\n| `compile/docker_helper.py` | Run Docker builds and repository scripts in builder images, and report compiler identities, timings, and cache statistics |\n| `packaging/artifact_link.py` | Link an uploaded diagnostics artifact (upload-artifact's `artifact-url`) from the job summary |\n| `packaging/find_artifact.py` | Find exactly one build artifact by glob pattern (fails on none or several), optionally copying it |\n| `packaging/validate_native_package.py` | Validate native package identity, version, contents, and installed layout |\n| `packaging/verify_executable.py` | Boot-test a built QGroundControl executable, AppImage, or DMG; keep a failed boot's output, stacks, crash reports, and minidumps for the diagnostics artifact |\n| `android/android_boot_test.py` | Run the emulator boot smoke test |\n| `android/android_build_retry.py` | Retry an Android build after a known intermittent Qt deployment-settings JSON truncation |\n| `android/android_collect_diagnostics.py` | Collect emulator, ADB, AVD, and GStreamer diagnostics |\n| `android/android_lint.py` | Run `gradlew lintRelease` in the Gradle project androiddeployqt generated, annotate the first Error and Fatal issues from lint's XML report at their `android/` sources (else Gradle's errors), and write the job summary's Android Lint section (result, duration, error and warning counts, first errors); exits with Gradle's status |\n| `android/android_sdk_helper.py` | Install the `build-config.json` Android SDK/NDK packages with retries, configure tool paths, and create the PR debug keystore |\n| `ios/ios_boot_test.py` | Run the QGC smoke test in a disposable iOS simulator within a 25-minute budget; save each failed attempt's simulator and CoreSimulator logs and crash reports, annotate the failing phase and attempt, and write the Boot test summary |\n| `ios/ios_package.py` | Select signing profiles and validate/package iOS bundles |\n| `publish/attest_helper.py` | Prepare artifacts for attestation (checksum sidecar, CPM dependency SBOM, build manifest) and decide the AWS publish; verify or write standalone checksums |\n| `publish/aws_upload.py` | Publish artifacts to S3 `builds/<ref>/` (the runner's ref by default), plus `latest/` and one CloudFront invalidation for release tags |\n| `publish/deploy_docs.py` | Deploy generated documentation to an external Pages repository |\n| `publish/pages_history.py` | Library: the Pages dashboard's history of master push builds: fetch the deployed `data/history.json` (any failure but a 404 stops the build, and so does a 404 once a `pages.yml` run has succeeded), collect each new run's result, wall time, Build-step durations, package sizes, JUnit suites and failed tests, `src/` line coverage, and unique Code Analysis warnings, newest first until the 15-minute collection deadline, with two 120-second attempts per artifact, and merge by commit and workflow within the 1,000-commit and 365-day caps |\n| `publish/pages_render.py` | Library: the Pages site's self-contained HTML pages and inline SVG charts (no scripts or external assets), each chart followed by a table of its latest values and linking every run |\n| `publish/pages_site.py` | Build the GitHub Pages site for `pages.yml`: the CI health dashboard, `schemas/` (each `$id` must be its site URL), `downloads/` of the S3 master packages with their SHA-256 sidecars and provenance commits, and `api/` from the latest master Doxygen artifact or a link to api.qgroundcontrol.com (also when its download stalls); `--new-history` (`PAGES_NEW_HISTORY`) starts a new history when the deployed one is gone; checks its pages' internal links and writes the job summary's Pages site section |\n| `publish/release_branch_check.py` | Stop `release.yml` before it drafts a release for a tag that no `origin/Stable*` branch contains |\n| `publish/release_notes.py` | Draft a tag's release notes with git-cliff (`tools/configs/cliff.toml`) and create its draft GitHub Release unless one exists |\n| `reporting/collect_build_status.py` | Collect platform, pre-commit, Code Analysis, and every other workflow's status for a head SHA (with `--status-out`, each run's state, attempt, duration and total wall time, and failed-step links and lost runners from its jobs) and save the latest runs for `download_artifacts.py` |\n| `reporting/download_artifacts.py` | Download prefix-selected artifacts from matching completed workflow runs into one directory each, with a manifest naming each artifact's workflow and whether it downloaded, and record each workflow's latest run and its distributable artifacts (size, id, expiry) |\n| `reporting/generate_build_results_comment.py` | Render the consolidated PR build-results comment: verdict, per-workflow table, other checks, the PR base's builds, failures, and collapsed tests, coverage, artifact downloads, pre-commit, and Code Analysis details, compared with the base's own builds |\n| `reporting/gh_pr_size_label.py` | Apply a pull request's `size/*` label from its changed lines |\n| `reporting/junit_report.py` | Summarize JUnit results (counts, failures, slowest tests) in the job summary, annotate failed tests and, optionally, slow tests (parsed with `common/junit.py`, like the build-results comment and the Pages dashboard) |\n| `reporting/report_context.py` | Resolve a pull_request build's PR and base commit, and with `--comment` recheck that the build is current and create or update the PR's build-results comment |\n| `reporting/setup_summary.py` | Write the job summary's Setup section for `build-setup`, also after a failed setup step, which it names: runner, image, RunsOn instance, CPUs and RAM, Qt (version, installer, source), CMake, compiler, Xcode, JDK, Android NDK, Python groups, and each build cache's restore result |\n| `reporting/size_analysis.py` | Analyze and report binary-size changes |\n| `reporting/verify_coverage_thresholds.py` | Enforce line and branch coverage thresholds (read from `CMakeCache.txt` with `--build-dir`) |\n| `reporting/workflow_runs.py` | Library: read the triggering `workflow_run` payload and the configured platform workflows, which default the reporting scripts' run-query arguments; validate, select, and group workflow runs; load run snapshots |\n| `gstreamer/gstreamer_archive.py` | Package GStreamer builds and optionally upload them to S3 |\n| `gstreamer/mirror_gstreamer.py` | Verify and mirror upstream GStreamer release artifacts to S3 (`mirror-gstreamer.yml` sets its options through `MIRROR_*` variables) |\n| `security/generate_cpm_sbom.py` | Generate a CycloneDX or SPDX SBOM from CPM package metadata and the bundled GStreamer |\n| `security/install_grype.py` | Install checksum-verified Grype release assets into the scan action tool cache |\n\nThe script index and one-to-one production test coverage are checked by\n[`tools/tests/ci/contracts/test_scripts_docs.py`](../tests/ci/contracts/test_scripts_docs.py).\n\n## Shared Code and Bootstrapping\n\nEntrypoints bootstrap like `tools/setup/` scripts: insert the `tools/` directory into `sys.path`\nwith one bare statement, import `_bootstrap` for the CI debug hooks, then import from the module\nthat owns the symbol ([details](../common/README.md#bootstrapping-imports)). Scripts import each\nother as package modules:\n\n```python\nsys.path.insert(0, str(Path(__file__).resolve().parents[2]))\n\nimport _bootstrap as _bootstrap\nfrom common.gh_actions import write_github_output\nfrom qgc_tools.release_tag_check import check_release_tag\n\nfrom ci.planning.detect_changes import detect_builds\n```\n\n`ci` is the top import-linter layer: it may import the other `tools/` packages, and none of them may\nimport it. Its subpackages are independent of each other (only `publish/attest_helper.py` reuses\n`security/generate_cpm_sbom.py`); move code that several of them need into `common` or `qgc_tools`.\nKeep package `__init__.py` files import-free. Sparse-checkout jobs must include the\nentrypoint, `tools/_bootstrap.py`, imported `ci` modules with their package `__init__.py` files, and\ntransitive `common` and `qgc_tools` modules; `test_bootstrap_sparse_checkout.py` verifies that\nclosure.\n\n`cache/cache_backend.py`, `runner/apt_install_retry.py`, `runner/enable_kvm.py`,\n`runner/install_multipass.py`, `runner/runner_image.py`, `packaging/validate_native_package.py`,\nand `ios/ios_boot_test.py` are standalone: they import only the standard library and skip the\nbootstrap. `runner-images.yml` checks out `runner_image.py` by itself, so it must stay that way.\n\nScripts that run before `setup-python` use the runner's system interpreter and must import only the\nstandard library and stdlib-only `tools/` modules. `PRE_SETUP_PYTHON_SCRIPTS` in\n[`tools/tests/test_stdlib_only.py`](../tests/test_stdlib_only.py) lists them; the test imports each\none under `python -I -S` and fails when a job without `setup-python` runs a script missing from the\nlist. `setup-python` puts `tools/.venv` first on `PATH`; on Windows `install_python.py` also copies\nthe venv's `python.exe` to `python3.exe`, so later `python3` steps run in the venv on every OS.\nAs a fallback for a runner whose `PATH` still prefers its own interpreter, the test also holds every\nscript that a composite action runs as `python3` to the same rule; `_VENV_ACTION_SCRIPTS` exempts\nthe few that need third-party packages.\n\n## Tests\n\nAdd `tools/tests/ci/test_<script>.py` with every new production script; every test there must match\na script. Entry scripts define `main(argv: list[str] | None = None) -> int`\n(`tools/tests/test_bootstrap.py` checks the signature), so tests call `main([...])` instead of\npatching `sys.argv`. Tests of workflows, composite actions, presets, and deploy files go in\n`tools/tests/ci/contracts/`. Load repository YAML through `tools/tests/_helpers.py`\n(`load_workflow()`, `load_action()`, `load_yaml()`, `get_steps()`, `get_step()`): it parses each\nfile once with libyaml, keys workflow triggers under `\"on\"`, and returns a private copy;\n`workflow_names()` and `action_names()` enumerate them. Build temporary git repositories with the\n`git_repo` fixture, `_helpers.git()` and `commit_files()`, which ignore user and system git\nconfiguration. Point `GITHUB_OUTPUT`, `GITHUB_ENV`, `GITHUB_PATH` and `GITHUB_STEP_SUMMARY` at\nempty temporary files with the `gh_output`, `gh_env`, `gh_path` and `gh_summary` fixtures, and\nparse what a script wrote with `read_github_file()`, which also reads `name<<DELIMITER` blocks.\n`workflow_run_event(**fields)` points `GITHUB_EVENT_PATH` at a `workflow_run` payload.\nImport scripts outside the `tools/` packages with `load_script()`. A test reads the repository files\nit checks directly, without skipping when they are missing: a file absent from a CI sparse checkout\nfails the test. Mark the tests of each script that `windows.yml` runs on Windows, and of other\nWindows-specific code paths, with `pytestmark = pytest.mark.windows`; the Windows CI job runs\n`pytest -n 2 --durations=15 -m windows tools/tests` (two xdist workers are a trial; revert to\nserial if CI shows it slower).\nRun the same suite as CI (`pytest -q -n auto tools/tests` in the locked `tools/.venv`):\n\n```bash\njust test-python\n```\n\nTests run in parallel (pytest-xdist) and in random order (pytest-randomly, which also reseeds\n`random`), so a test must not depend on another test's side effects. The run ends with\n`Reproduce the test order with --randomly-seed=N`; replay that order serially with\n`just test-python -n 0 --randomly-seed=N`, or run in file order with `-p no:randomly`.\n`tools/tests/conftest.py` restores `os.environ` after each test, and for the whole session removes\nthe runner's context (`CI`, `GITHUB_*`, `RUNNER_*`, `ACTIONS_*`, `RUNS_ON_*`, `QGC_CI_DEBUG`,\n`QGC_ACTIONS_CACHE_BACKEND`), so tests behave the same in CI as locally and never write to the\njob's real output or summary files; a test that needs one of these variables sets it.\npytest-timeout fails any test that runs longer than 60 s. The `test` group also provides:\n\n- pytest-subprocess's `fp` fixture for sequences of faked commands (`fp.register()`); prefer it\n  over patching `subprocess.run` when the code under test runs real commands.\n- Hypothesis for property tests (`@given`); `conftest.py` disables its per-example deadline.\n  Hypothesis keeps failing examples in `.cache/hypothesis/` (set in `conftest.py`) and replays them\n  first on the next run.\n- syrupy's `snapshot` fixture for rendered output; snapshots live in `__snapshots__/*.ambr` next\n  to the test. After an intended change, refresh them with `--snapshot-update` and review the diff.\n","tools/common/README.md":"# QGC Shared Tooling\n\n`tools/common/` contains small, dependency-light helpers shared by QGC developer and CI scripts.\nImport from the module that defines a helper; `common/__init__.py` intentionally does not re-export\nsymbols. Explicit imports keep runtime and sparse-checkout dependencies visible.\n\n```python\nfrom common.file_traversal import find_repo_root\nfrom common.proc import run_captured\n```\n\n## Module Index\n\n| Module | Purpose |\n| ------ | ------- |\n| `analysis_diagnostics.py` | analyze.py's `*-diagnostics.json` reports: each tool's unique warnings across shards, and those on changed lines |\n| `aws.py` | Allowlisted public S3 object checks and uploads |\n| `build_config.py` | `.github/build-config.json` lookup, validation, and CI export |\n| `cmake.py` | CMake cache variable parsing |\n| `coverage_summary.py` | gcovr JSON summary line, function, and branch coverage metrics |\n| `file_traversal.py` | Repository-root discovery and filtered C++ file traversal |\n| `format.py` | Human-readable byte, size-delta, count, duration, percentage, and ratio formatting, and one-line clipped names (`clip`) |\n| `gh_actions.py` | GitHub CLI calls, read-only API retries and JSON reads, workflow artifact archive downloads, GitHub timestamps and durations, annotations (with optional `title`, `file`, `line`, and `col`), log groups, outputs, summaries, and CI detection |\n| `git.py` | Captured Git commands, default-branch discovery, pull request diff refs, diff bases, and changed line ranges |\n| `gstreamer.py` | GStreamer SDK package and mirror URLs and their pinned checksums |\n| `io.py` | JSON/TOML I/O, checksums, atomic writes, and safe archive extraction |\n| `junit.py` | CTest JUnit XML test cases (outcome, duration, failure excerpt) and suite wall time |\n| `logging.py` | Color-aware terminal logging |\n| `markdown.py` | GitHub-Flavored Markdown tables, code spans and blocks, `<details>` blocks, untrusted-text escaping, and the shared job-summary section layout (`summary_section`) |\n| `net.py` | Dependency-free atomic downloads and small fetches with optional SHA-256 verification; retries only transient failures (connection errors, timeouts, 5xx, 408, 429), so a 404 fails at once; `fetch_release_member` extracts one member from a pinned release archive |\n| `paths.py` | `TOOLS_DIR` and `REPO_ROOT`, resolved from the module location |\n| `platform.py` | OS and CPU architecture normalization |\n| `proc.py` | Captured, tee (through Bash, or a live-flushed Python pipe), and bounded-retry subprocess execution (optionally killing a timed-out attempt's whole process tree, `kill_process_tree`; the final failure logs the command and the end of its captured stderr), bytes-or-text output decoding, `require_tools` (`MissingToolError` names every tool missing from `PATH`), and deadline polling with `wait_until` |\n| `qt.py` | Qt installer-kit discovery for host tools such as `qt-cmake` and `lupdate` |\n| `tool_version.py` | External-tool and `uv.lock` version lookup |\n| `xml.py` | Safe XML parsing with entity-declaration rejection |\n\nAPI behavior and edge cases are covered by matching files under `tools/tests/`, such as\n`test_proc.py`, `test_io.py`, and `test_gh_actions.py`.\n\n## Bootstrapping Imports\n\nTop-level `tools/*.py` scripts run with `tools/` as `sys.path[0]` and import `common` directly.\nEntry scripts in subdirectories (`tools/setup/`, `tools/ci/<package>/`, ...) first insert `tools/`\nwith one bare statement, which Ruff's import-position rule (E402) accepts:\n\n```python\nsys.path.insert(0, str(Path(__file__).resolve().parents[2]))  # tools/ci/<package>/x.py\n\nimport _bootstrap as _bootstrap\nfrom common.proc import run_captured\n```\n\n`import _bootstrap as _bootstrap` installs the CI debug hooks (see\n[`tools/README.md`](../README.md#python-script-conventions)); the redundant alias marks the unused\nimport as intentional. Library modules without a `__main__` block do not touch `sys.path`.\n`tools/tests/test_bootstrap.py` enforces this shape.\n\nCI jobs that use sparse checkout must include the entrypoint, `tools/_bootstrap.py`, and transitive\n`ci`, `common`, and `qgc_tools` modules. `test_bootstrap_sparse_checkout.py` checks that closure\nautomatically.\n\n## Adding or Reusing Helpers\n\nBefore adding a helper, search this directory and its call sites. Add shared code only when it has\nmultiple consumers or centralizes a correctness boundary such as safe extraction, retries, GitHub\noutput encoding, or platform normalization.\n\nWhen a new helper is warranted:\n\n1. Put it in the narrowest existing module; create a module only for a distinct concern.\n2. Keep imports dependency-light and defer optional packages until the function that needs them.\n3. Export the supported surface through the module's `__all__` when it has one.\n4. Add focused tests under `tools/tests/`.\n5. Run `just test-python` and `just lint` (Ruff, Pyright, and import-linter run as pre-commit hooks).\n6. Update sparse-checkout lists if the automatic policy test reports a missing module.\n","tools/coding-style/README.md":"# QGroundControl Coding Style Examples\n\nReference files demonstrating QGroundControl's coding conventions. For the authoritative rules\n(naming, include order, C++20, QML, logging), see **[CODING_STYLE.md](../../CODING_STYLE.md)** — these\nfiles are worked examples of it.\n\n## Files\n\n| File | Demonstrates |\n| --- | --- |\n| `CodingStyle.h` | Header conventions: `#pragma once`, include order, forward declarations, `Q_DECLARE_LOGGING_CATEGORY`, QML macros, naming |\n| `CodingStyle.cc` | Implementation conventions: include order, `QGC_LOGGING_CATEGORY`, C++20 features, const correctness |\n| `CodingStyle.qml` | QML conventions: import/property order, naming, declarative bindings |\n\n## Related Documentation\n\n- **[CODING_STYLE.md](../../CODING_STYLE.md)** - Full coding style guide\n- **[.clang-format](../../.clang-format)** - Automatic C++ formatting rules\n- **[.clang-tidy](../../.clang-tidy)** - Static analysis configuration\n\n## Formatting & Static Analysis\n\n```bash\n./tools/analyze.py --tool clang-format --fix        # Format lines changed since the default branch\n./tools/analyze.py --tool clang-format              # Check changed lines (same rule as the pre-commit hook)\n./tools/analyze.py --tool clang-format --fix --all  # Reformat whole files (dedicated formatting change only)\n./tools/analyze.py                                   # clang-tidy on changed files\n./tools/analyze.py --all                             # clang-tidy on all files\n```\n\nOr use the `just` wrappers (`just format-fix`, `just analyze`) — see [tools/README.md](../README.md).\n","tools/configs/yamlfmt.yaml":"# yamlfmt trial: the manual-stage `yamlfmt` hook runs `yamlfmt -lint`; yamllint remains the\n# enforced YAML check. Settings follow .yamllint.yml: no document start marker (document-start is\n# disabled), two-space indented sequences, at most 200 columns, and comments padded by two spaces\n# (yamllint requires at least one).\nexclude:\n  # clang-format and clang-tidy read these as YAML, but their documented layout keeps `---`.\n  - .clang-format\n  - .clang-tidy\nformatter:\n  type: basic\n  max_line_length: 200\n  pad_line_comments: 2\n  retain_line_breaks_single: true\n  scan_folded_as_literal: true\n","tools/configs/gersemirc.yaml":"# gersemi trial: the manual-stage `gersemi` hook passes this file with --config; cmake-format and\n# cmake-lint remain the enforced CMake hooks. Settings approximate .cmake-format.yaml.\n# Files and directories defining QGC commands, so gersemi derives their keywords; gersemi resolves\n# them relative to this file. The remaining cmake/tests and cmake/GStreamer/tests files are left\n# out: their stubs redefine some of these commands.\ndefinitions:\n  - ../../cmake/CompilerWarnings.cmake\n  - ../../cmake/GStreamer/Components.cmake\n  - ../../cmake/GStreamer/Download.cmake\n  - ../../cmake/GStreamer/Install.cmake\n  - ../../cmake/GStreamer/Layout.cmake\n  - ../../cmake/GStreamer/Link.cmake\n  - ../../cmake/GStreamer/Notice.cmake\n  - ../../cmake/GStreamer/PkgConfig.cmake\n  - ../../cmake/GStreamer/Platform.cmake\n  - ../../cmake/GStreamer/PluginPolicy.cmake\n  - ../../cmake/GStreamer/Probe.cmake\n  - ../../cmake/GStreamer/SdkState.cmake\n  - ../../cmake/GStreamer/StaticInit.cmake\n  - ../../cmake/GStreamer/Targets.cmake\n  - ../../cmake/GStreamer/platform\n  - ../../cmake/GStreamer/tests/_assert.cmake\n  - ../../cmake/Helpers.cmake\n  - ../../cmake/PrintSummary.cmake\n  - ../../cmake/QGCTest.cmake\n  - ../../cmake/install\n  - ../../cmake/modules\n  - ../../cmake/platform\n  - ../../cmake/tests/CMakeLists.txt\n  - ../../cmake/tests/ExpectedFailureTest.cmake\n  - ../../src/VideoManager/VideoReceiver/GStreamer/HwBuffers/CMakeLists.txt\n  - ../../test/CMakeLists.txt\n  - ../../test/GPS/CMakeLists.txt\n  - ../../test/Portable/CMakeLists.txt\nindent: 4\nline_length: 120\n# Qt's commands (qt_add_*) and file-local test helpers are unknown; gersemi keeps their argument layout.\nwarn_about_unknown_commands: false\n","tools/configs/editorconfig-checker.json":"{\n  \"$schema\": \"https://raw.githubusercontent.com/editorconfig-checker/editorconfig-checker/v4.0.2/.editorconfig-checker.schema.json\",\n  \"Exclude\": [\n    \"\\\\.(keystore|lz4|mesh)$\",\n    \"\\\\.ambr$\",\n    \"^test/GPS/Core/Protocols/(corpus|fixtures)/\",\n    \"^translations/\",\n    \"^android/res/values/strings\\\\.xml$\",\n    \"^android/src/org/mavlink/qgroundcontrol/QGCActivity\\\\.java$\",\n    \"^src/AutoPilotPlugins/PX4/(ActuatorFact\\\\.qml|AirframeComponentController\\\\.cc|FlightModesComponentSummary\\\\.qml)$\",\n    \"^src/Comms/MockLink/MockLink\\\\.General\\\\.MetaData\\\\.json$\",\n    \"^src/FirmwarePlugin/PX4/px4_custom_mode\\\\.h$\",\n    \"^src/FlyView/FlightDisplayViewUVC\\\\.qml$\",\n    \"^src/Utilities/Geo/Formats/ogckml22\\\\.xsd$\",\n    \"^src/Vehicle/Actuators/(Actuators|Mixer)\\\\.cc$\",\n    \"^src/Vehicle/FTPManager\\\\.h$\",\n    \"^src/Vehicle/VehicleSetup/(Bootloader\\\\.h|FirmwareUpgradeController\\\\.(cc|h))$\"\n  ],\n  \"Disable\": {\n    \"IndentSize\": true,\n    \"MaxLineLength\": true\n  }\n}\n","tools/README.md":"# QGroundControl Tools\n\nDevelopment tools, scripts, and configuration files for QGroundControl. This is the full\nreference for every `just` recipe and standalone script; [AGENTS.md](../AGENTS.md) has the\ncondensed day-to-day workflow.\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Directory Structure](#directory-structure)\n- [Just Command Reference](#just-command-reference)\n- [Direct Script Usage](#direct-script-usage)\n- [Development Scripts](#development-scripts)\n- [Setup Scripts](#setup-scripts)\n- [Python Environment](#python-environment)\n- [Python Script Conventions](#python-script-conventions)\n- [Static Analyzers](#static-analyzers)\n- [Debugging Tools](#debugging-tools)\n- [Other Tool Areas](#other-tool-areas)\n- [Code Quality Tools](#code-quality-tools)\n- [VS Code Integration](#vs-code-integration)\n- [Centralized Configuration](#centralized-configuration)\n\n## Quick Start\n\nInstall uv, then the locked `dev` Python profile into `tools/.venv`; the profile includes `just`:\n\n```bash\n# Linux/macOS; on Windows install uv from https://docs.astral.sh/uv/getting-started/installation/\npython3 tools/setup/install_uv.py\nexport PATH=\"$HOME/.local/bin:$PATH\"\n\npython3 tools/setup/install_python.py dev   # Windows: python tools/setup/install_python.py dev\n. tools/.venv/bin/activate                  # PowerShell: . tools/.venv/Scripts/Activate.ps1\n```\n\nA separately installed `just` (`brew install just`, `cargo install just`) must be 1.48 or newer\nbecause the justfile uses `set lazy`; Ubuntu's `apt install just` ships 1.21, which is too old.\n\nEveryday loop:\n\n```bash\njust             # List all recipes\njust setup       # First-time: deps + configure + build\njust build       # Incremental build\njust check       # lint + Python tooling tests + application tests — run before declaring done\n```\n\n## Directory Structure\n\n```text\ntools/\n├── _bootstrap.py            # Import bootstrap for entry scripts (tools/ on sys.path, CI debug hooks)\n├── analyze.py               # Static analysis and formatting (clang-format, clang-tidy, clazy, qmllint, qmlformat)\n├── build_profile.py         # Summarize Ninja and Clang time-trace build hotspots\n├── check_deps.py            # Check for outdated dependencies\n├── clean.py                 # Clean build artifacts and caches\n├── configure.py             # CMake configuration wrapper\n├── coverage_report.py       # Code coverage reports\n├── moccache_launcher.py     # moccache entry point: the CMake AUTOMOC launcher and the stats/trim CLI\n├── pre_commit.py            # Pre-commit runner with CI summaries\n├── terrain_tiles.py         # Terrarium elevation tiles from Copernicus GLO-30 (GeoMap)\n├── update_px4_metadata.py   # Refresh the bundled PX4 airframe and parameter metadata\n├── validate_cmake_presets.py # CMake presets against the locked cmake package's schema (hook)\n├── validate_configs.py      # Cross-field checks for build-config.json and link exceptions\n├── pyproject.toml, uv.lock  # Locked Python tooling groups\n├── analyzers/               # analyze.py backends and QGC-specific analyzers\n├── ci/                      # GitHub Actions entrypoints, grouped by CI concern\n├── coding-style/            # Code style examples\n├── common/                  # Shared Python utilities\n├── configs/                 # Tool configuration files\n│   ├── ccache.conf          # ccache configuration\n│   ├── cliff.toml           # git-cliff release-notes sections (just release-notes)\n│   ├── Doxyfile             # Doxygen API docs, run from the repository root\n│   ├── editorconfig-checker.json # editorconfig-checker exclusions and relaxed checks\n│   ├── gersemirc.yaml       # gersemi CMake formatter trial (manual hook)\n│   ├── lychee.toml          # Link checker settings and dated URL exceptions\n│   ├── trufflehog-exclude.txt # Paths the trufflehog pre-commit hook skips\n│   └── yamlfmt.yaml         # yamlfmt YAML formatter trial (manual hook)\n├── debuggers/               # Debugger formatters, profiling, Android memory capture, Valgrind suppressions\n├── generators/              # Code generation (MAVLink enums, config/settings QML)\n├── moccache/                # Content-addressed cache for Qt moc (AUTOMOC wrapper)\n├── qgc_tools/               # Shared policy: Python environment, configure presets, Docker variants\n├── setup/                   # Environment setup scripts\n├── simulation/              # Vehicle simulators\n├── terrain/                 # Terrain tile generation package (see terrain_tiles.py)\n├── tests/                   # pytest suite for tools/ (CI script and contract tests in tests/ci/)\n└── translations/            # Translation tools (lupdate, JSON extraction, pseudo-localization)\n```\n\n## Just Command Reference\n\nAll recipes are defined in [`../justfile`](../justfile), grouped by the sections below; run `just`\nwith no arguments for the same list. Most recipes run Python through\n`uv run --frozen --project tools`. `BUILD_TYPE` (default `Debug`), `JOBS` (default: all cores),\nand `QT_DIR` override the build recipes; `just info` prints them with the Qt, CMake, and GStreamer\nversions from `.github/build-config.json` ([Centralized Configuration](#centralized-configuration)).\n\n### Setup\n\n| Recipe            | Description                                                    |\n| ----------------- | -------------------------------------------------------------- |\n| `just deps`       | Auto-detect and install system build dependencies              |\n| `just vscode`     | Install missing VS Code workspace files from tracked templates |\n\n### Build\n\n| Recipe              | Description                                                                                      |\n| ------------------- | ------------------------------------------------------------------------------------------------ |\n| `just configure`    | Configure through the matching `default*` CMake preset (Debug by default)                        |\n| `just build`        | Build the project (uses all cores by default; override with `JOBS=N`)                            |\n| `just release`      | Configure and build in Release mode (testing disabled)                                           |\n| `just clean [ARGS]` | Clean the build directory; forwards `ARGS` to `tools/clean.py` (`--cache`, `--all`, `--dry-run`) |\n| `just rebuild`      | `clean` + `configure` + `build`                                                                  |\n| `just setup`        | Full first-time setup: `deps` + `configure` + `build`                                            |\n\n### Quality\n\n| Recipe                  | Description                                                                                           |\n| ----------------------- | ----------------------------------------------------------------------------------------------------- |\n| `just test`             | Run application tests via CTest; defaults to labels `Unit`/`Integration`, excluding `Flaky`/`Network` |\n| `just test-python`      | Run the Python tooling tests (`tools/tests`, incl. `tools/tests/ci`) in parallel, in random order     |\n| `just lint [ARGS]`      | Run non-manual hooks on all files with prek (`prek run --all-files`); `ARGS` select hooks/stages      |\n| `just lint-docs [ARGS]` | Lint English docs with only the docs hooks (every page; `--changed` for branch changes)               |\n| `just format`           | Check clang-format on lines changed since the default branch (no changes)                             |\n| `just format-fix`       | Apply clang-format to lines changed since the default branch                                          |\n| `just analyze`          | Run static analysis (`tools/analyze.py`, default tool clang-tidy)                                     |\n| `just coverage`         | Build with coverage instrumentation, run tests, generate report                                       |\n| `just check`            | `lint` + `test-python` + `test` — run before declaring a task done                                    |\n\nOverride `test` label filters via environment variables or positional args:\n\n```bash\njust test                              # LABELS=Unit|Integration EXCLUDE=Flaky|Network (defaults)\nLABELS=\"Slow\" just test                # override via env var\njust test \"Slow\" \"Network\"             # override via positional args (labels, exclude)\n```\n\n### Run & Deploy\n\n| Recipe        | Description                                                                    |\n| ------------- | ------------------------------------------------------------------------------ |\n| `just run`    | Launch the built `QGroundControl` binary                                       |\n| `just docs`   | Build the VitePress documentation site (`npm run docs:build` in `docs/`)       |\n| `just docker` | Build the `ubuntu` Docker variant (`deploy/docker/run_docker.py build ubuntu`) |\n\n### Utilities\n\n| Recipe                   | Description                                                                                           |\n| ------------------------ | ----------------------------------------------------------------------------------------------------- |\n| `just info`              | Print resolved build configuration (Qt version/dir, CMake min, GStreamer, jobs, ...)                  |\n| `just check-deps`        | Check Qt, GStreamer, Python, and build tool versions (`tools/check_deps.py`)                          |\n| `just check-gstreamer`   | Check the latest GStreamer patch common to all SDK platforms                                          |\n| `just translations`      | Update translation sources (`tools/translations/qgc_lupdate.py`)                                      |\n| `just distclean`         | Clean build, caches, generated files, and `node_modules/`                                             |\n| `just validate-configs`  | Validate build settings and link exceptions ([Centralized Configuration](#centralized-configuration)) |\n| `just release-notes TAG` | Print draft release notes for a `vX.Y.Z` tag ([Release notes](#release-notes))                        |\n| `just lint-qml-build`    | Lint all QML against the build's generated type information ([analyze.py](#analyzepy))                |\n\n## Direct Script Usage\n\nPrefer `just` recipes; call the scripts below directly for flags a recipe doesn't expose. Run them\nfrom the repository root with `tools/.venv` activated ([Quick Start](#quick-start)), or prefix the\ncommand with `uv run --frozen --project tools --group dev` instead. The `dev` profile covers every\nscript here.\n\nRepository configure/build/test/workflow presets are rooted at [`../CMakePresets.json`](../CMakePresets.json).\nUse `CMakeUserPresets.json` for machine-local paths or derived presets; it is ignored by Git and\npreserved by `just clean`. `tools/configure.py` (behind `just configure`) requires the Qt kit's\n`qt-cmake`, found through `--qt-root` (`QT_DIR` for `just`), `QT_ROOT_DIR`, or the newest standard\nQt install for the host architecture. Use `--no-qt-cmake` only when supplying an explicit target\ntoolchain, as Android CI does. `configure.py` configures into the selected preset's `binaryDir`,\nwhere `cmake --build --preset` and `ctest --preset` look for the tree; `-B` overrides it.\n\n## Development Scripts\n\n### analyze.py\n\nRun static analysis and formatting on source code. Underlies `just format`, `just format-fix`,\n`just analyze`, and `just lint-qml-build`.\n\n```bash\npython tools/analyze.py                              # Analyze changed files (default tool: clang-tidy)\npython tools/analyze.py --all                        # Analyze all files\npython tools/analyze.py --tool clang-format --fix    # Format changed lines\npython tools/analyze.py --tool clang-format --all    # Check formatting (all files)\npython tools/analyze.py src/Vehicle/                 # Analyze specific directory\npython tools/analyze.py --tool clang-tidy --tool clazy --cache-dir .cache/analysis-results  # Both, one scan\npython tools/analyze.py --tool clang-tidy --all --shard 1 --shard-count 4  # One of four disjoint partitions\npython tools/analyze.py --tool clang-tidy --profile-checks src/Vehicle/Vehicle.cc  # Slower per-check profiling\npython tools/analyze.py --tool clang-tidy --fix src/Vehicle/  # Apply clang-tidy fixes (local only)\n```\n\nPaths are relative to the repository root; absolute paths inside the checkout also work.\n\nOther `--tool` choices: `clazy`, `qmllint`, `qmlformat` (reports what `qmlformat` would change,\nnever rewrites), `vehicle-null-check`, `qt-translate-noop-check`.\nWithout paths or `--all`, files changed since the merge base with the default branch (or\n`PR_BASE_SHA`; in GitHub's merge checkout of `PR_HEAD_SHA`, its first parent) are analyzed,\nincluding uncommitted and untracked files; without a usable base every file is. For clang-format the\nselection narrows to changed lines, matching the pre-commit hook: deletion-only changes are skipped\nand new or untracked files are checked in full. `--all` or explicit paths check (or `--fix`) whole\nfiles for intentional reformatting. `--advisory` keeps warnings informational; error-level\ndiagnostics and tool or compiler invocation errors still fail.\n\n**Compiler analysis** (clang-tidy, Clazy) reads `compile_commands.json` from `--build-dir`\n(default `build`) and needs generated headers, not a full build. Configure Ninja with\n`CMAKE_EXPORT_COMPILE_COMMANDS=ON`, `CMAKE_DISABLE_PRECOMPILE_HEADERS=ON`,\n`CMAKE_AUTOGEN_ORIGIN_DEPENDS=OFF`, `CMAKE_GLOBAL_AUTOGEN_TARGET=ON`, and `QGC_UNITY_BUILD=OFF`,\nthen build only `cmake --build <dir> --target qgc-analysis-headers` (`autogen` is needed only for\nIWYU). Repeating `--tool` shares one file selection and dependency scan; `--cache-dir <dir>`\nreplays unchanged results and needs `clang-scan-deps`. `tools/setup/build_clazy.py` builds the\nClazy that CI uses. `--tool clang-tidy --fix` applies the selected translation units' fixes with\n`clang-apply-replacements` ([analyzers/README.md](analyzers/README.md#clang-tidy-fixes)); CI never\napplies fixes. The clang-tidy and Clazy pre-commit hooks run only with `--hook-stage manual`;\nfast hooks remain the required PR gate. CI sharding and result caching are described in\n[ci-overview.md](../.github/ci-overview.md); output files and profiling in\n[analyzers/README.md](analyzers/README.md).\n\n**QML:** `--tool qmllint --qml-build` checks QML against the generated `<build-dir>/qml` type\ninformation after a build. Missing imports, properties, and types are errors; the fast\nsource-only hook keeps its limited checks. `just lint-qml-build` runs it on every QML file with\n`--advisory`.\n\n### pre_commit.py\n\nRuns the [`.pre-commit-config.yaml`](../.pre-commit-config.yaml) hooks with\n[prek](https://github.com/j178/prek) and CI-friendly summaries; the pre-commit workflow calls it\nwith `--ci --changed --results-dir`. A `--changed` run whose docs lint configuration changed also\nruns the docs hooks over every English page.\n\n```bash\npython tools/pre_commit.py --changed                             # Files changed vs PR_BASE_SHA or the default branch\npython tools/pre_commit.py --changed --hook ruff --hook pyright  # Only these hooks (repeatable)\npython tools/pre_commit.py --docs --changed                      # English docs with only the docs hooks (just lint-docs)\npython tools/pre_commit.py --changed --needs-qt                  # Whether the selection has QML (qmllint needs Qt)\npython tools/pre_commit.py --install                             # Install the pre-commit and commit-msg hooks\npython tools/pre_commit.py --prepare-cache                       # Install the hook environments CI caches, prune the rest\n```\n\nThe selected hooks run in one `prek run` call; its output streams to the console and, with\n`--results-dir`, into `pre-commit-output.txt`. `--ci` skips the local-only `trufflehog` hook unless\n`SKIP` is already set. `--prepare-cache` installs every hook environment except\n`CACHE_SKIPPED_HOOKS` (TruffleHog and the Renovate validator) and then runs `prek cache gc`; the\npre-commit workflow runs it on pushes that miss the prek cache, and saves the cache only when the\ninstall succeeded.\n\nThe `precommit` group pins prek, which also accepts pre-commit's commands and options\n(`prek run <id> --hook-stage manual --files <paths>`, `SKIP=<id>`). The configuration stays\ncompatible with stock pre-commit, which the group keeps as a fallback runner for one release.\n`editorconfig-checker` enforces [`.editorconfig`](../.editorconfig) with the exclusions and relaxed\nchecks in `configs/editorconfig-checker.json`; `cmake-presets-schema` validates `CMakePresets.json` and\n`cmake/presets/*.json` offline against the schema shipped in the locked `cmake` package.\nManual-stage hooks run only on request: `clang-tidy`, `clazy`, the CI validators that download pinned\ntools (`renovate-config-validator`, `packer-validate`, `runs-on-lint`, which `pre_commit.py --ci` also\nruns on the changed files), and\nthree formatter trials that report differences without rewriting files: `gersemi` (CMake,\n`configs/gersemirc.yaml`), `yamlfmt` (`configs/yamlfmt.yaml`), and `qmlformat` (`analyzers/qmlformat.py`,\n`.qmlformat.ini`). For example, `just lint gersemi --hook-stage manual` checks every CMake file.\n\n### clean.py\n\nClean build artifacts and caches. Underlies `just clean` and `just distclean`.\n\n```bash\npython tools/clean.py              # Clean build directory\npython tools/clean.py --all        # Clean everything (build, caches, node_modules/, Python bytecode outside venvs)\npython tools/clean.py --cache      # Clean only repo-local caches (.cache, incl. ccache and the clangd index, plus a legacy .ccache)\npython tools/clean.py --dry-run    # Show what would be removed\n```\n\n### build_profile.py\n\nSummarize build-time hotspots from Ninja logs and optional Clang time traces. The parsing and report\ncode lives in `qgc_tools/build_profile.py`, which `ci/compile/cmake_helper.py` and\n`ci/compile/docker_helper.py` also use.\n\n```bash\npython tools/build_profile.py -B build                 # Report slowest Ninja edges and rebuild churn\npython tools/build_profile.py -B build --limit 25      # Show more rows per section\npython tools/build_profile.py -B build --json          # Machine-readable output\npython tools/build_profile.py -B build --json-out r.json > r.txt  # Text and JSON from one scan\npython tools/build_profile.py -B build --output-dir build/profile  # Ninja-only snapshot\npython tools/build_profile.py -B build --step-summary --extra-report \"Title=other.txt\"  # Plus a job-summary section\npython tools/build_profile.py --install-clang-build-analyzer .cache/ClangBuildAnalyzer  # Pinned binary (Linux x86_64)\n```\n\nFor per-translation-unit trace details, configure with `-DQGC_TIME_TRACE=ON`, rebuild, then rerun\nthe report — it scans the build dir for Clang `-ftime-trace` JSON and highlights the slowest events.\n\nOrdinary CI builds upload Ninja-only snapshots with separate link and generated-code rankings.\nTask totals overlap because commands run in parallel; they are not elapsed build time. The manual\nBuild Profile workflow installs the ClangBuildAnalyzer release pinned (with its SHA-256) in\n`.github/build-config.json`'s `tools.clang_build_analyzer`, and `--step-summary` writes its job\nsummary: build type, commit, and compiler, with this report and each `--extra-report` collapsed.\n\n### moccache/\n\nContent-addressed cache for Qt's moc, wired in through `AUTOMOC_EXECUTABLE` while the\n`QGC_USE_MOCCACHE` CMake option is ON (the default). The generated launcher runs\n`moccache_launcher.py`, which imports the `moccache` package so its bytecode is cached in\n`tools/moccache/__pycache__`. A moc run loads only `core.py`; `trim.py` (LRU eviction) and `stats.py`\n(hit/miss reports) are imported only for their subcommands or a due auto-trim, keeping each cache\nhit's startup short. The stats and trim commands run through the launcher too\n(`python3 tools/moccache_launcher.py --show-stats ...`).\nMisses fall through to the real moc and never fail the build. The\n[dev guide Build Caching section](../docs/en/qgc-dev-guide/getting_started/index.md#build-caching)\ncovers the cache key, environment variables, and stats commands. Tests:\n`tools/tests/test_moccache.py`.\n\n### coverage_report.py\n\nGenerate code coverage reports through the CMake coverage targets. Underlies `just coverage`.\nConfigures the `Linux-coverage` preset's `build/Linux-coverage/` directory (override the directory\nwith `-b`/`--build-dir`). Requires `gcovr` (`coverage` group, included in `dev`).\nCoverage compiles pass `-fprofile-update=atomic`, which ccache caches only from 4.10; an older\ndistro ccache (Ubuntu 24.04 ships 4.9.1) gets no cache hits in coverage builds.\nReports are Cobertura `coverage.xml` (uploaded to Codecov), gcovr's JSON `coverage-summary.json`\n(read by the step summary, `verify_coverage_thresholds.py`, and the PR build-results comment), and\nHTML `coverage.html`. Reporting fails when the build directory holds no `.gcda` counters, so tests\nthat wrote no coverage data cannot publish a zero-coverage report.\n\n```bash\npython tools/coverage_report.py              # Build with coverage, run tests like CI, generate XML, JSON + HTML\npython tools/coverage_report.py --report     # Generate report only from existing data (CI adds --step-summary)\npython tools/coverage_report.py --open       # Full run, then open the HTML report\npython tools/coverage_report.py --clean      # Delete the coverage build directory\n```\n\n**Direct CMake usage:**\n\n```bash\ncmake -B build -DQGC_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug\ncmake --build build\nctest --test-dir build\ncmake --build build --target coverage-report  # XML, JSON summary + HTML from existing data\ncmake --build build --target coverage         # Run Unit tests + generate XML, JSON summary + HTML\ncmake --build build --target coverage-clean   # Remove coverage data and reports\n```\n\n`coverage_report.py` runs CI's two test phases through the `Linux-coverage` test preset, which\nexcludes `Flaky` and `Network`: Unit tests at one process per CPU, then Integration tests at `-j2`,\nbecause instrumented integration tests time out at full parallelism. The CMake `coverage` target\nruns only the Unit tests.\n\n### check_deps.py\n\nCheck for outdated dependencies. Underlies `just check-deps`.\n\n```bash\npython tools/check_deps.py              # Check all dependencies\npython tools/check_deps.py --qt         # Check Qt version\npython tools/check_deps.py --gstreamer  # Find latest patch common to all SDK platforms\n```\n\n### API documentation (Doxygen)\n\nRun Doxygen from the repository root, as the Doxygen workflow does; HTML goes to `docs/api/html/`.\nThe workflow builds and deploys weekly; pull requests run it only when they change the Doxyfile or\nthe workflow, because warnings do not fail the build. Master runs keep the HTML artifact for eight\ndays, and the Pages workflow publishes the latest under <https://mavlink.github.io/qgroundcontrol/api/>.\n\n```bash\ndoxygen tools/configs/Doxyfile\n```\n\nRequires `doxygen`; `graphviz` enables diagrams.\n\n### Release notes\n\nRelease tags are created and pushed by hand. `just release-notes vX.Y.Z` prints the notes that\n`release.yml` drafts for that tag: git-cliff (`release` group) lists the commits since the previous\n`vX.Y.Z` tag reachable from it, grouped by type as configured in `configs/cliff.toml`, as full\nConventional Commits subjects under `## Changes in vX.Y.Z`. For a tag that does not exist yet it\nlists the unreleased commits since the last release reachable from `HEAD`.\n\n```bash\njust release-notes v5.1.5                                # Notes for an existing tag\npython tools/ci/publish/release_notes.py v5.2.0          # Same script; v5.2.0 not tagged yet\n```\n\n### PX4 metadata\n\n`px4-metadata.yml` runs `update_px4_metadata.py` daily and opens a pull request when the bundled PX4\nairframe (`AirframeFactMetaData.xml`) or parameter (`PX4ParameterFactMetaData.json`) metadata\nchanges upstream. It retries both downloads from artifacts.px4.io and replaces neither file unless\nboth are valid: well-formed airframe XML without a DTD, and xz-compressed JSON that lists parameters.\n\n```bash\npython3 tools/update_px4_metadata.py\n```\n\n## Setup Scripts\n\nScripts in `setup/` configure development environments and read versions from\n`.github/build-config.json`. They need only the standard library, so they also run with the host\n`python3` before `tools/.venv` exists. `install_dependencies` is a package\n(`tools/setup/install_dependencies/`) run through its `__main__.py`; without `--platform` it\ndetects the host. `build_gstreamer.py` is the entry for the `setup/gstreamer/` package: `config`\n(build settings, Meson options and plugins), `host` (commands, host arch and jobs), `meson` (Linux,\nmacOS, Windows), `cerbero` (Android, iOS) and `cli` (arguments, CI defaults and arch/prefix\noutputs). The Build GStreamer workflow first runs `setup/gstreamer/ci_plan.py`, which rejects an\narchitecture the dispatch's runner cannot build and outputs the runner's Qt and apt category.\n\n| Script                                    | Platform              | Description                                                                            |\n| ----------------------------------------- | --------------------- | -------------------------------------------------------------------------------------- |\n| `install_dependencies --platform debian`  | Linux (Debian/Ubuntu) | Install build dependencies via apt                                                     |\n| `install_dependencies --platform fedora`  | Linux (Fedora/RHEL)   | Install build dependencies via dnf                                                     |\n| `install_dependencies --platform arch`    | Linux (Arch)          | Install build dependencies via pacman                                                  |\n| `install_dependencies --platform macos`   | macOS                 | Install dependencies via Homebrew + GStreamer                                          |\n| `install_dependencies --platform windows` | Windows               | Install GStreamer (optional `--vulkan`, `--msvc`, `--msvc-arm64`, `--nsis`)            |\n| `install_uv.py`                           | Linux, macOS          | Install the uv release named by `required-version` when `uv` is not on `PATH`          |\n| `install_python.py`                       | All                   | Install locked Python tool groups via uv ([Python Environment](#python-environment))   |\n| `install_qt.py`                           | All                   | Install Qt with [QGC's installer or aqtinstall](#qt-installer) (CI: `plan`/`ensure`)   |\n| `setup_vscode.py`                         | All                   | Install missing VS Code workspace files from tracked templates                         |\n| `read_config.py`                          | All                   | Read `.github/build-config.json`                                                       |\n| `build_gstreamer.py`                      | All                   | Build GStreamer from source (optional)                                                 |\n| `build_android_openssl.py`                | Android               | Cross-compile OpenSSL for Android; configure builds only the build type's variant      |\n| `build_clazy.py`                          | Linux                 | Build the pinned `clazy-standalone` against the analysis LLVM, as CI does              |\n\nLinux `--category` limits the install to `core`, `qt`, `gstreamer`, `sdl`, `audio` or `misc`.\nOn Debian, `android` is `core` plus the OpenJDK for `android.java_version` in `build-config.json`;\nthe Docker Android image uses it, while CI's Android jobs install `core` and get the JDK from\n`setup-java`. Debian's `gstreamer_build` and `gstreamer_build_android`, outside the full set, hold\nwhat `build_gstreamer.py` needs for a Linux source build and an Android (Cerbero) build; the Build\nGStreamer workflow installs them.\n`--list [--platform NAME]` lists each platform's packages by category and\n`--print-packages [--category NAME]` prints one list on a single line; neither installs anything.\nLocally, the Linux installers also sync the locked `build` group (CMake, Ninja, `just`, ...) into\n`tools/.venv` and print how to activate it; CI build jobs instead use the `build.cmake_version` and\n`build.ninja_version` pins in `build-config.json`, which `build-setup` installs.\n\nOn macOS, the GStreamer runtime and development packages come from the official server, falling\nback to QGroundControl's public S3 mirror. Both sources are verified against the SHA-256 checksums\npinned in `.github/build-config.json`; installation stops if either package cannot be verified.\n\n```bash\n# Linux: Install all dependencies\npython3 tools/setup/install_dependencies --platform debian\n\n# Linux: build tools plus Gradle's JDK only, for Android builds\npython3 tools/setup/install_dependencies --platform debian --category android\n\n# macOS: Install all dependencies\npython3 tools/setup/install_dependencies --platform macos\n\n# Windows (as Admin):\npython .\\tools\\setup\\install_dependencies --platform windows\n\n# Install Qt from the configured version and modules\npython3 tools/setup/install_qt.py install --from-config --host linux --arch linux_gcc_64\n\n# Build GStreamer from source (optional — CMake auto-downloads pre-built SDKs)\npython3 tools/setup/install_dependencies --platform debian --category gstreamer_build\npython3 tools/setup/build_gstreamer.py --platform linux --prefix /opt/gstreamer\n\n# Build Clazy like the Code Analysis workflow; then put .cache/clazy/bin on PATH\npython3 tools/setup/build_clazy.py --prefix .cache/clazy\n\n# Read build config values\npython3 tools/setup/read_config.py --get qt.version\n```\n\n### Qt installer\n\n`install_qt.py` installs Qt with one of two tools, chosen by `--installer` or `QGC_QT_INSTALLER`:\n\n- `qgc`, the default: the standard-library package `setup/qt_installer/`. It reads the\n  combination's `Updates.xml` from download.qt.io, checks it and every archive against the SHA-256\n  sums that download.qt.io publishes (archives may come from any mirror), extracts the archives in parallel with\n  `cmake -E tar`, and applies aqtinstall's post-install edits. CMake comes from `PATH`, else from\n  the tools environment's `build` group. The Qt root gets `.qgc-qt-manifest.json` (archives,\n  checksums, patched files) and, for full installs, the `.qgc-modules` marker that preinstalled-SDK\n  reuse reads.\n- `aqt`: aqtinstall from the locked `qt` group, which stays as the fallback.\n\nBoth produce the same tree apart from install paths in patched text files and pkg-config `prefix=`\nlines (aqt leaves them unpatched on `linux_arm64`); `install_qt.py compare` checks this for a\ncombination. Android built on a Windows host differs on purpose: `qgc` pairs it with the\n`msvc2022_64` host Qt, while aqt names a `mingw_64` one that QGC never installs; builds pass\n`QT_HOST_PATH` either way. Like aqt, `qgc` reports but does not install addon dependencies missing from\n`--modules`, such as `qtquick3d`'s `qtquicktimeline`. The `plan` cache key's digest includes a non-aqt\ninstaller, so neither tool restores the other's tree.\n\n## Python Environment\n\n`install_python.py` installs dependency groups defined in `tools/pyproject.toml`:\n`scripts`, `precommit`, `test`, `qt`, `coverage`, `build`, `dev` (default), `lint`, `terrain`,\n`simulation`, `release`, and `all`. Groups compose smaller profiles: `dev` includes the build, Qt,\nlint, test, and release (git-cliff) tools. `build` adds CMake, Ninja, Meson, `just`, prek (with\npre-commit), and gcovr; `simulation` adds pymavlink for the `simulation/` MAVLink tools. The Pyright\nhook also syncs `terrain` so it can type-check `tools/terrain`. `--list-groups` prints each group's packages. `[tool.uv] required-version` in\n`tools/pyproject.toml` is the single uv version: CI, `install_uv.py`, and the Docker image install\nexactly that floor, and older uv releases refuse the project (run `uv self update`).\n\nAll Python dependencies are resolved in `tools/uv.lock`. Setup requires uv and uses\n`uv sync --frozen --inexact` into `tools/.venv`, so repeating it with a smaller group preserves\ninstalled tools. Use `--replace` only to deliberately replace the profile; `--check` validates the\nrunning interpreter's installed versions and Python/platform markers without installing. CMake,\nVS Code, and CI use the same environment; CMake creates it with the `scripts` group when it is\nmissing (`QGC_AUTO_PYTHON_VENV`).\n\nFor an image-owned environment, set `QGC_TOOLS_PROJECT` to the directory containing\n`pyproject.toml` and `uv.lock`, and `QGC_PYTHON_ENV` to the environment directory.\nDocker uses `/opt/qgc-tools` and `/opt/qgc-venv`. Qt installs that fall back to aqtinstall use\nthe locked `qt` group ([Qt installer](#qt-installer)).\n\n`common/` contains low-level utilities ([module index](common/README.md)); `qgc_tools/` holds\nshared policy such as the Python environment, configure-preset selection, Docker variants, and\nrelease-tag checks ([qgc_tools/README.md](qgc_tools/README.md)). Command-line entrypoints are the\ntop-level `tools/*.py` scripts and those in `setup/`, `ci/`, and `deploy/`. Import-linter enforces\nthese dependency boundaries: `ci` is the top layer, and `setup` sits beside `analyzers`,\n`generators`, `terrain`, and `translations`, which must not import each other.\n\n## Python Script Conventions\n\nNew and edited scripts under `tools/` follow one shape, built on the [`common/`](common/README.md)\nhelpers:\n\n- **Entry**: scripts in subpackages first run the bare statement\n  `sys.path.insert(0, str(Path(__file__).resolve().parents[N]))` (N levels below `tools/`);\n  top-level scripts already have `tools/` on `sys.path`. Entry scripts then\n  `import _bootstrap as _bootstrap`, which installs the CI debug hooks; the redundant alias marks\n  the import as intentional. Library modules leave `sys.path` alone\n  ([details](common/README.md#bootstrapping-imports)).\n- **Main**: define `main(argv: list[str] | None = None) -> int` and end with\n  `raise SystemExit(main())` under `if __name__ == \"__main__\":`, so tests can call `main([...])`.\n- **Output**: log locally with `common.logging` (`log_info`, `log_warn`, `log_error`); in CI,\n  report problems as `common.gh_actions` annotations (`gh_error`, `gh_warning`) and write step\n  outputs and summaries with its `write_github_output` and `write_step_summary`. Build each\n  job-summary section with `common.markdown.summary_section` (H3 title, facts table, collapsed\n  details) and format numbers with `common.format` so every job reads the same way.\n- **Subprocesses and network**: run commands through `common.proc` (`run_captured`, `run_tee`,\n  `run_with_retry`), check required executables with its `require_tools` and poll with its\n  `wait_until`, download with `common.net`, and call GitHub through `common.gh_actions.gh()`.\n- **Files**: locate the checkout with `common.paths` (`REPO_ROOT`, `TOOLS_DIR`) rather than\n  `Path(__file__).resolve().parents[N]` outside the entry statement, and read and write JSON with\n  `common.io` (`read_json`, `write_json`).\n\n## Static Analyzers\n\n`analyzers/` holds the `analyze.py` backends, whose `clang_format.py` also runs as the\nchanged-region `clang-format` pre-commit hook and `qmlformat.py` as the manual-stage `qmlformat`\ncheck, and two QGC-specific checks that also run as\npre-commit hooks: `vehicle_null_check.py` detects `activeVehicle()` results dereferenced without a\nnull check, and `qt_translate_noop_check.py` detects `QT_TRANSLATE_NOOP`/`QT_TR_NOOP` results used\nas runtime strings. See [analyzers/README.md](analyzers/README.md) for usage and output formats.\n\n## Debugging Tools\n\n### debuggers/profile.py\n\nProfile QGC for performance and memory issues.\n\n```bash\npython tools/debuggers/profile.py                # CPU profiling with perf\npython tools/debuggers/profile.py --memcheck     # Memory leak detection (valgrind)\npython tools/debuggers/profile.py --callgrind    # CPU profiling (valgrind)\npython tools/debuggers/profile.py --massif       # Heap profiling (valgrind)\npython tools/debuggers/profile.py --heaptrack    # Heap profiling (heaptrack)\npython tools/debuggers/profile.py --sanitize     # Build with QGC_ENABLE_ASAN/UBSAN in build/sanitize/, then run\npython tools/debuggers/profile.py --config Debug -- --logging:full  # Forward application arguments\n```\n\n### debuggers/android_mem_capture.py\n\nSample an Android app's memory (`dumpsys meminfo` PSS breakdown plus system `MemAvailable`) over\nadb at a fixed interval and write a CSV, for soak testing and before/after comparisons. Handles\nprocess restarts/LMK kills; press Enter during capture to drop a phase marker (POSIX terminals\nonly). Defaults to the QGC package.\n\n```bash\npython tools/debuggers/android_mem_capture.py --label geomap --interval 10   # Capture until Ctrl+C\npython tools/debuggers/android_mem_capture.py --label soak --duration 30m    # Timed capture\npython tools/debuggers/android_mem_capture.py --compare run-a.csv run-b.csv  # Summarize/compare runs\n```\n\nThe compare summary reports start/peak/end and a second-half trend (kB/min) for total PSS,\ngraphics, native heap, and system available memory.\n\n### Debugger Formatters and Suppressions\n\n- `debuggers/gdb-pretty-printers/` — GDB pretty printers for Qt 6 types; see\n  [its README](debuggers/gdb-pretty-printers/README.md) for setup.\n- `debuggers/qt6.natvis` — Visual Studio debugger visualizers for Qt 6 types. Add it to your\n  project's Natvis files, or point `visualizerFile` at it in a VS Code `cppvsdbg` launch\n  configuration.\n- `debuggers/valgrind.supp` — Valgrind suppressions used by `profile.py` and CTest memcheck.\n\n## Other Tool Areas\n\nThese areas have their own READMEs:\n\n- [ci/](ci/README.md) — the GitHub Actions entrypoints, in subpackages such as `planning/`,\n  `cache/`, `compile/`, `publish/`, and `reporting/`; workflows run them by path, and their tests\n  live in `tests/ci/` (workflow and action contracts in `tests/ci/contracts/`).\n- [simulation/](simulation/README.md) — `mock_vehicle.py`, a lightweight MAVLink vehicle on\n  UDP 14550, `run_arducopter_sitl.py`, ArduCopter SITL in Docker or from a local build on\n  `tcp://localhost:5760`, `fly_plan.py`, which flies a `.plan` in ArduPilot or PX4 SITL and\n  records the track and images, `mock_ntrip_caster.py`, an offline NTRIP caster on\n  `127.0.0.1:2101` for testing RTK corrections, and `rc_transmitter.py`, a web page of RC channel\n  sliders for PX4 SITL.\n- [translations/](translations/README.md) — `just translations` runs `qgc_lupdate.py` (lupdate,\n  the JSON string extractor, and `pseudo_loc.py`); `check_placeholders.py` guards translation\n  placeholders.\n- [terrain/](terrain/README.md) — `terrain_tiles.py` builds, validates, and uploads the\n  terrarium elevation tiles GeoMap renders; it needs the `terrain` group.\n- [generators/config_qml/](generators/config_qml/README.md) and\n  [generators/settings_qml/](generators/settings_qml/README.md) — JSON schemas for the vehicle\n  config and application settings pages CMake generates at configure time.\n\n## Code Quality Tools\n\n### ccache.conf\n\nConfiguration for [ccache](https://ccache.dev/). CMake's compiler launcher points ccache at\n`tools/configs/ccache.conf` unless `CCACHE_CONFIGPATH` is already set; see the dev guide's\n[Build Caching](../docs/en/qgc-dev-guide/getting_started/index.md#build-caching) section.\n\n```bash\n# Manual use:\nexport CCACHE_CONFIGPATH=/path/to/qgroundcontrol/tools/configs/ccache.conf\n```\n\n### coding-style/\n\nWorked examples of [CODING_STYLE.md](../CODING_STYLE.md): `CodingStyle.h`, `CodingStyle.cc`, and\n`CodingStyle.qml` ([coding-style/README.md](coding-style/README.md)).\n\n## VS Code Integration\n\n`.vscode/` tracks `extensions.json` (recommended extensions) and templates for `settings.json`\n(editor and CMake integration), `tasks.json` (build, test, format tasks), and `launch.json` (debug\nconfigurations) as `*.default.json`. `just vscode` copies missing ones without overwriting local\nfiles. Open the repository in VS Code and install the recommended extensions.\n\n## Centralized Configuration\n\nVersion numbers and build settings are centralized in `.github/build-config.json`, grouped into\n`qt`, `android`, `apple`, `build`, `gstreamer`, and `analysis` objects (schema:\n`.github/build-config.schema.json`). Scripts read it so local development and CI use the same\nversions. Read a value with its dotted path, e.g. `read_config.py --get qt.version` or\n`--get android.ndk_full_version`; exported env vars and CI outputs derive from the path\n(`android.ndk_full_version` → `ANDROID_NDK_FULL_VERSION` / `android_ndk_full_version`).\n\nRun `just validate-configs` after changing build versions, schemas, or link exceptions.\nIt validates required fields, minimum versions, SDK ordering, checksum coverage, and exception expiry.\nThe pre-commit gate triggers on schema-only edits too. External link exceptions in `configs/lychee.toml`\nare exact URLs with a review deadline. Docs builds check English internal links without\nexceptions; the all-locale build allows a finite list of existing translation links in\n`docs/.vitepress/config.mjs`. CI's documentation checks are described in\n[ci-overview.md](../.github/ci-overview.md).\n","tools/qgc_tools/README.md":"# qgc_tools Documentation\n\nPath: `tools/qgc_tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/qgc_tools/README.md)","tools/simulation/README.md":"# simulation Documentation\n\nPath: `tools/simulation/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/simulation/README.md)","tools/terrain/README.md":"# terrain Documentation\n\nPath: `tools/terrain/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/terrain/README.md)","tools/translations/README.md":"# translations Documentation\n\nPath: `tools/translations/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/translations/README.md)","tools/debuggers/gdb-pretty-printers/README.md":"# gdb-pretty-printers Documentation\n\nPath: `tools/debuggers/gdb-pretty-printers/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/debuggers/gdb-pretty-printers/README.md)","tools/generators/config_qml/README.md":"# config_qml Documentation\n\nPath: `tools/generators/config_qml/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/config_qml/README.md)","tools/generators/settings_qml/README.md":"# settings_qml Documentation\n\nPath: `tools/generators/settings_qml/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/settings_qml/README.md)","docs/en/qgc-dev-guide/tools/index.md":"# Tools Skill\n\nPath: `docs/en/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/index.md)","docs/en/qgc-dev-guide/tools/mock_link.md":"# Tools Skill\n\nPath: `docs/en/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/mock_link.md)","docs/ko/qgc-dev-guide/tools/index.md":"# Tools Skill\n\nPath: `docs/ko/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/index.md)","docs/ko/qgc-dev-guide/tools/mock_link.md":"# Tools Skill\n\nPath: `docs/ko/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/mock_link.md)","docs/tr/qgc-dev-guide/tools/index.md":"# Tools Skill\n\nPath: `docs/tr/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/index.md)","docs/tr/qgc-dev-guide/tools/mock_link.md":"# Tools Skill\n\nPath: `docs/tr/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/mock_link.md)","docs/zh/qgc-dev-guide/tools/index.md":"# Tools Skill\n\nPath: `docs/zh/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/index.md)","docs/zh/qgc-dev-guide/tools/mock_link.md":"# Tools Skill\n\nPath: `docs/zh/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/mock_link.md)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nInstructions for AI coding agents (Codex, Claude Code, etc.) working on QGroundControl.\n\n## Quick References\n\n- [CODING_STYLE.md](CODING_STYLE.md) — Naming, formatting, C++20 features, QML style, logging\n- [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) — Architecture patterns (Fact System, Multi-Vehicle, FirmwarePlugin)\n- [tools/README.md](tools/README.md) — Development scripts, tooling, and every `just` recipe\n- [test/README.md](test/README.md) — Test framework, base classes, CTest labels, MultiSignalSpy, sanitizers, coverage\n- [cmake/README.md](cmake/README.md) — CMake modules, presets, and test options\n- [.github/ci-overview.md](.github/ci-overview.md) — CI workflow/action/script layout and conventions\n- [.pre-commit-config.yaml](.pre-commit-config.yaml) — Lint hooks (`just lint` runs every hook not marked `manual`)\n\n## Required Preflight\n\nBefore modifying any repository file:\n\n1. Read [CODING_STYLE.md](CODING_STYLE.md) completely.\n2. Read the task-specific references listed in this file.\n3. Do not begin editing until those reads are complete.\n4. Before the first edit, state in a progress update which instruction files were read.\n\n## Golden Rules\n\nThese are the non-negotiables. The first four are QGC's core architecture patterns. The pre-commit hook named\nin a rule fails CI on a violation. Full list with code examples:\n[.github/CONTRIBUTING.md#architecture-patterns](.github/CONTRIBUTING.md#architecture-patterns) and\n[CODING_STYLE.md#common-pitfalls](CODING_STYLE.md#common-pitfalls).\n\n- **Fact System** — ALL vehicle parameters flow through Facts; never create custom parameter storage.\n- **Multi-Vehicle** — ALWAYS null-check `activeVehicle()` / `Vehicle*` before dereferencing (`vehicle-null-check`\n  flags unchecked `activeVehicle()` results).\n- **Firmware Plugin** — use `vehicle->firmwarePlugin()` for firmware-specific behavior, not `if (px4)` branches.\n- **QML Integration** — register types with `QML_ELEMENT`/`QML_SINGLETON`/`QML_UNCREATABLE`; expose state via `Q_PROPERTY`.\n- **No `Q_ASSERT` in production code** — use defensive checks with early returns (`check-no-qassert`).\n- **No `QTest::ignoreMessage`** in tests — use `expectLogMessage`/`ignoreLogMessage` (`check-no-qtest-ignore-message`).\n- **No fixed-delay `QTest::qWait(<n>)`** — use `QTRY_*_WITH_TIMEOUT` or `QSignalSpy::wait` (`check-no-fixed-qwait`).\n- **Comments** — prefer self-documenting code; comment only non-obvious intent, constraints, or workarounds.\n\n## Critical Files (Read First!)\n\n1. `src/FactSystem/Fact.h` — Parameter system foundation\n2. `src/Vehicle/Vehicle.h` — Core vehicle model\n3. `src/FirmwarePlugin/FirmwarePlugin.h` — Firmware abstraction\n\n## Code Structure\n\nKey modules (`src/` has about 30 subdirectories):\n\n```text\nsrc/\n├── Vehicle/          # Vehicle state/comms\n├── Comms/            # Link layer (serial, UDP, TCP, Bluetooth)\n├── FactSystem/       # Parameter management\n├── FirmwarePlugin/   # PX4/ArduPilot abstraction\n├── AutoPilotPlugins/ # Vehicle setup UI\n├── MissionManager/   # Mission planning\n├── MAVLink/          # Protocol handling\n├── VideoManager/     # Video pipeline (GStreamer)\n├── FlyView/          # In-flight UI\n├── PlanView/         # Mission planning UI\n├── QmlControls/      # Reusable QML components\n└── Settings/         # Persistent settings\n```\n\n## Build & Test Commands\n\nThe `just` recipes are the canonical workflow — see [tools/README.md](tools/README.md) for the full list. They\nconfigure and build `build/` with the `default*` preset for `BUILD_TYPE` (default `Debug`, tests enabled).\n\n```bash\njust configure          # CMake configure through the default preset\njust build              # incremental build; uses all cores (override with JOBS=N)\njust test               # ctest --preset default, LABELS=\"Unit|Integration\" EXCLUDE=\"Flaky|Network\"\njust lint               # prek run --all-files (the pre-commit hooks)\njust lint-docs          # only the docs hooks, on docs/en pages (pass --changed for branch changes)\njust test-python        # pytest for tools/tests (CI script tests in tools/tests/ci)\njust check              # lint + test-python + test\njust format-fix         # apply clang-format to changed lines\njust info               # print configured versions (Qt, CMake minimum, GStreamer) and build settings\n```\n\n- **Build incrementally** — rebuild every few file edits during multi-file C++/Qt work, not just at the end; fix build errors before continuing.\n- **Tight test loops** — iterate one test with `ctest --test-dir build -R <name>` or\n  `build/Debug/QGroundControl --unittest:<name>` (Linux path); run `just test` on the final pass.\n- **Match CI** — Linux CI runs the `Unit` and then the `Integration` label as separate CTest runs, excluding\n  `Flaky|Network`, in the `Linux-coverage` and `Linux-sanitizers` (ASan + UBSan; also excludes `NoSanitizer`)\n  presets. Reproduce a sanitizer failure with `cmake --workflow --preset Linux-sanitizers` (needs `QT_ROOT_DIR`)\n  and coverage with `just coverage`. CI lints only the files changed from the PR base, like\n  `uv run --frozen --project tools --group dev python tools/pre_commit.py --changed`.\n  See [.github/ci-overview.md](.github/ci-overview.md) for the rest.\n\n## Definition of Done\n\nBefore considering a change complete:\n\n1. `just build` succeeds.\n2. `just lint` passes.\n3. Relevant tests pass: `ctest --test-dir build -R <name>` for the touched area, `just test-python` for Python\n   tooling, and `just test` on the final pass.\n4. Commit message follows Conventional Commits (below).\n\n## Commit & Review Conventions\n\nCommit messages follow **Conventional Commits** — the type decides the release-notes section\n(`tools/configs/cliff.toml` → git-cliff). Use: `feat`, `fix`, `perf`, `revert` (listed in release notes);\n`docs`, `style`, `chore`, `refactor`, `test`, `build`, `ci` (omitted unless breaking). Example: `fix(Vehicle): guard null activeVehicle in telemetry handler`.\n`prek install` adds a `commit-msg` hook that checks this format; Renovate's `deps` type, `fixup!`/`squash!`,\nmerge, and revert messages also pass.\n\nKeep pull request history intentional and easy to review. Organize related work into coherent commits and\ncombine fixups or closely related incremental changes before publication. Avoid a trail of tiny work-in-progress,\ncleanup, or review-fix commits. Each commit should represent a clear, reviewable change without mixing unrelated work.\n\nYour output will be reviewed by another AI agent before being accepted. Keep changes focused and\nminimal, use clear naming, and leave explanatory commit messages. Avoid unrelated changes,\ncommented-out code, or ambiguous TODOs.\n\n---\n\n**Key Principle**: Match the style of code you're editing. See [CODING_STYLE.md](CODING_STYLE.md) for conventions and [CODING_STYLE.md#examples](CODING_STYLE.md#examples) for canonical Vehicle/Fact/QML snippets.\n","isInternal":false,"tokens":1733,"sizeBytes":6980},{"name":"index.md","path":"docs/en/qgc-dev-guide/tools/index.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/index.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"# Tools Skill\n\nPath: `docs/en/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/index.md)","tokens":300,"sizeBytes":189},{"name":"mock_link.md","path":"docs/en/qgc-dev-guide/tools/mock_link.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/mock_link.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"# Tools Skill\n\nPath: `docs/en/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/mock_link.md)","tokens":300,"sizeBytes":197},{"name":"index.md","path":"docs/ko/qgc-dev-guide/tools/index.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/index.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"# Tools Skill\n\nPath: `docs/ko/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/index.md)","tokens":300,"sizeBytes":189},{"name":"mock_link.md","path":"docs/ko/qgc-dev-guide/tools/mock_link.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/mock_link.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"# Tools Skill\n\nPath: `docs/ko/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/mock_link.md)","tokens":300,"sizeBytes":197},{"name":"index.md","path":"docs/tr/qgc-dev-guide/tools/index.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/index.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"# Tools Skill\n\nPath: `docs/tr/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/index.md)","tokens":300,"sizeBytes":189},{"name":"mock_link.md","path":"docs/tr/qgc-dev-guide/tools/mock_link.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/mock_link.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"# Tools Skill\n\nPath: `docs/tr/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/mock_link.md)","tokens":300,"sizeBytes":197},{"name":"index.md","path":"docs/zh/qgc-dev-guide/tools/index.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/index.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"# Tools Skill\n\nPath: `docs/zh/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/index.md)","tokens":300,"sizeBytes":189},{"name":"mock_link.md","path":"docs/zh/qgc-dev-guide/tools/mock_link.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/mock_link.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"# Tools Skill\n\nPath: `docs/zh/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/mock_link.md)","tokens":300,"sizeBytes":197},{"name":"editorconfig-checker.json","path":"tools/configs/editorconfig-checker.json","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/configs/editorconfig-checker.json","title":"Configs Skill","category":"anthropic-skill","format":"json","content":"{\n  \"$schema\": \"https://raw.githubusercontent.com/editorconfig-checker/editorconfig-checker/v4.0.2/.editorconfig-checker.schema.json\",\n  \"Exclude\": [\n    \"\\\\.(keystore|lz4|mesh)$\",\n    \"\\\\.ambr$\",\n    \"^test/GPS/Core/Protocols/(corpus|fixtures)/\",\n    \"^translations/\",\n    \"^android/res/values/strings\\\\.xml$\",\n    \"^android/src/org/mavlink/qgroundcontrol/QGCActivity\\\\.java$\",\n    \"^src/AutoPilotPlugins/PX4/(ActuatorFact\\\\.qml|AirframeComponentController\\\\.cc|FlightModesComponentSummary\\\\.qml)$\",\n    \"^src/Comms/MockLink/MockLink\\\\.General\\\\.MetaData\\\\.json$\",\n    \"^src/FirmwarePlugin/PX4/px4_custom_mode\\\\.h$\",\n    \"^src/FlyView/FlightDisplayViewUVC\\\\.qml$\",\n    \"^src/Utilities/Geo/Formats/ogckml22\\\\.xsd$\",\n    \"^src/Vehicle/Actuators/(Actuators|Mixer)\\\\.cc$\",\n    \"^src/Vehicle/FTPManager\\\\.h$\",\n    \"^src/Vehicle/VehicleSetup/(Bootloader\\\\.h|FirmwareUpgradeController\\\\.(cc|h))$\"\n  ],\n  \"Disable\": {\n    \"IndentSize\": true,\n    \"MaxLineLength\": true\n  }\n}\n","frontmatter":{"$schema":"https://raw.githubusercontent.com/editorconfig-checker/editorconfig-checker/v4.0.2/.editorconfig-checker.schema.json","Exclude":["\\.(keystore|lz4|mesh)$","\\.ambr$","^test/GPS/Core/Protocols/(corpus|fixtures)/","^translations/","^android/res/values/strings\\.xml$","^android/src/org/mavlink/qgroundcontrol/QGCActivity\\.java$","^src/AutoPilotPlugins/PX4/(ActuatorFact\\.qml|AirframeComponentController\\.cc|FlightModesComponentSummary\\.qml)$","^src/Comms/MockLink/MockLink\\.General\\.MetaData\\.json$","^src/FirmwarePlugin/PX4/px4_custom_mode\\.h$","^src/FlyView/FlightDisplayViewUVC\\.qml$","^src/Utilities/Geo/Formats/ogckml22\\.xsd$","^src/Vehicle/Actuators/(Actuators|Mixer)\\.cc$","^src/Vehicle/FTPManager\\.h$","^src/Vehicle/VehicleSetup/(Bootloader\\.h|FirmwareUpgradeController\\.(cc|h))$"],"Disable":{"IndentSize":true,"MaxLineLength":true}},"isInternal":false,"tokens":293,"sizeBytes":967},{"name":"gersemirc.yaml","path":"tools/configs/gersemirc.yaml","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/configs/gersemirc.yaml","title":"Configs Skill","category":"anthropic-skill","format":"yaml","content":"# gersemi trial: the manual-stage `gersemi` hook passes this file with --config; cmake-format and\n# cmake-lint remain the enforced CMake hooks. Settings approximate .cmake-format.yaml.\n# Files and directories defining QGC commands, so gersemi derives their keywords; gersemi resolves\n# them relative to this file. The remaining cmake/tests and cmake/GStreamer/tests files are left\n# out: their stubs redefine some of these commands.\ndefinitions:\n  - ../../cmake/CompilerWarnings.cmake\n  - ../../cmake/GStreamer/Components.cmake\n  - ../../cmake/GStreamer/Download.cmake\n  - ../../cmake/GStreamer/Install.cmake\n  - ../../cmake/GStreamer/Layout.cmake\n  - ../../cmake/GStreamer/Link.cmake\n  - ../../cmake/GStreamer/Notice.cmake\n  - ../../cmake/GStreamer/PkgConfig.cmake\n  - ../../cmake/GStreamer/Platform.cmake\n  - ../../cmake/GStreamer/PluginPolicy.cmake\n  - ../../cmake/GStreamer/Probe.cmake\n  - ../../cmake/GStreamer/SdkState.cmake\n  - ../../cmake/GStreamer/StaticInit.cmake\n  - ../../cmake/GStreamer/Targets.cmake\n  - ../../cmake/GStreamer/platform\n  - ../../cmake/GStreamer/tests/_assert.cmake\n  - ../../cmake/Helpers.cmake\n  - ../../cmake/PrintSummary.cmake\n  - ../../cmake/QGCTest.cmake\n  - ../../cmake/install\n  - ../../cmake/modules\n  - ../../cmake/platform\n  - ../../cmake/tests/CMakeLists.txt\n  - ../../cmake/tests/ExpectedFailureTest.cmake\n  - ../../src/VideoManager/VideoReceiver/GStreamer/HwBuffers/CMakeLists.txt\n  - ../../test/CMakeLists.txt\n  - ../../test/GPS/CMakeLists.txt\n  - ../../test/Portable/CMakeLists.txt\nindent: 4\nline_length: 120\n# Qt's commands (qt_add_*) and file-local test helpers are unknown; gersemi keeps their argument layout.\nwarn_about_unknown_commands: false\n","isInternal":false,"tokens":459,"sizeBytes":1694},{"name":"yamlfmt.yaml","path":"tools/configs/yamlfmt.yaml","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/configs/yamlfmt.yaml","title":"Configs Skill","category":"anthropic-skill","format":"yaml","content":"# yamlfmt trial: the manual-stage `yamlfmt` hook runs `yamlfmt -lint`; yamllint remains the\n# enforced YAML check. Settings follow .yamllint.yml: no document start marker (document-start is\n# disabled), two-space indented sequences, at most 200 columns, and comments padded by two spaces\n# (yamllint requires at least one).\nexclude:\n  # clang-format and clang-tidy read these as YAML, but their documented layout keeps `---`.\n  - .clang-format\n  - .clang-tidy\nformatter:\n  type: basic\n  max_line_length: 200\n  pad_line_comments: 2\n  retain_line_breaks_single: true\n  scan_folded_as_literal: true\n","isInternal":false,"tokens":160,"sizeBytes":596},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# QGroundControl AI Assistant Guide\n\nThe canonical AI agent guide is [AGENTS.md](../AGENTS.md) — **read it first.**\nIt lists the critical files, code structure, build/test commands, CI layout,\nand links to every topic-specific reference (CODING_STYLE, CONTRIBUTING,\ntools/README, test/README, .github/ci-overview).\n","isInternal":false,"tokens":77,"sizeBytes":317},{"name":"qgc_maps_plugin.json","path":"src/QtLocationPlugin/qgc_maps_plugin.json","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/src/QtLocationPlugin/qgc_maps_plugin.json","title":"QtLocationPlugin Plugin Manifest","category":"plugin-manifest","format":"json","content":"{\n    \"Keys\": [\"qgc-maps\"],\n    \"Provider\": \"QGroundControl\",\n    \"Version\": 101,\n    \"Experimental\": false,\n    \"Features\": [\n        \"OnlineMappingFeature\",\n        \"OfflineMappingFeature\"\n    ]\n}\n","frontmatter":{"Keys":["qgc-maps"],"Provider":"QGroundControl","Version":101,"Experimental":false,"Features":["OnlineMappingFeature","OfflineMappingFeature"]},"isInternal":false,"tokens":53,"sizeBytes":199},{"name":"README.md","path":"tools/analyzers/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/analyzers/README.md","title":"analyzers Documentation","category":"plugin-manifest","format":"markdown","content":"# QGC Static Analyzers\n\nBackends for [`tools/analyze.py`](../analyze.py) and QGC-specific static analyzers for C++ code.\nFor `analyze.py` usage and compiler-analysis prerequisites, see\n[tools/README.md](../README.md#analyzepy); for CI planning, sharding, and result caching, see\n[ci-overview.md](../../.github/ci-overview.md).\n\n## Compiler Analysis Output\n\nCompiler analysis (clang-tidy, Clazy) writes these files to the build directory:\n\n- `<tool>-progress.jsonl` records each file's duration and status as it completes, so\n  interrupted CI jobs retain partial timing evidence.\n- `<tool>-timings.json` adds selection, elapsed and accumulated worker time, shard coverage,\n  result-cache statistics, and (when profiling) per-check costs.\n- `<tool>-diagnostics.json` lists each unique diagnostic once (line, column, severity, and check,\n  by file relative to the checkout, or `build/...` for generated files), so reports can count a\n  header's warnings once across translation units and shards. `commit` names the checkout whose\n  line numbers they use (`GITHUB_SHA`, which on a pull request is GitHub's merge commit rather\n  than the PR head, else `git rev-parse HEAD`; absent when neither is known). With `PR_HEAD_SHA`\n  set (Code Analysis on a pull request), `changed` repeats those on lines the pull request changed\n  (`common.git.changed_ranges` against the PR's diff base).\n- `<tool>-raw.txt` keeps every translation unit's full output; the console shows each identical\n  diagnostic block once.\n\n`--cache-dir` (`result_cache.py`) replays results whose tool, configuration, compile command, and\npreprocessed inputs are unchanged; tool failures, timeouts, and crashes always run again.\n\nPer-check profiling adds substantial overhead and is disabled by default. Use\n`python tools/analyze.py --tool clang-tidy --profile-checks src/LogManager/LogEntryTableModel.cc`\nor the manual workflow's `profile_checks` input with a narrow path and `analyze_all: false`.\nProfiled runs print the most expensive checks and archive the original profiles under\n`clang-tidy-profiles/`. Check costs are summed across translation units and overlap with parallel\nwork. Ordinary runs retain per-file timing and progress without instrumenting individual checks.\n\n## clang-tidy Fixes\n\n`python tools/analyze.py --tool clang-tidy --fix [PATH...]` runs clang-tidy with\n`--export-fixes` for each selected translation unit, writing `clang-tidy-fixes/*.yaml` in the build\ndirectory, then merges them with `clang-apply-replacements --ignore-insert-conflict`. Headers shared\nby several translation units therefore receive each fix once, which running `clang-tidy --fix` in\nparallel cannot guarantee. The `clang-apply-replacements` beside `clang-tidy` is preferred (both ship\nin LLVM packages and the pinned `clang-tidy` PyPI wheel), then `PATH`.\n\n- Fix runs bypass `--cache-dir`: a replayed result would export no fixes.\n- Header fixes are limited to this checkout's `src/`, `test/`, and `tools/`, because CPM sources and\n  generated headers in other `src/` directories also match `.clang-tidy`'s `HeaderFilterRegex`.\n- Fixes from translation units that failed to compile are discarded, as `clang-tidy --fix` does.\n- Fixed code is not reformatted; run `just format-fix`, review with `git diff`, and rerun without\n  `--fix` for findings that have no automatic fix.\n- `analyze.py` refuses `--tool clang-tidy --fix` when `CI` or `GITHUB_ACTIONS` is set; CI only\n  reports findings.\n\n## clang-format Hook\n\n`clang_format.py` also runs as the `clang-format` pre-commit hook\n(`python3 tools/analyzers/clang_format.py [--fix] FILE...`). It checks, or with `--fix` formats, only\nthe lines that differ from the PR merge base (pre-commit's `PRE_COMMIT_FROM_REF` and\n`PRE_COMMIT_TO_REF`) or, locally, from `HEAD`. New files are checked in full and deletion-only\nchanges are skipped, as in `analyze.py --tool clang-format`.\n\n## Vehicle Null-Check Analyzer\n\nDetects unsafe patterns where `activeVehicle()` results are used without null checks.\n\n### Usage\n\n```bash\n# Analyze specific files\npython3 tools/analyzers/vehicle_null_check.py src/Vehicle/*.cc\n\n# Analyze directory recursively\npython3 tools/analyzers/vehicle_null_check.py src/\n\n# JSON output for CI/editor integration\npython3 tools/analyzers/vehicle_null_check.py --json src/\n\n# Show help\npython3 tools/analyzers/vehicle_null_check.py --help\n```\n\n### Detected Patterns\n\n| Pattern | Risk | Example |\n| --- | --- | --- |\n| `unsafe_active_vehicle_direct` | High | `activeVehicle()->method()` |\n| `unsafe_active_vehicle_use` | High | Variable used after `activeVehicle()` without check |\n\n### Example Output\n\n```text\nsrc/Example.cc:42:15: warning: unsafe_active_vehicle_direct\n  activeVehicle()->parameterManager()->getParameter(...);\n  Suggestion: Add null check before using activeVehicle():\n  Vehicle *vehicle = MultiVehicleManager::instance()->activeVehicle();\n  if (!vehicle) return;\n```\n\n### JSON Output Format\n\n```json\n[\n  {\n    \"file\": \"src/Example.cc\",\n    \"line\": 42,\n    \"column\": 15,\n    \"pattern\": \"unsafe_active_vehicle_direct\",\n    \"code\": \"activeVehicle()->parameterManager()->...\",\n    \"suggestion\": \"Add null check before using activeVehicle():...\"\n  }\n]\n```\n\n### Pre-commit Integration\n\nAlready configured in `.pre-commit-config.yaml`. Runs automatically on C++ files.\n\n```bash\n# Run manually on all files\nprek run vehicle-null-check --all-files\n```\n\n### Suppressing False Positives\n\nThe analyzer looks back 10 lines for null checks. If you have a valid null check that isn't detected, restructure your code to have the check closer to the usage, or add a comment explaining why it's safe.\n\n```cpp\n// Safe: null check is visible to analyzer\nVehicle *vehicle = MultiVehicleManager::instance()->activeVehicle();\nif (!vehicle) {\n    return;\n}\nvehicle->doSomething();  // OK - analyzer sees the check above\n```\n\n## QT_TRANSLATE_NOOP Check\n\n`qt_translate_noop_check.py` reports `QT_TRANSLATE_NOOP`/`QT_TR_NOOP` results that are returned\nor used as runtime strings (for example in `.arg()` or `QString(...)`). These macros only mark\nstrings for extraction; translate at the call site with `QCoreApplication::translate()` or `tr()`.\nIt runs as the `qt-translate-noop-check` pre-commit hook and, like the null-check analyzer, takes\nfiles or directories and `--json`.\n\n## qmlformat Check\n\n`qmlformat.py` runs Qt's `qmlformat` (from `PATH`, like qmllint) on each file in stdout mode with the\nnearest `.qmlformat.ini` and prints a unified diff where the output differs; it never rewrites files.\nIt backs `analyze.py --tool qmlformat` and the manual-stage `qmlformat` hook\n(`prek run qmlformat --hook-stage manual --files <file.qml>`). Apply a result with `qmlformat -i`.\n\n## Shared Utilities\n\n`base.py` defines `AnalysisResult` and `AnalyzerBase` for `analyze.py` backends, and\n`run_line_check_cli()`, the shared command line (paths and `--json`) of the two line-based checks.\nFile discovery uses `file_traversal.py` from [`tools/common/`](../common/README.md).\n\n## Adding New Analyzers\n\n1. Create a new Python script in `tools/analyzers/` and bootstrap imports as\n   `vehicle_null_check.py` does (see [Bootstrapping Imports](../common/README.md#bootstrapping-imports))\n2. Use `find_cpp_files()` from `common.file_traversal`\n3. For an `analyze.py --tool`, subclass `analyzers.base.AnalyzerBase` and add one entry to\n   `ANALYZERS` in `tools/analyze.py`\n4. For a line-based check, fill `AnalysisResult.findings` and define\n   `main(argv)` as `run_line_check_cli(YourAnalyzer, argv)`\n5. Add tests under `tools/tests/`, and add to `.pre-commit-config.yaml` if appropriate, running it\n   through `uv run --frozen --project tools --group scripts python` like the other hooks\n","isInternal":false,"tokens":1862,"sizeBytes":7687},{"name":"README.md","path":"tools/ci/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/ci/README.md","title":"ci Documentation","category":"plugin-manifest","format":"markdown","content":"# CI Scripts\n\n`tools/ci/` contains the Python entrypoints that GitHub Actions workflows and composite actions run\nby file path (`python3 \"${GITHUB_WORKSPACE}/tools/ci/<package>/<script>.py\"`). Each subpackage\ngroups one CI concern; put reusable helpers with multiple consumers in\n[`tools/common/`](../common/README.md) or [`tools/qgc_tools/`](../qgc_tools/README.md).\n\n## Packages\n\n| Package | Scope |\n| --- | --- |\n| `planning/` | Change detection and build-matrix planners |\n| `cache/` | Compiler, moc, CPM, and GitHub Actions cache planning and maintenance |\n| `runner/` | Runner host preparation and managed runner images |\n| `compile/` | CMake and Docker build drivers with timing and compiler-cache reporting |\n| `packaging/` | Locate, validate, and boot-test packaged build outputs |\n| `android/` | Android SDK setup, build retry, lint reporting, and emulator smoke tests |\n| `ios/` | iOS packaging and simulator smoke tests |\n| `publish/` | Attestation, publishing, release gating, draft release notes, and the GitHub Pages site |\n| `reporting/` | Build-results comments, job-summary Setup and test sections, size and coverage reports, and PR labels |\n| `gstreamer/` | GStreamer SDK packaging and mirroring |\n| `security/` | Dependency SBOM generation and vulnerability-scanner installation |\n\nSubpackage names avoid `build*` and `release`: `.gitignore` ignores directories with those names,\nand Ruff skips `build` directories.\n\n## Script Index\n\n| Script | Purpose |\n| --- | --- |\n| `planning/analysis_matrix.py` | Plan Code Analysis jobs (configure and analyzer arguments per tool family): PR shards and scan scope, one cache-seeding job for master pushes, none when nothing compiled changed |\n| `planning/android_matrix.py` | Library: the Android build legs (hosts, runners, ABIs) for `plan_platform.py` |\n| `planning/detect_changes.py` | Library: decide which platforms an event builds, including the release-tag gate |\n| `planning/plan_docker_builds.py` | Library: select the Docker variants an event builds, each with the image push target |\n| `planning/plan_platform.py` | `_detect-changes.yml`'s planner: change detection and the release-tag gate once, then the Android or Docker matrix |\n| `cache/build_cache_artifact.py` | Retain compiler/moc snapshots as short-lived workflow artifacts and restore them |\n| `cache/cache_backend.py` | Detect whether RunsOn's S3 proxy serves the Actions cache and export `QGC_ACTIONS_CACHE_BACKEND` for `setup-python` |\n| `cache/cache_plan.py` | Resolve the cache action's save policy, scope, keys, ccache budget, compiler/moc/CPM environment, and the sccache pilot's selection |\n| `cache/ccache_helper.py` | Install the pinned, checksum-verified ccache (and mold, plus sccache for the [sccache pilot](../../.github/ci-overview.md#sccache-pilot), on Linux) and plan `save-build-cache`: gate PR saves, pack the snapshot artifact, and choose the cache keys |\n| `cache/cpm_helper.py` | Fingerprint CPM dependencies and the GStreamer SDK config for `cache_plan.py`, and create and apply runner-image CPM seeds |\n| `cache/gh_cache_cleanup.py` | List and optionally delete GitHub Actions caches, taking the branch and mode from `cache-cleanup.yml`'s triggering event (`CLEANUP_*` variables override them) |\n| `runner/analysis_tools.py` | Install the clang-tidy wheel hash-pinned in `build-config.json`, verify `.clang-tidy`, and put LLVM, clang-tidy, and Clazy on `PATH` for `analysis.yml` |\n| `runner/apt_install_retry.py` | Install apt packages with bounded retries, RunsOn's in-region EC2 mirror, and a runner-owned download cache |\n| `runner/build_setup.py` | Validate `build-setup`'s inputs and resolve the values its steps read: Qt hosts, architectures and modules, cache target, Xcode and MSVC selections, Windows ARM64 cross builds' host Qt, Android ABIs and SDK environment, and the `build-config.json` tool versions |\n| `runner/enable_kvm.py` | Open `/dev/kvm` to every user on a nested-virtualization runner |\n| `runner/install_dependencies_helper.py` | `install-dependencies` entry points: plan the Linux apt cache key, install the apt packages or the macOS Homebrew packages and GStreamer (both without the tools CI pins elsewhere), and install Windows MSVC and NSIS (macOS and Windows in three timed attempts that kill a hung attempt's process tree) |\n| `runner/install_multipass.py` | Install Multipass and wait for daemon readiness; with `--allow-bridge-forwarding`, undo Docker's iptables `FORWARD` drop policy so guests reach the network |\n| `runner/runner_image.py` | Check `runner-images.yml`'s branch and AWS settings, then output, verify, promote, and retire QGC runner AMIs |\n| `runner/validate_runner_configs.py` | `packer-validate` and `runs-on-lint` hooks: validate the runner image Packer template and lint the RunsOn configuration with the Packer and RunsOn CLI pinned in `build-config.json` |\n| `compile/cmake_helper.py` | Run CI configure, build, and CTest steps with standard arguments and timing, tee configure and build output to logs, and write the job summary's Configure (result, duration, preset, generator, configure-check reuse) and Build (result, duration, toolchain, this build's compiler and moc cache hit rates, slowest build steps) sections; after a failure, annotate and summarize the first CMake error or the failed targets and first build errors |\n| `compile/docker_helper.py` | Run Docker builds and repository scripts in builder images, and report compiler identities, timings, and cache statistics |\n| `packaging/artifact_link.py` | Link an uploaded diagnostics artifact (upload-artifact's `artifact-url`) from the job summary |\n| `packaging/find_artifact.py` | Find exactly one build artifact by glob pattern (fails on none or several), optionally copying it |\n| `packaging/validate_native_package.py` | Validate native package identity, version, contents, and installed layout |\n| `packaging/verify_executable.py` | Boot-test a built QGroundControl executable, AppImage, or DMG; keep a failed boot's output, stacks, crash reports, and minidumps for the diagnostics artifact |\n| `android/android_boot_test.py` | Run the emulator boot smoke test |\n| `android/android_build_retry.py` | Retry an Android build after a known intermittent Qt deployment-settings JSON truncation |\n| `android/android_collect_diagnostics.py` | Collect emulator, ADB, AVD, and GStreamer diagnostics |\n| `android/android_lint.py` | Run `gradlew lintRelease` in the Gradle project androiddeployqt generated, annotate the first Error and Fatal issues from lint's XML report at their `android/` sources (else Gradle's errors), and write the job summary's Android Lint section (result, duration, error and warning counts, first errors); exits with Gradle's status |\n| `android/android_sdk_helper.py` | Install the `build-config.json` Android SDK/NDK packages with retries, configure tool paths, and create the PR debug keystore |\n| `ios/ios_boot_test.py` | Run the QGC smoke test in a disposable iOS simulator within a 25-minute budget; save each failed attempt's simulator and CoreSimulator logs and crash reports, annotate the failing phase and attempt, and write the Boot test summary |\n| `ios/ios_package.py` | Select signing profiles and validate/package iOS bundles |\n| `publish/attest_helper.py` | Prepare artifacts for attestation (checksum sidecar, CPM dependency SBOM, build manifest) and decide the AWS publish; verify or write standalone checksums |\n| `publish/aws_upload.py` | Publish artifacts to S3 `builds/<ref>/` (the runner's ref by default), plus `latest/` and one CloudFront invalidation for release tags |\n| `publish/deploy_docs.py` | Deploy generated documentation to an external Pages repository |\n| `publish/pages_history.py` | Library: the Pages dashboard's history of master push builds: fetch the deployed `data/history.json` (any failure but a 404 stops the build, and so does a 404 once a `pages.yml` run has succeeded), collect each new run's result, wall time, Build-step durations, package sizes, JUnit suites and failed tests, `src/` line coverage, and unique Code Analysis warnings, newest first until the 15-minute collection deadline, with two 120-second attempts per artifact, and merge by commit and workflow within the 1,000-commit and 365-day caps |\n| `publish/pages_render.py` | Library: the Pages site's self-contained HTML pages and inline SVG charts (no scripts or external assets), each chart followed by a table of its latest values and linking every run |\n| `publish/pages_site.py` | Build the GitHub Pages site for `pages.yml`: the CI health dashboard, `schemas/` (each `$id` must be its site URL), `downloads/` of the S3 master packages with their SHA-256 sidecars and provenance commits, and `api/` from the latest master Doxygen artifact or a link to api.qgroundcontrol.com (also when its download stalls); `--new-history` (`PAGES_NEW_HISTORY`) starts a new history when the deployed one is gone; checks its pages' internal links and writes the job summary's Pages site section |\n| `publish/release_branch_check.py` | Stop `release.yml` before it drafts a release for a tag that no `origin/Stable*` branch contains |\n| `publish/release_notes.py` | Draft a tag's release notes with git-cliff (`tools/configs/cliff.toml`) and create its draft GitHub Release unless one exists |\n| `reporting/collect_build_status.py` | Collect platform, pre-commit, Code Analysis, and every other workflow's status for a head SHA (with `--status-out`, each run's state, attempt, duration and total wall time, and failed-step links and lost runners from its jobs) and save the latest runs for `download_artifacts.py` |\n| `reporting/download_artifacts.py` | Download prefix-selected artifacts from matching completed workflow runs into one directory each, with a manifest naming each artifact's workflow and whether it downloaded, and record each workflow's latest run and its distributable artifacts (size, id, expiry) |\n| `reporting/generate_build_results_comment.py` | Render the consolidated PR build-results comment: verdict, per-workflow table, other checks, the PR base's builds, failures, and collapsed tests, coverage, artifact downloads, pre-commit, and Code Analysis details, compared with the base's own builds |\n| `reporting/gh_pr_size_label.py` | Apply a pull request's `size/*` label from its changed lines |\n| `reporting/junit_report.py` | Summarize JUnit results (counts, failures, slowest tests) in the job summary, annotate failed tests and, optionally, slow tests (parsed with `common/junit.py`, like the build-results comment and the Pages dashboard) |\n| `reporting/report_context.py` | Resolve a pull_request build's PR and base commit, and with `--comment` recheck that the build is current and create or update the PR's build-results comment |\n| `reporting/setup_summary.py` | Write the job summary's Setup section for `build-setup`, also after a failed setup step, which it names: runner, image, RunsOn instance, CPUs and RAM, Qt (version, installer, source), CMake, compiler, Xcode, JDK, Android NDK, Python groups, and each build cache's restore result |\n| `reporting/size_analysis.py` | Analyze and report binary-size changes |\n| `reporting/verify_coverage_thresholds.py` | Enforce line and branch coverage thresholds (read from `CMakeCache.txt` with `--build-dir`) |\n| `reporting/workflow_runs.py` | Library: read the triggering `workflow_run` payload and the configured platform workflows, which default the reporting scripts' run-query arguments; validate, select, and group workflow runs; load run snapshots |\n| `gstreamer/gstreamer_archive.py` | Package GStreamer builds and optionally upload them to S3 |\n| `gstreamer/mirror_gstreamer.py` | Verify and mirror upstream GStreamer release artifacts to S3 (`mirror-gstreamer.yml` sets its options through `MIRROR_*` variables) |\n| `security/generate_cpm_sbom.py` | Generate a CycloneDX or SPDX SBOM from CPM package metadata and the bundled GStreamer |\n| `security/install_grype.py` | Install checksum-verified Grype release assets into the scan action tool cache |\n\nThe script index and one-to-one production test coverage are checked by\n[`tools/tests/ci/contracts/test_scripts_docs.py`](../tests/ci/contracts/test_scripts_docs.py).\n\n## Shared Code and Bootstrapping\n\nEntrypoints bootstrap like `tools/setup/` scripts: insert the `tools/` directory into `sys.path`\nwith one bare statement, import `_bootstrap` for the CI debug hooks, then import from the module\nthat owns the symbol ([details](../common/README.md#bootstrapping-imports)). Scripts import each\nother as package modules:\n\n```python\nsys.path.insert(0, str(Path(__file__).resolve().parents[2]))\n\nimport _bootstrap as _bootstrap\nfrom common.gh_actions import write_github_output\nfrom qgc_tools.release_tag_check import check_release_tag\n\nfrom ci.planning.detect_changes import detect_builds\n```\n\n`ci` is the top import-linter layer: it may import the other `tools/` packages, and none of them may\nimport it. Its subpackages are independent of each other (only `publish/attest_helper.py` reuses\n`security/generate_cpm_sbom.py`); move code that several of them need into `common` or `qgc_tools`.\nKeep package `__init__.py` files import-free. Sparse-checkout jobs must include the\nentrypoint, `tools/_bootstrap.py`, imported `ci` modules with their package `__init__.py` files, and\ntransitive `common` and `qgc_tools` modules; `test_bootstrap_sparse_checkout.py` verifies that\nclosure.\n\n`cache/cache_backend.py`, `runner/apt_install_retry.py`, `runner/enable_kvm.py`,\n`runner/install_multipass.py`, `runner/runner_image.py`, `packaging/validate_native_package.py`,\nand `ios/ios_boot_test.py` are standalone: they import only the standard library and skip the\nbootstrap. `runner-images.yml` checks out `runner_image.py` by itself, so it must stay that way.\n\nScripts that run before `setup-python` use the runner's system interpreter and must import only the\nstandard library and stdlib-only `tools/` modules. `PRE_SETUP_PYTHON_SCRIPTS` in\n[`tools/tests/test_stdlib_only.py`](../tests/test_stdlib_only.py) lists them; the test imports each\none under `python -I -S` and fails when a job without `setup-python` runs a script missing from the\nlist. `setup-python` puts `tools/.venv` first on `PATH`; on Windows `install_python.py` also copies\nthe venv's `python.exe` to `python3.exe`, so later `python3` steps run in the venv on every OS.\nAs a fallback for a runner whose `PATH` still prefers its own interpreter, the test also holds every\nscript that a composite action runs as `python3` to the same rule; `_VENV_ACTION_SCRIPTS` exempts\nthe few that need third-party packages.\n\n## Tests\n\nAdd `tools/tests/ci/test_<script>.py` with every new production script; every test there must match\na script. Entry scripts define `main(argv: list[str] | None = None) -> int`\n(`tools/tests/test_bootstrap.py` checks the signature), so tests call `main([...])` instead of\npatching `sys.argv`. Tests of workflows, composite actions, presets, and deploy files go in\n`tools/tests/ci/contracts/`. Load repository YAML through `tools/tests/_helpers.py`\n(`load_workflow()`, `load_action()`, `load_yaml()`, `get_steps()`, `get_step()`): it parses each\nfile once with libyaml, keys workflow triggers under `\"on\"`, and returns a private copy;\n`workflow_names()` and `action_names()` enumerate them. Build temporary git repositories with the\n`git_repo` fixture, `_helpers.git()` and `commit_files()`, which ignore user and system git\nconfiguration. Point `GITHUB_OUTPUT`, `GITHUB_ENV`, `GITHUB_PATH` and `GITHUB_STEP_SUMMARY` at\nempty temporary files with the `gh_output`, `gh_env`, `gh_path` and `gh_summary` fixtures, and\nparse what a script wrote with `read_github_file()`, which also reads `name<<DELIMITER` blocks.\n`workflow_run_event(**fields)` points `GITHUB_EVENT_PATH` at a `workflow_run` payload.\nImport scripts outside the `tools/` packages with `load_script()`. A test reads the repository files\nit checks directly, without skipping when they are missing: a file absent from a CI sparse checkout\nfails the test. Mark the tests of each script that `windows.yml` runs on Windows, and of other\nWindows-specific code paths, with `pytestmark = pytest.mark.windows`; the Windows CI job runs\n`pytest -n 2 --durations=15 -m windows tools/tests` (two xdist workers are a trial; revert to\nserial if CI shows it slower).\nRun the same suite as CI (`pytest -q -n auto tools/tests` in the locked `tools/.venv`):\n\n```bash\njust test-python\n```\n\nTests run in parallel (pytest-xdist) and in random order (pytest-randomly, which also reseeds\n`random`), so a test must not depend on another test's side effects. The run ends with\n`Reproduce the test order with --randomly-seed=N`; replay that order serially with\n`just test-python -n 0 --randomly-seed=N`, or run in file order with `-p no:randomly`.\n`tools/tests/conftest.py` restores `os.environ` after each test, and for the whole session removes\nthe runner's context (`CI`, `GITHUB_*`, `RUNNER_*`, `ACTIONS_*`, `RUNS_ON_*`, `QGC_CI_DEBUG`,\n`QGC_ACTIONS_CACHE_BACKEND`), so tests behave the same in CI as locally and never write to the\njob's real output or summary files; a test that needs one of these variables sets it.\npytest-timeout fails any test that runs longer than 60 s. The `test` group also provides:\n\n- pytest-subprocess's `fp` fixture for sequences of faked commands (`fp.register()`); prefer it\n  over patching `subprocess.run` when the code under test runs real commands.\n- Hypothesis for property tests (`@given`); `conftest.py` disables its per-example deadline.\n  Hypothesis keeps failing examples in `.cache/hypothesis/` (set in `conftest.py`) and replays them\n  first on the next run.\n- syrupy's `snapshot` fixture for rendered output; snapshots live in `__snapshots__/*.ambr` next\n  to the test. After an intended change, refresh them with `--snapshot-update` and review the diff.\n","isInternal":false,"tokens":4231,"sizeBytes":17852},{"name":"README.md","path":"tools/coding-style/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/coding-style/README.md","title":"coding-style Documentation","category":"plugin-manifest","format":"markdown","content":"# QGroundControl Coding Style Examples\n\nReference files demonstrating QGroundControl's coding conventions. For the authoritative rules\n(naming, include order, C++20, QML, logging), see **[CODING_STYLE.md](../../CODING_STYLE.md)** — these\nfiles are worked examples of it.\n\n## Files\n\n| File | Demonstrates |\n| --- | --- |\n| `CodingStyle.h` | Header conventions: `#pragma once`, include order, forward declarations, `Q_DECLARE_LOGGING_CATEGORY`, QML macros, naming |\n| `CodingStyle.cc` | Implementation conventions: include order, `QGC_LOGGING_CATEGORY`, C++20 features, const correctness |\n| `CodingStyle.qml` | QML conventions: import/property order, naming, declarative bindings |\n\n## Related Documentation\n\n- **[CODING_STYLE.md](../../CODING_STYLE.md)** - Full coding style guide\n- **[.clang-format](../../.clang-format)** - Automatic C++ formatting rules\n- **[.clang-tidy](../../.clang-tidy)** - Static analysis configuration\n\n## Formatting & Static Analysis\n\n```bash\n./tools/analyze.py --tool clang-format --fix        # Format lines changed since the default branch\n./tools/analyze.py --tool clang-format              # Check changed lines (same rule as the pre-commit hook)\n./tools/analyze.py --tool clang-format --fix --all  # Reformat whole files (dedicated formatting change only)\n./tools/analyze.py                                   # clang-tidy on changed files\n./tools/analyze.py --all                             # clang-tidy on all files\n```\n\nOr use the `just` wrappers (`just format-fix`, `just analyze`) — see [tools/README.md](../README.md).\n","isInternal":false,"tokens":361,"sizeBytes":1562},{"name":"README.md","path":"tools/common/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/common/README.md","title":"common Documentation","category":"plugin-manifest","format":"markdown","content":"# QGC Shared Tooling\n\n`tools/common/` contains small, dependency-light helpers shared by QGC developer and CI scripts.\nImport from the module that defines a helper; `common/__init__.py` intentionally does not re-export\nsymbols. Explicit imports keep runtime and sparse-checkout dependencies visible.\n\n```python\nfrom common.file_traversal import find_repo_root\nfrom common.proc import run_captured\n```\n\n## Module Index\n\n| Module | Purpose |\n| ------ | ------- |\n| `analysis_diagnostics.py` | analyze.py's `*-diagnostics.json` reports: each tool's unique warnings across shards, and those on changed lines |\n| `aws.py` | Allowlisted public S3 object checks and uploads |\n| `build_config.py` | `.github/build-config.json` lookup, validation, and CI export |\n| `cmake.py` | CMake cache variable parsing |\n| `coverage_summary.py` | gcovr JSON summary line, function, and branch coverage metrics |\n| `file_traversal.py` | Repository-root discovery and filtered C++ file traversal |\n| `format.py` | Human-readable byte, size-delta, count, duration, percentage, and ratio formatting, and one-line clipped names (`clip`) |\n| `gh_actions.py` | GitHub CLI calls, read-only API retries and JSON reads, workflow artifact archive downloads, GitHub timestamps and durations, annotations (with optional `title`, `file`, `line`, and `col`), log groups, outputs, summaries, and CI detection |\n| `git.py` | Captured Git commands, default-branch discovery, pull request diff refs, diff bases, and changed line ranges |\n| `gstreamer.py` | GStreamer SDK package and mirror URLs and their pinned checksums |\n| `io.py` | JSON/TOML I/O, checksums, atomic writes, and safe archive extraction |\n| `junit.py` | CTest JUnit XML test cases (outcome, duration, failure excerpt) and suite wall time |\n| `logging.py` | Color-aware terminal logging |\n| `markdown.py` | GitHub-Flavored Markdown tables, code spans and blocks, `<details>` blocks, untrusted-text escaping, and the shared job-summary section layout (`summary_section`) |\n| `net.py` | Dependency-free atomic downloads and small fetches with optional SHA-256 verification; retries only transient failures (connection errors, timeouts, 5xx, 408, 429), so a 404 fails at once; `fetch_release_member` extracts one member from a pinned release archive |\n| `paths.py` | `TOOLS_DIR` and `REPO_ROOT`, resolved from the module location |\n| `platform.py` | OS and CPU architecture normalization |\n| `proc.py` | Captured, tee (through Bash, or a live-flushed Python pipe), and bounded-retry subprocess execution (optionally killing a timed-out attempt's whole process tree, `kill_process_tree`; the final failure logs the command and the end of its captured stderr), bytes-or-text output decoding, `require_tools` (`MissingToolError` names every tool missing from `PATH`), and deadline polling with `wait_until` |\n| `qt.py` | Qt installer-kit discovery for host tools such as `qt-cmake` and `lupdate` |\n| `tool_version.py` | External-tool and `uv.lock` version lookup |\n| `xml.py` | Safe XML parsing with entity-declaration rejection |\n\nAPI behavior and edge cases are covered by matching files under `tools/tests/`, such as\n`test_proc.py`, `test_io.py`, and `test_gh_actions.py`.\n\n## Bootstrapping Imports\n\nTop-level `tools/*.py` scripts run with `tools/` as `sys.path[0]` and import `common` directly.\nEntry scripts in subdirectories (`tools/setup/`, `tools/ci/<package>/`, ...) first insert `tools/`\nwith one bare statement, which Ruff's import-position rule (E402) accepts:\n\n```python\nsys.path.insert(0, str(Path(__file__).resolve().parents[2]))  # tools/ci/<package>/x.py\n\nimport _bootstrap as _bootstrap\nfrom common.proc import run_captured\n```\n\n`import _bootstrap as _bootstrap` installs the CI debug hooks (see\n[`tools/README.md`](../README.md#python-script-conventions)); the redundant alias marks the unused\nimport as intentional. Library modules without a `__main__` block do not touch `sys.path`.\n`tools/tests/test_bootstrap.py` enforces this shape.\n\nCI jobs that use sparse checkout must include the entrypoint, `tools/_bootstrap.py`, and transitive\n`ci`, `common`, and `qgc_tools` modules. `test_bootstrap_sparse_checkout.py` checks that closure\nautomatically.\n\n## Adding or Reusing Helpers\n\nBefore adding a helper, search this directory and its call sites. Add shared code only when it has\nmultiple consumers or centralizes a correctness boundary such as safe extraction, retries, GitHub\noutput encoding, or platform normalization.\n\nWhen a new helper is warranted:\n\n1. Put it in the narrowest existing module; create a module only for a distinct concern.\n2. Keep imports dependency-light and defer optional packages until the function that needs them.\n3. Export the supported surface through the module's `__all__` when it has one.\n4. Add focused tests under `tools/tests/`.\n5. Run `just test-python` and `just lint` (Ruff, Pyright, and import-linter run as pre-commit hooks).\n6. Update sparse-checkout lists if the automatic policy test reports a missing module.\n","isInternal":false,"tokens":1170,"sizeBytes":4996},{"name":"README.md","path":"tools/debuggers/gdb-pretty-printers/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/debuggers/gdb-pretty-printers/README.md","title":"gdb-pretty-printers Documentation","category":"plugin-manifest","format":"markdown","content":"# gdb-pretty-printers Documentation\n\nPath: `tools/debuggers/gdb-pretty-printers/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/debuggers/gdb-pretty-printers/README.md)","tokens":300,"sizeBytes":229},{"name":"README.md","path":"tools/generators/config_qml/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/config_qml/README.md","title":"config_qml Documentation","category":"plugin-manifest","format":"markdown","content":"# config_qml Documentation\n\nPath: `tools/generators/config_qml/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/config_qml/README.md)","tokens":300,"sizeBytes":204},{"name":"README.md","path":"tools/generators/settings_qml/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/settings_qml/README.md","title":"settings_qml Documentation","category":"plugin-manifest","format":"markdown","content":"# settings_qml Documentation\n\nPath: `tools/generators/settings_qml/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/settings_qml/README.md)","tokens":300,"sizeBytes":210},{"name":"README.md","path":"tools/qgc_tools/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/qgc_tools/README.md","title":"qgc_tools Documentation","category":"plugin-manifest","format":"markdown","content":"# qgc_tools Documentation\n\nPath: `tools/qgc_tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/qgc_tools/README.md)","tokens":300,"sizeBytes":179},{"name":"README.md","path":"tools/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/README.md","title":"tools Documentation","category":"plugin-manifest","format":"markdown","content":"# QGroundControl Tools\n\nDevelopment tools, scripts, and configuration files for QGroundControl. This is the full\nreference for every `just` recipe and standalone script; [AGENTS.md](../AGENTS.md) has the\ncondensed day-to-day workflow.\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Directory Structure](#directory-structure)\n- [Just Command Reference](#just-command-reference)\n- [Direct Script Usage](#direct-script-usage)\n- [Development Scripts](#development-scripts)\n- [Setup Scripts](#setup-scripts)\n- [Python Environment](#python-environment)\n- [Python Script Conventions](#python-script-conventions)\n- [Static Analyzers](#static-analyzers)\n- [Debugging Tools](#debugging-tools)\n- [Other Tool Areas](#other-tool-areas)\n- [Code Quality Tools](#code-quality-tools)\n- [VS Code Integration](#vs-code-integration)\n- [Centralized Configuration](#centralized-configuration)\n\n## Quick Start\n\nInstall uv, then the locked `dev` Python profile into `tools/.venv`; the profile includes `just`:\n\n```bash\n# Linux/macOS; on Windows install uv from https://docs.astral.sh/uv/getting-started/installation/\npython3 tools/setup/install_uv.py\nexport PATH=\"$HOME/.local/bin:$PATH\"\n\npython3 tools/setup/install_python.py dev   # Windows: python tools/setup/install_python.py dev\n. tools/.venv/bin/activate                  # PowerShell: . tools/.venv/Scripts/Activate.ps1\n```\n\nA separately installed `just` (`brew install just`, `cargo install just`) must be 1.48 or newer\nbecause the justfile uses `set lazy`; Ubuntu's `apt install just` ships 1.21, which is too old.\n\nEveryday loop:\n\n```bash\njust             # List all recipes\njust setup       # First-time: deps + configure + build\njust build       # Incremental build\njust check       # lint + Python tooling tests + application tests — run before declaring done\n```\n\n## Directory Structure\n\n```text\ntools/\n├── _bootstrap.py            # Import bootstrap for entry scripts (tools/ on sys.path, CI debug hooks)\n├── analyze.py               # Static analysis and formatting (clang-format, clang-tidy, clazy, qmllint, qmlformat)\n├── build_profile.py         # Summarize Ninja and Clang time-trace build hotspots\n├── check_deps.py            # Check for outdated dependencies\n├── clean.py                 # Clean build artifacts and caches\n├── configure.py             # CMake configuration wrapper\n├── coverage_report.py       # Code coverage reports\n├── moccache_launcher.py     # moccache entry point: the CMake AUTOMOC launcher and the stats/trim CLI\n├── pre_commit.py            # Pre-commit runner with CI summaries\n├── terrain_tiles.py         # Terrarium elevation tiles from Copernicus GLO-30 (GeoMap)\n├── update_px4_metadata.py   # Refresh the bundled PX4 airframe and parameter metadata\n├── validate_cmake_presets.py # CMake presets against the locked cmake package's schema (hook)\n├── validate_configs.py      # Cross-field checks for build-config.json and link exceptions\n├── pyproject.toml, uv.lock  # Locked Python tooling groups\n├── analyzers/               # analyze.py backends and QGC-specific analyzers\n├── ci/                      # GitHub Actions entrypoints, grouped by CI concern\n├── coding-style/            # Code style examples\n├── common/                  # Shared Python utilities\n├── configs/                 # Tool configuration files\n│   ├── ccache.conf          # ccache configuration\n│   ├── cliff.toml           # git-cliff release-notes sections (just release-notes)\n│   ├── Doxyfile             # Doxygen API docs, run from the repository root\n│   ├── editorconfig-checker.json # editorconfig-checker exclusions and relaxed checks\n│   ├── gersemirc.yaml       # gersemi CMake formatter trial (manual hook)\n│   ├── lychee.toml          # Link checker settings and dated URL exceptions\n│   ├── trufflehog-exclude.txt # Paths the trufflehog pre-commit hook skips\n│   └── yamlfmt.yaml         # yamlfmt YAML formatter trial (manual hook)\n├── debuggers/               # Debugger formatters, profiling, Android memory capture, Valgrind suppressions\n├── generators/              # Code generation (MAVLink enums, config/settings QML)\n├── moccache/                # Content-addressed cache for Qt moc (AUTOMOC wrapper)\n├── qgc_tools/               # Shared policy: Python environment, configure presets, Docker variants\n├── setup/                   # Environment setup scripts\n├── simulation/              # Vehicle simulators\n├── terrain/                 # Terrain tile generation package (see terrain_tiles.py)\n├── tests/                   # pytest suite for tools/ (CI script and contract tests in tests/ci/)\n└── translations/            # Translation tools (lupdate, JSON extraction, pseudo-localization)\n```\n\n## Just Command Reference\n\nAll recipes are defined in [`../justfile`](../justfile), grouped by the sections below; run `just`\nwith no arguments for the same list. Most recipes run Python through\n`uv run --frozen --project tools`. `BUILD_TYPE` (default `Debug`), `JOBS` (default: all cores),\nand `QT_DIR` override the build recipes; `just info` prints them with the Qt, CMake, and GStreamer\nversions from `.github/build-config.json` ([Centralized Configuration](#centralized-configuration)).\n\n### Setup\n\n| Recipe            | Description                                                    |\n| ----------------- | -------------------------------------------------------------- |\n| `just deps`       | Auto-detect and install system build dependencies              |\n| `just vscode`     | Install missing VS Code workspace files from tracked templates |\n\n### Build\n\n| Recipe              | Description                                                                                      |\n| ------------------- | ------------------------------------------------------------------------------------------------ |\n| `just configure`    | Configure through the matching `default*` CMake preset (Debug by default)                        |\n| `just build`        | Build the project (uses all cores by default; override with `JOBS=N`)                            |\n| `just release`      | Configure and build in Release mode (testing disabled)                                           |\n| `just clean [ARGS]` | Clean the build directory; forwards `ARGS` to `tools/clean.py` (`--cache`, `--all`, `--dry-run`) |\n| `just rebuild`      | `clean` + `configure` + `build`                                                                  |\n| `just setup`        | Full first-time setup: `deps` + `configure` + `build`                                            |\n\n### Quality\n\n| Recipe                  | Description                                                                                           |\n| ----------------------- | ----------------------------------------------------------------------------------------------------- |\n| `just test`             | Run application tests via CTest; defaults to labels `Unit`/`Integration`, excluding `Flaky`/`Network` |\n| `just test-python`      | Run the Python tooling tests (`tools/tests`, incl. `tools/tests/ci`) in parallel, in random order     |\n| `just lint [ARGS]`      | Run non-manual hooks on all files with prek (`prek run --all-files`); `ARGS` select hooks/stages      |\n| `just lint-docs [ARGS]` | Lint English docs with only the docs hooks (every page; `--changed` for branch changes)               |\n| `just format`           | Check clang-format on lines changed since the default branch (no changes)                             |\n| `just format-fix`       | Apply clang-format to lines changed since the default branch                                          |\n| `just analyze`          | Run static analysis (`tools/analyze.py`, default tool clang-tidy)                                     |\n| `just coverage`         | Build with coverage instrumentation, run tests, generate report                                       |\n| `just check`            | `lint` + `test-python` + `test` — run before declaring a task done                                    |\n\nOverride `test` label filters via environment variables or positional args:\n\n```bash\njust test                              # LABELS=Unit|Integration EXCLUDE=Flaky|Network (defaults)\nLABELS=\"Slow\" just test                # override via env var\njust test \"Slow\" \"Network\"             # override via positional args (labels, exclude)\n```\n\n### Run & Deploy\n\n| Recipe        | Description                                                                    |\n| ------------- | ------------------------------------------------------------------------------ |\n| `just run`    | Launch the built `QGroundControl` binary                                       |\n| `just docs`   | Build the VitePress documentation site (`npm run docs:build` in `docs/`)       |\n| `just docker` | Build the `ubuntu` Docker variant (`deploy/docker/run_docker.py build ubuntu`) |\n\n### Utilities\n\n| Recipe                   | Description                                                                                           |\n| ------------------------ | ----------------------------------------------------------------------------------------------------- |\n| `just info`              | Print resolved build configuration (Qt version/dir, CMake min, GStreamer, jobs, ...)                  |\n| `just check-deps`        | Check Qt, GStreamer, Python, and build tool versions (`tools/check_deps.py`)                          |\n| `just check-gstreamer`   | Check the latest GStreamer patch common to all SDK platforms                                          |\n| `just translations`      | Update translation sources (`tools/translations/qgc_lupdate.py`)                                      |\n| `just distclean`         | Clean build, caches, generated files, and `node_modules/`                                             |\n| `just validate-configs`  | Validate build settings and link exceptions ([Centralized Configuration](#centralized-configuration)) |\n| `just release-notes TAG` | Print draft release notes for a `vX.Y.Z` tag ([Release notes](#release-notes))                        |\n| `just lint-qml-build`    | Lint all QML against the build's generated type information ([analyze.py](#analyzepy))                |\n\n## Direct Script Usage\n\nPrefer `just` recipes; call the scripts below directly for flags a recipe doesn't expose. Run them\nfrom the repository root with `tools/.venv` activated ([Quick Start](#quick-start)), or prefix the\ncommand with `uv run --frozen --project tools --group dev` instead. The `dev` profile covers every\nscript here.\n\nRepository configure/build/test/workflow presets are rooted at [`../CMakePresets.json`](../CMakePresets.json).\nUse `CMakeUserPresets.json` for machine-local paths or derived presets; it is ignored by Git and\npreserved by `just clean`. `tools/configure.py` (behind `just configure`) requires the Qt kit's\n`qt-cmake`, found through `--qt-root` (`QT_DIR` for `just`), `QT_ROOT_DIR`, or the newest standard\nQt install for the host architecture. Use `--no-qt-cmake` only when supplying an explicit target\ntoolchain, as Android CI does. `configure.py` configures into the selected preset's `binaryDir`,\nwhere `cmake --build --preset` and `ctest --preset` look for the tree; `-B` overrides it.\n\n## Development Scripts\n\n### analyze.py\n\nRun static analysis and formatting on source code. Underlies `just format`, `just format-fix`,\n`just analyze`, and `just lint-qml-build`.\n\n```bash\npython tools/analyze.py                              # Analyze changed files (default tool: clang-tidy)\npython tools/analyze.py --all                        # Analyze all files\npython tools/analyze.py --tool clang-format --fix    # Format changed lines\npython tools/analyze.py --tool clang-format --all    # Check formatting (all files)\npython tools/analyze.py src/Vehicle/                 # Analyze specific directory\npython tools/analyze.py --tool clang-tidy --tool clazy --cache-dir .cache/analysis-results  # Both, one scan\npython tools/analyze.py --tool clang-tidy --all --shard 1 --shard-count 4  # One of four disjoint partitions\npython tools/analyze.py --tool clang-tidy --profile-checks src/Vehicle/Vehicle.cc  # Slower per-check profiling\npython tools/analyze.py --tool clang-tidy --fix src/Vehicle/  # Apply clang-tidy fixes (local only)\n```\n\nPaths are relative to the repository root; absolute paths inside the checkout also work.\n\nOther `--tool` choices: `clazy`, `qmllint`, `qmlformat` (reports what `qmlformat` would change,\nnever rewrites), `vehicle-null-check`, `qt-translate-noop-check`.\nWithout paths or `--all`, files changed since the merge base with the default branch (or\n`PR_BASE_SHA`; in GitHub's merge checkout of `PR_HEAD_SHA`, its first parent) are analyzed,\nincluding uncommitted and untracked files; without a usable base every file is. For clang-format the\nselection narrows to changed lines, matching the pre-commit hook: deletion-only changes are skipped\nand new or untracked files are checked in full. `--all` or explicit paths check (or `--fix`) whole\nfiles for intentional reformatting. `--advisory` keeps warnings informational; error-level\ndiagnostics and tool or compiler invocation errors still fail.\n\n**Compiler analysis** (clang-tidy, Clazy) reads `compile_commands.json` from `--build-dir`\n(default `build`) and needs generated headers, not a full build. Configure Ninja with\n`CMAKE_EXPORT_COMPILE_COMMANDS=ON`, `CMAKE_DISABLE_PRECOMPILE_HEADERS=ON`,\n`CMAKE_AUTOGEN_ORIGIN_DEPENDS=OFF`, `CMAKE_GLOBAL_AUTOGEN_TARGET=ON`, and `QGC_UNITY_BUILD=OFF`,\nthen build only `cmake --build <dir> --target qgc-analysis-headers` (`autogen` is needed only for\nIWYU). Repeating `--tool` shares one file selection and dependency scan; `--cache-dir <dir>`\nreplays unchanged results and needs `clang-scan-deps`. `tools/setup/build_clazy.py` builds the\nClazy that CI uses. `--tool clang-tidy --fix` applies the selected translation units' fixes with\n`clang-apply-replacements` ([analyzers/README.md](analyzers/README.md#clang-tidy-fixes)); CI never\napplies fixes. The clang-tidy and Clazy pre-commit hooks run only with `--hook-stage manual`;\nfast hooks remain the required PR gate. CI sharding and result caching are described in\n[ci-overview.md](../.github/ci-overview.md); output files and profiling in\n[analyzers/README.md](analyzers/README.md).\n\n**QML:** `--tool qmllint --qml-build` checks QML against the generated `<build-dir>/qml` type\ninformation after a build. Missing imports, properties, and types are errors; the fast\nsource-only hook keeps its limited checks. `just lint-qml-build` runs it on every QML file with\n`--advisory`.\n\n### pre_commit.py\n\nRuns the [`.pre-commit-config.yaml`](../.pre-commit-config.yaml) hooks with\n[prek](https://github.com/j178/prek) and CI-friendly summaries; the pre-commit workflow calls it\nwith `--ci --changed --results-dir`. A `--changed` run whose docs lint configuration changed also\nruns the docs hooks over every English page.\n\n```bash\npython tools/pre_commit.py --changed                             # Files changed vs PR_BASE_SHA or the default branch\npython tools/pre_commit.py --changed --hook ruff --hook pyright  # Only these hooks (repeatable)\npython tools/pre_commit.py --docs --changed                      # English docs with only the docs hooks (just lint-docs)\npython tools/pre_commit.py --changed --needs-qt                  # Whether the selection has QML (qmllint needs Qt)\npython tools/pre_commit.py --install                             # Install the pre-commit and commit-msg hooks\npython tools/pre_commit.py --prepare-cache                       # Install the hook environments CI caches, prune the rest\n```\n\nThe selected hooks run in one `prek run` call; its output streams to the console and, with\n`--results-dir`, into `pre-commit-output.txt`. `--ci` skips the local-only `trufflehog` hook unless\n`SKIP` is already set. `--prepare-cache` installs every hook environment except\n`CACHE_SKIPPED_HOOKS` (TruffleHog and the Renovate validator) and then runs `prek cache gc`; the\npre-commit workflow runs it on pushes that miss the prek cache, and saves the cache only when the\ninstall succeeded.\n\nThe `precommit` group pins prek, which also accepts pre-commit's commands and options\n(`prek run <id> --hook-stage manual --files <paths>`, `SKIP=<id>`). The configuration stays\ncompatible with stock pre-commit, which the group keeps as a fallback runner for one release.\n`editorconfig-checker` enforces [`.editorconfig`](../.editorconfig) with the exclusions and relaxed\nchecks in `configs/editorconfig-checker.json`; `cmake-presets-schema` validates `CMakePresets.json` and\n`cmake/presets/*.json` offline against the schema shipped in the locked `cmake` package.\nManual-stage hooks run only on request: `clang-tidy`, `clazy`, the CI validators that download pinned\ntools (`renovate-config-validator`, `packer-validate`, `runs-on-lint`, which `pre_commit.py --ci` also\nruns on the changed files), and\nthree formatter trials that report differences without rewriting files: `gersemi` (CMake,\n`configs/gersemirc.yaml`), `yamlfmt` (`configs/yamlfmt.yaml`), and `qmlformat` (`analyzers/qmlformat.py`,\n`.qmlformat.ini`). For example, `just lint gersemi --hook-stage manual` checks every CMake file.\n\n### clean.py\n\nClean build artifacts and caches. Underlies `just clean` and `just distclean`.\n\n```bash\npython tools/clean.py              # Clean build directory\npython tools/clean.py --all        # Clean everything (build, caches, node_modules/, Python bytecode outside venvs)\npython tools/clean.py --cache      # Clean only repo-local caches (.cache, incl. ccache and the clangd index, plus a legacy .ccache)\npython tools/clean.py --dry-run    # Show what would be removed\n```\n\n### build_profile.py\n\nSummarize build-time hotspots from Ninja logs and optional Clang time traces. The parsing and report\ncode lives in `qgc_tools/build_profile.py`, which `ci/compile/cmake_helper.py` and\n`ci/compile/docker_helper.py` also use.\n\n```bash\npython tools/build_profile.py -B build                 # Report slowest Ninja edges and rebuild churn\npython tools/build_profile.py -B build --limit 25      # Show more rows per section\npython tools/build_profile.py -B build --json          # Machine-readable output\npython tools/build_profile.py -B build --json-out r.json > r.txt  # Text and JSON from one scan\npython tools/build_profile.py -B build --output-dir build/profile  # Ninja-only snapshot\npython tools/build_profile.py -B build --step-summary --extra-report \"Title=other.txt\"  # Plus a job-summary section\npython tools/build_profile.py --install-clang-build-analyzer .cache/ClangBuildAnalyzer  # Pinned binary (Linux x86_64)\n```\n\nFor per-translation-unit trace details, configure with `-DQGC_TIME_TRACE=ON`, rebuild, then rerun\nthe report — it scans the build dir for Clang `-ftime-trace` JSON and highlights the slowest events.\n\nOrdinary CI builds upload Ninja-only snapshots with separate link and generated-code rankings.\nTask totals overlap because commands run in parallel; they are not elapsed build time. The manual\nBuild Profile workflow installs the ClangBuildAnalyzer release pinned (with its SHA-256) in\n`.github/build-config.json`'s `tools.clang_build_analyzer`, and `--step-summary` writes its job\nsummary: build type, commit, and compiler, with this report and each `--extra-report` collapsed.\n\n### moccache/\n\nContent-addressed cache for Qt's moc, wired in through `AUTOMOC_EXECUTABLE` while the\n`QGC_USE_MOCCACHE` CMake option is ON (the default). The generated launcher runs\n`moccache_launcher.py`, which imports the `moccache` package so its bytecode is cached in\n`tools/moccache/__pycache__`. A moc run loads only `core.py`; `trim.py` (LRU eviction) and `stats.py`\n(hit/miss reports) are imported only for their subcommands or a due auto-trim, keeping each cache\nhit's startup short. The stats and trim commands run through the launcher too\n(`python3 tools/moccache_launcher.py --show-stats ...`).\nMisses fall through to the real moc and never fail the build. The\n[dev guide Build Caching section](../docs/en/qgc-dev-guide/getting_started/index.md#build-caching)\ncovers the cache key, environment variables, and stats commands. Tests:\n`tools/tests/test_moccache.py`.\n\n### coverage_report.py\n\nGenerate code coverage reports through the CMake coverage targets. Underlies `just coverage`.\nConfigures the `Linux-coverage` preset's `build/Linux-coverage/` directory (override the directory\nwith `-b`/`--build-dir`). Requires `gcovr` (`coverage` group, included in `dev`).\nCoverage compiles pass `-fprofile-update=atomic`, which ccache caches only from 4.10; an older\ndistro ccache (Ubuntu 24.04 ships 4.9.1) gets no cache hits in coverage builds.\nReports are Cobertura `coverage.xml` (uploaded to Codecov), gcovr's JSON `coverage-summary.json`\n(read by the step summary, `verify_coverage_thresholds.py`, and the PR build-results comment), and\nHTML `coverage.html`. Reporting fails when the build directory holds no `.gcda` counters, so tests\nthat wrote no coverage data cannot publish a zero-coverage report.\n\n```bash\npython tools/coverage_report.py              # Build with coverage, run tests like CI, generate XML, JSON + HTML\npython tools/coverage_report.py --report     # Generate report only from existing data (CI adds --step-summary)\npython tools/coverage_report.py --open       # Full run, then open the HTML report\npython tools/coverage_report.py --clean      # Delete the coverage build directory\n```\n\n**Direct CMake usage:**\n\n```bash\ncmake -B build -DQGC_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug\ncmake --build build\nctest --test-dir build\ncmake --build build --target coverage-report  # XML, JSON summary + HTML from existing data\ncmake --build build --target coverage         # Run Unit tests + generate XML, JSON summary + HTML\ncmake --build build --target coverage-clean   # Remove coverage data and reports\n```\n\n`coverage_report.py` runs CI's two test phases through the `Linux-coverage` test preset, which\nexcludes `Flaky` and `Network`: Unit tests at one process per CPU, then Integration tests at `-j2`,\nbecause instrumented integration tests time out at full parallelism. The CMake `coverage` target\nruns only the Unit tests.\n\n### check_deps.py\n\nCheck for outdated dependencies. Underlies `just check-deps`.\n\n```bash\npython tools/check_deps.py              # Check all dependencies\npython tools/check_deps.py --qt         # Check Qt version\npython tools/check_deps.py --gstreamer  # Find latest patch common to all SDK platforms\n```\n\n### API documentation (Doxygen)\n\nRun Doxygen from the repository root, as the Doxygen workflow does; HTML goes to `docs/api/html/`.\nThe workflow builds and deploys weekly; pull requests run it only when they change the Doxyfile or\nthe workflow, because warnings do not fail the build. Master runs keep the HTML artifact for eight\ndays, and the Pages workflow publishes the latest under <https://mavlink.github.io/qgroundcontrol/api/>.\n\n```bash\ndoxygen tools/configs/Doxyfile\n```\n\nRequires `doxygen`; `graphviz` enables diagrams.\n\n### Release notes\n\nRelease tags are created and pushed by hand. `just release-notes vX.Y.Z` prints the notes that\n`release.yml` drafts for that tag: git-cliff (`release` group) lists the commits since the previous\n`vX.Y.Z` tag reachable from it, grouped by type as configured in `configs/cliff.toml`, as full\nConventional Commits subjects under `## Changes in vX.Y.Z`. For a tag that does not exist yet it\nlists the unreleased commits since the last release reachable from `HEAD`.\n\n```bash\njust release-notes v5.1.5                                # Notes for an existing tag\npython tools/ci/publish/release_notes.py v5.2.0          # Same script; v5.2.0 not tagged yet\n```\n\n### PX4 metadata\n\n`px4-metadata.yml` runs `update_px4_metadata.py` daily and opens a pull request when the bundled PX4\nairframe (`AirframeFactMetaData.xml`) or parameter (`PX4ParameterFactMetaData.json`) metadata\nchanges upstream. It retries both downloads from artifacts.px4.io and replaces neither file unless\nboth are valid: well-formed airframe XML without a DTD, and xz-compressed JSON that lists parameters.\n\n```bash\npython3 tools/update_px4_metadata.py\n```\n\n## Setup Scripts\n\nScripts in `setup/` configure development environments and read versions from\n`.github/build-config.json`. They need only the standard library, so they also run with the host\n`python3` before `tools/.venv` exists. `install_dependencies` is a package\n(`tools/setup/install_dependencies/`) run through its `__main__.py`; without `--platform` it\ndetects the host. `build_gstreamer.py` is the entry for the `setup/gstreamer/` package: `config`\n(build settings, Meson options and plugins), `host` (commands, host arch and jobs), `meson` (Linux,\nmacOS, Windows), `cerbero` (Android, iOS) and `cli` (arguments, CI defaults and arch/prefix\noutputs). The Build GStreamer workflow first runs `setup/gstreamer/ci_plan.py`, which rejects an\narchitecture the dispatch's runner cannot build and outputs the runner's Qt and apt category.\n\n| Script                                    | Platform              | Description                                                                            |\n| ----------------------------------------- | --------------------- | -------------------------------------------------------------------------------------- |\n| `install_dependencies --platform debian`  | Linux (Debian/Ubuntu) | Install build dependencies via apt                                                     |\n| `install_dependencies --platform fedora`  | Linux (Fedora/RHEL)   | Install build dependencies via dnf                                                     |\n| `install_dependencies --platform arch`    | Linux (Arch)          | Install build dependencies via pacman                                                  |\n| `install_dependencies --platform macos`   | macOS                 | Install dependencies via Homebrew + GStreamer                                          |\n| `install_dependencies --platform windows` | Windows               | Install GStreamer (optional `--vulkan`, `--msvc`, `--msvc-arm64`, `--nsis`)            |\n| `install_uv.py`                           | Linux, macOS          | Install the uv release named by `required-version` when `uv` is not on `PATH`          |\n| `install_python.py`                       | All                   | Install locked Python tool groups via uv ([Python Environment](#python-environment))   |\n| `install_qt.py`                           | All                   | Install Qt with [QGC's installer or aqtinstall](#qt-installer) (CI: `plan`/`ensure`)   |\n| `setup_vscode.py`                         | All                   | Install missing VS Code workspace files from tracked templates                         |\n| `read_config.py`                          | All                   | Read `.github/build-config.json`                                                       |\n| `build_gstreamer.py`                      | All                   | Build GStreamer from source (optional)                                                 |\n| `build_android_openssl.py`                | Android               | Cross-compile OpenSSL for Android; configure builds only the build type's variant      |\n| `build_clazy.py`                          | Linux                 | Build the pinned `clazy-standalone` against the analysis LLVM, as CI does              |\n\nLinux `--category` limits the install to `core`, `qt`, `gstreamer`, `sdl`, `audio` or `misc`.\nOn Debian, `android` is `core` plus the OpenJDK for `android.java_version` in `build-config.json`;\nthe Docker Android image uses it, while CI's Android jobs install `core` and get the JDK from\n`setup-java`. Debian's `gstreamer_build` and `gstreamer_build_android`, outside the full set, hold\nwhat `build_gstreamer.py` needs for a Linux source build and an Android (Cerbero) build; the Build\nGStreamer workflow installs them.\n`--list [--platform NAME]` lists each platform's packages by category and\n`--print-packages [--category NAME]` prints one list on a single line; neither installs anything.\nLocally, the Linux installers also sync the locked `build` group (CMake, Ninja, `just`, ...) into\n`tools/.venv` and print how to activate it; CI build jobs instead use the `build.cmake_version` and\n`build.ninja_version` pins in `build-config.json`, which `build-setup` installs.\n\nOn macOS, the GStreamer runtime and development packages come from the official server, falling\nback to QGroundControl's public S3 mirror. Both sources are verified against the SHA-256 checksums\npinned in `.github/build-config.json`; installation stops if either package cannot be verified.\n\n```bash\n# Linux: Install all dependencies\npython3 tools/setup/install_dependencies --platform debian\n\n# Linux: build tools plus Gradle's JDK only, for Android builds\npython3 tools/setup/install_dependencies --platform debian --category android\n\n# macOS: Install all dependencies\npython3 tools/setup/install_dependencies --platform macos\n\n# Windows (as Admin):\npython .\\tools\\setup\\install_dependencies --platform windows\n\n# Install Qt from the configured version and modules\npython3 tools/setup/install_qt.py install --from-config --host linux --arch linux_gcc_64\n\n# Build GStreamer from source (optional — CMake auto-downloads pre-built SDKs)\npython3 tools/setup/install_dependencies --platform debian --category gstreamer_build\npython3 tools/setup/build_gstreamer.py --platform linux --prefix /opt/gstreamer\n\n# Build Clazy like the Code Analysis workflow; then put .cache/clazy/bin on PATH\npython3 tools/setup/build_clazy.py --prefix .cache/clazy\n\n# Read build config values\npython3 tools/setup/read_config.py --get qt.version\n```\n\n### Qt installer\n\n`install_qt.py` installs Qt with one of two tools, chosen by `--installer` or `QGC_QT_INSTALLER`:\n\n- `qgc`, the default: the standard-library package `setup/qt_installer/`. It reads the\n  combination's `Updates.xml` from download.qt.io, checks it and every archive against the SHA-256\n  sums that download.qt.io publishes (archives may come from any mirror), extracts the archives in parallel with\n  `cmake -E tar`, and applies aqtinstall's post-install edits. CMake comes from `PATH`, else from\n  the tools environment's `build` group. The Qt root gets `.qgc-qt-manifest.json` (archives,\n  checksums, patched files) and, for full installs, the `.qgc-modules` marker that preinstalled-SDK\n  reuse reads.\n- `aqt`: aqtinstall from the locked `qt` group, which stays as the fallback.\n\nBoth produce the same tree apart from install paths in patched text files and pkg-config `prefix=`\nlines (aqt leaves them unpatched on `linux_arm64`); `install_qt.py compare` checks this for a\ncombination. Android built on a Windows host differs on purpose: `qgc` pairs it with the\n`msvc2022_64` host Qt, while aqt names a `mingw_64` one that QGC never installs; builds pass\n`QT_HOST_PATH` either way. Like aqt, `qgc` reports but does not install addon dependencies missing from\n`--modules`, such as `qtquick3d`'s `qtquicktimeline`. The `plan` cache key's digest includes a non-aqt\ninstaller, so neither tool restores the other's tree.\n\n## Python Environment\n\n`install_python.py` installs dependency groups defined in `tools/pyproject.toml`:\n`scripts`, `precommit`, `test`, `qt`, `coverage`, `build`, `dev` (default), `lint`, `terrain`,\n`simulation`, `release`, and `all`. Groups compose smaller profiles: `dev` includes the build, Qt,\nlint, test, and release (git-cliff) tools. `build` adds CMake, Ninja, Meson, `just`, prek (with\npre-commit), and gcovr; `simulation` adds pymavlink for the `simulation/` MAVLink tools. The Pyright\nhook also syncs `terrain` so it can type-check `tools/terrain`. `--list-groups` prints each group's packages. `[tool.uv] required-version` in\n`tools/pyproject.toml` is the single uv version: CI, `install_uv.py`, and the Docker image install\nexactly that floor, and older uv releases refuse the project (run `uv self update`).\n\nAll Python dependencies are resolved in `tools/uv.lock`. Setup requires uv and uses\n`uv sync --frozen --inexact` into `tools/.venv`, so repeating it with a smaller group preserves\ninstalled tools. Use `--replace` only to deliberately replace the profile; `--check` validates the\nrunning interpreter's installed versions and Python/platform markers without installing. CMake,\nVS Code, and CI use the same environment; CMake creates it with the `scripts` group when it is\nmissing (`QGC_AUTO_PYTHON_VENV`).\n\nFor an image-owned environment, set `QGC_TOOLS_PROJECT` to the directory containing\n`pyproject.toml` and `uv.lock`, and `QGC_PYTHON_ENV` to the environment directory.\nDocker uses `/opt/qgc-tools` and `/opt/qgc-venv`. Qt installs that fall back to aqtinstall use\nthe locked `qt` group ([Qt installer](#qt-installer)).\n\n`common/` contains low-level utilities ([module index](common/README.md)); `qgc_tools/` holds\nshared policy such as the Python environment, configure-preset selection, Docker variants, and\nrelease-tag checks ([qgc_tools/README.md](qgc_tools/README.md)). Command-line entrypoints are the\ntop-level `tools/*.py` scripts and those in `setup/`, `ci/`, and `deploy/`. Import-linter enforces\nthese dependency boundaries: `ci` is the top layer, and `setup` sits beside `analyzers`,\n`generators`, `terrain`, and `translations`, which must not import each other.\n\n## Python Script Conventions\n\nNew and edited scripts under `tools/` follow one shape, built on the [`common/`](common/README.md)\nhelpers:\n\n- **Entry**: scripts in subpackages first run the bare statement\n  `sys.path.insert(0, str(Path(__file__).resolve().parents[N]))` (N levels below `tools/`);\n  top-level scripts already have `tools/` on `sys.path`. Entry scripts then\n  `import _bootstrap as _bootstrap`, which installs the CI debug hooks; the redundant alias marks\n  the import as intentional. Library modules leave `sys.path` alone\n  ([details](common/README.md#bootstrapping-imports)).\n- **Main**: define `main(argv: list[str] | None = None) -> int` and end with\n  `raise SystemExit(main())` under `if __name__ == \"__main__\":`, so tests can call `main([...])`.\n- **Output**: log locally with `common.logging` (`log_info`, `log_warn`, `log_error`); in CI,\n  report problems as `common.gh_actions` annotations (`gh_error`, `gh_warning`) and write step\n  outputs and summaries with its `write_github_output` and `write_step_summary`. Build each\n  job-summary section with `common.markdown.summary_section` (H3 title, facts table, collapsed\n  details) and format numbers with `common.format` so every job reads the same way.\n- **Subprocesses and network**: run commands through `common.proc` (`run_captured`, `run_tee`,\n  `run_with_retry`), check required executables with its `require_tools` and poll with its\n  `wait_until`, download with `common.net`, and call GitHub through `common.gh_actions.gh()`.\n- **Files**: locate the checkout with `common.paths` (`REPO_ROOT`, `TOOLS_DIR`) rather than\n  `Path(__file__).resolve().parents[N]` outside the entry statement, and read and write JSON with\n  `common.io` (`read_json`, `write_json`).\n\n## Static Analyzers\n\n`analyzers/` holds the `analyze.py` backends, whose `clang_format.py` also runs as the\nchanged-region `clang-format` pre-commit hook and `qmlformat.py` as the manual-stage `qmlformat`\ncheck, and two QGC-specific checks that also run as\npre-commit hooks: `vehicle_null_check.py` detects `activeVehicle()` results dereferenced without a\nnull check, and `qt_translate_noop_check.py` detects `QT_TRANSLATE_NOOP`/`QT_TR_NOOP` results used\nas runtime strings. See [analyzers/README.md](analyzers/README.md) for usage and output formats.\n\n## Debugging Tools\n\n### debuggers/profile.py\n\nProfile QGC for performance and memory issues.\n\n```bash\npython tools/debuggers/profile.py                # CPU profiling with perf\npython tools/debuggers/profile.py --memcheck     # Memory leak detection (valgrind)\npython tools/debuggers/profile.py --callgrind    # CPU profiling (valgrind)\npython tools/debuggers/profile.py --massif       # Heap profiling (valgrind)\npython tools/debuggers/profile.py --heaptrack    # Heap profiling (heaptrack)\npython tools/debuggers/profile.py --sanitize     # Build with QGC_ENABLE_ASAN/UBSAN in build/sanitize/, then run\npython tools/debuggers/profile.py --config Debug -- --logging:full  # Forward application arguments\n```\n\n### debuggers/android_mem_capture.py\n\nSample an Android app's memory (`dumpsys meminfo` PSS breakdown plus system `MemAvailable`) over\nadb at a fixed interval and write a CSV, for soak testing and before/after comparisons. Handles\nprocess restarts/LMK kills; press Enter during capture to drop a phase marker (POSIX terminals\nonly). Defaults to the QGC package.\n\n```bash\npython tools/debuggers/android_mem_capture.py --label geomap --interval 10   # Capture until Ctrl+C\npython tools/debuggers/android_mem_capture.py --label soak --duration 30m    # Timed capture\npython tools/debuggers/android_mem_capture.py --compare run-a.csv run-b.csv  # Summarize/compare runs\n```\n\nThe compare summary reports start/peak/end and a second-half trend (kB/min) for total PSS,\ngraphics, native heap, and system available memory.\n\n### Debugger Formatters and Suppressions\n\n- `debuggers/gdb-pretty-printers/` — GDB pretty printers for Qt 6 types; see\n  [its README](debuggers/gdb-pretty-printers/README.md) for setup.\n- `debuggers/qt6.natvis` — Visual Studio debugger visualizers for Qt 6 types. Add it to your\n  project's Natvis files, or point `visualizerFile` at it in a VS Code `cppvsdbg` launch\n  configuration.\n- `debuggers/valgrind.supp` — Valgrind suppressions used by `profile.py` and CTest memcheck.\n\n## Other Tool Areas\n\nThese areas have their own READMEs:\n\n- [ci/](ci/README.md) — the GitHub Actions entrypoints, in subpackages such as `planning/`,\n  `cache/`, `compile/`, `publish/`, and `reporting/`; workflows run them by path, and their tests\n  live in `tests/ci/` (workflow and action contracts in `tests/ci/contracts/`).\n- [simulation/](simulation/README.md) — `mock_vehicle.py`, a lightweight MAVLink vehicle on\n  UDP 14550, `run_arducopter_sitl.py`, ArduCopter SITL in Docker or from a local build on\n  `tcp://localhost:5760`, `fly_plan.py`, which flies a `.plan` in ArduPilot or PX4 SITL and\n  records the track and images, `mock_ntrip_caster.py`, an offline NTRIP caster on\n  `127.0.0.1:2101` for testing RTK corrections, and `rc_transmitter.py`, a web page of RC channel\n  sliders for PX4 SITL.\n- [translations/](translations/README.md) — `just translations` runs `qgc_lupdate.py` (lupdate,\n  the JSON string extractor, and `pseudo_loc.py`); `check_placeholders.py` guards translation\n  placeholders.\n- [terrain/](terrain/README.md) — `terrain_tiles.py` builds, validates, and uploads the\n  terrarium elevation tiles GeoMap renders; it needs the `terrain` group.\n- [generators/config_qml/](generators/config_qml/README.md) and\n  [generators/settings_qml/](generators/settings_qml/README.md) — JSON schemas for the vehicle\n  config and application settings pages CMake generates at configure time.\n\n## Code Quality Tools\n\n### ccache.conf\n\nConfiguration for [ccache](https://ccache.dev/). CMake's compiler launcher points ccache at\n`tools/configs/ccache.conf` unless `CCACHE_CONFIGPATH` is already set; see the dev guide's\n[Build Caching](../docs/en/qgc-dev-guide/getting_started/index.md#build-caching) section.\n\n```bash\n# Manual use:\nexport CCACHE_CONFIGPATH=/path/to/qgroundcontrol/tools/configs/ccache.conf\n```\n\n### coding-style/\n\nWorked examples of [CODING_STYLE.md](../CODING_STYLE.md): `CodingStyle.h`, `CodingStyle.cc`, and\n`CodingStyle.qml` ([coding-style/README.md](coding-style/README.md)).\n\n## VS Code Integration\n\n`.vscode/` tracks `extensions.json` (recommended extensions) and templates for `settings.json`\n(editor and CMake integration), `tasks.json` (build, test, format tasks), and `launch.json` (debug\nconfigurations) as `*.default.json`. `just vscode` copies missing ones without overwriting local\nfiles. Open the repository in VS Code and install the recommended extensions.\n\n## Centralized Configuration\n\nVersion numbers and build settings are centralized in `.github/build-config.json`, grouped into\n`qt`, `android`, `apple`, `build`, `gstreamer`, and `analysis` objects (schema:\n`.github/build-config.schema.json`). Scripts read it so local development and CI use the same\nversions. Read a value with its dotted path, e.g. `read_config.py --get qt.version` or\n`--get android.ndk_full_version`; exported env vars and CI outputs derive from the path\n(`android.ndk_full_version` → `ANDROID_NDK_FULL_VERSION` / `android_ndk_full_version`).\n\nRun `just validate-configs` after changing build versions, schemas, or link exceptions.\nIt validates required fields, minimum versions, SDK ordering, checksum coverage, and exception expiry.\nThe pre-commit gate triggers on schema-only edits too. External link exceptions in `configs/lychee.toml`\nare exact URLs with a review deadline. Docs builds check English internal links without\nexceptions; the all-locale build allows a finite list of existing translation links in\n`docs/.vitepress/config.mjs`. CI's documentation checks are described in\n[ci-overview.md](../.github/ci-overview.md).\n","isInternal":false,"tokens":9537,"sizeBytes":41409},{"name":"README.md","path":"tools/simulation/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/simulation/README.md","title":"simulation Documentation","category":"plugin-manifest","format":"markdown","content":"# simulation Documentation\n\nPath: `tools/simulation/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/simulation/README.md)","tokens":300,"sizeBytes":182},{"name":"README.md","path":"tools/terrain/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/terrain/README.md","title":"terrain Documentation","category":"plugin-manifest","format":"markdown","content":"# terrain Documentation\n\nPath: `tools/terrain/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/terrain/README.md)","tokens":300,"sizeBytes":173},{"name":"README.md","path":"tools/translations/README.md","rawUrl":"https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/translations/README.md","title":"translations Documentation","category":"plugin-manifest","format":"markdown","content":"# translations Documentation\n\nPath: `tools/translations/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/translations/README.md)","tokens":300,"sizeBytes":188}],"systemPromptSnippet":"<yakaai_skills repo=\"mavlink/qgroundcontrol\">\n<!-- File: AGENTS.md (Tokens: ~1733 | Category: root-instruction) -->\n# AGENTS.md\n\nInstructions for AI coding agents (Codex, Claude Code, etc.) working on QGroundControl.\n\n## Quick References\n\n- [CODING_STYLE.md](CODING_STYLE.md) — Naming, formatting, C++20 features, QML style, logging\n- [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) — Architecture patterns (Fact System, Multi-Vehicle, FirmwarePlugin)\n- [tools/README.md](tools/README.md) — Development scripts, tooling, and every `just` recipe\n- [test/README.md](test/README.md) — Test framework, base classes, CTest labels, MultiSignalSpy, sanitizers, coverage\n- [cmake/README.md](cmake/README.md) — CMake modules, presets, and test options\n- [.github/ci-overview.md](.github/ci-overview.md) — CI workflow/action/script layout and conventions\n- [.pre-commit-config.yaml](.pre-commit-config.yaml) — Lint hooks (`just lint` runs every hook not marked `manual`)\n\n## Required Preflight\n\nBefore modifying any repository file:\n\n1. Read [CODING_STYLE.md](CODING_STYLE.md) completely.\n2. Read the task-specific references listed in this file.\n3. Do not begin editing until those reads are complete.\n4. Before the first edit, state in a progress update which instruction files were read.\n\n## Golden Rules\n\nThese are the non-negotiables. The first four are QGC's core architecture patterns. The pre-commit hook named\nin a rule fails CI on a violation. Full list with code examples:\n[.github/CONTRIBUTING.md#architecture-patterns](.github/CONTRIBUTING.md#architecture-patterns) and\n[CODING_STYLE.md#common-pitfalls](CODING_STYLE.md#common-pitfalls).\n\n- **Fact System** — ALL vehicle parameters flow through Facts; never create custom parameter storage.\n- **Multi-Vehicle** — ALWAYS null-check `activeVehicle()` / `Vehicle*` before dereferencing (`vehicle-null-check`\n  flags unchecked `activeVehicle()` results).\n- **Firmware Plugin** — use `vehicle->firmwarePlugin()` for firmware-specific behavior, not `if (px4)` branches.\n- **QML Integration** — register types with `QML_ELEMENT`/`QML_SINGLETON`/`QML_UNCREATABLE`; expose state via `Q_PROPERTY`.\n- **No `Q_ASSERT` in production code** — use defensive checks with early returns (`check-no-qassert`).\n- **No `QTest::ignoreMessage`** in tests — use `expectLogMessage`/`ignoreLogMessage` (`check-no-qtest-ignore-message`).\n- **No fixed-delay `QTest::qWait(<n>)`** — use `QTRY_*_WITH_TIMEOUT` or `QSignalSpy::wait` (`check-no-fixed-qwait`).\n- **Comments** — prefer self-documenting code; comment only non-obvious intent, constraints, or workarounds.\n\n## Critical Files (Read First!)\n\n1. `src/FactSystem/Fact.h` — Parameter system foundation\n2. `src/Vehicle/Vehicle.h` — Core vehicle model\n3. `src/FirmwarePlugin/FirmwarePlugin.h` — Firmware abstraction\n\n## Code Structure\n\nKey modules (`src/` has about 30 subdirectories):\n\n```text\nsrc/\n├── Vehicle/          # Vehicle state/comms\n├── Comms/            # Link layer (serial, UDP, TCP, Bluetooth)\n├── FactSystem/       # Parameter management\n├── FirmwarePlugin/   # PX4/ArduPilot abstraction\n├── AutoPilotPlugins/ # Vehicle setup UI\n├── MissionManager/   # Mission planning\n├── MAVLink/          # Protocol handling\n├── VideoManager/     # Video pipeline (GStreamer)\n├── FlyView/          # In-flight UI\n├── PlanView/         # Mission planning UI\n├── QmlControls/      # Reusable QML components\n└── Settings/         # Persistent settings\n```\n\n## Build & Test Commands\n\nThe `just` recipes are the canonical workflow — see [tools/README.md](tools/README.md) for the full list. They\nconfigure and build `build/` with the `default*` preset for `BUILD_TYPE` (default `Debug`, tests enabled).\n\n```bash\njust configure          # CMake configure through the default preset\njust build              # incremental build; uses all cores (override with JOBS=N)\njust test               # ctest --preset default, LABELS=\"Unit|Integration\" EXCLUDE=\"Flaky|Network\"\njust lint               # prek run --all-files (the pre-commit hooks)\njust lint-docs          # only the docs hooks, on docs/en pages (pass --changed for branch changes)\njust test-python        # pytest for tools/tests (CI script tests in tools/tests/ci)\njust check              # lint + test-python + test\njust format-fix         # apply clang-format to changed lines\njust info               # print configured versions (Qt, CMake minimum, GStreamer) and build settings\n```\n\n- **Build incrementally** — rebuild every few file edits during multi-file C++/Qt work, not just at the end; fix build errors before continuing.\n- **Tight test loops** — iterate one test with `ctest --test-dir build -R <name>` or\n  `build/Debug/QGroundControl --unittest:<name>` (Linux path); run `just test` on the final pass.\n- **Match CI** — Linux CI runs the `Unit` and then the `Integration` label as separate CTest runs, excluding\n  `Flaky|Network`, in the `Linux-coverage` and `Linux-sanitizers` (ASan + UBSan; also excludes `NoSanitizer`)\n  presets. Reproduce a sanitizer failure with `cmake --workflow --preset Linux-sanitizers` (needs `QT_ROOT_DIR`)\n  and coverage with `just coverage`. CI lints only the files changed from the PR base, like\n  `uv run --frozen --project tools --group dev python tools/pre_commit.py --changed`.\n  See [.github/ci-overview.md](.github/ci-overview.md) for the rest.\n\n## Definition of Done\n\nBefore considering a change complete:\n\n1. `just build` succeeds.\n2. `just lint` passes.\n3. Relevant tests pass: `ctest --test-dir build -R <name>` for the touched area, `just test-python` for Python\n   tooling, and `just test` on the final pass.\n4. Commit message follows Conventional Commits (below).\n\n## Commit & Review Conventions\n\nCommit messages follow **Conventional Commits** — the type decides the release-notes section\n(`tools/configs/cliff.toml` → git-cliff). Use: `feat`, `fix`, `perf`, `revert` (listed in release notes);\n`docs`, `style`, `chore`, `refactor`, `test`, `build`, `ci` (omitted unless breaking). Example: `fix(Vehicle): guard null activeVehicle in telemetry handler`.\n`prek install` adds a `commit-msg` hook that checks this format; Renovate's `deps` type, `fixup!`/`squash!`,\nmerge, and revert messages also pass.\n\nKeep pull request history intentional and easy to review. Organize related work into coherent commits and\ncombine fixups or closely related incremental changes before publication. Avoid a trail of tiny work-in-progress,\ncleanup, or review-fix commits. Each commit should represent a clear, reviewable change without mixing unrelated work.\n\nYour output will be reviewed by another AI agent before being accepted. Keep changes focused and\nminimal, use clear naming, and leave explanatory commit messages. Avoid unrelated changes,\ncommented-out code, or ambiguous TODOs.\n\n---\n\n**Key Principle**: Match the style of code you're editing. See [CODING_STYLE.md](CODING_STYLE.md) for conventions and [CODING_STYLE.md#examples](CODING_STYLE.md#examples) for canonical Vehicle/Fact/QML snippets.\n\n<!-- File: docs/en/qgc-dev-guide/tools/index.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Tools Skill\n\nPath: `docs/en/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/index.md)\n\n<!-- File: docs/en/qgc-dev-guide/tools/mock_link.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Tools Skill\n\nPath: `docs/en/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/en/qgc-dev-guide/tools/mock_link.md)\n\n<!-- File: docs/ko/qgc-dev-guide/tools/index.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Tools Skill\n\nPath: `docs/ko/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/index.md)\n\n<!-- File: docs/ko/qgc-dev-guide/tools/mock_link.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Tools Skill\n\nPath: `docs/ko/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/ko/qgc-dev-guide/tools/mock_link.md)\n\n<!-- File: docs/tr/qgc-dev-guide/tools/index.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Tools Skill\n\nPath: `docs/tr/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/index.md)\n\n<!-- File: docs/tr/qgc-dev-guide/tools/mock_link.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Tools Skill\n\nPath: `docs/tr/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/tr/qgc-dev-guide/tools/mock_link.md)\n\n<!-- File: docs/zh/qgc-dev-guide/tools/index.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Tools Skill\n\nPath: `docs/zh/qgc-dev-guide/tools/index.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/index.md)\n\n<!-- File: docs/zh/qgc-dev-guide/tools/mock_link.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Tools Skill\n\nPath: `docs/zh/qgc-dev-guide/tools/mock_link.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/docs/zh/qgc-dev-guide/tools/mock_link.md)\n\n<!-- File: tools/configs/editorconfig-checker.json (Tokens: ~293 | Category: anthropic-skill) -->\n{\n  \"$schema\": \"https://raw.githubusercontent.com/editorconfig-checker/editorconfig-checker/v4.0.2/.editorconfig-checker.schema.json\",\n  \"Exclude\": [\n    \"\\\\.(keystore|lz4|mesh)$\",\n    \"\\\\.ambr$\",\n    \"^test/GPS/Core/Protocols/(corpus|fixtures)/\",\n    \"^translations/\",\n    \"^android/res/values/strings\\\\.xml$\",\n    \"^android/src/org/mavlink/qgroundcontrol/QGCActivity\\\\.java$\",\n    \"^src/AutoPilotPlugins/PX4/(ActuatorFact\\\\.qml|AirframeComponentController\\\\.cc|FlightModesComponentSummary\\\\.qml)$\",\n    \"^src/Comms/MockLink/MockLink\\\\.General\\\\.MetaData\\\\.json$\",\n    \"^src/FirmwarePlugin/PX4/px4_custom_mode\\\\.h$\",\n    \"^src/FlyView/FlightDisplayViewUVC\\\\.qml$\",\n    \"^src/Utilities/Geo/Formats/ogckml22\\\\.xsd$\",\n    \"^src/Vehicle/Actuators/(Actuators|Mixer)\\\\.cc$\",\n    \"^src/Vehicle/FTPManager\\\\.h$\",\n    \"^src/Vehicle/VehicleSetup/(Bootloader\\\\.h|FirmwareUpgradeController\\\\.(cc|h))$\"\n  ],\n  \"Disable\": {\n    \"IndentSize\": true,\n    \"MaxLineLength\": true\n  }\n}\n\n<!-- File: tools/configs/gersemirc.yaml (Tokens: ~459 | Category: anthropic-skill) -->\n# gersemi trial: the manual-stage `gersemi` hook passes this file with --config; cmake-format and\n# cmake-lint remain the enforced CMake hooks. Settings approximate .cmake-format.yaml.\n# Files and directories defining QGC commands, so gersemi derives their keywords; gersemi resolves\n# them relative to this file. The remaining cmake/tests and cmake/GStreamer/tests files are left\n# out: their stubs redefine some of these commands.\ndefinitions:\n  - ../../cmake/CompilerWarnings.cmake\n  - ../../cmake/GStreamer/Components.cmake\n  - ../../cmake/GStreamer/Download.cmake\n  - ../../cmake/GStreamer/Install.cmake\n  - ../../cmake/GStreamer/Layout.cmake\n  - ../../cmake/GStreamer/Link.cmake\n  - ../../cmake/GStreamer/Notice.cmake\n  - ../../cmake/GStreamer/PkgConfig.cmake\n  - ../../cmake/GStreamer/Platform.cmake\n  - ../../cmake/GStreamer/PluginPolicy.cmake\n  - ../../cmake/GStreamer/Probe.cmake\n  - ../../cmake/GStreamer/SdkState.cmake\n  - ../../cmake/GStreamer/StaticInit.cmake\n  - ../../cmake/GStreamer/Targets.cmake\n  - ../../cmake/GStreamer/platform\n  - ../../cmake/GStreamer/tests/_assert.cmake\n  - ../../cmake/Helpers.cmake\n  - ../../cmake/PrintSummary.cmake\n  - ../../cmake/QGCTest.cmake\n  - ../../cmake/install\n  - ../../cmake/modules\n  - ../../cmake/platform\n  - ../../cmake/tests/CMakeLists.txt\n  - ../../cmake/tests/ExpectedFailureTest.cmake\n  - ../../src/VideoManager/VideoReceiver/GStreamer/HwBuffers/CMakeLists.txt\n  - ../../test/CMakeLists.txt\n  - ../../test/GPS/CMakeLists.txt\n  - ../../test/Portable/CMakeLists.txt\nindent: 4\nline_length: 120\n# Qt's commands (qt_add_*) and file-local test helpers are unknown; gersemi keeps their argument layout.\nwarn_about_unknown_commands: false\n\n<!-- File: tools/configs/yamlfmt.yaml (Tokens: ~160 | Category: anthropic-skill) -->\n# yamlfmt trial: the manual-stage `yamlfmt` hook runs `yamlfmt -lint`; yamllint remains the\n# enforced YAML check. Settings follow .yamllint.yml: no document start marker (document-start is\n# disabled), two-space indented sequences, at most 200 columns, and comments padded by two spaces\n# (yamllint requires at least one).\nexclude:\n  # clang-format and clang-tidy read these as YAML, but their documented layout keeps `---`.\n  - .clang-format\n  - .clang-tidy\nformatter:\n  type: basic\n  max_line_length: 200\n  pad_line_comments: 2\n  retain_line_breaks_single: true\n  scan_folded_as_literal: true\n\n<!-- File: .github/copilot-instructions.md (Tokens: ~77 | Category: copilot-instructions) -->\n# QGroundControl AI Assistant Guide\n\nThe canonical AI agent guide is [AGENTS.md](../AGENTS.md) — **read it first.**\nIt lists the critical files, code structure, build/test commands, CI layout,\nand links to every topic-specific reference (CODING_STYLE, CONTRIBUTING,\ntools/README, test/README, .github/ci-overview).\n\n<!-- File: src/QtLocationPlugin/qgc_maps_plugin.json (Tokens: ~53 | Category: plugin-manifest) -->\n{\n    \"Keys\": [\"qgc-maps\"],\n    \"Provider\": \"QGroundControl\",\n    \"Version\": 101,\n    \"Experimental\": false,\n    \"Features\": [\n        \"OnlineMappingFeature\",\n        \"OfflineMappingFeature\"\n    ]\n}\n\n<!-- File: tools/analyzers/README.md (Tokens: ~1862 | Category: plugin-manifest) -->\n# QGC Static Analyzers\n\nBackends for [`tools/analyze.py`](../analyze.py) and QGC-specific static analyzers for C++ code.\nFor `analyze.py` usage and compiler-analysis prerequisites, see\n[tools/README.md](../README.md#analyzepy); for CI planning, sharding, and result caching, see\n[ci-overview.md](../../.github/ci-overview.md).\n\n## Compiler Analysis Output\n\nCompiler analysis (clang-tidy, Clazy) writes these files to the build directory:\n\n- `<tool>-progress.jsonl` records each file's duration and status as it completes, so\n  interrupted CI jobs retain partial timing evidence.\n- `<tool>-timings.json` adds selection, elapsed and accumulated worker time, shard coverage,\n  result-cache statistics, and (when profiling) per-check costs.\n- `<tool>-diagnostics.json` lists each unique diagnostic once (line, column, severity, and check,\n  by file relative to the checkout, or `build/...` for generated files), so reports can count a\n  header's warnings once across translation units and shards. `commit` names the checkout whose\n  line numbers they use (`GITHUB_SHA`, which on a pull request is GitHub's merge commit rather\n  than the PR head, else `git rev-parse HEAD`; absent when neither is known). With `PR_HEAD_SHA`\n  set (Code Analysis on a pull request), `changed` repeats those on lines the pull request changed\n  (`common.git.changed_ranges` against the PR's diff base).\n- `<tool>-raw.txt` keeps every translation unit's full output; the console shows each identical\n  diagnostic block once.\n\n`--cache-dir` (`result_cache.py`) replays results whose tool, configuration, compile command, and\npreprocessed inputs are unchanged; tool failures, timeouts, and crashes always run again.\n\nPer-check profiling adds substantial overhead and is disabled by default. Use\n`python tools/analyze.py --tool clang-tidy --profile-checks src/LogManager/LogEntryTableModel.cc`\nor the manual workflow's `profile_checks` input with a narrow path and `analyze_all: false`.\nProfiled runs print the most expensive checks and archive the original profiles under\n`clang-tidy-profiles/`. Check costs are summed across translation units and overlap with parallel\nwork. Ordinary runs retain per-file timing and progress without instrumenting individual checks.\n\n## clang-tidy Fixes\n\n`python tools/analyze.py --tool clang-tidy --fix [PATH...]` runs clang-tidy with\n`--export-fixes` for each selected translation unit, writing `clang-tidy-fixes/*.yaml` in the build\ndirectory, then merges them with `clang-apply-replacements --ignore-insert-conflict`. Headers shared\nby several translation units therefore receive each fix once, which running `clang-tidy --fix` in\nparallel cannot guarantee. The `clang-apply-replacements` beside `clang-tidy` is preferred (both ship\nin LLVM packages and the pinned `clang-tidy` PyPI wheel), then `PATH`.\n\n- Fix runs bypass `--cache-dir`: a replayed result would export no fixes.\n- Header fixes are limited to this checkout's `src/`, `test/`, and `tools/`, because CPM sources and\n  generated headers in other `src/` directories also match `.clang-tidy`'s `HeaderFilterRegex`.\n- Fixes from translation units that failed to compile are discarded, as `clang-tidy --fix` does.\n- Fixed code is not reformatted; run `just format-fix`, review with `git diff`, and rerun without\n  `--fix` for findings that have no automatic fix.\n- `analyze.py` refuses `--tool clang-tidy --fix` when `CI` or `GITHUB_ACTIONS` is set; CI only\n  reports findings.\n\n## clang-format Hook\n\n`clang_format.py` also runs as the `clang-format` pre-commit hook\n(`python3 tools/analyzers/clang_format.py [--fix] FILE...`). It checks, or with `--fix` formats, only\nthe lines that differ from the PR merge base (pre-commit's `PRE_COMMIT_FROM_REF` and\n`PRE_COMMIT_TO_REF`) or, locally, from `HEAD`. New files are checked in full and deletion-only\nchanges are skipped, as in `analyze.py --tool clang-format`.\n\n## Vehicle Null-Check Analyzer\n\nDetects unsafe patterns where `activeVehicle()` results are used without null checks.\n\n### Usage\n\n```bash\n# Analyze specific files\npython3 tools/analyzers/vehicle_null_check.py src/Vehicle/*.cc\n\n# Analyze directory recursively\npython3 tools/analyzers/vehicle_null_check.py src/\n\n# JSON output for CI/editor integration\npython3 tools/analyzers/vehicle_null_check.py --json src/\n\n# Show help\npython3 tools/analyzers/vehicle_null_check.py --help\n```\n\n### Detected Patterns\n\n| Pattern | Risk | Example |\n| --- | --- | --- |\n| `unsafe_active_vehicle_direct` | High | `activeVehicle()->method()` |\n| `unsafe_active_vehicle_use` | High | Variable used after `activeVehicle()` without check |\n\n### Example Output\n\n```text\nsrc/Example.cc:42:15: warning: unsafe_active_vehicle_direct\n  activeVehicle()->parameterManager()->getParameter(...);\n  Suggestion: Add null check before using activeVehicle():\n  Vehicle *vehicle = MultiVehicleManager::instance()->activeVehicle();\n  if (!vehicle) return;\n```\n\n### JSON Output Format\n\n```json\n[\n  {\n    \"file\": \"src/Example.cc\",\n    \"line\": 42,\n    \"column\": 15,\n    \"pattern\": \"unsafe_active_vehicle_direct\",\n    \"code\": \"activeVehicle()->parameterManager()->...\",\n    \"suggestion\": \"Add null check before using activeVehicle():...\"\n  }\n]\n```\n\n### Pre-commit Integration\n\nAlready configured in `.pre-commit-config.yaml`. Runs automatically on C++ files.\n\n```bash\n# Run manually on all files\nprek run vehicle-null-check --all-files\n```\n\n### Suppressing False Positives\n\nThe analyzer looks back 10 lines for null checks. If you have a valid null check that isn't detected, restructure your code to have the check closer to the usage, or add a comment explaining why it's safe.\n\n```cpp\n// Safe: null check is visible to analyzer\nVehicle *vehicle = MultiVehicleManager::instance()->activeVehicle();\nif (!vehicle) {\n    return;\n}\nvehicle->doSomething();  // OK - analyzer sees the check above\n```\n\n## QT_TRANSLATE_NOOP Check\n\n`qt_translate_noop_check.py` reports `QT_TRANSLATE_NOOP`/`QT_TR_NOOP` results that are returned\nor used as runtime strings (for example in `.arg()` or `QString(...)`). These macros only mark\nstrings for extraction; translate at the call site with `QCoreApplication::translate()` or `tr()`.\nIt runs as the `qt-translate-noop-check` pre-commit hook and, like the null-check analyzer, takes\nfiles or directories and `--json`.\n\n## qmlformat Check\n\n`qmlformat.py` runs Qt's `qmlformat` (from `PATH`, like qmllint) on each file in stdout mode with the\nnearest `.qmlformat.ini` and prints a unified diff where the output differs; it never rewrites files.\nIt backs `analyze.py --tool qmlformat` and the manual-stage `qmlformat` hook\n(`prek run qmlformat --hook-stage manual --files <file.qml>`). Apply a result with `qmlformat -i`.\n\n## Shared Utilities\n\n`base.py` defines `AnalysisResult` and `AnalyzerBase` for `analyze.py` backends, and\n`run_line_check_cli()`, the shared command line (paths and `--json`) of the two line-based checks.\nFile discovery uses `file_traversal.py` from [`tools/common/`](../common/README.md).\n\n## Adding New Analyzers\n\n1. Create a new Python script in `tools/analyzers/` and bootstrap imports as\n   `vehicle_null_check.py` does (see [Bootstrapping Imports](../common/README.md#bootstrapping-imports))\n2. Use `find_cpp_files()` from `common.file_traversal`\n3. For an `analyze.py --tool`, subclass `analyzers.base.AnalyzerBase` and add one entry to\n   `ANALYZERS` in `tools/analyze.py`\n4. For a line-based check, fill `AnalysisResult.findings` and define\n   `main(argv)` as `run_line_check_cli(YourAnalyzer, argv)`\n5. Add tests under `tools/tests/`, and add to `.pre-commit-config.yaml` if appropriate, running it\n   through `uv run --frozen --project tools --group scripts python` like the other hooks\n\n<!-- File: tools/ci/README.md (Tokens: ~4231 | Category: plugin-manifest) -->\n# CI Scripts\n\n`tools/ci/` contains the Python entrypoints that GitHub Actions workflows and composite actions run\nby file path (`python3 \"${GITHUB_WORKSPACE}/tools/ci/<package>/<script>.py\"`). Each subpackage\ngroups one CI concern; put reusable helpers with multiple consumers in\n[`tools/common/`](../common/README.md) or [`tools/qgc_tools/`](../qgc_tools/README.md).\n\n## Packages\n\n| Package | Scope |\n| --- | --- |\n| `planning/` | Change detection and build-matrix planners |\n| `cache/` | Compiler, moc, CPM, and GitHub Actions cache planning and maintenance |\n| `runner/` | Runner host preparation and managed runner images |\n| `compile/` | CMake and Docker build drivers with timing and compiler-cache reporting |\n| `packaging/` | Locate, validate, and boot-test packaged build outputs |\n| `android/` | Android SDK setup, build retry, lint reporting, and emulator smoke tests |\n| `ios/` | iOS packaging and simulator smoke tests |\n| `publish/` | Attestation, publishing, release gating, draft release notes, and the GitHub Pages site |\n| `reporting/` | Build-results comments, job-summary Setup and test sections, size and coverage reports, and PR labels |\n| `gstreamer/` | GStreamer SDK packaging and mirroring |\n| `security/` | Dependency SBOM generation and vulnerability-scanner installation |\n\nSubpackage names avoid `build*` and `release`: `.gitignore` ignores directories with those names,\nand Ruff skips `build` directories.\n\n## Script Index\n\n| Script | Purpose |\n| --- | --- |\n| `planning/analysis_matrix.py` | Plan Code Analysis jobs (configure and analyzer arguments per tool family): PR shards and scan scope, one cache-seeding job for master pushes, none when nothing compiled changed |\n| `planning/android_matrix.py` | Library: the Android build legs (hosts, runners, ABIs) for `plan_platform.py` |\n| `planning/detect_changes.py` | Library: decide which platforms an event builds, including the release-tag gate |\n| `planning/plan_docker_builds.py` | Library: select the Docker variants an event builds, each with the image push target |\n| `planning/plan_platform.py` | `_detect-changes.yml`'s planner: change detection and the release-tag gate once, then the Android or Docker matrix |\n| `cache/build_cache_artifact.py` | Retain compiler/moc snapshots as short-lived workflow artifacts and restore them |\n| `cache/cache_backend.py` | Detect whether RunsOn's S3 proxy serves the Actions cache and export `QGC_ACTIONS_CACHE_BACKEND` for `setup-python` |\n| `cache/cache_plan.py` | Resolve the cache action's save policy, scope, keys, ccache budget, compiler/moc/CPM environment, and the sccache pilot's selection |\n| `cache/ccache_helper.py` | Install the pinned, checksum-verified ccache (and mold, plus sccache for the [sccache pilot](../../.github/ci-overview.md#sccache-pilot), on Linux) and plan `save-build-cache`: gate PR saves, pack the snapshot artifact, and choose the cache keys |\n| `cache/cpm_helper.py` | Fingerprint CPM dependencies and the GStreamer SDK config for `cache_plan.py`, and create and apply runner-image CPM seeds |\n| `cache/gh_cache_cleanup.py` | List and optionally delete GitHub Actions caches, taking the branch and mode from `cache-cleanup.yml`'s triggering event (`CLEANUP_*` variables override them) |\n| `runner/analysis_tools.py` | Install the clang-tidy wheel hash-pinned in `build-config.json`, verify `.clang-tidy`, and put LLVM, clang-tidy, and Clazy on `PATH` for `analysis.yml` |\n| `runner/apt_install_retry.py` | Install apt packages with bounded retries, RunsOn's in-region EC2 mirror, and a runner-owned download cache |\n| `runner/build_setup.py` | Validate `build-setup`'s inputs and resolve the values its steps read: Qt hosts, architectures and modules, cache target, Xcode and MSVC selections, Windows ARM64 cross builds' host Qt, Android ABIs and SDK environment, and the `build-config.json` tool versions |\n| `runner/enable_kvm.py` | Open `/dev/kvm` to every user on a nested-virtualization runner |\n| `runner/install_dependencies_helper.py` | `install-dependencies` entry points: plan the Linux apt cache key, install the apt packages or the macOS Homebrew packages and GStreamer (both without the tools CI pins elsewhere), and install Windows MSVC and NSIS (macOS and Windows in three timed attempts that kill a hung attempt's process tree) |\n| `runner/install_multipass.py` | Install Multipass and wait for daemon readiness; with `--allow-bridge-forwarding`, undo Docker's iptables `FORWARD` drop policy so guests reach the network |\n| `runner/runner_image.py` | Check `runner-images.yml`'s branch and AWS settings, then output, verify, promote, and retire QGC runner AMIs |\n| `runner/validate_runner_configs.py` | `packer-validate` and `runs-on-lint` hooks: validate the runner image Packer template and lint the RunsOn configuration with the Packer and RunsOn CLI pinned in `build-config.json` |\n| `compile/cmake_helper.py` | Run CI configure, build, and CTest steps with standard arguments and timing, tee configure and build output to logs, and write the job summary's Configure (result, duration, preset, generator, configure-check reuse) and Build (result, duration, toolchain, this build's compiler and moc cache hit rates, slowest build steps) sections; after a failure, annotate and summarize the first CMake error or the failed targets and first build errors |\n| `compile/docker_helper.py` | Run Docker builds and repository scripts in builder images, and report compiler identities, timings, and cache statistics |\n| `packaging/artifact_link.py` | Link an uploaded diagnostics artifact (upload-artifact's `artifact-url`) from the job summary |\n| `packaging/find_artifact.py` | Find exactly one build artifact by glob pattern (fails on none or several), optionally copying it |\n| `packaging/validate_native_package.py` | Validate native package identity, version, contents, and installed layout |\n| `packaging/verify_executable.py` | Boot-test a built QGroundControl executable, AppImage, or DMG; keep a failed boot's output, stacks, crash reports, and minidumps for the diagnostics artifact |\n| `android/android_boot_test.py` | Run the emulator boot smoke test |\n| `android/android_build_retry.py` | Retry an Android build after a known intermittent Qt deployment-settings JSON truncation |\n| `android/android_collect_diagnostics.py` | Collect emulator, ADB, AVD, and GStreamer diagnostics |\n| `android/android_lint.py` | Run `gradlew lintRelease` in the Gradle project androiddeployqt generated, annotate the first Error and Fatal issues from lint's XML report at their `android/` sources (else Gradle's errors), and write the job summary's Android Lint section (result, duration, error and warning counts, first errors); exits with Gradle's status |\n| `android/android_sdk_helper.py` | Install the `build-config.json` Android SDK/NDK packages with retries, configure tool paths, and create the PR debug keystore |\n| `ios/ios_boot_test.py` | Run the QGC smoke test in a disposable iOS simulator within a 25-minute budget; save each failed attempt's simulator and CoreSimulator logs and crash reports, annotate the failing phase and attempt, and write the Boot test summary |\n| `ios/ios_package.py` | Select signing profiles and validate/package iOS bundles |\n| `publish/attest_helper.py` | Prepare artifacts for attestation (checksum sidecar, CPM dependency SBOM, build manifest) and decide the AWS publish; verify or write standalone checksums |\n| `publish/aws_upload.py` | Publish artifacts to S3 `builds/<ref>/` (the runner's ref by default), plus `latest/` and one CloudFront invalidation for release tags |\n| `publish/deploy_docs.py` | Deploy generated documentation to an external Pages repository |\n| `publish/pages_history.py` | Library: the Pages dashboard's history of master push builds: fetch the deployed `data/history.json` (any failure but a 404 stops the build, and so does a 404 once a `pages.yml` run has succeeded), collect each new run's result, wall time, Build-step durations, package sizes, JUnit suites and failed tests, `src/` line coverage, and unique Code Analysis warnings, newest first until the 15-minute collection deadline, with two 120-second attempts per artifact, and merge by commit and workflow within the 1,000-commit and 365-day caps |\n| `publish/pages_render.py` | Library: the Pages site's self-contained HTML pages and inline SVG charts (no scripts or external assets), each chart followed by a table of its latest values and linking every run |\n| `publish/pages_site.py` | Build the GitHub Pages site for `pages.yml`: the CI health dashboard, `schemas/` (each `$id` must be its site URL), `downloads/` of the S3 master packages with their SHA-256 sidecars and provenance commits, and `api/` from the latest master Doxygen artifact or a link to api.qgroundcontrol.com (also when its download stalls); `--new-history` (`PAGES_NEW_HISTORY`) starts a new history when the deployed one is gone; checks its pages' internal links and writes the job summary's Pages site section |\n| `publish/release_branch_check.py` | Stop `release.yml` before it drafts a release for a tag that no `origin/Stable*` branch contains |\n| `publish/release_notes.py` | Draft a tag's release notes with git-cliff (`tools/configs/cliff.toml`) and create its draft GitHub Release unless one exists |\n| `reporting/collect_build_status.py` | Collect platform, pre-commit, Code Analysis, and every other workflow's status for a head SHA (with `--status-out`, each run's state, attempt, duration and total wall time, and failed-step links and lost runners from its jobs) and save the latest runs for `download_artifacts.py` |\n| `reporting/download_artifacts.py` | Download prefix-selected artifacts from matching completed workflow runs into one directory each, with a manifest naming each artifact's workflow and whether it downloaded, and record each workflow's latest run and its distributable artifacts (size, id, expiry) |\n| `reporting/generate_build_results_comment.py` | Render the consolidated PR build-results comment: verdict, per-workflow table, other checks, the PR base's builds, failures, and collapsed tests, coverage, artifact downloads, pre-commit, and Code Analysis details, compared with the base's own builds |\n| `reporting/gh_pr_size_label.py` | Apply a pull request's `size/*` label from its changed lines |\n| `reporting/junit_report.py` | Summarize JUnit results (counts, failures, slowest tests) in the job summary, annotate failed tests and, optionally, slow tests (parsed with `common/junit.py`, like the build-results comment and the Pages dashboard) |\n| `reporting/report_context.py` | Resolve a pull_request build's PR and base commit, and with `--comment` recheck that the build is current and create or update the PR's build-results comment |\n| `reporting/setup_summary.py` | Write the job summary's Setup section for `build-setup`, also after a failed setup step, which it names: runner, image, RunsOn instance, CPUs and RAM, Qt (version, installer, source), CMake, compiler, Xcode, JDK, Android NDK, Python groups, and each build cache's restore result |\n| `reporting/size_analysis.py` | Analyze and report binary-size changes |\n| `reporting/verify_coverage_thresholds.py` | Enforce line and branch coverage thresholds (read from `CMakeCache.txt` with `--build-dir`) |\n| `reporting/workflow_runs.py` | Library: read the triggering `workflow_run` payload and the configured platform workflows, which default the reporting scripts' run-query arguments; validate, select, and group workflow runs; load run snapshots |\n| `gstreamer/gstreamer_archive.py` | Package GStreamer builds and optionally upload them to S3 |\n| `gstreamer/mirror_gstreamer.py` | Verify and mirror upstream GStreamer release artifacts to S3 (`mirror-gstreamer.yml` sets its options through `MIRROR_*` variables) |\n| `security/generate_cpm_sbom.py` | Generate a CycloneDX or SPDX SBOM from CPM package metadata and the bundled GStreamer |\n| `security/install_grype.py` | Install checksum-verified Grype release assets into the scan action tool cache |\n\nThe script index and one-to-one production test coverage are checked by\n[`tools/tests/ci/contracts/test_scripts_docs.py`](../tests/ci/contracts/test_scripts_docs.py).\n\n## Shared Code and Bootstrapping\n\nEntrypoints bootstrap like `tools/setup/` scripts: insert the `tools/` directory into `sys.path`\nwith one bare statement, import `_bootstrap` for the CI debug hooks, then import from the module\nthat owns the symbol ([details](../common/README.md#bootstrapping-imports)). Scripts import each\nother as package modules:\n\n```python\nsys.path.insert(0, str(Path(__file__).resolve().parents[2]))\n\nimport _bootstrap as _bootstrap\nfrom common.gh_actions import write_github_output\nfrom qgc_tools.release_tag_check import check_release_tag\n\nfrom ci.planning.detect_changes import detect_builds\n```\n\n`ci` is the top import-linter layer: it may import the other `tools/` packages, and none of them may\nimport it. Its subpackages are independent of each other (only `publish/attest_helper.py` reuses\n`security/generate_cpm_sbom.py`); move code that several of them need into `common` or `qgc_tools`.\nKeep package `__init__.py` files import-free. Sparse-checkout jobs must include the\nentrypoint, `tools/_bootstrap.py`, imported `ci` modules with their package `__init__.py` files, and\ntransitive `common` and `qgc_tools` modules; `test_bootstrap_sparse_checkout.py` verifies that\nclosure.\n\n`cache/cache_backend.py`, `runner/apt_install_retry.py`, `runner/enable_kvm.py`,\n`runner/install_multipass.py`, `runner/runner_image.py`, `packaging/validate_native_package.py`,\nand `ios/ios_boot_test.py` are standalone: they import only the standard library and skip the\nbootstrap. `runner-images.yml` checks out `runner_image.py` by itself, so it must stay that way.\n\nScripts that run before `setup-python` use the runner's system interpreter and must import only the\nstandard library and stdlib-only `tools/` modules. `PRE_SETUP_PYTHON_SCRIPTS` in\n[`tools/tests/test_stdlib_only.py`](../tests/test_stdlib_only.py) lists them; the test imports each\none under `python -I -S` and fails when a job without `setup-python` runs a script missing from the\nlist. `setup-python` puts `tools/.venv` first on `PATH`; on Windows `install_python.py` also copies\nthe venv's `python.exe` to `python3.exe`, so later `python3` steps run in the venv on every OS.\nAs a fallback for a runner whose `PATH` still prefers its own interpreter, the test also holds every\nscript that a composite action runs as `python3` to the same rule; `_VENV_ACTION_SCRIPTS` exempts\nthe few that need third-party packages.\n\n## Tests\n\nAdd `tools/tests/ci/test_<script>.py` with every new production script; every test there must match\na script. Entry scripts define `main(argv: list[str] | None = None) -> int`\n(`tools/tests/test_bootstrap.py` checks the signature), so tests call `main([...])` instead of\npatching `sys.argv`. Tests of workflows, composite actions, presets, and deploy files go in\n`tools/tests/ci/contracts/`. Load repository YAML through `tools/tests/_helpers.py`\n(`load_workflow()`, `load_action()`, `load_yaml()`, `get_steps()`, `get_step()`): it parses each\nfile once with libyaml, keys workflow triggers under `\"on\"`, and returns a private copy;\n`workflow_names()` and `action_names()` enumerate them. Build temporary git repositories with the\n`git_repo` fixture, `_helpers.git()` and `commit_files()`, which ignore user and system git\nconfiguration. Point `GITHUB_OUTPUT`, `GITHUB_ENV`, `GITHUB_PATH` and `GITHUB_STEP_SUMMARY` at\nempty temporary files with the `gh_output`, `gh_env`, `gh_path` and `gh_summary` fixtures, and\nparse what a script wrote with `read_github_file()`, which also reads `name<<DELIMITER` blocks.\n`workflow_run_event(**fields)` points `GITHUB_EVENT_PATH` at a `workflow_run` payload.\nImport scripts outside the `tools/` packages with `load_script()`. A test reads the repository files\nit checks directly, without skipping when they are missing: a file absent from a CI sparse checkout\nfails the test. Mark the tests of each script that `windows.yml` runs on Windows, and of other\nWindows-specific code paths, with `pytestmark = pytest.mark.windows`; the Windows CI job runs\n`pytest -n 2 --durations=15 -m windows tools/tests` (two xdist workers are a trial; revert to\nserial if CI shows it slower).\nRun the same suite as CI (`pytest -q -n auto tools/tests` in the locked `tools/.venv`):\n\n```bash\njust test-python\n```\n\nTests run in parallel (pytest-xdist) and in random order (pytest-randomly, which also reseeds\n`random`), so a test must not depend on another test's side effects. The run ends with\n`Reproduce the test order with --randomly-seed=N`; replay that order serially with\n`just test-python -n 0 --randomly-seed=N`, or run in file order with `-p no:randomly`.\n`tools/tests/conftest.py` restores `os.environ` after each test, and for the whole session removes\nthe runner's context (`CI`, `GITHUB_*`, `RUNNER_*`, `ACTIONS_*`, `RUNS_ON_*`, `QGC_CI_DEBUG`,\n`QGC_ACTIONS_CACHE_BACKEND`), so tests behave the same in CI as locally and never write to the\njob's real output or summary files; a test that needs one of these variables sets it.\npytest-timeout fails any test that runs longer than 60 s. The `test` group also provides:\n\n- pytest-subprocess's `fp` fixture for sequences of faked commands (`fp.register()`); prefer it\n  over patching `subprocess.run` when the code under test runs real commands.\n- Hypothesis for property tests (`@given`); `conftest.py` disables its per-example deadline.\n  Hypothesis keeps failing examples in `.cache/hypothesis/` (set in `conftest.py`) and replays them\n  first on the next run.\n- syrupy's `snapshot` fixture for rendered output; snapshots live in `__snapshots__/*.ambr` next\n  to the test. After an intended change, refresh them with `--snapshot-update` and review the diff.\n\n<!-- File: tools/coding-style/README.md (Tokens: ~361 | Category: plugin-manifest) -->\n# QGroundControl Coding Style Examples\n\nReference files demonstrating QGroundControl's coding conventions. For the authoritative rules\n(naming, include order, C++20, QML, logging), see **[CODING_STYLE.md](../../CODING_STYLE.md)** — these\nfiles are worked examples of it.\n\n## Files\n\n| File | Demonstrates |\n| --- | --- |\n| `CodingStyle.h` | Header conventions: `#pragma once`, include order, forward declarations, `Q_DECLARE_LOGGING_CATEGORY`, QML macros, naming |\n| `CodingStyle.cc` | Implementation conventions: include order, `QGC_LOGGING_CATEGORY`, C++20 features, const correctness |\n| `CodingStyle.qml` | QML conventions: import/property order, naming, declarative bindings |\n\n## Related Documentation\n\n- **[CODING_STYLE.md](../../CODING_STYLE.md)** - Full coding style guide\n- **[.clang-format](../../.clang-format)** - Automatic C++ formatting rules\n- **[.clang-tidy](../../.clang-tidy)** - Static analysis configuration\n\n## Formatting & Static Analysis\n\n```bash\n./tools/analyze.py --tool clang-format --fix        # Format lines changed since the default branch\n./tools/analyze.py --tool clang-format              # Check changed lines (same rule as the pre-commit hook)\n./tools/analyze.py --tool clang-format --fix --all  # Reformat whole files (dedicated formatting change only)\n./tools/analyze.py                                   # clang-tidy on changed files\n./tools/analyze.py --all                             # clang-tidy on all files\n```\n\nOr use the `just` wrappers (`just format-fix`, `just analyze`) — see [tools/README.md](../README.md).\n\n<!-- File: tools/common/README.md (Tokens: ~1170 | Category: plugin-manifest) -->\n# QGC Shared Tooling\n\n`tools/common/` contains small, dependency-light helpers shared by QGC developer and CI scripts.\nImport from the module that defines a helper; `common/__init__.py` intentionally does not re-export\nsymbols. Explicit imports keep runtime and sparse-checkout dependencies visible.\n\n```python\nfrom common.file_traversal import find_repo_root\nfrom common.proc import run_captured\n```\n\n## Module Index\n\n| Module | Purpose |\n| ------ | ------- |\n| `analysis_diagnostics.py` | analyze.py's `*-diagnostics.json` reports: each tool's unique warnings across shards, and those on changed lines |\n| `aws.py` | Allowlisted public S3 object checks and uploads |\n| `build_config.py` | `.github/build-config.json` lookup, validation, and CI export |\n| `cmake.py` | CMake cache variable parsing |\n| `coverage_summary.py` | gcovr JSON summary line, function, and branch coverage metrics |\n| `file_traversal.py` | Repository-root discovery and filtered C++ file traversal |\n| `format.py` | Human-readable byte, size-delta, count, duration, percentage, and ratio formatting, and one-line clipped names (`clip`) |\n| `gh_actions.py` | GitHub CLI calls, read-only API retries and JSON reads, workflow artifact archive downloads, GitHub timestamps and durations, annotations (with optional `title`, `file`, `line`, and `col`), log groups, outputs, summaries, and CI detection |\n| `git.py` | Captured Git commands, default-branch discovery, pull request diff refs, diff bases, and changed line ranges |\n| `gstreamer.py` | GStreamer SDK package and mirror URLs and their pinned checksums |\n| `io.py` | JSON/TOML I/O, checksums, atomic writes, and safe archive extraction |\n| `junit.py` | CTest JUnit XML test cases (outcome, duration, failure excerpt) and suite wall time |\n| `logging.py` | Color-aware terminal logging |\n| `markdown.py` | GitHub-Flavored Markdown tables, code spans and blocks, `<details>` blocks, untrusted-text escaping, and the shared job-summary section layout (`summary_section`) |\n| `net.py` | Dependency-free atomic downloads and small fetches with optional SHA-256 verification; retries only transient failures (connection errors, timeouts, 5xx, 408, 429), so a 404 fails at once; `fetch_release_member` extracts one member from a pinned release archive |\n| `paths.py` | `TOOLS_DIR` and `REPO_ROOT`, resolved from the module location |\n| `platform.py` | OS and CPU architecture normalization |\n| `proc.py` | Captured, tee (through Bash, or a live-flushed Python pipe), and bounded-retry subprocess execution (optionally killing a timed-out attempt's whole process tree, `kill_process_tree`; the final failure logs the command and the end of its captured stderr), bytes-or-text output decoding, `require_tools` (`MissingToolError` names every tool missing from `PATH`), and deadline polling with `wait_until` |\n| `qt.py` | Qt installer-kit discovery for host tools such as `qt-cmake` and `lupdate` |\n| `tool_version.py` | External-tool and `uv.lock` version lookup |\n| `xml.py` | Safe XML parsing with entity-declaration rejection |\n\nAPI behavior and edge cases are covered by matching files under `tools/tests/`, such as\n`test_proc.py`, `test_io.py`, and `test_gh_actions.py`.\n\n## Bootstrapping Imports\n\nTop-level `tools/*.py` scripts run with `tools/` as `sys.path[0]` and import `common` directly.\nEntry scripts in subdirectories (`tools/setup/`, `tools/ci/<package>/`, ...) first insert `tools/`\nwith one bare statement, which Ruff's import-position rule (E402) accepts:\n\n```python\nsys.path.insert(0, str(Path(__file__).resolve().parents[2]))  # tools/ci/<package>/x.py\n\nimport _bootstrap as _bootstrap\nfrom common.proc import run_captured\n```\n\n`import _bootstrap as _bootstrap` installs the CI debug hooks (see\n[`tools/README.md`](../README.md#python-script-conventions)); the redundant alias marks the unused\nimport as intentional. Library modules without a `__main__` block do not touch `sys.path`.\n`tools/tests/test_bootstrap.py` enforces this shape.\n\nCI jobs that use sparse checkout must include the entrypoint, `tools/_bootstrap.py`, and transitive\n`ci`, `common`, and `qgc_tools` modules. `test_bootstrap_sparse_checkout.py` checks that closure\nautomatically.\n\n## Adding or Reusing Helpers\n\nBefore adding a helper, search this directory and its call sites. Add shared code only when it has\nmultiple consumers or centralizes a correctness boundary such as safe extraction, retries, GitHub\noutput encoding, or platform normalization.\n\nWhen a new helper is warranted:\n\n1. Put it in the narrowest existing module; create a module only for a distinct concern.\n2. Keep imports dependency-light and defer optional packages until the function that needs them.\n3. Export the supported surface through the module's `__all__` when it has one.\n4. Add focused tests under `tools/tests/`.\n5. Run `just test-python` and `just lint` (Ruff, Pyright, and import-linter run as pre-commit hooks).\n6. Update sparse-checkout lists if the automatic policy test reports a missing module.\n\n<!-- File: tools/debuggers/gdb-pretty-printers/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# gdb-pretty-printers Documentation\n\nPath: `tools/debuggers/gdb-pretty-printers/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/debuggers/gdb-pretty-printers/README.md)\n\n<!-- File: tools/generators/config_qml/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# config_qml Documentation\n\nPath: `tools/generators/config_qml/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/config_qml/README.md)\n\n<!-- File: tools/generators/settings_qml/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# settings_qml Documentation\n\nPath: `tools/generators/settings_qml/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/generators/settings_qml/README.md)\n\n<!-- File: tools/qgc_tools/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# qgc_tools Documentation\n\nPath: `tools/qgc_tools/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/qgc_tools/README.md)\n\n<!-- File: tools/README.md (Tokens: ~9537 | Category: plugin-manifest) -->\n# QGroundControl Tools\n\nDevelopment tools, scripts, and configuration files for QGroundControl. This is the full\nreference for every `just` recipe and standalone script; [AGENTS.md](../AGENTS.md) has the\ncondensed day-to-day workflow.\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Directory Structure](#directory-structure)\n- [Just Command Reference](#just-command-reference)\n- [Direct Script Usage](#direct-script-usage)\n- [Development Scripts](#development-scripts)\n- [Setup Scripts](#setup-scripts)\n- [Python Environment](#python-environment)\n- [Python Script Conventions](#python-script-conventions)\n- [Static Analyzers](#static-analyzers)\n- [Debugging Tools](#debugging-tools)\n- [Other Tool Areas](#other-tool-areas)\n- [Code Quality Tools](#code-quality-tools)\n- [VS Code Integration](#vs-code-integration)\n- [Centralized Configuration](#centralized-configuration)\n\n## Quick Start\n\nInstall uv, then the locked `dev` Python profile into `tools/.venv`; the profile includes `just`:\n\n```bash\n# Linux/macOS; on Windows install uv from https://docs.astral.sh/uv/getting-started/installation/\npython3 tools/setup/install_uv.py\nexport PATH=\"$HOME/.local/bin:$PATH\"\n\npython3 tools/setup/install_python.py dev   # Windows: python tools/setup/install_python.py dev\n. tools/.venv/bin/activate                  # PowerShell: . tools/.venv/Scripts/Activate.ps1\n```\n\nA separately installed `just` (`brew install just`, `cargo install just`) must be 1.48 or newer\nbecause the justfile uses `set lazy`; Ubuntu's `apt install just` ships 1.21, which is too old.\n\nEveryday loop:\n\n```bash\njust             # List all recipes\njust setup       # First-time: deps + configure + build\njust build       # Incremental build\njust check       # lint + Python tooling tests + application tests — run before declaring done\n```\n\n## Directory Structure\n\n```text\ntools/\n├── _bootstrap.py            # Import bootstrap for entry scripts (tools/ on sys.path, CI debug hooks)\n├── analyze.py               # Static analysis and formatting (clang-format, clang-tidy, clazy, qmllint, qmlformat)\n├── build_profile.py         # Summarize Ninja and Clang time-trace build hotspots\n├── check_deps.py            # Check for outdated dependencies\n├── clean.py                 # Clean build artifacts and caches\n├── configure.py             # CMake configuration wrapper\n├── coverage_report.py       # Code coverage reports\n├── moccache_launcher.py     # moccache entry point: the CMake AUTOMOC launcher and the stats/trim CLI\n├── pre_commit.py            # Pre-commit runner with CI summaries\n├── terrain_tiles.py         # Terrarium elevation tiles from Copernicus GLO-30 (GeoMap)\n├── update_px4_metadata.py   # Refresh the bundled PX4 airframe and parameter metadata\n├── validate_cmake_presets.py # CMake presets against the locked cmake package's schema (hook)\n├── validate_configs.py      # Cross-field checks for build-config.json and link exceptions\n├── pyproject.toml, uv.lock  # Locked Python tooling groups\n├── analyzers/               # analyze.py backends and QGC-specific analyzers\n├── ci/                      # GitHub Actions entrypoints, grouped by CI concern\n├── coding-style/            # Code style examples\n├── common/                  # Shared Python utilities\n├── configs/                 # Tool configuration files\n│   ├── ccache.conf          # ccache configuration\n│   ├── cliff.toml           # git-cliff release-notes sections (just release-notes)\n│   ├── Doxyfile             # Doxygen API docs, run from the repository root\n│   ├── editorconfig-checker.json # editorconfig-checker exclusions and relaxed checks\n│   ├── gersemirc.yaml       # gersemi CMake formatter trial (manual hook)\n│   ├── lychee.toml          # Link checker settings and dated URL exceptions\n│   ├── trufflehog-exclude.txt # Paths the trufflehog pre-commit hook skips\n│   └── yamlfmt.yaml         # yamlfmt YAML formatter trial (manual hook)\n├── debuggers/               # Debugger formatters, profiling, Android memory capture, Valgrind suppressions\n├── generators/              # Code generation (MAVLink enums, config/settings QML)\n├── moccache/                # Content-addressed cache for Qt moc (AUTOMOC wrapper)\n├── qgc_tools/               # Shared policy: Python environment, configure presets, Docker variants\n├── setup/                   # Environment setup scripts\n├── simulation/              # Vehicle simulators\n├── terrain/                 # Terrain tile generation package (see terrain_tiles.py)\n├── tests/                   # pytest suite for tools/ (CI script and contract tests in tests/ci/)\n└── translations/            # Translation tools (lupdate, JSON extraction, pseudo-localization)\n```\n\n## Just Command Reference\n\nAll recipes are defined in [`../justfile`](../justfile), grouped by the sections below; run `just`\nwith no arguments for the same list. Most recipes run Python through\n`uv run --frozen --project tools`. `BUILD_TYPE` (default `Debug`), `JOBS` (default: all cores),\nand `QT_DIR` override the build recipes; `just info` prints them with the Qt, CMake, and GStreamer\nversions from `.github/build-config.json` ([Centralized Configuration](#centralized-configuration)).\n\n### Setup\n\n| Recipe            | Description                                                    |\n| ----------------- | -------------------------------------------------------------- |\n| `just deps`       | Auto-detect and install system build dependencies              |\n| `just vscode`     | Install missing VS Code workspace files from tracked templates |\n\n### Build\n\n| Recipe              | Description                                                                                      |\n| ------------------- | ------------------------------------------------------------------------------------------------ |\n| `just configure`    | Configure through the matching `default*` CMake preset (Debug by default)                        |\n| `just build`        | Build the project (uses all cores by default; override with `JOBS=N`)                            |\n| `just release`      | Configure and build in Release mode (testing disabled)                                           |\n| `just clean [ARGS]` | Clean the build directory; forwards `ARGS` to `tools/clean.py` (`--cache`, `--all`, `--dry-run`) |\n| `just rebuild`      | `clean` + `configure` + `build`                                                                  |\n| `just setup`        | Full first-time setup: `deps` + `configure` + `build`                                            |\n\n### Quality\n\n| Recipe                  | Description                                                                                           |\n| ----------------------- | ----------------------------------------------------------------------------------------------------- |\n| `just test`             | Run application tests via CTest; defaults to labels `Unit`/`Integration`, excluding `Flaky`/`Network` |\n| `just test-python`      | Run the Python tooling tests (`tools/tests`, incl. `tools/tests/ci`) in parallel, in random order     |\n| `just lint [ARGS]`      | Run non-manual hooks on all files with prek (`prek run --all-files`); `ARGS` select hooks/stages      |\n| `just lint-docs [ARGS]` | Lint English docs with only the docs hooks (every page; `--changed` for branch changes)               |\n| `just format`           | Check clang-format on lines changed since the default branch (no changes)                             |\n| `just format-fix`       | Apply clang-format to lines changed since the default branch                                          |\n| `just analyze`          | Run static analysis (`tools/analyze.py`, default tool clang-tidy)                                     |\n| `just coverage`         | Build with coverage instrumentation, run tests, generate report                                       |\n| `just check`            | `lint` + `test-python` + `test` — run before declaring a task done                                    |\n\nOverride `test` label filters via environment variables or positional args:\n\n```bash\njust test                              # LABELS=Unit|Integration EXCLUDE=Flaky|Network (defaults)\nLABELS=\"Slow\" just test                # override via env var\njust test \"Slow\" \"Network\"             # override via positional args (labels, exclude)\n```\n\n### Run & Deploy\n\n| Recipe        | Description                                                                    |\n| ------------- | ------------------------------------------------------------------------------ |\n| `just run`    | Launch the built `QGroundControl` binary                                       |\n| `just docs`   | Build the VitePress documentation site (`npm run docs:build` in `docs/`)       |\n| `just docker` | Build the `ubuntu` Docker variant (`deploy/docker/run_docker.py build ubuntu`) |\n\n### Utilities\n\n| Recipe                   | Description                                                                                           |\n| ------------------------ | ----------------------------------------------------------------------------------------------------- |\n| `just info`              | Print resolved build configuration (Qt version/dir, CMake min, GStreamer, jobs, ...)                  |\n| `just check-deps`        | Check Qt, GStreamer, Python, and build tool versions (`tools/check_deps.py`)                          |\n| `just check-gstreamer`   | Check the latest GStreamer patch common to all SDK platforms                                          |\n| `just translations`      | Update translation sources (`tools/translations/qgc_lupdate.py`)                                      |\n| `just distclean`         | Clean build, caches, generated files, and `node_modules/`                                             |\n| `just validate-configs`  | Validate build settings and link exceptions ([Centralized Configuration](#centralized-configuration)) |\n| `just release-notes TAG` | Print draft release notes for a `vX.Y.Z` tag ([Release notes](#release-notes))                        |\n| `just lint-qml-build`    | Lint all QML against the build's generated type information ([analyze.py](#analyzepy))                |\n\n## Direct Script Usage\n\nPrefer `just` recipes; call the scripts below directly for flags a recipe doesn't expose. Run them\nfrom the repository root with `tools/.venv` activated ([Quick Start](#quick-start)), or prefix the\ncommand with `uv run --frozen --project tools --group dev` instead. The `dev` profile covers every\nscript here.\n\nRepository configure/build/test/workflow presets are rooted at [`../CMakePresets.json`](../CMakePresets.json).\nUse `CMakeUserPresets.json` for machine-local paths or derived presets; it is ignored by Git and\npreserved by `just clean`. `tools/configure.py` (behind `just configure`) requires the Qt kit's\n`qt-cmake`, found through `--qt-root` (`QT_DIR` for `just`), `QT_ROOT_DIR`, or the newest standard\nQt install for the host architecture. Use `--no-qt-cmake` only when supplying an explicit target\ntoolchain, as Android CI does. `configure.py` configures into the selected preset's `binaryDir`,\nwhere `cmake --build --preset` and `ctest --preset` look for the tree; `-B` overrides it.\n\n## Development Scripts\n\n### analyze.py\n\nRun static analysis and formatting on source code. Underlies `just format`, `just format-fix`,\n`just analyze`, and `just lint-qml-build`.\n\n```bash\npython tools/analyze.py                              # Analyze changed files (default tool: clang-tidy)\npython tools/analyze.py --all                        # Analyze all files\npython tools/analyze.py --tool clang-format --fix    # Format changed lines\npython tools/analyze.py --tool clang-format --all    # Check formatting (all files)\npython tools/analyze.py src/Vehicle/                 # Analyze specific directory\npython tools/analyze.py --tool clang-tidy --tool clazy --cache-dir .cache/analysis-results  # Both, one scan\npython tools/analyze.py --tool clang-tidy --all --shard 1 --shard-count 4  # One of four disjoint partitions\npython tools/analyze.py --tool clang-tidy --profile-checks src/Vehicle/Vehicle.cc  # Slower per-check profiling\npython tools/analyze.py --tool clang-tidy --fix src/Vehicle/  # Apply clang-tidy fixes (local only)\n```\n\nPaths are relative to the repository root; absolute paths inside the checkout also work.\n\nOther `--tool` choices: `clazy`, `qmllint`, `qmlformat` (reports what `qmlformat` would change,\nnever rewrites), `vehicle-null-check`, `qt-translate-noop-check`.\nWithout paths or `--all`, files changed since the merge base with the default branch (or\n`PR_BASE_SHA`; in GitHub's merge checkout of `PR_HEAD_SHA`, its first parent) are analyzed,\nincluding uncommitted and untracked files; without a usable base every file is. For clang-format the\nselection narrows to changed lines, matching the pre-commit hook: deletion-only changes are skipped\nand new or untracked files are checked in full. `--all` or explicit paths check (or `--fix`) whole\nfiles for intentional reformatting. `--advisory` keeps warnings informational; error-level\ndiagnostics and tool or compiler invocation errors still fail.\n\n**Compiler analysis** (clang-tidy, Clazy) reads `compile_commands.json` from `--build-dir`\n(default `build`) and needs generated headers, not a full build. Configure Ninja with\n`CMAKE_EXPORT_COMPILE_COMMANDS=ON`, `CMAKE_DISABLE_PRECOMPILE_HEADERS=ON`,\n`CMAKE_AUTOGEN_ORIGIN_DEPENDS=OFF`, `CMAKE_GLOBAL_AUTOGEN_TARGET=ON`, and `QGC_UNITY_BUILD=OFF`,\nthen build only `cmake --build <dir> --target qgc-analysis-headers` (`autogen` is needed only for\nIWYU). Repeating `--tool` shares one file selection and dependency scan; `--cache-dir <dir>`\nreplays unchanged results and needs `clang-scan-deps`. `tools/setup/build_clazy.py` builds the\nClazy that CI uses. `--tool clang-tidy --fix` applies the selected translation units' fixes with\n`clang-apply-replacements` ([analyzers/README.md](analyzers/README.md#clang-tidy-fixes)); CI never\napplies fixes. The clang-tidy and Clazy pre-commit hooks run only with `--hook-stage manual`;\nfast hooks remain the required PR gate. CI sharding and result caching are described in\n[ci-overview.md](../.github/ci-overview.md); output files and profiling in\n[analyzers/README.md](analyzers/README.md).\n\n**QML:** `--tool qmllint --qml-build` checks QML against the generated `<build-dir>/qml` type\ninformation after a build. Missing imports, properties, and types are errors; the fast\nsource-only hook keeps its limited checks. `just lint-qml-build` runs it on every QML file with\n`--advisory`.\n\n### pre_commit.py\n\nRuns the [`.pre-commit-config.yaml`](../.pre-commit-config.yaml) hooks with\n[prek](https://github.com/j178/prek) and CI-friendly summaries; the pre-commit workflow calls it\nwith `--ci --changed --results-dir`. A `--changed` run whose docs lint configuration changed also\nruns the docs hooks over every English page.\n\n```bash\npython tools/pre_commit.py --changed                             # Files changed vs PR_BASE_SHA or the default branch\npython tools/pre_commit.py --changed --hook ruff --hook pyright  # Only these hooks (repeatable)\npython tools/pre_commit.py --docs --changed                      # English docs with only the docs hooks (just lint-docs)\npython tools/pre_commit.py --changed --needs-qt                  # Whether the selection has QML (qmllint needs Qt)\npython tools/pre_commit.py --install                             # Install the pre-commit and commit-msg hooks\npython tools/pre_commit.py --prepare-cache                       # Install the hook environments CI caches, prune the rest\n```\n\nThe selected hooks run in one `prek run` call; its output streams to the console and, with\n`--results-dir`, into `pre-commit-output.txt`. `--ci` skips the local-only `trufflehog` hook unless\n`SKIP` is already set. `--prepare-cache` installs every hook environment except\n`CACHE_SKIPPED_HOOKS` (TruffleHog and the Renovate validator) and then runs `prek cache gc`; the\npre-commit workflow runs it on pushes that miss the prek cache, and saves the cache only when the\ninstall succeeded.\n\nThe `precommit` group pins prek, which also accepts pre-commit's commands and options\n(`prek run <id> --hook-stage manual --files <paths>`, `SKIP=<id>`). The configuration stays\ncompatible with stock pre-commit, which the group keeps as a fallback runner for one release.\n`editorconfig-checker` enforces [`.editorconfig`](../.editorconfig) with the exclusions and relaxed\nchecks in `configs/editorconfig-checker.json`; `cmake-presets-schema` validates `CMakePresets.json` and\n`cmake/presets/*.json` offline against the schema shipped in the locked `cmake` package.\nManual-stage hooks run only on request: `clang-tidy`, `clazy`, the CI validators that download pinned\ntools (`renovate-config-validator`, `packer-validate`, `runs-on-lint`, which `pre_commit.py --ci` also\nruns on the changed files), and\nthree formatter trials that report differences without rewriting files: `gersemi` (CMake,\n`configs/gersemirc.yaml`), `yamlfmt` (`configs/yamlfmt.yaml`), and `qmlformat` (`analyzers/qmlformat.py`,\n`.qmlformat.ini`). For example, `just lint gersemi --hook-stage manual` checks every CMake file.\n\n### clean.py\n\nClean build artifacts and caches. Underlies `just clean` and `just distclean`.\n\n```bash\npython tools/clean.py              # Clean build directory\npython tools/clean.py --all        # Clean everything (build, caches, node_modules/, Python bytecode outside venvs)\npython tools/clean.py --cache      # Clean only repo-local caches (.cache, incl. ccache and the clangd index, plus a legacy .ccache)\npython tools/clean.py --dry-run    # Show what would be removed\n```\n\n### build_profile.py\n\nSummarize build-time hotspots from Ninja logs and optional Clang time traces. The parsing and report\ncode lives in `qgc_tools/build_profile.py`, which `ci/compile/cmake_helper.py` and\n`ci/compile/docker_helper.py` also use.\n\n```bash\npython tools/build_profile.py -B build                 # Report slowest Ninja edges and rebuild churn\npython tools/build_profile.py -B build --limit 25      # Show more rows per section\npython tools/build_profile.py -B build --json          # Machine-readable output\npython tools/build_profile.py -B build --json-out r.json > r.txt  # Text and JSON from one scan\npython tools/build_profile.py -B build --output-dir build/profile  # Ninja-only snapshot\npython tools/build_profile.py -B build --step-summary --extra-report \"Title=other.txt\"  # Plus a job-summary section\npython tools/build_profile.py --install-clang-build-analyzer .cache/ClangBuildAnalyzer  # Pinned binary (Linux x86_64)\n```\n\nFor per-translation-unit trace details, configure with `-DQGC_TIME_TRACE=ON`, rebuild, then rerun\nthe report — it scans the build dir for Clang `-ftime-trace` JSON and highlights the slowest events.\n\nOrdinary CI builds upload Ninja-only snapshots with separate link and generated-code rankings.\nTask totals overlap because commands run in parallel; they are not elapsed build time. The manual\nBuild Profile workflow installs the ClangBuildAnalyzer release pinned (with its SHA-256) in\n`.github/build-config.json`'s `tools.clang_build_analyzer`, and `--step-summary` writes its job\nsummary: build type, commit, and compiler, with this report and each `--extra-report` collapsed.\n\n### moccache/\n\nContent-addressed cache for Qt's moc, wired in through `AUTOMOC_EXECUTABLE` while the\n`QGC_USE_MOCCACHE` CMake option is ON (the default). The generated launcher runs\n`moccache_launcher.py`, which imports the `moccache` package so its bytecode is cached in\n`tools/moccache/__pycache__`. A moc run loads only `core.py`; `trim.py` (LRU eviction) and `stats.py`\n(hit/miss reports) are imported only for their subcommands or a due auto-trim, keeping each cache\nhit's startup short. The stats and trim commands run through the launcher too\n(`python3 tools/moccache_launcher.py --show-stats ...`).\nMisses fall through to the real moc and never fail the build. The\n[dev guide Build Caching section](../docs/en/qgc-dev-guide/getting_started/index.md#build-caching)\ncovers the cache key, environment variables, and stats commands. Tests:\n`tools/tests/test_moccache.py`.\n\n### coverage_report.py\n\nGenerate code coverage reports through the CMake coverage targets. Underlies `just coverage`.\nConfigures the `Linux-coverage` preset's `build/Linux-coverage/` directory (override the directory\nwith `-b`/`--build-dir`). Requires `gcovr` (`coverage` group, included in `dev`).\nCoverage compiles pass `-fprofile-update=atomic`, which ccache caches only from 4.10; an older\ndistro ccache (Ubuntu 24.04 ships 4.9.1) gets no cache hits in coverage builds.\nReports are Cobertura `coverage.xml` (uploaded to Codecov), gcovr's JSON `coverage-summary.json`\n(read by the step summary, `verify_coverage_thresholds.py`, and the PR build-results comment), and\nHTML `coverage.html`. Reporting fails when the build directory holds no `.gcda` counters, so tests\nthat wrote no coverage data cannot publish a zero-coverage report.\n\n```bash\npython tools/coverage_report.py              # Build with coverage, run tests like CI, generate XML, JSON + HTML\npython tools/coverage_report.py --report     # Generate report only from existing data (CI adds --step-summary)\npython tools/coverage_report.py --open       # Full run, then open the HTML report\npython tools/coverage_report.py --clean      # Delete the coverage build directory\n```\n\n**Direct CMake usage:**\n\n```bash\ncmake -B build -DQGC_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug\ncmake --build build\nctest --test-dir build\ncmake --build build --target coverage-report  # XML, JSON summary + HTML from existing data\ncmake --build build --target coverage         # Run Unit tests + generate XML, JSON summary + HTML\ncmake --build build --target coverage-clean   # Remove coverage data and reports\n```\n\n`coverage_report.py` runs CI's two test phases through the `Linux-coverage` test preset, which\nexcludes `Flaky` and `Network`: Unit tests at one process per CPU, then Integration tests at `-j2`,\nbecause instrumented integration tests time out at full parallelism. The CMake `coverage` target\nruns only the Unit tests.\n\n### check_deps.py\n\nCheck for outdated dependencies. Underlies `just check-deps`.\n\n```bash\npython tools/check_deps.py              # Check all dependencies\npython tools/check_deps.py --qt         # Check Qt version\npython tools/check_deps.py --gstreamer  # Find latest patch common to all SDK platforms\n```\n\n### API documentation (Doxygen)\n\nRun Doxygen from the repository root, as the Doxygen workflow does; HTML goes to `docs/api/html/`.\nThe workflow builds and deploys weekly; pull requests run it only when they change the Doxyfile or\nthe workflow, because warnings do not fail the build. Master runs keep the HTML artifact for eight\ndays, and the Pages workflow publishes the latest under <https://mavlink.github.io/qgroundcontrol/api/>.\n\n```bash\ndoxygen tools/configs/Doxyfile\n```\n\nRequires `doxygen`; `graphviz` enables diagrams.\n\n### Release notes\n\nRelease tags are created and pushed by hand. `just release-notes vX.Y.Z` prints the notes that\n`release.yml` drafts for that tag: git-cliff (`release` group) lists the commits since the previous\n`vX.Y.Z` tag reachable from it, grouped by type as configured in `configs/cliff.toml`, as full\nConventional Commits subjects under `## Changes in vX.Y.Z`. For a tag that does not exist yet it\nlists the unreleased commits since the last release reachable from `HEAD`.\n\n```bash\njust release-notes v5.1.5                                # Notes for an existing tag\npython tools/ci/publish/release_notes.py v5.2.0          # Same script; v5.2.0 not tagged yet\n```\n\n### PX4 metadata\n\n`px4-metadata.yml` runs `update_px4_metadata.py` daily and opens a pull request when the bundled PX4\nairframe (`AirframeFactMetaData.xml`) or parameter (`PX4ParameterFactMetaData.json`) metadata\nchanges upstream. It retries both downloads from artifacts.px4.io and replaces neither file unless\nboth are valid: well-formed airframe XML without a DTD, and xz-compressed JSON that lists parameters.\n\n```bash\npython3 tools/update_px4_metadata.py\n```\n\n## Setup Scripts\n\nScripts in `setup/` configure development environments and read versions from\n`.github/build-config.json`. They need only the standard library, so they also run with the host\n`python3` before `tools/.venv` exists. `install_dependencies` is a package\n(`tools/setup/install_dependencies/`) run through its `__main__.py`; without `--platform` it\ndetects the host. `build_gstreamer.py` is the entry for the `setup/gstreamer/` package: `config`\n(build settings, Meson options and plugins), `host` (commands, host arch and jobs), `meson` (Linux,\nmacOS, Windows), `cerbero` (Android, iOS) and `cli` (arguments, CI defaults and arch/prefix\noutputs). The Build GStreamer workflow first runs `setup/gstreamer/ci_plan.py`, which rejects an\narchitecture the dispatch's runner cannot build and outputs the runner's Qt and apt category.\n\n| Script                                    | Platform              | Description                                                                            |\n| ----------------------------------------- | --------------------- | -------------------------------------------------------------------------------------- |\n| `install_dependencies --platform debian`  | Linux (Debian/Ubuntu) | Install build dependencies via apt                                                     |\n| `install_dependencies --platform fedora`  | Linux (Fedora/RHEL)   | Install build dependencies via dnf                                                     |\n| `install_dependencies --platform arch`    | Linux (Arch)          | Install build dependencies via pacman                                                  |\n| `install_dependencies --platform macos`   | macOS                 | Install dependencies via Homebrew + GStreamer                                          |\n| `install_dependencies --platform windows` | Windows               | Install GStreamer (optional `--vulkan`, `--msvc`, `--msvc-arm64`, `--nsis`)            |\n| `install_uv.py`                           | Linux, macOS          | Install the uv release named by `required-version` when `uv` is not on `PATH`          |\n| `install_python.py`                       | All                   | Install locked Python tool groups via uv ([Python Environment](#python-environment))   |\n| `install_qt.py`                           | All                   | Install Qt with [QGC's installer or aqtinstall](#qt-installer) (CI: `plan`/`ensure`)   |\n| `setup_vscode.py`                         | All                   | Install missing VS Code workspace files from tracked templates                         |\n| `read_config.py`                          | All                   | Read `.github/build-config.json`                                                       |\n| `build_gstreamer.py`                      | All                   | Build GStreamer from source (optional)                                                 |\n| `build_android_openssl.py`                | Android               | Cross-compile OpenSSL for Android; configure builds only the build type's variant      |\n| `build_clazy.py`                          | Linux                 | Build the pinned `clazy-standalone` against the analysis LLVM, as CI does              |\n\nLinux `--category` limits the install to `core`, `qt`, `gstreamer`, `sdl`, `audio` or `misc`.\nOn Debian, `android` is `core` plus the OpenJDK for `android.java_version` in `build-config.json`;\nthe Docker Android image uses it, while CI's Android jobs install `core` and get the JDK from\n`setup-java`. Debian's `gstreamer_build` and `gstreamer_build_android`, outside the full set, hold\nwhat `build_gstreamer.py` needs for a Linux source build and an Android (Cerbero) build; the Build\nGStreamer workflow installs them.\n`--list [--platform NAME]` lists each platform's packages by category and\n`--print-packages [--category NAME]` prints one list on a single line; neither installs anything.\nLocally, the Linux installers also sync the locked `build` group (CMake, Ninja, `just`, ...) into\n`tools/.venv` and print how to activate it; CI build jobs instead use the `build.cmake_version` and\n`build.ninja_version` pins in `build-config.json`, which `build-setup` installs.\n\nOn macOS, the GStreamer runtime and development packages come from the official server, falling\nback to QGroundControl's public S3 mirror. Both sources are verified against the SHA-256 checksums\npinned in `.github/build-config.json`; installation stops if either package cannot be verified.\n\n```bash\n# Linux: Install all dependencies\npython3 tools/setup/install_dependencies --platform debian\n\n# Linux: build tools plus Gradle's JDK only, for Android builds\npython3 tools/setup/install_dependencies --platform debian --category android\n\n# macOS: Install all dependencies\npython3 tools/setup/install_dependencies --platform macos\n\n# Windows (as Admin):\npython .\\tools\\setup\\install_dependencies --platform windows\n\n# Install Qt from the configured version and modules\npython3 tools/setup/install_qt.py install --from-config --host linux --arch linux_gcc_64\n\n# Build GStreamer from source (optional — CMake auto-downloads pre-built SDKs)\npython3 tools/setup/install_dependencies --platform debian --category gstreamer_build\npython3 tools/setup/build_gstreamer.py --platform linux --prefix /opt/gstreamer\n\n# Build Clazy like the Code Analysis workflow; then put .cache/clazy/bin on PATH\npython3 tools/setup/build_clazy.py --prefix .cache/clazy\n\n# Read build config values\npython3 tools/setup/read_config.py --get qt.version\n```\n\n### Qt installer\n\n`install_qt.py` installs Qt with one of two tools, chosen by `--installer` or `QGC_QT_INSTALLER`:\n\n- `qgc`, the default: the standard-library package `setup/qt_installer/`. It reads the\n  combination's `Updates.xml` from download.qt.io, checks it and every archive against the SHA-256\n  sums that download.qt.io publishes (archives may come from any mirror), extracts the archives in parallel with\n  `cmake -E tar`, and applies aqtinstall's post-install edits. CMake comes from `PATH`, else from\n  the tools environment's `build` group. The Qt root gets `.qgc-qt-manifest.json` (archives,\n  checksums, patched files) and, for full installs, the `.qgc-modules` marker that preinstalled-SDK\n  reuse reads.\n- `aqt`: aqtinstall from the locked `qt` group, which stays as the fallback.\n\nBoth produce the same tree apart from install paths in patched text files and pkg-config `prefix=`\nlines (aqt leaves them unpatched on `linux_arm64`); `install_qt.py compare` checks this for a\ncombination. Android built on a Windows host differs on purpose: `qgc` pairs it with the\n`msvc2022_64` host Qt, while aqt names a `mingw_64` one that QGC never installs; builds pass\n`QT_HOST_PATH` either way. Like aqt, `qgc` reports but does not install addon dependencies missing from\n`--modules`, such as `qtquick3d`'s `qtquicktimeline`. The `plan` cache key's digest includes a non-aqt\ninstaller, so neither tool restores the other's tree.\n\n## Python Environment\n\n`install_python.py` installs dependency groups defined in `tools/pyproject.toml`:\n`scripts`, `precommit`, `test`, `qt`, `coverage`, `build`, `dev` (default), `lint`, `terrain`,\n`simulation`, `release`, and `all`. Groups compose smaller profiles: `dev` includes the build, Qt,\nlint, test, and release (git-cliff) tools. `build` adds CMake, Ninja, Meson, `just`, prek (with\npre-commit), and gcovr; `simulation` adds pymavlink for the `simulation/` MAVLink tools. The Pyright\nhook also syncs `terrain` so it can type-check `tools/terrain`. `--list-groups` prints each group's packages. `[tool.uv] required-version` in\n`tools/pyproject.toml` is the single uv version: CI, `install_uv.py`, and the Docker image install\nexactly that floor, and older uv releases refuse the project (run `uv self update`).\n\nAll Python dependencies are resolved in `tools/uv.lock`. Setup requires uv and uses\n`uv sync --frozen --inexact` into `tools/.venv`, so repeating it with a smaller group preserves\ninstalled tools. Use `--replace` only to deliberately replace the profile; `--check` validates the\nrunning interpreter's installed versions and Python/platform markers without installing. CMake,\nVS Code, and CI use the same environment; CMake creates it with the `scripts` group when it is\nmissing (`QGC_AUTO_PYTHON_VENV`).\n\nFor an image-owned environment, set `QGC_TOOLS_PROJECT` to the directory containing\n`pyproject.toml` and `uv.lock`, and `QGC_PYTHON_ENV` to the environment directory.\nDocker uses `/opt/qgc-tools` and `/opt/qgc-venv`. Qt installs that fall back to aqtinstall use\nthe locked `qt` group ([Qt installer](#qt-installer)).\n\n`common/` contains low-level utilities ([module index](common/README.md)); `qgc_tools/` holds\nshared policy such as the Python environment, configure-preset selection, Docker variants, and\nrelease-tag checks ([qgc_tools/README.md](qgc_tools/README.md)). Command-line entrypoints are the\ntop-level `tools/*.py` scripts and those in `setup/`, `ci/`, and `deploy/`. Import-linter enforces\nthese dependency boundaries: `ci` is the top layer, and `setup` sits beside `analyzers`,\n`generators`, `terrain`, and `translations`, which must not import each other.\n\n## Python Script Conventions\n\nNew and edited scripts under `tools/` follow one shape, built on the [`common/`](common/README.md)\nhelpers:\n\n- **Entry**: scripts in subpackages first run the bare statement\n  `sys.path.insert(0, str(Path(__file__).resolve().parents[N]))` (N levels below `tools/`);\n  top-level scripts already have `tools/` on `sys.path`. Entry scripts then\n  `import _bootstrap as _bootstrap`, which installs the CI debug hooks; the redundant alias marks\n  the import as intentional. Library modules leave `sys.path` alone\n  ([details](common/README.md#bootstrapping-imports)).\n- **Main**: define `main(argv: list[str] | None = None) -> int` and end with\n  `raise SystemExit(main())` under `if __name__ == \"__main__\":`, so tests can call `main([...])`.\n- **Output**: log locally with `common.logging` (`log_info`, `log_warn`, `log_error`); in CI,\n  report problems as `common.gh_actions` annotations (`gh_error`, `gh_warning`) and write step\n  outputs and summaries with its `write_github_output` and `write_step_summary`. Build each\n  job-summary section with `common.markdown.summary_section` (H3 title, facts table, collapsed\n  details) and format numbers with `common.format` so every job reads the same way.\n- **Subprocesses and network**: run commands through `common.proc` (`run_captured`, `run_tee`,\n  `run_with_retry`), check required executables with its `require_tools` and poll with its\n  `wait_until`, download with `common.net`, and call GitHub through `common.gh_actions.gh()`.\n- **Files**: locate the checkout with `common.paths` (`REPO_ROOT`, `TOOLS_DIR`) rather than\n  `Path(__file__).resolve().parents[N]` outside the entry statement, and read and write JSON with\n  `common.io` (`read_json`, `write_json`).\n\n## Static Analyzers\n\n`analyzers/` holds the `analyze.py` backends, whose `clang_format.py` also runs as the\nchanged-region `clang-format` pre-commit hook and `qmlformat.py` as the manual-stage `qmlformat`\ncheck, and two QGC-specific checks that also run as\npre-commit hooks: `vehicle_null_check.py` detects `activeVehicle()` results dereferenced without a\nnull check, and `qt_translate_noop_check.py` detects `QT_TRANSLATE_NOOP`/`QT_TR_NOOP` results used\nas runtime strings. See [analyzers/README.md](analyzers/README.md) for usage and output formats.\n\n## Debugging Tools\n\n### debuggers/profile.py\n\nProfile QGC for performance and memory issues.\n\n```bash\npython tools/debuggers/profile.py                # CPU profiling with perf\npython tools/debuggers/profile.py --memcheck     # Memory leak detection (valgrind)\npython tools/debuggers/profile.py --callgrind    # CPU profiling (valgrind)\npython tools/debuggers/profile.py --massif       # Heap profiling (valgrind)\npython tools/debuggers/profile.py --heaptrack    # Heap profiling (heaptrack)\npython tools/debuggers/profile.py --sanitize     # Build with QGC_ENABLE_ASAN/UBSAN in build/sanitize/, then run\npython tools/debuggers/profile.py --config Debug -- --logging:full  # Forward application arguments\n```\n\n### debuggers/android_mem_capture.py\n\nSample an Android app's memory (`dumpsys meminfo` PSS breakdown plus system `MemAvailable`) over\nadb at a fixed interval and write a CSV, for soak testing and before/after comparisons. Handles\nprocess restarts/LMK kills; press Enter during capture to drop a phase marker (POSIX terminals\nonly). Defaults to the QGC package.\n\n```bash\npython tools/debuggers/android_mem_capture.py --label geomap --interval 10   # Capture until Ctrl+C\npython tools/debuggers/android_mem_capture.py --label soak --duration 30m    # Timed capture\npython tools/debuggers/android_mem_capture.py --compare run-a.csv run-b.csv  # Summarize/compare runs\n```\n\nThe compare summary reports start/peak/end and a second-half trend (kB/min) for total PSS,\ngraphics, native heap, and system available memory.\n\n### Debugger Formatters and Suppressions\n\n- `debuggers/gdb-pretty-printers/` — GDB pretty printers for Qt 6 types; see\n  [its README](debuggers/gdb-pretty-printers/README.md) for setup.\n- `debuggers/qt6.natvis` — Visual Studio debugger visualizers for Qt 6 types. Add it to your\n  project's Natvis files, or point `visualizerFile` at it in a VS Code `cppvsdbg` launch\n  configuration.\n- `debuggers/valgrind.supp` — Valgrind suppressions used by `profile.py` and CTest memcheck.\n\n## Other Tool Areas\n\nThese areas have their own READMEs:\n\n- [ci/](ci/README.md) — the GitHub Actions entrypoints, in subpackages such as `planning/`,\n  `cache/`, `compile/`, `publish/`, and `reporting/`; workflows run them by path, and their tests\n  live in `tests/ci/` (workflow and action contracts in `tests/ci/contracts/`).\n- [simulation/](simulation/README.md) — `mock_vehicle.py`, a lightweight MAVLink vehicle on\n  UDP 14550, `run_arducopter_sitl.py`, ArduCopter SITL in Docker or from a local build on\n  `tcp://localhost:5760`, `fly_plan.py`, which flies a `.plan` in ArduPilot or PX4 SITL and\n  records the track and images, `mock_ntrip_caster.py`, an offline NTRIP caster on\n  `127.0.0.1:2101` for testing RTK corrections, and `rc_transmitter.py`, a web page of RC channel\n  sliders for PX4 SITL.\n- [translations/](translations/README.md) — `just translations` runs `qgc_lupdate.py` (lupdate,\n  the JSON string extractor, and `pseudo_loc.py`); `check_placeholders.py` guards translation\n  placeholders.\n- [terrain/](terrain/README.md) — `terrain_tiles.py` builds, validates, and uploads the\n  terrarium elevation tiles GeoMap renders; it needs the `terrain` group.\n- [generators/config_qml/](generators/config_qml/README.md) and\n  [generators/settings_qml/](generators/settings_qml/README.md) — JSON schemas for the vehicle\n  config and application settings pages CMake generates at configure time.\n\n## Code Quality Tools\n\n### ccache.conf\n\nConfiguration for [ccache](https://ccache.dev/). CMake's compiler launcher points ccache at\n`tools/configs/ccache.conf` unless `CCACHE_CONFIGPATH` is already set; see the dev guide's\n[Build Caching](../docs/en/qgc-dev-guide/getting_started/index.md#build-caching) section.\n\n```bash\n# Manual use:\nexport CCACHE_CONFIGPATH=/path/to/qgroundcontrol/tools/configs/ccache.conf\n```\n\n### coding-style/\n\nWorked examples of [CODING_STYLE.md](../CODING_STYLE.md): `CodingStyle.h`, `CodingStyle.cc`, and\n`CodingStyle.qml` ([coding-style/README.md](coding-style/README.md)).\n\n## VS Code Integration\n\n`.vscode/` tracks `extensions.json` (recommended extensions) and templates for `settings.json`\n(editor and CMake integration), `tasks.json` (build, test, format tasks), and `launch.json` (debug\nconfigurations) as `*.default.json`. `just vscode` copies missing ones without overwriting local\nfiles. Open the repository in VS Code and install the recommended extensions.\n\n## Centralized Configuration\n\nVersion numbers and build settings are centralized in `.github/build-config.json`, grouped into\n`qt`, `android`, `apple`, `build`, `gstreamer`, and `analysis` objects (schema:\n`.github/build-config.schema.json`). Scripts read it so local development and CI use the same\nversions. Read a value with its dotted path, e.g. `read_config.py --get qt.version` or\n`--get android.ndk_full_version`; exported env vars and CI outputs derive from the path\n(`android.ndk_full_version` → `ANDROID_NDK_FULL_VERSION` / `android_ndk_full_version`).\n\nRun `just validate-configs` after changing build versions, schemas, or link exceptions.\nIt validates required fields, minimum versions, SDK ordering, checksum coverage, and exception expiry.\nThe pre-commit gate triggers on schema-only edits too. External link exceptions in `configs/lychee.toml`\nare exact URLs with a review deadline. Docs builds check English internal links without\nexceptions; the all-locale build allows a finite list of existing translation links in\n`docs/.vitepress/config.mjs`. CI's documentation checks are described in\n[ci-overview.md](../.github/ci-overview.md).\n\n<!-- File: tools/simulation/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# simulation Documentation\n\nPath: `tools/simulation/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/simulation/README.md)\n\n<!-- File: tools/terrain/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# terrain Documentation\n\nPath: `tools/terrain/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/terrain/README.md)\n\n<!-- File: tools/translations/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# translations Documentation\n\nPath: `tools/translations/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mavlink/qgroundcontrol/HEAD/tools/translations/README.md)\n</yakaai_skills>"}