Зависимости генераторов исходного кода не загружены в 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, но я напрямую ссылаюсь на проект анализатора следующим образом:
<ProjectReference Include="SG.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
В проекте анализатора я добавил
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
и все зависимости доступны в выходном каталоге, но VS не использует этот каталог - он использует
AppData\Local\Temp\VBCSCompiler\AnalyzerAssemblyLoader\[...]
вместо этого он копирует туда только одну DLL.
Что можно сделать, чтобы это заработало?
4 ответа
Как это должно работать, изложено в кулинарной книге генераторов исходных текстов с их примером:
<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. Если ваши зависимости находятся в том же репозитории, что и анализатор / генератор, вы можете просто втянуть код во время сборки, например, отсюда :
<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>
Затем я создал
AssemblyResolve
обработчик для
AppDomain
(статический конструктор в классе генератора) вот так:
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());
};
не то же самое, чтоPackageReference
!
GeneratePathProperty не будет предпринимать никаких действий дляProjectReference
потому что это не пакет.
Итак, вам просто нужно добавитьTargetPathWithTargetPlatformMoniker
указывая на вашу локальную DLL в Source Generatorcsproj
.
Пример с расположением вывода по умолчанию:
Структура папок
- Solution Folder
| - MySolution.sln
| + MyProject
| | - MyProject.csproj
| | + bin
| | | + Debug
| | | | + netstandard2.0
| | | | | - MyProject.dll
| | | + Release
| | | | + netstandard2.0
| | | | | - MyProject.dll
| + MySourceGenerator
| | - MySourceGenerator.csproj
MySourceGenerator.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyProject\MyProject.csproj" PrivateAssets="all"/>
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="..\MyProject\bin\$(Configuration)\netstandard2.0\MyProject.dll" IncludeRuntimeDependency="false"/>
</ItemGroup>
</Target>
</Project>
На этот вопрос сейчас есть окончательный ответ. Примеры генератора исходного кода содержат пример с необходимой настройкой.
Я использовал это, чтобы сделать исходный генератор с 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>