diff --git a/.clang-tidy b/.clang-tidy index 96cc8d04..38cbc675 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,21 +1,33 @@ --- HeaderFilterRegex: 'src/(?!contrib/).*' +# Skip CMake autogen output (AUTOUIC/AUTOMOC/AUTORCC headers under build/). +ExcludeHeaderFilterRegex: '.*_autogen[/\\].*' +# TODO: Reevaluate -modernize-use-nodiscard. Checks: > *, -abseil-*, -altera-*, -android-*, + -bugprone-easily-swappable-parameters, + -concurrency-mt-unsafe, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-prefer-member-initializer, + -cppcoreguidelines-pro-type-static-cast-downcast, -fuchsia-*, -google-*, google-build-using-namespace, -linuxkernel-*, + -llvm-prefer-static-over-anonymous-namespace, -llvmlibc-*, - -cppcoreguidelines-avoid-magic-numbers, - -cppcoreguidelines-owning-memory, - -bugprone-easily-swappable-parameters, - -concurrency-mt-unsafe, - -cppcoreguidelines-prefer-member-initializer, + -modernize-use-nodiscard, -modernize-use-trailing-return-type, - -readability-identifier-length, + -performance-enum-size, -readability-function-cognitive-complexity, + -readability-identifier-length, -readability-magic-numbers, +CheckOptions: + cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.ExcludeClasses: >- + ::std::map;::std::unordered_map;::std::flat_map; + ::QJsonObject;::QMap;::QHash;::QMultiMap;::QMultiHash; + ::QVariantMap;::QVariantHash diff --git a/.clangd b/.clangd index fd3d2a8f..d57915f9 100644 --- a/.clangd +++ b/.clangd @@ -1,2 +1,5 @@ CompileFlags: CompilationDatabase: build/dev +Diagnostics: + ClangTidy: + FastCheckFilter: None diff --git a/justfile b/justfile index 5785c0e8..29e277a9 100644 --- a/justfile +++ b/justfile @@ -44,13 +44,14 @@ format *args: pwsh tools/run-clang-format.ps1 {{args}} # Run clang-tidy (use -Fix to apply, -Staged for staged files only, -Check for a single check). +# Depends on `build` (not just `configure`) so AUTOUIC/AUTOMOC/AUTORCC outputs exist. [group('dev')] -lint *args: configure +lint *args: build pwsh tools/run-clang-tidy.ps1 {{args}} # Run clazy Qt-aware checks (use -Fix to apply, -Staged for staged files only, -Check for a single check or level). [group('dev')] -clazy *args: configure +clazy *args: build pwsh tools/run-clazy.ps1 {{args}} # Profile build time with -ftime-trace and emit a ClangBuildAnalyzer report. @@ -67,10 +68,10 @@ analyze-build-time: analyze-build-time-report: ClangBuildAnalyzer --analyze build/{{timetrace_preset}}/cba.bin -# Remove all build directories. +# Invoke the preset's clean target (removes built artifacts, keeps CMake cache). [group('dev')] clean: - rm -rf build + cmake --build --preset {{preset}} --target clean # Generate release notes for the given version, insert the corresponding # entry into the appdata, and bump CMakeLists.txt versions. diff --git a/tools/run-clang-tidy.ps1 b/tools/run-clang-tidy.ps1 index 247af2af..01b83d4a 100644 --- a/tools/run-clang-tidy.ps1 +++ b/tools/run-clang-tidy.ps1 @@ -34,15 +34,38 @@ if (-not (Test-Path "$buildDir/compile_commands.json")) { $buildDir = Resolve-Path $buildDir -$tidyArgs = @("-p", $buildDir) +# Strip CMake PCH flags from compile_commands.json before invoking clang-tidy. +# CMake injects MSVC-style /Yu, /Yc, /Fp, and /FI cmake_pch.h flags, but the +# resulting .pch is in MSVC's binary format which Clang cannot read, causing +# "file doesn't start with precompiled file magic" errors. Write a filtered +# copy of the database to a temp dir and point clang-tidy at it. +$tempDbDir = Join-Path ([System.IO.Path]::GetTempPath()) "zeal-clang-tidy-db-$PID" +New-Item -ItemType Directory -Path $tempDbDir -Force | Out-Null + +$compileDb = Get-Content "$buildDir/compile_commands.json" | ConvertFrom-Json +foreach ($entry in $compileDb) { + $cmd = $entry.command + # MSVC / clang-cl style. + $cmd = $cmd -replace '\s+/Yu\S*', '' + $cmd = $cmd -replace '\s+/Yc\S*', '' + $cmd = $cmd -replace '\s+/Fp\S*', '' + $cmd = $cmd -replace '\s+/FI\S*cmake_pch\S*', '' + # GCC / Clang style. + $cmd = $cmd -replace '\s+-include\s+\S*cmake_pch\S*', '' + $cmd = $cmd -replace '\s+-include-pch\s+\S*\.pch', '' + $entry.command = $cmd +} +$compileDb | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $tempDbDir "compile_commands.json") + +$tidyArgs = @("-p", $tempDbDir) if ($Check) { if ($Check -match '^clang-diagnostic-(.+)$') { # clang-diagnostic-* checks can't be enabled via Checks; use a no-op check # as a placeholder and enable the compiler warning via -extra-arg. - $tidyArgs += "--config={Checks: '-*,android-cloexec-accept', HeaderFilterRegex: 'src.*'}" + $tidyArgs += "--checks=-*,android-cloexec-accept" $tidyArgs += "-extra-arg=-W$($Matches[1])" } else { - $tidyArgs += "--config={Checks: '-*,$Check', HeaderFilterRegex: 'src.*'}" + $tidyArgs += "--checks=-*,$Check" } } if ($Fix) { @@ -52,7 +75,6 @@ if ($Fix) { $srcDir = Join-Path -Path $PSScriptRoot -ChildPath "..\src" # Only process files present in compile_commands.json to skip platform-specific files. -$compileDb = Get-Content "$buildDir/compile_commands.json" | ConvertFrom-Json $dbFiles = $compileDb | ForEach-Object { [System.IO.Path]::GetFullPath($_.file) } if ($Staged) { @@ -70,13 +92,45 @@ if ($Staged) { if (-not $files) { Write-Information "No files to process." + Remove-Item -Recurse -Force $tempDbDir exit 0 } -foreach ($file in $files) { - Write-Verbose "Processing $file" - & clang-tidy @tidyArgs --quiet $file - if ($LASTEXITCODE -ne 0) { - exit 1 +# Tally warnings by check name as clang-tidy output streams through. +# Each warning/error line ends with "[check-name]" or "[check1,check2,...]". +$counts = @{} + +try { + foreach ($file in $files) { + Write-Verbose "Processing $file" + & clang-tidy @tidyArgs --quiet $file 2>&1 | ForEach-Object { + $line = "$_" + $line + if ($line -match '\[([a-zA-Z0-9.,_-]+)\]\s*$') { + foreach ($check in $Matches[1] -split ',') { + $check = $check.Trim() + if ($counts.ContainsKey($check)) { + $counts[$check]++ + } else { + $counts[$check] = 1 + } + } + } + } + if ($LASTEXITCODE -ne 0) { + exit 1 + } + } +} finally { + Remove-Item -Recurse -Force $tempDbDir + if ($counts.Count -gt 0) { + "" + "Warning summary:" + $counts.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object { + " {0,6} {1}" -f $_.Value, $_.Key + } + $total = ($counts.Values | Measure-Object -Sum).Sum + " ------" + " {0,6} total" -f $total } }