| | | 1 | | // Copyright DotNet API Diff Project Contributors - SPDX Identifier: MIT |
| | | 2 | | using System.Reflection; |
| | | 3 | | using System.Runtime.Loader; |
| | | 4 | | using Microsoft.Extensions.Logging; |
| | | 5 | | |
| | | 6 | | namespace DotNetApiDiff.AssemblyLoading; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// Custom assembly load context that provides isolation for loaded assemblies |
| | | 10 | | /// </summary> |
| | | 11 | | public class IsolatedAssemblyLoadContext : AssemblyLoadContext |
| | | 12 | | { |
| | | 13 | | private readonly string _assemblyDirectory; |
| | | 14 | | private readonly AssemblyDependencyResolver _resolver; |
| | | 15 | | private readonly string _mainAssemblyPath; |
| | | 16 | | private readonly ILogger? _logger; |
| | | 17 | | |
| | | 18 | | /// <summary> |
| | | 19 | | /// Creates a new isolated assembly load context |
| | | 20 | | /// </summary> |
| | | 21 | | /// <param name="assemblyPath">Path to the main assembly</param> |
| | | 22 | | public IsolatedAssemblyLoadContext(string assemblyPath) |
| | 37 | 23 | | : base(isCollectible: true) |
| | 37 | 24 | | { |
| | 37 | 25 | | _mainAssemblyPath = assemblyPath; |
| | 37 | 26 | | _assemblyDirectory = Path.GetDirectoryName(assemblyPath) ?? string.Empty; |
| | 37 | 27 | | _resolver = new AssemblyDependencyResolver(assemblyPath); |
| | 37 | 28 | | _logger = null; |
| | 37 | 29 | | } |
| | | 30 | | |
| | | 31 | | /// <summary> |
| | | 32 | | /// Creates a new isolated assembly load context |
| | | 33 | | /// </summary> |
| | | 34 | | /// <param name="assemblyPath">Path to the main assembly</param> |
| | | 35 | | /// <param name="logger">Logger for diagnostic information</param> |
| | | 36 | | public IsolatedAssemblyLoadContext(string assemblyPath, ILogger logger) |
| | 27 | 37 | | : this(assemblyPath) |
| | 27 | 38 | | { |
| | 27 | 39 | | _logger = logger; |
| | 27 | 40 | | } |
| | | 41 | | |
| | | 42 | | /// <summary> |
| | | 43 | | /// Additional search paths for assemblies |
| | | 44 | | /// </summary> |
| | 358 | 45 | | public List<string> AdditionalSearchPaths { get; } = new List<string>(); |
| | | 46 | | |
| | | 47 | | /// <summary> |
| | | 48 | | /// Adds an additional search path for assemblies |
| | | 49 | | /// </summary> |
| | | 50 | | /// <param name="path">Path to search for assemblies</param> |
| | | 51 | | public void AddSearchPath(string path) |
| | 165 | 52 | | { |
| | 165 | 53 | | if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !AdditionalSearchPaths.Contains(path)) |
| | 156 | 54 | | { |
| | 156 | 55 | | AdditionalSearchPaths.Add(path); |
| | 156 | 56 | | _logger?.LogDebug("Added search path: {Path}", path); |
| | 156 | 57 | | } |
| | 165 | 58 | | } |
| | | 59 | | |
| | | 60 | | /// <summary> |
| | | 61 | | /// Loads an assembly with the given name |
| | | 62 | | /// </summary> |
| | | 63 | | /// <param name="assemblyName">The assembly name to load</param> |
| | | 64 | | /// <returns>The loaded assembly or null if not found</returns> |
| | | 65 | | protected override System.Reflection.Assembly? Load(AssemblyName assemblyName) |
| | 8 | 66 | | { |
| | | 67 | | try |
| | 8 | 68 | | { |
| | 8 | 69 | | _logger?.LogDebug("Attempting to resolve assembly: {AssemblyName}", assemblyName.FullName); |
| | | 70 | | |
| | | 71 | | // First, try to resolve using the dependency resolver |
| | 8 | 72 | | string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); |
| | 8 | 73 | | if (assemblyPath != null) |
| | 3 | 74 | | { |
| | 3 | 75 | | _logger?.LogDebug("Resolved assembly {AssemblyName} to path: {Path}", assemblyName.FullName, assemblyPat |
| | 3 | 76 | | return LoadFromAssemblyPath(assemblyPath); |
| | | 77 | | } |
| | | 78 | | |
| | | 79 | | // Next, try to find the assembly in the same directory |
| | 5 | 80 | | string potentialPath = Path.Combine(_assemblyDirectory, $"{assemblyName.Name}.dll"); |
| | 5 | 81 | | if (File.Exists(potentialPath)) |
| | 0 | 82 | | { |
| | 0 | 83 | | _logger?.LogDebug("Found assembly {AssemblyName} in directory: {Path}", assemblyName.FullName, potential |
| | 0 | 84 | | return LoadFromAssemblyPath(potentialPath); |
| | | 85 | | } |
| | | 86 | | |
| | | 87 | | // If we can't resolve it, return null to let the runtime handle it |
| | 5 | 88 | | _logger?.LogDebug("Could not resolve assembly: {AssemblyName}", assemblyName.FullName); |
| | 5 | 89 | | return null; |
| | | 90 | | } |
| | 0 | 91 | | catch (Exception ex) |
| | 0 | 92 | | { |
| | 0 | 93 | | _logger?.LogWarning( |
| | 0 | 94 | | ex, |
| | 0 | 95 | | "Error resolving assembly {AssemblyName} for {MainAssembly}", |
| | 0 | 96 | | assemblyName.FullName, |
| | 0 | 97 | | _mainAssemblyPath); |
| | 0 | 98 | | throw; |
| | | 99 | | } |
| | 8 | 100 | | } |
| | | 101 | | |
| | | 102 | | /// <summary> |
| | | 103 | | /// Loads a native library with the given name |
| | | 104 | | /// </summary> |
| | | 105 | | /// <param name="libName">The library name to load</param> |
| | | 106 | | /// <returns>The loaded library handle or IntPtr.Zero if not found</returns> |
| | | 107 | | protected override IntPtr LoadUnmanagedDll(string libName) |
| | 3 | 108 | | { |
| | | 109 | | try |
| | 3 | 110 | | { |
| | 3 | 111 | | _logger?.LogDebug("Attempting to resolve native library: {LibName}", libName); |
| | | 112 | | |
| | | 113 | | // First, try to resolve using the dependency resolver |
| | 3 | 114 | | string? libraryPath = _resolver.ResolveUnmanagedDllToPath(libName); |
| | 3 | 115 | | if (libraryPath != null) |
| | 0 | 116 | | { |
| | 0 | 117 | | _logger?.LogDebug("Resolved native library {LibName} to path: {Path}", libName, libraryPath); |
| | 0 | 118 | | return LoadUnmanagedDllFromPath(libraryPath); |
| | | 119 | | } |
| | | 120 | | |
| | | 121 | | // Next, try to find the library in the same directory |
| | 3 | 122 | | string potentialPath = Path.Combine(_assemblyDirectory, libName); |
| | 3 | 123 | | if (File.Exists(potentialPath)) |
| | 0 | 124 | | { |
| | 0 | 125 | | _logger?.LogDebug("Found native library {LibName} in directory: {Path}", libName, potentialPath); |
| | 0 | 126 | | return LoadUnmanagedDllFromPath(potentialPath); |
| | | 127 | | } |
| | | 128 | | |
| | | 129 | | // If we can't resolve it, return IntPtr.Zero to let the runtime handle it |
| | 3 | 130 | | _logger?.LogDebug("Could not resolve native library: {LibName}", libName); |
| | 3 | 131 | | return IntPtr.Zero; |
| | | 132 | | } |
| | 0 | 133 | | catch (Exception ex) |
| | 0 | 134 | | { |
| | 0 | 135 | | _logger?.LogWarning( |
| | 0 | 136 | | ex, |
| | 0 | 137 | | "Error resolving native library {LibName} for {MainAssembly}", |
| | 0 | 138 | | libName, |
| | 0 | 139 | | _mainAssemblyPath); |
| | 0 | 140 | | throw; |
| | | 141 | | } |
| | 3 | 142 | | } |
| | | 143 | | } |