2

Hi I'm trying to write a SourceGenerator that uses the Humanizer package (This package is only needed while generating code and should not be added as a reference in the project that uses the SourceGenerator)

So I have the following files:

TestSourceGenerator\TestSourceGenerator.csproj

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <LangVersion>latest</LangVersion>
        <Nullable>enable</Nullable>
        <IncludeBuildOutput>false</IncludeBuildOutput>
        <IsRoslynComponent>true</IsRoslynComponent>
        <ImplicitUsings>enable</ImplicitUsings>
        <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput</TargetsForTfmSpecificContentInPackage>
        <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
    </PropertyGroup>
    
    <ItemGroup>
        <PackageReference Include="Humanizer.Core" Version="2.14.1" PrivateAssets="all" GeneratePathProperty="true" />
        <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.1.0" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CSharp" Version="4.7.0" PrivateAssets="all" />
    </ItemGroup>

    <Target Name="_AddAnalyzersToOutput">
        <ItemGroup>
            <TfmSpecificPackageFile Include="$(OutputPath)\$(AssemblyName).dll" PackagePath="analyzers/dotnet/cs" Pack="true" Visible="false" />
            <TfmSpecificPackageFile Include="$(OutputPath)\Humanizer.dll" PackagePath="analyzers/dotnet/cs" Pack="true" Visible="false" />
        </ItemGroup>
    </Target>

</Project>

TestSourceGenerator\SourceGenerator.cs

using System.Text;
using Humanizer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace TestSourceGenerator;

[Generator]
public class SourceGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {

        var builder = new StringBuilder(1024);

        builder.AppendLine(@$"using System;
namespace {context.Compilation.Assembly.ContainingNamespace?.Name ?? context.Compilation.Assembly.Name};
static class AssemblyInformation {{
    public static string Name = ""{context.Compilation.AssemblyName}"";
    public static string HumanizedName = ""{context.Compilation.AssemblyName.Humanize()}"";
}}
");

        context.AddSource("AssemblyInformation.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
    }

    public void Initialize(GeneratorInitializationContext context)
    {

    }
}

TestSourceGenerator\Properties\launchSettings.json

{
  "profiles": {
    "Profile 1": {
      "commandName": "DebugRoslynComponent",
      "targetProject": "..\\ConsoleApp1\\ConsoleApp1.csproj"
    }
  }
}

ConsoleApp1\ConsoleApp1.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\TestSourceGenerator\TestSourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true"  />
  </ItemGroup>

</Project>

ConsoleApp1\Program.cs

Console.WriteLine("Name: " + AssemblyInformation.Name);
Console.WriteLine("Name: " + AssemblyInformation.HumanizedName);

Every thing works as expected when I debug the SourceGenerator using Profile 1. But if I try to compile ConsoleApp1 it complains that it can't find the Humanizer even when its there

Generator 'SourceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'FileNotFoundException' with message 'Could not load file or assembly 'Humanizer, Version=2.14.0.0, Culture=neutral, PublicKeyToken=979442b78dfc278e' or one of its dependencies. The system cannot find the file specified.'

What am I missing? I have verified that Humanizer is part of the nuget file when I pack the SourceGenerator.

A copy of the code can be found here: https://github.com/AnderssonPeter/TestSourceGenerator

11
  • github.com/dotnet/roslyn/blob/main/docs/features/… Commented Nov 28, 2022 at 15:42
  • @canton7 i have applied the changes according to that post, but it yields the same error, github.com/AnderssonPeter/TestSourceGenerator/tree/canton7 Commented Nov 28, 2022 at 15:54
  • I don't know whether it matters, but the PrivateAssets attributes differ Commented Nov 28, 2022 at 16:07
  • Yeah, it works if you remove those. Make sure to restart VS -- it caches SGs very aggressively (you'll want to develop your SG using unit tests, which has the benefit of avoiding the problem) Commented Nov 28, 2022 at 16:27
  • @canton7 I'm somewhat confused where do my PrivateAssets differ? In my TestSourceGenerator.csproj I have Humanizer.Core with PrivateAssets="all" just like they have in the cookbook? But just for the fun I removed all PrivateAssets="all" cleaned my solution, restarted vs and ran again, same error :( Commented Nov 28, 2022 at 16:45

1 Answer 1

2

I know this question is a bit stale, but as it still pops-up on Google a lot I would like to show the current solution I have that combines hints that were given in the comments.

As far as I can tell adding a dependency like this works both when referencing the generator in the same solution as via a Nuget package, though I haven't tested that scenario extensively.

The .csproj of my generator depends on the Tomlyn library / Tomlyn.dll and I've added it like this:

    <!-- Generation time dependencies need special treatment 
        see: https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md#use-functionality-from-nuget-packages
        and: https://github.com/dotnet/roslyn-sdk/blob/main/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj
    -->
    <ItemGroup>
        <PackageReference Include="Tomlyn" Version="0.17.0" GeneratePathProperty="true" PrivateAssets="all" />
        <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />      
        <None Include="$(PKGTomlyn)\lib\netstandard2.0\*.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
    </ItemGroup>

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

    <Target Name="GetDependencyTargetPaths">
        <ItemGroup>                     
            <TargetPathWithTargetPlatformMoniker Include="$(PKGTomlyn)\lib\netstandard2.0\Tomlyn.dll" IncludeRuntimeDependency="false" />
        </ItemGroup>
    </Target>

Note the GeneratePathProperty="true" and PrivateAssets="all" on the PackageReference.

The two Nones in the item group are from advice here that should make this dependency available during generation time ONLY when packaging the generator as a Nuget package. While the second PropertyGroup and Target should use the example from here to make sure the Tomlyn.dll file is in the right directory when using the generator in the same solution.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.