$ErrorActionPreference = "Stop" $inputDirs = @PSC_CPPCHECK_INPUT_DIRS@ $excludeDirs = @PSC_CPPCHECK_EXCLUDE_DIRS@ $enabledChecks = @PSC_CPPCHECK_ENABLE@ $suppressions = @PSC_CPPCHECK_SUPPRESSIONS@ $outputDir = "@PSC_CPPCHECK_OUTPUT_DIR@" $compileCommands = "@PSC_CPPCHECK_COMPILE_COMMANDS@" $configuredCppcheck = "@PSC_CPPCHECK_EXECUTABLE@" $checkLevel = "@PSC_CPPCHECK_CHECK_LEVEL@" $jobs = [int]"@PSC_CPPCHECK_JOBS@" function Resolve-Tool([string]$Configured, [string]$Name, [string[]]$Fallbacks) { if ($Configured -and (Test-Path -LiteralPath $Configured)) { return (Resolve-Path -LiteralPath $Configured).Path } $command = Get-Command $Name -ErrorAction SilentlyContinue | Select-Object -First 1 if ($command) { return $command.Source } foreach ($fallback in $Fallbacks) { if (Test-Path -LiteralPath $fallback) { return (Resolve-Path -LiteralPath $fallback).Path } } throw "$Name executable was not found" } function Normalize-Path([string]$Path) { return [IO.Path]::GetFullPath($Path).Replace("\", "/").TrimEnd("/") } function Test-InRoots([string]$Path, [string[]]$Roots) { $normalized = Normalize-Path $Path foreach ($root in $Roots) { if ($normalized.StartsWith($root + "/", [StringComparison]::OrdinalIgnoreCase) -or $normalized.Equals($root, [StringComparison]::OrdinalIgnoreCase)) { return $true } } return $false } $cppcheck = Resolve-Tool $configuredCppcheck "cppcheck" @("C:\Program Files\Cppcheck\cppcheck.exe") if (!(Test-Path -LiteralPath $compileCommands)) { throw "compile_commands.json was not found at $compileCommands" } $roots = @($inputDirs | ForEach-Object { Normalize-Path $_ }) $excludes = @($excludeDirs | ForEach-Object { Normalize-Path $_ }) New-Item -ItemType Directory -Force -Path $outputDir | Out-Null $database = Get-Content -LiteralPath $compileCommands -Raw | ConvertFrom-Json $selected = @($database | Where-Object { $file = Normalize-Path $_.file (Test-InRoots $file $roots) -and !(Test-InRoots $file $excludes) -and [IO.Path]::GetExtension($file) -in @(".c", ".cc", ".cpp", ".cxx") }) if ($selected.Count -eq 0) { throw "No translation units matched the configured input directories" } $filteredDatabase = Join-Path $outputDir "compile_commands.json" [IO.File]::WriteAllText($filteredDatabase, ($selected | ConvertTo-Json -Depth 8), [Text.UTF8Encoding]::new($false)) $xmlPath = Join-Path $outputDir "cppcheck.xml" $cacheDir = Join-Path $outputDir "cache" New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null $arguments = @( "--project=$filteredDatabase", "--enable=$($enabledChecks -join ',')", "--check-level=$checkLevel", "--std=c++20", "--inconclusive", "--inline-suppr", "--output-format=xmlv3", "--output-file=$xmlPath", "--cppcheck-build-dir=$cacheDir" "-j" $jobs ) foreach ($suppression in $suppressions) { $arguments += "--suppress=$suppression" } & $cppcheck @arguments $toolExitCode = $LASTEXITCODE if (!(Test-Path -LiteralPath $xmlPath)) { throw "Cppcheck did not produce $xmlPath" } [xml]$xml = Get-Content -LiteralPath $xmlPath -Raw $records = [Collections.Generic.List[object]]::new() foreach ($errorNode in $xml.results.errors.error) { $locations = @($errorNode.location) if ($locations.Count -eq 0) { $records.Add([pscustomobject]@{ Severity = [string]$errorNode.severity Id = [string]$errorNode.id Message = [string]$errorNode.msg File = "" Line = 0 Column = 0 }) continue } foreach ($location in $locations) { $locationFile = [string]$location.file if ($locationFile -and (!(Test-InRoots $locationFile $roots) -or (Test-InRoots $locationFile $excludes))) { continue } $records.Add([pscustomobject]@{ Severity = [string]$errorNode.severity Id = [string]$errorNode.id Message = [string]$errorNode.msg File = $locationFile Line = [int]$location.line Column = [int]$location.column }) } } $records | Export-Csv -LiteralPath (Join-Path $outputDir "cppcheck.csv") -NoTypeInformation -Encoding UTF8 $severity = @($records | Group-Object Severity | Sort-Object Count -Descending | ForEach-Object { [pscustomobject]@{ severity = $_.Name; count = $_.Count } }) $summary = [ordered]@{ tool = "cppcheck" version = (& $cppcheck --version | Select-Object -First 1).Trim() translation_units = $selected.Count tool_exit_code = $toolExitCode findings = $records.Count severity = $severity top = @($records | Select-Object -First 100) } [IO.File]::WriteAllText((Join-Path $outputDir "summary.json"), ($summary | ConvertTo-Json -Depth 8), [Text.UTF8Encoding]::new($false)) Write-Output "Cppcheck report: $outputDir"