| | | 1 | | using System.Runtime.CompilerServices; |
| | | 2 | | |
| | | 3 | | using NexusLabs.Needlr.Generators; |
| | | 4 | | |
| | | 5 | | namespace MultiProjectApp.Features.Notifications; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// A sink that receives and processes notifications emitted by the application. |
| | | 9 | | /// </summary> |
| | | 10 | | /// <remarks> |
| | | 11 | | /// This interface exists to demonstrate the <c>RegisterPlugins()</c> cross-generator pattern. |
| | | 12 | | /// In a real scenario a second generator (such as a CacheProviderGenerator) would |
| | | 13 | | /// emit types implementing this interface, and TypeRegistryGenerator would not be able to see |
| | | 14 | | /// them because Roslyn generators operate on the original compilation in isolation. |
| | | 15 | | /// </remarks> |
| | | 16 | | public interface INotificationSink |
| | | 17 | | { |
| | | 18 | | void OnNotification(string recipient, string message); |
| | | 19 | | } |
| | | 20 | | |
| | | 21 | | /// <summary> |
| | | 22 | | /// Writes notifications to the console as an audit trail. |
| | | 23 | | /// </summary> |
| | | 24 | | /// <remarks> |
| | | 25 | | /// In the multi-generator scenario this type would be emitted by a second source generator. |
| | | 26 | | /// TypeRegistryGenerator can see it here because it is hand-written, but the |
| | | 27 | | /// <c>NotificationSinkRegistrations</c> class below demonstrates the pattern a generated |
| | | 28 | | /// <c>[ModuleInitializer]</c> would use. |
| | | 29 | | /// </remarks> |
| | | 30 | | public sealed record AuditLogNotificationSink : INotificationSink |
| | | 31 | | { |
| | | 32 | | public void OnNotification(string recipient, string message) => |
| | | 33 | | Console.WriteLine($"[Audit] {recipient}: {message}"); |
| | | 34 | | } |
| | | 35 | | |
| | | 36 | | /// <summary> |
| | | 37 | | /// Simulates the module initializer that a second source generator would emit to register |
| | | 38 | | /// its plugin types with Needlr at runtime, bypassing the Roslyn generator isolation boundary. |
| | | 39 | | /// </summary> |
| | | 40 | | /// <remarks> |
| | | 41 | | /// <para> |
| | | 42 | | /// Roslyn source generators run against the original compilation — a generator cannot see types |
| | | 43 | | /// emitted by another generator in the same build. This means <c>TypeRegistryGenerator</c> |
| | | 44 | | /// cannot include types produced by a second generator in the TypeRegistry. |
| | | 45 | | /// </para> |
| | | 46 | | /// <para> |
| | | 47 | | /// The workaround is runtime registration: the second generator emits a |
| | | 48 | | /// <c>[ModuleInitializer]</c> (like the one below) that calls |
| | | 49 | | /// <c>NeedlrSourceGenBootstrap.RegisterPlugins()</c>. All module initializers complete before |
| | | 50 | | /// any user code runs, so the plugins are available by the time the application calls |
| | | 51 | | /// <c>IPluginFactory.CreatePluginsFromAssemblies<T>()</c>. |
| | | 52 | | /// </para> |
| | | 53 | | /// <para> |
| | | 54 | | /// <c>NeedlrSourceGenBootstrap.Combine()</c> deduplicates across all registrations, so if |
| | | 55 | | /// TypeRegistryGenerator also sees the type (as in this example where it is hand-written), |
| | | 56 | | /// only one instance appears in the merged provider. |
| | | 57 | | /// </para> |
| | | 58 | | /// </remarks> |
| | | 59 | | internal static class NotificationSinkRegistrations |
| | | 60 | | { |
| | | 61 | | #pragma warning disable CA2255 |
| | | 62 | | [ModuleInitializer] |
| | | 63 | | internal static void Initialize() |
| | | 64 | | #pragma warning restore CA2255 |
| | | 65 | | { |
| | 8 | 66 | | NeedlrSourceGenBootstrap.RegisterPlugins(static () => |
| | 24 | 67 | | [ |
| | 24 | 68 | | new PluginTypeInfo( |
| | 24 | 69 | | typeof(AuditLogNotificationSink), |
| | 24 | 70 | | [typeof(INotificationSink)], |
| | 1 | 71 | | static () => new AuditLogNotificationSink(), |
| | 24 | 72 | | []) |
| | 24 | 73 | | ]); |
| | 8 | 74 | | } |
| | | 75 | | } |