Skip to content

Commit

Permalink
Changed the source generator to use the hoist pattern
Browse files Browse the repository at this point in the history
Instead of having the users source generator be the entry point we create the entrypoint. This way we can 100% make sure the assembly resolver loaded before the users types get loaded. The main problem was that if users source generator contained static fields of types contained within external assemblies this would explode because the assembly resolver had not be subscribed yet
  • Loading branch information
ByronMayne committed Feb 16, 2024
1 parent 303d9e3 commit 99ce352
Show file tree
Hide file tree
Showing 30 changed files with 894 additions and 318 deletions.
3 changes: 0 additions & 3 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
<!-- Paths | SGF Contracts -->
<SGFContractsProjectDir>$(SGFSourceDir)SourceGenerator.Foundations.Contracts\</SGFContractsProjectDir>
<SGFContractsProjectPath>$(SGFContractsProjectDir)SourceGenerator.Foundations.Contracts.csproj</SGFContractsProjectPath>
<!-- Paths | SGF Injector -->
<SGFInjectorProjectDir>$(SGFSourceDir)SourceGenerator.Foundations.Injector\</SGFInjectorProjectDir>
<SGFInjectorProjectPath>$(SGFInjectorProjectDir)SourceGenerator.Foundations.Injector.csproj</SGFInjectorProjectPath>
<!-- Paths | SGF MSBuild -->
<SGFMSBuildProjectDir>$(SGFSourceDir)SourceGenerator.Foundations.MSBuild\</SGFMSBuildProjectDir>
<SGFMSBuildProjectPath>$(SGFMSBuildProjectDir)SourceGenerator.Foundations.MSBuild.csproj</SGFMSBuildProjectPath>
Expand Down
4 changes: 0 additions & 4 deletions src/Nuget.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

