2

I'm trying to write a class in .Net Core using the Roslyn compiler to compile my project and return me the ClassDefinitions in that project. I am going to use that information to generate code using T4.

It's a .Net Core 3.1 project.

I can't find any good documentation on what packages I should use to accomplish this.

In the Solution getter after loading the Solution I get no documents in the Documents property and 1 Diagnostic message for each project it is trying to load:

System.ApplicationException: 'Msbuild failed when processing the file 'C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.GroupTool.Web\Wur.GroupTool.Web.csproj' with message: The SDK resolver type "WorkloadSdkResolver" failed to load. Could not load file or assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.  C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.GroupTool.Web\Wur.GroupTool.Web.csproj
Msbuild failed when processing the file 'C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.GroupTool.Core\Wur.GroupTool.Core.csproj' with message: The SDK resolver type "WorkloadSdkResolver" failed to load. Could not load file or assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.  C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.GroupTool.Core\Wur.GroupTool.Core.csproj
Msbuild failed when processing the file 'C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.GroupTool.Core.Tests\Wur.GroupTool.Core.Tests.csproj' with message: The SDK resolver type "WorkloadSdkResolver" failed to load. Could not load file or assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.  C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.GroupTool.Core.Tests\Wur.GroupTool.Core.Tests.csproj
Msbuild failed when processing the file 'C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.Roslyn.Metadata\Wur.Roslyn.Metadata.csproj' with message: The SDK resolver type "WorkloadSdkResolver" failed to load. Could not load file or assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.  C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.Roslyn.Metadata\Wur.Roslyn.Metadata.csproj
Msbuild failed when processing the file 'C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.Roslyn.Metadata.Tests\Wur.Roslyn.Metadata.Tests.csproj' with message: The SDK resolver type "WorkloadSdkResolver" failed to load. Could not load file or assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.  C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.Roslyn.Metadata.Tests\Wur.Roslyn.Metadata.Tests.csproj
'

I have these packages installed (yes, even tried the prerelease):

enter image description here

Also had to set the 'runtime' in the 'Exclude Assets' property:

enter image description here

Here's my class:

using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Wur.Roslyn.Metadata
{
    public class RoslynProjectProvider
    {
        public string PathToSolution { get; private set; }
        public string ProjectName { get; private set; }

        public RoslynProjectProvider(string pathToSolution, string projectName)
        {
            PathToSolution = pathToSolution;
            ProjectName = projectName;
        }

        private MSBuildWorkspace _Workspace;

        public MSBuildWorkspace Workspace
        {
            get
            {
                if (_Workspace == null)
                {
                    MSBuildLocator.RegisterDefaults();

                    _Workspace = MSBuildWorkspace.Create();
                }

                return _Workspace;
            }
        }

        private Solution _Solution;

        public Solution Solution
        {
            get
            {
                if (_Solution == null)
                {
                    _Solution = Workspace.OpenSolutionAsync(PathToSolution).Result;

                    if(Workspace.Diagnostics.Count > 0)
                    {
                        StringBuilder sb = new StringBuilder();

                        foreach (var diagnostic in Workspace.Diagnostics)
                        {
                            sb.Append(diagnostic.Message).Append(Environment.NewLine);
                        }

                        throw new ApplicationException(sb.ToString());
                    }
                }

                return _Solution;
            }
        }

        private Project _Project;

        /// <summary>
        /// Singleton Project in a solution
        /// </summary>
        public Project Project
        {
            get
            {
                if (_Project == null)
                {
                    _Project = Solution.Projects.FirstOrDefault(p => p.Name.Equals(ProjectName, StringComparison.InvariantCultureIgnoreCase));

                    if (_Project == null)
                    {
                        throw new ApplicationException($"Cannot find project {ProjectName}");
                    }
                }

                return _Project;
            }
        }

        private Compilation _Compilation;

        /// <summary>
        /// Singleton compilation of the project
        /// </summary>
        public Compilation ProjectCompilation
        {
            get
            {
                if (_Compilation == null)
                {
                    _Compilation = Project.GetCompilationAsync().Result;
                }

                return _Compilation;
            }
        }

        private List<ClassDeclarationSyntax> _Classes;

        public List<ClassDeclarationSyntax> Classes
        {
            get
            {
                if (_Classes == null)
                {
                    _Classes = new List<ClassDeclarationSyntax>();

                    foreach (var document in Project.Documents)
                    {
                        var tree = document.GetSyntaxTreeAsync().Result;

                        var semanticModel = ProjectCompilation.GetSemanticModel(tree);

                        foreach (var type in tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>())
                        {
                            _Classes.Add(type);
                        }
                    }
                }

                return _Classes;
            }
        }
    }
}

And a unit test to test the class:

public class UnitTest1
{
    [Fact]
    public void Test1()
    {
        var provider = new RoslynProjectProvider(@"C:\Projects\FB-IT\grouptool\sources\Wur.Grouptool\Wur.Grouptool.sln", "Wur.GroupTool.Core");

        var classes = provider.Classes;
    }
}

== EDIT ==

