G
YouTube TikTok Games
Skip to content

Latest commit

 

History

History

tools

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

QGroundControl Tools

Development tools, scripts, and configuration files for QGroundControl. This is the full reference for every just recipe and standalone script; AGENTS.md has the condensed day-to-day workflow.

Table of Contents

Quick Start

Install uv, then the locked dev Python profile into tools/.venv; the profile includes just. uv installs the Python the tools run on (Python Environment), so the system Python is not used:

uv run --frozen --project tools python tools/setup/install_python.py dev
. tools/.venv/bin/activate                  # PowerShell: . tools/.venv/Scripts/Activate.ps1

A separately installed just (brew install just, cargo install just) must be 1.48 or newer because the justfile uses set lazy; Ubuntu's apt install just ships 1.21, which is too old.

Everyday loop:

just             # List all recipes
just setup       # First-time: deps + Qt + configure + build
just build       # Incremental build
just check       # lint + Python tooling tests + application tests — run before declaring done

Directory Structure

tools/
├── _bootstrap.py            # Import bootstrap for entry scripts (tools/ on sys.path, CI debug hooks)
├── analyze.py               # Static analysis and formatting (clang-format, clang-tidy, clazy, qmllint, qmlformat)
├── build_profile.py         # Summarize Ninja and Clang time-trace build hotspots
├── check_deps.py            # Check for outdated dependencies
├── ci_script_index.py       # Generate the CI script index in ci/README.md from module docstrings
├── clean.py                 # Clean build artifacts and caches
├── configure.py             # CMake configuration wrapper
├── coverage_report.py       # Code coverage reports
├── moccache_launcher.py     # moccache entry point: the CMake AUTOMOC launcher and the stats/trim CLI
├── pre_commit.py            # Pre-commit runner with CI summaries
├── terrain_tiles.py         # Terrarium elevation tiles from Copernicus GLO-30 (GeoMap)
├── update_px4_metadata.py   # Refresh the bundled PX4 airframe and parameter metadata
├── validate_cmake_presets.py # CMake presets against the locked cmake package's schema (hook)
├── validate_configs.py      # Cross-field checks for build-config.json and link exceptions
├── pyproject.toml, uv.lock  # Locked Python tooling groups
├── analyzers/               # analyze.py backends and QGC-specific analyzers
├── ci/                      # GitHub Actions entrypoints, grouped by CI concern
├── coding-style/            # Code style examples
├── common/                  # Shared Python utilities
├── configs/                 # Tool configuration files
│   ├── ccache.conf          # ccache configuration
│   ├── cliff.toml           # git-cliff release-notes sections (just release-notes)
│   ├── Doxyfile             # Doxygen API docs, run from the repository root
│   ├── editorconfig-checker.json # editorconfig-checker exclusions and relaxed checks
│   ├── gersemirc.yaml       # gersemi CMake formatter trial (manual hook)
│   ├── lychee.toml          # Link checker settings and dated URL exceptions
│   ├── trufflehog-exclude.txt # Paths the trufflehog pre-commit hook skips
│   └── yamlfmt.yaml         # yamlfmt YAML formatter trial (manual hook)
├── debuggers/               # Debugger formatters, profiling, Android memory capture, Valgrind suppressions
├── generators/              # Code generation (MAVLink enums, config/settings QML)
├── moccache/                # Content-addressed cache for Qt moc (AUTOMOC wrapper)
├── qgc_tools/               # Shared policy: Python environment, configure presets, Docker variants
├── setup/                   # Environment setup scripts
├── simulation/              # Vehicle simulators
├── terrain/                 # Terrain tile generation package (see terrain_tiles.py)
├── tests/                   # pytest suite for tools/ (CI script and contract tests in tests/ci/)
└── translations/            # Translation tools (lupdate, JSON extraction, pseudo-localization)

Just Command Reference

All recipes are defined in ../justfile, grouped by the sections below; run just with no arguments for the same list. Most recipes run Python through uv run --frozen --project tools. BUILD_TYPE (default Debug), JOBS (default: all cores), and QT_DIR override the build recipes; just info prints them with the Qt, CMake, and GStreamer versions from .github/build-config.json (Centralized Configuration).

Setup

Recipe Description
just deps Auto-detect and install system build dependencies
just qt [ARGS] Install the configured Qt into ~/Qt unless configure already finds it; --dry-run previews
just vscode Install missing VS Code workspace files from tracked templates

Build

Recipe Description
just configure Configure through the matching default* CMake preset (Debug by default)
just build Build the project (uses all cores by default; override with JOBS=N)
just release Configure and build in Release mode (testing disabled)
just clean [ARGS] Clean the build directory; forwards ARGS to tools/clean.py (--cache, --all, --dry-run)
just rebuild clean + configure + build
just setup Full first-time setup: deps + qt + configure + build

