Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions documentation/general/decouple-vs-and-net-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,6 @@ Solutions that mix .NET SDK and Visual Studio projects will end up with multiple

The compiler will offer a property that allows SDK projects to use the MSBuild version of the compiler when being built with `msbuild`: `<RoslynCompilerType>Framework</RoslynCompilerType>`. This can be added to a `Directory.Build.props` file to impact the entire solution. This is not expected to be a common scenario but is available for customers who need it. This property will be ignored when using `dotnet build` as there is no way to fall back to the Visual Studio compiler in that scenario.

> [!NOTE]
> These values are recognized for property `RoslynCompilerType`:
> - `Core`: use the compiler that comes with the .NET SDK
> - `Framework`: use the compiler that comes with .NET Framework MSBuild
> - `FrameworkPackage`: download the Microsoft.Net.Sdk.Compilers.Toolset package which contains the .NET Framework compiler corresponding to the .NET SDK version

### .NET Framework based analyzers

There are a few analyzers which are built against .NET Framework TFMs. That means when loaded in a .NET Core compiler it could lead to compatibility issues. This is not expected to be a significant issue as our processes have been pushing customers to target `netstandard` in analyzers for 5+ years. However it is possible that some customers will run into issues here.
Expand All @@ -137,6 +131,20 @@ Today there is not a 100% reliable way to shutdown the VBCSCompiler process. The

