2025-08-14 17:38:51 +01:00
|
|
|
|
#nullable enable
|
2022-12-14 20:56:08 +00:00
|
|
|
|
using System.Collections.Generic;
|
2025-08-14 17:38:51 +01:00
|
|
|
|
using System.Collections.Immutable;
|
2022-12-14 20:56:08 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using Microsoft.CodeAnalysis;
|
|
|
|
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
2025-08-14 17:38:51 +01:00
|
|
|
|
using Microsoft.CodeAnalysis.Text;
|
2022-12-14 20:56:08 +00:00
|
|
|
|
|
|
|
|
|
|
namespace Aaru.Generators;
|
|
|
|
|
|
|
|
|
|
|
|
[Generator]
|
2025-08-14 17:38:51 +01:00
|
|
|
|
public sealed class PluginRegisterGenerator : IIncrementalGenerator
|
2022-12-14 20:56:08 +00:00
|
|
|
|
{
|
2025-08-17 01:08:19 +01:00
|
|
|
|
// name → (registration method, directOnly)
|
|
|
|
|
|
static readonly (string Name, string Method, bool DirectOnly)[] PluginMap = new[]
|
2025-08-14 17:38:51 +01:00
|
|
|
|
{
|
2025-08-17 01:08:19 +01:00
|
|
|
|
("IArchive", "RegisterArchivePlugins", true), ("IChecksum", "RegisterChecksumPlugins", true),
|
|
|
|
|
|
("IFilesystem", "RegisterFilesystemPlugins", true), ("IFilter", "RegisterFilterPlugins", true),
|
|
|
|
|
|
("IFloppyImage", "RegisterFloppyImagePlugins", true),
|
|
|
|
|
|
("IMediaImage", "RegisterMediaImagePlugins", true), // direct only
|
|
|
|
|
|
("IPartition", "RegisterPartitionPlugins", true),
|
|
|
|
|
|
("IReadOnlyFilesystem", "RegisterReadOnlyFilesystemPlugins", true),
|
|
|
|
|
|
("IWritableFloppyImage", "RegisterWritableFloppyImagePlugins", true),
|
|
|
|
|
|
("IWritableImage", "RegisterWritableImagePlugins", false), // inherited OK
|
|
|
|
|
|
("IByteAddressableImage", "RegisterByteAddressablePlugins", false),
|
|
|
|
|
|
("IFluxImage", "RegisterFluxImagePlugins", true),
|
|
|
|
|
|
("IWritableFluxImage", "RegisterWritableFluxImagePlugins", false)
|
|
|
|
|
|
|
|
|
|
|
|
// …add more as needed…
|
2025-08-14 17:38:51 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#region IIncrementalGenerator Members
|
|
|
|
|
|
|
2025-08-16 22:59:54 +01:00
|
|
|
|
public void Initialize(IncrementalGeneratorInitializationContext ctx)
|
2025-08-14 17:38:51 +01:00
|
|
|
|
{
|
2025-08-17 01:08:19 +01:00
|
|
|
|
// 1) grab every ClassDeclarationSyntax that has a base list
|
|
|
|
|
|
IncrementalValueProvider<ImmutableArray<ClassDeclarationSyntax>> classSyntaxes = ctx.SyntaxProvider
|
2025-08-16 22:59:54 +01:00
|
|
|
|
.CreateSyntaxProvider((node, ct) => node is ClassDeclarationSyntax cds && cds.BaseList != null,
|
|
|
|
|
|
(ctx, ct) => (ClassDeclarationSyntax)ctx.Node)
|
2025-08-17 01:08:19 +01:00
|
|
|
|
.Collect();
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
// 2) combine with the compilation for symbol lookups
|
2025-08-16 22:59:54 +01:00
|
|
|
|
IncrementalValueProvider<(Compilation Left, ImmutableArray<ClassDeclarationSyntax> Right)>
|
2025-08-17 01:08:19 +01:00
|
|
|
|
compilationAndClasses = ctx.CompilationProvider.Combine(classSyntaxes);
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
// 3) register our source output
|
2025-08-16 22:59:54 +01:00
|
|
|
|
ctx.RegisterSourceOutput(compilationAndClasses,
|
2025-08-17 01:08:19 +01:00
|
|
|
|
(spc, source) =>
|
2025-08-16 22:59:54 +01:00
|
|
|
|
{
|
|
|
|
|
|
(Compilation? compilation, ImmutableArray<ClassDeclarationSyntax> classDecls) =
|
2025-08-17 01:08:19 +01:00
|
|
|
|
source;
|
|
|
|
|
|
|
|
|
|
|
|
if(compilation is null) return;
|
|
|
|
|
|
|
|
|
|
|
|
// load all plugin‐interface symbols
|
|
|
|
|
|
(string Name, string Method, bool DirectOnly, INamedTypeSymbol? Symbol)[]
|
|
|
|
|
|
ifaceDefs = PluginMap.Select(x =>
|
|
|
|
|
|
{
|
|
|
|
|
|
INamedTypeSymbol? sym =
|
|
|
|
|
|
compilation
|
|
|
|
|
|
.GetTypeByMetadataName($"Aaru.CommonTypes.Interfaces.{x.Name}");
|
|
|
|
|
|
|
|
|
|
|
|
return (x.Name, x.Method, x.DirectOnly, Symbol: sym);
|
|
|
|
|
|
})
|
|
|
|
|
|
.Where(x => x.Symbol is not null)
|
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
// load IPluginRegister
|
|
|
|
|
|
INamedTypeSymbol? registerSym =
|
2025-08-16 22:59:54 +01:00
|
|
|
|
compilation
|
|
|
|
|
|
.GetTypeByMetadataName("Aaru.CommonTypes.Interfaces.IPluginRegister");
|
|
|
|
|
|
|
|
|
|
|
|
var plugins = new List<PluginInfo>();
|
|
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
foreach(ClassDeclarationSyntax? decl in classDecls.Distinct())
|
2025-08-16 22:59:54 +01:00
|
|
|
|
{
|
2025-08-17 01:08:19 +01:00
|
|
|
|
SemanticModel model = compilation.GetSemanticModel(decl.SyntaxTree);
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
var cls = model.GetDeclaredSymbol(decl, spc.CancellationToken)
|
|
|
|
|
|
as INamedTypeSymbol;
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
if(cls is null) continue;
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
// direct vs. all (transitive) interfaces
|
|
|
|
|
|
ImmutableArray<INamedTypeSymbol> directIfaces = cls.Interfaces;
|
|
|
|
|
|
ImmutableArray<INamedTypeSymbol> allIfaces = cls.AllInterfaces;
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
|
|
|
|
|
var info = new PluginInfo
|
|
|
|
|
|
{
|
2025-08-17 01:08:19 +01:00
|
|
|
|
Namespace = cls.ContainingNamespace.ToDisplayString(),
|
|
|
|
|
|
ClassName = cls.Name,
|
2025-08-16 22:59:54 +01:00
|
|
|
|
IsRegister =
|
2025-08-17 01:08:19 +01:00
|
|
|
|
registerSym != null &&
|
|
|
|
|
|
allIfaces.Contains(registerSym, SymbolEqualityComparer.Default)
|
2025-08-16 22:59:54 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
// for each plugin interface, choose direct or inherited match
|
|
|
|
|
|
foreach((string Name, string Method, bool DirectOnly,
|
|
|
|
|
|
INamedTypeSymbol? Symbol) in ifaceDefs)
|
2025-08-16 22:59:54 +01:00
|
|
|
|
{
|
2025-08-17 01:08:19 +01:00
|
|
|
|
bool matches = DirectOnly
|
|
|
|
|
|
? directIfaces.Contains(Symbol!,
|
|
|
|
|
|
SymbolEqualityComparer.Default)
|
|
|
|
|
|
: allIfaces.Contains(Symbol!,
|
|
|
|
|
|
SymbolEqualityComparer.Default);
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
if(matches) info.Interfaces.Add((Name, Method));
|
2025-08-16 22:59:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(info.IsRegister || info.Interfaces.Count > 0) plugins.Add(info);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(plugins.Count == 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
// find the one class that implements IPluginRegister
|
2025-08-17 01:08:19 +01:00
|
|
|
|
PluginInfo? registrar = plugins.FirstOrDefault(p => p.IsRegister);
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
if(registrar is null) return;
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
|
|
|
|
|
// build the generated file
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
|
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
|
|
|
|
|
|
sb.AppendLine("using Aaru.CommonTypes.Interfaces;");
|
2025-08-17 01:08:19 +01:00
|
|
|
|
sb.AppendLine($"namespace {registrar.Namespace};");
|
|
|
|
|
|
sb.AppendLine($"public sealed partial class {registrar.ClassName} : IPluginRegister");
|
2025-08-16 22:59:54 +01:00
|
|
|
|
sb.AppendLine("{");
|
|
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
// emit one registration method per plugin‐interface
|
|
|
|
|
|
foreach((string Name, string Method, bool _) in PluginMap)
|
2025-08-16 22:59:54 +01:00
|
|
|
|
{
|
2025-08-17 01:08:19 +01:00
|
|
|
|
sb.AppendLine($" public void {Method}(IServiceCollection services)");
|
2025-08-16 22:59:54 +01:00
|
|
|
|
sb.AppendLine(" {");
|
|
|
|
|
|
|
2025-08-17 01:08:19 +01:00
|
|
|
|
foreach(string? impl in plugins
|
|
|
|
|
|
.Where(pi => pi.Interfaces.Any(i => i.Name == Name))
|
|
|
|
|
|
.Select(pi => pi.ClassName)
|
|
|
|
|
|
.Distinct())
|
|
|
|
|
|
sb.AppendLine($" services.AddTransient<{Name}, {impl}>();");
|
2025-08-16 22:59:54 +01:00
|
|
|
|
|
|
|
|
|
|
sb.AppendLine(" }");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sb.AppendLine("}");
|
|
|
|
|
|
spc.AddSource("Register.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
|
|
|
|
|
|
});
|
2025-08-14 17:38:51 +01:00
|
|
|
|
}
|
2022-12-14 20:56:08 +00:00
|
|
|
|
|
2025-08-14 17:38:51 +01:00
|
|
|
|
#endregion
|
2022-12-14 20:56:08 +00:00
|
|
|
|
|
2025-08-14 17:38:51 +01:00
|
|
|
|
#region Nested type: PluginInfo
|
2023-10-03 23:24:05 +01:00
|
|
|
|
|
2025-08-16 22:59:54 +01:00
|
|
|
|
class PluginInfo
|
2022-12-14 20:56:08 +00:00
|
|
|
|
{
|
2025-08-17 01:08:19 +01:00
|
|
|
|
public string ClassName = "";
|
|
|
|
|
|
public readonly List<(string Name, string Method)> Interfaces = new();
|
|
|
|
|
|
public bool IsRegister;
|
|
|
|
|
|
public string Namespace = "";
|
2022-12-14 20:56:08 +00:00
|
|
|
|
}
|
2023-10-03 23:24:05 +01:00
|
|
|
|
|
|
|
|
|
|
#endregion
|
2022-12-14 20:56:08 +00:00
|
|
|
|
}
|