<# .SYNOPSIS Builds TheDescentEditor (Win64 Development) and prints one PASS/FAIL line. .DESCRIPTION This PC has no registered Visual Studio, so UnrealBuildTool only finds the MSVC toolchain through the AutoSDK junction under D:\HADAL - The Descent\.tools\AutoSDK. Every build therefore needs UE_SDKS_ROOT; this script sets it for the child process only. Prints only compiler/linker errors, the warning count and the result line. The full UBT output always goes to Saved\Logs\build_last.log; pass -Verbose to also stream it to the console. If an editor has the project open it keeps Binaries\Win64\UnrealEditor-TheDescent.dll locked and a normal link fails with LNK1104. This script never closes an editor; it detects the lock and links the module under a numbered name instead (UnrealEditor-TheDescent-.dll, UBT's -ModuleWithSuffix, the same mechanism hot reload uses). UnrealEditor.modules then points at that file, so the next editor or scenario launch loads it. Older numbered copies that are no longer locked are deleted. The next build with no editor open goes back to the plain name. .EXAMPLE powershell -NoProfile -ExecutionPolicy Bypass -File Tools\build.ps1 Exit code 0 = PASS, 1 = FAIL. #> [CmdletBinding()] param( # Force a full rebuild of the module (rarely needed). [switch]$Rebuild ) $ErrorActionPreference = 'Stop' $ProjectRoot = Split-Path -Parent $PSScriptRoot $Uproject = Join-Path $ProjectRoot 'TheDescent.uproject' $EngineRoot = 'E:\Tools\UE_5.8' $BuildBat = Join-Path $EngineRoot 'Engine\Build\BatchFiles\Build.bat' $BinDir = Join-Path $ProjectRoot 'Binaries\Win64' $LogDir = Join-Path $ProjectRoot 'Saved\Logs' $LogFile = Join-Path $LogDir 'build_last.log' if (-not (Test-Path $BuildBat)) { Write-Host "BUILD FAIL: Build.bat not found at $BuildBat"; exit 1 } New-Item -ItemType Directory -Force -Path $LogDir | Out-Null $env:UE_SDKS_ROOT = 'D:\HADAL - The Descent\.tools\AutoSDK' function Test-FileLocked([string]$Path) { if (-not (Test-Path $Path)) { return $false } try { $fs = [System.IO.File]::Open($Path, 'Open', 'ReadWrite', 'None') $fs.Close() return $false } catch { return $true } } $buildArgs = @('TheDescentEditor', 'Win64', 'Development', "-Project=`"$Uproject`"", '-WaitMutex', '-NoHotReloadFromIDE') if ($Rebuild) { $buildArgs += '-Rebuild' } # Numbered module name when an editor holds the plain DLL. $plainDll = Join-Path $BinDir 'UnrealEditor-TheDescent.dll' $suffixNote = '' # An editor started after a numbered build holds THAT numbered DLL, not the plain one (UnrealEditor.modules pointed at # it). A normal build then fails with "Unable to delete hot-reload file", so a locked numbered DLL counts as "held" too. $heldDll = $null if (Test-FileLocked $plainDll) { $heldDll = $plainDll } else { $heldDll = Get-ChildItem -Path $BinDir -Filter 'UnrealEditor-TheDescent-*.dll' -ErrorAction SilentlyContinue | Where-Object { Test-FileLocked $_.FullName } | Select-Object -First 1 -ExpandProperty FullName } if ($Rebuild -and $heldDll) { # -Rebuild cleans first; with the DLL locked the clean fails half-way and deletes UnrealEditor.modules, which # leaves the project unloadable until the next successful build. Refuse instead. Write-Host "BUILD FAIL -Rebuild needs the editor closed (it holds $heldDll). Run without -Rebuild, or close the editor." exit 1 } if ($heldDll) { $existing = Get-ChildItem -Path $BinDir -Filter 'UnrealEditor-TheDescent-*.dll' -ErrorAction SilentlyContinue | ForEach-Object { if ($_.BaseName -match '-(\d+)$') { [int]$Matches[1] } } $next = 1 if ($existing) { $next = ([int](($existing | Measure-Object -Maximum).Maximum)) + 1 } $buildArgs += "`"-ModuleWithSuffix=TheDescent,$next`"" $suffixNote = " (editor holds the DLL; linked as UnrealEditor-TheDescent-$next.dll)" } $sw = [System.Diagnostics.Stopwatch]::StartNew() # cmd /c keeps the batch file's own exit code and lets us merge stderr without PowerShell turning lines into errors. $cmdLine = '"' + $BuildBat + '" ' + ($buildArgs -join ' ') + ' > "' + $LogFile + '" 2>&1' cmd.exe /c $cmdLine $exit = $LASTEXITCODE $sw.Stop() $lines = Get-Content -Path $LogFile -ErrorAction SilentlyContinue if ($VerbosePreference -eq 'Continue') { $lines | ForEach-Object { Write-Host $_ } } # MSVC / clang-cl / UBT / UHT error shapes. $errorLines = $lines | Where-Object { $_ -match ': (fatal )?error [A-Z]*\d+|: error:|\berror LNK\d+|^ERROR:|UnrealBuildTool.*(Exception|failed)|Result: Failed' } | Select-Object -Unique $warnCount = ($lines | Where-Object { $_ -match ': warning [A-Z]*\d+' } | Select-Object -Unique | Measure-Object).Count if ($VerbosePreference -ne 'Continue') { foreach ($l in $errorLines) { Write-Host $l } } if ($exit -eq 0 -and $suffixNote -ne '') { # Tidy: drop numbered DLLs/PDBs older than the one just linked, where the OS lets us. Get-ChildItem -Path $BinDir -Filter 'UnrealEditor-TheDescent-*' -ErrorAction SilentlyContinue | Where-Object { $_.BaseName -notmatch ('-{0:D4}$' -f $next) } | # UBT zero-pads the suffix to 4 digits ForEach-Object { try { Remove-Item $_.FullName -Force -ErrorAction Stop } catch { } } } $secs = [math]::Round($sw.Elapsed.TotalSeconds, 1) if ($exit -eq 0) { Write-Host ("BUILD PASS TheDescentEditor Win64 Development {0}s warnings={1}{2}" -f $secs, $warnCount, $suffixNote) exit 0 } else { Write-Host ("BUILD FAIL exit={0} {1}s errors={2} log={3}" -f $exit, $secs, @($errorLines).Count, $LogFile) exit 1 }