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
19.05k stars 4.03k forks source link

MSBuildWorkspace: Loading a solution with a ProjectReference prevents compilation #36072

Open rowland-banks-abide opened 5 years ago

rowland-banks-abide commented 5 years ago

Version Used: Microsoft.Build.Locator v1.2.2 Microsoft.CodeAnalysis v3.1.0 Microsoft.CodeAnalysis.Workspaces.MSBuild v3.1.0

Steps to Reproduce:

See attached "RoslynTest.zip" for Zip file demonstrating problem. Unzip to C:\Source (or modify the code to point to correct location) and run RoslynTest.sln

RoslynTest.zip

  1. Create a solution containing two netstandard2.0 libraries (A and B). Each solution should have a simple class in it.
  2. Execute the code below, and note that both projects compile.
  3. Add a ProjectReference so that B depends on A.
  4. Execute the code below and note that only project A compiles.
static async Task Main(string[] args)
{
    MSBuildLocator.RegisterDefaults();
    using (var workspace = MSBuildWorkspace.Create())
    {
        workspace.WorkspaceFailed += (sender, workspaceFailedArgs) => WriteLine(workspaceFailedArgs.Diagnostic.Message);
        var solution = await workspace.OpenSolutionAsync(@"c:\source\ForRoslynTest\ForRoslynTest.sln");
        WriteLine($"Loaded solution {solution.FilePath}");

        var projectTree = workspace.CurrentSolution.GetProjectDependencyGraph();
        foreach (var projectId in projectTree.GetTopologicallySortedProjects())
        {
            await CompileProject(workspace.CurrentSolution.GetProject(projectId));
        }
    }
}

private static async Task CompileProject(Project project)
{
    WriteLine($"Compiling {project.Name}. It has {project.MetadataReferences.Count} metadata references.");
    var compilation = await project.GetCompilationAsync();
    var errors = compilation.GetDiagnostics().Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
    if (errors.Any())
    {
        WriteLine($"COMPILATION ERROR: {compilation.AssemblyName}: {errors.Count()} compilation errors: \n\t{string.Join("\n\t", errors.Where(e => false).Select(e => e.ToString()))}");
    }
    else
    {
        WriteLine($"Project {project.Name} compiled with no errors");
    }
}

Expected Behavior: Both projects compile with 0 errors.

Actual Behavior: Project B fails to compile. In addition, project B reports that it has 0 Metadata references (whereas it has 113 without the PackageReference)

In particular, I note the following output reported:

Msbuild failed when processing the file 'c:\source\ForRoslynTest\DownstreamLibrary\DownstreamLibrary.csproj' with message: C:\Program Files\dotnet\sdk\2.1.602\Microsoft.Common.CurrentVersion.targets: (1548, 5): The "Microsoft.Build.Tasks.ResolveNonMSBuildProjectOutput" task could not be loaded from the assembly Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.  Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.

Found project reference without a matching metadata reference: c:\source\ForRoslynTest\CoreLibrary\CoreLibrary.csproj

Loaded solution c:\source\ForRoslynTest\ForRoslynTest.sln

Compiling CoreLibrary. It has 113 metadata references.
Project CoreLibrary compiled with no errors

Compiling DownstreamLibrary. It has 0 metadata references.
COMPILATION ERROR: DownstreamLibrary: 3 compilation errors:
        c:\source\ForRoslynTest\DownstreamLibrary\Class1.cs(1,7): error CS0246: The type or namespace name 'System' could not be found (are you missing a using directive or an assembly reference?)
        c:\source\ForRoslynTest\DownstreamLibrary\Class1.cs(5,18): error CS0518: Predefined type 'System.Object' is not defined or imported
        c:\source\ForRoslynTest\DownstreamLibrary\Class1.cs(5,18): error CS1729: 'object' does not contain a constructor that takes 0 arguments
rowland-banks-abide commented 5 years ago

Using Procmon, I believe it is reading the Microsoft.Build.Tasks.Core assembly from C:\Program Files\dotnet\sdk\2.1.602\Microsoft.Build.Tasks.Core.dll.

Interestingly, when I decompile this with ILSpy, I find that the ResolveNonMSBuildProjectOutput class does not exist - I suspect this is the root of my problem.

The version of the DLL is 15.1.0.0 but the assembly file version is 16.0.450.56488.

I have older versions of 15.1.0.0 of this DLL on my machine (e.g 15.1.1012.6693) that do have this class in place.

