dotnet / interactive

.NET Interactive combines the power of .NET with many other languages to create notebooks, REPLs, and embedded coding experiences. Share code, explore data, write, and learn across your apps in ways you couldn't before.
MIT License
2.89k stars 387 forks source link

How to use MSBuildWorkspace in Roslyn to analyse C# projects in .NET Interactive? #1985

Closed roberthusak closed 1 year ago

roberthusak commented 2 years ago

The package and version I'm asking about: Version: 1.0.317502+9c2a225c543085a04516cc4fc490919d5925ba87 Build date: 2022-04-20T11:12:26.5754202Z https://github.com/dotnet/interactive

Question

Hi, I'd like to use Microsoft Roslyn to analyse an existing C# project from a Jupyter notebook in VS Code, mainly to explore and visualise the code base. What is the best way to use Roslyn in .NET Interactive so that I don't stumble upon problems with wrongly loaded assemblies etc.?

I'm trying to load the project using the MSBuildWorkspace so that all the source files and references are loaded by MSBuild. To properly initialize MSBuild, MSBuildLocator is used:

#r "nuget:Microsoft.Build.Locator"
#r "nuget:Microsoft.CodeAnalysis.CSharp.Workspaces"
#r "nuget:Microsoft.CodeAnalysis.Workspaces.MSBuild"

using System.IO;
using System.Reflection;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.MSBuild;

if (!MSBuildLocator.IsRegistered)
{
    MSBuildLocator.RegisterDefaults();
}

using (var workspace = MSBuildWorkspace.Create())
{
    var project = await workspace.OpenProjectAsync(@"C:\path\to\the.csproj");
}

However, I'm currently getting the following exception:

Error: System.TypeLoadException: Could not load type 'Microsoft.Build.Framework.Traits' from assembly 'Microsoft.Build.Framework, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
   at Microsoft.Build.Evaluation.ProjectCollection..ctor(IDictionary`2 globalProperties, IEnumerable`1 loggers, IEnumerable`1 remoteLoggers, ToolsetDefinitionLocations toolsetDefinitionLocations, Int32 maxNodeCount, Boolean onlyLogCriticalEvents, Boolean loadProjectsReadOnly)
   at Microsoft.Build.Evaluation.ProjectCollection..ctor(IDictionary`2 globalProperties)
   at Microsoft.CodeAnalysis.MSBuild.Build.ProjectBuildManager.StartBatchBuild(IDictionary`2 globalProperties) in /_/src/Workspaces/Core/MSBuild/MSBuild/Build/ProjectBuildManager.cs:line 161
   at Microsoft.CodeAnalysis.MSBuild.MSBuildProjectLoader.Worker.LoadAsync(CancellationToken cancellationToken) in /_/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs:line 134
   at Microsoft.CodeAnalysis.MSBuild.MSBuildProjectLoader.LoadProjectInfoAsync(String projectFilePath, ProjectMap projectMap, IProgress`1 progress, ILogger msbuildLogger, CancellationToken cancellationToken) in /_/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs:line 276
   at Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace.OpenProjectAsync(String projectFilePath, ILogger msbuildLogger, IProgress`1 progress, CancellationToken cancellationToken) in /_/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs:line 251
   at Submission#3.<<Initialize>>d__0.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)

By attaching to the process and inspecting loaded assemblies via VS, I found that .NET Interactive is distributed with its own assembly Microsoft.Build.Framework.dll, which is loaded instead of the one located in the SDK folder (Microsoft.Build.dll is thanks to MSBuildLocator correctly loaded from the SDK folder):

roslyn_net_interactive_modules

The type Microsoft.Build.Framework.Traits is missing in the assembly distributed with .NET Interactive, hence the exception.

I wasn't able to force the runtime to load Microsoft.Build.Framework.dll from the SDK folder (AssemblyLoadContext.Default.LoadFromAssemblyPath etc.) instead of the one distributed with .NET Interactive. However, even if I succeeded, it doesn't even seem to me as the correct way to solve this, as it may cause other problems along the way. Do you have any suggestions how to approach this problem? And, maybe, how to approach assembly name collisions in .NET Interactive in general?

I might end up running MSBuild in the separate process and then creating the Roslyn Compilation object in the notebook manually (apart from the MSBuild stuff, Roslyn seems to work in notebooks), but I'd like to do it in a cleaner way, if there is any.

Thanks a lot in advance, Robert

RomanKubanek1 commented 2 years ago

I solved it by installing Microsoft.Build.Framework.dll version 17.1.

brettfo commented 1 year ago

We pull in a specific version of Roslyn to provide C# language support, so as it was mentioned above, you'll need to pull in any additional packages necessary to load/analyze the project, like MSBuild.