diff --git a/.clang-tidy b/.clang-tidy index c5306d66b..4e0120145 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,32 +1,8 @@ # SPDX-FileCopyrightText: Copyright (C) swift Project Community / Contributors # SPDX-License-Identifier: CC0-1.0 -Checks: - - '-*' - - 'modernize-*' - - '-modernize-use-trailing-return-type' - - '-modernize-use-nodiscard' - - '-modernize-pass-by-value' - - '-modernize-avoid-c-arrays' - - 'bugprone-*' - - '-bugprone-easily-swappable-parameters' - - 'readability-*' - - '-readability-identifier-length' - - '-readability-implicit-bool-conversion' - - '-readability-braces-around-statements' - - '-readability-function-cognitive-complexity' - - '-readability-convert-member-functions-to-static' - - '-readability-math-missing-parentheses' - - '-readability-avoid-unconditional-preprocessor-if' - - '-readability-magic-numbers' - - '-readability-container-size-empty' - - 'cppcoreguidelines-*' - - '-cppcoreguidelines-avoid-magic-numbers' - - '-cppcoreguidelines-non-private-member-variables-in-classes' - - '-cppcoreguidelines-avoid-c-arrays' - - '-cppcoreguidelines-owning-memory' - - '-cppcoreguidelines-misleading-capture-default-by-value' - - '-cppcoreguidelines-pro-bounds-array-to-pointer-decay' +Checks: > + -*, CheckOptions: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 29a0074e6..486ea3de2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,7 @@ env: do_vatsim_key: ${{ github.event_name == 'push' }} do_symbols: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} do_doxygen: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + do_static_analysis: ${{ github.event_name == 'push' && github.ref != 'refs/heads/main' }} qt_version: 6.10.0 bitrock_version: qt-professional-24.7.0 bitrock_url: https://releases.installbuilder.com/installbuilder @@ -104,6 +105,62 @@ jobs: uses: actions/upload-pages-artifact@v3 with: path: docs/html/ + - name: Check if clang-tidy analysis is required + id: check_need_clang_tidy + run: | + if python3 -u scripts/run_static_analysis.py --check-changed-files; then + echo "should_run_clang_tidy=false" >> $GITHUB_OUTPUT + else + echo "should_run_clang_tidy=true" >> $GITHUB_OUTPUT + fi + - name: Checkout repository + if: ${{ steps.check_need_clang_tidy.outputs.should_run_clang_tidy == 'true' }} + uses: actions/checkout@v4 + with: + fetch-depth: '0' + submodules: 'true' + - name: Install Qt + if: ${{ steps.check_need_clang_tidy.outputs.should_run_clang_tidy == 'true' }} + uses: jurplel/install-qt-action@v4 + with: + version: ${{ env.qt_version }} + modules: 'qtmultimedia' + cache: true + - name: Install dependencies + if: ${{ steps.check_need_clang_tidy.outputs.should_run_clang_tidy == 'true' }} + run: | + sudo apt-get -y install dbus-x11 libglu1-mesa-dev libpulse-dev libdbus-1-dev ninja-build + pip3 install requests conan + - name: Checkout externals + if: ${{ env.use_externals == 'true' && steps.check_need_clang_tidy.outputs.should_run_clang_tidy == 'true' }} + uses: actions/checkout@v4 + env: + EXTERNALS_PAT: ${{ secrets.EXTERNALS_PAT }} + with: + repository: ${{ env.externals }} + ref: ${{ env.externals_sha }} + token: ${{ env.EXTERNALS_PAT }} + path: 'third_party/externals' + - name: Install conan dependencies + if: ${{ steps.check_need_clang_tidy.outputs.should_run_clang_tidy == 'true' }} + shell: bash + env: + ARTIFACTORY_USER: ${{ secrets.ARTIFACTORY_USER }} + ARTIFACTORY_TOKEN: ${{ secrets.ARTIFACTORY_TOKEN }} + run: | + conan profile detect + conan remote disable conancenter + conan remote add swift https://artifactory.swift-project.org/artifactory/api/conan/conan-local + conan remote login swift "$ARTIFACTORY_USER" --password "$ARTIFACTORY_TOKEN" + conan install . --output-folder=build_conan --deployer=full_deploy -pr=ci/profile_linux + - name: Run clang-tidy + if: ${{ steps.check_need_clang_tidy.outputs.should_run_clang_tidy == 'true' }} + shell: bash + run: | + mkdir build && pushd build + cmake .. --preset ci-build-linux-no-pch + popd + python3 scripts/run_static_analysis.py --clang-tidy --build-path build --changed-files-ci buildLinux: runs-on: ubuntu-22.04 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c8a5476d..cc8f841ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ option(SWIFT_MINIFY_DEBUG_SYMBOLS "Minify debug symbols" OFF) option(SWIFT_ONLY_XSWIFTBUS_WORKAROUND "Only build xswiftbus (useful when compiling on ARM)" OFF) option(SWIFT_USE_CRASHPAD "Use crashpad" OFF) +option(SWIFT_USE_PCH "Use precompiled headers" ON) # Shortcut to only build xswiftbus diff --git a/CMakePresets.json b/CMakePresets.json index 03bba8ed1..7c4a02749 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -77,6 +77,22 @@ "SWIFT_USE_CRASHPAD": "OFF" } }, + { + "name": "ci-build-linux-no-pch", + "displayName": "CI Build Linux without PCH (used for static code analysis)", + "inherits": "ci-build-linux", + "generator": "Ninja", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "VATSIM_KEY_JSON": "", + "SWIFT_USE_PCH": "OFF" + } + }, { "name": "dev-debug", "displayName": "Development Debug", diff --git a/scripts/run_clang_tidy.py b/scripts/run_clang_tidy.py deleted file mode 100644 index f20462ee6..000000000 --- a/scripts/run_clang_tidy.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/env python - -# SPDX-FileCopyrightText: Copyright (C) 2025 swift Project Community / Contributors -# SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 - -"""This script runs clang-tidy only on files that have changed compared to the latest origin/main branch.""" - -import argparse - -import utils -import os -import subprocess - -def run_clang_tidy(build_path: str): - src_path = utils.get_swift_source_path() - os.chdir(src_path) - - result = subprocess.run( - ['git', 'diff', '--name-only', 'origin/main...HEAD'], - check=True, - stdout=subprocess.PIPE, - text=True - ) - - files = [line for line in result.stdout.splitlines() if (line.endswith('.cpp') or line.endswith('.h')) and not line.startswith('tests')] - nproc = os.cpu_count() - subprocess.run([ - 'xargs', - '-P', str(nproc), - '-n', '1', - '-r', - 'clang-tidy', - '-p', build_path, - '--warnings-as-errors', '*' - ], input='\n'.join(files), text=True, check=True) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(prog="swift clang-tidy helper") - parser.add_argument("--build-path", required=True, help='Path to build folder') - args = parser.parse_args() - - run_clang_tidy(args.build_path) diff --git a/scripts/run_static_analysis.py b/scripts/run_static_analysis.py new file mode 100644 index 000000000..76d11f73e --- /dev/null +++ b/scripts/run_static_analysis.py @@ -0,0 +1,160 @@ +#!/bin/env python + +# SPDX-FileCopyrightText: Copyright (C) 2025 swift Project Community / Contributors +# SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 + +"""This script runs static analysis tools only on files that have changed compared to the latest origin/main branch.""" + +import argparse +import json +import os +import subprocess +from subprocess import CalledProcessError + +import utils + +# Currently we are not checking all directories as they might need to run compilation first for Qt UIC +CHECK_DIRECTORIES = [ + "src/core", + "src/misc", + "src/input", + "src/sound", +] + + +def _get_all_files(build_path: str) -> list[str]: + """Get all files inside the compile commands.""" + src_path = utils.get_swift_source_path() + os.chdir(src_path) + + with open(os.path.join(build_path, "compile_commands.json"), 'r') as f: + commands = json.load(f) + commands = set([os.path.relpath(entry["file"], utils.get_swift_source_path()) for entry in commands]) + + commands = [command for command in commands if + not command.startswith("third_party") and not command.startswith(build_path)] + return commands + + +def _get_all_files_ci(build_path: str) -> list[str]: + """Get all files in the compile commands and which will be checked in CI runs.""" + src_path = utils.get_swift_source_path() + os.chdir(src_path) + + with open(os.path.join(build_path, "compile_commands.json"), 'r') as f: + commands = json.load(f) + commands = set([os.path.relpath(entry["file"], utils.get_swift_source_path()) for entry in commands]) + + commands = [command for command in commands if command.startswith(tuple(CHECK_DIRECTORIES))] + return commands + + +def _get_changed_files_ci(build_path: str) -> set[str]: + """Get all files in the compile commands which has changed since the last main commit and are in the included directories.""" + src_path = utils.get_swift_source_path() + os.chdir(src_path) + + result = subprocess.run( + ['git', 'diff', '--name-only', 'origin/main'], + check=True, + stdout=subprocess.PIPE, + text=True + ) + with open(os.path.join(build_path, "compile_commands.json"), 'r') as f: + commands = json.load(f) + commands = set([os.path.relpath(entry["file"], utils.get_swift_source_path()) for entry in commands]) + + files = set([line for line in result.stdout.splitlines() if + (line.endswith('.cpp') or line.endswith('.h')) and line.startswith(tuple(CHECK_DIRECTORIES))]) + return files & commands + + +def _has_changed_files() -> bool: + src_path = utils.get_swift_source_path() + os.chdir(src_path) + + result = subprocess.run( + ['git', 'diff', '--name-only', 'origin/main'], + check=True, + stdout=subprocess.PIPE, + text=True + ) + + return len([line for line in result.stdout.splitlines() if + (line.endswith('.cpp') or line.endswith('.h')) and line.startswith(tuple(CHECK_DIRECTORIES))]) > 0 + + +def run_clang_tidy(build_path: str, changed_source_files: set[str]): + print(f"Run clang-tidy on files: {changed_source_files}") + nproc = 10 + try: + subprocess.run([ + 'xargs', + '-P', str(nproc), + '-n', '1', + '-r', + 'clang-tidy', + '-p', build_path, + '--warnings-as-errors', '*', + '--quiet', + '--header-filter', f'{utils.get_swift_source_path()}/src/', + ], input='\n'.join(changed_source_files), text=True, check=True) + except CalledProcessError: + print("Clang-tidy finished with errors") + exit(1) + + +def run_clazy(build_path: str, changed_source_files: set[str]): + print(f"Run clazy on files: {changed_source_files}") + nproc = os.cpu_count() + try: + subprocess.run([ + 'clazy-standalone', + '-p', build_path, + "-extra-arg", "-Werror", + "-extra-arg", "-Wno-unnecessary-virtual-specifier", + '--header-filter', '(config|core|gui|input|misc|plugins|sound|swiftcore|swiftdata|swiftguistandard|swiftlauncher|xswiftbus)/', + *changed_source_files + ], text=True, check=True) + except CalledProcessError: + print("Clazy finished with errors") + exit(1) + + +def main(): + parser = argparse.ArgumentParser(prog="swift clang-tidy helper") + parser.add_argument("--build-path", help='Path to build folder') + + check_mode = parser.add_mutually_exclusive_group(required=True) + check_mode.add_argument("--all-files", action="store_true", + help="Run check on all files in the compile commands") + check_mode.add_argument("--all-files-ci", action="store_true", + help="Run check on all files in the compile commands and which will be checked in CI runs.") + check_mode.add_argument("--changed-files-ci", action="store_true", + help="Run check on all files in the compile commands which are changed since the last main commit and are in the included directories (no Qt UIC files).") + check_mode.add_argument("--check-changed-files", action="store_true", + help="Check if files have changed for evaluation. Program exits with 0 if no files changed; with 1 otherwise") + + parser.add_argument("--clang-tidy", action="store_true", + help="Run clang-tidy checks") + parser.add_argument("--clazy", action="store_true", + help="Run clazy checks") + args = parser.parse_args() + + if args.all_files: + source_files = _get_all_files(args.build_path) + elif args.all_files_ci: + source_files = _get_all_files_ci(args.build_path) + elif args.changed_files_ci: + source_files = _get_changed_files_ci(args.build_path) + else: + exit(1 if _has_changed_files() else 0) + + if args.clang_tidy: + run_clang_tidy(args.build_path, source_files) + elif args.clazy: + run_clazy(args.build_path, source_files) + + +if __name__ == '__main__': + main() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1a4f7e67f..48fba4b95 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -272,11 +272,14 @@ endif() target_compile_definitions(core PRIVATE BUILD_SWIFT_CORE_LIB) -target_precompile_headers(core - PRIVATE - ${SWIFT_MISC_PCH} - ${SWIFT_CORE_PCH} -) + +if(SWIFT_USE_PCH) + target_precompile_headers(core + PRIVATE + ${SWIFT_MISC_PCH} + ${SWIFT_CORE_PCH} + ) +endif() install(TARGETS core LIBRARY DESTINATION lib diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 0d95bbf76..3152aafe9 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -881,12 +881,14 @@ target_link_libraries(gui target_compile_definitions(gui PRIVATE BUILD_SWIFT_GUI_LIB) -target_precompile_headers(gui - PRIVATE - ${SWIFT_MISC_PCH} - ${SWIFT_CORE_PCH} - ${SWIFT_GUI_PCH} -) +if(SWIFT_USE_PCH) + target_precompile_headers(gui + PRIVATE + ${SWIFT_MISC_PCH} + ${SWIFT_CORE_PCH} + ${SWIFT_GUI_PCH} + ) +endif() install(TARGETS gui LIBRARY DESTINATION lib diff --git a/src/misc/CMakeLists.txt b/src/misc/CMakeLists.txt index 40bd0d4c2..018a84ab4 100644 --- a/src/misc/CMakeLists.txt +++ b/src/misc/CMakeLists.txt @@ -718,10 +718,12 @@ if(APPLE) target_link_libraries(misc PRIVATE "-lbsm -framework AVFoundation -framework Security -framework CoreFoundation -framework ApplicationServices -framework Foundation -framework IOKit") endif() -target_precompile_headers(misc - PRIVATE - ${SWIFT_MISC_PCH} -) +if(SWIFT_USE_PCH) + target_precompile_headers(misc + PRIVATE + ${SWIFT_MISC_PCH} + ) +endif() install(TARGETS misc LIBRARY DESTINATION lib