Turnerj PageViews += 1;

The pain points of C# source generators: February 2022 Update

Feb 21, 2022

This is an update to my previous post about the pain points of C# source generators. Since writing about it in April 2021, there has been a bit of progress.

Debugging Source Generators

This has gotten significantly easier in Visual Studio (specifically from v16.10), at least in my usecases. Previously I said that I wanted to:

  • Put a breakpoint in the source generator code
  • Press the "Debug" button in Visual Studio
  • Code stops at the breakpoint

Thanks to some updates in Visual Studio, you can do this! To start, add the following to your project file for the source generator:

<IsRoslynComponent>true</IsRoslynComponent>

After this, you need to enable the "Roslyn Component" option in the project properties and select the appropriate target. For me with Schema.NET, it effectively generated the following "launchSettings.json" file in the "Properties" folder:

{
  "profiles": {
    "Schema.NET.Tool": {
      "commandName": "DebugRoslynComponent",
      "targetProject": "..\\..\\Source\\Schema.NET\\Schema.NET.csproj"
    }
  }
}

Then, set the source generator project as the startup project. Start the debugger and watch any breakpoints you have configured get hit. I've had this successfully working with Schema.NET and the source generator I wrote for it.

For me, debugging is practically a solved problem with source generators now.

Transient Dependencies

The short answer is, transient dependencies are basically just as painful for my usecase as they were originally. The issue seems to stem from how analyzers work with the build process - I'm no expert in this area though is something I've been looking a little into to see if I could help push this forwards.

I previously raised this in the Roslyn repo through the issues I encountered making transient dependencies work for Schema.NET. Since then, I've raised a more direct issue in the SDK repo for tracking the support for automatic transient dependency packaging. Progress has mostly stalled on that issue too as it seems there may be a number of blocker issues (due to the analyzer work I mentioned).

My previous post had the following as a potential automatic transient dependency solution:

<PropertyGroup>
    <GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>

<Target Name="GetDependencyTargetPaths" AfterTargets="ResolvePackageDependenciesForBuild">
    <ItemGroup>
        <TargetPathWithTargetPlatformMoniker Include="@(ResolvedCompileFileDefinitions)" IncludeRuntimeDependency="false" />
    </ItemGroup>
</Target>

This had two issues:

  • ResolveCompileFileDefinitions wasn't always available
  • ResolveCompileFileDefinitions contains more than the specific dependencies we are wanting

On GitHub, @ericstj mentioned:

  • Add DependsOnTargets="ResolveReferences" to fix the first problem
  • Setting CopyLocalLockFileAssemblies=true and using ReferenceCopyLocalPaths item instead to fix the second

Another developer on GitHub, @HavenDV, has a different alteration on my solution that works for NuGet packages:

<!-- 
    https://github.com/dotnet/roslyn/issues/52017#issuecomment-1046216200
    This automatically adds explicit and transient dependencies so that they are available at the time the generator is executed. 
-->
<Target Name="AddGenerationTimeReferences" AfterTargets="ResolvePackageDependenciesForBuild">
    <ItemGroup>
        <None Include="@(ResolvedCompileFileDefinitions)" Pack="true" PackagePath="analyzers/dotnet/cs" />
    </ItemGroup>
</Target>

If you are wanting to experiment further with automatic transient dependencies, you can look into how those help the situation.

So far I haven't seen any updates about whether fixes to transient dependencies will make it in the .NET 7 SDK but one can hope!