340 lines
14 KiB
Plaintext
340 lines
14 KiB
Plaintext
$ErrorActionPreference = 'Stop'
|
|
$inputs = @PSC_INPUT_DIRS@
|
|
$excludeDirs = @PSC_EXCLUDE_DIRS@
|
|
$outputDir = '@PSC_OUTPUT_DIR@'
|
|
$format = '@PSC_FORMAT@'
|
|
$dotExe = '@PSC_DOT_EXECUTABLE@'
|
|
$maxDepth = @PSC_MAX_DEPTH@
|
|
$collapseThreshold = @PSC_COLLAPSE_THRESHOLD@
|
|
$excludeThirdParty = @PSC_EXCLUDE_THIRD_PARTY@
|
|
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
|
|
function Convert-To_Generic_Path([string]$path) {
|
|
return ([System.IO.Path]::GetFullPath($path)).Replace('\', '/')
|
|
}
|
|
function Convert-To_Dot_Text([string]$text) {
|
|
return $text.Replace('\', '/').Replace('"', '\"')
|
|
}
|
|
function Get-Root_Name([string]$root) {
|
|
$path = Convert-To_Generic_Path $root
|
|
if ($path -match '/module/radio$') {
|
|
return 'radio'
|
|
}
|
|
if ($path -match '/CPP_Core/Core$') {
|
|
return 'CPP_Core'
|
|
}
|
|
if ($path -match '/YSGraphic_Core$') {
|
|
return 'YSGraphic_Core'
|
|
}
|
|
return [System.IO.Path]::GetFileName($path)
|
|
}
|
|
function Get-Relative_Path([string]$root, [string]$path) {
|
|
$rootPath = Convert-To_Generic_Path $root
|
|
$fullPath = Convert-To_Generic_Path $path
|
|
if ($fullPath.StartsWith($rootPath)) {
|
|
return $fullPath.Substring($rootPath.Length).TrimStart('/')
|
|
}
|
|
return $fullPath
|
|
}
|
|
function Test-Excluded_Path([string]$path) {
|
|
$fullPath = Convert-To_Generic_Path $path
|
|
if ($excludeThirdParty -and $fullPath.Contains('/third_party/')) {
|
|
return $true
|
|
}
|
|
foreach ($excludeDir in $excludeDirs) {
|
|
if ($excludeDir.Length -eq 0) {
|
|
continue
|
|
}
|
|
$excludePath = Convert-To_Generic_Path $excludeDir
|
|
if ($fullPath.StartsWith($excludePath)) {
|
|
return $true
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
function Get-Source_Files([string[]]$roots) {
|
|
$extensions = @('.h', '.hpp', '.hh', '.cpp', '.cxx', '.cc')
|
|
$files = [System.Collections.Generic.List[object]]::new()
|
|
foreach ($root in $roots) {
|
|
if (!(Test-Path -LiteralPath $root)) {
|
|
continue
|
|
}
|
|
Get-ChildItem -LiteralPath $root -Recurse -File | Where-Object {
|
|
$extensions -contains $_.Extension.ToLowerInvariant() -and !(Test-Excluded_Path $_.FullName)
|
|
} | ForEach-Object {
|
|
$files.Add($_)
|
|
}
|
|
}
|
|
return $files
|
|
}
|
|
function Invoke-Dot([string]$name, [System.Collections.Generic.List[string]]$lines) {
|
|
$dotFile = Join-Path $outputDir ($name + '.dot')
|
|
$imageFile = Join-Path $outputDir ($name + '.' + $format)
|
|
Set-Content -LiteralPath $dotFile -Value $lines -Encoding UTF8
|
|
& $dotExe ('-T' + $format) $dotFile '-o' $imageFile
|
|
if ($LASTEXITCODE -ne 0) {
|
|
exit $LASTEXITCODE
|
|
}
|
|
}
|
|
function Write-Graphviz_Index() {
|
|
$items = @(
|
|
'radio_directory_tree',
|
|
'radio_module_dependency',
|
|
'radio_cmake_targets',
|
|
'radio_include_dependency',
|
|
'radio_render_architecture'
|
|
)
|
|
$html = [System.Collections.Generic.List[string]]::new()
|
|
$html.Add('<!doctype html><meta charset="utf-8"><title>Radio Graphviz</title><h1>Radio Graphviz</h1><ul>')
|
|
foreach ($item in $items) {
|
|
$file = $item + '.' + $format
|
|
if (Test-Path -LiteralPath (Join-Path $outputDir $file)) {
|
|
$html.Add('<li><a href="' + $file + '">' + $item + '</a></li>')
|
|
} else {
|
|
$html.Add('<li>' + $item + ' (not generated)</li>')
|
|
}
|
|
}
|
|
$html.Add('</ul>')
|
|
Set-Content -LiteralPath (Join-Path $outputDir 'index.html') -Value $html -Encoding UTF8
|
|
}
|
|
function Add-Header([System.Collections.Generic.List[string]]$lines, [string]$title) {
|
|
$lines.Add('digraph "' + (Convert-To_Dot_Text $title) + '" {')
|
|
$lines.Add('rankdir=LR;')
|
|
$lines.Add('splines=ortho;')
|
|
$lines.Add('concentrate=true;')
|
|
$lines.Add('forcelabels=true;')
|
|
$lines.Add('graph [fontname="Consolas", label="' + (Convert-To_Dot_Text $title) + '", labelloc=t, fontsize=18];')
|
|
$lines.Add('node [shape=box, style="rounded,filled", fontname="Consolas", fontsize=10];')
|
|
$lines.Add('edge [fontname="Consolas", fontsize=9];')
|
|
}
|
|
function Add-Legend([System.Collections.Generic.List[string]]$lines, [string]$mode) {
|
|
$lines.Add('subgraph cluster_legend {')
|
|
$lines.Add('label="legend";')
|
|
$lines.Add('style="rounded,dashed";')
|
|
$lines.Add('"legend_module" [label="module/folder", fillcolor="#dbeafe", tooltip="聚合模块或目录节点"];')
|
|
$lines.Add('"legend_target" [label="cmake target", fillcolor="#dcfce7", tooltip="CMake target 节点"];')
|
|
$lines.Add('"legend_buffer" [label="buffer/cache", fillcolor="#fef3c7", tooltip="缓冲区或长期缓存"];')
|
|
$lines.Add('"legend_runtime" [label="runtime", fillcolor="#fee2e2", tooltip="运行期任务或 Qt 绘制入口"];')
|
|
if ($mode -eq 'include') {
|
|
$lines.Add('"legend_edge" [label="include:N node = include count", fillcolor="#f8fafc"];')
|
|
} else {
|
|
$lines.Add('"legend_edge" [label="edge label = relation", fillcolor="#f8fafc"];')
|
|
}
|
|
$lines.Add('}')
|
|
}
|
|
function Get-Immediate_Source_Count([string]$dir) {
|
|
$extensions = @('.h', '.hpp', '.hh', '.cpp', '.cxx', '.cc')
|
|
return @(Get-ChildItem -LiteralPath $dir -File | Where-Object {
|
|
$extensions -contains $_.Extension.ToLowerInvariant()
|
|
}).Count
|
|
}
|
|
function Get-Visible_Directories([string]$dir) {
|
|
return @(Get-ChildItem -LiteralPath $dir -Directory | Where-Object {
|
|
!(Test-Excluded_Path $_.FullName)
|
|
})
|
|
}
|
|
function Add-Directory_Node([System.Collections.Generic.List[string]]$lines, [string]$root, [string]$dir, [int]$depth, [string]$parentId) {
|
|
if ($depth -gt $maxDepth -or (Test-Excluded_Path $dir)) {
|
|
return
|
|
}
|
|
$rootName = Get-Root_Name $root
|
|
$relative = Get-Relative_Path $root $dir
|
|
$name = if ($relative.Length -eq 0) { $rootName } else { [System.IO.Path]::GetFileName($dir) }
|
|
$idPath = if ($relative.Length -eq 0) { $rootName } else { $rootName + '/' + $relative }
|
|
$id = 'dir:' + $idPath
|
|
$fileCount = Get-Immediate_Source_Count $dir
|
|
$children = Get-Visible_Directories $dir
|
|
$label = (Convert-To_Dot_Text $name) + '\nfiles: ' + $fileCount + '\ndirs: ' + $children.Count
|
|
$tooltip = Convert-To_Dot_Text $idPath
|
|
$lines.Add('"' + $id + '" [label="' + $label + '", tooltip="' + $tooltip + '", fillcolor="#dbeafe"];')
|
|
if ($parentId.Length -gt 0) {
|
|
$lines.Add('"' + $parentId + '" -> "' + $id + '" [xlabel="contains"];')
|
|
}
|
|
if ($depth -eq $maxDepth) {
|
|
return
|
|
}
|
|
if ($fileCount -gt $collapseThreshold -and $depth -gt 0) {
|
|
return
|
|
}
|
|
foreach ($child in $children) {
|
|
Add-Directory_Node $lines $root $child.FullName ($depth + 1) $id
|
|
}
|
|
}
|
|
function New-Directory_Tree_Graph() {
|
|
$lines = [System.Collections.Generic.List[string]]::new()
|
|
Add-Header $lines 'radio directory_tree'
|
|
foreach ($root in $inputs) {
|
|
if (!(Test-Path -LiteralPath $root)) {
|
|
continue
|
|
}
|
|
$rootName = Get-Root_Name $root
|
|
$clusterName = $rootName.Replace('-', '_').Replace('/', '_')
|
|
$lines.Add('subgraph "cluster_' + $clusterName + '" {')
|
|
$lines.Add('label="' + (Convert-To_Dot_Text $rootName) + '";')
|
|
$lines.Add('style="rounded";')
|
|
Add-Directory_Node $lines $root $root 0 ''
|
|
$lines.Add('}')
|
|
}
|
|
Add-Legend $lines 'directory'
|
|
$lines.Add('}')
|
|
Invoke-Dot 'radio_directory_tree' $lines
|
|
}
|
|
function Get-Root_For_File([string]$path) {
|
|
$fullPath = Convert-To_Generic_Path $path
|
|
foreach ($root in $inputs) {
|
|
$rootPath = Convert-To_Generic_Path $root
|
|
if ($fullPath.StartsWith($rootPath)) {
|
|
return $root
|
|
}
|
|
}
|
|
return ''
|
|
}
|
|
function Get-Module_Key([string]$path, [int]$depth) {
|
|
$root = Get-Root_For_File $path
|
|
if ($root.Length -eq 0) {
|
|
return ''
|
|
}
|
|
$rootName = Get-Root_Name $root
|
|
$relative = Get-Relative_Path $root $path
|
|
$parts = @($relative.Split('/') | Where-Object { $_.Length -gt 0 })
|
|
if ($rootName -eq 'radio') {
|
|
$base = 'module/radio'
|
|
} elseif ($rootName -eq 'CPP_Core') {
|
|
$base = 'third_party/CPP_Core/Core'
|
|
} elseif ($rootName -eq 'YSGraphic_Core') {
|
|
$base = 'third_party/Graphic_Core/YSGraphic_Core'
|
|
} else {
|
|
$base = $rootName
|
|
}
|
|
if ($parts.Count -eq 0 -or $depth -eq 0) {
|
|
return $base
|
|
}
|
|
$take = [Math]::Min($depth, $parts.Count)
|
|
return $base + '/' + (($parts | Select-Object -First $take) -join '/')
|
|
}
|
|
function Test-Ignored_Include([string]$includeText) {
|
|
$text = $includeText.Trim()
|
|
if ($text -match '^(Q[A-ZA-z0-9_]*|Qt[A-ZA-z0-9_/]*|QObject|QWidget|QString|QVector|QMap|QList)$') {
|
|
return $true
|
|
}
|
|
if ($text -match '^(array|atomic|chrono|cmath|cstdint|cstdio|cstdlib|cstring|filesystem|functional|limits|map|memory|mutex|optional|set|span|string|string_view|thread|tuple|type_traits|unordered_map|utility|vector)$') {
|
|
return $true
|
|
}
|
|
if ($text -match '^(boost/|asio|windows\.h|winsock|d3d|GL/)') {
|
|
return $true
|
|
}
|
|
return $false
|
|
}
|
|
function Resolve-Include_Path([string]$includeText, [hashtable]$suffixMap, [hashtable]$nameMap) {
|
|
$key = $includeText.Replace('\', '/').ToLowerInvariant()
|
|
if ($suffixMap.ContainsKey($key)) {
|
|
return $suffixMap[$key]
|
|
}
|
|
$leaf = [System.IO.Path]::GetFileName($key)
|
|
if ($nameMap.ContainsKey($leaf)) {
|
|
return $nameMap[$leaf]
|
|
}
|
|
return ''
|
|
}
|
|
function Add-Dependency_Graph([string]$name, [string]$title, [int]$sourceDepth, [int]$targetDepth) {
|
|
$files = Get-Source_Files $inputs
|
|
$suffixMap = @{}
|
|
$nameMap = @{}
|
|
foreach ($file in $files) {
|
|
$root = Get-Root_For_File $file.FullName
|
|
$relative = (Get-Relative_Path $root $file.FullName).ToLowerInvariant()
|
|
$leaf = $file.Name.ToLowerInvariant()
|
|
$suffixMap[$relative] = $file.FullName
|
|
if (!$nameMap.ContainsKey($leaf)) {
|
|
$nameMap[$leaf] = $file.FullName
|
|
}
|
|
}
|
|
$edges = @{}
|
|
foreach ($file in $files) {
|
|
$sourceKey = Get-Module_Key $file.FullName $sourceDepth
|
|
if ($sourceKey.Length -eq 0) {
|
|
continue
|
|
}
|
|
Get-Content -LiteralPath $file.FullName | ForEach-Object {
|
|
if ($_ -match '^\s*#\s*include\s*[<"]([^">]+)[">]') {
|
|
$includeText = $Matches[1]
|
|
if (Test-Ignored_Include $includeText) {
|
|
return
|
|
}
|
|
$targetFile = Resolve-Include_Path $includeText $suffixMap $nameMap
|
|
if ($targetFile.Length -eq 0) {
|
|
return
|
|
}
|
|
$targetKey = Get-Module_Key $targetFile $targetDepth
|
|
if ($targetKey.Length -eq 0 -or $targetKey -eq $sourceKey) {
|
|
return
|
|
}
|
|
$edgeKey = $sourceKey + '|' + $targetKey
|
|
if (!$edges.ContainsKey($edgeKey)) {
|
|
$edges[$edgeKey] = 0
|
|
}
|
|
$edges[$edgeKey]++
|
|
}
|
|
}
|
|
}
|
|
$nodes = [System.Collections.Generic.HashSet[string]]::new()
|
|
$lines = [System.Collections.Generic.List[string]]::new()
|
|
Add-Header $lines $title
|
|
foreach ($edgeKey in $edges.Keys) {
|
|
$parts = $edgeKey.Split('|')
|
|
$nodes.Add($parts[0]) | Out-Null
|
|
$nodes.Add($parts[1]) | Out-Null
|
|
}
|
|
foreach ($node in $nodes) {
|
|
$label = Convert-To_Dot_Text ([System.IO.Path]::GetFileName($node))
|
|
$tooltip = Convert-To_Dot_Text $node
|
|
$lines.Add('"' + $node + '" [label="' + $label + '", tooltip="' + $tooltip + '", fillcolor="#dbeafe"];')
|
|
}
|
|
$edgeIndex = 0
|
|
foreach ($edgeKey in $edges.Keys) {
|
|
$parts = $edgeKey.Split('|')
|
|
$countNode = 'include_count:' + $edgeIndex
|
|
$lines.Add('"' + $countNode + '" [label="include: ' + $edges[$edgeKey] + '", tooltip="' + (Convert-To_Dot_Text $edgeKey) + '", fillcolor="#f8fafc"];')
|
|
$lines.Add('"' + $parts[0] + '" -> "' + $countNode + '";')
|
|
$lines.Add('"' + $countNode + '" -> "' + $parts[1] + '";')
|
|
$edgeIndex++
|
|
}
|
|
Add-Legend $lines 'include'
|
|
$lines.Add('}')
|
|
Invoke-Dot $name $lines
|
|
}
|
|
function New-Render_Architecture_Graph() {
|
|
$lines = [System.Collections.Generic.List[string]]::new()
|
|
Add-Header $lines 'radio render_architecture'
|
|
$lines.Add('"Plot" [label="Plot", fillcolor="#dbeafe"];')
|
|
$lines.Add('"Renderable" [label="Renderable", fillcolor="#dbeafe"];')
|
|
$lines.Add('"Render_Data" [label="Render_Data", fillcolor="#dcfce7"];')
|
|
$lines.Add('"Render_State" [label="Render_State", fillcolor="#dcfce7"];')
|
|
$lines.Add('"Input_Data" [label="Input_Data", fillcolor="#dcfce7"];')
|
|
$lines.Add('"Render_Pipeline" [label="Render_Pipeline", fillcolor="#dcfce7"];')
|
|
$lines.Add('"State_Buffer" [label="State_Buffer[3]", fillcolor="#fef3c7"];')
|
|
$lines.Add('"Input_Buffer" [label="Input_Buffer[3]", fillcolor="#fef3c7"];')
|
|
$lines.Add('"Render_Cache" [label="Render_Cache", fillcolor="#fef3c7"];')
|
|
$lines.Add('"Color_Buffer" [label="Color_Buffer[3]", fillcolor="#fef3c7"];')
|
|
$lines.Add('"CPU_Render_Task" [label="CPU Render Task", fillcolor="#fee2e2"];')
|
|
$lines.Add('"Qt_paintEvent" [label="Qt paintEvent", fillcolor="#fee2e2"];')
|
|
$lines.Add('"Plot" -> "Renderable" [xlabel="owns/schedules"];')
|
|
$lines.Add('"Renderable" -> "Render_Data" [xlabel="owns"];')
|
|
$lines.Add('"Render_Data" -> "Render_State" [xlabel="state snapshot"];')
|
|
$lines.Add('"Render_Data" -> "Input_Data" [xlabel="input batch"];')
|
|
$lines.Add('"Render_Data" -> "Render_Cache" [xlabel="long-lived cache"];')
|
|
$lines.Add('"Renderable" -> "Render_Pipeline" [xlabel="submit/finish/publish"];')
|
|
$lines.Add('"Render_Pipeline" -> "State_Buffer" [xlabel="edit/ready/render"];')
|
|
$lines.Add('"Render_Pipeline" -> "Input_Buffer" [xlabel="write/ready/render"];')
|
|
$lines.Add('"Render_Pipeline" -> "CPU_Render_Task" [xlabel="dispatch"];')
|
|
$lines.Add('"CPU_Render_Task" -> "Color_Buffer" [xlabel="write render color"];')
|
|
$lines.Add('"Color_Buffer" -> "Qt_paintEvent" [xlabel="ready/front consume"];')
|
|
Add-Legend $lines 'architecture'
|
|
$lines.Add('}')
|
|
Invoke-Dot 'radio_render_architecture' $lines
|
|
}
|
|
New-Directory_Tree_Graph
|
|
Add-Dependency_Graph 'radio_module_dependency' 'radio module_dependency' 0 0
|
|
Add-Dependency_Graph 'radio_include_dependency' 'radio include_dependency' 1 1
|
|
New-Render_Architecture_Graph
|
|
Write-Graphviz_Index
|