To mitigate this we will be fixing the `build-server shutdown` command to be reliable across all the scenarios we care about. The details of this are captured in [issue 45956](https://github.com/dotnet/sdk/issues/45956).

## RoslynCompilerType

Based on the value of the `RoslynCompilerType` property, the SDK sets property `RoslynTasksAssembly` to a full path to a [Roslyn build task DLL][roslyn-build-task],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets mention RoslynTargetsPath as well here as getting a compiler properly setup for msbuild seems to require both of these.

Copy link
Member Author

@jjonescz jjonescz Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think RoslynTargetsPath will be still required for msbuild even with this: dotnet/msbuild#12045 ? (Assuming that PR works which it currently does not :D)

Anyway, I'm not sure how to define RoslynTargetsPath. Is it also "guaranteed" to be set by the SDK just like RoslynTasksAssembly? But the toolset package doesn't set RoslynTargetsPath, it sets only RoslynTasksAssembly, isn't that wrong?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it should be guaranteed to be set by SDK projects. That is because it's guaranteed to be set by non-SDK projects, due to msbuid app config, and hence we need to make that it's properly set in all cases. It's been around so long it's seemingly guaranteed that others are depending on it.

and the SDK targets use `$(RoslynTasksAssembly)` to load the build task.

The SDK also sets `RoslynTargetsPath` to the directory path of the roslyn tasks assembly. This property is used by some targets
but it should be avoided if possible because the tasks assembly name can change as well, not just the directory path.

These values are recognized for property `RoslynCompilerType`:
- `Core`: use the compiler that comes with the .NET SDK
- `Framework`: use the compiler that comes with .NET Framework MSBuild
- `FrameworkPackage`: download the Microsoft.Net.Sdk.Compilers.Toolset package which contains the .NET Framework compiler corresponding to the .NET SDK version
- `Custom`: the SDK will not override `RoslynTasksAssembly` - used for example by Microsoft.Net.Compilers.Toolset package which injects its own version of the build task

## Alternative

### Make the compiler in Visual Studio pluggable
Expand Down Expand Up @@ -189,6 +197,7 @@ There is only one version of the DevKit extension. It is released using the late
[matrix-of-paine]: https://aka.ms/dotnet/matrixofpaine
[sdk-lifecycle]: https://learn.microsoft.com/en-us/dotnet/core/porting/versioning-sdk-msbuild-vs#lifecycle
[code-razor-vs-load]: https://github.com/dotnet/roslyn/blob/9aea80927e3d4e5a2846efaa710438c0d8d2bfa2/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs#L1009
[roslyn-build-task]: https://github.com/dotnet/roslyn/blob/ccb05769e5298ac23c01b33a180a0b3715f53a18/src/Compilers/Core/MSBuildTask/README.md
[setup-dotnet]: https://github.com/actions/setup-dotnet
[pr-detect-torn-state]: https://github.com/dotnet/installer/pull/19144
[issue-analyzer-mt]: https://github.com/dotnet/sdk/issues/20355
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ Copyright (c) .NET Foundation. All rights reserved.
</PropertyGroup>

<!-- NOTE: Keep in sync with https://github.com/dotnet/msbuild/blob/main/src/Tasks/Microsoft.Common.tasks -->
<!-- `Condition="Exists('$(RoslynTargetsPath)')` is needed because the package might not be yet downloaded during restore in VS
<!-- `Condition="Exists(...)` is needed because the package might not be yet downloaded during restore in VS
and we do not want to fail the restore phase because of that. -->
<UsingTask TaskName="Microsoft.CodeAnalysis.BuildTasks.CopyRefAssembly" AssemblyFile="$(RoslynTasksAssembly)" Condition="Exists('$(RoslynTasksAssembly)')" />
<UsingTask TaskName="Microsoft.CodeAnalysis.BuildTasks.Csc" AssemblyFile="$(RoslynTasksAssembly)" Condition="Exists('$(RoslynTasksAssembly)')" />
Expand Down
37 changes: 36 additions & 1 deletion test/Microsoft.NET.Build.Tests/RoslynBuildTaskTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using System.Runtime.CompilerServices;
using Basic.CompilerLog.Util;
using Microsoft.Build.Logging.StructuredLogger;
using Microsoft.CodeAnalysis;

namespace Microsoft.NET.Build.Tests;

Expand Down Expand Up @@ -51,6 +53,14 @@ public void FullMSBuild_NonSdkStyle(bool useSharedCompilation, Language language
VerifyCompiler(buildCommand, FxCompilerFileName(language), useSharedCompilation);
}

[FullMSBuildOnlyTheory, CombinatorialData]
public void FullMSBuild_SdkStyle_ToolsetPackage(bool useSharedCompilation, Language language)
{
var testAsset = CreateProject(useSharedCompilation, language, AddCompilersToolsetPackage);
var buildCommand = BuildAndRunUsingMSBuild(testAsset);
VerifyCompiler(buildCommand, FxCompilerFileName(language), useSharedCompilation, toolsetPackage: true);
}

[Theory, CombinatorialData]
public void DotNet(bool useSharedCompilation, Language language)
{
Expand All @@ -59,6 +69,14 @@ public void DotNet(bool useSharedCompilation, Language language)
VerifyCompiler(buildCommand, CoreCompilerFileName(language), useSharedCompilation);
}

[Theory, CombinatorialData]
public void DotNet_ToolsetPackage(bool useSharedCompilation, Language language)
{
var testAsset = CreateProject(useSharedCompilation, language, AddCompilersToolsetPackage);
var buildCommand = BuildAndRunUsingDotNet(testAsset);
VerifyCompiler(buildCommand, CoreCompilerFileName(language), useSharedCompilation, toolsetPackage: true);
}

private TestAsset CreateProject(bool useSharedCompilation, Language language, Action<TestProject>? configure = null, [CallerMemberName] string callingMethod = "")
{
var (projExtension, sourceName, sourceText) = language switch
Expand Down Expand Up @@ -102,6 +120,13 @@ End Module
return _testAssetsManager.CreateTestProject(project, callingMethod: callingMethod, targetExtension: projExtension);
}

private static void AddCompilersToolsetPackage(TestProject project)
{
string roslynVersion = typeof(Compilation).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.Split('+')[0];
Assert.False(string.IsNullOrEmpty(roslynVersion));
project.PackageReferences.Add(new TestPackageReference("Microsoft.Net.Compilers.Toolset", roslynVersion));
}

private TestCommand BuildAndRunUsingMSBuild(TestAsset testAsset)
{
var buildCommand = new MSBuildCommand(testAsset, "Build");
Expand Down Expand Up @@ -130,13 +155,23 @@ private void Run(FileInfo outputFile)
.And.HaveStdOut("42");
}

private static void VerifyCompiler(TestCommand buildCommand, string compilerFileName, bool usedCompilerServer)
private static void VerifyCompiler(TestCommand buildCommand, string compilerFileName, bool usedCompilerServer, bool toolsetPackage = false)
{
var binaryLogPath = Path.Join(buildCommand.WorkingDirectory, "msbuild.binlog");
using (var reader = BinaryLogReader.Create(binaryLogPath))
{
var call = reader.ReadAllCompilerCalls().Should().ContainSingle().Subject;
Path.GetFileName(call.CompilerFilePath).Should().Be(compilerFileName);

const string toolsetPackageName = "microsoft.net.compilers.toolset";
if (toolsetPackage)
{
call.CompilerFilePath.Should().Contain(toolsetPackageName);
}
else
{
call.CompilerFilePath.Should().NotContain(toolsetPackageName);
}
}

// Verify compiler server message.
Expand Down
Loading