It asks for version 5.0.0.0 but the version of System.Runtime is lower. Could that be the problem here?

enter image description here

== EDIT ==

Remark to first answer: Ok, got it working finally. Had to download the .Net Core 3.1.404 SDK from here: dotnet.microsoft.com/download/dotnet-core/thank-you/…. Added the path as you suggested and voila it started working.

It works fine in the unit test but not in the T4 template. When I try to run the custom tool I get the following Exception (enabled binding logging):

Severity    Code    Description Project File    Line    Suppression State
Error       Running transformation: System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
File name: 'System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at Microsoft.VisualStudio.TextTemplating9A1960153A0EC5389F1AC1A553BA062D21024CF3A0BEA5F9915226007DBBA0457E39D7A79FF10F4293C4331881904FF0454986C06541AC3156F88310BB537850.GeneratedTextTransformation.TransformText()
   at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
   at Microsoft.VisualStudio.TextTemplating.TransformationRunner.PerformTransformation()

=== Pre-bind state information ===
LOG: DisplayName = System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 (Fully-specified)
LOG: Appbase = file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/
LOG: Initial PrivatePath = NULL
Calling assembly : Wur.Roslyn.Metadata, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in LoadFrom load context.
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: Using application configuration file: C:\Users\User\AppData\Local\Microsoft\VisualStudio\16.0_966a030a\devenv.exe.config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Post-policy reference: System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/System.Runtime/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PublicAssemblies/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PublicAssemblies/System.Runtime/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/System.Runtime/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/CommonExtensions/Microsoft/TestWindow/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/CommonExtensions/Microsoft/TestWindow/System.Runtime/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/CommonExtensions/Platform/Debugger/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/CommonExtensions/Platform/Debugger/System.Runtime/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/DataCollectors/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/DataCollectors/System.Runtime/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/DataCollectors/x86/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/DataCollectors/x86/System.Runtime/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/System.Runtime/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PublicAssemblies/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PublicAssemblies/System.Runtime/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/System.Runtime/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/CommonExtensions/Microsoft/TestWindow/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/CommonExtensions/Microsoft/TestWindow/System.Runtime/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/CommonExtensions/Platform/Debugger/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/CommonExtensions/Platform/Debugger/System.Runtime/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/DataCollectors/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/DataCollectors/System.Runtime/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/DataCollectors/x86/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/IDE/PrivateAssemblies/DataCollectors/x86/System.Runtime/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Projects/FB-IT/grouptool/sources/Wur.GroupTool/Wur.Roslyn.Metadata/bin/Debug/netcoreapp3.1/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Projects/FB-IT/grouptool/sources/Wur.GroupTool/Wur.Roslyn.Metadata/bin/Debug/netcoreapp3.1/System.Runtime/System.Runtime.DLL.
LOG: Attempting download of new URL file:///C:/Projects/FB-IT/grouptool/sources/Wur.GroupTool/Wur.Roslyn.Metadata/bin/Debug/netcoreapp3.1/System.Runtime.EXE.
LOG: Attempting download of new URL file:///C:/Projects/FB-IT/grouptool/sources/Wur.GroupTool/Wur.Roslyn.Metadata/bin/Debug/netcoreapp3.1/System.Runtime/System.Runtime.EXE.    Wur.GroupTool.Core.ViewModels   C:\Projects\FB-IT\grouptool\sources\Wur.GroupTool\Wur.GroupTool.Core.ViewModels\ViewModels\Web\PlayGround.tt    1

Is it possible it just doesn't find it because it's not there. There's no 4.2.2.0 version in my directories:

enter image description here

Paul Sinnema
  • 2,534
  • 2
  • 20
  • 33

1 Answers1

2

SDK version mismatch

