ByronMayne / SourceGenerator.Foundations

A Source Generator for Source Generators. Gives out of the box support for transistent dependencies and logging right in Visual Studio

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nuget GitHub Workflow Status

SourceGenerator.Foundations

Source Generators are awesome but working with them can be a bit painful. This library is designed to try to smooth out some bumps. To get started using the library just add a nuget reference. Once that is done you will have access to new types contained within your assembly.

Quick Start

To get started all you need to do is add the NuGet package reference. You may or may not have to restart Visual Studio for the new types to show up. Then implement the base class IncrementalGenerator.

namespace Example
{
    // IncrementalGenerator, is a generated type from `SourceGenerator.Foundations'
    public class ExampleSourceGenerator : IncrementalGenerator 
    {
       protected override void OnInitialize(IncrementalGeneratorInitializationContext context)
       {
            // Attaches Visaul Studio debugger without prompt 
            AttachDebugger();

            // Writes output to Visual Studios output window
            WriteLine("Hello World");
       }
    }
}

What is inside?

There is a series of features this library hopes to provide.

Nuget Nightmares

Source generators run in a unique place and their references don't act the same. If you have a source generator that attempts to reference a NuGet package it will fail to be found at runtime. The reason is the source generators need to have their dll's alongside the running process but that also means that the project the generator is running aginst will to. If you look online the best option is to do this:

<ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.1" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
    <EmbeddedResource Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" Visible="false" />
</ItemGroup>

This MSBuild logic adds a reference to Newtonsoft.Json version 13.0.1 and sets a few attributes. Using GeneratePathProperty="true" will emit the variable PKGNewtonsoft_Json which points at the root of the NuGet package on disk. Using EmbeddedResource we then give the full path to the dll. When the project is compiled the dlls will be embeded as a resource.

This works well but requires a lot of boilerplate. Even worse is this is just for one package. You would need to repeat this for every dependency and any transitive ones as well. This gets out of hand very fast.

Source.Generator foundations automates this all for you. Just add your NuGet references and nothing else

Logging Framework

Source generator run in the background and it can be very hard to debug. If you want to make a log you have to write the files to disk and open to read them.

With this library we leverage the existing Output window and create an entry for each source generator. This is all done by using some Visaul Studio api. Internally this uses a custom Skin for Serilog.net. Every generator comes with a Logger property allowing you to output any information

output

Captured Exceptions

When a source generator throws an exception it normally just stops. In the base library we process all exceptions and forward their output to the Source Generator output tab.

[13:35:39 ERR] Error! An unhandle exception was thrown while running the source generator.
System.IO.FileNotFoundException: Could not load file or assembly 'System.Collections.Immutable, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
File name: 'System.Collections.Immutable, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at Jawon.SourceGenerator.JawonSourceGenerator.OnInitialize(IncrementalGeneratorInitializationContext context)
   at SGF.IncrementalGenerator.Microsoft.CodeAnalysis.IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext context)

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

Example of a missing Assembly exception

Debugging

To be able to debug a source generators one of the suggested methods is to add the following.

static CustomSourceGenerator()
{
    Debugger.Launch();
}

This will popup the following window and you have to select your visual studio instance. Instaed with foundations you can just call a single method and break points work from that point on.

AutoAttach

Helpers

Included in the project is a series of helper classes to help you while working on your generator.

SGFPath

  • Contains methods for normalizing paths which does the following
    • files/../cats -> /cats
    • [Windows] files\cats/dogs -> \\files\\cats\\dogs
    • [Linux|Mac] files\cats/dogs -> /files/cats/dogs
  • Combing paths without having to worry about leading or trailing slashes.

How it works

Script Injector

It all starts with ScriptInjector.cs. This is a source generator loops over the resources within the current assembly and finds all *.cs files whos name starts with SGF.Script:: and copies them into the target assembly. The image below shows some of the scripts that are copied.

ScriptInjector

If you would like a script to be copied over you can add it as a ItemGroup element called SGF_EmbeddedScript which will be embedded in your assembly and prefixed with the correct name.

Assembly Resolver

When your source generator runs it needs to find it's dependencies and this is often what fails. When you want to provide external packages you will have to write your own resolve that is able to locate the required assemblies. So instead we have the ScriptInjector inject an implemention for you. This assembly resolver will loop over all the resources in the current assembly and look for all resources that start with SGF.Assembly::. If the assembly being requested to be loaded exists in the resources, it's extracted and loaded into the current appdomain.

AssemblyResolver

You can embed any assemblies you want by adding them to <SGF_EmbeddedAssembly Include="Your Assembly Path"/>

About

A Source Generator for Source Generators. Gives out of the box support for transistent dependencies and logging right in Visual Studio

License:Apache License 2.0


Languages

Language:C# 100.0%