Quality

Recipe Description
just test Run application tests via CTest; defaults to labels Unit/Integration, excluding Flaky/Network
just test-python [ARGS] Run the Python tooling tests (tools/tests) with CI's dependency groups, in parallel, in random order
just lint [ARGS] Run non-manual hooks on all files with prek (prek run --all-files); ARGS select hooks/stages
just lint-docs [ARGS] Lint English docs with only the docs hooks (every page; --changed for branch changes)
just format Check clang-format on lines changed since the default branch (no changes)
just format-fix Apply clang-format to lines changed since the default branch
just format-fix-python [ARGS] Apply Ruff's lint fixes and formatting (Python files, Python blocks in Markdown), or only to ARGS paths
just analyze Run static analysis (tools/analyze.py, default tool clang-tidy)
just coverage Build with coverage instrumentation, run tests, generate report
just check lint + test-python + test — run before declaring a task done

Override test label filters via environment variables or positional args:

just test                              # LABELS=Unit|Integration EXCLUDE=Flaky|Network (defaults)
LABELS="Slow" just test                # override via env var
just test "Slow" "Network"             # override via positional args (labels, exclude)

Run & Deploy

Recipe Description
just run Launch the built QGroundControl binary
just docs Build the VitePress documentation site (npm run docs:build in docs/)
just docker Build the ubuntu Docker variant (deploy/docker/run_docker.py build ubuntu)

Utilities

Recipe Description
just info Print resolved build configuration (Qt version/dir, CMake min, GStreamer, jobs, ...)
just check-deps Check Qt, GStreamer, Python, and build tool versions (tools/check_deps.py)
just check-gstreamer Check the latest GStreamer patch common to all SDK platforms
just translations Update translation sources (tools/translations/qgc_lupdate.py); --check only reports bad tr()
just distclean Clean build, caches, generated files, and node_modules/
just validate-configs Validate build settings and link exceptions (Centralized Configuration)
just release-notes TAG Print draft release notes for a vX.Y.Z tag (Release notes)
just lint-qml-build Lint all QML against the build's generated type information (analyze.py)

Direct Script Usage

Prefer just recipes; call the scripts below directly for flags a recipe doesn't expose. Run them from the repository root with tools/.venv activated (Quick Start), or prefix the command with uv run --frozen --project tools --group dev instead. The dev profile covers every script here.

Repository configure/build/test/workflow presets are rooted at ../CMakePresets.json. Use CMakeUserPresets.json for machine-local paths or derived presets; it is ignored by Git and preserved by just clean. tools/configure.py (behind just configure) requires the Qt kit's qt-cmake, found through --qt-root (QT_DIR for just), QT_ROOT_DIR, or the newest standard Qt install for the host architecture (~/Qt, then /opt/Qt on Linux and /Applications/Qt on macOS; C:/Qt, then ~/Qt on Windows). just qt installs the configured Qt into ~/Qt (Qt installer). Use --no-qt-cmake only when supplying an explicit target toolchain, as Android CI does. configure.py configures into the selected preset's binaryDir, where cmake --build --preset and ctest --preset look for the tree; -B overrides it.

Development Scripts

analyze.py

Run static analysis and formatting on source code. Underlies just format, just format-fix, just analyze, and just lint-qml-build.

python tools/analyze.py                              # Analyze changed files (default tool: clang-tidy)
python tools/analyze.py --all                        # Analyze all files
python tools/analyze.py --tool clang-format --fix    # Format changed lines
python tools/analyze.py --tool clang-format --all    # Check formatting (all files)
python tools/analyze.py src/Vehicle/                 # Analyze specific directory
python tools/analyze.py --tool clang-tidy --tool clazy --cache-dir .cache/analysis-results  # Both, one scan
python tools/analyze.py --tool clang-tidy --all --shard 1 --shard-count 4  # One of four disjoint partitions
python tools/analyze.py --tool clang-tidy --profile-checks src/Vehicle/Vehicle.cc  # Slower per-check profiling
python tools/analyze.py --tool clang-tidy --fix src/Vehicle/  # Apply clang-tidy fixes (local only)

Paths are relative to the repository root; absolute paths inside the checkout also work.

