dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.96k stars 4.02k forks source link

Source generator doesn't see ProjectReference in ReferencedAssemblySymbols in Roslyn time generation #57997

Closed KondzioSSJ4 closed 1 year ago

KondzioSSJ4 commented 2 years ago

Version Used:
Visual Studio 17.0.1 (newest) Microsoft.CodeAnalysis.CSharp.Workspaces 3.11.0

Steps to Reproduce:

  1. Create project BaseProject
  2. Create project AppProject
  3. Add project BaseProject to project AppProject as a project reference
  4. Create SourceGenerator project that generates code depending on reference project (code provided below)
  5. Add source generator to AppProject
  6. Restart Visual Studio
  7. Add in main function of AppProject the Console.Write of variable GeneratedCode.SymbolsInfo.SymbolCount (generated)
  8. Execute application, and you will get 1 (that's correct behavior)
  9. Go to project in Visual Studio, to see generated code... and you will see that function returns 0 (not expected!) (process what want to run this generator and return invalid result: ServiceHub.RoslynCodeAnalysisService.exe)

If you try to build a project with turn-on debugger, you also get 1 as a result of symbol.Count I also created test, but the result is also 1 (correct)

Code for Source Generator:

[Generator]
    public class RoslynBugSourceGenerator : ISourceGenerator
    {
        /// <summary>
        /// If have dot, then provide only first part
        /// </summary>
        private const string DependantProjectName = "BaseProject";

        public void Execute(GeneratorExecutionContext context)
        {
            var symbols = context
                .Compilation
                .SourceModule
                .ReferencedAssemblySymbols
                .Where(x => x.NamespaceNames.FirstOrDefault() == DependantProjectName)
                .ToList();

            var code = $@"//</auto-generated>
namespace GeneratedCode
{{
    public static class SymbolsInfo
    {{
        public static int SymbolCount => {symbols.Count};
    }}
}}";

            context.AddSource("SymbolsCount.cs", code);
        }

        public void Initialize(GeneratorInitializationContext context)
        {
#if DEBUG
            if (!Debugger.IsAttached)
            {
                Debugger.Launch();
            }
#endif
        }

Expected Behavior: That source generator will works the same in:

Actual Behavior: Roslyn when building SourceGenerator not include dependency projects before build

That error doesn't look scary, but because of this error, there isn't any possible way to create more advanced source generators in a normal live scenario, because currently most of the projects are separated by many csproj (for example to separate layers). e.g. You never saw project with CQRS and with query/commands and API controllers in the same csproj

KondzioSSJ4 commented 2 years ago

Walkaround... Instead of using x.NamespaceNames.FirstOrDefault() == DependantProjectName use: x.Identity.Name.StartsWith(DependantProjectName) for that single operation

but still... the same operation against test/build/vs should return the same results

jaredpar commented 2 years ago

@chsienki fyi

sharwell commented 2 years ago

I don't understand why this is expected to work:

.Where(x => x.NamespaceNames.FirstOrDefault() == DependantProjectName)

Assemblies have multiple namespaces, including namespaces that are implicitly added by project system tools. I don't see any documentation suggesting that the order of namespaces in this collection would require any specific item to appear first.

KondzioSSJ4 commented 2 years ago

@sharwell Ok, but it's strange that only doesn't work in Roslyn process and everywhere else is fine

Also... as far as I remember that x.NamespaceNames.Select(x => x.ToString()) returns different results in Roslyn than it returning in other ways and I think that was a problem for me

(that ticket stay for one month... so it's harder to remember details)

AlexeiScherbakov commented 2 years ago

I recently found that ProjectReference assembly is not copied in AnalyzerAssemblyLoader folder when CodeGenerator is preparing for run. In C:\Users\Alexei\AppData\Local\Temp\VS\AnalyzerAssemblyLoader\*\*\ directories, each directory has only one assembly and CodeGenerators with project references cannot start. I use Version 17.0.5 of vs

sharwell commented 2 years ago

I recently found that ProjectReference assembly is not copied in AnalyzerAssemblyLoader folder when CodeGenerator is preparing for run.

This is correct by default. You have to modify the project logic to itemize all necessary assemblies as part of GetTargetPathDependsOn. Here's an example involving both ProjectReference and PackageReference:

https://github.com/KathleenDollard/JackFruit/blob/f170542e1e4c32dd12113b6589906e2bad5116b8/JackFruitAppModel.CSharp/JackfruitAppModel.CSharp.fsproj#L34-L47

AlexeiScherbakov commented 2 years ago

Thanks, but when I can read about it? (Except microsoft employee repositories, they made it and know how it works 🤣 )

This is not covered by docs here: https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview

sharwell commented 2 years ago

I don't believe there's any documentation of this at this time. We'd like to make it more convenient, but so far have not completed that work.