Source generators that has package dependencies require some extra directives for Roslyn to load them properly, which differs depending if the generator is consumed as a package reference or a project reference.
Consider a source generator, MySourceGenerator, with a dependency to a fictive .NET Standard 2.0 package Foo.Bar. The package reference directive would look something like:
<ItemGroup>
<PackageReference Include="Foo.Bar" Version="1.2.3" PrivateAssets="all" GeneratePathProperty="true" />
</ItemGroup>
PrivateAssets="all" means that the consuming project doesn’t take a direct dependency, it’s not the consuming project that needs the dependency per se, it’s the compiler when running the source generator.
GeneratePathProperty="true" enables MSBuild targets to get the path to the corresponding assembly, which usually is within a global folder. The path will be used in both scenarios to reference the dependency.
Project Reference
A project consuming a source generator as a project reference would have a directive like:
<ItemGroup>
<ProjectReference Include="MySourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
We use the path (generated by GeneratePathProperty) to produce target path references to the dependency’s assembly to let MSBuild know about the dependency:
<PropertyGroup> <GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn> </PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PkgFooBar)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
IncludeRuntimeDependency="false" states that this is not a runtime dependency but a compile time dependency, which avoids it being copied to output.
Note that all transient dependencies used also need to be referenced!

Package Reference
A project consuming a source generator as a package reference would have a directive like:
<ItemGroup>
<PackageReference Include="MySourceGenerator" Version="x.y.z" PrivateAssets="all" />
</ItemGroup>
There is no MSBuild consuming the source generator project here, instead dependencies need to be packed into the source generator’s NuGet package. We can use the path in a similar fashion to reference the dependency to include it in the package using this directive:
<ItemGroup>
<None Include="$(PkgFooBar)\lib\netstandard2.0\*.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
analyzers/dotnet/cs is a special path in a NuGet package where analyzers and source generators are located. Their dependencies can be included at the same path.
This scenario is actually pretty well documented in the Source Generators Cookbook.
Don’t forget transient dependencies, as described above.
Generated Code Dependencies
We’ve covered how to handle dependencies the source generator it self depends on, but how about dependencies that the code it generates depend on? There is currently no way to define such package reference, unless it targets the same framework as the source generator, meaning .NET Standard 2.0. PackageReference directives must be compatible with the project where they are declared. This means that required dependencies must be referenced manually by the consumer.
It can be a good idea to check that the expected dependency is installed in the target assembly (context.Compilation.ReferencedAssemblyNames) from the source generator and produce a diagnostics message if it is missing.
Conclusion
Handling dependencies in source generators is clonky and requires quite a lot of manually added directives. Hopefully we’ll see a more streamlined solution in the future where Roslyn deals with this automatically.
Leave a Comment











