Reputation: 279
Edit3: At some point this just started working. No clue why. Maybe it was a VS bug that got fixed?
Edit2: Looking into the Analyzers node in solution explorer, I've discovered the source generator runs successfully when I first open the program, and then it stops and everything it generated goes away after only a few changes to my code.
immediately after opening solution:
> Analyzers
>> MySourceGenerators
>>> MySourceGenerators.NotifyPropertyChangesGenerator
>>>> _NotifyChangedClass_Notify.cs
after making any edits
> Analyzers
>> MySourceGenerators
>>> MySourceGenerators.NotifyPropertyChangesGenerator
>>>> This generator is not generating files.
Edit: After calling Debugger.Launch()
as suggested by comments, I can confirm that the generator code is running, and the source text looks exactly like it's supposed to. But both the IDE and compiler still give errors as if the results aren't being included.
I'm trying to setup a source generator to be run from a local project reference but can't get it to actually run. My NUnit tests are passing, so I know the actual generation logic is fine, but a barebones test project both fails to compile and reports errors in Visual Studio. I'm using Visual Studio 2022 Preview 5.0, in case that matters.
<--generator.csproj-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IncludeBuildOutpout>false</IncludeBuildOutpout>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
</ItemGroup>
</Project>
<--testproject.csproj-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MySourceGenerators\MySourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
</ItemGroup>
</Project>
//generator.cs
[Generator]
public class NotifyPropertyChangesGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var receiver = (NotifySyntaxReceiver)context.SyntaxReceiver!;
if (receiver.Classes.Count > 0)
{
foreach (var c in receiver.Classes)
{
/* Generate the source */
var source = SyntaxFactory.ParseCompilationUnit(builder.ToString())
.NormalizeWhitespace()
.GetText(Encoding.UTF8, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm.Sha256);
context.AddSource($"_{c.ClassDeclaration.Identifier.ValueText}_Notify", source);
}
}
}
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new NotifySyntaxReceiver());
}
}
class NotifySyntaxReceiver : ISyntaxReceiver
{
public List<NotifyClass> Classes { get; } = new();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax cds)
{
/* Identify classes that need generation */
}
}
}
//testproject.cs
internal class NotifyChangedClass : INotifyPropertyChanged
{
string n_Property;
}
Upvotes: 9
Views: 7792
Reputation: 1249
In my case, in my project which referenced the generator... needed the project reference defined as follws:
<ProjectReference Include="..\MySourceGenerator.Project\Sourcegenerator.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="true"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0" />
Upvotes: 0
Reputation: 366
Unfortunately, even in current VS 2022 (version 17.0.5) the support for this feature is somewhat limited. As you noticed, the only moment when VS shows the correct state of code generated is after VS restart (not just solution load/unload, but a complete restart of the application). It isn't a problem when a generator is completed, and you only want to check what was generated, but it's pain during generator development. So I ended up with such an approach during development:
At the given generator during its debugging/development, we can add the output of generated files not only to the compilation context but to a temporary directory at the file system or only to the temporary directory until we are satisfied with the result.
To force the generator to run, we need to force rebuild the "testproject.csproj" project.
I'd use the command line from the "testproject" project directory: 'dotnet clean; dotnet build
'.
The generated files will end up in the output directory. We can watch them using VS Code, for example. VS Code won't block open files, but any other notepad with a non-blocking read will suffice. It is not an ideal solution, but at this moment it removes the primary pain of generator development: to see the actual result of code generation, we don't have to restart VS.
Example code draft for "generator.csproj" project:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
namespace Target.Generators
{
[Generator]
public class TargetGenerator : ISourceGenerator
{
private readonly ISourceBuilder _sourceBuilder;
public TargetGenerator()
{
_sourceBuilder = new SourceBuilder();
}
public void Initialize(GeneratorInitializationContext context) =>
_sourceBuilder.Initialize(context);
public void Execute(GeneratorExecutionContext context)
{
// Uncomment these to lines to start debugging the generator in the separate VS instance
//// Debugger.Launch();
//// Debugger.Break();
// comment/uncomment these lines to use ether 'default' or 'debug' source file writer
////var fileWriter = new DefaultSourceFileWriter(context);
var fileWriter = new DebugSourceFileWriter(context, "C:\\code-gen");
var fileBuilders = _sourceBuilder.Build(context);
fileWriter.WriteFiles(fileBuilders);
}
}
public interface ISourceBuilder
{
void Initialize(GeneratorInitializationContext context);
IEnumerable<(string Filename, string Source)> Build(GeneratorExecutionContext context);
}
public class SourceBuilder : ISourceBuilder
{
public void Initialize(GeneratorInitializationContext context)
{
}
public IEnumerable<(string Filename, string Source)> Build(GeneratorExecutionContext context)
{
// Here should be an actual source code generator implementation
throw new NotImplementedException();
}
}
public interface ISourceFileWriter
{
void WriteFiles(IEnumerable<(string Filename, string Source)> sourceFiles);
}
public class DefaultSourceFileWriter : ISourceFileWriter
{
private readonly GeneratorExecutionContext _context;
public DefaultSourceFileWriter(GeneratorExecutionContext context)
{
_context = context;
}
public void WriteFiles(IEnumerable<(string Filename, string Source)> sourceFiles)
{
foreach (var sourceFile in sourceFiles)
{
AddFile(sourceFile);
}
}
protected virtual void AddFile((string Filename, string Source) sourceFile)
{
_context.AddSource(
sourceFile.Filename,
SourceText.From(sourceFile.Source, Encoding.UTF8));
}
}
public class DebugSourceFileWriter : DefaultSourceFileWriter
{
private readonly string _outputDirectoryRoot;
public DebugSourceFileWriter(
GeneratorExecutionContext context,
string outputDirectoryRoot)
: base(context)
{
_outputDirectoryRoot = outputDirectoryRoot;
}
protected override void AddFile((string Filename, string Source) sourceFile)
{
bool done = false;
int cnt = 0;
while (!done)
{
try
{
var fullFileName = Path.Combine(_outputDirectoryRoot, sourceFile.Filename);
File.WriteAllText(fullFileName, sourceFile.Source, Encoding.UTF8);
done = true;
}
catch
{
cnt++;
if (cnt > 5)
{
done = true;
}
Thread.Sleep(100);
}
}
}
}
}
Upvotes: 4
Reputation: 49260
Source generators target netstandard2.0
, your project targets net6.0
.
That isn't an issue when you use source generators via PackageReference
.
I think for ProjectReference
to work in this case you need to add the SetTargetFramework
meta data.
<ItemGroup>
<ProjectReference Include="..\MySourceGenerators\MySourceGenerators.csproj"
OutputItemType="Analyzer"
SetTargetFramework="netstandard2.0"
ReferenceOutputAssembly="false"/>
</ItemGroup>
That might work, sorry can't try right now.
Upvotes: 1