Other --tool choices: clazy, qmllint, qmlformat (reports what qmlformat would change, never rewrites), vehicle-null-check, qt-translate-noop-check. Without paths or --all, files changed since the merge base with the default branch (or PR_BASE_SHA; in GitHub's merge checkout of PR_HEAD_SHA, its first parent) are analyzed, including uncommitted and untracked files; without a usable base every file is. For clang-format the selection narrows to changed lines, matching the pre-commit hook: deletion-only changes are skipped and new or untracked files are checked in full. --all or explicit paths check (or --fix) whole files for intentional reformatting. --advisory keeps warnings informational; error-level diagnostics and tool or compiler invocation errors still fail.

Compiler analysis (clang-tidy, Clazy) reads compile_commands.json from --build-dir (default build) and needs generated headers, not a full build. Configure Ninja with CMAKE_EXPORT_COMPILE_COMMANDS=ON, CMAKE_DISABLE_PRECOMPILE_HEADERS=ON, CMAKE_AUTOGEN_ORIGIN_DEPENDS=OFF, CMAKE_GLOBAL_AUTOGEN_TARGET=ON, and QGC_UNITY_BUILD=OFF, then build only cmake --build <dir> --target qgc-analysis-headers (autogen is needed only for IWYU). Repeating --tool shares one file selection and dependency scan; --cache-dir <dir> replays unchanged results and needs clang-scan-deps. tools/setup/build_clazy.py builds the Clazy that CI uses; tools/setup/install_analysis_tools.py installs CI's clang-tidy and Clazy toolchain (the analysis LLVM, the pinned clang-tidy wheel, and Clazy, but not IWYU), as the devcontainer image does; source .cache/analysis-tools/env.sh (its default prefix) before running tools/analyze.py. --tool clang-tidy --fix applies the selected translation units' fixes with clang-apply-replacements (analyzers/README.md); CI never applies fixes. The clang-tidy and Clazy pre-commit hooks run only with --hook-stage manual; fast hooks remain the PR lint check. CI sharding and result caching are described in the Code Analysis notes; output files and profiling in analyzers/README.md.

QML: --tool qmllint --qml-build checks QML against the generated <build-dir>/qml type information after a build. Missing imports, properties, and types are errors; the fast source-only hook keeps its limited checks. just lint-qml-build runs it on every QML file with --advisory.

pre_commit.py

Runs the .pre-commit-config.yaml hooks with prek and CI-friendly summaries; the pre-commit workflow calls it with --ci --changed --results-dir. A --changed run whose docs lint configuration changed also runs the docs hooks over every English page.

python tools/pre_commit.py --changed                             # Files changed vs PR_BASE_SHA or the default branch
python tools/pre_commit.py --changed --hook ruff --hook pyright  # Only these hooks (repeatable)
python tools/pre_commit.py --docs --changed                      # English docs with only the docs hooks (just lint-docs)
python tools/pre_commit.py --changed --needs-qt                  # Whether the selection has QML (qmllint needs Qt)
python tools/pre_commit.py --install                             # Install the pre-commit and commit-msg hooks
python tools/pre_commit.py --prepare-cache                       # Install the hook environments CI caches, prune the rest

The selected hooks run in one prek run call; its output streams to the console and, with --results-dir, into pre-commit-output.txt. A failed run ends with the command that repeats it and, for the report-only Ruff and clang-format hooks, the recipe that applies their fixes (just format-fix-python, just format-fix); the CI job summary shows the same text. --ci skips the local-only trufflehog hook unless SKIP is already set. --prepare-cache installs every hook environment except CACHE_SKIPPED_HOOKS (TruffleHog and the Renovate validator) and then runs prek cache gc; the pre-commit workflow runs it on pushes that miss the prek cache, and saves the cache only when the install succeeded (workflow notes).

The precommit group pins prek, which also accepts pre-commit's commands and options (prek run <id> --hook-stage manual --files <paths>, SKIP=<id>). The configuration stays compatible with stock pre-commit, which the group keeps as a fallback runner for one release. editorconfig-checker enforces .editorconfig with the exclusions and relaxed checks in configs/editorconfig-checker.json; cmake-presets-schema validates CMakePresets.json and cmake/presets/*.json offline against the schema shipped in the locked cmake package; check-renovate schema-checks .github/renovate.json. The C++ formatting hook checks modified regions against the PR merge base (or HEAD locally), following CODING_STYLE.md; new files and unchanged files supplied in a full sweep are checked in full. prek install also installs a commit-msg hook enforcing Conventional Commits; default_stages keeps the other hooks out of that stage.

Most hooks check only the changed files, so a changed configuration or locked tool version rechecks everything it governs: ruff-check-tree and ruff-format-tree run over the whole tree when ruff.toml, tools/pyproject.toml, or tools/uv.lock change; Pyright, which always checks the whole project, also runs for pyrightconfig.json, tools/pyproject.toml, and tools/uv.lock; and import-linter for tools/pyproject.toml and tools/uv.lock. A changed docs lint configuration (.vale.ini, .markdownlint*, .typos.toml, .pre-commit-config.yaml, tools/pre_commit.py) likewise rechecks every English page with the docs hooks.

Manual-stage hooks run only on request: clang-tidy, clazy (CI runs both in analysis.yml), the CI validators that download pinned tools (renovate-config-validator, packer-validate, runs-on-lint, which pre_commit.py --ci also runs, each only when its own files change; Packer and the RunsOn CLI are the build-config.json pins, verified and cached in .cache/tools/), and three formatter trials that report differences without rewriting files: gersemi (CMake, configs/gersemirc.yaml), yamlfmt (configs/yamlfmt.yaml), and qmlformat (analyzers/qmlformat.py, .qmlformat.ini). For example, just lint gersemi --hook-stage manual checks every CMake file.

clean.py

Clean build artifacts and caches. Underlies just clean and just distclean.

python tools/clean.py              # Clean build directory
python tools/clean.py --all        # Clean everything (build, caches, node_modules/, Python bytecode outside venvs)
python tools/clean.py --cache      # Clean only repo-local caches (.cache, incl. ccache and the clangd index; pytest's tools/.cache; a legacy .ccache)
python tools/clean.py --dry-run    # Show what would be removed

build_profile.py

Summarize build-time hotspots from Ninja logs and optional Clang time traces. The parsing and report code lives in qgc_tools/build_profile.py, which ci/compile/cmake_helper.py and ci/compile/docker_helper.py also use.

python tools/build_profile.py -B build                 # Report slowest Ninja edges and rebuild churn
python tools/build_profile.py -B build --limit 25      # Show more rows per section
python tools/build_profile.py -B build --json          # Machine-readable output
python tools/build_profile.py -B build --json-out r.json > r.txt  # Text and JSON from one scan
python tools/build_profile.py -B build --output-dir build/profile  # Ninja-only snapshot
python tools/build_profile.py -B build --step-summary --extra-report "Title=other.txt"  # Plus a job-summary section
python tools/build_profile.py --install-clang-build-analyzer .cache/ClangBuildAnalyzer  # Pinned binary (Linux x86_64)

For per-translation-unit trace details, configure with -DQGC_TIME_TRACE=ON, rebuild, then rerun the report — it scans the build dir for Clang -ftime-trace JSON and highlights the slowest events.

Ordinary CI builds upload Ninja-only snapshots with separate link and generated-code rankings. Task totals overlap because commands run in parallel; they are not elapsed build time. The manual Build Profile workflow installs the ClangBuildAnalyzer release pinned (with its SHA-256) in .github/build-config.json's tools.clang_build_analyzer, and --step-summary writes its job summary: build type, commit, and compiler, with this report and each --extra-report collapsed.

moccache/

Content-addressed cache for Qt's moc, wired in through AUTOMOC_EXECUTABLE while the QGC_USE_MOCCACHE CMake option is ON (the default). The generated launcher runs moccache_launcher.py, which imports the moccache package so its bytecode is cached in tools/moccache/__pycache__. A moc run loads only core.py; trim.py (LRU eviction) and stats.py (hit/miss reports) are imported only for their subcommands or a due auto-trim, keeping each cache hit's startup short. The stats and trim commands run through the launcher too (python tools/moccache_launcher.py --show-stats ...). Misses fall through to the real moc and never fail the build. The dev guide Build Caching section covers the cache key, environment variables, and stats commands. Tests: tools/tests/test_moccache.py.

coverage_report.py

Generate code coverage reports through the CMake coverage targets. Underlies just coverage. Configures the Linux-coverage preset's build/Linux-coverage/ directory (override the directory with -b/--build-dir). Requires gcovr (coverage group, included in dev). Coverage compiles pass -fprofile-update=atomic, which ccache caches only from 4.10; an older distro ccache (Ubuntu 24.04 ships 4.9.1) gets no cache hits in coverage builds. Reports are Cobertura coverage.xml (uploaded to Codecov), gcovr's JSON coverage-summary.json (read by the step summary, verify_coverage_thresholds.py, and the PR build-results comment), and HTML coverage.html. Reporting fails when the build directory holds no .gcda counters, so tests that wrote no coverage data cannot publish a zero-coverage report.

python tools/coverage_report.py              # Build with coverage, run tests like CI, generate XML, JSON + HTML
python tools/coverage_report.py --report     # Generate report only from existing data (CI adds --step-summary)
python tools/coverage_report.py --open       # Full run, then open the HTML report
python tools/coverage_report.py --clean      # Delete the coverage build directory

Direct CMake usage:

cmake -B build -DQGC_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build
cmake --build build --target coverage-report  # XML, JSON summary + HTML from existing data
cmake --build build --target coverage         # Run Unit tests + generate XML, JSON summary + HTML
cmake --build build --target coverage-clean   # Remove coverage data and reports

coverage_report.py runs CI's two test phases through the Linux-coverage test preset, which excludes Flaky and Network: Unit tests at one process per CPU, then Integration tests at -j2, because instrumented integration tests time out at full parallelism. The CMake coverage target runs only the Unit tests.

check_deps.py

Check for outdated dependencies. Underlies just check-deps.

python tools/check_deps.py              # Check all dependencies
python tools/check_deps.py --qt         # Check Qt version
python tools/check_deps.py --gstreamer  # Find latest patch common to all SDK platforms

API documentation (Doxygen)

Run Doxygen from the repository root, as the Doxygen workflow does; HTML goes to docs/api/html/. The workflow builds and deploys weekly; pull requests run it only when they change the Doxyfile or the workflow, because warnings do not fail the build. Master runs keep the HTML artifact for eight days, and the Pages workflow publishes the latest under https://mavlink.github.io/qgroundcontrol/api/.

doxygen tools/configs/Doxyfile

Requires doxygen; graphviz enables diagrams.

Release notes

Release tags are created and pushed by hand. just release-notes vX.Y.Z prints the notes that release.yml drafts for that tag: git-cliff (release group) lists the commits since the previous vX.Y.Z tag reachable from it, grouped by type as configured in configs/cliff.toml, as full Conventional Commits subjects under ## Changes in vX.Y.Z. For a tag that does not exist yet it lists the unreleased commits since the last release reachable from HEAD.

just release-notes v5.1.5                                # Notes for an existing tag
python tools/ci/publish/release_notes.py v5.2.0          # Same script; v5.2.0 not tagged yet

ci_script_index.py

Regenerates the script index in ci/README.md from the module docstrings of tools/ci/ and from the workflows, actions, hooks, and recipes that name each script. Run it after adding, moving, or redescribing a CI script; --check, which the Python tests run, fails when the committed index is out of date.

python tools/ci_script_index.py           # Rewrite the index
python tools/ci_script_index.py --check   # Verify it

PX4 metadata

px4-metadata.yml runs update_px4_metadata.py daily and opens a pull request when the bundled PX4 airframe (AirframeFactMetaData.xml) or parameter (PX4ParameterFactMetaData.json) metadata changes upstream. It retries both downloads from artifacts.px4.io and replaces neither file unless both are valid: well-formed airframe XML without a DTD, and xz-compressed JSON that lists parameters.

python tools/update_px4_metadata.py

Setup Scripts

Scripts in setup/ configure development environments and read versions from .github/build-config.json. They need only the standard library, so they also run before tools/.venv has any packages: uv run --frozen --project tools python runs them on a fresh checkout, as does a host python3 of the required version. install_dependencies is a package (tools/setup/install_dependencies/) run through its __main__.py; without --platform it detects the host. build_gstreamer.py is the entry for the setup/gstreamer/ package: config (build settings, and the Meson options, one per plugin of gstreamer.plugins), host (commands, host arch and jobs), meson (Linux, macOS, Windows), cerbero (Android, iOS) and cli (arguments and defaults). Configure uses the official SDKs (or the system GStreamer on Linux) unless GStreamer_ROOT_DIR points it at another install prefix, such as a source build's.

Script Platform Description
install_dependencies --platform debian Linux (Debian/Ubuntu) Install build dependencies via apt
install_dependencies --platform fedora Linux (Fedora/RHEL) Install build dependencies via dnf
install_dependencies --platform arch Linux (Arch) Install build dependencies via pacman
install_dependencies --platform macos macOS Install dependencies via Homebrew + GStreamer
install_dependencies --platform windows Windows Install GStreamer (optional --vulkan, --msvc, --msvc-arm64, --nsis)
install_uv.py Linux, macOS Install the uv release named by required-version when uv is not on PATH
install_python.py All Install locked Python tool groups via uv (Python Environment)
install_qt.py All Install Qt with QGC's installer or aqtinstall (just qt: local)
setup_vscode.py All Install missing VS Code workspace files from tracked templates
read_config.py All Read .github/build-config.json
build_gstreamer.py All Build GStreamer from source (optional)
build_android_openssl.py Android Cross-compile OpenSSL for Android; configure builds only the build type's variant
build_clazy.py Linux Build the pinned clazy-standalone against the analysis LLVM, as CI does
install_analysis_tools.py Debian 13+/Ubuntu 24+ Install the analysis LLVM, pinned clang-tidy and Clazy, as CI and the devcontainer
install_ccache.py Linux Install the ccache release pinned in build-config.json, as CI and the devcontainer

Linux --category limits the install to core, qt, gstreamer, sdl, audio or misc. On Debian, android is core plus the OpenJDK for android.java_version in build-config.json; the Docker Android image uses it, while CI's Android jobs install core and get the JDK from setup-java. Debian's gstreamer_build and gstreamer_build_android, outside the full set, hold what build_gstreamer.py needs for a Linux source build and an Android (Cerbero) build. --list [--platform NAME] lists each platform's packages by category and --print-packages [--category NAME] prints one list on a single line; neither installs anything. Locally, the Linux installers also sync the locked build group (CMake, Ninja, just, ...) into tools/.venv and print how to activate it; CI build jobs instead use the build.cmake_version and build.ninja_version pins in build-config.json, which build-setup installs.

On macOS and Windows, the GStreamer packages come from the official server, falling back to QGroundControl's public S3 mirror: the official server gets two tries and three minutes, then the mirror resumes the partial download. Both sources are verified against the SHA-256 checksums pinned in .github/build-config.json; installation stops if a package cannot be verified.

# Linux: Install all dependencies
uv run --frozen --project tools python tools/setup/install_dependencies --platform debian

# Linux: build tools plus Gradle's JDK only, for Android builds
uv run --frozen --project tools python tools/setup/install_dependencies --platform debian --category android

# macOS: Install all dependencies
uv run --frozen --project tools python tools/setup/install_dependencies --platform macos

# Windows (as Admin):
uv run --frozen --project tools python tools/setup/install_dependencies --platform windows

# Install the configured Qt for this machine into ~/Qt, where configure looks (just qt)
uv run --frozen --project tools --group build python tools/setup/install_qt.py local

# Install a chosen kit of the configured Qt; without --outdir it lands in ./.qt, which configure does not search
uv run --frozen --project tools --group build python tools/setup/install_qt.py install --from-config --host linux --arch linux_gcc_64 --outdir ~/Qt

# Build GStreamer from source (optional — CMake auto-downloads pre-built SDKs)
uv run --frozen --project tools python tools/setup/install_dependencies --platform debian --category gstreamer_build
uv run --frozen --project tools python tools/setup/build_gstreamer.py --platform linux --prefix /opt/gstreamer

# Build Clazy like the Code Analysis workflow; then put .cache/clazy/bin on PATH
uv run --frozen --project tools python tools/setup/build_clazy.py --prefix .cache/clazy

# Read build config values
uv run --frozen --project tools python tools/setup/read_config.py --get qt.version

Qt installer

install_qt.py local (just qt) installs the Qt version and modules in .github/build-config.json for the host's desktop kit into ~/Qt, the first directory tools/configure.py searches outside Windows. It installs nothing when configure already finds that version in a standard install directory or through QT_ROOT_DIR or --qt-root, or when one of those two selects another Qt, and it names the kit configure selects when that is not the installed one. CI's qt-install action calls plan and ensure instead.

install_qt.py installs Qt with one of two tools, chosen by --installer or QGC_QT_INSTALLER:

  • qgc, the default: the standard-library package setup/qt_installer/. It reads the combination's Updates.xml from download.qt.io, checks it and every archive against the SHA-256 sums that download.qt.io publishes (archives may come from any mirror), extracts the archives in parallel with cmake -E tar, and applies aqtinstall's post-install edits. CMake comes from PATH, else from the tools environment's build group. The Qt root gets .qgc-qt-manifest.json (archives, checksums, patched files) and, for full installs, the .qgc-modules marker that preinstalled-SDK reuse reads.
  • aqt: aqtinstall from the locked qt group, which stays as the fallback.

Both produce the same tree apart from install paths in patched text files and pkg-config prefix= lines (aqt leaves them unpatched on linux_arm64); install_qt.py compare checks this for a combination. Android built on a Windows host differs on purpose: qgc pairs it with the msvc2022_64 host Qt, while aqt names a mingw_64 one that QGC never installs; builds pass QT_HOST_PATH either way. Like aqt, qgc reports but does not install addon dependencies missing from --modules, such as qtquick3d's qtquicktimeline. The plan cache key's digest includes a non-aqt installer, so neither tool restores the other's tree.

Python Environment

install_python.py installs dependency groups defined in tools/pyproject.toml: scripts, precommit, test, qt, coverage, build, dev (default), lint, terrain, simulation, release, and all. Groups compose smaller profiles: dev includes the build, Qt, lint, test, release (git-cliff), and simulation tools. It leaves out terrain: rasterio has wheels only for macOS 15 or newer, glibc 2.28 or newer, and Windows, and builds from source against GDAL elsewhere. just test-python adds terrain to run the Python tests as CI does; without the release, terrain, and simulation groups the release-notes, terrain, and mock-vehicle tests skip. test also installs the cmake package for the preset schema tests; build sets its range. build adds CMake, Ninja, Meson, just, prek (with pre-commit), and gcovr; its CMake requirement is cmake>=3.28,<5, and tools/uv.lock holds the major version of CI's build.cmake_version pin (test_cmake_version_alignment.py ties the two), which the just recipes run through uv run --group build. simulation adds pymavlink for the simulation/ MAVLink tools. The Pyright hook also syncs terrain so it can type-check tools/terrain. --list-groups prints each group's packages. [tool.uv] required-version in tools/pyproject.toml is the single uv version: CI, install_uv.py, and the builder and fuzzing images install exactly that floor, and older uv releases refuse the project (run uv self update).

tools/.python-version names the one Python the tools run on. uv reads it wherever it is given the project (--project tools): it builds tools/.venv, CI's environment, and the Docker and Vagrant environments on that version, downloading it when the host has none, and rebuilds an environment left on another version. requires-python, Ruff's target-version, and Pyright's pythonVersion follow it (just validate-configs checks them), so no tools code needs to run on an older Python. uv is therefore the only prerequisite; a host python3 is used only when a script is run with it directly, and then it must be at least that version.

All Python dependencies are resolved in tools/uv.lock. Setup requires uv and uses uv sync --frozen --inexact into tools/.venv, so repeating it with a smaller group preserves installed tools. Use --replace only to deliberately replace the profile; --check validates the running interpreter's version, installed versions, and Python/platform markers without installing. CMake, VS Code, and CI use the same environment; CMake creates it with uv and the scripts group when it is missing, and repairs one that fails --check (QGC_AUTO_PYTHON_VENV).

For an image-owned environment, set QGC_TOOLS_PROJECT to the directory containing pyproject.toml and uv.lock, and QGC_PYTHON_ENV to the environment directory. Docker uses /opt/qgc-tools and /opt/qgc-venv, with uv's Python in /opt/qgc-python (UV_PYTHON_INSTALL_DIR) so the non-root build user can run it. Qt installs that fall back to aqtinstall use the locked qt group (Qt installer).

common/ contains low-level utilities (module index); qgc_tools/ holds shared policy such as the Python environment, configure-preset selection, Docker variants, and release-tag checks (qgc_tools/README.md). Command-line entrypoints are the top-level tools/*.py scripts and those in setup/, ci/, and deploy/. Import-linter enforces these dependency boundaries: ci is the top layer, and setup sits beside analyzers, generators, terrain, and translations, which must not import each other.

Python Script Conventions

New and edited scripts under tools/ follow one shape, built on the common/ helpers:

  • Entry: scripts in subpackages first run the bare statement sys.path.insert(0, str(Path(__file__).resolve().parents[N])) (N levels below tools/); top-level scripts already have tools/ on sys.path. Entry scripts then import _bootstrap as _bootstrap, which installs the CI debug hooks; the redundant alias marks the import as intentional. Library modules leave sys.path alone (details).
  • Main: define main(argv: list[str] | None = None) -> int and end with raise SystemExit(main()) under if __name__ == "__main__":, so tests can call main([...]).
  • Output: log locally with common.logging (log_info, log_warn, log_error); in CI, report problems as common.gh_actions annotations (gh_error, gh_warning) and write step outputs and summaries with its write_github_output and write_step_summary. Build each job-summary section with common.markdown.summary_section (H3 title, facts table, collapsed details) and format numbers with common.format so every job reads the same way.
  • Subprocesses and network: run commands through common.proc (run_captured, run_tee, run_with_retry), check required executables with its require_tools and poll with its wait_until, download with common.net, and call GitHub through common.gh_actions.gh().
  • Files: locate the checkout with common.paths (REPO_ROOT, TOOLS_DIR) rather than Path(__file__).resolve().parents[N] outside the entry statement, and read and write JSON with common.io (read_json, write_json).

Static Analyzers

analyzers/ holds the analyze.py backends, whose clang_format.py also runs as the changed-region clang-format pre-commit hook and qmlformat.py as the manual-stage qmlformat check, and two QGC-specific checks that also run as pre-commit hooks: vehicle_null_check.py detects activeVehicle() results dereferenced without a null check, and qt_translate_noop_check.py detects QT_TRANSLATE_NOOP/QT_TR_NOOP results used as runtime strings. See analyzers/README.md for usage and output formats.

Debugging Tools

debuggers/profile.py

Profile QGC for performance and memory issues.

python tools/debuggers/profile.py                # CPU profiling with perf
python tools/debuggers/profile.py --memcheck     # Memory leak detection (valgrind)
python tools/debuggers/profile.py --callgrind    # CPU profiling (valgrind)
python tools/debuggers/profile.py --massif       # Heap profiling (valgrind)
python tools/debuggers/profile.py --heaptrack    # Heap profiling (heaptrack)
python tools/debuggers/profile.py --sanitize     # Build with QGC_ENABLE_ASAN/UBSAN in build/sanitize/, then run
python tools/debuggers/profile.py --config Debug -- --logging:full  # Forward application arguments

debuggers/android_mem_capture.py

Sample an Android app's memory (dumpsys meminfo PSS breakdown plus system MemAvailable) over adb at a fixed interval and write a CSV, for soak testing and before/after comparisons. Handles process restarts/LMK kills; press Enter during capture to drop a phase marker (POSIX terminals only). Defaults to the QGC package.

python tools/debuggers/android_mem_capture.py --label geomap --interval 10   # Capture until Ctrl+C
python tools/debuggers/android_mem_capture.py --label soak --duration 30m    # Timed capture
python tools/debuggers/android_mem_capture.py --compare run-a.csv run-b.csv  # Summarize/compare runs

The compare summary reports start/peak/end and a second-half trend (kB/min) for total PSS, graphics, native heap, and system available memory.

Debugger Formatters and Suppressions

  • debuggers/gdb-pretty-printers/ — GDB pretty printers for Qt 6 types; see its README for setup.
  • debuggers/qt6.natvis — Visual Studio debugger visualizers for Qt 6 types. Add it to your project's Natvis files, or point visualizerFile at it in a VS Code cppvsdbg launch configuration.
  • debuggers/valgrind.supp — Valgrind suppressions used by profile.py and CTest memcheck.

Other Tool Areas

These areas have their own READMEs:

  • ci/ — the GitHub Actions entrypoints, in subpackages such as planning/, cache/, compile/, publish/, and reporting/; workflows run them by path, and their tests live in tests/ci/ (workflow and action contracts in tests/ci/contracts/). The CI overview describes what runs on a pull request and how to change CI.
  • simulation/ — mock_vehicle.py, a lightweight MAVLink vehicle on UDP 14550, run_arducopter_sitl.py, ArduCopter SITL in Docker or from a local build on tcp://localhost:5760, fly_plan.py, which flies a .plan in ArduPilot or PX4 SITL and records the track and images, mock_ntrip_caster.py, an offline NTRIP caster on 127.0.0.1:2101 for testing RTK corrections, and rc_transmitter.py, a web page of RC channel sliders for PX4 SITL.
  • translations/ — just translations runs qgc_lupdate.py (lupdate, the JSON string extractor, and pseudo_loc.py); with --check it only reports the tr() calls lupdate rejects or cannot extract, as Linux CI does. check_placeholders.py guards translation placeholders.
  • terrain/ — terrain_tiles.py builds, validates, and uploads the terrarium elevation tiles GeoMap renders; it needs the terrain group.
  • generators/config_qml/ and generators/settings_qml/ — JSON schemas for the vehicle config and application settings pages CMake generates at configure time.

Code Quality Tools

ccache.conf

Configuration for ccache. CMake's compiler launcher points ccache at tools/configs/ccache.conf unless CCACHE_CONFIGPATH is already set; see the dev guide's Build Caching section.

# Manual use:
export CCACHE_CONFIGPATH=/path/to/qgroundcontrol/tools/configs/ccache.conf

coding-style/

Worked examples of CODING_STYLE.md: CodingStyle.h, CodingStyle.cc, and CodingStyle.qml (coding-style/README.md).

VS Code Integration

.vscode/ tracks extensions.json (recommended extensions) and templates for settings.json (editor and CMake integration), tasks.json (build, test, format tasks), and launch.json (debug configurations) as *.default.json. just vscode copies missing ones without overwriting local files. Open the repository in VS Code and install the recommended extensions.

Centralized Configuration

Version numbers and build settings are centralized in .github/build-config.json. Its sections, and every key, type, and required field, are defined by .github/build-config.schema.json; read the schema rather than a list here. Scripts and CMake read the file so local development and CI use the same versions. Read a value with its dotted path, e.g. read_config.py --get qt.version or --get android.ndk_full_version, with common.build_config.get_build_config_value(), or, as CI step outputs, with read_config.py --github-output <dotted.path>... (dots become underscores); exported env vars and CI outputs derive from the path (android.ndk_full_version → ANDROID_NDK_FULL_VERSION / android_ndk_full_version). How the pins are updated (Renovate, digests by hand) is described in the dependency notes and the CI overview.

Run just validate-configs after changing build versions, schemas, or link exceptions. It validates required fields, minimum versions, SDK ordering, checksum coverage, and exception expiry. The pre-commit gate triggers on schema-only edits too. External link exceptions in configs/lychee.toml are exact URLs with a review deadline. Docs builds check English internal links without exceptions; the all-locale build allows a finite list of existing translation links in docs/.vitepress/config.mjs. CI's documentation checks are described in the workflow notes.