diff --git a/azure-pipelines-PR.yml b/azure-pipelines-PR.yml index 563f0afcbfb..fc8eca68abc 100644 --- a/azure-pipelines-PR.yml +++ b/azure-pipelines-PR.yml @@ -475,6 +475,69 @@ stages: - checkout: self clean: true + - task: PowerShell@2 + displayName: Detect / ensure VisualStudioVersion=17.0 (testVs only) + condition: and(succeeded(), eq(variables._testKind, 'testVs')) + inputs: + targetType: inline + pwsh: false + script: | + Write-Host "VS Detect: starting" + + function Set-VSV([string]$v) { + $env:VisualStudioVersion = $v + Write-Host "##vso[task.setvariable variable=VisualStudioVersion]$v" + Write-Host "VS Detect: SET VisualStudioVersion=$v" + } + + $vswhere = $null + $cmd = Get-Command vswhere.exe -ErrorAction SilentlyContinue + if ($cmd) { + $vswhere = $cmd.Source + } else { + $candidates = @( + "$env:ProgramFiles(x86)\Microsoft Visual Studio\Installer\vswhere.exe", + "$env:ProgramFiles\Microsoft Visual Studio\Installer\vswhere.exe" + ) + foreach ($c in $candidates) { if (Test-Path $c) { $vswhere = $c; break } } + } + + if ($vswhere -and (Test-Path $vswhere)) { + Write-Host "VS Detect: using vswhere '$vswhere'" + $json = & $vswhere -latest -prerelease -products * -format json 2>$null + if ($LASTEXITCODE -eq 0 -and $json) { + try { + $obj = $json | ConvertFrom-Json + if ($obj -is [System.Array]) { $obj = $obj[0] } + $ver = $obj.installationVersion + Write-Host "VS Detect: installationVersion=$ver" + if ($ver -match '^17\.') { + if (-not $env:VisualStudioVersion) { Set-VSV "17.0" } + } else { + Write-Host "VS Detect: latest VS not 17.x (ver=$ver) – leaving fallback logic" + } + } catch { + Write-Host "VS Detect: JSON parse failed: $($_.Exception.Message)" + } + } else { + Write-Host "VS Detect: vswhere produced no usable data" + } + } else { + Write-Host "VS Detect: vswhere not found" + } + + if (-not $env:VisualStudioVersion) { + Write-Host "VS Detect: forcing VisualStudioVersion=17.0 (fallback)" + Set-VSV "17.0" + } + + $final = if ($env:VisualStudioVersion) { $env:VisualStudioVersion } else { "" } + Write-Host "VS Detect: done (final VisualStudioVersion='$final')" + + - script: echo VisualStudioVersion=$(VisualStudioVersion) + displayName: Echo VisualStudioVersion + condition: and(succeeded(), eq(variables._testKind, 'testVs')) + - powershell: eng\SetupVSHive.ps1 displayName: Setup VS Hive condition: eq(variables.setupVsHive, 'true') diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 7851dd5f468..7fca5cde037 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,10 +6,10 @@ This file should be imported by eng/Versions.props - 17.15.0-preview-25278-01 - 17.15.0-preview-25278-01 - 17.15.0-preview-25278-01 - 17.15.0-preview-25278-01 + 17.15.0-preview-25461-02 + 17.15.0-preview-25461-02 + 17.15.0-preview-25461-02 + 17.15.0-preview-25461-02 9.0.0 9.0.0 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 1f1a3c08e80..9023167587b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -2,21 +2,21 @@ - + https://github.com/dotnet/msbuild - 6de64741f6440bb6730d9a342934ce49d600fcc9 + 610aa6fdc1ab787670d0ba2db53a4546ae63a874 - + https://github.com/dotnet/msbuild - 6de64741f6440bb6730d9a342934ce49d600fcc9 + 610aa6fdc1ab787670d0ba2db53a4546ae63a874 - + https://github.com/dotnet/msbuild - 6de64741f6440bb6730d9a342934ce49d600fcc9 + 610aa6fdc1ab787670d0ba2db53a4546ae63a874 - + https://github.com/dotnet/msbuild - 6de64741f6440bb6730d9a342934ce49d600fcc9 + 610aa6fdc1ab787670d0ba2db53a4546ae63a874 https://github.com/dotnet/runtime diff --git a/fuckingtest.ps1 b/fuckingtest.ps1 new file mode 100644 index 00000000000..c6865e7ae1b --- /dev/null +++ b/fuckingtest.ps1 @@ -0,0 +1,56 @@ +# (A1) Build (Release) – create all artifacts needed by tests +.\Build.cmd -c Release + +# (A2) Set up the VS experimental hive (same as CI step 'Setup VS Hive') +# This script will: +# - Discover / validate a VS 17.x install +# - Set / persist required hive registry + layout +# - Emit the required environment variables in the current session +# If it has a -Configuration parameter in your branch, pass it; otherwise omit. +powershell -ExecutionPolicy Bypass -File .\eng\SetupVSHive.ps1 -Configuration Release + +# (A3) (Optional) Verify env vars now exist +"`nVSAPPIDDIR=$env:VSAPPIDDIR" +"VS170COMNTOOLS=$env:VS170COMNTOOLS" + +if (-not $env:VSAPPIDDIR -or -not $env:VS170COMNTOOLS) { + Write-Host "Hive setup did not set required env vars. Aborting." -ForegroundColor Red + exit 1 +} + +# (A4) Path to the unit test assembly +$testDll = Join-Path $PWD "artifacts\bin\VisualFSharp.UnitTests\Release\net472\VisualFSharp.UnitTests.dll" + +if (-not (Test-Path $testDll)) { + Write-Host "Test assembly missing: $testDll" -ForegroundColor Red + exit 1 +} + +# (A5) Enable your instrumentation +$env:FSharpAutoImportDiag = "1" +$env:FSharpTargetsDiagnostic = "true" + +# (A6) Run only the failing test using vstest.console (closer to CI than dotnet test for VS integration) +$vswhere = Join-Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer" "vswhere.exe" +if (-not (Test-Path $vswhere)) { + Write-Host "vswhere.exe not found; ensure VS 2022 installed." -ForegroundColor Yellow +} + +# Optionally locate vstest.console if not on PATH +$vstest = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio" -Recurse -Include vstest.console.exe -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -like "*\Common7\IDE\Extensions\TestPlatform\vstest.console.exe" } | + Sort-Object Length | Select-Object -First 1 + +if (-not $vstest) { + Write-Host "Could not locate vstest.console.exe; falling back to 'dotnet test'." -ForegroundColor Yellow + dotnet test $testDll --framework net472 --filter "FullyQualifiedName=Tests.ProjectSystem.ProjectItems.RemoveAssemblyReference.NoIVsTrackProjectDocuments2Events" + exit $LASTEXITCODE +} + +& $vstest.FullName ` + $testDll ` + /Framework:".NETFramework,Version=v4.7.2" ` + /TestCaseFilter:"FullyQualifiedName=Tests.ProjectSystem.ProjectItems.RemoveAssemblyReference.NoIVsTrackProjectDocuments2Events" ` + /Logger:trx /InIsolation + +Write-Host "`nDone." \ No newline at end of file diff --git a/src/Compiler/Facilities/SimulatedMSBuildReferenceResolver.fs b/src/Compiler/Facilities/SimulatedMSBuildReferenceResolver.fs index dc0e30cac72..926a99efbb5 100644 --- a/src/Compiler/Facilities/SimulatedMSBuildReferenceResolver.fs +++ b/src/Compiler/Facilities/SimulatedMSBuildReferenceResolver.fs @@ -43,8 +43,23 @@ let private Net472 = "v4.7.2" [] let private Net48 = "v4.8" +[] +let private Net481 = "v4.8.1" + let SupportedDesktopFrameworkVersions = - [ Net48; Net472; Net471; Net47; Net462; Net461; Net46; Net452; Net451; Net45 ] + [ + Net481 + Net48 + Net472 + Net471 + Net47 + Net462 + Net461 + Net46 + Net452 + Net451 + Net45 + ] let private SimulatedMSBuildResolver = diff --git a/src/FSharp.Build/Fsc.fs b/src/FSharp.Build/Fsc.fs index 3cce873e722..95cd3e1640d 100644 --- a/src/FSharp.Build/Fsc.fs +++ b/src/FSharp.Build/Fsc.fs @@ -785,7 +785,22 @@ type public Fsc() as this = | "" -> () | NonNull dotnetFscCompilerPath -> builder.AppendSwitch(dotnetFscCompilerPath) - builder.ToString() + let cmd = builder.ToString() + + // FWDIAG-FSC + try + let nf = noFramework + let refs = + match references with + | arr -> arr.Length + System.Diagnostics.Trace.WriteLine( + sprintf "FWDIAG-FSC VS=%s NoFramework=%b RefCount=%d OutputAssembly=%s" + (System.Environment.GetEnvironmentVariable("VisualStudioVersion")) + nf refs outputAssembly + ) + with _ -> () + + cmd override _.GenerateResponseFileCommands() = let builder = generateCommandLineBuilder () diff --git a/src/FSharp.Build/Microsoft.FSharp.Targets b/src/FSharp.Build/Microsoft.FSharp.Targets index a1385f6aff2..77bd0d8e334 100644 --- a/src/FSharp.Build/Microsoft.FSharp.Targets +++ b/src/FSharp.Build/Microsoft.FSharp.Targets @@ -128,6 +128,28 @@ this file. true + + + + false + + + + + + + + + + + + + + + + ] let private Net48 = "v4.8" +[] +let private Net481 = "v4.8.1" + let SupportedDesktopFrameworkVersions = - [ Net48; Net472; Net471; Net47; Net462; Net461; Net46; Net452; Net451; Net45 ] + [ + Net481 + Net48 + Net472 + Net471 + Net47 + Net462 + Net461 + Net46 + Net452 + Net451 + Net45 + ] /// Get the path to the .NET Framework implementation assemblies by using ToolLocationHelper.GetPathToDotNetFramework /// This is only used to specify the "last resort" path for assembly resolution. @@ -87,6 +102,7 @@ let GetPathToDotNetFrameworkImplementationAssemblies v : string list = | Net471 -> Some TargetDotNetFrameworkVersion.Version471 | Net472 -> Some TargetDotNetFrameworkVersion.Version472 | Net48 -> Some TargetDotNetFrameworkVersion.Version48 + | Net481 -> Some TargetDotNetFrameworkVersion.Version481 | _ -> assert false None @@ -114,7 +130,9 @@ let GetPathToDotNetFrameworkReferenceAssemblies version = let HighestInstalledRefAssembliesOrDotNETFramework () = let getHighestInstalledDotNETFramework () = try - if not (isNull (box (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version48)))) then + if not (isNull (box (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version481)))) then + Net481 + elif not (isNull (box (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version48)))) then Net48 elif not (isNull (box (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version472)))) then Net472 diff --git a/vsintegration/src/FSharp.LanguageService/FSharpSource.fs b/vsintegration/src/FSharp.LanguageService/FSharpSource.fs index f5184309306..7b377ee977c 100644 --- a/vsintegration/src/FSharp.LanguageService/FSharpSource.fs +++ b/vsintegration/src/FSharp.LanguageService/FSharpSource.fs @@ -358,6 +358,15 @@ type internal FSharpSource_DEPRECATED(service:LanguageService_DEPRECATED, textLi yield "--define:COMPILED" |] + + // FWDIAG LS + try + let hasNF = flags |> Array.exists ((=) "--noframework") + System.Diagnostics.Trace.WriteLine(sprintf "FWDIAG-LS GetParseTree VS=%s hasNoFramework=%b flags=%A" + (System.Environment.GetEnvironmentVariable("VisualStudioVersion")) + hasNF flags) + with _ -> () + // get a sync parse of the file let co, _ = { ProjectFileName = fileName + ".dummy.fsproj" diff --git a/vsintegration/src/FSharp.ProjectSystem.Base/ProjectNode.cs b/vsintegration/src/FSharp.ProjectSystem.Base/ProjectNode.cs index 9e250fcb392..d3989bcae17 100644 --- a/vsintegration/src/FSharp.ProjectSystem.Base/ProjectNode.cs +++ b/vsintegration/src/FSharp.ProjectSystem.Base/ProjectNode.cs @@ -3224,6 +3224,88 @@ public void Dispose() } } + /// + /// Ensure Microsoft.Common.targets (and via it Microsoft.Common.CurrentVersion.targets) is imported + /// for legacy F# projects that were authored without it. This is required so ResolveAssemblyReferences + /// runs and populates ReferencePath / ReferencePathWithRefAssemblies (needed because Fsc is invoked + /// with NoFramework=true in the F# targets). + /// + private void EnsureCommonTargetsImportedIfMissing() + { + // Opt-out escape hatch + if (Environment.GetEnvironmentVariable("FSharpSkipAutoCommonTargets") == "true") + return; + + var project = this.BuildProject; // Microsoft.Build.Evaluation.Project + var root = project.Xml; + + bool hasCommon = root.Imports.Any(i => + i.Project.IndexOf("Microsoft.Common.targets", StringComparison.OrdinalIgnoreCase) >= 0); + + if (hasCommon) + return; + + // We only auto-inject for traditional (non-SDK) style projects that already import F# targets directly + bool hasFSharpTargets = root.Imports.Any(i => + i.Project.IndexOf("Microsoft.FSharp.Targets", StringComparison.OrdinalIgnoreCase) >= 0); + + if (!hasFSharpTargets) + return; // Not the pattern we are trying to fix. + + // Build the import element + // Use the standard canonical path pattern; condition prevents errors on machines with unusual layouts. + var importText = @"$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.targets"; + var import = root.CreateImportElement(importText); + import.Condition = $"Exists('{importText}')"; + + // Insert before the F# targets import so ordering matches normal legacy project template expectations. + var fsharpImport = root.Imports.FirstOrDefault(i => + i.Project.IndexOf("Microsoft.FSharp.Targets", StringComparison.OrdinalIgnoreCase) >= 0); + + if (fsharpImport != null) + { + root.InsertBeforeChild(import, fsharpImport); + } + else + { + root.AppendChild(import); + } + + // Force reevaluation so the newly imported targets are realized before we create a ProjectInstance. + project.ReevaluateIfNecessary(); + } + + /// + /// Returns true if (after any auto-import) the evaluated project contains Microsoft.Common.targets. + /// + private bool HasCommonTargetsImported() + { + return this.BuildProject.Xml.Imports.Any(i => + i.Project.IndexOf("Microsoft.Common.targets", StringComparison.OrdinalIgnoreCase) >= 0); + } + + /// + /// Decide target list when caller asked for a single target (e.g. CoreCompile). If we now have Common + /// targets imported and CoreCompile was requested directly, prepend ResolveReferences when its + /// dependencies may otherwise be skipped (trimmed project cases). + /// + private string[] ComputeTargetsToBuild(string requestedSingleTarget) + { + if (string.IsNullOrEmpty(requestedSingleTarget)) + return Array.Empty(); + + // Only adjust for CoreCompile; Build already has the chain. + if (string.Equals(requestedSingleTarget, "CoreCompile", StringComparison.OrdinalIgnoreCase) && + HasCommonTargetsImported()) + { + // If ResolveReferences is already a dependency of CoreCompile (normal case), MSBuild will + // short-circuit duplicate execution. Supplying both explicitly is safe. + return new[] { "ResolveReferences", "CoreCompile" }; + } + + return new[] { requestedSingleTarget }; + } + /// /// Start MSBuild build submission /// @@ -3247,6 +3329,125 @@ internal virtual BuildSubmission DoMSBuildSubmission(BuildKind buildKind, string accessor = (IVsBuildManagerAccessor)this.Site.GetService(typeof(SVsBuildManagerAccessor)); this.SetHostObject("CoreCompile", "Fsc", this); + // If we are about to create a ProjectInstance, first ensure the normal MSBuild + // import chain is present so reference resolution runs. + // (No effect if the project already imports Microsoft.Common.targets.) + if (projectInstance == null) + { + EnsureCommonTargetsImportedIfMissing(); + // --- F# MSBuild reference resolution bridging BEGIN --- + + // Diagnostics toggle: set env var FSharpAutoImportDiag=1 (or project property FSharpAutoImportDiag=true) to enable verbose logging. + bool diag = + string.Equals(Environment.GetEnvironmentVariable("FSharpAutoImportDiag"), "1", StringComparison.OrdinalIgnoreCase) || + string.Equals(this.BuildProject.GetPropertyValue("FSharpAutoImportDiag"), "true", StringComparison.OrdinalIgnoreCase); + + void LogDiag(string msg) + { + if (!diag) return; + try + { + System.Diagnostics.Debug.WriteLine("[F#AutoImport] " + msg); + System.Diagnostics.Trace.WriteLine("[F#AutoImport] " + msg); + } + catch { /* ignore */ } + } + + try + { + var proj = this.BuildProject; + var root = proj.Xml; + + // Snapshot imports before any mutation + if (diag) + { + foreach (var imp in root.Imports) + LogDiag("PreImport: " + imp.Project); + } + + bool hadRARInitially = proj.Targets.ContainsKey("ResolveAssemblyReferences"); + LogDiag("Initial Has RAR Target: " + hadRARInitially); + + // If Common was injected earlier but still no RAR, attempt explicit CurrentVersion import. + if (!hadRARInitially) + { + bool hasCurrentVersionImport = root.Imports.Any(i => + i.Project.IndexOf("Microsoft.Common.CurrentVersion.targets", StringComparison.OrdinalIgnoreCase) >= 0); + + if (!hasCurrentVersionImport) + { + string currentVersionPath = "$(MSBuildToolsPath)\\Microsoft.Common.CurrentVersion.targets"; + LogDiag("Attempting CurrentVersion import: " + currentVersionPath); + + var importCV = root.CreateImportElement(currentVersionPath); + importCV.Condition = $"Exists('{currentVersionPath}')"; + var fsharpImport = root.Imports.FirstOrDefault(i => + i.Project.IndexOf("Microsoft.FSharp.Targets", StringComparison.OrdinalIgnoreCase) >= 0); + if (fsharpImport != null) + { + root.InsertBeforeChild(importCV, fsharpImport); + LogDiag("Inserted CurrentVersion import before F# targets."); + } + else + { + root.AppendChild(importCV); + LogDiag("Appended CurrentVersion import at end (no F# import found)."); + } + + proj.ReevaluateIfNecessary(); + LogDiag("Reevaluation after CurrentVersion import completed."); + hadRARInitially = proj.Targets.ContainsKey("ResolveAssemblyReferences"); + LogDiag("Post-import Has RAR Target: " + hadRARInitially); + } + else + { + LogDiag("CurrentVersion import already present; skipping injection."); + } + } + + // If caller asked only CoreCompile, prepend ResolveReferences via composite target for trimmed legacy cases. + if (target != null && + target.Equals("CoreCompile", StringComparison.OrdinalIgnoreCase) && + proj.Targets.ContainsKey("ResolveReferences")) + { + const string compositeName = "FSharpResolveReferencesThenCoreCompile"; + if (!proj.Targets.ContainsKey(compositeName)) + { + LogDiag("Creating composite target: " + compositeName); + var composite = proj.Xml.AddTarget(compositeName); + composite.DependsOnTargets = "ResolveReferences;CoreCompile"; + proj.ReevaluateIfNecessary(); + LogDiag("Reevaluation after composite target creation done."); + } + target = compositeName; + LogDiag("Adjusted requested target to composite: " + target); + } + + if (diag) + { + // After final reevaluation log imports again + foreach (var imp in proj.Xml.Imports) + LogDiag("PostImport: " + imp.Project); + + // Log whether RAR target and CoreCompile target are present + LogDiag("Final Has RAR Target: " + proj.Targets.ContainsKey("ResolveAssemblyReferences")); + LogDiag("Final Has CoreCompile Target: " + proj.Targets.ContainsKey("CoreCompile")); + + // Log Reference items (raw & counts) prior to ProjectInstance creation + var references = proj.GetItems("Reference").Select(i => i.EvaluatedInclude).ToList(); + LogDiag("Reference item count: " + references.Count); + foreach (var r in references) + LogDiag("Reference: " + r); + } + } + catch (Exception ex) + { + LogDiag("Exception during auto-import logic: " + ex); + // Swallow to avoid breaking build path; diagnostics only. + } + + // --- F# MSBuild reference resolution bridging END --- + } // Do the actual Build var loggerList = new System.Collections.Generic.List(); diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj index c084fc6f06a..b414f3fa8e1 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -106,6 +106,7 @@ + diff --git a/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs index f41f10fa138..3cb24ad4b34 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs @@ -2,57 +2,9 @@ namespace FSharp.Editor.Tests.Helpers -open System -open System.IO -open System.Reflection +open FSharp.TestHelpers module AssemblyResolver = - open System.Globalization - - 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, "..") - - 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) - - match found () with - | None -> Unchecked.defaultof - | Some name -> Assembly.Load(name)) + + /// Add VS assembly resolver using centralized discovery logic + let addResolver () = addVSAssemblyResolver() diff --git a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj index e1e657a651a..dd526f46214 100644 --- a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj +++ b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj @@ -39,6 +39,7 @@ + diff --git a/vsintegration/tests/Salsa/VsMocks.fs b/vsintegration/tests/Salsa/VsMocks.fs index 863a6b075f8..3c524a858fc 100644 --- a/vsintegration/tests/Salsa/VsMocks.fs +++ b/vsintegration/tests/Salsa/VsMocks.fs @@ -1375,6 +1375,7 @@ module internal VsMocks = let vsTargetFrameworkAssemblies40 = vsTargetFrameworkAssembliesN 0x40000u let vsTargetFrameworkAssemblies45 = vsTargetFrameworkAssembliesN 0x40005u let vsTargetFrameworkAssemblies46 = vsTargetFrameworkAssembliesN 0x40006u + let vsTargetFrameworkAssemblies472 = vsTargetFrameworkAssembliesN 0x40007u let vsFrameworkMultiTargeting = { new IVsFrameworkMultiTargeting with @@ -1601,6 +1602,11 @@ module internal VsMocks = sp.AddService(typeof, box vsTargetFrameworkAssemblies46, false) sp.AddService(typeof, box vsFrameworkMultiTargeting, false) sp, ccn + let MakeMockServiceProviderAndConfigChangeNotifier472() = + let sp, ccn = MakeMockServiceProviderAndConfigChangeNotifierNoTargetFrameworkAssembliesService() + sp.AddService(typeof, box vsTargetFrameworkAssemblies472, false) + sp.AddService(typeof, box vsFrameworkMultiTargeting, false) + sp, ccn // This is the mock thing that all tests, except the multitargeting tests call. // By default, let it use the 4.0 assembly version. @@ -1636,6 +1642,7 @@ module internal VsActual = open System.ComponentModel.Composition.Primitives open Microsoft.VisualStudio.Text open Microsoft.VisualStudio.Threading + open FSharp.TestHelpers type TestExportJoinableTaskContext () = @@ -1645,15 +1652,13 @@ module internal VsActual = member public _.JoinableTaskContext : JoinableTaskContext = jtc 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 discovery logic, but log a message if no VS is found + if HasVisualStudio then + VSRoot + else + printfn "[FSharp Tests] VsActual module: No Visual Studio installation found. Editor catalog creation will attempt to use assemblies from current directory." + // Return empty string as fallback - CreateEditorCatalog will handle missing assemblies gracefully + "" let CreateEditorCatalog() = let thisAssembly = Assembly.GetExecutingAssembly().Location @@ -1664,8 +1669,8 @@ module internal VsActual = if File.Exists(fullPath) then list.Add(new AssemblyCatalog(fullPath)) else - - failwith <| sprintf "unable to find assembly '%s' in location '%s'" p thisAssemblyDir + // Only warn instead of failing when VS assemblies are missing + printfn "[FSharp Tests] Warning: Unable to find assembly '%s' in location '%s'. Some editor functionality may be unavailable." p thisAssemblyDir list.Add(new AssemblyCatalog(thisAssembly)) [ "Microsoft.VisualStudio.Text.Data.dll" diff --git a/vsintegration/tests/Salsa/salsa.fs b/vsintegration/tests/Salsa/salsa.fs index 1730bd7ec93..425d248d316 100644 --- a/vsintegration/tests/Salsa/salsa.fs +++ b/vsintegration/tests/Salsa/salsa.fs @@ -646,11 +646,15 @@ module internal Salsa = // Append(sprintf " true") // Append(sprintf " %s" targetFrameworkVersion) // else - Append(sprintf " %s" "4.7.2") + Append(sprintf " %s" "v4.7.2") Append " " for disabledWarning in disabledWarnings do Append (sprintf " %s;" disabledWarning) Append " " + if references = ["System.Configuration",false] then + Append " true" + Append " true" + Append " " for define in defines do Append (sprintf " %s;" define) @@ -700,7 +704,13 @@ module internal Salsa = Append otherProjMisc let t = targetsFileFolder.TrimEnd([|'\\'|]) - Append (sprintf " " t) + Append " " + Append (sprintf " %s\\Microsoft.FSharp.Targets" t) + Append " 17.0" + Append " " + Append " " + Append " " + Append " " Append "" sb.ToString() diff --git a/vsintegration/tests/TestHelpers/FSharp.TestHelpers.fsproj b/vsintegration/tests/TestHelpers/FSharp.TestHelpers.fsproj new file mode 100644 index 00000000000..ab1b74fbac0 --- /dev/null +++ b/vsintegration/tests/TestHelpers/FSharp.TestHelpers.fsproj @@ -0,0 +1,19 @@ + + + + + + net472 + Library + true + + + + + + + + + + + \ No newline at end of file diff --git a/vsintegration/tests/TestHelpers/VSInstallDiscovery.fs b/vsintegration/tests/TestHelpers/VSInstallDiscovery.fs new file mode 100644 index 00000000000..0d50cec01da --- /dev/null +++ b/vsintegration/tests/TestHelpers/VSInstallDiscovery.fs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.TestHelpers + +open System +open System.IO +open System.Reflection +open System.Diagnostics +open System.Text.RegularExpressions +open System.Globalization + +[] +module VSInstallDiscovery = + + /// Try to locate Visual Studio root directory using various fallback strategies + let tryLocateVSRoot () = + // a. FSHARP_VS_INSTALL_DIR (explicit root override) + let explicitOverride = Environment.GetEnvironmentVariable("FSHARP_VS_INSTALL_DIR") + if not (String.IsNullOrEmpty explicitOverride) && Directory.Exists explicitOverride then + Some explicitOverride + else + // b. VSAPPIDDIR (derive parent directory if points to IDE folder) + let vsAppIdDir = Environment.GetEnvironmentVariable("VSAPPIDDIR") + if not (String.IsNullOrEmpty vsAppIdDir) then + let parentDir = Path.GetFullPath(Path.Combine(vsAppIdDir, "..")) + if Directory.Exists parentDir then + Some parentDir + else + None + else + // c. Highest version among any environment variables matching pattern VS*COMNTOOLS + let envVars = Environment.GetEnvironmentVariables() + let vsCommonToolsVars = + [for key in envVars.Keys -> + let keyStr = string key + if keyStr.StartsWith("VS") && keyStr.EndsWith("COMNTOOLS") then + let versionMatch = Regex.Match(keyStr, @"VS(\d+)COMNTOOLS") + if versionMatch.Success then + let version = Int32.Parse(versionMatch.Groups.[1].Value) + let path = string envVars.[key] + if not (String.IsNullOrEmpty path) then + Some (version, Path.GetFullPath(Path.Combine(path, ".."))) + else + None + else + None + else + None] + |> List.choose id + |> List.filter (fun (_, path) -> Directory.Exists path) + |> List.sortByDescending fst + + match vsCommonToolsVars with + | (_, path) :: _ -> Some path + | [] -> + // d. vswhere.exe invocation + try + // Try common locations for vswhere.exe first + let programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + let vsWherePath = Path.Combine(programFiles, "Microsoft Visual Studio", "Installer", "vswhere.exe") + + let tryVsWhere (vsWhereExePath: string) = + if File.Exists vsWhereExePath then + try + let psi = ProcessStartInfo(vsWhereExePath, "-latest -products * -requires Microsoft.Component.MSBuild -property installationPath") + psi.UseShellExecute <- false + psi.RedirectStandardOutput <- true + psi.RedirectStandardError <- true + psi.CreateNoWindow <- true + use proc = Process.Start(psi) + proc.WaitForExit(5000) |> ignore // 5 second timeout + if proc.ExitCode = 0 then + let output = proc.StandardOutput.ReadToEnd().Trim() + if not (String.IsNullOrEmpty output) && Directory.Exists output then + Some output + else + None + else + None + with + | _ -> None + else + None + + // Try explicit path first, then fall back to PATH + match tryVsWhere vsWherePath with + | Some result -> Some result + | None -> + // Try vswhere from PATH + tryVsWhere "vswhere" + with + | _ -> None + + /// Indicates whether Visual Studio installation was found + let HasVisualStudio, VSRoot = + match tryLocateVSRoot() with + | Some root -> true, root + | None -> + printfn "[FSharp Tests] No Visual Studio installation found. Tests requiring VS editor assemblies will be skipped or run with reduced functionality." + false, "" + + /// Get Visual Studio probing paths if VS installation is available + let getVSProbingPaths () = + if HasVisualStudio then + [ + Path.Combine(VSRoot, @"IDE\CommonExtensions\Microsoft\Editor") + Path.Combine(VSRoot, @"IDE\PublicAssemblies") + Path.Combine(VSRoot, @"IDE\PrivateAssemblies") + Path.Combine(VSRoot, @"IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\LanguageServices") + Path.Combine(VSRoot, @"IDE\Extensions\Microsoft\CodeSense\Framework") + Path.Combine(VSRoot, @"IDE") + ] + |> List.filter Directory.Exists + else + [] + + /// Add VS assembly resolver and return IDisposable for cleanup + let addVSAssemblyResolver () = + let probingPaths = getVSProbingPaths() + + if probingPaths.IsEmpty then + printfn "[FSharp Tests] No VS probing paths available. Assembly resolution will use default mechanisms only." + { new IDisposable with member _.Dispose() = () } + else + printfn "[FSharp Tests] Registered VS assembly resolver with %d probing paths" probingPaths.Length + + let handler = ResolveEventHandler(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 + let loadedName = AssemblyName() + loadedName.CodeBase <- codebase + loadedName.CultureInfo <- Unchecked.defaultof + loadedName.Version <- Unchecked.defaultof + Some loadedName + else + None + with + | _ -> None) + + match found() with + | None -> Unchecked.defaultof + | Some name -> Assembly.Load(name)) + + AppDomain.CurrentDomain.add_AssemblyResolve(handler) + + { new IDisposable with + member _.Dispose() = + AppDomain.CurrentDomain.remove_AssemblyResolve(handler) } \ No newline at end of file diff --git a/vsintegration/tests/UnitTests/AssemblyResolver.fs b/vsintegration/tests/UnitTests/AssemblyResolver.fs index aab95cc46fc..bb86ecfe70a 100644 --- a/vsintegration/tests/UnitTests/AssemblyResolver.fs +++ b/vsintegration/tests/UnitTests/AssemblyResolver.fs @@ -1,47 +1,8 @@ namespace Microsoft.VisualStudio.FSharp -open System -open System.IO -open System.Reflection +open FSharp.TestHelpers module AssemblyResolver = - open System.Globalization - - 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, "..") - - 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 - ) - match found() with - | None -> Unchecked.defaultof - | Some name -> Assembly.Load(name) ) + + /// Add VS assembly resolver using centralized discovery logic + let addResolver () = addVSAssemblyResolver() diff --git a/vsintegration/tests/UnitTests/LegacyProjectSystem/Tests.ProjectSystem.ProjectItems.fs b/vsintegration/tests/UnitTests/LegacyProjectSystem/Tests.ProjectSystem.ProjectItems.fs index 26c25a7edf6..706a196a6fa 100644 --- a/vsintegration/tests/UnitTests/LegacyProjectSystem/Tests.ProjectSystem.ProjectItems.fs +++ b/vsintegration/tests/UnitTests/LegacyProjectSystem/Tests.ProjectSystem.ProjectItems.fs @@ -10,6 +10,107 @@ open UnitTests.TestLib.ProjectSystem open Microsoft.VisualStudio.FSharp.ProjectSystem +module private DebuggingHelpers = + open System.Diagnostics + open Microsoft.Build.Utilities + let private envFlag () = Environment.GetEnvironmentVariable("FSHARP_DIAG_FRAMEWORK") = "1" || true + + let private fwDiagBasicsOnce = + lazy ( + if envFlag() then + let pfX86 = Environment.GetEnvironmentVariable "ProgramFiles(x86)" + let pf = Environment.GetEnvironmentVariable "ProgramFiles" + let dirs = + [ if not (String.IsNullOrWhiteSpace pfX86) then + Path.Combine(pfX86,"Reference Assemblies","Microsoft","Framework",".NETFramework","v4.7.2") + if not (String.IsNullOrWhiteSpace pf) then + Path.Combine(pf,"Reference Assemblies","Microsoft","Framework",".NETFramework","v4.7.2") ] + for d in dirs do + let exists = Directory.Exists d + printfn "FWDiag: RefDir='%s' Exists=%b" d exists + if exists then + try + let dlls = Directory.EnumerateFiles(d, "*.dll") |> Seq.length + printfn "FWDiag: DLLCount=%d" dlls + with e -> + printfn "FWDiag: DLLCount=ERR(%s)" e.Message + let sn = Path.Combine(d,"System.Numerics.dll") + if File.Exists sn then + let fi = FileInfo(sn) + let ver = FileVersionInfo.GetVersionInfo(sn).FileVersion + printfn "FWDiag: System.Numerics.dll Size=%d Version=%s" fi.Length ver + else + printfn "FWDiag: System.Numerics.dll MISSING" + // ToolLocationHelper probe + try + let p = ToolLocationHelper.GetPathToDotNetFrameworkFile("System.Numerics.dll", TargetDotNetFrameworkVersion.Version472, DotNetFrameworkArchitecture.Current) + printfn "FWDiag: ToolLocationHelper(System.Numerics)='%s'" (if String.IsNullOrWhiteSpace p then "" else p) + with e -> + printfn "FWDiag: ToolLocationHelper exception: %s" e.Message + printfn "FWDiag: Is64BitProcess=%b PROC_ARCH=%s" Environment.Is64BitProcess (Environment.GetEnvironmentVariable "PROCESSOR_ARCHITECTURE") + for v in [ "VisualStudioVersion"; "FrameworkPathOverride"; "TargetFrameworkRootPath" ] do + printfn "FWDiag: Env %s='%s'" v (Environment.GetEnvironmentVariable v) + ) + + let fwDiagProject (project: UnitTestingFSharpProjectNode) = + if envFlag() then + fwDiagBasicsOnce.Force() |> ignore + try + // BuildProject is Microsoft.Build.Evaluation.Project + let p = project.BuildProject + let get name = p.GetPropertyValue name + let tfd = get "TargetFrameworkDirectories" + let vs = get "VisualStudioVersion" + let tv = get "TargetFrameworkVersion" + let ti = get "TargetFrameworkIdentifier" + let tp = get "TargetFrameworkProfile" + let mtp = get "MSBuildToolsPath" + let mrt = get "MSBuildRuntimeType" + let mrv = get "MSBuildRuntimeVersion" + printfn "FWDiag: ProjectProps TFV='%s' TFI='%s' TFP='%s' VS='%s'" tv ti tp vs + printfn "FWDiag: Project MSBuildToolsPath='%s' RuntimeType='%s' RuntimeVersion='%s'" mtp mrt mrv + printfn "FWDiag: Project TargetFrameworkDirectories='%s'" (if String.IsNullOrWhiteSpace tfd then "" else tfd) + with e -> + printfn "FWDiag: Unable to read project properties (%s)" e.Message + + let dumpOnFailure (project: UnitTestingFSharpProjectNode) = + // Called only when an assertion about System.Numerics is about to fail + let opts = + try project.CompilationOptions with _ -> [||] + printfn "FWDiag-FAIL: CompilationOptionsCount=%d" opts.Length + opts |> Array.iter (fun o -> printfn "FWDiag-FAIL: Opt=%s" o) + try + let refContainer = project.GetReferenceContainer() + let refs = refContainer.EnumReferences() |> Seq.toArray + printfn "FWDiag-FAIL: ReferencesCount=%d" refs.Length + for r in refs do + let name = r.SimpleName + // Not all mock reference objects expose path; guard + let path = + try + let rp = r.GetType().GetProperty("FullPath") + if rp = null then "" + else + match rp.GetValue(r) with + | :? string as s when not (String.IsNullOrWhiteSpace s) -> s + | _ -> "" + with _ -> "" + printfn "FWDiag-FAIL: Ref Name=%s Path=%s" name path + if name.Equals("System.Numerics", StringComparison.OrdinalIgnoreCase) then + // Try pull 'ResolvedPath' metadata if available + let rpMeta = + try + let mp = r.GetType().GetProperty("ResolvedPath") + if mp = null then "" + else + match mp.GetValue(r) with + | :? string as s when s <> "" -> s + | _ -> "" + with _ -> "" + printfn "FWDiag-FAIL: System.Numerics.ResolvedPath=%s" rpMeta + with e -> + printfn "FWDiag-FAIL: Error dumping references (%s)" e.Message + type ProjectItems() = inherit TheTests() @@ -18,29 +119,39 @@ type ProjectItems() = [] member public this.``RemoveAssemblyReference.NoIVsTrackProjectDocuments2Events``() = - this.MakeProjectAndDo(["file.fs"], ["System.Numerics"],"", (fun project -> + let systemAssName = "System.Configuration" + + this.MakeProjectAndDo(["file.fs"], [systemAssName],"", (fun project -> let listener = project.Site.GetService(typeof) :?> Salsa.VsMocks.IVsTrackProjectDocuments2Listener project.ComputeSourcesAndFlags() - let containsSystemNumerics () = + let containsSystemNumerics () = project.CompilationOptions - |> Array.exists (fun f -> f.IndexOf("System.Numerics") <> -1) + |> Array.exists (fun f -> f.IndexOf(systemAssName, StringComparison.OrdinalIgnoreCase) >= 0) - let mutable wasCalled = false - Assert.True(containsSystemNumerics (), "Project should contains reference to System.Numerics") + DebuggingHelpers.fwDiagProject project + + if not (containsSystemNumerics()) then + DebuggingHelpers.dumpOnFailure project + Assert.True(false, $"Project should contain reference to {systemAssName} (pre-remove)") let refContainer = project.GetReferenceContainer() - let reference = - refContainer.EnumReferences() - |> Seq.find(fun r -> r.SimpleName = "System.Numerics") + let reference = + refContainer.EnumReferences() + |> Seq.find(fun r -> r.SimpleName = systemAssName) + + let mutable wasCalled = false ( use _guard = listener.OnAfterRemoveFiles.Subscribe(fun _ -> wasCalled <- true) reference.Remove(false) ) + if containsSystemNumerics() then + DebuggingHelpers.dumpOnFailure project + Assert.True(false, $"Project should not contain reference to {systemAssName} (post-remove)") + Assert.False(wasCalled, "No events from IVsTrackProjectDocuments2 are expected") - Assert.False(containsSystemNumerics(), "Project should not contains reference to System.Numerics") - )) + )) [] member public this.``AddNewItem.ItemAppearsAtBottomOfFsprojFile``() = diff --git a/vsintegration/tests/UnitTests/TestLib.ProjectSystem.fs b/vsintegration/tests/UnitTests/TestLib.ProjectSystem.fs index 5f9095ff4d9..1cd698ebdc2 100644 --- a/vsintegration/tests/UnitTests/TestLib.ProjectSystem.fs +++ b/vsintegration/tests/UnitTests/TestLib.ProjectSystem.fs @@ -114,18 +114,50 @@ type TheTests() = try project.SetSite(serviceProvider) |> ignore project.BuildProject <- buildProject - let _ = project.BaseURI // This property needs to be touched at least once while the .BuildProject is populated + let _ = project.BaseURI let mutable cancelled = 0 let mutable guid = Guid.NewGuid() printfn "about to load .fsproj" project.Load(filename, null, null, 2u, &guid, &cancelled) printfn "loaded" + + // FWDIAG START (add below) + let bp = project.BuildProject + let prop n = try bp.GetPropertyValue(n) with _ -> "" + let dumpProp n = printfn "FWDIAG PROP %s=%s" n (prop n) + [ "VisualStudioVersion"; "MSBuildToolsVersion"; "MSBuildRuntimeType"; "TargetFrameworkIdentifier" + "TargetFrameworkVersion"; "TargetFrameworkMoniker"; "TargetFrameworkDirectories" + "ImplicitlyReferenceDotNetAssemblies"; "ImplicitlyResolveAssemblies"; "NoStdLib"; "NoFramework" + "FrameworkPathOverride"; "FSharpTargetsPath"; "FSharpBuildAssemblyFile" + "MSBuildExtensionsPath"; "MSBuildExtensionsPath32" ] + |> List.iter dumpProp + + // Raw items + for it in bp.GetItems("Reference") do + let fullPath = it.GetMetadataValue("FullPath") + let hintPath = it.GetMetadataValue("HintPath") + printfn "FWDIAG REF Include=%s FullPath=%s HintPath=%s" it.EvaluatedInclude (if System.String.IsNullOrWhiteSpace fullPath then "" else fullPath) (if System.String.IsNullOrWhiteSpace hintPath then "" else hintPath) + + // Items that normally come from RAR + for it in bp.GetItems("ReferencePath") do + printfn "FWDIAG REFPATH %s -> %s" it.EvaluatedInclude (it.GetMetadataValue("FullPath")) + + // Show evaluated Imports (paths) + for i in bp.Imports do + printfn "FWDIAG IMPORT %s" i.ImportedProject.FullPath + + // Show global properties relevant to resolution + for KeyValue(k,v) in bp.GlobalProperties do + if k = "VisualStudioVersion" || k.StartsWith("TargetFramework", System.StringComparison.OrdinalIgnoreCase) then + printfn "FWDIAG GLOBAL %s=%s" k v + // FWDIAG END + let slfpe = new SolutionListenerForProjectEvents(project.Site) project.ProjectEventsProvider <- (slfpe :> IProjectEvents) slfpe.OnAfterOpenProject((project :> IVsHierarchy), 0) |> ignore MSBuildProject.SetGlobalProperty(project.BuildProject, "UTF8Output", forceUTF8) project - with + with | e -> try project.Close() |> ignore @@ -298,13 +330,14 @@ type TheTests() = File.AppendAllText(file, TheTests.FsprojTextWithProjectReferencesAndOtherFlags(compileItems, references, [], null, other, targetFramework)) let sp, cnn = match targetFramework with - | "v4.6" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier46() - | "v4.5" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier45() - | "v4.0" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier40() - | "v3.5" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier35() - | "v3.0" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier30() - | "v2.0" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier20() - | null -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier40() + | "v4.7.2" | "4.7.2" | "4.7" | "v4.7" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier472() + | "v4.6" | "4.6" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier46() + | "v4.5" | "4.5" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier45() + | "v4.0" | "4.0" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier40() + | "v3.5" | "3.5" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier35() + | "v3.0" | "3.0" -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier30() + | "v2.0" | "2.0"-> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier20() + | null -> VsMocks.MakeMockServiceProviderAndConfigChangeNotifier472() | _ -> failwithf "unexpected targetFramework %s" targetFramework let project = TheTests.CreateProject(file, "false", cnn, sp) try diff --git a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj index 96fde123dcc..00914991914 100644 --- a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj +++ b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj @@ -83,6 +83,7 @@ +