Files
gamedev-the-steward/tools/quality.ps1
T
2026-08-12 20:10:54 +02:00

605 lines
22 KiB
PowerShell

#!/usr/bin/env pwsh
# quality.ps1 — Local quality gate for GDScript projects (Windows).
# Usage: pwsh ./tools/quality.ps1 [-Changed] [--changed]
param([switch]$Changed)
if ($args -contains '--changed') { $Changed = $true }
$ROOT = Split-Path -Parent $PSScriptRoot
$script:PreviousLocation = Get-Location
Set-Location $ROOT
$LOG = "logs/quality/latest"
New-Item -ItemType Directory -Path $LOG -Force | Out-Null
$GODOT_PROFILE = Join-Path $ROOT "logs/quality/godot_profile"
New-Item -ItemType Directory -Path $GODOT_PROFILE -Force | Out-Null
$OVERALL = $false
$ERRORS = @()
$FIXES = @()
$OWNED_GDSCRIPT_ROOTS = @("player", "simulation", "tests", "tools", "world")
$CROSS_PLATFORM_PLUGIN_FILES = @(
"addons/terrain_3d/terrain.gdextension",
"addons/terrain_3d/bin/libterrain.macos.debug.framework/libterrain.macos.debug",
"addons/terrain_3d/bin/libterrain.macos.release.framework/libterrain.macos.release",
"addons/terrain_3d/bin/libterrain.windows.debug.x86_64.dll",
"addons/terrain_3d/bin/libterrain.windows.release.x86_64.dll"
)
$REQUIRED_GODOT_SERIES = "4.7"
$RUNNING_ON_MACOS = [Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
[Runtime.InteropServices.OSPlatform]::OSX
)
function Exit-Quality {
param([int]$ExitCode)
Set-Location $script:PreviousLocation
exit $ExitCode
}
# -- tool detection -----------------------------------------------------------
function ConvertTo-ConsoleGodotPath($Path) {
if (-not $Path) { return $null }
$separator = [IO.Path]::DirectorySeparatorChar
$normalized = $Path.Replace([char]47, $separator).Replace([char]92, $separator).Trim('"')
if ($normalized.EndsWith(".exe")) {
$consolePath = $normalized -replace '\.exe$', '_console.exe'
if (Test-Path $consolePath) { return $consolePath }
}
if (Test-Path $normalized) { return $normalized }
return $null
}
function Find-ProjectGodot {
$metadata = Join-Path $ROOT ".godot/editor/project_metadata.cfg"
if (-not (Test-Path $metadata)) { return $null }
$line = Select-String -Path $metadata -Pattern '^executable_path=' | Select-Object -First 1
if (-not $line) { return $null }
$rawPath = ($line.Line -replace '^executable_path=', '').Trim().Trim('"')
return ConvertTo-ConsoleGodotPath $rawPath
}
function Find-Godot {
if ($env:GODOT_BIN) { return (ConvertTo-ConsoleGodotPath $env:GODOT_BIN) }
$projectGodot = Find-ProjectGodot
if ($projectGodot) { return $projectGodot }
foreach ($name in @("godot", "godot4")) {
$command = Get-Command $name -ErrorAction SilentlyContinue
if (-not $command) { continue }
$candidate = ConvertTo-ConsoleGodotPath $command.Source
if ($candidate) { return $candidate }
}
$common = @()
if ($env:ProgramFiles) { $common += Join-Path $env:ProgramFiles "Godot/godot.exe" }
if (${env:ProgramFiles(x86)}) { $common += Join-Path ${env:ProgramFiles(x86)} "Godot/godot.exe" }
if ($env:LOCALAPPDATA) {
$common += Join-Path $env:LOCALAPPDATA "Godot/godot.exe"
$common += Join-Path $env:LOCALAPPDATA "Programs/Godot/godot.exe"
}
if ($env:USERPROFILE) { $common += Join-Path $env:USERPROFILE "scoop/apps/godot/current/godot.exe" }
foreach ($path in $common) {
$candidate = ConvertTo-ConsoleGodotPath $path
if ($candidate) { return $candidate }
}
$searchRoots = @()
if ($env:ProgramFiles) { $searchRoots += Join-Path $env:ProgramFiles "Godot" }
if (${env:ProgramFiles(x86)}) { $searchRoots += Join-Path ${env:ProgramFiles(x86)} "Godot" }
if ($env:LOCALAPPDATA) {
$searchRoots += Join-Path $env:LOCALAPPDATA "Godot"
$searchRoots += Join-Path $env:LOCALAPPDATA "Programs/Godot"
}
foreach ($searchRoot in $searchRoots) {
$bundled = Get-ChildItem $searchRoot -Filter "Godot*_console.exe" -File -ErrorAction SilentlyContinue |
Sort-Object Name -Descending |
Select-Object -First 1
if ($bundled) { return $bundled.FullName }
}
return $null
}
function Resolve-GdTool {
param(
[string]$ExecutableName,
[string]$ModuleName
)
$localTool = Join-Path $ROOT ".venv/Scripts/$ExecutableName.exe"
if (Test-Path $localTool) {
return [pscustomobject]@{ Command = $localTool; Prefix = @() }
}
$directTool = Get-Command $ExecutableName -ErrorAction SilentlyContinue
if ($directTool) {
return [pscustomobject]@{ Command = $directTool.Source; Prefix = @() }
}
$localPython = Join-Path $ROOT ".venv/Scripts/python.exe"
$pythonCandidates = @(
[pscustomobject]@{ Command = $localPython; Prefix = @() },
[pscustomobject]@{ Command = "python"; Prefix = @() },
[pscustomobject]@{ Command = "python3"; Prefix = @() },
[pscustomobject]@{ Command = "py"; Prefix = @("-3") }
)
foreach ($candidate in $pythonCandidates) {
$isLocalPython = $candidate.Command -eq $localPython
if ($isLocalPython) {
if (-not (Test-Path $candidate.Command)) { continue }
$command = $candidate.Command
} else {
$resolved = Get-Command $candidate.Command -ErrorAction SilentlyContinue
if (-not $resolved) { continue }
$command = $resolved.Source
}
$prefix = @($candidate.Prefix)
$null = @(& $command @prefix -m $ModuleName --help 2>&1)
if ($LASTEXITCODE -eq 0) {
return [pscustomobject]@{ Command = $command; Prefix = $prefix + @("-m", $ModuleName) }
}
}
return $null
}
$script:GdFormatRunner = $null
$script:GdLintRunner = $null
function Test-GdFormat {
$script:GdFormatRunner = Resolve-GdTool "gdformat" "gdtoolkit.formatter"
return $null -ne $script:GdFormatRunner
}
function Test-GdLint {
$script:GdLintRunner = Resolve-GdTool "gdlint" "gdtoolkit.linter"
return $null -ne $script:GdLintRunner
}
$script:GdToolExitCode = 0
function Invoke-GdFormat {
$command = $script:GdFormatRunner.Command
$prefix = @($script:GdFormatRunner.Prefix)
& $command @prefix @args 2>&1
$script:GdToolExitCode = $LASTEXITCODE
}
function Invoke-GdLint {
$command = $script:GdLintRunner.Command
$prefix = @($script:GdLintRunner.Prefix)
& $command @prefix @args 2>&1
$script:GdToolExitCode = $LASTEXITCODE
}
function Get-ChangedGd {
$changed = @(git diff --name-only HEAD -- '*.gd' 2>$null)
$untracked = @(git ls-files --others --exclude-standard -- '*.gd' 2>$null)
return @(
(@($changed) + @($untracked)) |
Where-Object { $_ -ne '' -and -not $_.Replace('\', '/').StartsWith('addons/') } |
Sort-Object -Unique
)
}
function To-ResPath($p) {
$p = $p.Replace('\', '/')
$normalizedRoot = $ROOT.Replace('\', '/').TrimEnd('/')
$rootPrefix = "$normalizedRoot/"
if ($p.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)) {
$p = $p.Substring($rootPrefix.Length)
}
if ($p.StartsWith('./')) { $p = $p.Substring(2) }
return "res://$p"
}
function Test-AllowedScenarioDiagnostic {
param([string]$Diagnostic)
# Godot 4.7 can fail to read the system certificate store on macOS when HOME
# is intentionally isolated. Scenario tests do not perform network access.
if ($script:RUNNING_ON_MACOS -and
$Diagnostic -ceq 'ERROR: Condition "ret != noErr" is true. Returning: ""') {
return $true
}
# Pinned Terrain3D 1.0.2 invokes this Godot 4.7 compatibility method while
# loading its vendored GDExtension. Remove this exception with that vendor fix.
if ($Diagnostic -ceq 'WARNING: instance_reset_physics_interpolation() is deprecated.') {
return $true
}
# Godot 4.7's dummy headless renderer retains exactly one DummyShader RID at
# process teardown. A different RID type or count remains a gate failure.
if ($Diagnostic -ceq "ERROR: 1 RID allocations of type 'N13RendererDummy15MaterialStorage11DummyShaderE' were leaked at exit.") {
return $true
}
return $false
}
function Invoke-GodotWithTimeout {
param(
[string]$ArgumentString,
[int]$TimeoutSeconds = 30
)
$psi = New-Object Diagnostics.ProcessStartInfo
$psi.FileName = $GODOT
$psi.Arguments = $ArgumentString
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.UseShellExecute = $false
$psi.CreateNoWindow = $true
$previousAppData = $env:APPDATA
$previousLocalAppData = $env:LOCALAPPDATA
$env:APPDATA = $GODOT_PROFILE
$env:LOCALAPPDATA = $GODOT_PROFILE
try {
$p = [Diagnostics.Process]::Start($psi)
} catch {
return "Failed to start Godot: $($_.Exception.Message)", $null
} finally {
$env:APPDATA = $previousAppData
$env:LOCALAPPDATA = $previousLocalAppData
}
$outputTask = $p.StandardOutput.ReadToEndAsync()
$errorTask = $p.StandardError.ReadToEndAsync()
$exited = $p.WaitForExit($TimeoutSeconds * 1000)
if (-not $exited) {
$p.Kill()
$p.WaitForExit()
$output = $outputTask.Result
$err = $errorTask.Result
return ($output + $err), $null
}
$output = $outputTask.Result
$err = $errorTask.Result
return ($output + $err), $p.ExitCode
}
# -- godot --------------------------------------------------------------------
$GODOT = Find-Godot
if (-not $GODOT) {
Write-Host "ERROR: Godot binary not found. Set GODOT_BIN or add godot/godot4 to PATH."
Exit-Quality 1
}
$godotVersionOutput = @(& $GODOT --version 2>&1)
$godotVersionExit = $LASTEXITCODE
if ($godotVersionExit -ne 0 -or $godotVersionOutput.Count -eq 0) {
Write-Host "ERROR: Could not query the Godot version from '$GODOT'."
Exit-Quality 1
}
$GODOT_VERSION = ([string]$godotVersionOutput[0]).Trim()
$requiredVersionPattern = '^' + [regex]::Escape($REQUIRED_GODOT_SERIES) + '([.-]|$)'
if ($GODOT_VERSION -notmatch $requiredVersionPattern) {
Write-Host "ERROR: Godot $REQUIRED_GODOT_SERIES.x is required; found '$GODOT_VERSION'."
Exit-Quality 1
}
# -- 0. Cross-platform plugin payload -----------------------------------------
$platformAssetsResult = "PASS"
Set-Content -Path "$LOG/platform-assets.log" -Value "" -Encoding utf8
foreach ($pluginFile in $CROSS_PLATFORM_PLUGIN_FILES) {
if (-not (Test-Path $pluginFile -PathType Leaf)) {
$platformAssetsResult = "FAIL"
Add-Content -Path "$LOG/platform-assets.log" -Value "Missing $pluginFile" -Encoding utf8
$ERRORS += "platform-assets: missing res://$pluginFile"
}
}
if ($platformAssetsResult -eq "FAIL") {
$FIXES += "Restore the complete pinned Terrain3D 1.0.2 package"
}
# -- 1. Godot import bootstrap ------------------------------------------------
$importResult = "PASS"
Set-Content -Path "$LOG/godot-import.log" -Value "" -Encoding utf8
$classCache = Join-Path $ROOT ".godot/global_script_class_cache.cfg"
$hasProjectClasses = (Test-Path $classCache) -and
(Select-String -Path $classCache -SimpleMatch '"class": &"SimNPC"' -Quiet)
$hasGutClasses = (Test-Path $classCache) -and
(Select-String -Path $classCache -SimpleMatch '"class": &"GutTest"' -Quiet)
$changedGd = @(Get-ChangedGd)
$needsImport = (-not $hasProjectClasses) -or
((Test-Path "addons/gut/gut_cmdln.gd") -and (-not $hasGutClasses)) -or
($changedGd.Count -gt 0)
if ($needsImport) {
$importArgs = "--headless --path `"$ROOT`" --import"
$importOutput, $importExit = Invoke-GodotWithTimeout -ArgumentString $importArgs -TimeoutSeconds 60
$importOutput | Add-Content -Path "$LOG/godot-import.log" -Encoding utf8
if ($importExit -eq $null -or $importExit -ne 0) {
$importResult = "FAIL"
if ($importExit -eq $null) {
Add-Content -Path "$LOG/godot-import.log" -Value "[timed out after 60s]" -Encoding utf8
}
}
}
$hasProjectClasses = (Test-Path $classCache) -and
(Select-String -Path $classCache -SimpleMatch '"class": &"SimNPC"' -Quiet)
$hasGutClasses = (Test-Path $classCache) -and
(Select-String -Path $classCache -SimpleMatch '"class": &"GutTest"' -Quiet)
if (-not $hasProjectClasses -or ((Test-Path "addons/gut/gut_cmdln.gd") -and (-not $hasGutClasses))) {
$importResult = "FAIL"
}
$importLoadError = Select-String -Path "$LOG/godot-import.log" -Pattern 'SCRIPT ERROR:|Parse Error:|Failed to load script' -Quiet
if ($importLoadError) {
$importResult = "FAIL"
}
if ($importResult -eq "FAIL") {
$ERRORS += "godot-import: failed to build the global class cache"
$FIXES += "Fix Godot import errors before running headless checks"
}
# -- 2. gdformat --------------------------------------------------------------
$fmtAvail = Test-GdFormat
$fmtResult = "PASS"
$fmtFailed = $false
Set-Content -Path "$LOG/gdformat.log" -Value "" -Encoding utf8
if (-not $fmtAvail) {
$fmtResult = "FAIL"
Add-Content -Path "$LOG/gdformat.log" -Value "gdformat not found -- install requirements-dev.txt (see docs/local_quality_gate.md)" -Encoding utf8
$ERRORS += "gdformat: required formatter not found"
$FIXES += "Install the pinned dev tools from requirements-dev.txt"
} else {
if ($Changed) {
$files = Get-ChangedGd
if ($files.Count -eq 0) {
Add-Content -Path "$LOG/gdformat.log" -Value "No changed .gd files to check." -Encoding utf8
} else {
$total = 0; $bad = 0
foreach ($f in $files) {
if (-not (Test-Path $f)) { continue }
$total++
$out = Invoke-GdFormat "--check" $f
$formatExit = $script:GdToolExitCode
$out | Add-Content -Path "$LOG/gdformat.log" -Encoding utf8
if ($formatExit -ne 0) { $bad++; $fmtFailed = $true }
}
$msg = "($bad/$total files need formatting)"
Add-Content -Path "$LOG/gdformat.log" -Value $msg -Encoding utf8
}
} else {
$formatArgs = @("--check") + $OWNED_GDSCRIPT_ROOTS
$out = Invoke-GdFormat @formatArgs
$formatExit = $script:GdToolExitCode
$out | Add-Content -Path "$LOG/gdformat.log" -Encoding utf8
if ($formatExit -ne 0) { $fmtFailed = $true }
}
}
if ($fmtFailed) { $fmtResult = "FAIL" }
$formatDiagnostics = Select-String -SimpleMatch "would reformat" -Path "$LOG/gdformat.log" -Quiet
if ($formatDiagnostics) {
$lines = Select-String -SimpleMatch "would reformat" -Path "$LOG/gdformat.log" | Select-Object -ExpandProperty Line
foreach ($line in $lines) {
$p = ($line -replace '^would reformat ', '').Trim()
$rp = To-ResPath $p
$ERRORS += "$rp needs formatting"
}
$FIXES += "Run gdformat to auto-format files"
} elseif ($fmtFailed) {
$ERRORS += "gdformat: formatter exited non-zero; see $LOG/gdformat.log"
$FIXES += "Fix the gdformat error before continuing"
}
# -- 3. gdlint ----------------------------------------------------------------
$lintAvail = Test-GdLint
$lintResult = "PASS"
$lintFailed = $false
Set-Content -Path "$LOG/gdlint.log" -Value "" -Encoding utf8
if (-not $lintAvail) {
$lintResult = "FAIL"
Add-Content -Path "$LOG/gdlint.log" -Value "gdlint not found -- install requirements-dev.txt (see docs/local_quality_gate.md)" -Encoding utf8
$ERRORS += "gdlint: required linter not found"
$FIXES += "Install the pinned dev tools from requirements-dev.txt"
} else {
if ($Changed) {
$files = Get-ChangedGd
if ($files.Count -eq 0) {
Add-Content -Path "$LOG/gdlint.log" -Value "No changed .gd files to lint." -Encoding utf8
} else {
foreach ($f in $files) {
if (-not (Test-Path $f)) { continue }
$out = Invoke-GdLint $f
$lintExit = $script:GdToolExitCode
$out | Add-Content -Path "$LOG/gdlint.log" -Encoding utf8
if ($lintExit -ne 0) { $lintFailed = $true }
}
}
} else {
$out = Invoke-GdLint @OWNED_GDSCRIPT_ROOTS
$lintExit = $script:GdToolExitCode
$out | Add-Content -Path "$LOG/gdlint.log" -Encoding utf8
if ($lintExit -ne 0) { $lintFailed = $true }
}
}
if ($lintFailed) { $lintResult = "FAIL" }
$lintLines = Select-String -Pattern '^[^ ]+:\d+:\d+:' -Path "$LOG/gdlint.log" | Select-Object -ExpandProperty Line
if ($lintLines) {
foreach ($line in $lintLines) {
$parts = $line -split ':', 4
if ($parts.Count -lt 4) { continue }
$file = $parts[0]; $lineno = $parts[1]; $msg = $parts[3]
$rp = To-ResPath $file
$ERRORS += "$rp`:$lineno $msg"
}
foreach ($line in $lintLines) {
$parts = $line -split ':', 5
if ($parts.Count -lt 5) { continue }
$rule = $parts[3]; $fname = Split-Path -Leaf $parts[0]
$FIXES += "Fix $rule in $fname"
}
} elseif ($lintFailed) {
$ERRORS += "gdlint: linter exited non-zero; see $LOG/gdlint.log"
$FIXES += "Fix the gdlint error before continuing"
}
# -- 4. godot headless check --------------------------------------------------
$godotResult = "PASS"
Set-Content -Path "$LOG/godot-check.log" -Value "" -Encoding utf8
$godotArgs = "--headless --path `"$ROOT`" --script res://tests/simulation_definitions_test.gd"
$godotOutput, $godotExit = Invoke-GodotWithTimeout -ArgumentString $godotArgs
$godotOutput | Out-File -FilePath "$LOG/godot-check.log" -Encoding utf8
if ($godotExit -eq $null) {
$godotResult = "FAIL"
Add-Content -Path "$LOG/godot-check.log" -Value "[timed out after 30s]" -Encoding utf8
} elseif ($godotExit -eq 0) {
$godotResult = "PASS"
} else {
$godotResult = "FAIL"
$godotOutput | Select-String -Pattern 'error|warning|parse|syntax' | Select-Object -First 20 | ForEach-Object {
$ERRORS += "godot: $_"
}
$FIXES += "Fix Godot parser errors"
}
$godotLoadError = Select-String -Path "$LOG/godot-check.log" -Pattern 'SCRIPT ERROR:|Parse Error:|Failed to load script' -Quiet
if ($godotLoadError) {
$godotResult = "FAIL"
$ERRORS += "godot: project check reported a script load or parse error"
$FIXES += "Fix Godot parser errors"
}
# -- 5. project scenario tests ------------------------------------------------
$scenarioResult = "PASS"
Set-Content -Path "$LOG/scenarios.log" -Value "" -Encoding utf8
$scenarioTests = Get-ChildItem "tests" -Filter "*_test.gd" | Sort-Object Name
foreach ($test in $scenarioTests) {
Add-Content -Path "$LOG/scenarios.log" -Value "[RUN] $($test.Name)" -Encoding utf8
$testArgs = "--headless --path `"$ROOT`" --script res://tests/$($test.Name)"
$testOutput, $testExit = Invoke-GodotWithTimeout -ArgumentString $testArgs
$testOutput | Add-Content -Path "$LOG/scenarios.log" -Encoding utf8
if ($testExit -eq $null -or $testExit -ne 0) {
$scenarioResult = "FAIL"
$ERRORS += "scenario: $($test.Name) failed or timed out"
}
}
$compatibilityTest = "jajce_presentation_quality_test.gd"
$compatibilityLabel = "$compatibilityTest (gl_compatibility)"
Add-Content -Path "$LOG/scenarios.log" -Value "[RUN] $compatibilityLabel" -Encoding utf8
$compatibilityArgs = "--headless --rendering-method gl_compatibility --path `"$ROOT`" --script res://tests/$compatibilityTest"
$compatibilityOutput, $compatibilityExit = Invoke-GodotWithTimeout -ArgumentString $compatibilityArgs
$compatibilityOutput | Add-Content -Path "$LOG/scenarios.log" -Encoding utf8
if ($compatibilityExit -eq $null -or $compatibilityExit -ne 0) {
$scenarioResult = "FAIL"
$ERRORS += "scenario: $compatibilityLabel failed or timed out"
}
$unexpectedScenarioDiagnostics = @()
$currentScenario = "unknown scenario"
foreach ($rawLine in Get-Content -Path "$LOG/scenarios.log") {
if ($rawLine.StartsWith('[RUN] ')) {
$currentScenario = $rawLine.Substring(6)
continue
}
$diagnostic = $rawLine.Trim()
if ($diagnostic -cnotmatch '^(?:(?:SCRIPT )?ERROR|WARNING):') { continue }
if (-not (Test-AllowedScenarioDiagnostic $diagnostic)) {
$unexpectedScenarioDiagnostics += "$currentScenario`: $diagnostic"
}
}
if ($unexpectedScenarioDiagnostics.Count -gt 0) {
$scenarioResult = "FAIL"
$ERRORS += "scenario: Godot reported $($unexpectedScenarioDiagnostics.Count) unallowlisted ERROR/WARNING diagnostic(s)"
foreach ($diagnostic in $unexpectedScenarioDiagnostics | Select-Object -First 20) {
$ERRORS += "scenario: $diagnostic"
}
}
$scenarioLoadError = Select-String -Path "$LOG/scenarios.log" -Pattern 'SCRIPT ERROR:|Parse Error:|Failed to load script' -Quiet
if ($scenarioLoadError) {
$scenarioResult = "FAIL"
$ERRORS += "scenario: Godot reported a script load or parse error"
}
if ($scenarioResult -eq "FAIL") {
$FIXES += "Fix failing project scenario tests"
}
# -- 6. GUT -------------------------------------------------------------------
$gutResult = "PASS"
Set-Content -Path "$LOG/gut.log" -Value "" -Encoding utf8
if (-not (Test-Path "addons/gut/gut_cmdln.gd")) {
$gutResult = "FAIL"
$ERRORS += "gut: pinned addon is missing"
$FIXES += "Restore addons/gut from pinned GUT v9.7.1"
} else {
$gutArgs = "--headless --path `"$ROOT`" -s addons/gut/gut_cmdln.gd -gexit -gdisable_colors"
$gutOutput, $gutExit = Invoke-GodotWithTimeout -ArgumentString $gutArgs
$gutOutput | Add-Content -Path "$LOG/gut.log" -Encoding utf8
if ($gutExit -eq $null) {
$gutResult = "FAIL"
Add-Content -Path "$LOG/gut.log" -Value "[timed out after 30s]" -Encoding utf8
$ERRORS += "gut: GUT test timed out after 30 seconds"
$FIXES += "Fix GUT tests (timeout)"
} elseif ($gutExit -eq 0) {
$gutResult = "PASS"
} else {
$gutResult = "FAIL"
$gutOutput | Select-String -Pattern 'fail|error|assert' | Select-Object -First 20 | ForEach-Object {
$ERRORS += "gut: $_"
}
$FIXES += "Fix failing GUT tests"
}
$gutLoadError = Select-String -Path "$LOG/gut.log" -Pattern 'Some GUT class_names have not been imported|SCRIPT ERROR:|Parse Error:|Failed to load script' -Quiet
if ($gutLoadError) {
$gutResult = "FAIL"
$ERRORS += "gut: addon or unit tests failed to load"
$FIXES += "Run Godot 4.7 headless import and fix GUT load errors"
}
}
# -- summary ------------------------------------------------------------------
if ($platformAssetsResult -eq 'FAIL' -or $importResult -eq 'FAIL' -or
$fmtResult -eq 'FAIL' -or $lintResult -eq 'FAIL' -or $godotResult -eq 'FAIL' -or
$scenarioResult -eq 'FAIL' -or $gutResult -eq 'FAIL') {
$OVERALL = $true
}
Write-Host ""
if ($OVERALL) { Write-Host "QUALITY RESULT: FAIL" } else { Write-Host "QUALITY RESULT: PASS" }
Write-Host "Godot: $GODOT_VERSION"
Write-Host ""
Write-Host " platform-assets $platformAssetsResult"
Write-Host " godot-import $importResult"
Write-Host " gdformat $fmtResult"
Write-Host " gdlint $lintResult"
Write-Host " godot-check $godotResult"
Write-Host " scenarios $scenarioResult"
Write-Host " gut $gutResult"
$seen = @{}
foreach ($e in $ERRORS) {
if ($seen.ContainsKey($e)) { continue }
$seen[$e] = $true
Write-Host " $e"
}
if ($OVERALL -and $FIXES.Count -gt 0) {
Write-Host ""
Write-Host " NEXT FIX:"
$seenFix = @{}
$idx = 1
foreach ($fix in $FIXES) {
if ($seenFix.ContainsKey($fix)) { continue }
$seenFix[$fix] = $true
Write-Host " $idx. $fix"
$idx++
}
}
Write-Host ""
Write-Host "Full logs:"
Write-Host " $LOG/"
Write-Host ""
# save summary
if ($OVERALL) { $resultText = "FAIL" } else { $resultText = "PASS" }
$summary = @"
QUALITY RESULT: $resultText
Godot: $GODOT_VERSION
platform-assets $platformAssetsResult
godot-import $importResult
gdformat $fmtResult
gdlint $lintResult
godot-check $godotResult
scenarios $scenarioResult
gut $gutResult
"@
Set-Content -Path "$LOG/summary.txt" -Value $summary -Encoding utf8
if ($OVERALL) { Exit-Quality 1 } else { Exit-Quality 0 }