From 5b3ef17916cd97b23f840a21beec9516658fd756 Mon Sep 17 00:00:00 2001 From: Adam Boniecki <20281641+abonie@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:20:16 +0100 Subject: [PATCH 1/4] Update the agent pool for the PR builds (#19102) --- azure-pipelines-PR.yml | 54 +++++++++++++----------------------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/azure-pipelines-PR.yml b/azure-pipelines-PR.yml index cfa5411ee33..eee3e74008b 100644 --- a/azure-pipelines-PR.yml +++ b/azure-pipelines-PR.yml @@ -55,6 +55,8 @@ variables: value: Release - name: _PublishUsingPipelines value: true + - name: _WindowsMachineQueueName + value: windows.vs2026preview.scout.amd64.open - name: VisualStudioDropName value: Products/$(System.TeamProject)/$(Build.Repository.Name)/$(Build.SourceBranchName)/$(Build.BuildNumber) - name: Codeql.Enabled @@ -101,7 +103,7 @@ stages: value: Test pool: name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) timeoutInMinutes: 90 strategy: maxParallel: 2 @@ -216,12 +218,8 @@ stages: - job: WindowsLangVersionPreview pool: - # The PR build definition sets this variable: - # WindowsMachineQueueName=Windows.vs2022.amd64.open - # and there is an alternate build definition that sets this to a queue that is always scouting the - # next preview of Visual Studio. name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) timeoutInMinutes: 120 steps: - checkout: self @@ -256,12 +254,8 @@ stages: - job: WindowsNoRealsig_testCoreclr pool: - # The PR build definition sets this variable: - # WindowsMachineQueueName=Windows.vs2022.amd64.open - # and there is an alternate build definition that sets this to a queue that is always scouting the - # next preview of Visual Studio. name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) timeoutInMinutes: 120 steps: - checkout: self @@ -306,12 +300,8 @@ stages: - job: WindowsNoRealsig_testDesktop pool: - # The PR build definition sets this variable: - # WindowsMachineQueueName=Windows.vs2022.amd64.open - # and there is an alternate build definition that sets this to a queue that is always scouting the - # next preview of Visual Studio. name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) timeoutInMinutes: 120 strategy: parallel: 4 @@ -359,12 +349,8 @@ stages: - job: WindowsStrictIndentation pool: - # The PR build definition sets this variable: - # WindowsMachineQueueName=Windows.vs2022.amd64.open - # and there is an alternate build definition that sets this to a queue that is always scouting the - # next preview of Visual Studio. name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) timeoutInMinutes: 120 steps: - checkout: self @@ -400,7 +386,7 @@ stages: - job: WindowsNoStrictIndentation pool: name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) timeoutInMinutes: 120 steps: - checkout: self @@ -441,12 +427,8 @@ stages: - name: __VSNeverShowWhatsNew value: 1 pool: - # The PR build definition sets this variable: - # WindowsMachineQueueName=Windows.vs2022.amd64.open - # and there is an alternate build definition that sets this to a queue that is always scouting the - # next preview of Visual Studio. name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) timeoutInMinutes: 120 strategy: matrix: @@ -546,12 +528,8 @@ stages: - name: __VSNeverShowWhatsNew value: 1 pool: - # The PR build definition sets this variable: - # WindowsMachineQueueName=Windows.vs2022.amd64.open - # and there is an alternate build definition that sets this to a queue that is always scouting the - # next preview of Visual Studio. name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) timeoutInMinutes: 120 strategy: parallel: 4 @@ -622,7 +600,7 @@ stages: - job: MockOfficial pool: name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) steps: - checkout: self clean: true @@ -721,7 +699,7 @@ stages: - job: EndToEndBuildTests pool: name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) strategy: maxParallel: 2 matrix: @@ -761,7 +739,7 @@ stages: - job: Plain_Build_Windows pool: name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) variables: - name: _BuildConfig value: Debug @@ -816,7 +794,7 @@ stages: - job: Benchmarks pool: name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) variables: - name: _BuildConfig value: Release @@ -832,7 +810,7 @@ stages: - job: Build_And_Test_AOT_Windows pool: name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) strategy: maxParallel: 2 matrix: @@ -873,7 +851,7 @@ stages: - job: ILVerify pool: name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals $(WindowsMachineQueueName) + demands: ImageOverride -equals $(_WindowsMachineQueueName) steps: - checkout: self clean: true From a60664c7f004157818707b6b2f7af135c8b491b6 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:48:26 +0000 Subject: [PATCH 2/4] Implement test-only Visual Studio installation discovery infrastructure (#18906) --- .../FSharp.Test.Utilities.fsproj | 2 + .../VSInstallDiscovery.fs | 128 ++++++++++++++++++ .../Helpers/AssemblyResolver.fs | 31 +++-- .../tests/Salsa/VisualFSharp.Salsa.fsproj | 2 +- .../tests/UnitTests/AssemblyResolver.fs | 23 ++-- 5 files changed, 163 insertions(+), 23 deletions(-) create mode 100644 tests/FSharp.Test.Utilities/VSInstallDiscovery.fs diff --git a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj index c1aebde1ed3..7bc4f6e058f 100644 --- a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj +++ b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj @@ -32,6 +32,7 @@ + @@ -93,6 +94,7 @@ + diff --git a/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs b/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs new file mode 100644 index 00000000000..e491ea329bd --- /dev/null +++ b/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.Test + +/// Test-only Visual Studio installation discovery infrastructure. +/// Provides a centralized, robust, and graceful discovery mechanism for Visual Studio installations +/// used by integration/editor/unit tests under vsintegration/tests. +module VSInstallDiscovery = + + open System + open System.IO + open System.Diagnostics + + /// Result of VS installation discovery + type VSInstallResult = + | Found of installPath: string * source: string + | NotFound of reason: string + + /// Attempts to find a Visual Studio installation using multiple fallback strategies + let tryFindVSInstallation () : VSInstallResult = + + /// Check if a path exists and looks like a valid VS installation + let validateVSPath path = + if String.IsNullOrEmpty(path) then false + else + try + let fullPath = Path.GetFullPath(path) + Directory.Exists(fullPath) && + Directory.Exists(Path.Combine(fullPath, "IDE")) && + (File.Exists(Path.Combine(fullPath, "IDE", "devenv.exe")) || + File.Exists(Path.Combine(fullPath, "IDE", "VSIXInstaller.exe"))) + with + | _ -> false + + /// Strategy 1: VSAPPIDDIR (derive parent of Common7/IDE) + let tryVSAppIdDir () = + let envVar = Environment.GetEnvironmentVariable("VSAPPIDDIR") + if not (String.IsNullOrEmpty(envVar)) then + try + let parentPath = Path.Combine(envVar, "..") + if validateVSPath parentPath then + Some (Found (Path.GetFullPath(parentPath), "VSAPPIDDIR environment variable")) + else None + with + | _ -> None + else None + + /// Strategy 2: Highest version among VS*COMNTOOLS environment variables + let tryVSCommonTools () = + let vsVersions = [ + ("VS180COMNTOOLS", 18) // Visual Studio 2026 + ("VS170COMNTOOLS", 17) // Visual Studio 2022 + ("VS160COMNTOOLS", 16) // Visual Studio 2019 + ("VS150COMNTOOLS", 15) // Visual Studio 2017 + ("VS140COMNTOOLS", 14) // Visual Studio 2015 + ("VS120COMNTOOLS", 12) // Visual Studio 2013 + ] + + vsVersions + |> List.tryPick (fun (envName, version) -> + let envVar = Environment.GetEnvironmentVariable(envName) + if not (String.IsNullOrEmpty(envVar)) then + try + let parentPath = Path.Combine(envVar, "..") + if validateVSPath parentPath then + Some (Found (Path.GetFullPath(parentPath), $"{envName} environment variable (VS version {version})")) + else None + with + | _ -> None + else None) + + /// Strategy 3: vswhere.exe (Visual Studio Installer) + let tryVSWhere () = + try + let programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + let vswherePath = Path.Combine(programFiles, "Microsoft Visual Studio", "Installer", "vswhere.exe") + + if File.Exists(vswherePath) then + let startInfo = ProcessStartInfo( + FileName = vswherePath, + Arguments = "-latest -products * -requires Microsoft.Component.MSBuild -property installationPath", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + ) + + use proc = Process.Start(startInfo) + proc.WaitForExit(5000) |> ignore // 5 second timeout + + if proc.ExitCode = 0 then + let output = proc.StandardOutput.ReadToEnd().Trim() + if validateVSPath output then + Some (Found (Path.GetFullPath(output), "vswhere.exe discovery")) + else None + else None + else None + with + | _ -> None + + // Try each strategy in order of precedence + match tryVSAppIdDir () with + | Some result -> result + | None -> + match tryVSCommonTools () with + | Some result -> result + | None -> + match tryVSWhere () with + | Some result -> result + | None -> NotFound "No Visual Studio installation found using any discovery method" + + /// Gets the VS installation directory, with graceful fallback behavior. + /// Returns None if no VS installation can be found, allowing callers to handle gracefully. + let tryGetVSInstallDir () : string option = + match tryFindVSInstallation () with + | Found (path, _) -> Some path + | NotFound _ -> None + + /// Gets the VS installation directory with detailed logging. + /// Useful for debugging installation discovery issues in tests. + let getVSInstallDirWithLogging (logAction: string -> unit) : string option = + match tryFindVSInstallation () with + | Found (path, source) -> + logAction $"Visual Studio installation found at: {path} (via {source})" + Some path + | NotFound reason -> + logAction $"Visual Studio installation not found: {reason}" + None diff --git a/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs index f41f10fa138..78a57793517 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs @@ -8,21 +8,26 @@ open System.Reflection module AssemblyResolver = open System.Globalization + open FSharp.Test.VSInstallDiscovery let vsInstallDir = - // use the environment variable to find the VS installdir - let vsvar = - let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS") - - if String.IsNullOrEmpty var then - Environment.GetEnvironmentVariable("VSAPPIDDIR") - else - var - - if String.IsNullOrEmpty vsvar then - failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found." - - Path.Combine(vsvar, "..") + // Use centralized VS installation discovery with graceful fallback + match tryGetVSInstallDir () with + | Some dir -> dir + | None -> + // Fallback to legacy behavior for backward compatibility + let vsvar = + let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS") + + if String.IsNullOrEmpty var then + Environment.GetEnvironmentVariable("VSAPPIDDIR") + else + var + + if String.IsNullOrEmpty vsvar then + failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found." + + Path.Combine(vsvar, "..") let probingPaths = [| diff --git a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj index 6396eada6a9..f7410214430 100644 --- a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj +++ b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj @@ -16,7 +16,7 @@ - + CompilerLocation.fs diff --git a/vsintegration/tests/UnitTests/AssemblyResolver.fs b/vsintegration/tests/UnitTests/AssemblyResolver.fs index aab95cc46fc..38b5ee45290 100644 --- a/vsintegration/tests/UnitTests/AssemblyResolver.fs +++ b/vsintegration/tests/UnitTests/AssemblyResolver.fs @@ -6,17 +6,22 @@ open System.Reflection module AssemblyResolver = open System.Globalization + open FSharp.Test.VSInstallDiscovery let vsInstallDir = - // use the environment variable to find the VS installdir - let vsvar = - let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS") - if String.IsNullOrEmpty var then - Environment.GetEnvironmentVariable("VSAPPIDDIR") - else - var - if String.IsNullOrEmpty vsvar then failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found." - Path.Combine(vsvar, "..") + // Use centralized VS installation discovery with graceful fallback + match tryGetVSInstallDir () with + | Some dir -> dir + | None -> + // Fallback to legacy behavior for backward compatibility + let vsvar = + let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS") + if String.IsNullOrEmpty var then + Environment.GetEnvironmentVariable("VSAPPIDDIR") + else + var + if String.IsNullOrEmpty vsvar then failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found." + Path.Combine(vsvar, "..") let probingPaths = [| Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\Editor") From 7861ca6766f7cae6c3b11118321eb0e956c2a9b2 Mon Sep 17 00:00:00 2001 From: Adam Boniecki <20281641+abonie@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:25:08 +0100 Subject: [PATCH 3/4] Quick fix for vs dir discovery (#19086) --- vsintegration/tests/Salsa/VsMocks.fs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/vsintegration/tests/Salsa/VsMocks.fs b/vsintegration/tests/Salsa/VsMocks.fs index 5189b7faec5..1ce67c04763 100644 --- a/vsintegration/tests/Salsa/VsMocks.fs +++ b/vsintegration/tests/Salsa/VsMocks.fs @@ -1653,12 +1653,18 @@ module internal VsActual = let vsInstallDir = // use the environment variable to find the VS installdir let vsvar = - let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS") - if String.IsNullOrEmpty var then - Environment.GetEnvironmentVariable("VSAPPIDDIR") + // Try VS180COMNTOOLS first, then VS170COMNTOOLS, then VSAPPIDDIR + // TODO : use tryGetVSInstallDir from test utils instead + let var18 = Environment.GetEnvironmentVariable("VS180COMNTOOLS") + if String.IsNullOrEmpty var18 then + let var17 = Environment.GetEnvironmentVariable("VS170COMNTOOLS") + if String.IsNullOrEmpty var17 then + Environment.GetEnvironmentVariable("VSAPPIDDIR") + else + var17 else - var - if String.IsNullOrEmpty vsvar then failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found." + var18 + if String.IsNullOrEmpty vsvar then failwith "VS180COMNTOOLS, VS170COMNTOOLS and VSAPPIDDIR environment variables not found." Path.Combine(vsvar, "..") let CreateEditorCatalog() = From 78552e7342c738a7a826d823723f60acfdfca64b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:38:06 +0000 Subject: [PATCH 4/4] Centralize Visual Studio installation discovery and assembly resolution in test infrastructure (#19085) --- .../FSharp.Test.Utilities.fsproj | 3 +- .../VSInstallDiscovery.fs | 52 ++++++++++++++++ .../Helpers/AssemblyResolver.fs | 61 ++----------------- .../tests/Salsa/VisualFSharp.Salsa.fsproj | 3 + vsintegration/tests/Salsa/VsMocks.fs | 18 +----- .../tests/UnitTests/AssemblyResolver.fs | 52 ++-------------- 6 files changed, 67 insertions(+), 122 deletions(-) diff --git a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj index 7bc4f6e058f..d899b551e30 100644 --- a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj +++ b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj @@ -102,7 +102,8 @@ - $(NoWarn);NU1510 + $(NoWarn);NU1510;44 + diff --git a/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs b/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs index e491ea329bd..e59eec8eed2 100644 --- a/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs +++ b/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs @@ -126,3 +126,55 @@ module VSInstallDiscovery = | NotFound reason -> logAction $"Visual Studio installation not found: {reason}" None + + /// Gets the VS installation directory or fails with a detailed error message. + /// This is the recommended method for test scenarios that require VS to be installed. + let getVSInstallDirOrFail () : string = + match tryFindVSInstallation () with + | Found (path, _) -> path + | NotFound reason -> + failwith $"Visual Studio installation not found: {reason}. Ensure VS is installed or environment variables (VSAPPIDDIR, VS*COMNTOOLS) are set." + +/// Assembly resolver for Visual Studio test infrastructure. +/// Provides centralized assembly resolution for VS integration tests. +module VSAssemblyResolver = + open System + open System.IO + open System.Reflection + open System.Globalization + + /// Adds an assembly resolver that probes Visual Studio installation directories. + /// This should be called early in test initialization to ensure VS assemblies can be loaded. + let addResolver () = + let vsInstallDir = VSInstallDiscovery.getVSInstallDirOrFail () + + let probingPaths = + [| + Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\Editor") + Path.Combine(vsInstallDir, @"IDE\PublicAssemblies") + Path.Combine(vsInstallDir, @"IDE\PrivateAssemblies") + Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\LanguageServices") + Path.Combine(vsInstallDir, @"IDE\Extensions\Microsoft\CodeSense\Framework") + Path.Combine(vsInstallDir, @"IDE") + |] + + AppDomain.CurrentDomain.add_AssemblyResolve(fun _ args -> + let found () = + probingPaths + |> Seq.tryPick (fun p -> + try + let name = AssemblyName(args.Name) + let codebase = Path.GetFullPath(Path.Combine(p, name.Name) + ".dll") + if File.Exists(codebase) then + name.CodeBase <- codebase + name.CultureInfo <- Unchecked.defaultof + name.Version <- Unchecked.defaultof + Some name + else + None + with _ -> + None) + + match found () with + | None -> Unchecked.defaultof + | Some name -> Assembly.Load(name)) diff --git a/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs index 78a57793517..ef8c01ca908 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs @@ -2,62 +2,9 @@ namespace FSharp.Editor.Tests.Helpers -open System -open System.IO -open System.Reflection - module AssemblyResolver = - open System.Globalization - open FSharp.Test.VSInstallDiscovery - - let vsInstallDir = - // Use centralized VS installation discovery with graceful fallback - match tryGetVSInstallDir () with - | Some dir -> dir - | None -> - // Fallback to legacy behavior for backward compatibility - let vsvar = - let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS") - - if String.IsNullOrEmpty var then - Environment.GetEnvironmentVariable("VSAPPIDDIR") - else - var - - if String.IsNullOrEmpty vsvar then - failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found." - - Path.Combine(vsvar, "..") - - let probingPaths = - [| - Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\Editor") - Path.Combine(vsInstallDir, @"IDE\PublicAssemblies") - Path.Combine(vsInstallDir, @"IDE\PrivateAssemblies") - Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\LanguageServices") - Path.Combine(vsInstallDir, @"IDE\Extensions\Microsoft\CodeSense\Framework") - Path.Combine(vsInstallDir, @"IDE") - |] - - let addResolver () = - AppDomain.CurrentDomain.add_AssemblyResolve (fun h args -> - let found () = - (probingPaths) - |> Seq.tryPick (fun p -> - try - let name = AssemblyName(args.Name) - let codebase = Path.GetFullPath(Path.Combine(p, name.Name) + ".dll") - - if File.Exists(codebase) then - name.CodeBase <- codebase - name.CultureInfo <- Unchecked.defaultof - name.Version <- Unchecked.defaultof - Some(name) - else - None - with _ -> - None) + open FSharp.Test.VSAssemblyResolver - match found () with - | None -> Unchecked.defaultof - | Some name -> Assembly.Load(name)) + /// Adds an assembly resolver that probes Visual Studio installation directories. + /// This is a compatibility shim that delegates to the centralized implementation. + let addResolver = addResolver diff --git a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj index f7410214430..d41b116c21e 100644 --- a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj +++ b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj @@ -24,6 +24,9 @@ UnitTests.TestLib.Utils.fs + + VSInstallDiscovery.fs + diff --git a/vsintegration/tests/Salsa/VsMocks.fs b/vsintegration/tests/Salsa/VsMocks.fs index 1ce67c04763..54fe8b906de 100644 --- a/vsintegration/tests/Salsa/VsMocks.fs +++ b/vsintegration/tests/Salsa/VsMocks.fs @@ -1642,6 +1642,7 @@ module internal VsActual = open System.ComponentModel.Composition.Primitives open Microsoft.VisualStudio.Text open Microsoft.VisualStudio.Threading + open FSharp.Test.VSInstallDiscovery type TestExportJoinableTaskContext () = @@ -1650,22 +1651,7 @@ module internal VsActual = [)>] member public _.JoinableTaskContext : JoinableTaskContext = jtc - let vsInstallDir = - // use the environment variable to find the VS installdir - let vsvar = - // Try VS180COMNTOOLS first, then VS170COMNTOOLS, then VSAPPIDDIR - // TODO : use tryGetVSInstallDir from test utils instead - let var18 = Environment.GetEnvironmentVariable("VS180COMNTOOLS") - if String.IsNullOrEmpty var18 then - let var17 = Environment.GetEnvironmentVariable("VS170COMNTOOLS") - if String.IsNullOrEmpty var17 then - Environment.GetEnvironmentVariable("VSAPPIDDIR") - else - var17 - else - var18 - if String.IsNullOrEmpty vsvar then failwith "VS180COMNTOOLS, VS170COMNTOOLS and VSAPPIDDIR environment variables not found." - Path.Combine(vsvar, "..") + let vsInstallDir = getVSInstallDirOrFail () let CreateEditorCatalog() = let thisAssembly = Assembly.GetExecutingAssembly().Location diff --git a/vsintegration/tests/UnitTests/AssemblyResolver.fs b/vsintegration/tests/UnitTests/AssemblyResolver.fs index 38b5ee45290..cf36b723e40 100644 --- a/vsintegration/tests/UnitTests/AssemblyResolver.fs +++ b/vsintegration/tests/UnitTests/AssemblyResolver.fs @@ -1,52 +1,8 @@ namespace Microsoft.VisualStudio.FSharp -open System -open System.IO -open System.Reflection - module AssemblyResolver = - open System.Globalization - open FSharp.Test.VSInstallDiscovery - - let vsInstallDir = - // Use centralized VS installation discovery with graceful fallback - match tryGetVSInstallDir () with - | Some dir -> dir - | None -> - // Fallback to legacy behavior for backward compatibility - let vsvar = - let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS") - if String.IsNullOrEmpty var then - Environment.GetEnvironmentVariable("VSAPPIDDIR") - else - var - if String.IsNullOrEmpty vsvar then failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found." - Path.Combine(vsvar, "..") - - let probingPaths = [| - Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\Editor") - Path.Combine(vsInstallDir, @"IDE\PublicAssemblies") - Path.Combine(vsInstallDir, @"IDE\PrivateAssemblies") - Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\LanguageServices") - Path.Combine(vsInstallDir, @"IDE\Extensions\Microsoft\CodeSense\Framework") - Path.Combine(vsInstallDir, @"IDE") - |] + open FSharp.Test.VSAssemblyResolver - let addResolver () = - AppDomain.CurrentDomain.add_AssemblyResolve(fun h args -> - let found () = - (probingPaths ) |> Seq.tryPick(fun p -> - try - let name = AssemblyName(args.Name) - let codebase = Path.GetFullPath(Path.Combine(p, name.Name) + ".dll") - if File.Exists(codebase) then - name.CodeBase <- codebase - name.CultureInfo <- Unchecked.defaultof - name.Version <- Unchecked.defaultof - Some (name) - else None - with | _ -> None - ) - match found() with - | None -> Unchecked.defaultof - | Some name -> Assembly.Load(name) ) + /// Adds an assembly resolver that probes Visual Studio installation directories. + /// This is a compatibility shim that delegates to the centralized implementation. + let addResolver = addResolver