Some checks failed
		
		
	
	Docs CI / lint-and-preview (push) Has been cancelled
				
			- Implemented `chrome-path.js` to define functions for locating Chromium binaries across different platforms and nested directories. - Added `verify-chromium.js` to check for the presence of the Chromium binary and log the results, including candidate paths checked. - The scripts support Linux, Windows, and macOS environments, enhancing the flexibility of Chromium binary detection.
		
			
				
	
	
		
			148 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			148 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Concurrent;
 | |
| using System.Collections.Generic;
 | |
| using System.Collections.ObjectModel;
 | |
| using System.Linq;
 | |
| using System.Reflection;
 | |
| using Microsoft.Extensions.Logging;
 | |
| using StellaOps.Plugin;
 | |
| using StellaOps.Plugin.Hosting;
 | |
| using StellaOps.Scanner.Core.Security;
 | |
| 
 | |
| namespace StellaOps.Scanner.Analyzers.Lang.Plugin;
 | |
| 
 | |
| public interface ILanguageAnalyzerPluginCatalog
 | |
| {
 | |
|     IReadOnlyCollection<ILanguageAnalyzerPlugin> Plugins { get; }
 | |
| 
 | |
|     void LoadFromDirectory(string directory, bool seal = true);
 | |
| 
 | |
|     IReadOnlyList<ILanguageAnalyzer> CreateAnalyzers(IServiceProvider services);
 | |
| }
 | |
| 
 | |
| public sealed class LanguageAnalyzerPluginCatalog : ILanguageAnalyzerPluginCatalog
 | |
| {
 | |
|     private readonly ILogger<LanguageAnalyzerPluginCatalog> _logger;
 | |
|     private readonly IPluginCatalogGuard _guard;
 | |
|     private readonly ConcurrentDictionary<string, Assembly> _assemblies = new(StringComparer.OrdinalIgnoreCase);
 | |
|     private IReadOnlyList<ILanguageAnalyzerPlugin> _plugins = Array.Empty<ILanguageAnalyzerPlugin>();
 | |
| 
 | |
|     public LanguageAnalyzerPluginCatalog(IPluginCatalogGuard guard, ILogger<LanguageAnalyzerPluginCatalog> logger)
 | |
|     {
 | |
|         _guard = guard ?? throw new ArgumentNullException(nameof(guard));
 | |
|         _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 | |
|     }
 | |
| 
 | |
|     public IReadOnlyCollection<ILanguageAnalyzerPlugin> Plugins => _plugins;
 | |
| 
 | |
|     public void LoadFromDirectory(string directory, bool seal = true)
 | |
|     {
 | |
|         ArgumentException.ThrowIfNullOrWhiteSpace(directory);
 | |
|         var fullDirectory = Path.GetFullPath(directory);
 | |
| 
 | |
|         var options = new PluginHostOptions
 | |
|         {
 | |
|             PluginsDirectory = fullDirectory,
 | |
|             EnsureDirectoryExists = false,
 | |
|             RecursiveSearch = false,
 | |
|         };
 | |
|         options.SearchPatterns.Add("StellaOps.Scanner.Analyzers.*.dll");
 | |
| 
 | |
|         var result = PluginHost.LoadPlugins(options, _logger);
 | |
|         if (result.Plugins.Count == 0)
 | |
|         {
 | |
|             _logger.LogWarning("No language analyzer plug-ins discovered under '{Directory}'.", fullDirectory);
 | |
|         }
 | |
| 
 | |
|         foreach (var descriptor in result.Plugins)
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 _guard.EnsureRegistrationAllowed(descriptor.AssemblyPath);
 | |
|                 _assemblies[descriptor.AssemblyPath] = descriptor.Assembly;
 | |
|                 _logger.LogInformation(
 | |
|                     "Registered language analyzer plug-in assembly '{Assembly}' from '{Path}'.",
 | |
|                     descriptor.Assembly.FullName,
 | |
|                     descriptor.AssemblyPath);
 | |
|             }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 _logger.LogError(ex, "Failed to register language analyzer plug-in '{Path}'.", descriptor.AssemblyPath);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         RefreshPluginList();
 | |
| 
 | |
|         if (seal)
 | |
|         {
 | |
|             _guard.Seal();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public IReadOnlyList<ILanguageAnalyzer> CreateAnalyzers(IServiceProvider services)
 | |
|     {
 | |
|         ArgumentNullException.ThrowIfNull(services);
 | |
| 
 | |
|         if (_plugins.Count == 0)
 | |
|         {
 | |
|             _logger.LogWarning("No language analyzer plug-ins available; skipping language analysis.");
 | |
|             return Array.Empty<ILanguageAnalyzer>();
 | |
|         }
 | |
| 
 | |
|         var analyzers = new List<ILanguageAnalyzer>(_plugins.Count);
 | |
| 
 | |
|         foreach (var plugin in _plugins)
 | |
|         {
 | |
|             if (!IsPluginAvailable(plugin, services))
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             try
 | |
|             {
 | |
|                 var analyzer = plugin.CreateAnalyzer(services);
 | |
|                 if (analyzer is null)
 | |
|                 {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 analyzers.Add(analyzer);
 | |
|             }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 _logger.LogError(ex, "Language analyzer plug-in '{Plugin}' failed to create analyzer instance.", plugin.Name);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (analyzers.Count == 0)
 | |
|         {
 | |
|             _logger.LogWarning("All language analyzer plug-ins were unavailable.");
 | |
|             return Array.Empty<ILanguageAnalyzer>();
 | |
|         }
 | |
| 
 | |
|         analyzers.Sort(static (a, b) => string.CompareOrdinal(a.Id, b.Id));
 | |
|         return new ReadOnlyCollection<ILanguageAnalyzer>(analyzers);
 | |
|     }
 | |
| 
 | |
|     private void RefreshPluginList()
 | |
|     {
 | |
|         var assemblies = _assemblies.Values.ToArray();
 | |
|         var plugins = PluginLoader.LoadPlugins<ILanguageAnalyzerPlugin>(assemblies);
 | |
|         _plugins = plugins is IReadOnlyList<ILanguageAnalyzerPlugin> list
 | |
|             ? list
 | |
|             : new ReadOnlyCollection<ILanguageAnalyzerPlugin>(plugins.ToArray());
 | |
|     }
 | |
| 
 | |
|     private static bool IsPluginAvailable(ILanguageAnalyzerPlugin plugin, IServiceProvider services)
 | |
|     {
 | |
|         try
 | |
|         {
 | |
|             return plugin.IsAvailable(services);
 | |
|         }
 | |
|         catch
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| }
 |