/// <summary> /// Finds the assembly locations for all assemblies containing plugins of the specified type. /// </summary> /// <param name="pluginPath">The path to plugins.</param> /// <param name="sharedTypes">A list of shared types to be used when loading plugins into custom assembly contexts.</param> /// <returns>A list of assembly locations.</returns> public IEnumerable <string> FindPluginAssemblies(string pluginPath, Type[] sharedTypes) { _logger.LogInformation(InformationMessages.SearchingPluginPath, typeof(TPlugin).Name, pluginPath); var assemblyPaths = new List <string>(); // Check directory var path = new DirectoryInfo(pluginPath); if (path.Exists) { var files = path.GetFiles(_options.FindPattern, SearchOption.AllDirectories); if (files.Any()) { _logger.LogInformation(InformationMessages.LibraryCountFoundInPath, files.Length, path.FullName); _logger.LogInformation(InformationMessages.ScanningLibraries); foreach (var libPath in files) { _logger.LogTrace(TraceMessages.CheckingLibraryForPlugins, libPath.FullName); // Create plugin assembly load context to hold the assemblies and dependencies per library var pluginContext = new PluginAssemblyLoadContext(libPath.FullName, sharedTypes); // Don't load the library containing the plugin type itself into the plugin AssemblyLoadContext, // otherwise the IsAssignableFrom won't return a match, because the same assembly in different // contexts have types that are not the same as each other when comparing in .NET Core. if (libPath.Name != new FileInfo(typeof(TPlugin).Assembly.Location).Name) { try { var assembly = pluginContext.LoadFromAssemblyPath(libPath.FullName); foreach (var type in assembly.GetExportedTypes().Where(t => !t.IsAbstract)) { var interfaceType = typeof(TPlugin); if (interfaceType.IsAssignableFrom(type)) { _logger.LogTrace(TraceMessages.FoundPlugin, type.FullName, libPath.FullName); assemblyPaths.Add(type.Assembly.Location); } } } catch (FileNotFoundException e) { _logger.LogDebug(TraceMessages.IgnoringLibraryMissingDependencies, libPath.FullName); _logger.LogDebug(e.ToString()); } catch (BadImageFormatException e) { _logger.LogDebug(TraceMessages.IgnoringLibraryNotDotNetImage, libPath.FullName); _logger.LogDebug(e.ToString()); } catch (FileLoadException e) { _logger.LogDebug(TraceMessages.IgnoringLibraryFileLoadFailed, libPath.FullName); _logger.LogDebug(e.ToString()); } } // Unload context pluginContext.Unload(); // Without collecting deterministically, some directories with lots of DLL's can // eventually cause an Internal CLR error (segmentation fault). This may still occur // but this alleviates the issue. Always use a search pattern (--find-pattern arg) to // reduce number of DLL's to scan. GC.Collect(); GC.WaitForPendingFinalizers(); } } else { _logger.LogInformation(InformationMessages.LibrariesNotFoundInPath, path.FullName); } } else { _logger.LogError(ErrorMessages.PluginPathDoesNotExist, path.FullName); } return(assemblyPaths); }