From bbaecec35237ae1a0f9170bdfe50e8d29571d6a8 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Mon, 16 Mar 2026 20:23:54 -0400 Subject: [PATCH 1/2] Improve Visual Studio toolchain detection for Windows builds --- Build/Agent/FwBuildEnvironment.psm1 | 48 ++++++++++++++++++++++++++++ Build/Src/FwBuildTasks/Make.cs | 49 ++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/Build/Agent/FwBuildEnvironment.psm1 b/Build/Agent/FwBuildEnvironment.psm1 index b7486e6be1..70ea6dae61 100644 --- a/Build/Agent/FwBuildEnvironment.psm1 +++ b/Build/Agent/FwBuildEnvironment.psm1 @@ -14,6 +14,51 @@ # VS Environment Functions # ============================================================================= +function Get-PreferredVcToolBinPath { + <# + .SYNOPSIS + Returns the preferred HostX64\x64 MSVC tool bin directory. + #> + if (-not $env:VCINSTALLDIR) { + return $null + } + + $toolsRoot = Join-Path $env:VCINSTALLDIR 'Tools\MSVC' + if (-not (Test-Path $toolsRoot)) { + return $null + } + + $preferred = Get-ChildItem -Path $toolsRoot -Directory -ErrorAction SilentlyContinue | + Sort-Object Name -Descending | + ForEach-Object { Join-Path $_.FullName 'bin\HostX64\x64' } | + Where-Object { Test-Path $_ } | + Select-Object -First 1 + + return $preferred +} + +function Ensure-PreferredVcToolPath { + <# + .SYNOPSIS + Moves the preferred HostX64\x64 MSVC bin directory to the front of PATH. + #> + $preferred = Get-PreferredVcToolBinPath + 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 @@ -27,6 +72,7 @@ function Initialize-VsDevEnvironment { } if ($env:VCINSTALLDIR) { + Ensure-PreferredVcToolPath Write-Host '[OK] Visual Studio environment already initialized' -ForegroundColor Green return } @@ -87,6 +133,8 @@ function Initialize-VsDevEnvironment { 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 } diff --git a/Build/Src/FwBuildTasks/Make.cs b/Build/Src/FwBuildTasks/Make.cs index 55458e919d..07b62dd083 100644 --- a/Build/Src/FwBuildTasks/Make.cs +++ b/Build/Src/FwBuildTasks/Make.cs @@ -111,6 +111,39 @@ protected override string ToolName } } + private static string FindVisualStudioToolPath(string vcInstallDir, string toolName) + { + if (String.IsNullOrEmpty(vcInstallDir) || !Directory.Exists(vcInstallDir)) + return null; + + string toolsRoot = Path.Combine(vcInstallDir, "Tools", "MSVC"); + if (!Directory.Exists(toolsRoot)) + return null; + + string[] versionDirs = Directory.GetDirectories(toolsRoot); + Array.Sort(versionDirs, StringComparer.OrdinalIgnoreCase); + Array.Reverse(versionDirs); + + foreach (string versionDir in versionDirs) + { + string[] candidateDirs = + { + Path.Combine(versionDir, "bin", "Hostx64", "x64"), + Path.Combine(versionDir, "bin", "Hostx64", "x86"), + Path.Combine(versionDir, "bin", "Hostx86", "x86"), + Path.Combine(versionDir, "bin", "Hostx86", "x64") + }; + + foreach (string candidateDir in candidateDirs) + { + if (File.Exists(Path.Combine(candidateDir, toolName))) + return candidateDir; + } + } + + return null; + } + private void CheckToolPath() { string path = Environment.GetEnvironmentVariable("PATH"); @@ -140,7 +173,21 @@ private void CheckToolPath() // Fall Back to the install directory (if VCINSTALLDIR is set) if (!String.IsNullOrEmpty(vcInstallDir)) { - ToolPath = Path.Combine(vcInstallDir, "bin"); + string visualStudioToolPath = FindVisualStudioToolPath(vcInstallDir, ToolName); + if (!String.IsNullOrEmpty(visualStudioToolPath)) + { + ToolPath = visualStudioToolPath; + return; + } + + string legacyToolPath = Path.Combine(vcInstallDir, "bin"); + if (File.Exists(Path.Combine(legacyToolPath, ToolName))) + { + ToolPath = legacyToolPath; + return; + } + + ToolPath = String.Empty; } else { From 42e67f1eb61663d8ce90f2800cd7e963aebf1ac0 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Mon, 16 Mar 2026 20:23:55 -0400 Subject: [PATCH 2/2] Fix toolchain version sorting and docs --- Build/Agent/FwBuildEnvironment.psm1 | 21 ++++++++++++++++++++- Build/Agent/Verify-FwDependencies.ps1 | 10 ++++++++-- Build/Src/FwBuildTasks/Make.cs | 27 +++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/Build/Agent/FwBuildEnvironment.psm1 b/Build/Agent/FwBuildEnvironment.psm1 index 70ea6dae61..7d775afb64 100644 --- a/Build/Agent/FwBuildEnvironment.psm1 +++ b/Build/Agent/FwBuildEnvironment.psm1 @@ -28,8 +28,27 @@ function Get-PreferredVcToolBinPath { return $null } + $versionSort = { + $parsedVersion = [version]'0.0' + $isVersion = [version]::TryParse($_.Name, [ref]$parsedVersion) + if ($isVersion) { + return $parsedVersion + } + + return [version]'0.0' + } + + $sortProperties = @( + @{ Expression = { + $parsedVersion = [version]'0.0' + [version]::TryParse($_.Name, [ref]$parsedVersion) + }; Descending = $true } + @{ Expression = $versionSort; Descending = $true } + @{ Expression = { $_.Name }; Descending = $true } + ) + $preferred = Get-ChildItem -Path $toolsRoot -Directory -ErrorAction SilentlyContinue | - Sort-Object Name -Descending | + Sort-Object -Property $sortProperties | ForEach-Object { Join-Path $_.FullName 'bin\HostX64\x64' } | Where-Object { Test-Path $_ } | Select-Object -First 1 diff --git a/Build/Agent/Verify-FwDependencies.ps1 b/Build/Agent/Verify-FwDependencies.ps1 index 5d1195dac5..18510ffced 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()] @@ -217,7 +223,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 07b62dd083..933b5999fd 100644 --- a/Build/Src/FwBuildTasks/Make.cs +++ b/Build/Src/FwBuildTasks/Make.cs @@ -121,8 +121,7 @@ private static string FindVisualStudioToolPath(string vcInstallDir, string toolN return null; string[] versionDirs = Directory.GetDirectories(toolsRoot); - Array.Sort(versionDirs, StringComparer.OrdinalIgnoreCase); - Array.Reverse(versionDirs); + Array.Sort(versionDirs, CompareVersionDirectories); foreach (string versionDir in versionDirs) { @@ -144,6 +143,30 @@ private static string FindVisualStudioToolPath(string vcInstallDir, string toolN return null; } + private static int CompareVersionDirectories(string left, string right) + { + string leftName = Path.GetFileName(left); + string rightName = Path.GetFileName(right); + + Version leftVersion; + Version rightVersion; + bool leftIsVersion = Version.TryParse(leftName, out leftVersion); + bool rightIsVersion = Version.TryParse(rightName, out rightVersion); + + if (leftIsVersion && rightIsVersion) + { + int versionComparison = rightVersion.CompareTo(leftVersion); + if (versionComparison != 0) + return versionComparison; + } + else if (leftIsVersion != rightIsVersion) + { + return rightIsVersion.CompareTo(leftIsVersion); + } + + return StringComparer.OrdinalIgnoreCase.Compare(rightName, leftName); + } + private void CheckToolPath() { string path = Environment.GetEnvironmentVariable("PATH");