EDIT I've just upgraded to 2.2.300 and confirmed that the same issue is in place (except now slightly worse as it's complaining that it can't find Newtonsoft.Json v9.0.0.0, as the version in the dotnet\sdk\2.2.300 folder is 10.0.0.0. I can fix that by installing Newtonsoft.Json v9.0.1 into my RoslynTest project, and then I'm back to the reported issue)

rowland-banks-abide commented 5 years ago

I've discovered that if I change the TargetFramework of RoslynTest project to net472, rather than netcoreapp2.1 it works.

I've posted this as a Stack Overflow question as well:

daniel-munch-cko commented 5 years ago

Running into this exact behaviour as well - Is there any updates on it? Thanks!

daniel-munch-cko commented 5 years ago

This comment https://github.com/maca88/AsyncGenerator/issues/24#issuecomment-339807306 suggests setting BuildingInsideVisualStudio to false.

However, if I create a workspace like this

var workspace = MSBuildWorkspace.Create(new Dictionary<string, string>() {
  { "BuildingInsideVisualStudio", "false" }
});

I'm running into the following exception

Unhandled Exception: System.ArgumentException: An element with the same key but a different value already exists. Key: BuildingInsideVisualStudio
   at System.Collections.Immutable.ImmutableDictionary`2.HashBucket.Add(TKey key, TValue value, IEqualityComparer`1 keyOnlyComparer, IEqualityComparer`1 valueComparer, KeyCollisionBehavior behavior, OperationResult& result)
   at System.Collections.Immutable.ImmutableDictionary`2.AddRange(IEnumerable`1 items, MutationInput origin, KeyCollisionBehavior collisionBehavior)
   at System.Collections.Immutable.ImmutableDictionary`2.AddRange(IEnumerable`1 pairs, Boolean avoidToHashMap)
   at System.Collections.Immutable.ImmutableDictionary`2.AddRange(IEnumerable`1 pairs)
   at Microsoft.CodeAnalysis.MSBuild.Build.ProjectBuildManager.StartBatchBuild(IDictionary`2 globalProperties) in /_/src/Workspaces/Core/MSBuild/MSBuild/Build/ProjectBuildManager.cs:line 152
   at Microsoft.CodeAnalysis.MSBuild.MSBuildProjectLoader.Worker.LoadAsync(CancellationToken cancellationToken) in /_/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs:line 133
   at Microsoft.CodeAnalysis.MSBuild.MSBuildProjectLoader.LoadSolutionInfoAsync(String solutionFilePath, IProgress`1 progress, CancellationToken cancellationToken) in /_/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs:line 199
   at Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace.OpenSolutionAsync(String solutionFilePath, IProgress`1 progress, CancellationToken cancellationToken) in /_/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs:line 183
   at checkout_ap_analyzer.Program.ExecuteLogAnalysis(String solutionFile) in /Users/daniel.munch/Projects/checkout-ap-analyzer/Program.cs:line 64
   at checkout_ap_analyzer.Program.Main(String[] args) in /Users/daniel.munch/Projects/checkout-ap-analyzer/Program.cs:line 24
   at checkout_ap_analyzer.Program.<Main>(String[] args)
ionoy commented 5 years ago

@daniel-munch-cko Same here. I even tried overriding default properties with reflection, but it still fails to build with the following message:

The "ResolvePackageAssets" task failed unexpectedly. System.IO.FileLoadException: Could not load file or assembly 'NuGet.Frameworks, Version=4.9.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. Could not find or load a specific file. (Exception from HRESULT: 0x80131621)

I really need this functionality... If anyone finds a workaround, please post it here.

daniel-munch-cko commented 5 years ago

On my Mac I ended up using mono to run my application on full .net472 which works (just like mentioned by the original author of this issue), but admittedly this is a bit annoying since it alters my full .NET Core experience.

ionoy commented 5 years ago

Unfortunately, my application targets Core and I can't use .net472. I could probably create an external console application that would send me the required data, but it's a terrible solution compared to in-process analysis.

Now I'm more interested to know if this issue is solvable at all or is it a dead-end.

daniel-munch-cko commented 5 years ago

I see, yeah that's indeed a terrible workaround.

My next attempt would have been to file a similar issue (or to link this issue) in the MSBuildLocator repository here: https://github.com/microsoft/MSBuildLocator/

That's without evidence, but the error message seems to suggest troubles in locating an assembly, which is exactly what the MSBuildLocator is dealing with. It might be worth having a closer look what it does and if this issue actually belongs rather to them than to Roslyn itself.

ionoy commented 5 years ago

It's my understanding that MSBuildLocator works correctly by locating the Core SDK. The issue is that Core SDK can't build non-Core projects anymore (if it ever could). So, in its' current state, MSBuildWorkspace only works for building .NET Core projects. Maybe this was intended from the beginning.

huancz commented 5 years ago

What led me to this report - I was trying to build (netcore) console analyzer for another netcore project. At least it was easy to switch the analyzer to 4.7.2. Whatever SDK MSBuildLocator locates should be able to work in that scenario...

ionoy commented 5 years ago

@daniel-munch-cko I ported my console app to net472 and while executing it on Mono I get PlatformNotSupportedException from MSBuildLocator.RegisterDefaults() call. (https://github.com/microsoft/MSBuildLocator/issues/72)

Did you encounter anything similar?

InfinitiesLoop commented 5 years ago

Same problem here. I'm trying to build .NET core app that runs analysis on a non-core .NET app. It's a unity project, so can't just change that -- unity generates a sln and csproj's. If I run the exact same code as a .NET 4.7 console app, it works fine. Would love to hear from someone whether the scenario I am going for is even supported or if this is a legitimate bug.

daniel-munch-cko commented 5 years ago

Sorry @ionoy realised I forgot to reply - I think I saw something similar, I explicitly registered the Mono MSBuild path using this line

MSBuildLocator.RegisterMSBuildPath("/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/msbuild/15.0/bin/");
ionoy commented 5 years ago

@daniel-munch-cko I did that too, but it didn't help. There was a new error on constructing MSBuildWorkspace, something with reflection and Sqlite.raw. Ultimately I switched to AdHocWorkspace and .NET Core, which worked like a charm. Although, this solution requires the knowledge of all references and source files.

daniel-munch-cko commented 5 years ago

I think I ran into the same issue with Sqlite.raw - manually adding a NuGet reference to SQLitePCLRaw.core fixed it for me as far as I can remember

ErikSchierboom commented 5 years ago

Although, this solution requires the knowledge of all references and source files.

This is what I don't like about that approach. It's slightly odd to me that compiling a .NET Core project such a hassle. Is this really an unsupported use case, or does anyone have an example?

ddizh commented 5 years ago

I've also faced the same issue trying to load nectoreapp2.1 *.csproj with three netstandard2.0 project references on OSx.

rainersigwald commented 5 years ago

Same problem here. I'm trying to build .NET core app that runs analysis on a non-core .NET app. It's a unity project, so can't just change that -- unity generates a sln and csproj's. If I run the exact same code as a .NET 4.7 console app, it works fine. Would love to hear from someone whether the scenario I am going for is even supported or if this is a legitimate bug.

@InfinitiesLoop No, that's not a supported scenario. .NET Core MSBuild doesn't support every operation that full .NET Framework MSBuild.exe does, and has a different extensibility model (Visual Studio allows extensions to add MSBuild logic, which isn't available to .NET Core MSBuild).

As a result of that, some projects can only be built with full framework MSBuild, so an application that wants to use the MSBuild API for them (including indirectly through MSBuildWorkspace) must target .NET Framework and use full-framework MSBuild.

@d-dizhevsky Looking through this thread, I see several related issues. Can you be explicit about what problem you have?

ddizh commented 5 years ago

@rainersigwald I have created repro project here: https://github.com/d-dizhevsky/owss

It contains:

The WebAPI1 has a project reference on TestCoreLib. Just do cd src/OpenWebSecurityScanner/ and then dotnet run.

Thats my output on OSx: image

Please note that if you remove TestCoreLib project reference from WebAPI1 then it works properly.

rainersigwald commented 5 years ago

@d-dizhevsky Thanks! I opened https://github.com/microsoft/msbuild/issues/4770 to track fixing that problem.

Setting BuildingInsideVisualStudio would indeed disable the call to that task, but I think it might break other parts of design-time-like builds, even beyond the ImmutableDictionary exception mentioned above.

pkrukp commented 4 years ago

I have similar problem - I have .net framework application which tries to open a .net core project using msbuild api (to later use roslyn analysis). Opened project contains error diagnostics. What's interesting, if I run Debug>Run Debugging, the problems disappear.

I filed a bug for msbuild, but maybe this is more roslyn related? https://github.com/microsoft/msbuild/issues/5002

miyu commented 4 years ago

== Emphasis for MS devs before wall-of-text manual workaround: there's a manual workaround, but this bug is NOT user error and likely affects a significant number of .net core deployments using Roslyn. It LIKELY either has to do with a mix of VS deployment version & MSBuild loading an old .NET Core SDK.

The bug likely manifests in many other ways. For example, it caused my Roslyn setup to fail to resolve float symbol via GetSymbolInfo(someFloatTypeSyntax).

I found a manual workaround to this WorkspaceFailed error:

[Failure] Msbuild failed when processing the with message: The "Microsoft.Build.Tasks.ResolveNonMSBuildProjectOutput" task could not be loaded from the assembly Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a. Confirm that the declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.

There were a few resolution steps:

  1. following https://github.com/microsoft/msbuild/issues/4770 I updated VS 2019 from 16.2 to 16.5.3... This didn't fix my bug, but it's worth documenting I did this.
  2. I upgraded my Microsoft.Build.* and Microsoft.CodeAnalysis dependencies to latest, THIS DIDN'T FIX THINGS YET, same bug.
  3. I navigated to C:\Program Files\dotnet\sdk which previously had a few directories:
    1.0.0/                  1.0.0-rc4-004771/  2.0.3/    2.1.505/
    1.0.0-preview1-002702/  1.0.3/             2.1.202/  3.1.100/
    1.0.0-preview2-003121/  1.0.4/             2.1.4/    3.1.201/
    1.0.0-preview4-004233/  1.1.0/             2.1.502/  NuGetFallbackFolder/
  4. I renamed this to sdk_o and created a new folder C:\Program Files\dotnet\sdk, copying in only 3.1.201/

Success! This error was gone, but running my roslyn app then resulted in some error like (paths stripped)

[Failure] Msbuild failed when processing the file with message The "ProcessFrameworkReferences" task failed unexpectedly. System.IO.FileNotFoundException: Could not load file or assembly 'NuGet.Frameworks, Version=5.5.0.4, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the file specified. File name: 'NuGet.Frameworks, Version=5.5.0.4, Culture=neutral, PublicKeyToken=31bf3856ad364e35'

Solved this one by adding NuGet.ProjectModel 5.5.1 to my project, the csproj now has the following package references:

   <ItemGroup>
     <PackageReference Include="Microsoft.Build" Version="16.5.0" ExcludeAssets="runtime" />
     <PackageReference Include="Microsoft.Build.Framework" Version="16.5.0" ExcludeAssets="runtime" />
     <PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
     <PackageReference Include="Microsoft.Build.Tasks.Core" Version="16.5.0" ExcludeAssets="runtime" />
     <PackageReference Include="Microsoft.CodeAnalysis" Version="3.5.0" />
     <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.5.0" />
     <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.5.0" />
     <PackageReference Include="NuGet.ProjectModel" Version="5.5.1" />
   </ItemGroup>

No more WorkspaceFailed events for this code:

Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults();
var workspace = MSBuildWorkspace.Create();
workspace.WorkspaceFailed += (s, e) => { Console.WriteLine(e.Diagnostic); };
var project = await workspace.OpenProjectAsync(@"C:/path/to.csproj");     

My csproj looks as follows:

<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
      <TargetFramework>netcoreapp3.1</TargetFramework>
      <UseWindowsForms>true</UseWindowsForms>
      <OutputType>Library</OutputType>
      <ApplicationIcon />
      <StartupObject />
   </PropertyGroup>

   <ItemGroup>
      <ProjectReference Include="..\..\dependencies\some.csproj" />
   </ItemGroup>
</Project>
rainersigwald commented 4 years ago

running my roslyn app then resulted in some error like (paths stripped)

[Failure] Msbuild failed when processing the file with message The "ProcessFrameworkReferences" task failed unexpectedly. System.IO.FileNotFoundException: Could not load file or assembly 'NuGet.Frameworks, Version=5.5.0.4, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the file specified. File name: 'NuGet.Frameworks, Version=5.5.0.4, Culture=neutral, PublicKeyToken=31bf3856ad364e35'

Solved this one by adding NuGet.ProjectModel 5.5.1 to my project

This is a result of microsoft/MSBuildLocator#86, so we should be able to avoid the need for that reference.