Search code examples
c#.netsourcegenerators

Source Generators dependencies not loaded in Visual Studio


I am working on source generator and I have problems with dependencies:

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.'

There is a lot of information on how to pack dependencies into nuget, but I reference analyzer project directly like this:

<ProjectReference Include="SG.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

In analyzer project I added <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> and all dependencies are available in output directory, but VS is not using that directory - it uses AppData\Local\Temp\VBCSCompiler\AnalyzerAssemblyLoader\[...] instead and it copies only one DLL there.

What can be done to make that work?


Solution

  • I found the way to make it work more or less reliably with some hacks.

    Before that I also tried ILMerge, but it didn't work (missing method exceptions).

    Solution:

    First of all I embeded dependencies in source generator assembly like this:

    <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>
    

    Then I created AssemblyResolve handler for AppDomain (static constructor in generator class) like so:

    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());
    };