diff --git a/.vscode/settings.json b/.vscode/settings.json index 521141af5a..1ecd074674 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -77,7 +77,6 @@ "resharper.build.useResharperBuild": false, "resharper.build.restorePackagesOnBuild": true, "resharper.build.smartNugetRestore": true, - "resharper.build.customMsbuildPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\MSBuild.exe", // Git Graph extension settings "git-graph.dialog.merge.noFastForward": false, "git-graph.dialog.merge.squashCommits": false, diff --git a/Build/Agent/FwBuildEnvironment.psm1 b/Build/Agent/FwBuildEnvironment.psm1 index 5b73699100..3e35080796 100644 --- a/Build/Agent/FwBuildEnvironment.psm1 +++ b/Build/Agent/FwBuildEnvironment.psm1 @@ -1,148 +1,365 @@ <# .SYNOPSIS - Visual Studio and build tool environment helpers for FieldWorks. + Visual Studio and build tool environment helpers for FieldWorks. .DESCRIPTION - Provides VS environment initialization, MSBuild execution, and - VSTest path discovery. + Provides VS environment initialization, MSBuild execution, and + VSTest path discovery. .NOTES - Used by FwBuildHelpers.psm1 - do not import directly. + Used by FwBuildHelpers.psm1 - do not import directly. #> # ============================================================================= # VS Environment Functions # ============================================================================= +function Get-VsWherePath { + <# + .SYNOPSIS + Returns the path to the Microsoft-provided vswhere executable. + #> + $candidates = @() + if ($env:ProgramFiles) { + $candidates += (Join-Path -Path $env:ProgramFiles -ChildPath 'Microsoft Visual Studio\Installer\vswhere.exe') + } + + $programFilesX86 = ${env:ProgramFiles(x86)} + if ($programFilesX86) { + $candidates += (Join-Path -Path $programFilesX86 -ChildPath 'Microsoft Visual Studio\Installer\vswhere.exe') + } + + foreach ($candidate in $candidates | Select-Object -Unique) { + if (Test-Path $candidate) { + return $candidate + } + } + + return $null +} + +function Get-VsInstallationInfo { + <# + .SYNOPSIS + Returns installation metadata for the latest matching Visual Studio instance. + #> + param( + [string[]]$Requires = @() + ) + + $vsWhere = Get-VsWherePath + if (-not $vsWhere) { + return $null + } + + $vsWhereArgs = @('-latest', '-products', '*') + if ($Requires -and $Requires.Count -gt 0) { + $vsWhereArgs += '-requires' + $vsWhereArgs += $Requires + } + + $installationPath = & $vsWhere @vsWhereArgs -property installationPath + if (-not $installationPath) { + return $null + } + + $displayVersion = & $vsWhere @vsWhereArgs -property catalog_productDisplayVersion + + return [pscustomobject]@{ + VsWherePath = $vsWhere + InstallationPath = $installationPath + DisplayVersion = $displayVersion + } +} + +function Get-VsToolchainInfo { + <# + .SYNOPSIS + Returns derived toolchain paths for the latest matching Visual Studio instance. + #> + param( + [string[]]$Requires = @('Microsoft.Component.MSBuild') + ) + + $vsInfo = Get-VsInstallationInfo -Requires $Requires + if (-not $vsInfo) { + return $null + } + + $installationPath = $vsInfo.InstallationPath + $vsDevCmdPath = Join-Path $installationPath 'Common7\Tools\VsDevCmd.bat' + if (-not (Test-Path $vsDevCmdPath)) { + $vsDevCmdPath = $null + } + + $msbuildCandidates = @( + (Join-Path $installationPath 'MSBuild\Current\Bin\amd64\MSBuild.exe'), + (Join-Path $installationPath 'MSBuild\Current\Bin\MSBuild.exe') + ) + $msbuildPath = $msbuildCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + $vsTestPath = Join-Path $installationPath 'Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe' + if (-not (Test-Path $vsTestPath)) { + $vsTestPath = $null + } + + $vcInstallDir = Join-Path $installationPath 'VC' + if (-not (Test-Path $vcInstallDir)) { + $vcInstallDir = $null + } + + $vcTargetsPath = Join-Path $installationPath 'MSBuild\Microsoft\VC\v170' + if (-not (Test-Path $vcTargetsPath)) { + $vcTargetsPath = $null + } + + return [pscustomobject]@{ + VsWherePath = $vsInfo.VsWherePath + InstallationPath = $installationPath + DisplayVersion = $vsInfo.DisplayVersion + VsDevCmdPath = $vsDevCmdPath + MSBuildPath = $msbuildPath + VSTestPath = $vsTestPath + VcInstallDir = $vcInstallDir + VCTargetsPath = $vcTargetsPath + } +} + +function Get-VsDevEnvironmentVariables { + <# + .SYNOPSIS + Returns the environment variables produced by VsDevCmd.bat. + #> + param( + [string]$Architecture = 'amd64', + [string]$HostArchitecture = 'amd64', + [string[]]$Requires = @('Microsoft.Component.MSBuild', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64') + ) + + $toolchain = Get-VsToolchainInfo -Requires $Requires + if (-not $toolchain) { + return $null + } + + if (-not $toolchain.VsDevCmdPath) { + throw "Unable to locate VsDevCmd.bat under '$($toolchain.InstallationPath)'." + } + + $cmdArgs = "`"$($toolchain.VsDevCmdPath)`" -no_logo -arch=$Architecture -host_arch=$HostArchitecture && set" + $envOutput = & cmd.exe /c $cmdArgs 2>&1 + if ($LASTEXITCODE -ne 0) { + throw 'Failed to initialize Visual Studio environment' + } + + $variables = [ordered]@{} + foreach ($line in $envOutput) { + $parts = $line -split '=', 2 + if ($parts.Length -eq 2 -and $parts[0]) { + $variables[$parts[0]] = $parts[1] + } + } + + return [pscustomobject]@{ + Toolchain = $toolchain + Variables = [pscustomobject]$variables + } +} + +function Get-ActiveVcToolBinPath { + <# + .SYNOPSIS + Returns the HostX64\x64 tool bin directory for the active VC toolset. + #> + if (-not [string]::IsNullOrWhiteSpace($env:VCToolsInstallDir)) { + $preferred = Join-Path $env:VCToolsInstallDir 'bin\HostX64\x64' + if (Test-Path (Join-Path $preferred 'cl.exe')) { + return $preferred + } + } + + if (-not [string]::IsNullOrWhiteSpace($env:VCINSTALLDIR)) { + $legacy = Join-Path $env:VCINSTALLDIR 'bin' + if (Test-Path (Join-Path $legacy 'cl.exe')) { + return $legacy + } + } + + return $null +} + +function Test-VsDevEnvironmentActive { + <# + .SYNOPSIS + Returns true when a full VsDevCmd environment is already active. + #> + if ($env:OS -ne 'Windows_NT') { + return $false + } + + if ([string]::IsNullOrWhiteSpace($env:VSCMD_VER) -or [string]::IsNullOrWhiteSpace($env:VCToolsInstallDir)) { + return $false + } + + $activeVcToolPath = Get-ActiveVcToolBinPath + if (-not $activeVcToolPath) { + return $false + } + + $cl = Get-Command 'cl.exe' -ErrorAction SilentlyContinue + $nmake = Get-Command 'nmake.exe' -ErrorAction SilentlyContinue + if (-not $cl -or -not $nmake) { + return $false + } + + $normalizedToolPath = $activeVcToolPath.TrimEnd('\') + $clDirectory = (Split-Path -Parent $cl.Source).TrimEnd('\') + $nmakeDirectory = (Split-Path -Parent $nmake.Source).TrimEnd('\') + + return [string]::Equals($clDirectory, $normalizedToolPath, [System.StringComparison]::OrdinalIgnoreCase) -and + [string]::Equals($nmakeDirectory, $normalizedToolPath, [System.StringComparison]::OrdinalIgnoreCase) +} + +function Ensure-PreferredVcToolPath { + <# + .SYNOPSIS + Moves the active HostX64\x64 MSVC bin directory to the front of PATH. + #> + $preferred = Get-ActiveVcToolBinPath + if (-not $preferred) { + return + } + + $pathEntries = @() + if (-not [string]::IsNullOrWhiteSpace($env:PATH)) { + $pathEntries = $env:PATH -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } + } + + $filteredEntries = $pathEntries | Where-Object { + -not [string]::Equals($_.TrimEnd('\'), $preferred.TrimEnd('\'), [System.StringComparison]::OrdinalIgnoreCase) + } + + $env:PATH = (@($preferred) + $filteredEntries) -join ';' +} + function Initialize-VsDevEnvironment { - <# - .SYNOPSIS - Initializes the Visual Studio Developer environment. - .DESCRIPTION - Sets up environment variables for native C++ compilation (x64 only). - Safe to call multiple times - will skip if already initialized. - #> - if ($env:OS -ne 'Windows_NT') { - return - } - - if ($env:VCINSTALLDIR) { - Write-Host '[OK] Visual Studio environment already initialized' -ForegroundColor Green - return - } - - Write-Host 'Initializing Visual Studio Developer environment...' -ForegroundColor Yellow - - $vswhereCandidates = @() - if ($env:ProgramFiles) { - $pfVswhere = Join-Path -Path $env:ProgramFiles -ChildPath 'Microsoft Visual Studio\Installer\vswhere.exe' - if (Test-Path $pfVswhere) { $vswhereCandidates += $pfVswhere } - } - $programFilesX86 = ${env:ProgramFiles(x86)} - if ($programFilesX86) { - $pf86Vswhere = Join-Path -Path $programFilesX86 -ChildPath 'Microsoft Visual Studio\Installer\vswhere.exe' - if (Test-Path $pf86Vswhere) { $vswhereCandidates += $pf86Vswhere } - } - - if (-not $vswhereCandidates) { - Write-Host '' - Write-Host '[ERROR] Visual Studio 2017+ not found' -ForegroundColor Red - Write-Host ' Install from: https://visualstudio.microsoft.com/downloads/' -ForegroundColor Yellow - throw 'Visual Studio not found' - } - - $vsInstallPath = & $vswhereCandidates[0] -latest -requires Microsoft.Component.MSBuild Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -products * -property installationPath - if (-not $vsInstallPath) { - Write-Host '' - Write-Host '[ERROR] Visual Studio found but missing required C++ tools' -ForegroundColor Red - Write-Host ' Please install the "Desktop development with C++" workload' -ForegroundColor Yellow - throw 'Visual Studio C++ tools not found' - } - - $vsDevCmd = Join-Path -Path $vsInstallPath -ChildPath 'Common7\Tools\VsDevCmd.bat' - if (-not (Test-Path $vsDevCmd)) { - throw "Unable to locate VsDevCmd.bat under '$vsInstallPath'." - } - - # x64-only build - $arch = 'amd64' - $vsVersion = Split-Path (Split-Path (Split-Path (Split-Path $vsInstallPath))) -Leaf - Write-Host " Found Visual Studio $vsVersion at: $vsInstallPath" -ForegroundColor Gray - Write-Host " Setting up environment for $arch..." -ForegroundColor Gray - - $cmdArgs = "`"$vsDevCmd`" -no_logo -arch=$arch -host_arch=$arch && set" - $envOutput = & cmd.exe /c $cmdArgs 2>&1 - if ($LASTEXITCODE -ne 0) { - throw 'Failed to initialize Visual Studio environment' - } - - foreach ($line in $envOutput) { - $parts = $line -split '=', 2 - if ($parts.Length -eq 2 -and $parts[0]) { - Set-Item -Path "Env:$($parts[0])" -Value $parts[1] - } - } - - if (-not $env:VCINSTALLDIR) { - throw 'Visual Studio C++ environment not configured' - } - - Write-Host '[OK] Visual Studio environment initialized successfully' -ForegroundColor Green - Write-Host " VCINSTALLDIR: $env:VCINSTALLDIR" -ForegroundColor Gray + <# + .SYNOPSIS + Initializes the Visual Studio Developer environment. + .DESCRIPTION + Sets up environment variables for native C++ compilation (x64 only). + Safe to call multiple times - will skip if already initialized. + #> + if ($env:OS -ne 'Windows_NT') { + return + } + + if (Test-VsDevEnvironmentActive) { + Ensure-PreferredVcToolPath + Write-Host '[OK] Visual Studio environment already initialized' -ForegroundColor Green + return + } + + if ($env:VCINSTALLDIR -or $env:VCToolsInstallDir -or $env:VSCMD_VER) { + Write-Host '[WARN] Partial Visual Studio environment detected. Reinitializing...' -ForegroundColor Yellow + } + + Write-Host 'Initializing Visual Studio Developer environment...' -ForegroundColor Yellow + + $vsToolchain = Get-VsToolchainInfo -Requires @('Microsoft.Component.MSBuild', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64') + + if (-not $vsToolchain) { + $vsWhere = Get-VsWherePath + Write-Host '' + if (-not $vsWhere) { + Write-Host '[ERROR] Visual Studio 2017+ not found' -ForegroundColor Red + Write-Host ' Install from: https://visualstudio.microsoft.com/downloads/' -ForegroundColor Yellow + throw 'Visual Studio not found' + } + + Write-Host '[ERROR] Visual Studio found but missing required C++ tools' -ForegroundColor Red + Write-Host ' Please install the "Desktop development with C++" workload' -ForegroundColor Yellow + throw 'Visual Studio C++ tools not found' + } + + # x64-only build + $arch = 'amd64' + $vsInstallPath = $vsToolchain.InstallationPath + $vsVersion = if ([string]::IsNullOrWhiteSpace($vsToolchain.DisplayVersion)) { + Split-Path (Split-Path (Split-Path (Split-Path $vsInstallPath))) -Leaf + } + else { + $vsToolchain.DisplayVersion + } + Write-Host " Found Visual Studio $vsVersion at: $vsInstallPath" -ForegroundColor Gray + Write-Host " Setting up environment for $arch..." -ForegroundColor Gray + + $vsEnvironment = Get-VsDevEnvironmentVariables -Architecture $arch -HostArchitecture $arch + foreach ($variable in $vsEnvironment.Variables.PSObject.Properties) { + Set-Item -Path "Env:$($variable.Name)" -Value $variable.Value + } + + if (-not (Test-VsDevEnvironmentActive)) { + throw 'Visual Studio C++ environment not configured' + } + + Ensure-PreferredVcToolPath + + Write-Host '[OK] Visual Studio environment initialized successfully' -ForegroundColor Green + Write-Host " VCINSTALLDIR: $env:VCINSTALLDIR" -ForegroundColor Gray } function Get-CvtresDiagnostics { - <# - .SYNOPSIS - Returns details about the cvtres.exe resolved in the current session. - #> - $result = [ordered]@{ - Path = $null - IsVcToolset = $false - IsDotNetFramework = $false - } - - $cmd = Get-Command "cvtres.exe" -ErrorAction SilentlyContinue - if ($cmd) { - $result.Path = $cmd.Source - $lower = $result.Path.ToLowerInvariant() - $result.IsVcToolset = $lower -match "[\\/]vc[\\/]tools[\\/]msvc[\\/][^\\/]+[\\/]bin[\\/]hostx64[\\/]x64[\\/]cvtres\.exe$" - $result.IsDotNetFramework = $lower -match "windows[\\/]microsoft\.net[\\/]framework" - return $result - } - - if ($env:VCINSTALLDIR) { - $candidates = Get-ChildItem -Path (Join-Path $env:VCINSTALLDIR "Tools\MSVC\*") -Filter cvtres.exe -Recurse -ErrorAction SilentlyContinue | - Sort-Object FullName -Descending - if ($candidates -and $candidates.Count -gt 0) { - $result.Path = $candidates[0].FullName - $lower = $result.Path.ToLowerInvariant() - $result.IsVcToolset = $lower -match "[\\/]vc[\\/]tools[\\/]msvc[\\/][^\\/]+[\\/]bin[\\/]hostx64[\\/]x64[\\/]cvtres\.exe$" - $result.IsDotNetFramework = $lower -match "windows[\\/]microsoft\.net[\\/]framework" - } - } - - return $result + <# + .SYNOPSIS + Returns details about the cvtres.exe resolved in the current session. + #> + $result = [ordered]@{ + Path = $null + IsVcToolset = $false + IsDotNetFramework = $false + } + + $cmd = Get-Command "cvtres.exe" -ErrorAction SilentlyContinue + if ($cmd) { + $result.Path = $cmd.Source + $lower = $result.Path.ToLowerInvariant() + $result.IsVcToolset = $lower -match "[\\/]vc[\\/]tools[\\/]msvc[\\/][^\\/]+[\\/]bin[\\/]hostx64[\\/]x64[\\/]cvtres\.exe$" + $result.IsDotNetFramework = $lower -match "windows[\\/]microsoft\.net[\\/]framework" + return $result + } + + if ($env:VCINSTALLDIR) { + $candidates = Get-ChildItem -Path (Join-Path $env:VCINSTALLDIR "Tools\MSVC\*") -Filter cvtres.exe -Recurse -ErrorAction SilentlyContinue | + Sort-Object FullName -Descending + if ($candidates -and $candidates.Count -gt 0) { + $result.Path = $candidates[0].FullName + $lower = $result.Path.ToLowerInvariant() + $result.IsVcToolset = $lower -match "[\\/]vc[\\/]tools[\\/]msvc[\\/][^\\/]+[\\/]bin[\\/]hostx64[\\/]x64[\\/]cvtres\.exe$" + $result.IsDotNetFramework = $lower -match "windows[\\/]microsoft\.net[\\/]framework" + } + } + + return $result } function Test-CvtresCompatibility { - <# - .SYNOPSIS - Emits warnings if cvtres.exe resolves to a non-VC toolset binary. - #> - $diag = Get-CvtresDiagnostics - - if (-not $diag.Path) { - Write-Host "[WARN] cvtres.exe not found after VS environment setup. Toolchain may be incomplete." -ForegroundColor Yellow - return - } - - if ($diag.IsDotNetFramework) { - Write-Host "[WARN] cvtres.exe resolves to a .NET Framework path. Prefer the VC toolset version (Hostx64\\x64). $($diag.Path)" -ForegroundColor Yellow - } - elseif (-not $diag.IsVcToolset) { - Write-Host "[WARN] cvtres.exe is not from the VC toolset Hostx64\\x64 folder. Confirm PATH ordering. $($diag.Path)" -ForegroundColor Yellow - } + <# + .SYNOPSIS + Emits warnings if cvtres.exe resolves to a non-VC toolset binary. + #> + $diag = Get-CvtresDiagnostics + + if (-not $diag.Path) { + Write-Host "[WARN] cvtres.exe not found after VS environment setup. Toolchain may be incomplete." -ForegroundColor Yellow + return + } + + if ($diag.IsDotNetFramework) { + Write-Host "[WARN] cvtres.exe resolves to a .NET Framework path. Prefer the VC toolset version (Hostx64\\x64). $($diag.Path)" -ForegroundColor Yellow + } + elseif (-not $diag.IsVcToolset) { + Write-Host "[WARN] cvtres.exe is not from the VC toolset Hostx64\\x64 folder. Confirm PATH ordering. $($diag.Path)" -ForegroundColor Yellow + } } # ============================================================================= @@ -150,89 +367,95 @@ function Test-CvtresCompatibility { # ============================================================================= function Get-MSBuildPath { - <# - .SYNOPSIS - Gets the path to MSBuild.exe. - .DESCRIPTION - Returns the MSBuild command, either from PATH or 'msbuild' as fallback. - #> - $msbuildCmd = Get-Command msbuild -ErrorAction SilentlyContinue - if ($msbuildCmd) { - return $msbuildCmd.Source - } - return 'msbuild' + <# + .SYNOPSIS + Gets the path to MSBuild.exe. + .DESCRIPTION + Returns the MSBuild command, either from PATH or 'msbuild' as fallback. + #> + $msbuildCmd = Get-Command msbuild -ErrorAction SilentlyContinue + if ($msbuildCmd) { + return $msbuildCmd.Source + } + + $toolchain = Get-VsToolchainInfo -Requires @('Microsoft.Component.MSBuild') + if ($toolchain -and $toolchain.MSBuildPath) { + return $toolchain.MSBuildPath + } + + return 'msbuild' } function Invoke-MSBuild { - <# - .SYNOPSIS - Executes MSBuild with proper error handling. - .DESCRIPTION - Runs MSBuild with the specified arguments and handles errors appropriately. - .PARAMETER Arguments - Array of arguments to pass to MSBuild. - .PARAMETER Description - Human-readable description of the build step. - .PARAMETER LogPath - Optional path to write build output to a log file. - .PARAMETER TailLines - If specified, only displays the last N lines of output. - #> - param( - [Parameter(Mandatory)] - [string[]]$Arguments, - [Parameter(Mandatory)] - [string]$Description, - [string]$LogPath = '', - [int]$TailLines = 0 - ) - - $msbuildCmd = Get-MSBuildPath - Write-Host "Running $Description..." -ForegroundColor Cyan - - if ($TailLines -gt 0) { - # Capture all output, optionally log to file, then display tail - $output = & $msbuildCmd $Arguments 2>&1 | ForEach-Object { $_.ToString() } - $exitCode = $LASTEXITCODE - - if ($LogPath) { - $logDir = Split-Path -Parent $LogPath - if ($logDir -and -not (Test-Path $logDir)) { - New-Item -Path $logDir -ItemType Directory -Force | Out-Null - } - $output | Out-File -FilePath $LogPath -Encoding utf8 - } - - # Display last N lines - $totalLines = $output.Count - if ($totalLines -gt $TailLines) { - Write-Host "... ($($totalLines - $TailLines) lines omitted, showing last $TailLines) ..." -ForegroundColor DarkGray - $output | Select-Object -Last $TailLines | ForEach-Object { Write-Host $_ } - } - else { - $output | ForEach-Object { Write-Host $_ } - } - - $LASTEXITCODE = $exitCode - } - elseif ($LogPath) { - $logDir = Split-Path -Parent $LogPath - if ($logDir -and -not (Test-Path $logDir)) { - New-Item -Path $logDir -ItemType Directory -Force | Out-Null - } - & $msbuildCmd $Arguments | Tee-Object -FilePath $LogPath - } - else { - & $msbuildCmd $Arguments - } - - if ($LASTEXITCODE -ne 0) { - $errorMsg = "MSBuild failed during $Description with exit code $LASTEXITCODE" - if ($LASTEXITCODE -eq -1073741819) { - $errorMsg += " (0xC0000005 - Access Violation). This indicates a crash in native code during build." - } - throw $errorMsg - } + <# + .SYNOPSIS + Executes MSBuild with proper error handling. + .DESCRIPTION + Runs MSBuild with the specified arguments and handles errors appropriately. + .PARAMETER Arguments + Array of arguments to pass to MSBuild. + .PARAMETER Description + Human-readable description of the build step. + .PARAMETER LogPath + Optional path to write build output to a log file. + .PARAMETER TailLines + If specified, only displays the last N lines of output. + #> + param( + [Parameter(Mandatory)] + [string[]]$Arguments, + [Parameter(Mandatory)] + [string]$Description, + [string]$LogPath = '', + [int]$TailLines = 0 + ) + + $msbuildCmd = Get-MSBuildPath + Write-Host "Running $Description..." -ForegroundColor Cyan + + if ($TailLines -gt 0) { + # Capture all output, optionally log to file, then display tail + $output = & $msbuildCmd $Arguments 2>&1 | ForEach-Object { $_.ToString() } + $exitCode = $LASTEXITCODE + + if ($LogPath) { + $logDir = Split-Path -Parent $LogPath + if ($logDir -and -not (Test-Path $logDir)) { + New-Item -Path $logDir -ItemType Directory -Force | Out-Null + } + $output | Out-File -FilePath $LogPath -Encoding utf8 + } + + # Display last N lines + $totalLines = $output.Count + if ($totalLines -gt $TailLines) { + Write-Host "... ($($totalLines - $TailLines) lines omitted, showing last $TailLines) ..." -ForegroundColor DarkGray + $output | Select-Object -Last $TailLines | ForEach-Object { Write-Host $_ } + } + else { + $output | ForEach-Object { Write-Host $_ } + } + + $LASTEXITCODE = $exitCode + } + elseif ($LogPath) { + $logDir = Split-Path -Parent $LogPath + if ($logDir -and -not (Test-Path $logDir)) { + New-Item -Path $logDir -ItemType Directory -Force | Out-Null + } + & $msbuildCmd $Arguments | Tee-Object -FilePath $LogPath + } + else { + & $msbuildCmd $Arguments + } + + if ($LASTEXITCODE -ne 0) { + $errorMsg = "MSBuild failed during $Description with exit code $LASTEXITCODE" + if ($LASTEXITCODE -eq -1073741819) { + $errorMsg += " (0xC0000005 - Access Violation). This indicates a crash in native code during build." + } + throw $errorMsg + } } # ============================================================================= @@ -240,42 +463,25 @@ function Invoke-MSBuild { # ============================================================================= function Get-VSTestPath { - <# - .SYNOPSIS - Finds vstest.console.exe in PATH or known locations. - .DESCRIPTION - First checks PATH, then falls back to known VS installation paths. - #> - - # Try PATH first (setup scripts add vstest to PATH) - $vstestFromPath = Get-Command "vstest.console.exe" -ErrorAction SilentlyContinue - if ($vstestFromPath) { - return $vstestFromPath.Source - } - - # Fall back to known installation paths - $programFilesX86 = ${env:ProgramFiles(x86)} - if (-not $programFilesX86) { $programFilesX86 = "C:\Program Files (x86)" } - - $vstestCandidates = @( - # BuildTools - "$programFilesX86\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe", - "C:\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe", - # TestAgent (sometimes installed separately) - "$programFilesX86\Microsoft Visual Studio\2022\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe", - # Full VS installations - "${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe", - "${env:ProgramFiles}\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe", - "${env:ProgramFiles}\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" - ) - - foreach ($candidate in $vstestCandidates) { - if (Test-Path $candidate) { - return $candidate - } - } - - return $null + <# + .SYNOPSIS + Finds vstest.console.exe in PATH or known locations. + .DESCRIPTION + First checks PATH, then falls back to known VS installation paths. + #> + + # Try PATH first (setup scripts add vstest to PATH) + $vstestFromPath = Get-Command "vstest.console.exe" -ErrorAction SilentlyContinue + if ($vstestFromPath) { + return $vstestFromPath.Source + } + + $toolchain = Get-VsToolchainInfo -Requires @('Microsoft.Component.MSBuild') + if ($toolchain -and $toolchain.VSTestPath) { + return $toolchain.VSTestPath + } + + return $null } # ============================================================================= @@ -283,9 +489,14 @@ function Get-VSTestPath { # ============================================================================= Export-ModuleMember -Function @( - 'Initialize-VsDevEnvironment', + 'Get-VsWherePath', + 'Get-VsInstallationInfo', + 'Get-VsToolchainInfo', + 'Get-VsDevEnvironmentVariables', + 'Test-VsDevEnvironmentActive', + 'Initialize-VsDevEnvironment', 'Test-CvtresCompatibility', - 'Get-MSBuildPath', - 'Invoke-MSBuild', - 'Get-VSTestPath' + 'Get-MSBuildPath', + 'Invoke-MSBuild', + 'Get-VSTestPath' ) diff --git a/Build/Agent/Run-VsTests.ps1 b/Build/Agent/Run-VsTests.ps1 index 64ed1c1540..9de229e6b0 100644 --- a/Build/Agent/Run-VsTests.ps1 +++ b/Build/Agent/Run-VsTests.ps1 @@ -54,6 +54,7 @@ param( ) $ErrorActionPreference = 'Continue' # Don't stop on stderr output from vstest +Import-Module (Join-Path $PSScriptRoot 'FwBuildEnvironment.psm1') -Force # Find repo root (where FieldWorks.sln is) $repoRoot = $PSScriptRoot @@ -71,12 +72,7 @@ if (-not $OutputDir) { } $runSettings = Join-Path $repoRoot "Test.runsettings" -$vsTestPath = "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" - -if (-not (Test-Path $vsTestPath)) { - # Try BuildTools path - $vsTestPath = "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" -} +$vsTestPath = Get-VSTestPath if (-not (Test-Path $vsTestPath)) { Write-Error "vstest.console.exe not found. Install Visual Studio 2022 or Build Tools." diff --git a/Build/Agent/Setup-FwBuildEnv.ps1 b/Build/Agent/Setup-FwBuildEnv.ps1 index a95fe4c6f5..f6f4784d3a 100644 --- a/Build/Agent/Setup-FwBuildEnv.ps1 +++ b/Build/Agent/Setup-FwBuildEnv.ps1 @@ -35,6 +35,8 @@ param( ) $ErrorActionPreference = 'Stop' +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Import-Module (Join-Path $scriptDir 'FwBuildEnvironment.psm1') -Force function Write-Status { param([string]$Message, [string]$Status = "INFO", [string]$Color = "White") @@ -97,7 +99,6 @@ Write-Host "OutputGitHubEnv: $OutputGitHubEnv" Write-Host "Verify: $Verify" Write-Host "" -$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $repoRoot = Resolve-Path "$scriptDir\..\.." # Set FW_ROOT_CODE_DIR and FW_ROOT_DATA_DIR for DirectoryFinder fallback @@ -109,6 +110,7 @@ Set-EnvVar -Name "FW_ROOT_DATA_DIR" -Value $distFiles $results = @{ VSPath = $null MSBuildPath = $null + VSTestPath = $null Errors = @() } @@ -117,31 +119,38 @@ $results = @{ # ---------------------------------------------------------------------------- Write-Host "--- Locating Visual Studio ---" -ForegroundColor Cyan -$vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -if (Test-Path $vsWhere) { - $vsPath = & $vsWhere -latest -requires Microsoft.Component.MSBuild -products * -property installationPath - if ($vsPath) { - Write-Status "Visual Studio: $vsPath" -Status "OK" - $results.VSPath = $vsPath - - # Set VS environment variables - Set-EnvVar -Name "VSINSTALLDIR" -Value "$vsPath\" - Set-EnvVar -Name "VCINSTALLDIR" -Value "$vsPath\VC\" - - # VCTargetsPath for C++ builds - $vcTargets = Join-Path $vsPath 'MSBuild\Microsoft\VC\v170' - if (Test-Path $vcTargets) { - Set-EnvVar -Name "VCTargetsPath" -Value $vcTargets - } +$toolchain = Get-VsToolchainInfo -Requires @('Microsoft.Component.MSBuild', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64') +if ($toolchain) { + $results.VSPath = $toolchain.InstallationPath + $results.MSBuildPath = $toolchain.MSBuildPath + $results.VSTestPath = $toolchain.VSTestPath + + if ([string]::IsNullOrWhiteSpace($toolchain.DisplayVersion)) { + Write-Status "Visual Studio: $($toolchain.InstallationPath)" -Status "OK" } else { - Write-Status "Visual Studio not found via vswhere" -Status "FAIL" - $results.Errors += "Visual Studio not found" + Write-Status "Visual Studio $($toolchain.DisplayVersion): $($toolchain.InstallationPath)" -Status "OK" + } + + # Export installation hints only; build/test scripts still self-initialize via VsDevCmd. + Set-EnvVar -Name "VSINSTALLDIR" -Value ($toolchain.InstallationPath.TrimEnd('\') + '\') + if ($toolchain.VcInstallDir) { + Set-EnvVar -Name "VCINSTALLDIR" -Value ($toolchain.VcInstallDir.TrimEnd('\') + '\') + } + if ($toolchain.VCTargetsPath) { + Set-EnvVar -Name "VCTargetsPath" -Value $toolchain.VCTargetsPath } } else { - Write-Status "vswhere.exe not found at: $vsWhere" -Status "FAIL" - $results.Errors += "vswhere.exe not found" + $vsWhere = Get-VsWherePath + if ($vsWhere) { + Write-Status "Visual Studio with MSBuild and C++ tools not found" -Status "FAIL" + $results.Errors += "Visual Studio with MSBuild and C++ tools not found" + } + else { + Write-Status "vswhere.exe not found" -Status "FAIL" + $results.Errors += "vswhere.exe not found" + } } # ---------------------------------------------------------------------------- @@ -150,25 +159,11 @@ else { Write-Host "" Write-Host "--- Locating MSBuild ---" -ForegroundColor Cyan -$msbuildCandidates = @() -if ($results.VSPath) { - $msbuildCandidates += Join-Path $results.VSPath 'MSBuild\Current\Bin\MSBuild.exe' - $msbuildCandidates += Join-Path $results.VSPath 'MSBuild\Current\Bin\amd64\MSBuild.exe' -} -$msbuildCandidates += "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" -$msbuildCandidates += "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe" -$msbuildCandidates += "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe" -$msbuildCandidates += "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" - -foreach ($candidate in $msbuildCandidates) { - if (Test-Path $candidate) { - $results.MSBuildPath = $candidate - Write-Status "MSBuild: $candidate" -Status "OK" - break - } +if ($results.MSBuildPath) { + Write-Status "MSBuild: $($results.MSBuildPath)" -Status "OK" + Add-ToPath -Path (Split-Path -Parent $results.MSBuildPath) | Out-Null } - -if (-not $results.MSBuildPath) { +else { Write-Status "MSBuild not found" -Status "FAIL" $results.Errors += "MSBuild not found" } @@ -203,37 +198,12 @@ if (-not $foundNetfx) { Write-Host "" Write-Host "--- Locating VSTest ---" -ForegroundColor Cyan -$vstestPath = $null -$vstestCandidates = @() - -# Check VS installation paths first -if ($results.VSPath) { - $vstestCandidates += Join-Path $results.VSPath 'Common7\IDE\CommonExtensions\Microsoft\TestWindow' -} - -# Add known installation paths (BuildTools, TestAgent, etc.) -$vstestCandidates += @( - 'C:\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow', - "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow", - "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow", - "${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow", - "${env:ProgramFiles}\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\TestWindow", - "${env:ProgramFiles}\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow" -) - -foreach ($candidate in $vstestCandidates) { - if ($candidate -and (Test-Path (Join-Path $candidate 'vstest.console.exe'))) { - $vstestPath = $candidate - Add-ToPath -Path $vstestPath | Out-Null - break - } -} - -if (-not $vstestPath) { - Write-Status "vstest.console.exe not found" -Status "WARN" +if ($results.VSTestPath) { + Add-ToPath -Path (Split-Path -Parent $results.VSTestPath) | Out-Null + Write-Status "VSTest: $($results.VSTestPath)" -Status "OK" } else { - Write-Status "VSTest: $vstestPath" -Status "OK" + Write-Status "vstest.console.exe not found" -Status "WARN" } # ---------------------------------------------------------------------------- @@ -246,6 +216,7 @@ Write-Host "=== Setup Complete ===" -ForegroundColor Cyan if ($OutputGitHubEnv -and $env:GITHUB_OUTPUT) { "msbuild-path=$($results.MSBuildPath)" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append "vs-install-path=$($results.VSPath)" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + "vstest-path=$($results.VSTestPath)" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append } # Return results object for programmatic use diff --git a/Build/Agent/Setup-InstallerBuild.ps1 b/Build/Agent/Setup-InstallerBuild.ps1 index 32f6e4ae92..87d7bd0b47 100644 --- a/Build/Agent/Setup-InstallerBuild.ps1 +++ b/Build/Agent/Setup-InstallerBuild.ps1 @@ -47,6 +47,7 @@ param( $ErrorActionPreference = 'Stop' $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $repoRoot = Split-Path -Parent (Split-Path -Parent $scriptDir) +Import-Module (Join-Path $scriptDir 'FwBuildEnvironment.psm1') -Force Write-Host "========================================" -ForegroundColor Cyan Write-Host " FieldWorks Installer Build Setup" -ForegroundColor Cyan @@ -55,6 +56,10 @@ Write-Host "" $issues = @() $warnings = @() +$vsDevEnvActive = $false +$restoreWrappedCommand = $null +$buildWrappedCommand = $null +$patchWrappedCommand = $null #region WiX Toolset Validation @@ -90,57 +95,41 @@ if (Test-Path $heatFromRepoPackages) { Write-Host "`n--- Checking Visual Studio / MSBuild ---" -ForegroundColor Yellow -$vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -$vsInstall = $null -$vsDevEnvActive = $false +$toolchain = Get-VsToolchainInfo -Requires @('Microsoft.Component.MSBuild', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64') +$vsDevEnvActive = Test-VsDevEnvironmentActive -if (Test-Path $vsWhere) { - $vsInstall = & $vsWhere -latest -property installationPath 2>$null - if ($vsInstall) { - $vsVersion = & $vsWhere -latest -property catalog_productDisplayVersion 2>$null +if ($toolchain) { + $vsVersion = if ([string]::IsNullOrWhiteSpace($toolchain.DisplayVersion)) { 'unknown version' } else { $toolchain.DisplayVersion } Write-Host "[OK] Visual Studio 2022: $vsVersion" -ForegroundColor Green - # Check for MSBuild - $msbuildPath = Join-Path $vsInstall "MSBuild\Current\Bin\MSBuild.exe" - if (Test-Path $msbuildPath) { - Write-Host "[OK] MSBuild found: $msbuildPath" -ForegroundColor Green + if ($toolchain.MSBuildPath) { + Write-Host "[OK] MSBuild found: $($toolchain.MSBuildPath)" -ForegroundColor Green } else { $issues += "MSBuild not found in VS installation" } - # Check for VsDevCmd - $vsDevCmd = Join-Path $vsInstall "Common7\Tools\VsDevCmd.bat" - $launchVsDevShell = Join-Path $vsInstall "Common7\Tools\Launch-VsDevShell.ps1" - if ((Test-Path $vsDevCmd) -or (Test-Path $launchVsDevShell)) { + if ($toolchain.VsDevCmdPath) { Write-Host "[OK] VS Developer environment scripts available" -ForegroundColor Green + $restoreWrappedCommand = 'cmd /c "call ""{0}"" -arch=amd64 >nul && msbuild Build/InstallerBuild.proj /t:RestorePackages /p:Configuration=Debug /p:Platform=x64"' -f $toolchain.VsDevCmdPath + $buildWrappedCommand = 'cmd /c "call ""{0}"" -arch=amd64 >nul && msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:Configuration=Debug /p:Platform=x64 /p:config=release /m /v:n"' -f $toolchain.VsDevCmdPath + $patchWrappedCommand = 'cmd /c "call ""{0}"" -arch=amd64 >nul && msbuild Build/InstallerBuild.proj /t:BuildPatchInstaller /p:Configuration=Debug /p:Platform=x64 /p:config=release /m /v:n"' -f $toolchain.VsDevCmdPath + } else { + $issues += "VsDevCmd.bat not found in VS installation" } - # Check if VS Developer environment is active (nmake in PATH) - $nmake = Get-Command nmake.exe -ErrorAction SilentlyContinue - if ($nmake) { - Write-Host "[OK] VS Developer environment active (nmake in PATH)" -ForegroundColor Green - $vsDevEnvActive = $true + if ($vsDevEnvActive) { + Write-Host "[OK] VS Developer environment active" -ForegroundColor Green } else { - # Check if nmake exists in VS installation - $nmakePath = Join-Path $vsInstall "VC\Tools\MSVC\*\bin\Hostx64\x64\nmake.exe" - $nmakeExists = Get-ChildItem -Path $nmakePath -ErrorAction SilentlyContinue | Select-Object -First 1 - if ($nmakeExists) { Write-Host "[WARN] VS Developer environment NOT active" -ForegroundColor Yellow - Write-Host " nmake.exe exists but is not in PATH" -ForegroundColor Yellow - Write-Host " Run builds from VS Developer Command Prompt or use:" -ForegroundColor Yellow - Write-Host " cmd /c `"call `"$vsDevCmd`" -arch=amd64 && msbuild ...`"" -ForegroundColor Cyan - $warnings += "VS Developer environment not active (nmake not in PATH)" - } else { - Write-Host "[MISSING] C++ build tools (nmake.exe) not found" -ForegroundColor Red - Write-Host " Install 'Desktop development with C++' workload in VS Installer" -ForegroundColor Red - $issues += "C++ build tools not installed (nmake.exe missing)" - } - } - } else { - $issues += "Visual Studio 2022 not installed" + Write-Host " Run builds from VS Developer Command Prompt or use the detected VsDevCmd wrapper commands below" -ForegroundColor Yellow + $warnings += "VS Developer environment not active" } } else { + if (Get-VsWherePath) { + $issues += "Visual Studio 2022 with MSBuild and C++ tools not installed" + } else { $issues += "Visual Studio Installer not found" + } } #endregion @@ -344,17 +333,21 @@ if ($issues.Count -eq 0) { Write-Host " # Option 1: Open VS Developer Command Prompt and run commands there" -ForegroundColor Gray Write-Host " # Option 2: Use these one-liner commands from any PowerShell:" -ForegroundColor Gray Write-Host "" - Write-Host " # Restore packages" -ForegroundColor Gray - Write-Host ' cmd /c "call ""C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat"" -arch=amd64 >nul && msbuild Build/InstallerBuild.proj /t:RestorePackages /p:Configuration=Release /p:Platform=x64"' -ForegroundColor Cyan - Write-Host "" - Write-Host " # Build base installer" -ForegroundColor Gray - Write-Host ' cmd /c "call ""C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat"" -arch=amd64 >nul && msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:Configuration=Release /p:Platform=x64 /p:config=release /m /v:n"' -ForegroundColor Cyan - Write-Host "" - - if ($SetupPatch) { - Write-Host " # Build patch installer" -ForegroundColor Gray - Write-Host ' cmd /c "call ""C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat"" -arch=amd64 >nul && msbuild Build/InstallerBuild.proj /t:BuildPatchInstaller /p:Configuration=Release /p:Platform=x64 /p:config=release /m /v:n"' -ForegroundColor Cyan + if ($restoreWrappedCommand -and $buildWrappedCommand) { + Write-Host " # Restore packages" -ForegroundColor Gray + Write-Host " $restoreWrappedCommand" -ForegroundColor Cyan + Write-Host "" + Write-Host " # Build base installer" -ForegroundColor Gray + Write-Host " $buildWrappedCommand" -ForegroundColor Cyan Write-Host "" + + if ($SetupPatch -and $patchWrappedCommand) { + Write-Host " # Build patch installer" -ForegroundColor Gray + Write-Host " $patchWrappedCommand" -ForegroundColor Cyan + Write-Host "" + } + } else { + Write-Host " Unable to derive a VsDevCmd wrapper command for this installation." -ForegroundColor Yellow } } diff --git a/Build/Agent/Verify-FwDependencies.ps1 b/Build/Agent/Verify-FwDependencies.ps1 index 10c943dc16..60250ad7a4 100644 --- a/Build/Agent/Verify-FwDependencies.ps1 +++ b/Build/Agent/Verify-FwDependencies.ps1 @@ -5,6 +5,8 @@ .DESCRIPTION Checks for required tools and SDKs needed to build FieldWorks. Can be run locally for testing or called from GitHub Actions workflows. + By default, the script writes host output only and does not emit result objects. + Use -PassThru when a caller needs structured results returned on the pipeline. Expected dependencies (typically pre-installed on windows-latest): - Visual Studio 2022 with Desktop & C++ workloads @@ -24,7 +26,8 @@ If specified, prints the full per-dependency section headers and success details instead of the compact summary-only output. .PARAMETER PassThru - If specified, returns the dependency result objects for scripting callers instead of writing them implicitly. + If specified, returns the dependency result objects for scripting callers. + Without -PassThru, the script is quiet-by-default and writes host output only. .EXAMPLE # Quick check @@ -45,6 +48,9 @@ .EXAMPLE # Capture structured results for automation $results = .\Build\Agent\Verify-FwDependencies.ps1 -IncludeOptional -PassThru + +.NOTES + Behavioral change: this script no longer emits dependency result objects unless -PassThru is specified. #> [CmdletBinding()] @@ -56,6 +62,7 @@ param( ) $ErrorActionPreference = 'Stop' +Import-Module (Join-Path $PSScriptRoot 'FwBuildEnvironment.psm1') -Force function Test-Dependency { param( @@ -158,12 +165,14 @@ $results += Test-Dependency -Name "Windows SDK" -Check { # Visual Studio / MSBuild $results += Test-Dependency -Name "Visual Studio 2022" -Check { - $vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" - if (-not (Test-Path $vsWhere)) { throw "vswhere.exe not found" } - $vsPath = & $vsWhere -latest -requires Microsoft.Component.MSBuild -products * -property installationPath - if (-not $vsPath) { throw "No VS installation with MSBuild found" } - $version = & $vsWhere -latest -property catalog_productDisplayVersion - return "Version $version at $vsPath" + $vsInfo = Get-VsInstallationInfo -Requires @('Microsoft.Component.MSBuild', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64') + if (-not $vsInfo) { + $vsWhere = Get-VsWherePath + if (-not $vsWhere) { throw "vswhere.exe not found" } + throw "No VS installation with MSBuild and C++ tools found" + } + + return "Version $($vsInfo.DisplayVersion) at $($vsInfo.InstallationPath)" } # MSBuild @@ -173,11 +182,9 @@ $results += Test-Dependency -Name "MSBuild" -Check { $version = (& msbuild.exe -version -nologo 2>$null | Select-Object -Last 1) return "Version $version" } - # Try via vswhere - $vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" - $vsPath = & $vsWhere -latest -requires Microsoft.Component.MSBuild -products * -property installationPath 2>$null - if ($vsPath) { - $msbuildPath = Join-Path $vsPath 'MSBuild\Current\Bin\MSBuild.exe' + $vsInfo = Get-VsInstallationInfo -Requires @('Microsoft.Component.MSBuild') + if ($vsInfo) { + $msbuildPath = Join-Path $vsInfo.InstallationPath 'MSBuild\Current\Bin\MSBuild.exe' if (Test-Path $msbuildPath) { return "Found at $msbuildPath (not in PATH)" } @@ -217,7 +224,7 @@ $results += Test-Dependency -Name "WiX Toolset (v6 via NuGet)" -Required "Option throw "Installer project not found: $wixProj" } - [xml]$wixProjXml = Get-Content -LiteralPath $wixProj + [xml]$wixProjXml = Get-Content -LiteralPath $wixProj -Raw $projectNode = $wixProjXml.Project $hasWixSdk = $false diff --git a/Build/Src/FwBuildTasks/Make.cs b/Build/Src/FwBuildTasks/Make.cs index 55458e919d..4a91985688 100644 --- a/Build/Src/FwBuildTasks/Make.cs +++ b/Build/Src/FwBuildTasks/Make.cs @@ -111,9 +111,21 @@ protected override string ToolName } } + private static string FindToolInDirectory(string directory, string toolName) + { + if (String.IsNullOrEmpty(directory) || !Directory.Exists(directory)) + return null; + + if (File.Exists(Path.Combine(directory, toolName))) + return directory; + + return null; + } + private void CheckToolPath() { string path = Environment.GetEnvironmentVariable("PATH"); + string vcToolsInstallDir = Environment.GetEnvironmentVariable("VCToolsInstallDir"); string vcInstallDir = Environment.GetEnvironmentVariable("VCINSTALLDIR"); //Console.WriteLine("DEBUG Make Task: PATH='{0}'", path); string makePath = ToolPath == null ? String.Empty : ToolPath.Trim(); @@ -128,7 +140,7 @@ private void CheckToolPath() if (File.Exists(Path.Combine(ToolPath, ToolName))) return; } - string[] splitPath = path.Split(new char[] { Path.PathSeparator }); + string[] splitPath = String.IsNullOrEmpty(path) ? new string[0] : path.Split(new[] { Path.PathSeparator }); foreach (var dir in splitPath) { if (File.Exists(Path.Combine(dir, ToolName))) @@ -137,10 +149,27 @@ private void CheckToolPath() return; } } - // Fall Back to the install directory (if VCINSTALLDIR is set) + if (!String.IsNullOrEmpty(vcToolsInstallDir)) + { + string activeToolPath = FindToolInDirectory(Path.Combine(vcToolsInstallDir, "bin", "Hostx64", "x64"), ToolName); + if (!String.IsNullOrEmpty(activeToolPath)) + { + ToolPath = activeToolPath; + return; + } + } + + // Fall back to the legacy VC install directory (if VCINSTALLDIR is set) if (!String.IsNullOrEmpty(vcInstallDir)) { - ToolPath = Path.Combine(vcInstallDir, "bin"); + string legacyToolPath = FindToolInDirectory(Path.Combine(vcInstallDir, "bin"), ToolName); + if (!String.IsNullOrEmpty(legacyToolPath)) + { + ToolPath = legacyToolPath; + return; + } + + ToolPath = String.Empty; } else { diff --git a/Setup-Developer-Machine.ps1 b/Setup-Developer-Machine.ps1 index e2526a7520..05d96cee49 100644 --- a/Setup-Developer-Machine.ps1 +++ b/Setup-Developer-Machine.ps1 @@ -26,6 +26,8 @@ param( $ErrorActionPreference = 'Stop' $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Import-Module (Join-Path $scriptDir 'Build\Agent\FwBuildEnvironment.psm1') -Force +$vsToolchain = $null Write-Host "========================================" -ForegroundColor Cyan Write-Host " FieldWorks Developer Machine Setup" -ForegroundColor Cyan @@ -57,22 +59,16 @@ if ($git) { # Check Visual Studio 2022 if (-not $SkipVSCheck) { - $vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" - if (Test-Path $vsWhere) { - $vsInstall = & $vsWhere -latest -property installationPath 2>$null - if ($vsInstall) { - $vsVersion = & $vsWhere -latest -property catalog_productDisplayVersion 2>$null - Write-Host "[OK] Visual Studio 2022: $vsVersion" -ForegroundColor Green - - # Check required workloads - $workloads = & $vsWhere -latest -property catalog_productLineVersion 2>$null - Write-Host " Location: $vsInstall" -ForegroundColor Gray - } else { - Write-Host "[MISSING] Visual Studio 2022 - Please install with:" -ForegroundColor Red - Write-Host " - .NET desktop development workload" -ForegroundColor Red - Write-Host " - Desktop development with C++ workload" -ForegroundColor Red - exit 1 - } + $vsToolchain = Get-VsToolchainInfo -Requires @('Microsoft.Component.MSBuild', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64') + if ($vsToolchain) { + $vsVersion = if ([string]::IsNullOrWhiteSpace($vsToolchain.DisplayVersion)) { 'unknown version' } else { $vsToolchain.DisplayVersion } + Write-Host "[OK] Visual Studio 2022: $vsVersion" -ForegroundColor Green + Write-Host " Location: $($vsToolchain.InstallationPath)" -ForegroundColor Gray + } elseif (Get-VsWherePath) { + Write-Host "[MISSING] Visual Studio 2022 - Please install with:" -ForegroundColor Red + Write-Host " - .NET desktop development workload" -ForegroundColor Red + Write-Host " - Desktop development with C++ workload" -ForegroundColor Red + exit 1 } else { Write-Host "[MISSING] Visual Studio 2022 - Please install from https://visualstudio.microsoft.com/" -ForegroundColor Red exit 1 @@ -245,15 +241,12 @@ Write-Host "`n--- Configuring PATH ---" -ForegroundColor Yellow $pathsToAdd = @() # VSTest (Visual Studio 2022) -$vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -if (Test-Path $vsWhere) { - $vsInstall = & $vsWhere -latest -property installationPath 2>$null - if ($vsInstall) { - $vstestPath = Join-Path $vsInstall 'Common7\IDE\CommonExtensions\Microsoft\TestWindow' - if (Test-Path (Join-Path $vstestPath 'vstest.console.exe')) { - $pathsToAdd += $vstestPath - } - } +if (-not $vsToolchain -and -not $SkipVSCheck) { + $vsToolchain = Get-VsToolchainInfo -Requires @('Microsoft.Component.MSBuild', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64') +} + +if ($vsToolchain -and $vsToolchain.VSTestPath) { + $pathsToAdd += (Split-Path -Parent $vsToolchain.VSTestPath) } # Update PATH