How to access project referenced assemblies in source generator?
Asked Answered
S

1

11

I have a Class Library project First.csproj with one file ICar.cs:

namespace First
{
    public interface ICar
    {
        string Name { get; set; }
    }
}

I have an empty Class Library project Second.csproj and Analyzer (source generator) project Second.Generator.csproj:

  1. First.csproj - has no project references
  2. Second.csproj - has references to First.csproj and Second.Generator.csproj
  3. Second.Generator.csproj - has no project references

I want to write Second.Generator.csproj MySourceGenerator.cs which takes Second.csproj, search all its Class Library project references (First.csproj in this case) and implement all its interfaces. Result should be this generated code:

namespace Second
{
    public class Car : First.ICar
    {
        public string Name { get; set; }
    }
}

Problem is that I cannot access referenced projects in source generator. I have tried to use reflection:

namespace Second.Generator
{
    [Generator]
    public class MySourceGenerator : ISourceGenerator
    {
        public void Initialize(GeneratorInitializationContext context)
        {
        }

        public void Execute(GeneratorExecutionContext context)
        {
            var first = context.Compilation.References.First(); //this is First.dll

            var assembly = Assembly.LoadFrom(first.Display);
        }
    }
}

But I cannot load the assembly:

Could not load file or assembly 'file:///...First\bin\Debug\net6.0\ref\First.dll' or one of its dependencies. Reference assemblies should not be loaded for execution. They can only be loaded in the Reflection-only loader context.

Any help will be appreciated. Thank you.

Stellular answered 10/8, 2021 at 18:40 Comment(0)
S
10

I have figured out some way with assembly symbols. Using reflection was not a good idea.

namespace Second.Generator
{
    [Generator]
    public class MySourceGenerator : ISourceGenerator
    {
        public void Initialize(GeneratorInitializationContext context)
        {
        }

        public void Execute(GeneratorExecutionContext context)
        {
            var types = context.Compilation.SourceModule.ReferencedAssemblySymbols.SelectMany(a =>
            {
                try
                {
                    var main = a.Identity.Name.Split('.').Aggregate(a.GlobalNamespace, (s, c) => s.GetNamespaceMembers().Single(m => m.Name.Equals(c)));

                    return GetAllTypes(main);
                }
                catch
                {
                    return Enumerable.Empty<ITypeSymbol>();
                }
            });

            var properties = types.Where(t => t.TypeKind == TypeKind.Interface && t.DeclaredAccessibility == Accessibility.Public).Select(t => new
            {
                Interface = t,
                Properties = t.GetMembers()
            });
        }

        private static IEnumerable<ITypeSymbol> GetAllTypes(INamespaceSymbol root)
        {
            foreach (var namespaceOrTypeSymbol in root.GetMembers())
            {
                if (namespaceOrTypeSymbol is INamespaceSymbol @namespace) foreach (var nested in GetAllTypes(@namespace)) yield return nested;

                else if (namespaceOrTypeSymbol is ITypeSymbol type) yield return type;
            }
        }
    }
}
Stellular answered 10/8, 2021 at 22:4 Comment(3)
This works perfectly if the reference from Second to First is a package reference, not a project reference. Any way to make this work with project references too ?Rupertruperta
@Rupertruperta The above code works perfectly for project references too, verified it right now by using this approach in my project.Malave
I think the line var main = a.Identity.Name.Split('.').Aggregate(a.GlobalNamespace, (s, c) => s.GetNamespaceMembers().Single(m => m.Name.Equals(c))); Is doing some filtering where the namespace needs to match the assembly name, not sure why the OP did that, but I passed a.GlobalNamespace directly to my equivalent of GetAllTypes and it has worked for me so far.Quadrilateral

© 2022 - 2024 — McMap. All rights reserved.