简体   繁体   English

C# 源代码生成器不包括来自项目参考的结果

[英]C# Source Generator not including results from Project Reference

Edit3: At some point this just started working. Edit3:在某些时候,这才开始起作用。 No clue why.不知道为什么。 Maybe it was a VS bug that got fixed?也许这是一个修复的VS错误?

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. Edit2:查看解决方案资源管理器中的 Analyzers 节点,我发现源生成器在我第一次打开程序时成功运行,然后它停止并且它生成的所有内容在我的代码只进行了一些更改后就消失了。

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.编辑:按照评论的建议调用Debugger.Launch()后,我可以确认生成器代码正在运行,并且源文本看起来与预期的完全一样。 But both the IDE and compiler still give errors as if the results aren't being included.但是 IDE 和编译器仍然会出现错误,就好像没有包含结果一样。

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.我的 NUnit 测试通过了,所以我知道实际的生成逻辑很好,但是准系统测试项目在 Visual Studio 中都无法编译并报告错误。 I'm using Visual Studio 2022 Preview 5.0, in case that matters.我正在使用 Visual Studio 2022 Preview 5.0,以防万一。

<--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;
}

Source generators target netstandard2.0 , your project targets net6.0 .源生成器针对netstandard2.0 ,您的项目针对net6.0 That isn't an issue when you use source generators via PackageReference .当您通过PackageReference使用源生成器时,这不是问题。

I think for ProjectReference to work in this case you need to add the SetTargetFramework meta data.认为ProjectReference在这种情况下工作,您需要添加SetTargetFramework元数据。

  <ItemGroup>
    <ProjectReference Include="..\MySourceGenerators\MySourceGenerators.csproj" 
                      OutputItemType="Analyzer"
                      SetTargetFramework="netstandard2.0"
                      ReferenceOutputAssembly="false"/>
  </ItemGroup>

That might work, sorry can't try right now.这可能有效,抱歉现在不能尝试。

Unfortunately, even in current VS 2022 (version 17.0.5) the support for this feature is somewhat limited.不幸的是,即使在当前的 VS 2022(版本 17.0.5)中,对该功能的支持也受到了一些限制。 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).正如您所注意到的,VS 显示生成的代码的正确状态的唯一时刻是在 VS 重新启动之后(不仅仅是解决方案加载/卸载,而是应用程序的完全重新启动)。 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.要强制生成器运行,我们需要强制重建“testproject.csproj”项目。 I'd use the command line from the "testproject" project directory: ' dotnet clean; dotnet build我会使用“testproject”项目目录中的命令行:' dotnet clean; dotnet build dotnet clean; dotnet build '.点网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 观看它们。 VS Code won't block open files, but any other notepad with a non-blocking read will suffice. VS Code 不会阻止打开的文件,但任何其他具有非阻塞读取的记事本就足够了。 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.这不是一个理想的解决方案,但此时它消除了生成器开发的主要痛苦:要查看代码生成的实际结果,我们不必重新启动 VS。

Example code draft for "generator.csproj" project: “generator.csproj”项目的示例代码草案:

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;
            while (!done)
            {
                int cnt = 0;
                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);
                }
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM