Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Source generator dependency unable to be resolved #52017

Closed
Turnerj opened this issue Mar 20, 2021 · 37 comments
Closed

Source generator dependency unable to be resolved #52017

Turnerj opened this issue Mar 20, 2021 · 37 comments

Comments

@Turnerj
Copy link

Turnerj commented Mar 20, 2021

Version Used:
MSBuild: 16.9.0.11203
.NET: 5.0.201
CSC: 3.900.21.12328 (comes with .NET 5.0.201 and used by dotnet build), 3.900.21.16010 (comes with Visual Studio and used at least by msbuild but probably VS too) - All the commits between these two versions
Visual Studio: 16.9.2

Steps to Reproduce / Actual Behaviour:
Sample project to recreate issue: https://github.com/Turnerj/SourceGeneratorDependencyTest

Using .NET CLI (like we use on a CI)

  1. Clean solution
  2. Build solution with command dotnet build
  3. Solution builds 🎉

Using MSBuild

  1. Clean solution
  2. Build solution with command msbuild
  3. Solution fails (see generator warning) 😕

Using Visual Studio

  1. Clean solution
  2. Build solution via Visual Studio
  3. Solution fails (see generator warning) 😞

Generator Warning

CSC : warning CS8784: Generator 'CustomSourceGenerator' failed to initialize. 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
'System.Text.Encodings.Web, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified.'

Expected Behavior:

Each method builds the project.

Notes:

The sample project is a minimum example of what we use in a full project: https://github.com/RehanSaeed/Schema.NET/
I'm following the guidance listed in this discussion about how to mark the package reference: #47517 (comment)
This is also seen on the Roslyn SDK C# samples: https://github.com/dotnet/roslyn-sdk/blob/0313c80ed950ac4f4eef11bb2e1c6d1009b328c4/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj#L13-L30
I've added the transient dependency (in my case System.Text.Encodings.Web) as per this comment in the same discussion: #47517 (reply in thread)

If I use the version 5.0.0 of System.Text.Encodings.Web, it does build with all methods. I've tried to debug binlogs of this but I haven't found anything explaining why it resolves correctly via dotnet build and not via Visual Studio (or msbuild).

I don't see why I can't specifically use version 5.0.1 of System.Text.Encodings.Web, hopefully you can work out what is going on!

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Mar 20, 2021
@jmarolf
Copy link
Contributor

jmarolf commented Mar 21, 2021

@Turnerj you must package all dependencies into your nuget package. System.Text.Encodings.Web for netstandard2.0 has a dependency on both System.Buffers and System.Memory (which in turn has a dependency on System.Numerics.Vectors). All of these types will be found implicitly on .NET Core and .NET 5 runtimes (i.e. dotnet build) but none of them exist on .NET Framework (both msbuild and Visual Studio).

The full set up dependencies you need to deploy are:

  • System.Text.Encodings.Web
    • System.Buffers 4.5.1
    • System.Memory 4.5.4
      • System.Numerics.Vectors, 4.4.0
  • System.Text.Json
    • Microsoft.Bcl.AsyncInterfaces, 5.0.0
    • System.Buffers, 4.5.1
    • System.Memory, 4.5.4
      • System.Numerics.Vectors, 4.4.0
    • System.Numerics.Vectors, 4.5.0
    • System.Runtime.CompilerServices.Unsafe, 5.0.0
    • System.Text.Encodings.Web, 5.0.0
    • System.Threading.Tasks.Extensions, 4.5.4

@Turnerj
Copy link
Author

Turnerj commented Mar 21, 2021

Thanks for the information @jmarolf - it sucks but is good to know why this is happening.

Is there any plans to changing this behaviour for dependencies used inside source generators? While source generators allow a lot of flexibility, some advanced functionality (like what we do in Schema.NET) is difficult to implement without having dependencies.

@Turnerj
Copy link
Author

Turnerj commented Mar 21, 2021

Edit: Ignore below, I re-read the actual warning message and missed the " or one of its dependencies" which would explain the message specifically.


Actually I have an additional question, while what you mentioned makes sense, why would the error message be specifically that it couldn't find System.Text.Encodings.Web instead of not finding one of those dependencies? Additionally how does that apply when going from version 5.0.0 for System.Text.Encodings.Web to 5.0.1?

Like with what you described, I would expect to see an warning saying it couldn't find one of those dependencies. Saying that it couldn't find System.Text.Encodings.Web because I've moved from 5.0.0 to 5.0.1, would seem like that its dependencies then did exist in .NET Framework (MSBuild/Visual Studio) - if that makes sense.

@jaredpar
Copy link
Member

Is there any plans to changing this behaviour for dependencies used inside source generators? While source generators allow a lot of flexibility, some advanced functionality (like what we do in Schema.NET) is difficult to implement without having dependencies.

There isn't a restriction here on having dependencies just a requirement that source generators deploy their dependencies with them.

Additionally how does that apply when going from version 5.0.0 for System.Text.Encodings.Web to 5.0.1?

That minor release added a lot more dependencies and that is reflecting in this issue you are hitting.

Actually I have an additional question, while what you mentioned makes sense, why would the error message be specifically that it couldn't find System.Text.Encodings.Web instead of not finding one of those dependencies?

This is a case where we are reflecting the error message that the .NET runtime provides when it attempts to load your DLL. Essentially we ask the runtime to load your generator and the runtime is providing the exception and the diagnostic here about the missing dependencies. It's not an error that we are directly providing.

Closing as this isn't an issue in the compiler. Happy to continue chatting here to help you work through this though and make sure your deployment is successful.

@chsienki

@jaredpar jaredpar added New Feature - Source Generators Source Generators and removed untriaged Issues and PRs which have not yet been triaged by a lead labels Mar 23, 2021
@Turnerj
Copy link
Author

Turnerj commented Mar 23, 2021

There isn't a restriction here on having dependencies just a requirement that source generators deploy their dependencies with them.

I guess my main issue here is that where one would normally just install the specific dependency they need, for a source generator I need to specify the entire tree of dependencies in my csproj file manually. While I'm sure a mountain of work has gone into making source generators work, this (and debugging source generators) make it feel somewhat incomplete.

While @jmarolf went out of their way to list all of the dependencies in my case (thank you!), basically I'm wondering why some combination of NuGet restore/compilation process isn't doing this automatically. NuGet restore already has to work out all the dependencies anyway however this current process of manually adding everything is quite intensive, especially for when package updates happen as I would need to re-check to see if any new dependencies are added.

So would there be any plans on changing this behaviour and having the compiler (or some other part of the build process) automatically handle this?

@Turnerj
Copy link
Author

Turnerj commented Mar 29, 2021

I've been looking into ways that a user can avoid manually specifying transient dependencies for a source generator and have something working with the following:

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

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  
  <ItemGroup Label="Package References">
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" />
    <PackageReference Include="System.Text.Json" Version="5.0.1" PrivateAssets="all" />
  </ItemGroup>

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

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

This effectively adds:

~\.nuget\packages\microsoft.bcl.asyncinterfaces\5.0.0\lib\netstandard2.0\Microsoft.Bcl.AsyncInterfaces.dll
~\.nuget\packages\microsoft.codeanalysis.common\3.9.0\lib\netstandard2.0\Microsoft.CodeAnalysis.dll
~\.nuget\packages\microsoft.codeanalysis.csharp\3.9.0\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.dll
~\.nuget\packages\system.buffers\4.5.1\ref\netstandard2.0\System.Buffers.dll
~\.nuget\packages\system.collections.immutable\5.0.0\lib\netstandard2.0\System.Collections.Immutable.dll
~\.nuget\packages\system.memory\4.5.4\lib\netstandard2.0\System.Memory.dll
~\.nuget\packages\system.numerics.vectors\4.5.0\ref\netstandard2.0\System.Numerics.Vectors.dll
~\.nuget\packages\system.reflection.metadata\5.0.0\lib\netstandard2.0\System.Reflection.Metadata.dll
~\.nuget\packages\system.runtime.compilerservices.unsafe\5.0.0\ref\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll
~\.nuget\packages\system.text.encoding.codepages\4.5.1\lib\netstandard2.0\System.Text.Encoding.CodePages.dll
~\.nuget\packages\system.text.encodings.web\5.0.0\lib\netstandard2.0\System.Text.Encodings.Web.dll
~\.nuget\packages\system.text.json\5.0.1\lib\netstandard2.0\System.Text.Json.dll
~\.nuget\packages\system.threading.tasks.extensions\4.5.4\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll

This isn't perfect as it adds a few other libraries against TargetPathWithTargetPlatformMoniker that shouldn't be there but with some more time processing the available data in the custom target, it would be possible to have this configured without any issues.

My question though goes back to: Why isn't something like this done automatically for projects with source generators anyway?

Honestly, I don't know really if this is much a Roslyn issue now or a default targets issue with MSBuild or .NET itself. I want to push for having this automatically handled by some part of the build process so users don't need to bother with manually configuring it.

Any advice on where this would be best to further discuss would be appreciated!

@Lonli-Lokli
Copy link

@jaredpar can you comment on that?

@kamronbatman
Copy link

kamronbatman commented May 18, 2021

What @Turnerj recommended did not work for me:

Exception was of type 'FileLoadException' with message 'Could not load file or assembly 'System.Text.Json, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. Could not find or load a specific file. (0x80131621)'

This is a source generator used against a .net 5 project.

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <LangVersion>preview</LangVersion>
        <BuildOutputTargetFolder>analyzers</BuildOutputTargetFolder>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.9.0" />
        <PackageReference Include="Humanizer.Core" Version="2.10.1" GeneratePathProperty="true" PrivateAssets="all" />
        <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" GeneratePathProperty="true" PrivateAssets="all" />
        <PackageReference Include="System.Buffers" Version="4.5.1" GeneratePathProperty="true" PrivateAssets="all" />
        <PackageReference Include="System.Memory" Version="4.5.4" GeneratePathProperty="true" PrivateAssets="all" />
        <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" GeneratePathProperty="true" PrivateAssets="all" />
        <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" GeneratePathProperty="true" PrivateAssets="all" />
        <PackageReference Include="System.Text.Encodings.Web" Version="5.0.1" GeneratePathProperty="true" PrivateAssets="all" />
        <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" GeneratePathProperty="true" PrivateAssets="all" />
        <PackageReference Include="System.Text.Json" Version="5.0.2" GeneratePathProperty="true" PrivateAssets="all" />
    </ItemGroup>
    
    <PropertyGroup>
        <GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
    </PropertyGroup>
    
    <Target Name="GetDependencyTargetPaths">
        <ItemGroup>
            <TargetPathWithTargetPlatformMoniker Include="$(PKGHumanizer_Core)\lib\netstandard2.0\Humanizer.dll" IncludeRuntimeDependency="false" />
            <TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Json)\lib\netstandard2.0\System.Text.Json.dll" IncludeRuntimeDependency="false" />
            <TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Encodings_Web)\lib\netstandard2.0\System.Text.Encodings.Web.dll" IncludeRuntimeDependency="false" />
            <TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Buffers)\lib\netstandard2.0\System.Buffers.dll" IncludeRuntimeDependency="false" />
            <TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Memory)\lib\netstandard2.0\System.Memory.dll" IncludeRuntimeDependency="false" />
            <TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Numerics_Vectors)\lib\netstandard2.0\System.Numerics.Vectors.dll" IncludeRuntimeDependency="false" />
            <TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Runtime_CompilerServices_Unsafe)\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll" IncludeRuntimeDependency="false" />
            <TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Threading_Tasks_Extensions)\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll" IncludeRuntimeDependency="false" />
            <TargetPathWithTargetPlatformMoniker Include="$(PKGMicrosoft_Bcl_AsyncInterfaces)\lib\netstandard2.0\Microsoft.Bcl.AsyncInterfaces.dll" IncludeRuntimeDependency="false" />
        </ItemGroup>
    </Target>
</Project>

@Turnerj
Copy link
Author

Turnerj commented May 18, 2021

@kamronbatman I've found recently my solution has some issues - I don't think I've got the right combination of AfterTargets specified OR that the data for ResolvedCompileFileDefinitions isn't always set. To work around it, I've found forcing a full solution rebuild immediately after it fails to build to works around this problem.

I may look at other solutions to automate this though for now, I'll put up with the occasional error during builds. I still believe the compiler should be doing this for us - tempted to raise it as another issue to at least get the ball rolling as it seems the compiler team isn't looking at this thread anymore.

@kamronbatman
Copy link

I literally can't get this to work no matter what I do. I'll have to switch to another Json package and lick my wounds. This is not worth my time and I wouldn't expect anyone to be an expert in the inner workings of msbuild/csproj.

@jaredpar
Copy link
Member

@kamronbatman

Exception was of type 'FileLoadException' with message 'Could not load file or assembly 'System.Text.Json, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. Could not find or load a specific file. (0x80131621)'

Do you have the binlog where this error was produced? Based on the error my expectation would be that you didn't pass this dependency via /analyzer: hence the compiler could not locate the file.

This is not worth my time and I wouldn't expect anyone to be an expert in the inner workings of msbuild/csproj.

I'm sorry you're having difficulty here, we realize this is a rough area. Unfortunately at the moment but unfortunately compilation execution is not an easy story right now. The compiler executes on a variety of runtimes and frameworks right now and analyzers / generators have to support all of them. Dependencies are challenging to reconcile easily with these restrictions and what does / does not need to be packaged is not easily automatable.

In the future when the compiler moves to .NET Core only this will get easier but there is still a bit of time before that happens.

@kamronbatman
Copy link

kamronbatman commented May 18, 2021

@jaredpar I don't pass anything via /analyzer
I am simply building or publishing the project that is using the source generator (which is in the same solution). I didn't read anywhere about passing dependencies via a /analyzer so I am not even sure how to do it. Can you show me an example?

As for the binlog, I'll generate that later today.

To add to the frustration, I thought we were done with old school msbuild style passing of dependencies with the newest csproj spec. 😆

Note for anyone having this issue too:
Using v4.7.2 works without going through all of this headache... How/why anyone is supposed to know that is also frustrating.

@jaredpar
Copy link
Member

I didn't read anywhere about passing dependencies via a /analyzer so I am not even sure how to do it. Can you show me an example?

All DLLs in the same folder as the analyzer will essentially be passed via /analyzer automatically. It's essentially implicit. Generally if the package has the right dependencies it will just work.

This is why I was asking for the binlog though. It will make it apparent what is happening here.

@Turnerj
Copy link
Author

Turnerj commented May 19, 2021

Hey @jaredpar - could I get your feedback on my "trick" earlier in the thread? That is, using ResolvedCompileFileDefinitions and passing it to TargetPathWithTargetPlatformMoniker.

More importantly than that for me though, is some sort of automated behaviour by the compiler for adding transient dependencies to TargetPathWithTargetPlatformMoniker something the compiler team would even consider? Like any package references with PrivateAssets="all" just automatically having all dependencies added to TargetPathWithTargetPlatformMoniker? If so, should I raise that as a separate issue so it can be tracked?

@jaredpar
Copy link
Member

That level of MSBuild magic is a bit beyond me. @chsienki can likely better say.

@chsienki
Copy link
Contributor

@Turnerj

To work around it, I've found forcing a full solution rebuild immediately after it fails to build to works around this problem.

The ResolvePackageDependenciesForBuild target is involved in restore so won't occur in future builds which is probably the behaviour you're seeing. I (think) you can substitute it for ResolvePackageAssets and it should fix it.

This isn't perfect as it adds a few other libraries against TargetPathWithTargetPlatformMoniker that shouldn't be there but with some more time processing the available data in the custom target, it would be possible to have this configured without any issues.

As you said above however, the trouble with this approach is it doesn't discriminate and basically adds _everything _ as an analyzer dependency. For small projects or things under your control that might be ok, but will slow the compilation as the list gets bigger, and worse potentially lead to really hard to debug version mismatches when you start adding more dependencies.

More importantly than that for me though, is some sort of automated behaviour by the compiler for adding transient dependencies to TargetPathWithTargetPlatformMoniker something the compiler team would even consider

Its definitely on our radar, and something that has come up more since the introduction of source generators. The problem existed with analyzers too, but authors were just less likely to use dependencies. We've been discussing a range of steps that would improve all these scenarios. Most of that work is going to be outside of Roslyn (though we'll obviously help) so you're probably best opening an issue in http://github.com/dotnet/sdk to get the ball rolling.

@chsienki
Copy link
Contributor

Hmm, this has got me thinking though. @jmarolf it looks like ResolvePackageAssets is explicitly spitting out the analyzers, which the compiler consumes. Presumably at this point (or earlier on when we write the cache file) we have access to the assets.json which describe the analyzer dependencies?

Can we just hook that task to return the dependencies as part of the analyzers directly (or even better, as an AnalyzerDependencies item group that we can merge later on to make debugging easier?)

@Turnerj
Copy link
Author

Turnerj commented May 21, 2021

Thanks for the information @chsienki ! I've raised an issue on the SDK repo here: dotnet/sdk#17775

@JeremyMorton
Copy link

JeremyMorton commented Jun 3, 2021

I am seeing this when trying to build a project that references my generator via ProjectReference:

CSC : warning CS8785: Generator 'ReadonlyStructGenerator' 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 'Microsoft.CodeAnalysis.Workspaces, Version=3.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the file specified.'

I have the generator pack that dependency up in its nupkg, but this isn't using the package to run the generator. How do I get this dependency available for the generator when it is used via ProjectReference?

ETA: I also have the dependencies written to the generator project's output directory:

    <AdditionalFiles Include="$(PkgMicrosoft_CodeAnalysis_CSharp_Workspaces)\lib\netstandard2.0\*.dll" CopyToOutputDirectory="PreserveNewest" />
    <AdditionalFiles Include="$(PkgMicrosoft_CodeAnalysis_Workspaces_Common)\lib\netstandard2.0\*.dll" CopyToOutputDirectory="PreserveNewest" />

@JeremyMorton
Copy link

JeremyMorton commented Jun 3, 2021

Using the clue of:

Based on the error my expectation would be that you didn't pass this dependency via /analyzer: hence the compiler could not locate the file.

I changed the project that is referencing the generator project to reference the depended upon assembly as an analyzer, and that made the dependency work.

    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.9.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="3.9.0" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

I have tried to work down the dependency graph from this, but have had no luck in getting past this message in Visual Studio:

CSC : warning CS8785: Generator 'ReadonlyStructGenerator' 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 'System.Composition.TypedParts, Version=1.0.31.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.'

And dotnet build still has this message:

CSC : warning CS8785: Generator 'ReadonlyStructGenerator' 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 'Microsoft.CodeAnalysis.Workspaces, Version=3.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the file specified.'

Does P2P just not support the generator having any dependencies, Even other Microsoft.CodeAnalysis.* assemblies? I would really like my generated code to be formatted.

@aktxyz
Copy link

aktxyz commented Jun 5, 2021

trying to use System.Text.Json in a source generator is failing for me also ... going to start trying the stuff in this thread to see if I can get past it ... and things were going so well with source generators ...

<PackageReference Include="System.Text.Json" Version="5.0.2" />

@kamronbatman
Copy link

trying to use System.Text.Json in a source generator is failing for me also ... going to start trying the stuff in this thread to see if I can get past it ... and things were going so well with source generators ...

<PackageReference Include="System.Text.Json" Version="5.0.2" />

I was able to get it to work using 5.0.0 when consumed by a .NET 5 project.

@aktxyz
Copy link

aktxyz commented Jun 5, 2021

woohoo ! thanks !

at first I switched to to 5.0.0 and was still getting the same exception when calling JsonSerializer.Serialize

<PackageReference Include="System.Text.Json" Version="5.0.0" />

BUT ... then I decided to try adding this incantation to the csproj and BOOM it worked.

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

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

Now, should I spend my day learning about the above stuff (TargetPathWithTargetPlatformMoniker/etc) ...

or actually making progress on the project ... hmm ... project it is !

@HelloKitty
Copy link

@aktxyz I found that referencing that did not work. Maybe this depends on compiler or VS version?

@aktxyz
Copy link

aktxyz commented Jun 6, 2021

I am on the latest vs 16.10.0 ... and the relevant sections of my csproj look like this ... hope this helps

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>preview</LangVersion>
    <NoWarn>1701;1702;1591;1998;CS0162;CS0168;CS0618;EF1001</NoWarn>
    <RootNamespace>hmm</RootNamespace>
    <LangVersion>8.0</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Humanizer.Core" Version="2.10.1" />
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
    <PackageReference Include="System.Text.Json" Version="5.0.0" />
  </ItemGroup>

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

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

