ci: Run static code analysis

This commit is contained in:
Lars Toenning
2025-10-25 12:31:19 +02:00
parent e29818a5be
commit 899b5d6ba4
9 changed files with 258 additions and 84 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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