<# .SYNOPSIS Prepares a fresh Windows workstation for building ElectricRoot. .DESCRIPTION Steps performed: * SVN command-line client: puts svn on the User PATH using the copy vendored under 3rdParty\SVN -- but only if no other svn client is already reachable, so a client the user installed themselves (e.g. TortoiseSVN's CLI tools) is never shadowed. * Prerequisite checks: reports (does NOT install) the other things a build needs -- Python and Visual Studio, which build.py requires, plus the .NET SDK, which only the net8.0+ projects need and build.py therefore does not enforce. Re-runnable and non-destructive: the PATH edit is idempotent and nothing here downloads or installs anything. Pass -WhatIf to preview the PATH change without making it. New steps get added as their own function + one call in the main block below. .NOTES A PATH change only reaches newly opened shells, not the one this script ran in. #> [CmdletBinding(SupportsShouldProcess)] param() Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # --- output helpers -------------------------------------------------------- function Write-Ok { param([string]$Message) Write-Host " [ OK ] $Message" -ForegroundColor Green } function Write-Warn { param([string]$Message) Write-Host " [WARN] $Message" -ForegroundColor Yellow; $script:WarnCount++ } function Write-Info { param([string]$Message) Write-Host " [ -- ] $Message" } function Write-Head { param([string]$Message) Write-Host $Message -ForegroundColor Cyan } # Is $Entry already one of the ';'-separated entries in $PathValue? Case-insensitive, and a # trailing backslash on either side is ignored so "C:\foo" and "C:\foo\" count as the same entry. function Test-PathEntryPresent { param([string]$PathValue, [string]$Entry) if ([string]::IsNullOrEmpty($PathValue)) { return $false } $target = $Entry.TrimEnd('\') foreach ($part in $PathValue.Split(';')) { if ([string]::IsNullOrWhiteSpace($part)) { continue } if ($part.Trim().TrimEnd('\') -ieq $target) { return $true } } return $false } # Minimum version for $Tool from the same BuildRequirements.json build.py reads. Returns $null (and # warns) on a read failure so the caller can still report the installed version. function Get-RequiredVersion { param([string]$Tool) $path = Join-Path $PSScriptRoot '..\BuildRequirements.json' if (-not (Test-Path -LiteralPath $path)) { Write-Warn "Requirements file not found at $path -- can't check the $Tool version." return $null } try { $requirements = Get-Content -LiteralPath $path -Raw | ConvertFrom-Json return [version]$requirements.$Tool.minimumVersion } catch { Write-Warn "Couldn't read the required $Tool version from $path -- $($_.Exception.Message)" return $null } } # Highest version "dotnet --list-sdks" reports, or $null if it lists none (a runtime-only install). # Lines look like "10.0.100 [C:\Program Files\dotnet\sdk]". function Get-LatestDotNetSdkVersion { param([string]$DotNetPath) $versions = @() foreach ($line in & $DotNetPath --list-sdks 2>$null) { try { $versions += [version]($line -split '\s+', 2)[0] } catch { # Not a version line -- dotnet prints guidance prose when nothing is installed. Skip it. } } if ($versions.Count -eq 0) { return $null } return ($versions | Sort-Object -Descending)[0] } # --- steps ----------------------------------------------------------------- function Set-SvnOnPath { Write-Head 'SVN command-line client' # If any svn already answers, leave the user's setup alone -- don't shadow a client they # installed themselves (which may well be newer than our vendored 1.8). $existing = Get-Command svn -ErrorAction SilentlyContinue if ($existing) { Write-Ok "svn already on PATH: $($existing.Source) -- leaving it as-is." return } $svnDir = Join-Path $RepoRoot '3rdParty\SVN' $svnExe = Join-Path $svnDir 'svn.exe' if (-not (Test-Path -LiteralPath $svnExe)) { Write-Warn "No svn on PATH, and the vendored copy is missing at $svnExe." Write-Warn 'Is the 3rdParty external checked out yet? Try "svn update" from the repo root.' return } # Added by a previous run but not visible in this session (new-shell lag)? $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if (Test-PathEntryPresent -PathValue $userPath -Entry $svnDir) { Write-Ok "$svnDir is already on the User PATH; open a new shell to pick it up." return } if ($PSCmdlet.ShouldProcess('User PATH', "append $svnDir")) { $newPath = if ([string]::IsNullOrEmpty($userPath)) { $svnDir } elseif ($userPath.EndsWith(';')) { "$userPath$svnDir" } else { "$userPath;$svnDir" } [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') $env:PATH = "$env:PATH;$svnDir" # so svn works in this session too, not just new ones Write-Ok "Added $svnDir to the User PATH. Open a new shell for other apps to see it." } } function Test-Python { Write-Head 'Python (drives build.py)' $python = Get-Command python -ErrorAction SilentlyContinue if (-not $python) { $python = Get-Command py -ErrorAction SilentlyContinue } if (-not $python) { Write-Warn 'Python not found on PATH -- build.py cannot run. Install Python 3.x.' return } $raw = (& $python.Source --version 2>&1) -join ' ' $found = $null try { $found = [version]($raw -replace '[^\d.]', '') # "Python 3.10.1" -> 3.10.1 } catch { # Unparseable version; fall through and report it verbatim, no comparison. } $required = Get-RequiredVersion -Tool 'python' if ($found -and $required -and $found -lt $required) { Write-Warn "$($python.Source) is Python $found, but build.py requires $required or newer." } else { Write-Ok "$($python.Source) ($raw)" } } function Test-VisualStudio { Write-Head 'Visual Studio (build.py shells out to it)' $vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' if (-not (Test-Path -LiteralPath $vswhere)) { Write-Warn 'vswhere not found -- no Visual Studio installer present. Install VS with the ".NET desktop development" workload.' return } $devenv = & $vswhere -latest -property productPath 2>$null if ($devenv) { Write-Ok "$devenv" } else { Write-Warn 'vswhere found no Visual Studio install. Install VS with the ".NET desktop development" workload.' } } function Test-DotNetSdk { Write-Head '.NET SDK (for the net8.0+ projects; Visual Studio builds the rest)' $dotnet = Get-Command dotnet -ErrorAction SilentlyContinue if (-not $dotnet) { Write-Warn 'dotnet not found on PATH. Install the .NET SDK from https://dotnet.microsoft.com/download.' return } # A box can carry several .NET runtimes (Visual Studio installs them) and still have no SDK at # all, or only an older one -- so report the SDK specifically, not "dotnet --version". $found = Get-LatestDotNetSdkVersion -DotNetPath $dotnet.Source if (-not $found) { Write-Warn "$($dotnet.Source) reports no installed SDKs -- runtimes only. Install the .NET SDK." return } $required = Get-RequiredVersion -Tool 'dotnetSdk' if ($required -and $found -lt $required) { Write-Warn "$($dotnet.Source) has SDK $found, but $required or newer is required." } else { Write-Ok "$($dotnet.Source) (SDK $found)" } } # --- main ------------------------------------------------------------------ $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path $script:WarnCount = 0 Write-Host '' Write-Host 'ElectricRoot workstation setup' -ForegroundColor White Write-Host "Repo root: $RepoRoot" Write-Host '' Set-SvnOnPath Write-Host '' Test-Python Write-Host '' Test-VisualStudio Write-Host '' Test-DotNetSdk Write-Host '' if ($script:WarnCount -gt 0) { Write-Host "Done, with $script:WarnCount warning(s) above to address before building." -ForegroundColor Yellow } else { Write-Host 'All prerequisites satisfied.' -ForegroundColor Green }