It appears that when you install latest Visual Studio (I've got 16.8.3) - MSBuildLocator defaults to using the .NET 5 MSBuild:

JsonConvert.SerializeObject(MSBuildLocator.QueryVisualStudioInstances(VisualStudioInstanceQueryOptions.Default)); 
// ouputs in VS 2019: 
// [{"Version":"5.0.101","VisualStudioRootPath":"C:\\Program Files\\dotnet\\sdk\\5.0.101\\","Name":".NET Core SDK","MSBuildPath":"C:\\Program Files\\dotnet\\sdk\\5.0.101\\","DiscoveryType":4}]
// just thought I'd try the same in LINQPad 5 and here's the output:
// [{"Version":{"Major":16,"Minor":8,"Build":30804,"Revision":86,"MajorRevision":0,"MinorRevision":86},"VisualStudioRootPath":"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community","Name":"Visual Studio Community 2019","MSBuildPath":"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\MSBuild\\Current\\Bin","DiscoveryType":2}]

And when you do MSBuildLocator.RegisterDefaults() - it grabs the first instance from the above list.

Then, it appears that loading .NET Core 3.1 project with .NET 5 MSBuild ends up with error:

Msbuild failed when processing the file '...' with message: The SDK resolver type "WorkloadSdkResolver" failed to load. Could not load file or assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.

You however can tell MSBuidLocator to initialise environment off specific SDK location: MSBuildLocator.RegisterMSBuildPath. So I tried updating your Workspace getter like so:

public MSBuildWorkspace Workspace
{
    get
    {
        if (_Workspace == null)
        {
            MSBuildLocator.RegisterMSBuildPath("C:\\Program Files\\dotnet\\sdk\\3.1.404");// your version might be different, check `dotnet --list-sdks`
            _Workspace = MSBuildWorkspace.Create();
        }
        return _Workspace;
    }
}

I then had to follow the diagnostic output and install the following packages:

<PackageReference Include="Microsoft.NET.HostModel" Version="3.1.6" />
<PackageReference Include="NuGet.Packaging" Version="5.8.0" />
<PackageReference Include="NuGet.ProjectModel" Version="5.8.0" />

after installing the packages into my project - I was able to successfully list classes in my test solution.

One more thing

that might be tripping you over is lack of reference to Microsoft.CodeAnalysis.CSharp.Workspaces (see this SO answer for a bit more background).

So I ended up changing your constructor like so:

public RoslynProjectProvider(string pathToSolution, string projectName)
{
    var _ = typeof(Microsoft.CodeAnalysis.CSharp.Formatting.CSharpFormattingOptions); // this line forces a reference so MSBuild loads the assembly in question.
    PathToSolution = pathToSolution;
    ProjectName = projectName;
}

I have posted complete working example to GitHub if you'd want to check it out, but it's pretty much all your code packaged to run in VS.

timur
  • 14,239
  • 2
  • 11
  • 32
  • Thanks for trying timur. The project needs to reference .Net Core 3.1 not .Net 5.0. The typeof() does not solve the problem either. – Paul Sinnema Dec 15 '20 at 18:20
  • Are both your projects .net core 3.1? – timur Dec 15 '20 at 20:30
  • Yes, all projects are 3.1 – Paul Sinnema Dec 17 '20 at 10:22
  • what does `var vs = MSBuildLocator.QueryVisualStudioInstances().Aggregate(new StringBuilder(), (sb,s) => sb.AppendLine(s.MSBuildPath)).ToString();` output when you put it before `MSBuildLocator.RegisterDefaults();`? – timur Dec 17 '20 at 11:05
  • C:\Program Files\dotnet\sdk\5.0.101\ – Paul Sinnema Dec 18 '20 at 19:28
  • Added some more details to my question – Paul Sinnema Dec 18 '20 at 19:44
  • yes, that's the behaviour I'm observing after having updated VS to latest version. did you try `RegisterMSBuildPath` as i outlined in my update? – timur Dec 18 '20 at 19:59
  • Ok, got it working finally. Had to download the .Net Core 3.1.404 SDK from here: https://dotnet.microsoft.com/download/dotnet-core/thank-you/sdk-3.1.404-windows-x64-installer. Added the path as you suggested and voila it started working. Thanks a lot for you help. – Paul Sinnema Dec 19 '20 at 00:33
  • i'll tidy up the answer to focus on your specific issue and update the repo with new code a bit later – timur Dec 19 '20 at 01:39
  • Now the library is working with the Unit Test but it fails in the template telling me it can’t load type System.Threading.CancellationToken from, there it is again, System.Runtime. – Paul Sinnema Dec 19 '20 at 09:34
  • A possible bug is the fact that the QueryVisualStudioInstances() does not report the 3.1 SDK. – Paul Sinnema Dec 19 '20 at 10:15
  • it is a weird behaviour indeed. check out what your generated template's `QueryVisualStudioInstances` shows up – timur Dec 19 '20 at 10:32
  • I've come to the point where I'm about to give up. I just can't get this to work. At the moment I do a new for the provider it start complaining it can't find System.Runtime. No clue how to fix this. Feedback of VS2019 is non existent. Debug does not give a clue either. – Paul Sinnema Dec 19 '20 at 22:56
  • do you see any intelligible messages out of your library? – timur Dec 20 '20 at 02:50
  • Edited the question and added the Exception – Paul Sinnema Dec 20 '20 at 10:23
  • it appears that your custom tool is running .net full framework 4 - so my crude understanding of the process the tool using this older runtime is trying to invoke roslyn on a project the requires newer SDK. I don't know enough of the environment to make a guess but I would try to either update your tool to .net core 3 or install that specific .net 4 SDK on your machine and try point your code there. – timur Dec 20 '20 at 10:55
  • on a side note, have you looked into replacing T4 with, say, [CodeDOM](https://learn.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/using-the-codedom)? – timur Dec 20 '20 at 10:57
  • i wonder if this [SO thread](https://stackoverflow.com/questions/59610009/net-core-3-1-could-not-load-file-or-assembly-system-runtime-version-4-2-2-0) might be of use – timur Dec 20 '20 at 11:07
  • Yeah, crossed my mind. I have a library from another project I could port to .Net Core that does just that. But I wanted to give Roslyn a go. – Paul Sinnema Dec 20 '20 at 11:07
  • and I think [this MS answer](https://developercommunity.visualstudio.com/content/problem/1114518/filenotfoundexception-systemruntime-version4220-wh.html) backs my framework compatibility idea – timur Dec 20 '20 at 11:08