结构优化

This commit is contained in:
2026-07-30 18:09:03 +08:00
parent a6f8d87bf2
commit 294dc7f32b
33 changed files with 21 additions and 12 deletions
@@ -0,0 +1,119 @@
$ErrorActionPreference = "Stop"
$inputDirs = @PSC_LLVM_INPUT_DIRS@
$excludeDirs = @PSC_LLVM_EXCLUDE_DIRS@
$outputDir = "@PSC_LLVM_OUTPUT_DIR@"
$compileCommands = "@PSC_LLVM_COMPILE_COMMANDS@"
$configuredClangTidy = "@PSC_LLVM_CLANG_TIDY@"
$topN = [int]"@PSC_LLVM_TOP_N@"
$threshold = [int]"@PSC_LLVM_THRESHOLD@"
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
}
$clangTidy = Resolve-Tool $configuredClangTidy "clang-tidy" @("C:\Program Files\LLVM\bin\clang-tidy.exe")
$clangCl = Join-Path (Split-Path $clangTidy -Parent) "clang-cl.exe"
if (!(Test-Path -LiteralPath $clangCl)) {
throw "clang-cl executable was not found next to clang-tidy"
}
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
$databaseDir = Join-Path $outputDir "compile_database"
New-Item -ItemType Directory -Force -Path $databaseDir | Out-Null
$database = Get-Content -LiteralPath $compileCommands -Raw | ConvertFrom-Json
$selected = [Collections.Generic.List[object]]::new()
foreach ($entry in $database) {
$file = Normalize-Path $entry.file
if (!(Test-InRoots $file $roots) -or (Test-InRoots $file $excludes)) {
continue
}
if ([IO.Path]::GetExtension($file) -notin @(".c", ".cc", ".cpp", ".cxx")) {
continue
}
$command = [string]$entry.command
$escapedClang = '"' + $clangCl + '"'
$command = [regex]::Replace($command, '^\s*(?:"[^"]+"|[^\s]+?)(?:cl(?:\.exe|\.bat))\s+', $escapedClang + " ", [Text.RegularExpressions.RegexOptions]::IgnoreCase)
$command = [regex]::Replace($command, '(?i)(?:^|\s)[/-](?:Fo|Fd|Fp|FI|Yu|Yc)(?:"[^"]*"|\S+)', " ")
$command = [regex]::Replace($command, '(?i)(?:^|\s)(?:/FS|/nologo|/RTC1|/Ob\d|/Od|-Z7|-MDd?)\b', " ")
$command = [regex]::Replace($command, '(?i)-external:I', "-I")
$command = [regex]::Replace($command, '(?i)\s-external:W\d\b', " ")
$command = [regex]::Replace($command, '\s+', " ").Trim()
$selected.Add([ordered]@{
directory = $entry.directory
command = $command
file = $entry.file
output = $entry.output
})
}
if ($selected.Count -eq 0) {
throw "No translation units matched the configured input directories"
}
$sanitizedDatabase = Join-Path $databaseDir "compile_commands.json"
[IO.File]::WriteAllText($sanitizedDatabase, ($selected | ConvertTo-Json -Depth 8), [Text.UTF8Encoding]::new($false))
$rawPath = Join-Path $outputDir "clang_tidy_raw.txt"
$rawWriter = [IO.StreamWriter]::new($rawPath, $false, [Text.UTF8Encoding]::new($false))
$records = [Collections.Generic.List[object]]::new()
$diagnosticErrors = 0
$config = "{CheckOptions: {readability-function-cognitive-complexity.Threshold: $threshold, readability-function-cognitive-complexity.DescribeBasicIncrements: false, readability-function-cognitive-complexity.IgnoreMacros: false}}"
foreach ($entry in $selected) {
$lines = & $clangTidy $entry.file "-p=$databaseDir" "-checks=-*,readability-function-cognitive-complexity" "-config=$config" "--quiet" 2>&1
foreach ($lineObject in $lines) {
$line = [string]$lineObject
$rawWriter.WriteLine($line)
if ($line -match '^(?<file>.+?):(?<line>\d+):(?<column>\d+): warning: (?:(?:function ''(?<name>[^'']+)'')|(?<lambda>lambda)) has cognitive complexity of (?<score>\d+)') {
$file = Normalize-Path $Matches.file
if ((Test-InRoots $file $roots) -and !(Test-InRoots $file $excludes)) {
$records.Add([pscustomobject]@{
Score = [int]$Matches.score
Function = if ($Matches.name) { $Matches.name } else { "<lambda>" }
File = $file
Line = [int]$Matches.line
Column = [int]$Matches.column
})
}
}
elseif ($line -match '(^| )error:') {
$diagnosticErrors++
}
}
}
$rawWriter.Dispose()
$top = @($records | Sort-Object @{Expression = "Score"; Descending = $true}, File, Line | Select-Object -First $topN)
$top | Export-Csv -LiteralPath (Join-Path $outputDir "cognitive_complexity.csv") -NoTypeInformation -Encoding UTF8
$summary = [ordered]@{
tool = "clang-tidy"
version = (& $clangTidy --version | Select-Object -First 1).Trim()
threshold = $threshold
translation_units = $selected.Count
findings = $records.Count
diagnostic_errors = $diagnosticErrors
top = $top
}
[IO.File]::WriteAllText((Join-Path $outputDir "summary.json"), ($summary | ConvertTo-Json -Depth 8), [Text.UTF8Encoding]::new($false))
Write-Output "LLVM cognitive complexity report: $outputDir"
@@ -0,0 +1,21 @@
param(
[string]$Version = "",
[switch]$Force
)
$ErrorActionPreference = "Stop"
$arguments = @("install", "--id", "LLVM.LLVM", "--exact", "--silent", "--accept-package-agreements", "--accept-source-agreements", "--disable-interactivity")
if ($Version) {
$arguments += @("--version", $Version)
}
if ($Force) {
$arguments += "--force"
}
& winget @arguments
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$clangTidy = "C:\Program Files\LLVM\bin\clang-tidy.exe"
if (!(Test-Path -LiteralPath $clangTidy)) {
throw "clang-tidy was not installed at $clangTidy"
}
& $clangTidy --version
@@ -0,0 +1 @@
include(${CMAKE_CURRENT_LIST_DIR}/main.cmake)
@@ -0,0 +1,49 @@
function(psc_llvm_ps_array out_value)
set(values)
foreach(item IN LISTS ARGN)
string(REPLACE "'" "''" escaped "${item}")
list(APPEND values "'${escaped}'")
endforeach()
string(REPLACE ";" "," value "${values}")
set(${out_value} "@(${value})" PARENT_SCOPE)
endfunction()
function(psc_add_llvm_cognitive_target target_name)
set(options)
set(oneValueArgs OUTPUT_DIR COMPILE_COMMANDS CLANG_TIDY_EXECUTABLE TOP_N THRESHOLD)
set(multiValueArgs INPUT_DIRS EXCLUDE_DIRS)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if (NOT ARG_OUTPUT_DIR)
set(ARG_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/metrics/${target_name}/llvm")
endif ()
if (NOT ARG_COMPILE_COMMANDS)
set(ARG_COMPILE_COMMANDS "${CMAKE_BINARY_DIR}/compile_commands.json")
endif ()
if (NOT ARG_CLANG_TIDY_EXECUTABLE)
set(ARG_CLANG_TIDY_EXECUTABLE "C:/Program Files/LLVM/bin/clang-tidy.exe")
endif ()
if (NOT ARG_TOP_N)
set(ARG_TOP_N 50)
endif ()
if (NOT DEFINED ARG_THRESHOLD)
set(ARG_THRESHOLD 0)
endif ()
if (NOT ARG_INPUT_DIRS)
set(ARG_INPUT_DIRS "${CMAKE_SOURCE_DIR}")
endif ()
psc_llvm_ps_array(PSC_LLVM_INPUT_DIRS ${ARG_INPUT_DIRS})
psc_llvm_ps_array(PSC_LLVM_EXCLUDE_DIRS ${ARG_EXCLUDE_DIRS})
set(PSC_LLVM_OUTPUT_DIR "${ARG_OUTPUT_DIR}")
set(PSC_LLVM_COMPILE_COMMANDS "${ARG_COMPILE_COMMANDS}")
set(PSC_LLVM_CLANG_TIDY "${ARG_CLANG_TIDY_EXECUTABLE}")
set(PSC_LLVM_TOP_N "${ARG_TOP_N}")
set(PSC_LLVM_THRESHOLD "${ARG_THRESHOLD}")
set(script_dir "${CMAKE_CURRENT_BINARY_DIR}/psc_metrics_scripts/${target_name}")
file(MAKE_DIRECTORY "${script_dir}")
set(script "${script_dir}/${target_name}_llvm_cognitive.ps1")
configure_file("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Cognitive_Complexity.ps1.in" "${script}" @ONLY)
add_custom_target(${target_name}
COMMAND pwsh -NoProfile -ExecutionPolicy Bypass -File "${script}"
VERBATIM
)
set(${target_name}_OUTPUT_DIR "${ARG_OUTPUT_DIR}" PARENT_SCOPE)
endfunction()