<!-- Changes the content of the nuget package by manually adding files-->
<Target Name="AppendNugetContent">
<Message Importance="high" Text="Importing: $(SGFInjectorProjectPath)bin\$(Configuration)\net6.0\*.*"/>
<ItemGroup>
<TfmSpecificPackageFile Include="$(SGFContractsProjectDir)bin\$(Configuration)\netstandard2.0\SourceGenerator.Foundations.Contracts.dll">
<PackagePath>lib/netstandard2.0/SourceGenerator.Foundations.Contracts.dll</PackagePath>
Expand All @@ -31,9 +30,6 @@
<TfmSpecificPackageFile Include="$(SGFMSBuildProjectDir)bin\$(Configuration)netstandard2.0\SourceGenerator.Foundations.MSBuild.*">
<PackagePath>sgf/injector/</PackagePath>
</TfmSpecificPackageFile>
<TfmSpecificPackageFile Include="$(SGFInjectorProjectDir)bin\$(Configuration)\net6.0\**\*.*">
<PackagePath>build</PackagePath>
</TfmSpecificPackageFile>
</ItemGroup>
</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@
<PackageReference Include="System.Management" Version="7.0.0" GeneratePathProperty="true" />
<ProjectReference Include="$(SGFContractsProjectPath)" />
</ItemGroup>
<Import Project="$(SGFSharedProjectItemsPath)" Label="Shared" />
<Import Project="$(SGFTargetsPath)" />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ namespace SGF
/// <summary>
/// Represents a enviroment where the user is authoring code in Visual Studio
/// </summary>
internal class WindowsDevelopmentPlatform : IDevelopmentPlatform
internal class WindowsDevelopmentPlatform : IGeneratorEnvironment
{
public PlatformType Type { get; }

public string Name { get; }


public WindowsDevelopmentPlatform()
{
Name = "VisualStudio";
Type = PlatformType.VisualStudio;

if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("VisualStudioVersion")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
<IsRoslynComponent>true</IsRoslynComponent>
<Platforms>AnyCPU;x64</Platforms>
<AssemblySearchPath_UseOutDir>true</AssemblySearchPath_UseOutDir>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SGFWindowsPluginProjectPath)" />
<ProjectReference Include="..\..\SourceGenerator.Foundations.Contracts\SourceGenerator.Foundations.Contracts.csproj" />
<ProjectReference Include="..\..\SourceGenerator.Foundations\SourceGenerator.Foundations.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<Import Project="..\..\SourceGenerator.Foundations.Shared\SourceGenerator.Foundations.Shared.projitems" Label="Shared" />
<Import Project="$(SGFTargetsPath)" />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace ConsoleApp.SourceGenerator
{
[Generator]
[SgfGenerator]
internal class ConsoleAppSourceGenerator : IncrementalGenerator
{
public class Payload
Expand All @@ -15,10 +15,12 @@ public class Payload
public string? Version { get; set; }
}

public ConsoleAppSourceGenerator() : base("ConsoleAppSourceGenerator")
{ }
public ConsoleAppSourceGenerator(IGeneratorEnvironment generatorPlatform, ILogger logger) : base("ConsoleAppSourceGenerator", generatorPlatform, logger)
{

}

protected override void OnInitialize(SgfInitializationContext context)
public override void OnInitialize(SgfInitializationContext context)
{
Payload payload = new()
{
Expand Down
1 change: 1 addition & 0 deletions src/Sandbox/ConsoleApp/ConsoleApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ConsoleApp.SourceGenerator\ConsoleApp.SourceGenerator.csproj">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
using System.Collections.Generic;
using System.Diagnostics;

namespace SGF.Platforms
namespace SGF.Environments
{
internal class GenericDevelopmentPlatform : IDevelopmentPlatform
public class GenericDevelopmentEnvironment : IGeneratorEnvironment
{
public string Name { get; }

public GenericDevelopmentEnvironment()
{
Name = "Generic";
}

public bool AttachDebugger(int processId)
{
return Debugger.Launch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@
namespace SGF
{
/// <summary>
/// Abstracts for development environments supported by the platform
/// Contains information about the enviroment that this source generator is running in. It allows you to
/// star tthe debuggger and get the custom platform loggers based on the context.
/// </summary>
public interface IDevelopmentPlatform
public interface IGeneratorEnvironment
{
/// <summary>
/// Gets the name of the environment
/// </summary>
string Name { get; }

/// <summary>
/// Attaches the debugger to the given process Id and returns back if it was successful or not. This can
/// fail if Visual Studio is not already running
Expand Down
90 changes: 90 additions & 0 deletions src/SourceGenerator.Foundations.Contracts/IncrementalGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Diagnostics;
using SGF.Diagnostics;
using Microsoft.CodeAnalysis;

namespace SGF
{
/// <summary>
/// Used as a base class for creating your own source generator. This class provides some helper
/// methods and impoved debugging expereince. The generator that implements this must apply the
/// <see cref="GeneratorAttribute"/> but not inheirt from <see cref="IIncrementalGenerator"/>
/// </summary>
public abstract class IncrementalGenerator : IDisposable
{
private readonly IGeneratorEnvironment m_developmentPlatform;

/// <summary>
/// Gets the name of the source generator
/// </summary>
public string Name { get; }

/// <summary>
/// Gets the log that can allow you to output information to your
/// IDE of choice
/// </summary>
public ILogger Logger { get; }


/// <summary>
/// Initializes a new instance of the incremental generator with an optional name
/// </summary>
protected IncrementalGenerator(
string? name,
IGeneratorEnvironment developmentPlatform,
ILogger logger)
{
m_developmentPlatform = developmentPlatform;
Name = name ?? GetType().Name;
Logger = logger;
}

/// <summary>
/// Implement to initalize the incremental source generator
/// </ summary >
public abstract void OnInitialize(SgfInitializationContext context);

/// <summary>
/// Override to add logic for disposing this instance and free resources
/// </summary>
protected virtual void Dipose()
{ }

/// <summary>
/// Attaches the debugger automtically if you are running from Visual Studio. You have the option
/// to stop or just continue
/// </summary>
protected void AttachDebugger()
{
Process process = Process.GetCurrentProcess();
m_developmentPlatform.AttachDebugger(process.Id);
}

/// <summary>
/// Raised when one of the generator functions throws an unhandle exception. Override this to define your own behaviour
/// to handle the exception.
/// </summary>
/// <param name="exception">The exception that was thrown</param>
protected virtual void OnException(Exception exception)
{
Logger.Error(exception, $"Unhandled exception was throw while running the generator {Name}");
}

/// <summary>
/// Events raised when the exception is being thrown by the app domain
/// </summary>
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
if (e.ExceptionObject is Exception exception)
{
OnException(exception);
}
}

/// <inheritdoc cref="IDisposable"/>
void IDisposable.Dispose()
{
Dipose();
}
}
}
14 changes: 14 additions & 0 deletions src/SourceGenerator.Foundations.Contracts/SgfGeneratorAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace SGF
{
/// <summary>
/// Applied a class the inheirts from <see cref="IncrementalGenerator"/>
/// that will have Source Generator Foundations wrapper generated around it. This adds
/// better error handling and logging to the given generator.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class SgfGeneratorAttribute : Attribute
{
}
}
131 changes: 131 additions & 0 deletions src/SourceGenerator.Foundations.Contracts/SgfInitializationContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using SGF.Diagnostics;
using System;
using System.Reflection;

namespace SGF
{
/// <summary>
/// Middleware wrapper around a <see cref="IncrementalGeneratorInitializationContext"/> to allow for
/// wraping with exception handling and provide a better user expereince
/// </summary>
public readonly struct SgfInitializationContext
{
private readonly ILogger m_logger;
private readonly IncrementalGeneratorInitializationContext m_context;

public SyntaxValueProvider SyntaxProvider => m_context.SyntaxProvider;
public IncrementalValueProvider<Compilation> CompilationProvider => m_context.CompilationProvider;
public IncrementalValueProvider<ParseOptions> ParseOptionsProvider => m_context.ParseOptionsProvider;
public IncrementalValuesProvider<AdditionalText> AdditionalTextsProvider => m_context.AdditionalTextsProvider;
public IncrementalValueProvider<AnalyzerConfigOptionsProvider> AnalyzerConfigOptionsProvider => m_context.AnalyzerConfigOptionsProvider;
public IncrementalValuesProvider<MetadataReference> MetadataReferencesProvider => m_context.MetadataReferencesProvider;

public SgfInitializationContext(
IncrementalGeneratorInitializationContext context,
ILogger logger)
{
m_logger = logger;
m_context = context;
}

public void RegisterSourceOutput<TSource>(IncrementalValueProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action)
{
ILogger logger = m_logger;

void wrappedAction(SourceProductionContext context, TSource source)
{
try
{
action(new(context, logger), source);
}
catch (Exception exception)
{
LogException(logger, exception, action.Method);
}
}
m_context.RegisterSourceOutput(source, wrappedAction);
}

public void RegisterSourceOutput<TSource>(IncrementalValuesProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action)
{
ILogger logger = m_logger;
void wrappedAction(SourceProductionContext context, TSource source)
{
try
{
action(new(context, logger), source);
}
catch (Exception exception)
{
LogException(logger, exception, action.Method);
}
}
m_context.RegisterSourceOutput(source, wrappedAction);
}

public void RegisterImplementationSourceOutput<TSource>(IncrementalValueProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action)
{
ILogger logger = m_logger;
void wrappedAction(SourceProductionContext context, TSource source)
{
try
{
SgfSourceProductionContext sgfContext = new SgfSourceProductionContext(context, logger);
action(sgfContext, source);
logger.Information($" SourceFiles: {sgfContext.SourceCount}");
}
catch (Exception exception)
{
LogException(logger, exception, action.Method);
}
}
m_context.RegisterImplementationSourceOutput(source, wrappedAction);
}



public void RegisterImplementationSourceOutput<TSource>(IncrementalValuesProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action)
{
ILogger logger = m_logger;

void wrappedAction(SourceProductionContext context, TSource source)
{
try
{
action(new(context, logger), source);
}
catch (Exception exception)
{
LogException(logger, exception, action.Method);
}
}
m_context.RegisterImplementationSourceOutput(source, wrappedAction);
}

public void RegisterPostInitializationOutput(Action<IncrementalGeneratorPostInitializationContext> callback)
{
ILogger logger = m_logger;
void wrappedCallback(IncrementalGeneratorPostInitializationContext context)
{
try
{
callback(context);
}
catch (Exception exception)
{
LogException(logger, exception, callback.Method);
}
}
m_context.RegisterPostInitializationOutput(wrappedCallback);
}

private static void LogException(ILogger logger, Exception exception, MethodInfo actionInfo)
{
string methodName = actionInfo.Name;
string className = actionInfo.DeclaringType.FullName;
logger.Error(exception, $"An {exception.GetType().Name} exception was thrown while invoking {className}.{methodName}");
}
}
}
Loading

0 comments on commit 99ce352

Please sign in to comment.