@HelloKitty
Copy link

HelloKitty commented Jun 7, 2021

@aktxyz Thank you for that information. My situation must be different since that solution doesn't fully work for me. In the case of locally referencing an analyzer that can work but when generating a Nuget package it seems required to manually specify transitient dependencies.

Additionally, my use of the AdhocWorkspace type ends up meaning that compilation of the source generator fails without also adding:

<PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="3.9.0-2.final">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

This is such a confusing feature of C#. It seems very difficult to use source generators due to all these issues. Hopefully this is all resolved one day by Microsoft.

Source Generators also have different behavior with assembly loading/redirection when generating nugets vs local project references. It's actually an absolute headache really.

@HavenDV
Copy link

HavenDV commented Feb 20, 2022

I've been looking into ways that a user can avoid manually specifying transient dependencies for a source generator and have something working with the following:

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

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

@Turnerj Today I came across that one of my transient dependencies was .Net Standard 1.0. And this is a pain, because it is a dependency on https://www.nuget.org/packages/NETStandard.Library/1.6.1

Your solution didn't work because all projects that include a library that depends on NETStandard.Library 1.6.1 showed a lot of errors due to library version conflicts at build time.
But it made me take a step further than your solution and wonder why not just do this:

<Target Name="AddGenerationTimeReferences" AfterTargets="ResolvePackageDependenciesForBuild">
    <ItemGroup>
      <None Include="@(ResolvedCompileFileDefinitions)" Pack="true" PackagePath="analyzers/dotnet/cs" />
    </ItemGroup>
</Target>

And it just works :)

@HavenDV
Copy link

HavenDV commented Feb 20, 2022

For my case, it created the correct NuGet package with all the required transient packages included in the analyzers\dotnet\cs folder.

<ItemGroup>
    <PackageReference Include="NSwag.CodeGeneration.CSharp" Version="13.15.9" PrivateAssets="all" />
    <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.6.8" PrivateAssets="all" />
    <PackageReference Include="NSwag.Core.Yaml" Version="13.15.9" PrivateAssets="all" />
  </ItemGroup>

image

Calculating this manually would be very painful.

There is a slight overhead here in the form of non-private libraries, but I think this can be excluded explicitly if you need it.

@Turnerj
Copy link
Author

Turnerj commented Feb 20, 2022

But it made me take a step further than your solution and wonder why not just do this:

<Target Name="GetDependencyTargetPaths" AfterTargets="ResolvePackageDependenciesForBuild">
    <ItemGroup>
      <None Include="@(ResolvedCompileFileDefinitions)" Pack="true" PackagePath="analyzers/dotnet/cs" />
    </ItemGroup>
</Target>

And it just works :)

Glad you got something working for yourself @HavenDV ! In my cases with source generators, they have been local projects and are only to aid the build of a different local project rather than packing the dependencies.

Still hoping for some movement on dotnet/sdk#17775 to automate all of this though AFAIK there are various blockers that are preventing progress. Fingers crossed for a .NET 7 fix!

@HavenDV
Copy link

HavenDV commented Feb 20, 2022

I would appreciate it if you would complete your article with this workaround for NuGet. I think it would save time for some people like me. I manually maintained a large number of transient dependencies for about a year.

@cl0ckt0wer
Copy link

It would be great if someone could update the section on nuget dependencies in the cookbook to include this:
https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md

@FrankSzendzielarz
Copy link

Hmm. None of the above solutions are working for me. Newtonsoft is file not found, no matter what I do.

@FrankSzendzielarz
Copy link

Spoke too soon. What fixed it for me was explicitly adding Newtonsoft into the nupkg

<None Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />

@harvinders
Copy link

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

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

After adding I was able to debug and see that my source generator worked. Sort off, after the control exits the Execute() I get an exception. My consuming project is net6.0-windows10.0.22621.0 and already has System.Text.Json.SourceGeneration Analyzer. Is it because the above csproj is also adding Microsoft.CodeAnalysis.CSharp, just the way it adds System.Text.Json?

