From 7c357960f30eb13e854c039f74ec96f695d963d3 Mon Sep 17 00:00:00 2001 From: Oleg Shparber Date: Sat, 4 Apr 2026 00:57:05 +0300 Subject: [PATCH] build(cmake): add clang-tidy integration (#1796) Add helper scripts to run tidy/format on Windows. --- .clang-tidy | 21 ++++++++++++ .clangd | 2 ++ CMakeLists.txt | 11 ++++++ CMakePresets.json | 1 + REUSE.toml | 4 ++- tools/run-clang-format.ps1 | 51 ++++++++++++++++++++++++++++ tools/run-clang-tidy.ps1 | 69 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 .clang-tidy create mode 100644 .clangd create mode 100644 tools/run-clang-format.ps1 create mode 100644 tools/run-clang-tidy.ps1 diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..424e29eb --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,21 @@ +--- +HeaderFilterRegex: 'src/.*' +Checks: > + *, + -abseil-*, + -altera-*, + -android-*, + -fuchsia-*, + -google-*, + google-build-using-namespace, + -linuxkernel-*, + -llvmlibc-*, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-owning-memory, + -bugprone-easily-swappable-parameters, + -concurrency-mt-unsafe, + -cppcoreguidelines-prefer-member-initializer, + -modernize-use-trailing-return-type, + -readability-identifier-length, + -readability-function-cognitive-complexity, + -readability-magic-numbers, diff --git a/.clangd b/.clangd new file mode 100644 index 00000000..fd3d2a8f --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: build/dev diff --git a/CMakeLists.txt b/CMakeLists.txt index c0caf313..f3eb17be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,17 @@ project(Zeal LANGUAGES CXX ) +option(ZEAL_USE_CLANG_TIDY "Enable clang-tidy static analysis" OFF) +if(ZEAL_USE_CLANG_TIDY) + find_program(CLANG_TIDY_PROGRAM clang-tidy) + if(CLANG_TIDY_PROGRAM) + message(STATUS "Using clang-tidy: ${CLANG_TIDY_PROGRAM}") + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_PROGRAM}") + else() + message(FATAL_ERROR "ZEAL_USE_CLANG_TIDY is enabled but clang-tidy was not found.") + endif() +endif() + option(ZEAL_USE_COMPILER_CACHE "Enable compiler cache (sccache/ccache)" OFF) if(ZEAL_USE_COMPILER_CACHE) find_program(CCACHE_PROGRAM sccache ccache) diff --git a/CMakePresets.json b/CMakePresets.json index 31764bdc..78035d6a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -42,6 +42,7 @@ "name": "dev", "inherits": ["release"], "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "ZEAL_DEPLOY_QT_ON_BUILD": "ON", "ZEAL_USE_COMPILER_CACHE": "ON" }, diff --git a/REUSE.toml b/REUSE.toml index d3aef997..c28db51b 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -28,8 +28,10 @@ SPDX-License-Identifier = "GPL-3.0-or-later" # MIT for auxiliary files. [[annotations]] path = [ - ".clang-format", ".clang-format-ignore", + ".clang-format", + ".clang-tidy", + ".clangd", ".editorconfig", ".gitattributes", ".github/**/*", diff --git a/tools/run-clang-format.ps1 b/tools/run-clang-format.ps1 new file mode 100644 index 00000000..81a3f12e --- /dev/null +++ b/tools/run-clang-format.ps1 @@ -0,0 +1,51 @@ +# +# run-clang-format.ps1 - Check or fix code formatting using clang-format +# +# SPDX-FileCopyrightText: Oleg Shparber, et al. +# SPDX-License-Identifier: MIT +# +# By default, runs in check mode (dry-run). +# Use -Fix to apply changes. +# Use -Staged to only process files staged for commit. +# Use -Verbose to print each file being processed. +# + +[CmdletBinding()] +param( + [switch]$Fix, + [switch]$Staged +) + +$srcDir = Join-Path -Path $PSScriptRoot -ChildPath "..\src" + +if ($Staged) { + $files = git diff --cached --name-only --diff-filter=ACMR -- "*.cpp" "*.h" | + ForEach-Object { Join-Path -Path $PSScriptRoot -ChildPath ".." -AdditionalChildPath $_ } | + Where-Object { Test-Path $_ } +} else { + $files = Get-ChildItem -Recurse $srcDir -Include *.cpp, *.h | + Select-Object -ExpandProperty FullName +} + +if (-not $files) { + Write-Information "No files to format." + exit 0 +} + +$formatArgs = @("--dry-run", "--Werror") +if ($Fix) { + $formatArgs = @("-i") +} + +$failed = $false +foreach ($file in $files) { + Write-Verbose "Processing $file" + & clang-format @formatArgs $file + if ($LASTEXITCODE -ne 0) { + $failed = $true + } +} + +if ($failed) { + exit 1 +} diff --git a/tools/run-clang-tidy.ps1 b/tools/run-clang-tidy.ps1 new file mode 100644 index 00000000..516b4bef --- /dev/null +++ b/tools/run-clang-tidy.ps1 @@ -0,0 +1,69 @@ +# +# run-clang-tidy.ps1 - Run clang-tidy static analysis on the codebase +# +# SPDX-FileCopyrightText: Oleg Shparber, et al. +# SPDX-License-Identifier: MIT +# +# By default, uses the project's .clang-tidy configuration. +# Use -Check to run a specific check (e.g., -Check readability-qualified-auto). +# Use -Fix to apply suggested fixes. +# Use -Staged to only process files staged for commit. +# Use -Verbose to print each file being processed. +# +# Requires a compile_commands.json in the build directory (build/dev). +# + +[CmdletBinding()] +param( + [string]$Check, + [switch]$Fix, + [switch]$Staged +) + +$buildDir = Join-Path -Path $PSScriptRoot -ChildPath "..\build\dev" + +if (-not (Test-Path "$buildDir/compile_commands.json")) { + Write-Error "compile_commands.json not found in $buildDir. Run 'cmake --preset dev' first." + exit 1 +} + +$buildDir = Resolve-Path $buildDir + +$tidyArgs = @("-p", $buildDir) +if ($Check) { + $tidyArgs += "--config={Checks: '-*,$Check', HeaderFilterRegex: 'src/.*'}" +} +if ($Fix) { + $tidyArgs += "--fix" +} + +$srcDir = Join-Path -Path $PSScriptRoot -ChildPath "..\src" + +if ($Staged) { + $files = git diff --cached --name-only --diff-filter=ACMR -- "*.cpp" | + ForEach-Object { Join-Path -Path $PSScriptRoot -ChildPath ".." -AdditionalChildPath $_ } | + Where-Object { Test-Path $_ } +} else { + $excludeDir = Join-Path -Path $srcDir -ChildPath "contrib" + $files = Get-ChildItem -Recurse $srcDir -Include *.cpp | + Where-Object { -not $_.FullName.StartsWith($excludeDir) } | + Select-Object -ExpandProperty FullName +} + +if (-not $files) { + Write-Information "No files to process." + exit 0 +} + +$failed = $false +foreach ($file in $files) { + Write-Verbose "Processing $file" + & clang-tidy @tidyArgs $file + if ($LASTEXITCODE -ne 0) { + $failed = $true + } +} + +if ($failed) { + exit 1 +}