fix: harden cross-platform quality tooling
This commit is contained in:
+141
-67
@@ -5,6 +5,7 @@ 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"
|
||||
@@ -16,12 +17,26 @@ $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"
|
||||
|
||||
function Exit-Quality {
|
||||
param([int]$ExitCode)
|
||||
Set-Location $script:PreviousLocation
|
||||
exit $ExitCode
|
||||
}
|
||||
|
||||
# -- tool detection -----------------------------------------------------------
|
||||
function ConvertTo-ConsoleGodotPath($Path) {
|
||||
if (-not $Path) { return $null }
|
||||
$normalized = $Path.Replace('/', '\').Trim('"')
|
||||
$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 }
|
||||
@@ -43,75 +58,110 @@ function Find-Godot {
|
||||
if ($env:GODOT_BIN) { return (ConvertTo-ConsoleGodotPath $env:GODOT_BIN) }
|
||||
$projectGodot = Find-ProjectGodot
|
||||
if ($projectGodot) { return $projectGodot }
|
||||
try { return (Get-Command godot -ErrorAction Stop).Source } catch {}
|
||||
try { return (Get-Command godot4 -ErrorAction Stop).Source } catch {}
|
||||
$common = @(
|
||||
"$env:ProgramFiles\Godot\godot.exe",
|
||||
"${env:ProgramFiles(x86)}\Godot\godot.exe",
|
||||
"$env:LOCALAPPDATA\Godot\godot.exe",
|
||||
"$env:USERPROFILE\AppData\Local\Godot\godot.exe"
|
||||
)
|
||||
foreach ($p in $common) { if (Test-Path $p) { return $p } }
|
||||
$bundled = Get-ChildItem "${env:ProgramFiles(x86)}\Godot" -Filter "Godot*_console.exe" -ErrorAction SilentlyContinue |
|
||||
Sort-Object Name -Descending |
|
||||
Select-Object -First 1
|
||||
if ($bundled) { return $bundled.FullName }
|
||||
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 {
|
||||
if (Test-Path "$ROOT/.venv/Scripts/gdformat.exe") { return $true }
|
||||
if (Get-Command gdformat -ErrorAction SilentlyContinue) { return $true }
|
||||
try {
|
||||
$null = & python -m gdtoolkit.formatter --help 2>&1
|
||||
if ($LASTEXITCODE -eq 0) { return $true }
|
||||
} catch {}
|
||||
try {
|
||||
$null = & python3 -m gdtoolkit.formatter --help 2>&1
|
||||
if ($LASTEXITCODE -eq 0) { return $true }
|
||||
} catch {}
|
||||
return $false
|
||||
$script:GdFormatRunner = Resolve-GdTool "gdformat" "gdtoolkit.formatter"
|
||||
return $null -ne $script:GdFormatRunner
|
||||
}
|
||||
|
||||
function Test-GdLint {
|
||||
if (Test-Path "$ROOT/.venv/Scripts/gdlint.exe") { return $true }
|
||||
if (Get-Command gdlint -ErrorAction SilentlyContinue) { return $true }
|
||||
try {
|
||||
$null = & python -m gdtoolkit.linter --help 2>&1
|
||||
if ($LASTEXITCODE -eq 0) { return $true }
|
||||
} catch {}
|
||||
try {
|
||||
$null = & python3 -m gdtoolkit.linter --help 2>&1
|
||||
if ($LASTEXITCODE -eq 0) { return $true }
|
||||
} catch {}
|
||||
return $false
|
||||
$script:GdLintRunner = Resolve-GdTool "gdlint" "gdtoolkit.linter"
|
||||
return $null -ne $script:GdLintRunner
|
||||
}
|
||||
|
||||
$script:GdToolExitCode = 0
|
||||
|
||||
function Invoke-GdFormat {
|
||||
$args = $args
|
||||
$localTool = "$ROOT/.venv/Scripts/gdformat.exe"
|
||||
if (Test-Path $localTool) {
|
||||
& $localTool @args 2>&1
|
||||
} elseif (Get-Command gdformat -ErrorAction SilentlyContinue) {
|
||||
& gdformat @args 2>&1
|
||||
} else {
|
||||
& python -m gdtoolkit.formatter @args 2>&1
|
||||
}
|
||||
$command = $script:GdFormatRunner.Command
|
||||
$prefix = @($script:GdFormatRunner.Prefix)
|
||||
& $command @prefix @args 2>&1
|
||||
$script:GdToolExitCode = $LASTEXITCODE
|
||||
}
|
||||
|
||||
function Invoke-GdLint {
|
||||
$args = $args
|
||||
$localTool = "$ROOT/.venv/Scripts/gdlint.exe"
|
||||
if (Test-Path $localTool) {
|
||||
& $localTool @args 2>&1
|
||||
} elseif (Get-Command gdlint -ErrorAction SilentlyContinue) {
|
||||
& gdlint @args 2>&1
|
||||
} else {
|
||||
& python -m gdtoolkit.linter @args 2>&1
|
||||
}
|
||||
$command = $script:GdLintRunner.Command
|
||||
$prefix = @($script:GdLintRunner.Prefix)
|
||||
& $command @prefix @args 2>&1
|
||||
$script:GdToolExitCode = $LASTEXITCODE
|
||||
}
|
||||
|
||||
@@ -123,7 +173,11 @@ function Get-ChangedGd {
|
||||
|
||||
function To-ResPath($p) {
|
||||
$p = $p.Replace('\', '/')
|
||||
if ($p.StartsWith($ROOT)) { $p = $p.Substring($ROOT.Length + 1) }
|
||||
$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"
|
||||
}
|
||||
@@ -146,6 +200,8 @@ function Invoke-GodotWithTimeout {
|
||||
$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
|
||||
@@ -158,7 +214,7 @@ function Invoke-GodotWithTimeout {
|
||||
$p.WaitForExit()
|
||||
$output = $outputTask.Result
|
||||
$err = $errorTask.Result
|
||||
return $output + $err, $null
|
||||
return ($output + $err), $null
|
||||
}
|
||||
$output = $outputTask.Result
|
||||
$err = $errorTask.Result
|
||||
@@ -169,22 +225,36 @@ function Invoke-GodotWithTimeout {
|
||||
$GODOT = Find-Godot
|
||||
if (-not $GODOT) {
|
||||
Write-Host "ERROR: Godot binary not found. Set GODOT_BIN or add godot/godot4 to PATH."
|
||||
exit 1
|
||||
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 1
|
||||
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 1
|
||||
Exit-Quality 1
|
||||
}
|
||||
|
||||
# -- 0. Godot import bootstrap ------------------------------------------------
|
||||
# -- 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"
|
||||
@@ -223,7 +293,7 @@ if ($importResult -eq "FAIL") {
|
||||
$FIXES += "Fix Godot import errors before running headless checks"
|
||||
}
|
||||
|
||||
# -- 1. gdformat --------------------------------------------------------------
|
||||
# -- 2. gdformat --------------------------------------------------------------
|
||||
$fmtAvail = Test-GdFormat
|
||||
$fmtResult = "PASS"
|
||||
$fmtFailed = $false
|
||||
@@ -275,7 +345,7 @@ if ($formatDiagnostics) {
|
||||
$FIXES += "Fix the gdformat error before continuing"
|
||||
}
|
||||
|
||||
# -- 2. gdlint ----------------------------------------------------------------
|
||||
# -- 3. gdlint ----------------------------------------------------------------
|
||||
$lintAvail = Test-GdLint
|
||||
$lintResult = "PASS"
|
||||
$lintFailed = $false
|
||||
@@ -328,7 +398,7 @@ if ($lintLines) {
|
||||
$FIXES += "Fix the gdlint error before continuing"
|
||||
}
|
||||
|
||||
# -- 3. godot headless check --------------------------------------------------
|
||||
# -- 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"
|
||||
@@ -353,7 +423,7 @@ if ($godotLoadError) {
|
||||
$FIXES += "Fix Godot parser errors"
|
||||
}
|
||||
|
||||
# -- 4. project scenario tests ------------------------------------------------
|
||||
# -- 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
|
||||
@@ -376,7 +446,7 @@ if ($scenarioResult -eq "FAIL") {
|
||||
$FIXES += "Fix failing project scenario tests"
|
||||
}
|
||||
|
||||
# -- 5. GUT -------------------------------------------------------------------
|
||||
# -- 6. GUT -------------------------------------------------------------------
|
||||
$gutResult = "PASS"
|
||||
Set-Content -Path "$LOG/gut.log" -Value "" -Encoding utf8
|
||||
if (-not (Test-Path "addons/gut/gut_cmdln.gd")) {
|
||||
@@ -410,7 +480,9 @@ if (-not (Test-Path "addons/gut/gut_cmdln.gd")) {
|
||||
}
|
||||
|
||||
# -- summary ------------------------------------------------------------------
|
||||
if ($importResult -eq 'FAIL' -or $fmtResult -eq 'FAIL' -or $lintResult -eq 'FAIL' -or $godotResult -eq 'FAIL' -or $scenarioResult -eq 'FAIL' -or $gutResult -eq 'FAIL') {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -418,6 +490,7 @@ 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"
|
||||
@@ -456,6 +529,7 @@ $summary = @"
|
||||
QUALITY RESULT: $resultText
|
||||
Godot: $GODOT_VERSION
|
||||
|
||||
platform-assets $platformAssetsResult
|
||||
godot-import $importResult
|
||||
gdformat $fmtResult
|
||||
gdlint $lintResult
|
||||
@@ -465,4 +539,4 @@ Godot: $GODOT_VERSION
|
||||
"@
|
||||
Set-Content -Path "$LOG/summary.txt" -Value $summary -Encoding utf8
|
||||
|
||||
if ($OVERALL) { exit 1 } else { exit 0 }
|
||||
if ($OVERALL) { Exit-Quality 1 } else { Exit-Quality 0 }
|
||||
|
||||
Reference in New Issue
Block a user