Exception thrown: 'System.InvalidCastException' in PropertyChanged.Fody.Analyzer.dll
Exception thrown: 'Microsoft.CodeAnalysis.UserFunctionException' in Microsoft.CodeAnalysis.dll
Exception thrown: 'Microsoft.CodeAnalysis.UserFunctionException' in Microsoft.CodeAnalysis.dll
Exception thrown: 'System.InvalidCastException' in System.Text.Json.SourceGeneration.dll
An unhandled exception of type 'System.InvalidCastException' occurred in System.Text.Json.SourceGeneration.dll
[A]Microsoft.CodeAnalysis.CSharp.Syntax.CompilationUnitSyntax cannot be cast to [B]Microsoft.CodeAnalysis.CSharp.Syntax.CompilationUnitSyntax. Type A originates from 'Microsoft.CodeAnalysis.CSharp, Version=4.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' in the context 'Default' at location 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Roslyn\Microsoft.CodeAnalysis.CSharp.dll'. Type B originates from 'Microsoft.CodeAnalysis.CSharp, Version=4.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' in the context 'LoadFrom' at location 'C:\Users\myuser\.nuget\packages\microsoft.codeanalysis.csharp\4.4.0\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.dll'.

@MeltyPlayer
Copy link

This bug has been open for nearly two years, are there any official updates on this issue? All of the solutions above seem to be unreliable workarounds--I haven't personally gotten them to work.

@kamronbatman
Copy link

I haven't had this issue since .net 6.0. You just have to use the libraries that are strictly compatible with the source generator analyzer framework.

@EdLichtman
Copy link

I've been having the issue myself @kamronbatman . I'm having trouble tracking what the TargetPathWithTargetPlatformMoniker actually does. In some cases it seems to work, in others, it doesn't.

I'm creating a custom NuGet repo on disk, and I have a project called "My.Analyzers.Extensions"

I have another project, which is an analyzer, called "My.Microservice.Builder.Analyzers" which references System.Text.Json, Microsoft.Bcl.AsyncInterfaces, and My.Analyzers.Extensions.

When I add
, then my My.Analyzer.Extensions dll has the same FileNotFound error.

However I have a workaround that seems to work, that uses the tag, which effectively packages a dll in the package. I'm concerned that it means that if the parent library that inherits this package has a different version, this analyzer will NOT use the different version, but will continue to use the packaged version. Is that Works as Designed?

Here's my workaround that only packages what you need. You just need to add 'Pack="true" ' to the PackageReference.

It uses a combination of @Turnerj and @kzu's blog post:
https://til.cazzulino.com/msbuild/how-to-include-package-reference-files-in-your-nuget-package

    <Target 
      Name="AddGenerationTimeReferences" 
      BeforeTargets="GetTargetPathWithTargetPlatformMoniker"
      AfterTargets="ResolvePackageDependenciesForBuild"
      Outputs="%(ResolvedCompileFileDefinitions.NuGetPackageId)">
        <ItemGroup>
            <NuGetPackageId Include="@(ResolvedCompileFileDefinitions -> '%(NuGetPackageId)')" />
        </ItemGroup>
        <PropertyGroup>
            <NuGetPackageId>@(NuGetPackageId -&gt; Distinct())</NuGetPackageId>
        </PropertyGroup>
        <ItemGroup>
            <PackageReferenceDependency Include="@(PackageReference -> WithMetadataValue('Identity', $(NuGetPackageId)))"/>
        </ItemGroup>
        <ItemGroup>
          <PackableReferenceDependency Include="@(PackageReferenceDependency -> WithMetadataValue('Pack', 'true'))"/>
        </ItemGroup>
        <PropertyGroup>
          <PackableReferenceDependency>@(PackableReferenceDependency -&gt; Distinct())</PackableReferenceDependency>
        </PropertyGroup>
        <ItemGroup>
            <ResolvedPackableCompileFileDefinitions
              Include="@(ResolvedCompileFileDefinitions -&gt; WithMetadataValue('NuGetPackageId', '$(PackableReferenceDependency)'))" />
        </ItemGroup>
        <ItemGroup>
            <!-- <TargetPathWithTargetPlatformMoniker Include="@(ResolvedPackableCompileFileDefinitions)" IncludeRuntimeDependency="false" /> -->
            <None Include="@(ResolvedPackableCompileFileDefinitions)" Pack="true" PackagePath="analyzers/dotnet/cs" />
        </ItemGroup>
    </Target>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests