roslyn: Loading net60 project into net472 host is not fully supported

Issue Description

Note: I am not entirely sure where this bug report belongs, as I am using multiple components and it is hard for me to figure out, which component is failing.

Context

In my project I use the nuget packages for

  • Roslyn (4.2)
  • Microsoft.Build (17.2)
  • Microsoft.Build.Framework
  • and Microsoft.Build.Locator (1.4.1).

I use these tools to open solution files and projects and to perform some analysis on them. My tool targets net472 and I select Visual Studio 2022 as Visual Studio instance using MSBuildLocator.

Example Program

Source code:

using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace ApplicationConfigurationSourceGenNotExecutedInMSBuildWorkspace
{
    static class Program
    {
        public static async Task Main()
        {
            foreach (var vs in MSBuildLocator.QueryVisualStudioInstances())
            {
                PrintInstanceInfo(vs);
            }

            // When targeting net472 use this line:
            VisualStudioInstance selectedVS = MSBuildLocator.QueryVisualStudioInstances().FirstOrDefault(vs => vs.Name.Contains("2022")); // selects VS 2022
            // When targeting net60 use this line:
            //VisualStudioInstance selectedVS = MSBuildLocator.QueryVisualStudioInstances().FirstOrDefault(vs => vs.Version >= new Version(6, 0)); // selects net 6.0 sdk

            if (selectedVS == null)
            {
                Console.WriteLine("No SDK found!");
                return;
            }

            Console.WriteLine("Use " + selectedVS.Name);

            MSBuildLocator.RegisterInstance(selectedVS);

            var workspace = MSBuildWorkspace.Create();

            var project = await workspace.OpenProjectAsync(@"<path-to>\SDKStyleNet60WinForms.csproj");
            var comp = await project.GetCompilationAsync();

            if (comp == null)
            {
                Console.WriteLine("Project does not support compilation.");
                return;
            }

            foreach (var d in comp.GetDiagnostics())
            {
                if (d.IsSuppressed || d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Hidden)
                {
                    continue;
                }
                Console.WriteLine(d.Severity + ": " + d.GetMessage());
            }

            void PrintInstanceInfo(VisualStudioInstance instance)
            {
                Console.WriteLine($"Name: {instance.Name}");
                Console.WriteLine($"Version: {instance.Version}");
                Console.WriteLine($"MSBuildPath: {instance.MSBuildPath}");
                Console.WriteLine($"VisualStudioRootPath: {instance.VisualStudioRootPath}");
                Console.WriteLine($"DiscoveryType: {instance.DiscoveryType}");
            }
        }
    }
}

Project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net472</TargetFrameworks>

	<RoslynVersion>4.2.0</RoslynVersion>
	<MicrosoftBuildVersion>17.2.0</MicrosoftBuildVersion>
	<MicrosoftBuildLocatorVersion>1.4.1</MicrosoftBuildLocatorVersion>
  </PropertyGroup>

  <ItemGroup>
	<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="$(RoslynVersion)" />
	<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(RoslynVersion)" />
	<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(RoslynVersion)" />
	<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(RoslynVersion)" />
	<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="$(RoslynVersion)" />
	<PackageReference Include="Microsoft.Build" Version="$(MicrosoftBuildVersion)" ExcludeAssets="runtime" />
	<PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildVersion)" ExcludeAssets="runtime" />
	<PackageReference Include="Microsoft.Build.Locator" Version="$(MicrosoftBuildLocatorVersion)" />
	<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MicrosoftBuildVersion)" ExcludeAssets="runtime" />
	<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildVersion)" ExcludeAssets="runtime" />
  </ItemGroup>
</Project>

When I try to open a .NET 6.0 SDK-style Windows Forms project (test-project.zip), then I get the error: CS0103 The name 'ApplicationConfiguration' does not exist in the current context

Which is a hint that the new System.Windows.Forms.Generators.ApplicationConfigurationGenerator is not loaded/executed. This is a probably a bug, because I explicitly selected Visual Studio 2022 using MSBuildLocator.RegisterInstance and Visual Studio 2022 officially supports building net6.0 applications and opening the same project in VS 2022 executes the code generator just fine.

When I switch to net6.0 as the target framework of my code, then the project can be loaded/analyzed successfully.

However, not all of our users have the newest .NET SDKs installed, so targeting classic .NET 4.x for our analysis would be preferable.

So my questions are:

  • Is this a bug? And if not:
  • How are we supposed to support different versions of MSBuild / .NET with our project? (We have a wide range of frameworks and project types to support)
  • Do I need to provide separate builds of my application for every (major) .NET version or is there an easier way?

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Comments: 15 (13 by maintainers)

Most upvoted comments

I didn’t read the full thread, but note that GetCompilationAsync doesn’t automatically run source generators. You would need to manually run them similar to what you see here: https://github.com/dotnet/roslyn-sdk/blob/1df5d394195ffcf6aab5b824ae420197ed1f0acc/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs#L245

say I fire up a command prompt and msbuild a SDK-targeting project. How is the SDK even picked there, high level?

MSBuild has “SDK resolvers” that are a pluggable interface (with about 3 implementations, not really intended as end-user pluggable). One of them is the .NET SDK resolver, which uses .NET APIs (hostfxr_resolve_sdk2) to figure out the right SDK given global.json state and what’s installed on the machine.

In the .NET SDK build you purposefully choose a .NET 6 MSBuild (and thus SDK) to build with.

This may also have been done inadvertently by targeting a .NET 6 runtime for your app that calls MSBuildLocator–Locator won’t list a higher SDK major version than the current runtime major version because you can’t load/build projects using that SDK (since they tend to depend on new runtime features).

In the Full Framework build you simply choose a VS MSBuild which will use the highest SDK unless the project has pinned a specific one with a global.json.

One subtlety: there’s a minimumMSBuildVersion file in the SDK that may cause a too-new SDK to be rejected by a slightly-older MSBuild.

In the example I think it makes sense for MSBuild/the .NET SDK resolver to resolve SDK 7.0.100-preview since that’s the highest version on the box.