diff --git a/setup/Swix/Microsoft.FSharp.Compiler.MSBuild/Microsoft.FSharp.Compiler.MSBuild.csproj b/setup/Swix/Microsoft.FSharp.Compiler.MSBuild/Microsoft.FSharp.Compiler.MSBuild.csproj index 9b03c15e580..ebccef01efe 100644 --- a/setup/Swix/Microsoft.FSharp.Compiler.MSBuild/Microsoft.FSharp.Compiler.MSBuild.csproj +++ b/setup/Swix/Microsoft.FSharp.Compiler.MSBuild/Microsoft.FSharp.Compiler.MSBuild.csproj @@ -101,7 +101,7 @@ folder "InstallDir:Common7\IDE\CommonExtensions\Microsoft\FSharp" file source="$(BinariesFolder)\FSharp.Core\$(Configuration)\netstandard2.0\FSharp.Core.dll" vs.file.ngen=yes vs.file.ngenArchitecture=All vs.file.ngenPriority=2 file source="$(BinariesFolder)\FSharp.Core\$(Configuration)\netstandard2.0\FSharp.Core.xml" file source="$(BinariesFolder)\FSharp.Build\$(Configuration)\$(TargetFramework)\FSharp.Build.dll" vs.file.ngen=yes vs.file.ngenArchitecture=All vs.file.ngenPriority=2 - file source="$(BinariesFolder)\Microsoft.DotNet.DependencyManager\$(Configuration)\net472\Microsoft.DotNet.DependencyManager.dll" vs.file.ngen=yes vs.file.ngenArchitecture=All vs.file.ngenPriority=2 + file source="$(BinariesFolder)\Microsoft.DotNet.DependencyManager\$(Configuration)\netstandard2.0\Microsoft.DotNet.DependencyManager.dll" vs.file.ngen=yes vs.file.ngenArchitecture=All vs.file.ngenPriority=2 file source="$(BinariesFolder)\FSharp.Build\$(Configuration)\$(TargetFramework)\Microsoft.Build.Framework.dll" file source="$(BinariesFolder)\FSharp.Build\$(Configuration)\$(TargetFramework)\Microsoft.Build.Tasks.Core.dll" file source="$(BinariesFolder)\FSharp.Build\$(Configuration)\$(TargetFramework)\Microsoft.Build.Utilities.Core.dll" diff --git a/src/fsharp/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs b/src/fsharp/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs index 251217e7c90..b49bec94db9 100644 --- a/src/fsharp/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs +++ b/src/fsharp/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs @@ -74,9 +74,13 @@ module internal ProjectFile = resolutions |> Array.filter(fun r -> not(String.IsNullOrEmpty(r.NugetPackageId) || - String.IsNullOrEmpty(r.NativePath)) && - Directory.Exists(r.NativePath)) - |> Array.map(fun r -> r.NativePath) + String.IsNullOrEmpty(r.NativePath))) + |> Array.map(fun r -> + if Directory.Exists(r.NativePath) then Some (r.NativePath) + elif File.Exists(r.NativePath) then Some (Path.GetDirectoryName(r.NativePath).Replace('\\', '/')) + else None) + |> Array.filter(fun r -> r.IsSome) + |> Array.map(fun r -> r.Value) Array.concat [|managedRoots; nativeRoots|] |> Array.distinct diff --git a/src/fsharp/Microsoft.DotNet.DependencyManager/DependencyProvider.fs b/src/fsharp/Microsoft.DotNet.DependencyManager/DependencyProvider.fs index 780df6b7799..f27681d4135 100644 --- a/src/fsharp/Microsoft.DotNet.DependencyManager/DependencyProvider.fs +++ b/src/fsharp/Microsoft.DotNet.DependencyManager/DependencyProvider.fs @@ -124,7 +124,6 @@ type ReflectionDependencyManagerProvider(theType: Type, resolveDeps: MethodInfo option, resolveDepsEx: MethodInfo option, outputDir: string option) = - let instance = Activator.CreateInstance(theType, [| outputDir :> obj |]) let nameProperty = nameProperty.GetValue >> string let keyProperty = keyProperty.GetValue >> string @@ -226,7 +225,6 @@ type ReflectionDependencyManagerProvider(theType: Type, /// Resolve the dependencies for the given arguments member this.ResolveDependencies(scriptDir, mainScriptName, scriptName, scriptExt, packageManagerTextLines, tfm, rid): IResolveDependenciesResult = - // The ResolveDependencies method, has two signatures, the original signaature in the variable resolveDeps and the updated signature resolveDepsEx // the resolve method can return values in two different tuples: // (bool * string list * string list * string list) @@ -275,10 +273,7 @@ type ReflectionDependencyManagerProvider(theType: Type, type DependencyProvider (assemblyProbingPaths: AssemblyResolutionProbe, nativeProbingRoots: NativeResolutionProbe) = // Note: creating a NativeDllResolveHandler currently installs process-wide handlers - let dllResolveHandler = - match nativeProbingRoots with - | null -> { new IDisposable with member _.Dispose() = () } - | _ -> new NativeDllResolveHandler(nativeProbingRoots) :> IDisposable + let dllResolveHandler = new NativeDllResolveHandler(nativeProbingRoots) // Note: creating a AssemblyResolveHandler currently installs process-wide handlers let assemblyResolveHandler = @@ -384,7 +379,6 @@ type DependencyProvider (assemblyProbingPaths: AssemblyResolutionProbe, nativePr /// Fetch a dependencymanager that supports a specific key member _.TryFindDependencyManagerByKey (compilerTools: string seq, outputDir: string, reportError: ResolvingErrorReport, key: string): IDependencyManagerProvider = - try RegisteredDependencyManagers compilerTools (Option.ofString outputDir) reportError |> Map.tryFind key @@ -407,7 +401,7 @@ type DependencyProvider (assemblyProbingPaths: AssemblyResolutionProbe, nativePr []implicitIncludeDir: string, []mainScriptName: string, []fileName: string): IResolveDependenciesResult = - + let key = (packageManager.Key, scriptExt, Seq.toArray packageManagerTextLines, executionTfm, executionRid, implicitIncludeDir, mainScriptName, fileName) let result = @@ -424,8 +418,10 @@ type DependencyProvider (assemblyProbingPaths: AssemblyResolutionProbe, nativePr let e = stripTieWrapper e Error (DependencyManager.SR.packageManagerError(e.Message)) )) - match result with - | Ok res -> res + match result with + | Ok res -> + dllResolveHandler.RefreshPathsInEnvironment(res.Roots) + res | Error (errorNumber, errorData) -> reportError.Invoke(ErrorReportType.Error, errorNumber, errorData) ReflectionDependencyManagerProvider.MakeResultFromFields(false, arrEmpty, arrEmpty, seqEmpty, seqEmpty, seqEmpty) @@ -436,5 +432,5 @@ type DependencyProvider (assemblyProbingPaths: AssemblyResolutionProbe, nativePr // Unregister everything registeredDependencyManagers <- None - dllResolveHandler.Dispose() + (dllResolveHandler :> IDisposable).Dispose() assemblyResolveHandler.Dispose() diff --git a/src/fsharp/Microsoft.DotNet.DependencyManager/Microsoft.DotNet.DependencyManager.fsproj b/src/fsharp/Microsoft.DotNet.DependencyManager/Microsoft.DotNet.DependencyManager.fsproj index f77c694ff6f..6e700032fff 100644 --- a/src/fsharp/Microsoft.DotNet.DependencyManager/Microsoft.DotNet.DependencyManager.fsproj +++ b/src/fsharp/Microsoft.DotNet.DependencyManager/Microsoft.DotNet.DependencyManager.fsproj @@ -4,9 +4,8 @@ Library - netstandard2.0;net472 + netstandard2.0 true - netstandard2.0 Microsoft.DotNet.DependencyManager $(NoWarn);45;55;62;75;1204 true diff --git a/src/fsharp/Microsoft.DotNet.DependencyManager/NativeDllResolveHandler.fs b/src/fsharp/Microsoft.DotNet.DependencyManager/NativeDllResolveHandler.fs index 672172655eb..8aab5e77c83 100644 --- a/src/fsharp/Microsoft.DotNet.DependencyManager/NativeDllResolveHandler.fs +++ b/src/fsharp/Microsoft.DotNet.DependencyManager/NativeDllResolveHandler.fs @@ -3,6 +3,7 @@ namespace Microsoft.DotNet.DependencyManager open System +open System.Collections.Concurrent open System.IO open System.Reflection open System.Runtime.InteropServices @@ -13,7 +14,6 @@ open Internal.Utilities.FSharpEnvironment /// host implements this, it's job is to return a list of package roots to probe. type NativeResolutionProbe = delegate of Unit -> seq -#if NETSTANDARD open System.Runtime.Loader // Cut down AssemblyLoadContext, for loading native libraries @@ -28,7 +28,6 @@ type NativeAssemblyLoadContext () = static member NativeLoadContext = new NativeAssemblyLoadContext() - /// Type that encapsulates Native library probing for managed packages type NativeDllResolveHandlerCoreClr (nativeProbingRoots: NativeResolutionProbe) = let probingFileNames (name: string) = @@ -65,7 +64,6 @@ type NativeDllResolveHandlerCoreClr (nativeProbingRoots: NativeResolutionProbe) |] let _resolveUnmanagedDll (_: Assembly) (name: string): IntPtr = - // Enumerate probing roots looking for a dll that matches the probing name in the probed locations let probeForNativeLibrary root rid name = // Look for name in root @@ -79,7 +77,7 @@ type NativeDllResolveHandlerCoreClr (nativeProbingRoots: NativeResolutionProbe) let probe = match nativeProbingRoots with | null -> None - | _ -> + | _ -> nativeProbingRoots.Invoke() |> Seq.tryPick(fun root -> probingFileNames name |> Seq.tryPick(fun name -> @@ -98,7 +96,9 @@ type NativeDllResolveHandlerCoreClr (nativeProbingRoots: NativeResolutionProbe) let eventInfo = typeof.GetEvent("ResolvingUnmanagedDll") let handler = Func (_resolveUnmanagedDll) - do if not (isNull eventInfo) then eventInfo.AddEventHandler(AssemblyLoadContext.Default, handler) + do + if not (isNull eventInfo) then + eventInfo.AddEventHandler(AssemblyLoadContext.Default, handler) interface IDisposable with member _x.Dispose() = @@ -106,20 +106,44 @@ type NativeDllResolveHandlerCoreClr (nativeProbingRoots: NativeResolutionProbe) eventInfo.RemoveEventHandler(AssemblyLoadContext.Default, handler) () -#endif - -type NativeDllResolveHandler (_nativeProbingRoots: NativeResolutionProbe) = - +type NativeDllResolveHandler (nativeProbingRoots: NativeResolutionProbe) = let handler:IDisposable option = -#if NETSTANDARD if isRunningOnCoreClr then - Some (new NativeDllResolveHandlerCoreClr(_nativeProbingRoots) :> IDisposable) + Some (new NativeDllResolveHandlerCoreClr(nativeProbingRoots) :> IDisposable) else -#endif None + let appendSemiColon (p:string) = + if not(p.EndsWith(";", StringComparison.OrdinalIgnoreCase)) then + p + ";" + else + p + + let addedPaths = ConcurrentBag() + + let addProbeToProcessPath probePath = + let probe = appendSemiColon probePath + let path = appendSemiColon (Environment.GetEnvironmentVariable("PATH")) + if not (path.Contains(probe)) then + Environment.SetEnvironmentVariable("PATH", path + probe) + addedPaths.Add probe + + let removeProbeFromProcessPath probePath = + if not(String.IsNullOrWhiteSpace(probePath)) then + let probe = appendSemiColon probePath + let path = appendSemiColon (Environment.GetEnvironmentVariable("PATH")) + if path.Contains(probe) then Environment.SetEnvironmentVariable("PATH", path.Replace(probe, "")) + + member internal _.RefreshPathsInEnvironment(roots: string seq) = + for probePath in roots do + addProbeToProcessPath probePath + interface IDisposable with member _.Dispose() = match handler with | None -> () | Some handler -> handler.Dispose() + + let mutable probe:string = null + while (addedPaths.TryTake(&probe)) do + removeProbeFromProcessPath probe diff --git a/src/fsharp/Microsoft.DotNet.DependencyManager/NativeDllResolveHandler.fsi b/src/fsharp/Microsoft.DotNet.DependencyManager/NativeDllResolveHandler.fsi index 6e2545e2a07..05f5567cb36 100644 --- a/src/fsharp/Microsoft.DotNet.DependencyManager/NativeDllResolveHandler.fsi +++ b/src/fsharp/Microsoft.DotNet.DependencyManager/NativeDllResolveHandler.fsi @@ -12,6 +12,8 @@ type NativeResolutionProbe = delegate of Unit -> seq type NativeDllResolveHandler = /// Construct a new NativeDllResolveHandler - new: _nativeProbingRoots: NativeResolutionProbe -> NativeDllResolveHandler + new: nativeProbingRoots: NativeResolutionProbe -> NativeDllResolveHandler + + member internal RefreshPathsInEnvironment: string seq -> unit interface IDisposable diff --git a/tests/FSharp.Compiler.Private.Scripting.UnitTests/DependencyManagerInteractiveTests.fs b/tests/FSharp.Compiler.Private.Scripting.UnitTests/DependencyManagerInteractiveTests.fs index 7e4e2780db9..192b12d0411 100644 --- a/tests/FSharp.Compiler.Private.Scripting.UnitTests/DependencyManagerInteractiveTests.fs +++ b/tests/FSharp.Compiler.Private.Scripting.UnitTests/DependencyManagerInteractiveTests.fs @@ -657,6 +657,55 @@ x |> Seq.iter(fun r -> try Assembly.Load("NoneSuchAssembly") |> ignore with _ -> () Assert.False (assemblyFound, "Invoke the assemblyProbingRoots callback -- Error the AssemblyResolve still fired ") + [] + member __.``Verify that Dispose cleans up the native paths added``() = + let nativeProbingRoots () = Seq.empty + + let appendSemiColon (p:string) = + if not(p.EndsWith(";", StringComparison.OrdinalIgnoreCase)) then + p + ";" + else + p + + let reportError = + let report errorType code message = + match errorType with + | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message + | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message + ResolvingErrorReport (report) + + let mutable initialPath:string = null + let mutable currentPath:string = null + let mutable finalPath:string = null + do + initialPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH")) + use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots)) + let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", reportError, "nuget") + let mutable currentPath:string = null + if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then + let result = dp.Resolve(idm, ".fsx", [|"r", "Microsoft.Data.Sqlite,3.1.7"|], reportError, "netstandard2.0") + Assert.Equal(true, result.Success) + currentPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH")) + finalPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH")) + Assert.True(currentPath <> initialPath) // The path was modified by #r "nuget: ..." + Assert.Equal(finalPath, initialPath) // IDispose correctly cleaned up the path + + initialPath <- null + currentPath <- null + finalPath <- null + do + initialPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH")) + let mutable currentPath:string = null + use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots)) + let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", reportError, "nuget") + let result = dp.Resolve(idm, ".fsx", [|"r", "Microsoft.Data.Sqlite,3.1.7"|], reportError, "netcoreapp3.1") + Assert.Equal(true, result.Success) + currentPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH")) + finalPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH")) + Assert.True(currentPath <> initialPath) // The path was modified by #r "nuget: ..." + Assert.Equal(finalPath, initialPath) // IDispose correctly cleaned up the path + + () [] member __.``Verify that #help produces help text for fsi + dependency manager``() = diff --git a/vsintegration/Vsix/VisualFSharpFull/VisualFSharpFull.csproj b/vsintegration/Vsix/VisualFSharpFull/VisualFSharpFull.csproj index d9c37232e40..4e602393aeb 100644 --- a/vsintegration/Vsix/VisualFSharpFull/VisualFSharpFull.csproj +++ b/vsintegration/Vsix/VisualFSharpFull/VisualFSharpFull.csproj @@ -95,7 +95,7 @@ All 2 True - TargetFramework=net472 + TargetFramework=netstandard2.0 {DED3BBD7-53F4-428A-8C9F-27968E768605}