Visual Studio 中未加载源生成器依赖项
Source Generators dependencies not loaded in Visual Studio
我正在使用源代码生成器,但我遇到了依赖问题:
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 'Flurl.Http, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.'
关于如何打包依赖到nuget的资料很多,但是我直接引用analyzer项目是这样的:
<ProjectReference Include="SG.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
在分析器项目中,我添加了 <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
并且所有依赖项都在输出目录中可用,但 VS 没有使用该目录 - 它使用 AppData\Local\Temp\VBCSCompiler\AnalyzerAssemblyLoader\[...]
而是只复制一个 DLL 到那里。
可以做些什么来实现这一目标?
应该 工作的方式在 source-generators cook-book 中有概述,他们的例子是:
<Project>
<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- Generates a package at build -->
<IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency -->
</PropertyGroup>
<ItemGroup>
<!-- Take a private dependency on Newtonsoft.Json (PrivateAssets=all) Consumers of this generator will not reference it.
Set GeneratePathProperty=true so we can reference the binaries via the PKGNewtonsoft_Json property -->
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" PrivateAssets="all" GeneratePathProperty="true" />
<!-- Package the generator in the analyzer directory of the nuget package -->
<None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<!-- Package the Newtonsoft.Json dependency alongside the generator assembly -->
<None Include="$(PkgNewtonsoft_Json)\lib\netstandard2.0\*.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Project>
但是,根据我的经验,这仍然不可靠或稳健;这是公认的,我的印象是改善这种体验是未来计划的一部分,但是:现在,老实说,最好只是不使用核心框架之外的依赖项和已加载的 Roslyn 库。如果您的依赖项与 analyzer/generator 在同一个 repo 中,您可以在构建过程中简单地吸收代码,for example from here:
<ItemGroup>
<!-- compile what we need from protobuf-net directly; package refs cause pure pain in anaylizers-->
<Compile Include="../protobuf-net.Core/**/*.cs" Link="protobuf-net.Core"/>
<Compile Remove="../protobuf-net.Core/obj/**/*.cs" />
<Compile Include="../protobuf-net.Reflection/**/*.cs" Link="protobuf-net.Reflection"/>
<Compile Remove="../protobuf-net.Reflection/obj/**/*.cs" />
</ItemGroup>
我找到了通过一些小技巧使它或多或少可靠地工作的方法。
在此之前我也尝试过ILMerge,但是没有用(缺少方法异常)。
解决方案:
首先,我像这样在源代码生成器程序集中嵌入依赖项:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" Visible="false" />
</ItemGroup>
然后我为 AppDomain
(生成器 class 中的静态构造函数)创建了 AssemblyResolve
处理程序,如下所示:
AppDomain.CurrentDomain.AssemblyResolve += (_, args) =>
{
AssemblyName name = new(args.Name);
Assembly loadedAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().FullName == name.FullName);
if (loadedAssembly != null)
{
return loadedAssembly;
}
string resourceName = $"Namespace.{name.Name}.dll";
using Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
if (resourceStream == null)
{
return null;
}
using MemoryStream memoryStream = new MemoryStream();
resourceStream.CopyTo(memoryStream);
return Assembly.Load(memoryStream.ToArray());
};
这个问题现在有了明确的答案。 Source Generator Samples 包含一个具有所需设置的示例 .csproj
。
我用它制作了一个带有 Sprache 的源代码生成器,效果很好。
在此处粘贴示例.csproj
以供参考:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftNetCompilersToolsetVersion)" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<!-- Generator dependencies -->
<PackageReference Include="CsvTextFieldParser" Version="1.2.2-preview" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="Handlebars.Net" Version="1.10.1" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn)</GetTargetPathDependsOn>;GetDependencyTargetPaths
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGCsvTextFieldParser)\lib\netstandard2.0\CsvTextFieldParser.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGHandlebars_Net)\lib\netstandard2.0\Handlebars.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
</Project>
我正在使用源代码生成器,但我遇到了依赖问题:
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 'Flurl.Http, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.'
关于如何打包依赖到nuget的资料很多,但是我直接引用analyzer项目是这样的:
<ProjectReference Include="SG.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
在分析器项目中,我添加了 <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
并且所有依赖项都在输出目录中可用,但 VS 没有使用该目录 - 它使用 AppData\Local\Temp\VBCSCompiler\AnalyzerAssemblyLoader\[...]
而是只复制一个 DLL 到那里。
可以做些什么来实现这一目标?
应该 工作的方式在 source-generators cook-book 中有概述,他们的例子是:
<Project>
<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- Generates a package at build -->
<IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency -->
</PropertyGroup>
<ItemGroup>
<!-- Take a private dependency on Newtonsoft.Json (PrivateAssets=all) Consumers of this generator will not reference it.
Set GeneratePathProperty=true so we can reference the binaries via the PKGNewtonsoft_Json property -->
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" PrivateAssets="all" GeneratePathProperty="true" />
<!-- Package the generator in the analyzer directory of the nuget package -->
<None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<!-- Package the Newtonsoft.Json dependency alongside the generator assembly -->
<None Include="$(PkgNewtonsoft_Json)\lib\netstandard2.0\*.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Project>
但是,根据我的经验,这仍然不可靠或稳健;这是公认的,我的印象是改善这种体验是未来计划的一部分,但是:现在,老实说,最好只是不使用核心框架之外的依赖项和已加载的 Roslyn 库。如果您的依赖项与 analyzer/generator 在同一个 repo 中,您可以在构建过程中简单地吸收代码,for example from here:
<ItemGroup>
<!-- compile what we need from protobuf-net directly; package refs cause pure pain in anaylizers-->
<Compile Include="../protobuf-net.Core/**/*.cs" Link="protobuf-net.Core"/>
<Compile Remove="../protobuf-net.Core/obj/**/*.cs" />
<Compile Include="../protobuf-net.Reflection/**/*.cs" Link="protobuf-net.Reflection"/>
<Compile Remove="../protobuf-net.Reflection/obj/**/*.cs" />
</ItemGroup>
我找到了通过一些小技巧使它或多或少可靠地工作的方法。
在此之前我也尝试过ILMerge,但是没有用(缺少方法异常)。
解决方案:
首先,我像这样在源代码生成器程序集中嵌入依赖项:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" Visible="false" />
</ItemGroup>
然后我为 AppDomain
(生成器 class 中的静态构造函数)创建了 AssemblyResolve
处理程序,如下所示:
AppDomain.CurrentDomain.AssemblyResolve += (_, args) =>
{
AssemblyName name = new(args.Name);
Assembly loadedAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().FullName == name.FullName);
if (loadedAssembly != null)
{
return loadedAssembly;
}
string resourceName = $"Namespace.{name.Name}.dll";
using Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
if (resourceStream == null)
{
return null;
}
using MemoryStream memoryStream = new MemoryStream();
resourceStream.CopyTo(memoryStream);
return Assembly.Load(memoryStream.ToArray());
};
这个问题现在有了明确的答案。 Source Generator Samples 包含一个具有所需设置的示例 .csproj
。
我用它制作了一个带有 Sprache 的源代码生成器,效果很好。
在此处粘贴示例.csproj
以供参考:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftNetCompilersToolsetVersion)" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<!-- Generator dependencies -->
<PackageReference Include="CsvTextFieldParser" Version="1.2.2-preview" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="Handlebars.Net" Version="1.10.1" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn)</GetTargetPathDependsOn>;GetDependencyTargetPaths
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGCsvTextFieldParser)\lib\netstandard2.0\CsvTextFieldParser.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGHandlebars_Net)\lib\netstandard2.0\Handlebars.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
</Project>