diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a6a8501b326..6908c1872ad 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -33,7 +33,8 @@ Before you begin, please: ### Development Environment - **Language**: C++20 with the Qt version defined in [`build-config.json`](build-config.json) -- **Build System**: CMake (minimum version defined in [`build-config.json`](build-config.json)) +- **Build System**: CMake (minimum version defined in [`build-config.json`](build-config.json)); see + the [CMake development guide](../cmake/README.md) for modules, presets, and focused tests - **Platforms**: Windows, macOS, Linux, Android, iOS - **IDE**: Qt Creator (recommended), VS Code, or your preferred IDE - **Build/test/lint commands**: see [tools/README.md](../tools/README.md) for the `just configure` / diff --git a/.github/ci-overview.md b/.github/ci-overview.md index a3ae24a5580..22a5d46ca2b 100644 --- a/.github/ci-overview.md +++ b/.github/ci-overview.md @@ -30,7 +30,7 @@ via composite actions and reusable workflows. Python helpers in `scripts/` are i ├── build-config.json # Centralized version numbers and build settings ├── build-config.schema.json # JSON Schema for build-config.json ├── dependabot.yml # Dependabot config (GitHub Actions only) -└── renovate.json # Renovate config (npm, python, pre-commit) +└── renovate.json # Renovate config (code, tooling, and dev-environment dependencies) ``` ## Workflows @@ -181,10 +181,11 @@ Python helpers in `.github/scripts/` invoked by workflows and composite actions. Dependency updates are split between two bots to avoid overlapping PRs: -- **Dependabot** (`.github/dependabot.yml`) owns `github-actions` updates only, grouped weekly. - Merge with `@dependabot merge`. -- **Renovate** (`.github/renovate.json`) owns `npm`, `python` (pep621/uv), and `pre-commit` - updates, grouped into a single weekly PR. GitHub Actions paths are excluded via `ignorePaths`. +- **Dependabot** (`.github/dependabot.yml`) owns action references in `.github/workflows`, grouped + weekly. Merge with `@dependabot merge`. +- **Renovate** (`.github/renovate.json`) owns `npm`, Python (pep621/uv), pre-commit, devcontainer, + Dockerfile, Gradle Wrapper, and composite-action dependencies. Workflow paths are excluded so + the bots do not open overlapping action updates. ## CI Conventions @@ -209,11 +210,11 @@ linting) and a pytest job covering both `tools/tests` and `.github/scripts/tests Run the CI script tests locally: ```bash -pytest -q .github/scripts/tests/ +uv run --project tools --extra scripts --extra test pytest -q .github/scripts/tests ``` -Run the full set the same way CI does (also covers `tools/`): +Run the full set locally with the same locked dependency groups CI installs (also covers `tools/`): ```bash -pytest -q tools/tests .github/scripts/tests +uv run --project tools --extra scripts --extra test pytest -q tools/tests .github/scripts/tests ``` diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 67cd69a5d5e..e530a20197d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,5 @@ -# Dependabot owns GitHub Actions only. npm, python (uv, /tools), and pre-commit are -# handled by Renovate (see .github/renovate.json). Merge bot PRs with: @dependabot merge. -# Docs: https://docs.github.com/en/code-security/dependabot +# Dependabot owns workflow actions. Renovate owns npm, Python, pre-commit, development +# environments, and composite actions (see .github/renovate.json). version: 2 updates: - package-ecosystem: "github-actions" diff --git a/.github/renovate.json b/.github/renovate.json index ba8dfeb58d3..1f1e81819aa 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -3,6 +3,7 @@ "extends": [ "config:recommended", ":dependencyDashboard", + ":enablePreCommit", ":semanticCommitTypeAll(deps)" ], "labels": [ @@ -11,8 +12,12 @@ "ignorePaths": [ ".github/workflows/**" ], - "description": "Active manager for npm, python (pep621/uv), and pre-commit. GitHub Actions are handled by Dependabot (see dependabot.yml); excluded here via ignorePaths.", + "description": "Manages npm, Python, pre-commit, development environments, Gradle Wrapper, Dockerfiles, and composite actions. Dependabot owns .github/workflows, which are excluded here.", "enabledManagers": [ + "devcontainer", + "dockerfile", + "github-actions", + "gradle-wrapper", "npm", "pep621", "pre-commit" @@ -20,7 +25,14 @@ "packageRules": [ { "description": "Auto-merge patch updates after CI passes", - "matchUpdateTypes": ["patch"], + "matchManagers": [ + "npm", + "pep621", + "pre-commit" + ], + "matchUpdateTypes": [ + "patch" + ], "automerge": true }, { @@ -34,6 +46,22 @@ "schedule": [ "before 6am on monday" ] + }, + { + "description": "Group composite GitHub Action updates not covered by Dependabot", + "groupName": "composite action dependencies", + "matchManagers": [ + "github-actions" + ] + }, + { + "description": "Group development environment and build bootstrap updates", + "groupName": "development environment dependencies", + "matchManagers": [ + "devcontainer", + "dockerfile", + "gradle-wrapper" + ] } ], "prConcurrentLimit": 5, diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 023df639a7c..331e6597a38 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,7 +35,7 @@ repos: # Secret detection - repo: https://github.com/trufflesecurity/trufflehog - rev: v3.95.7 + rev: v3.97.1 hooks: - id: trufflehog args: ['--only-verified', '--exclude-paths=tools/configs/trufflehog-exclude.txt'] @@ -43,7 +43,7 @@ repos: # C++ formatting (check-only, use 'clang-format -i' to fix) # Note: Install matching clang-format version locally to avoid formatting differences - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v22.1.5 + rev: v23.1.0 hooks: - id: clang-format args: ['--dry-run', '--Werror'] @@ -52,7 +52,7 @@ repos: # Python formatting (report-only) - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.20 + rev: v0.16.5 hooks: - id: ruff - id: ruff-format @@ -77,7 +77,7 @@ repos: # uv lockfile sync (keeps tools/uv.lock current with tools/pyproject.toml) - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.11.26 + rev: 0.12.8 hooks: - id: uv-lock files: ^tools/(pyproject\.toml|uv\.lock)$ @@ -85,17 +85,18 @@ repos: # Shell scripts - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.11.0.1 + rev: v0.11.0.1-1 hooks: - id: shellcheck args: ['-e', 'SC1091'] # Don't warn about not following sourced files # Dockerfile linting - repo: https://github.com/AleksaC/hadolint-py - rev: v2.14.0 + rev: v2.15.1 hooks: - id: hadolint - files: ^deploy/docker/Dockerfile-.*$ + args: ['--failure-threshold', 'warning'] + files: (^|/)Dockerfile([.-].*)?$ types: [file] # GitHub Actions linting @@ -123,23 +124,53 @@ repos: # JSON Schema validation for build-config.json - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.37.4 + rev: 0.38.0 hooks: - id: check-jsonschema name: Validate .github/build-config.json files: ^\.github/build-config\.json$ args: ['--schemafile', '.github/build-config.schema.json'] + - id: check-jsonschema + alias: validate-dependabot-config + name: Validate Dependabot config + files: ^\.github/dependabot\.ya?ml$ + args: ['--builtin-schema', 'vendor.dependabot'] + + - id: check-jsonschema + alias: validate-codecov-config + name: Validate Codecov config + files: ^\.github/codecov\.ya?ml$ + args: ['--builtin-schema', 'vendor.codecov'] + + - id: check-jsonschema + alias: validate-issue-template-config + name: Validate GitHub issue template config + files: ^\.github/ISSUE_TEMPLATE/config\.ya?ml$ + args: ['--builtin-schema', 'vendor.github-issue-config'] + + - id: check-jsonschema + alias: validate-issue-forms + name: Validate GitHub issue forms + files: ^\.github/ISSUE_TEMPLATE/(?!config\.ya?ml$).*\.ya?ml$ + args: ['--builtin-schema', 'vendor.github-issue-forms'] + + - id: check-jsonschema + alias: validate-compose-config + name: Validate Docker Compose config + files: ^deploy/docker/(?:docker-)?compose\.ya?ml$ + args: ['--builtin-schema', 'vendor.compose-spec'] + # Renovate config validation (schema-checks the active renovate.json) - repo: https://github.com/renovatebot/pre-commit-hooks - rev: 43.249.6 + rev: 44.56.1 hooks: - id: renovate-config-validator files: ^\.github/renovate\.json$ # Markdown linting (report-only, doesn't auto-fix) - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.49.0 + rev: v0.49.1 hooks: - id: markdownlint exclude: ^(translations/|tools/skills/|docs/(zh|ko|tr|ru|ja|de|es|fa|fr|he|uk)/|CHANGELOG\.md) @@ -156,12 +187,12 @@ repos: # install of the bare module fails and aborts the whole pre-commit run before any hook # fires. additional_dependencies: - - 'github.com/errata-ai/vale/v3/cmd/vale@v3.15.1' + - 'github.com/errata-ai/vale/v3/cmd/vale@v3.19.0' entry: vale sync files: ^(docs/|\.vale\.ini$) pass_filenames: false - repo: https://github.com/errata-ai/vale - rev: v3.15.1 + rev: v3.19.0 hooks: - id: vale files: ^docs/ @@ -169,7 +200,7 @@ repos: # Spell checking (report-only, doesn't auto-fix) - repo: https://github.com/crate-ci/typos - rev: v1.48.0 + rev: v1.50.1 hooks: - id: typos args: [] # Empty args disables auto-fix mode @@ -199,7 +230,7 @@ repos: language: python additional_dependencies: ['cmakelang==0.6.13', 'pyyaml==6.0.3'] files: (CMakeLists\.txt|\.cmake)$ - exclude: ^(build/|libs/) + exclude: ^(build/|libs/|cmake/modules/CPM\.cmake$) - id: cmake-lint name: cmake-lint @@ -207,7 +238,7 @@ repos: language: python additional_dependencies: ['cmakelang==0.6.13', 'pyyaml==6.0.3'] files: (CMakeLists\.txt|\.cmake)$ - exclude: ^(build/|libs/) + exclude: ^(build/|libs/|cmake/modules/CPM\.cmake$) - id: clazy name: Qt static analysis (clazy) diff --git a/cmake/README.md b/cmake/README.md new file mode 100644 index 00000000000..8580c1cd18a --- /dev/null +++ b/cmake/README.md @@ -0,0 +1,107 @@ +# CMake Development Guide + +This directory contains QGroundControl's reusable build modules, platform configuration, package +assembly, dependency finders, and pure-CMake regression tests. The root `CMakeLists.txt` remains the +entry point for normal builds. + +## Layout + +| Path | Purpose | +| --- | --- | +| `GStreamer/` | GStreamer discovery, download, validation, installation, and pure-CMake tests | +| `install/` | Runtime deployment and package assembly | +| `modules/` | Reusable project modules and dependency helpers | +| `platform/` | Android, Apple, Linux, and Windows configuration | +| `presets/` | Platform configure, build, test, package, and workflow presets | +| `tests/` | Configure-time contracts for shared CMake helpers | +| `find-modules/` | CMake find modules for optional dependencies | + +## Conventions + +- Add `include_guard(GLOBAL)` to reusable modules that define functions, macros, options, or global + state. +- Prefix project-owned functions, macros, targets, and cache variables with `qgc` or `QGC`. +- Validate required and unknown arguments in public helper functions. Fail during configure rather + than allowing a misspelled option to be ignored. +- Prefer target-scoped properties and commands over directory-global compile or link flags. +- Account for single- and multi-config generators. Custom test targets should pass their active + configuration to CTest. +- Keep host and target paths distinct in cross-builds, especially for Android and iOS. +- In GStreamer modules with repeated basenames, include the intended file by absolute path. +- When changing an existing file, format only the edited region. Whole-file formatting is reserved + for new files or an explicit formatting task. + +`cmake/modules/CPM.cmake` is vendored. Do not modify or reformat it as part of project CMake +maintenance; the pre-commit CMake hooks intentionally exclude it. + +Run the CMake lint hooks on the files you change: + +```bash +pre-commit run cmake-format --files path/to/file.cmake +pre-commit run cmake-lint --files path/to/file.cmake +``` + +## Tests + +After configuring a build with tests enabled, run the shared-module and finder tests with: + +```bash +ctest --test-dir build --output-on-failure -L CMake +ctest --test-dir build --output-on-failure -L FindModule +``` + +Use `just configure`, `just build`, and the relevant CTest labels for the final validation described +in the root [AGENTS.md](../AGENTS.md). + +## Presets + +The root [`CMakePresets.json`](../CMakePresets.json) aggregates the platform definitions in +`presets/`. CI selects one of those configure presets and limits workflow overrides to dynamic +values such as signing credentials, sanitizer selection, and target-specific paths. Keep build +type, generator, testing, coverage, toolchain, and cache defaults in the presets so local and CI +configuration cannot drift. + +The root file owns `cmakeMinimumRequired`; included fragments declare only their required preset +schema `version`. Preset build directories stay under `${sourceDir}/build` so separate source +checkouts cannot share or overwrite one another's build trees. + +List every configure, build, test, package, and workflow preset with: + +```bash +cmake --list-presets=all +``` + +Each visible configure preset has a same-name build preset and workflow preset. Workflows run tests +only for configurations that set `QGC_BUILD_TESTING=ON`; mobile and ARM64 cross-build debug presets +intentionally keep tests disabled because their target binaries cannot be assumed runnable on the +host. For example: + +```bash +cmake --workflow --preset Linux-debug +cmake --workflow --preset Linux-deb +``` + +The `just configure` and `just build` recipes use the matching `default*` configure and build +presets for `BUILD_TYPE`. `just test` always uses the `default` test preset and passes `BUILD_TYPE` +through `--build-config`. `tools/configure.py` selects a configure preset by build type and accepts +an explicit `--preset`; `--no-preset` is reserved for unsupported custom toolchains. Coverage uses +`Linux-coverage`. Docker and Multipass builders invoke `qt-cmake` directly because their mounted +paths and cross-toolchains are assembled at runtime; Vagrant uses the `Linux` preset with a build +directory override. + +| Preset family | Required environment | Notes | +| --- | --- | --- | +| `default*` | A discoverable Qt installation | Generic Ninja build in the source tree | +| `Linux*`, `macOS*`, `Windows*` | `QT_ROOT_DIR` | Qt target installation containing `qt.toolchain.cmake` | +| `Linux-arm64*`, `Windows-arm64*` | `QT_ROOT_DIR`, `QT_HOST_PATH` | Target and host Qt installations for ARM64 or cross-builds | +| `Android*` | `QT_TARGET_ROOT_DIR`, `QT_HOST_PATH`, `ANDROID_NDK`, `ANDROID_MIN_SDK` | Qt for Android plus its matching host Qt and Android toolchain | +| `iOS*` | `QT_ROOT_DIR`, `QT_HOST_PATH` | Qt for iOS plus its matching host Qt | + +Package presets exist only for CPack-backed artifacts: Linux `.deb`/`.rpm` packages and Windows +NSIS installers. The normal Linux AppImage and macOS DMG are install-time artifacts, so configure +and build their release workflow and then run `cmake --install --config Release`. +Android APK signing/deployment and iOS IPA assembly remain platform workflow steps rather than CPack +operations. + +Use `CMakeUserPresets.json` for machine-local paths or derived presets; it remains ignored by Git +and `tools/clean.py` preserves it. diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index ebf99d564a5..3d4e7cda0cb 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -114,6 +114,8 @@ CMD ["Release"] # ---------- android: Android APK build (SDK/NDK + Qt android target) ---------- FROM base AS android COPY tools/setup/install_dependencies/ /tmp/tools/setup/install_dependencies/ +# The Java version is centralized in build-config and package revisions vary by base image. +# hadolint ignore=DL3008 RUN python3 /tmp/tools/setup/install_dependencies --platform debian && \ python3 /tmp/tools/setup/read_config.py --export bash > /etc/profile.d/qgc.sh && \ . /etc/profile.d/qgc.sh && \ @@ -123,6 +125,8 @@ RUN python3 /tmp/tools/setup/install_dependencies --platform debian && \ ENV ANDROID_SDK_ROOT=/opt/android-sdk ENV ANDROID_HOME=$ANDROID_SDK_ROOT +# sdkmanager closes the license-input pipe; pipefail would treat yes's SIGPIPE as a failure. +# hadolint ignore=DL4006 RUN . /etc/profile.d/qgc.sh && \ mkdir -p $ANDROID_SDK_ROOT/cmdline-tools/latest && \ wget "https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS}_latest.zip" -O /opt/cmdline-tools.zip && \