NativeDllResolveHandler.fs 7.0 KB
Newer Older
1 2
// Copyright (c) Microsoft Corporation.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

3
namespace FSharp.Compiler.DependencyManager
4 5

open System
6
open System.Collections.Concurrent
7 8 9
open System.IO
open System.Reflection
open System.Runtime.InteropServices
10
open Internal.Utilities
11
open Internal.Utilities.FSharpEnvironment
12
open FSharp.Compiler.IO
13 14 15

/// Signature for Native library resolution probe callback
/// host implements this, it's job is to return a list of package roots to probe.
16
type NativeResolutionProbe = delegate of Unit -> seq<string>
17

18
/// Type that encapsulates Native library probing for managed packages
19
type NativeDllResolveHandlerCoreClr (nativeProbingRoots: NativeResolutionProbe option) =
20

21 22 23
    let nativeLibraryTryLoad =
        let nativeLibraryType: Type = Type.GetType("System.Runtime.InteropServices.NativeLibrary, System.Runtime.InteropServices", false)
        nativeLibraryType.GetMethod("TryLoad", [| typeof<string>; typeof<IntPtr>.MakeByRefType() |])
24

25 26 27
    let loadNativeLibrary path =
        let arguments = [| path:>obj; IntPtr.Zero:>obj |]
        if nativeLibraryTryLoad.Invoke(null, arguments) :?> bool then
28
            arguments[1] :?> IntPtr
29 30
        else
            IntPtr.Zero
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

    let probingFileNames (name: string) =
        // coreclr native library probing algorithm: https://github.com/dotnet/coreclr/blob/9773db1e7b1acb3ec75c9cc0e36bd62dcbacd6d5/src/System.Private.CoreLib/shared/System/Runtime/Loader/LibraryNameVariation.Unix.cs
        let isRooted = Path.IsPathRooted name
        let useSuffix s = not (name.Contains(s + ".") || name.EndsWith(s))          // linux devs often append version # to libraries I.e mydll.so.5.3.2
        let usePrefix = name.IndexOf(Path.DirectorySeparatorChar) = -1              // If name has directory information no add no prefix
                        && name.IndexOf(Path.AltDirectorySeparatorChar) = -1
                        && name.IndexOf(Path.PathSeparator) = -1
                        && name.IndexOf(Path.VolumeSeparatorChar) = -1
        let prefix = [| "lib" |]
        let suffix = [|
                if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
                    ".dll"
                    ".exe"
                elif RuntimeInformation.IsOSPlatform(OSPlatform.OSX) then
                    ".dylib"
                else
                    ".so"
            |]

        [|
            yield name                                                                              // Bare name
53
            if not isRooted then
54 55 56 57 58 59 60 61 62 63 64
                for s in suffix do
                    if useSuffix s then                                                             // Suffix without prefix
                        yield (sprintf "%s%s" name s)
                        if usePrefix then
                            for p in prefix do                                                      // Suffix with prefix
                                yield (sprintf "%s%s%s" p name s)
                    elif usePrefix then
                        for p in prefix do                                                          // Prefix
                            yield (sprintf "%s%s" p name)
        |]

65
    let resolveUnmanagedDll (_: Assembly) (name: string): IntPtr =
66 67 68 69 70
        // 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
            probingFileNames name |> Array.tryPick(fun name ->
                let path = Path.Combine(root, "runtimes", rid, "native", name)
71
                if FileSystem.FileExistsShim(path) then
72 73 74 75 76
                    Some path
                else
                    None)

        let probe =
77
            match nativeProbingRoots with
78 79
            | None -> None
            | Some nativeProbingRoots ->  
80
                nativeProbingRoots.Invoke()
81
                |> Seq.tryPick(fun root ->
82 83
                    probingFileNames name |> Seq.tryPick(fun name ->
                        let path = Path.Combine(root, name)
84
                        if FileSystem.FileExistsShim(path) then
85 86
                            Some path
                        else
87
                            RidHelpers.probingRids |> Seq.tryPick(fun rid -> probeForNativeLibrary root rid name)))
88 89

        match probe with
90
        | Some path -> loadNativeLibrary(path)
91 92 93 94
        | None -> IntPtr.Zero

    // netstandard 2.1 has this property, unfortunately we don't build with that yet
    //public event Func<Assembly, string, IntPtr> ResolvingUnmanagedDll
95 96
    let assemblyLoadContextType: Type = Type.GetType("System.Runtime.Loader.AssemblyLoadContext, System.Runtime.Loader", false)
    let eventInfo, handler, defaultAssemblyLoadContext =
97
        assemblyLoadContextType.GetEvent("ResolvingUnmanagedDll"),
98
        Func<Assembly, string, IntPtr> resolveUnmanagedDll,
99
        assemblyLoadContextType.GetProperty("Default", BindingFlags.Static ||| BindingFlags.Public).GetValue(null, null)
100

101
    do eventInfo.AddEventHandler(defaultAssemblyLoadContext, handler)
102 103

    interface IDisposable with
104 105
        member _x.Dispose() = eventInfo.RemoveEventHandler(defaultAssemblyLoadContext, handler)

106

107 108
type NativeDllResolveHandler (nativeProbingRoots: NativeResolutionProbe option) =

109
    let handler: IDisposable option =
110
        if isRunningOnCoreClr then
111
            Some (new NativeDllResolveHandlerCoreClr(nativeProbingRoots) :> IDisposable)
112 113 114
        else
            None

115
    let appendPathSeparator (p: string) =
116
        let separator = string Path.PathSeparator
117 118
        if not(p.EndsWith(separator, StringComparison.OrdinalIgnoreCase)) then
            p + separator
119 120 121 122 123 124
        else
            p

    let addedPaths = ConcurrentBag<string>()

    let addProbeToProcessPath probePath =
125 126
        let probe = appendPathSeparator probePath
        let path = appendPathSeparator (Environment.GetEnvironmentVariable("PATH"))
127 128 129 130 131 132
        if not (path.Contains(probe)) then
            Environment.SetEnvironmentVariable("PATH", path + probe)
            addedPaths.Add probe

    let removeProbeFromProcessPath probePath =
        if not(String.IsNullOrWhiteSpace(probePath)) then
133 134
            let probe = appendPathSeparator probePath
            let path = appendPathSeparator (Environment.GetEnvironmentVariable("PATH"))
135 136
            if path.Contains(probe) then Environment.SetEnvironmentVariable("PATH", path.Replace(probe, ""))

137 138
    new (nativeProbingRoots: NativeResolutionProbe) = new NativeDllResolveHandler(Option.ofObj nativeProbingRoots)

139 140 141 142
    member internal _.RefreshPathsInEnvironment(roots: string seq) =
        for probePath in roots do
            addProbeToProcessPath probePath

143 144 145 146 147
    interface IDisposable with
        member _.Dispose() =
            match handler with
            | None -> ()
            | Some handler -> handler.Dispose()
148

149
            let mutable probe:string = Unchecked.defaultof<string>
150 151
            while (addedPaths.TryTake(&probe)) do
                removeProbeFromProcessPath probe