diff --git a/src/msbuild/.vsts-dotnet.yml b/src/msbuild/.vsts-dotnet.yml
index 971723d8e9e6..ff755704e125 100644
--- a/src/msbuild/.vsts-dotnet.yml
+++ b/src/msbuild/.vsts-dotnet.yml
@@ -49,7 +49,10 @@ variables:
- name: SkipApplyOptimizationData
value: true
- name: EnableReleaseOneLocBuild
- value: true
+ value: false
+ - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
+ - name: EnableReleaseOneLocBuild
+ value: true
- name: Codeql.Enabled
value: true
- group: DotNet-MSBuild-SDLValidation-Params
diff --git a/src/msbuild/Directory.Build.props b/src/msbuild/Directory.Build.props
index 939e86a11897..9e77a16332d7 100644
--- a/src/msbuild/Directory.Build.props
+++ b/src/msbuild/Directory.Build.props
@@ -15,6 +15,12 @@
XUnitV3
+
+ <_MSBuildXUnitV3Pin>3.2.2
+ <_MSBuildMTPPin>1.9.1
+
+ $(_MSBuildXUnitV3Pin)
+ $(_MSBuildMTPPin)Exe
diff --git a/src/msbuild/eng/Version.Details.xml b/src/msbuild/eng/Version.Details.xml
index cca78c3f67b0..42dac4e29045 100644
--- a/src/msbuild/eng/Version.Details.xml
+++ b/src/msbuild/eng/Version.Details.xml
@@ -1,6 +1,6 @@
-
+
@@ -123,4 +123,4 @@
3454b2fe822e52373f2604856417b0e6bce71d70
-
+
\ No newline at end of file
diff --git a/src/msbuild/eng/Versions.props b/src/msbuild/eng/Versions.props
index 7bcfb77feaa6..ea4e77681353 100644
--- a/src/msbuild/eng/Versions.props
+++ b/src/msbuild/eng/Versions.props
@@ -81,7 +81,7 @@
- 10.0.106
+ 10.0.107
diff --git a/src/msbuild/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs b/src/msbuild/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs
index 7db8ea66dddf..44c8606a77c2 100644
--- a/src/msbuild/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs
+++ b/src/msbuild/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs
@@ -1153,7 +1153,12 @@ public void ProcessLoggingEventConcurrentWithShutdown_DoesNotThrow()
LoggingService loggingService = (LoggingService)LoggingService.CreateLoggingService(LoggerMode.Asynchronous, 1);
((IBuildComponent)loggingService).InitializeComponent(mockHost);
- loggingService.RegisterLogger(new ConsoleLogger());
+
+ loggingService.RegisterLogger(new MockLogger());
+
+ Exception loggingThreadException = null;
+ loggingService.OnLoggingThreadException += ex =>
+ Interlocked.CompareExchange(ref loggingThreadException, ex, null);
using ManualResetEvent startSignal = new ManualResetEvent(false);
Exception caughtException = null;
@@ -1166,7 +1171,10 @@ public void ProcessLoggingEventConcurrentWithShutdown_DoesNotThrow()
{
try
{
- BuildMessageEventArgs msg = new BuildMessageEventArgs($"Message {i}", null, null, MessageImportance.Low);
+ BuildMessageEventArgs msg = new BuildMessageEventArgs($"Message {i}", null, null, MessageImportance.Low)
+ {
+ BuildEventContext = new BuildEventContext(0, 0, 0, 0),
+ };
loggingService.ProcessLoggingEvent(msg);
}
catch (Exception ex)
@@ -1186,6 +1194,7 @@ public void ProcessLoggingEventConcurrentWithShutdown_DoesNotThrow()
bool joined = logThread.Join(TimeSpan.FromSeconds(10));
joined.ShouldBeTrue("Logging thread did not terminate within the allotted time.");
caughtException.ShouldBeNull();
+ loggingThreadException.ShouldBeNull();
}
#endregion
diff --git a/src/msbuild/src/Build.UnitTests/BackEnd/MSBuildClient_Tests.cs b/src/msbuild/src/Build.UnitTests/BackEnd/MSBuildClient_Tests.cs
new file mode 100644
index 000000000000..ed55fd078d9c
--- /dev/null
+++ b/src/msbuild/src/Build.UnitTests/BackEnd/MSBuildClient_Tests.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System;
+using System.IO;
+using System.Threading;
+using Microsoft.Build.Experimental;
+using Microsoft.Build.UnitTests;
+using Shouldly;
+using Xunit;
+
+namespace Microsoft.Build.UnitTests.BackEnd
+{
+ ///
+ /// Tests for the fallback behaviour.
+ ///
+ public class MSBuildClient_Tests
+ {
+ private readonly ITestOutputHelper _output;
+
+ public MSBuildClient_Tests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ ///
+ /// When the configured msbuild executable does not exist, launching the server fails.
+ /// must return a recoverable exit type rather than
+ /// letting an exception escape — it is the host's contract that any failure inside
+ /// Execute routes through so callers (e.g.
+ /// MSBuildClientApp) can fall back to in-process execution.
+ ///
+ ///
+ /// Regression coverage for the .NET 10.0.300 / Aspire timeout: when
+ /// DOTNET_CLI_USE_MSBUILD_SERVER=true is honoured but the server child cannot
+ /// start (e.g. the apphost can't find the .NET runtime),
+ /// must not propagate a or any other exception.
+ /// Pre-fix, an uncaught TimeoutException from NamedPipeClientStream.Connect
+ /// escaped past MSBuildClientApp and crashed the CLI; this test locks in the
+ /// no-exception-escape contract by simply calling Execute outside any try/catch
+ /// and asserting on the structured result.
+ ///
+ [Fact]
+ public void Execute_WithUnreachableServer_DoesNotPropagateException()
+ {
+ // Isolate from any real MSBuild server / DOTNET_CLI_USE_MSBUILD_SERVER state on
+ // the dev or CI machine. Without this the named-mutex check in MSBuildClient can
+ // observe a warm server from another test/run, which makes the assertions below
+ // non-deterministic.
+ using TestEnvironment env = TestEnvironment.Create(_output);
+ env.SetEnvironmentVariable("DOTNET_CLI_USE_MSBUILD_SERVER", null);
+ env.SetEnvironmentVariable("MSBUILDUSESERVER", null);
+
+ string[] commandLine = ["dummy.proj"];
+ string nonexistentMsBuild = Path.Combine(
+ Path.GetTempPath(),
+ "msbuildclient-tests-" + Guid.NewGuid().ToString("N"),
+ "MSBuild.dll");
+
+ MSBuildClient client = new MSBuildClient(commandLine, nonexistentMsBuild);
+
+ // The whole point of the regression fix: this must NOT throw. xUnit fails the
+ // test with the offending stack if any exception escapes, which is the
+ // primary regression contract being verified.
+ MSBuildClientExitResult result = client.Execute(CancellationToken.None);
+
+ result.ShouldNotBeNull();
+
+ // The unreachable-server path must produce one of the recoverable failure types
+ // so that MSBuildClientApp can fall back to in-process execution. Crucially,
+ // ServerBusy is excluded here: ServerBusy is the "another client is racing for
+ // the launch mutex" path and is not what an unreachable server should produce —
+ // accepting it would mask a real regression where the failure was misclassified.
+ result.MSBuildClientExitType.ShouldBeOneOf(
+ MSBuildClientExitType.LaunchError,
+ MSBuildClientExitType.UnableToConnect,
+ MSBuildClientExitType.UnknownServerState);
+
+ // No server child was successfully launched, so the diagnostic helper should not
+ // have observed an exit code. (Once issue #13718 lands and the diagnostic helper
+ // is plumbed through every connect failure, this gives MSBuildClientApp the right
+ // signal to pick the generic "server unavailable" message rather than the more
+ // specific "crashed with exit code N" one.)
+ result.ServerProcessExitCode.ShouldBeNull();
+ }
+ }
+}
diff --git a/src/msbuild/src/Build.UnitTests/BackEnd/TaskRouter_IntegrationTests.cs b/src/msbuild/src/Build.UnitTests/BackEnd/TaskRouter_IntegrationTests.cs
index a5b1b7627520..fa60eee08894 100644
--- a/src/msbuild/src/Build.UnitTests/BackEnd/TaskRouter_IntegrationTests.cs
+++ b/src/msbuild/src/Build.UnitTests/BackEnd/TaskRouter_IntegrationTests.cs
@@ -3,8 +3,11 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Reflection;
+using System.Text.RegularExpressions;
+using Microsoft.Build.BackEnd;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
@@ -381,7 +384,256 @@ public void ExplicitTaskHostFactory_OverridesRoutingLogic()
logger.FullLog.ShouldContain("TaskWithAttribute executed");
}
+ [Fact]
+ public void RequiresTransientTaskHost_ReturnsTrueForRestoreTask()
+ {
+ TaskRouter.RequiresTransientTaskHost(typeof(NuGet.Build.Tasks.RestoreTask)).ShouldBeTrue();
+ }
+
+ [Fact]
+ public void RequiresTransientTaskHost_ReturnsFalseForRegularTask()
+ {
+ TaskRouter.RequiresTransientTaskHost(typeof(NonEnlightenedTestTask)).ShouldBeFalse();
+ TaskRouter.RequiresTransientTaskHost(typeof(AttributeTestTask)).ShouldBeFalse();
+ }
+
+ [Fact]
+ public void RequiresTransientTaskHost_RoutedToTaskHost_InMultiThreadedMode()
+ {
+ string projectContent = $@"
+
+
+
+
+
+
+";
+
+ string projectFile = Path.Combine(_testProjectsDir, "RestoreTaskMT.proj");
+ File.WriteAllText(projectFile, projectContent);
+
+ var logger = new MockLogger(_output);
+ var buildParameters = new BuildParameters
+ {
+ MultiThreaded = true,
+ Loggers = [logger],
+ DisableInProcNode = false,
+ EnableNodeReuse = false,
+ };
+
+ var buildRequestData = new BuildRequestData(
+ projectFile,
+ new Dictionary(),
+ null,
+ ["TestTarget"],
+ null);
+
+ BuildManager buildManager = BuildManager.DefaultBuildManager;
+ BuildResult result = buildManager.Build(buildParameters, buildRequestData);
+
+ result.OverallResult.ShouldBe(BuildResultCode.Success);
+ TaskRouterTestHelper.AssertTaskUsedTaskHost(logger, "RestoreTask");
+ logger.FullLog.ShouldContain("RestoreTask executed");
+ }
+
+ [Fact]
+ public void RequiresTransientTaskHost_RoutedToTaskHost_InServerMode()
+ {
+ string projectContent = $@"
+
+
+
+
+
+
+";
+
+ string projectFile = Path.Combine(_testProjectsDir, "RestoreTaskServer.proj");
+ File.WriteAllText(projectFile, projectContent);
+
+ var logger = new MockLogger(_output);
+ var buildParameters = new BuildParameters
+ {
+ MultiThreaded = false,
+ Loggers = [logger],
+ DisableInProcNode = false,
+ EnableNodeReuse = false,
+
+ // Simulate running under the MSBuild Server. In production this flag is
+ // set process-wide by OutOfProcServerNode via BuildParameters.MarkProcessAsLongLivedHost.
+ IsLongLivedHost = true,
+ };
+
+ var buildRequestData = new BuildRequestData(
+ projectFile,
+ new Dictionary(),
+ null,
+ ["TestTarget"],
+ null);
+
+ BuildManager buildManager = BuildManager.DefaultBuildManager;
+ BuildResult result = buildManager.Build(buildParameters, buildRequestData);
+
+ result.OverallResult.ShouldBe(BuildResultCode.Success);
+ TaskRouterTestHelper.AssertTaskUsedTaskHost(logger, "RestoreTask");
+ logger.FullLog.ShouldContain("RestoreTask executed");
+ }
+
+ [Fact]
+ public void RequiresTransientTaskHost_GetsFreshProcess_OnEachInvocation_InMultiThreadedMode()
+ {
+ string projectContent = $@"
+
+
+
+
+
+
+";
+
+ string projectFile = Path.Combine(_testProjectsDir, "RestoreTaskFreshProcess.proj");
+ File.WriteAllText(projectFile, projectContent);
+
+ var logger = new MockLogger(_output);
+ var buildParameters = new BuildParameters
+ {
+ MultiThreaded = true,
+ Loggers = [logger],
+ DisableInProcNode = false,
+
+ // Reuse must stay ON so we test the workaround, not natural process death.
+ // With reuse off the test cannot distinguish "transient TaskHost (workaround)"
+ // from "sidecar TaskHost that happened to die between builds".
+ EnableNodeReuse = true,
+ };
+
+ var buildRequestData = new BuildRequestData(
+ projectFile,
+ new Dictionary(),
+ null,
+ ["TestTarget"],
+ null);
+
+ // Two separate Build cycles. The workaround forces nodeReuse=false on the spawned
+ // TaskHost so it dies at EndBuild, giving the next Build a fresh process.
+ BuildManager buildManager = BuildManager.DefaultBuildManager;
+ BuildResult result1 = buildManager.Build(buildParameters, buildRequestData);
+ BuildResult result2 = buildManager.Build(buildParameters, buildRequestData);
+
+ result1.OverallResult.ShouldBe(BuildResultCode.Success);
+ result2.OverallResult.ShouldBe(BuildResultCode.Success);
+ TaskRouterTestHelper.AssertTaskUsedTaskHost(logger, "RestoreTask");
+
+ int[] pids = ExtractReportedPids(logger.FullLog);
+
+ pids.Length.ShouldBe(2, $"Expected two RestoreTask invocations to log a PID. Log:{Environment.NewLine}{logger.FullLog}");
+ pids[0].ShouldNotBe(pids[1], "Each build must spawn a fresh TaskHost so NuGet static state cannot leak across builds.");
+ pids.ShouldNotContain(Process.GetCurrentProcess().Id, "TaskHost should be out-of-process from the test runner.");
+ }
+
+ [Fact]
+ public void RequiresTransientTaskHost_GetsFreshProcess_OnEachInvocation_InServerMode()
+ {
+ string projectContent = $@"
+
+
+
+
+
+
+";
+
+ string projectFile = Path.Combine(_testProjectsDir, "RestoreTaskFreshProcessServer.proj");
+ File.WriteAllText(projectFile, projectContent);
+ var logger = new MockLogger(_output);
+ var buildParameters = new BuildParameters
+ {
+ MultiThreaded = false,
+ Loggers = [logger],
+ DisableInProcNode = false,
+
+ // Reuse must stay ON so we test the workaround, not natural process death.
+ EnableNodeReuse = true,
+
+ // Simulate running under the MSBuild Server.
+ IsLongLivedHost = true,
+ };
+
+ var buildRequestData = new BuildRequestData(
+ projectFile,
+ new Dictionary(),
+ null,
+ ["TestTarget"],
+ null);
+
+ // Two separate Build cycles. The workaround forces nodeReuse=false on the spawned
+ // TaskHost so it dies at EndBuild, giving the next Build a fresh process.
+ BuildManager buildManager = BuildManager.DefaultBuildManager;
+ BuildResult result1 = buildManager.Build(buildParameters, buildRequestData);
+ BuildResult result2 = buildManager.Build(buildParameters, buildRequestData);
+
+ result1.OverallResult.ShouldBe(BuildResultCode.Success);
+ result2.OverallResult.ShouldBe(BuildResultCode.Success);
+ TaskRouterTestHelper.AssertTaskUsedTaskHost(logger, "RestoreTask");
+
+ int[] pids = ExtractReportedPids(logger.FullLog);
+
+ pids.Length.ShouldBe(2, $"Expected two RestoreTask invocations to log a PID. Log:{Environment.NewLine}{logger.FullLog}");
+ pids[0].ShouldNotBe(pids[1], "Each build must spawn a fresh TaskHost so NuGet static state cannot leak across builds.");
+ pids.ShouldNotContain(Process.GetCurrentProcess().Id, "TaskHost should be out-of-process from the test runner.");
+ }
+
+ private static int[] ExtractReportedPids(string log)
+ {
+ var pids = new List();
+ foreach (Match m in Regex.Matches(log, @"RestoreTask executed in PID=(\d+)"))
+ {
+ pids.Add(int.Parse(m.Groups[1].Value));
+ }
+
+ return pids.ToArray();
+ }
+
+ [Fact]
+ public void RequiresTransientTaskHost_RunsInProcess_WhenNoMTOrServer()
+ {
+ string projectContent = $@"
+
+
+
+
+
+
+";
+
+ string projectFile = Path.Combine(_testProjectsDir, "RestoreTaskNoMT.proj");
+ File.WriteAllText(projectFile, projectContent);
+
+ var logger = new MockLogger(_output);
+ var buildParameters = new BuildParameters
+ {
+ MultiThreaded = false,
+ Loggers = [logger],
+ DisableInProcNode = false,
+ EnableNodeReuse = false,
+ };
+
+ var buildRequestData = new BuildRequestData(
+ projectFile,
+ new Dictionary(),
+ null,
+ ["TestTarget"],
+ null);
+
+ BuildManager buildManager = BuildManager.DefaultBuildManager;
+ BuildResult result = buildManager.Build(buildParameters, buildRequestData);
+
+ result.OverallResult.ShouldBe(BuildResultCode.Success);
+
+ TaskRouterTestHelper.AssertTaskRanInProcess(logger, "RestoreTask");
+ logger.FullLog.ShouldContain("RestoreTask executed");
+ }
private string CreateTestProject(string taskName, string taskClass)
{
@@ -483,6 +735,25 @@ public override bool Execute()
#endregion
}
+// Test task in the NuGet.Build.Tasks namespace to simulate the real RestoreTask for routing tests.
+namespace NuGet.Build.Tasks
+{
+ ///
+ /// Simulates the NuGet RestoreTask for testing task routing workaround.
+ /// Has the same full name (NuGet.Build.Tasks.RestoreTask) that TaskRouter checks.
+ ///
+ public class RestoreTask : Task
+ {
+ public override bool Execute()
+ {
+ // Include the OS PID so tests can verify each invocation runs in a fresh
+ // TaskHost process
+ Log.LogMessage(MessageImportance.High, $"RestoreTask executed in PID={Process.GetCurrentProcess().Id}");
+ return true;
+ }
+ }
+}
+
// Custom attribute definition in Microsoft.Build.Framework namespace to match what TaskRouter expects
// TaskRouter looks for attributes with FullName = "Microsoft.Build.Framework.MSBuildMultiThreadableTaskAttribute"
// Since the real attribute is internal in Framework, we define our own test version here
diff --git a/src/msbuild/src/Build.UnitTests/FileLogger_Tests.cs b/src/msbuild/src/Build.UnitTests/FileLogger_Tests.cs
index 28161a9a7831..09d52d7dcaff 100644
--- a/src/msbuild/src/Build.UnitTests/FileLogger_Tests.cs
+++ b/src/msbuild/src/Build.UnitTests/FileLogger_Tests.cs
@@ -8,6 +8,7 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
using Microsoft.Build.Shared;
+using Shouldly;
using Xunit;
using EventSourceSink = Microsoft.Build.BackEnd.Logging.EventSourceSink;
using Project = Microsoft.Build.Evaluation.Project;
@@ -18,6 +19,13 @@ namespace Microsoft.Build.UnitTests
{
public class FileLogger_Tests
{
+ private readonly ITestOutputHelper _output;
+
+ public FileLogger_Tests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
///
/// Basic test of the file logger. Writes to a log file in the temp directory.
///
@@ -482,44 +490,48 @@ private void VerifyFileContent(string file, string expectedContent)
[Fact]
public void DistributedFileLoggerParameters()
{
- DistributedFileLogger fileLogger = new DistributedFileLogger();
- try
- {
- fileLogger.NodeId = 0;
- fileLogger.Initialize(new EventSourceSink());
- Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=msbuild0.log;", StringComparison.OrdinalIgnoreCase));
- fileLogger.Shutdown();
+ using TestEnvironment env = TestEnvironment.Create(_output);
- fileLogger.NodeId = 3;
- fileLogger.Parameters = "logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile.log");
- fileLogger.Initialize(new EventSourceSink());
- Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile3.log") + ";", StringComparison.OrdinalIgnoreCase));
- fileLogger.Shutdown();
+ // Use a transient folder under TEMP as the current directory so log files (notably the
+ // default "msbuild0.log" produced when no logfile parameter is specified) don't leak into
+ // the test bin directory, where another process (e.g. an antivirus scanner or a parallel
+ // test) may transiently lock them and cause this test to fail.
+ TransientTestFolder folder = env.CreateFolder();
+ env.SetCurrentDirectory(folder.Path);
- fileLogger.NodeId = 4;
- fileLogger.Parameters = "logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile.log");
- fileLogger.Initialize(new EventSourceSink());
- Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile4.log") + ";", StringComparison.OrdinalIgnoreCase));
- fileLogger.Shutdown();
+ DistributedFileLogger fileLogger = new DistributedFileLogger();
- Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "tempura"));
- fileLogger.NodeId = 1;
- fileLogger.Parameters = "logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "tempura", "mylogfile.log");
- fileLogger.Initialize(new EventSourceSink());
- Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "tempura", "mylogfile1.log") + ";", StringComparison.OrdinalIgnoreCase));
- fileLogger.Shutdown();
- }
- finally
- {
- if (Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "tempura")))
- {
- File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "tempura", "mylogfile1.log"));
- FileUtilities.DeleteWithoutTrailingBackslash(Path.Combine(Directory.GetCurrentDirectory(), "tempura"));
- }
- File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "mylogfile0.log"));
- File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "mylogfile3.log"));
- File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "mylogfile4.log"));
- }
+ fileLogger.NodeId = 0;
+ fileLogger.Initialize(new EventSourceSink());
+ fileLogger.InternalFilelogger.Parameters.ShouldBe(
+ "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=msbuild0.log;",
+ StringCompareShould.IgnoreCase);
+ fileLogger.Shutdown();
+
+ fileLogger.NodeId = 3;
+ fileLogger.Parameters = "logfile=" + Path.Combine(folder.Path, "mylogfile.log");
+ fileLogger.Initialize(new EventSourceSink());
+ fileLogger.InternalFilelogger.Parameters.ShouldBe(
+ "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(folder.Path, "mylogfile3.log") + ";",
+ StringCompareShould.IgnoreCase);
+ fileLogger.Shutdown();
+
+ fileLogger.NodeId = 4;
+ fileLogger.Parameters = "logfile=" + Path.Combine(folder.Path, "mylogfile.log");
+ fileLogger.Initialize(new EventSourceSink());
+ fileLogger.InternalFilelogger.Parameters.ShouldBe(
+ "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(folder.Path, "mylogfile4.log") + ";",
+ StringCompareShould.IgnoreCase);
+ fileLogger.Shutdown();
+
+ TransientTestFolder subfolder = folder.CreateDirectory("tempura");
+ fileLogger.NodeId = 1;
+ fileLogger.Parameters = "logfile=" + Path.Combine(subfolder.Path, "mylogfile.log");
+ fileLogger.Initialize(new EventSourceSink());
+ fileLogger.InternalFilelogger.Parameters.ShouldBe(
+ "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(subfolder.Path, "mylogfile1.log") + ";",
+ StringCompareShould.IgnoreCase);
+ fileLogger.Shutdown();
}
[Fact]
diff --git a/src/msbuild/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/msbuild/src/Build/BackEnd/BuildManager/BuildParameters.cs
index 1920c649399f..be053243ffad 100644
--- a/src/msbuild/src/Build/BackEnd/BuildManager/BuildParameters.cs
+++ b/src/msbuild/src/Build/BackEnd/BuildManager/BuildParameters.cs
@@ -64,6 +64,12 @@ public class BuildParameters : ITranslatable
///
private static string s_startupDirectory = NativeMethodsShared.GetCurrentDirectory();
+ ///
+ /// Process-wide flag indicating that the engine is hosted in a long-lived process
+ /// (e.g., the MSBuild Server) that persists across multiple build invocations.
+ ///
+ private static bool s_isLongLivedHost;
+
///
/// Indicates whether we should warn when a property is uninitialized when it is used.
///
@@ -328,6 +334,7 @@ internal BuildParameters(BuildParameters other, bool resetEnvironment = false)
Question = other.Question;
IsBuildCheckEnabled = other.IsBuildCheckEnabled;
IsTelemetryEnabled = other.IsTelemetryEnabled;
+ IsLongLivedHost = other.IsLongLivedHost;
ProjectCacheDescriptor = other.ProjectCacheDescriptor;
_enableTargetOutputLogging = other.EnableTargetOutputLogging;
}
@@ -799,6 +806,25 @@ internal IToolsetProvider ToolsetProvider
///
internal bool IsOutOfProc { get; set; }
+ ///
+ /// True when the engine is hosted in a long-lived process (e.g., MSBuild Server)
+ /// that persists across multiple build invocations. Used to opt tasks whose static
+ /// singleton state would leak across invocations out of sidecar TaskHost reuse.
+ /// See https://github.com/dotnet/msbuild/issues/13315.
+ ///
+ internal bool IsLongLivedHost
+ {
+ get => _isLongLivedHost;
+ set => _isLongLivedHost = value;
+ }
+
+ private bool _isLongLivedHost = s_isLongLivedHost;
+
+ ///
+ /// Marks the current process as a long-lived host.
+ ///
+ internal static void MarkProcessAsLongLivedHost() => s_isLongLivedHost = true;
+
///
public ProjectLoadSettings ProjectLoadSettings
{
@@ -978,6 +1004,7 @@ void ITranslatable.Translate(ITranslator translator)
translator.Translate(ref _reportFileAccesses);
translator.Translate(ref _enableTargetOutputLogging);
translator.Translate(ref _multiThreaded);
+ translator.Translate(ref _isLongLivedHost);
// ProjectRootElementCache is not transmitted.
// ResetCaches is not transmitted.
diff --git a/src/msbuild/src/Build/BackEnd/Client/MSBuildClient.cs b/src/msbuild/src/Build/BackEnd/Client/MSBuildClient.cs
index c10736832560..4c6a61fb3621 100644
--- a/src/msbuild/src/Build/BackEnd/Client/MSBuildClient.cs
+++ b/src/msbuild/src/Build/BackEnd/Client/MSBuildClient.cs
@@ -101,6 +101,12 @@ public sealed class MSBuildClient
///
private MSBuildClientPacketPump _packetPump = null!;
+ ///
+ /// PID of the server process this client launched (or null if no launch was attempted /
+ /// the server was already running). Used for diagnostics on connection failure.
+ ///
+ private int? _launchedServerPid;
+
///
/// Public constructor with parameters.
///
@@ -459,8 +465,19 @@ private bool TryLaunchServer()
];
NodeLauncher nodeLauncher = new NodeLauncher();
CommunicationsUtilities.Trace("Starting Server...");
- using Process msbuildProcess = nodeLauncher.Start(new NodeLaunchData(_msbuildLocation, string.Join(" ", msBuildServerOptions)), nodeId: 0);
- CommunicationsUtilities.Trace($"Server started with PID: {msbuildProcess?.Id}");
+
+ // Set DOTNET_ROOT so the apphost server child can locate the runtime; this
+ // override is replaced by the client's environment on the first build command
+ // (see OutOfProcServerNode.HandleServerNodeBuildCommand → SetEnvironment).
+ // The `!` works around dotnet/msbuild#13761.
+ NodeLaunchData launchData = new(
+ MSBuildLocation: _msbuildLocation,
+ CommandLineArgs: string.Join(" ", msBuildServerOptions),
+ EnvironmentOverrides: DotnetHostEnvironmentHelper.CreateDotnetRootEnvironmentOverrides()!);
+
+ using Process msbuildProcess = nodeLauncher.Start(launchData, nodeId: 0);
+ _launchedServerPid = msbuildProcess.Id;
+ CommunicationsUtilities.Trace($"Server started with PID: {_launchedServerPid}");
}
catch (Exception ex)
{
@@ -605,9 +622,36 @@ private bool TryConnectToServer(int timeoutMilliseconds)
{
tryAgain = false;
+ HandshakeResult result;
+ bool connected;
+ try
+ {
+ connected = NodeProviderOutOfProcBase.TryConnectToPipeStream(
+ _nodeStream, _pipeName, _handshake, Math.Max(1, timeoutMilliseconds - (int)sw.ElapsedMilliseconds), out result);
+ }
+ catch (TimeoutException)
+ {
+ // The underlying NamedPipeClientStream.Connect throws TimeoutException when the
+ // pipe never becomes available — typically because the server child process
+ // failed to start (e.g. apphost couldn't locate the runtime). Treat this as a
+ // recoverable connection failure so MSBuildClientApp can fall back to in-proc
+ // execution rather than crashing the whole CLI.
+ LogConnectFailureDiagnostics(timeoutMilliseconds, isTimeout: true, errorMessage: null);
+ _exitResult.MSBuildClientExitType = MSBuildClientExitType.UnableToConnect;
+ return false;
+ }
+ catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
+ {
+ // Mirror the exception-tolerant behavior of NodeProviderOutOfProcBase.TryConnectToProcess
+ // so any non-critical failure (UnauthorizedAccessException, IOException,
+ // InvalidOperationException, etc.) routes through the standard fallback path
+ // rather than escaping out of MSBuildClient.Execute.
+ LogConnectFailureDiagnostics(timeoutMilliseconds, isTimeout: false, errorMessage: ex.Message);
+ _exitResult.MSBuildClientExitType = MSBuildClientExitType.UnableToConnect;
+ return false;
+ }
- if (NodeProviderOutOfProcBase.TryConnectToPipeStream(
- _nodeStream, _pipeName, _handshake, Math.Max(1, timeoutMilliseconds - (int)sw.ElapsedMilliseconds), out HandshakeResult result))
+ if (connected)
{
return true;
}
@@ -623,7 +667,7 @@ private bool TryConnectToServer(int timeoutMilliseconds)
}
else
{
- CommunicationsUtilities.Trace($"Failed to connect to server: {result.ErrorMessage}");
+ LogConnectFailureDiagnostics(timeoutMilliseconds, isTimeout: result.Status is HandshakeStatus.Timeout, errorMessage: result.ErrorMessage);
_exitResult.MSBuildClientExitType = MSBuildClientExitType.UnableToConnect;
return false;
}
@@ -633,6 +677,57 @@ private bool TryConnectToServer(int timeoutMilliseconds)
return false;
}
+ ///
+ /// Emits a single diagnostic trace entry describing why connection to the MSBuild server
+ /// failed, including the launched server PID (if any) and its current state. This makes
+ /// the otherwise-opaque 20s timeout actionable when MSBUILDDEBUGCOMM tracing is enabled.
+ /// Also populates when the
+ /// launched server child has already exited, so the host can surface that fact to the
+ /// user-visible "falling back to in-proc" message instead of a generic timeout.
+ ///
+ private void LogConnectFailureDiagnostics(int timeoutMilliseconds, bool isTimeout, string? errorMessage)
+ {
+ string serverState;
+ if (_launchedServerPid is int pid)
+ {
+ try
+ {
+ using Process launched = Process.GetProcessById(pid);
+ if (launched.HasExited)
+ {
+ _exitResult.ServerProcessExitCode = launched.ExitCode;
+ serverState = $"PID {pid} (already exited with code {launched.ExitCode})";
+ }
+ else
+ {
+ serverState = $"PID {pid} (still running)";
+ }
+ }
+ catch (ArgumentException)
+ {
+ // Process already terminated and was reaped before we could query it.
+ serverState = $"PID {pid} (already exited)";
+ }
+ catch (InvalidOperationException)
+ {
+ serverState = $"PID {pid} (state unavailable)";
+ }
+ }
+ else
+ {
+ serverState = "no launch attempted (server reported as already running)";
+ }
+
+ string reason = isTimeout
+ ? $"timed out after {timeoutMilliseconds} ms waiting for the named pipe"
+ : $"connection error: {errorMessage}";
+
+ CommunicationsUtilities.Trace(
+ $"MSBuild server connection failed ({reason}). Launched server: {serverState}. " +
+ "Falling back to in-proc build. " +
+ "If the server child process exited immediately, ensure DOTNET_ROOT is set correctly so the apphost can locate the .NET runtime.");
+ }
+
private void WritePacket(Stream nodeStream, INodePacket packet)
{
MemoryStream memoryStream = _packetMemoryStream;
diff --git a/src/msbuild/src/Build/BackEnd/Client/MSBuildClientExitResult.cs b/src/msbuild/src/Build/BackEnd/Client/MSBuildClientExitResult.cs
index ef58bba7517d..76cf5d9ed4c4 100644
--- a/src/msbuild/src/Build/BackEnd/Client/MSBuildClientExitResult.cs
+++ b/src/msbuild/src/Build/BackEnd/Client/MSBuildClientExitResult.cs
@@ -20,5 +20,14 @@ public sealed class MSBuildClientExitResult
/// This field is null if MSBuild client execution was not successful.
///
public string? MSBuildAppExitTypeString { get; set; }
+
+ ///
+ /// When this client launched a server child process and that process had already exited
+ /// by the time we observed the connection failure, this is its exit code. null
+ /// otherwise (server still running, never launched, or its state could not be queried).
+ /// Hosts use this to surface "server crashed immediately on launch" to the user instead
+ /// of a generic timeout message.
+ ///
+ public int? ServerProcessExitCode { get; set; }
}
}
diff --git a/src/msbuild/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs b/src/msbuild/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs
index b86f689f60d9..c597b77bc61f 100644
--- a/src/msbuild/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs
+++ b/src/msbuild/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs
@@ -631,11 +631,17 @@ internal bool AcquireAndSetUpHost(
INodePacketFactory factory,
INodePacketHandler handler,
TaskHostConfiguration configuration,
- in TaskHostParameters taskHostParameters)
+ in TaskHostParameters taskHostParameters,
+ out int hostProcessId,
+ out bool wasNewlyCreated)
{
+ hostProcessId = -1;
+ wasNewlyCreated = false;
+
bool nodeCreationSucceeded;
if (!_nodeContexts.ContainsKey(nodeKey))
{
+ wasNewlyCreated = true;
nodeCreationSucceeded = CreateNode(nodeKey, factory, handler, configuration, taskHostParameters);
}
else
@@ -654,6 +660,16 @@ internal bool AcquireAndSetUpHost(
handlerStack.Push(handler);
}
+ try
+ {
+ hostProcessId = context.Process?.Id ?? -1;
+ }
+ catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
+ {
+ // Process has already exited or is otherwise inaccessible; PID is unavailable.
+ hostProcessId = -1;
+ }
+
// Configure the node.
context.SendData(configuration);
return true;
diff --git a/src/msbuild/src/Build/BackEnd/Components/RequestBuilder/TaskRouter.cs b/src/msbuild/src/Build/BackEnd/Components/RequestBuilder/TaskRouter.cs
index 3b9a6252e1a5..8262c7170d2d 100644
--- a/src/msbuild/src/Build/BackEnd/Components/RequestBuilder/TaskRouter.cs
+++ b/src/msbuild/src/Build/BackEnd/Components/RequestBuilder/TaskRouter.cs
@@ -84,6 +84,37 @@ private static bool HasMultiThreadableTaskAttribute(Type taskType)
});
}
+ ///
+ /// Full name of a task whose static singleton state makes it unsafe to run in a
+ /// long-lived sidecar TaskHost (which persists across invocations). Such tasks must
+ /// instead run in an explicit (transient) TaskHost that terminates after execution,
+ /// ensuring static state is cleaned up.
+ /// This is a temporary workaround until the task authors fix their static state issues.
+ /// See https://github.com/dotnet/msbuild/issues/13315
+ ///
+ private const string TaskRequiringTransientTaskHostFullName = "NuGet.Build.Tasks.RestoreTask";
+
+ ///
+ /// Determines if a task must be routed to an explicit (transient) TaskHost rather than
+ /// a reusable sidecar, because its static singleton state would leak across invocations.
+ /// Such tasks should run in a TaskHost that terminates after execution so all static
+ /// state is cleaned up.
+ ///
+ /// The type of the task to evaluate.
+ /// True if the task requires a transient TaskHost; false otherwise.
+ public static bool RequiresTransientTaskHost(Type taskType)
+ {
+ ErrorUtilities.VerifyThrowArgumentNull(taskType, nameof(taskType));
+
+ string? fullName = taskType.FullName;
+ if (fullName is null)
+ {
+ return false;
+ }
+
+ return string.Equals(fullName, TaskRequiringTransientTaskHostFullName, StringComparison.Ordinal);
+ }
+
///
/// Clears the thread-safety cache. Used primarily for testing.
///
diff --git a/src/msbuild/src/Build/BackEnd/Node/OutOfProcServerNode.cs b/src/msbuild/src/Build/BackEnd/Node/OutOfProcServerNode.cs
index 3abc9faaaab4..dca6ba59177f 100644
--- a/src/msbuild/src/Build/BackEnd/Node/OutOfProcServerNode.cs
+++ b/src/msbuild/src/Build/BackEnd/Node/OutOfProcServerNode.cs
@@ -108,6 +108,13 @@ public NodeEngineShutdownReason Run(out Exception? shutdownException)
return NodeEngineShutdownReason.Error;
}
+ // Mark the process as a long-lived host so per-build BuildParameters instances
+ // inherit IsLongLivedHost = true. This drives the workaround that routes tasks
+ // whose static state would leak across invocations (e.g., NuGet RestoreTask) to
+ // a transient TaskHost instead of a reusable sidecar.
+ // See https://github.com/dotnet/msbuild/issues/13315.
+ BuildParameters.MarkProcessAsLongLivedHost();
+
while (true)
{
NodeEngineShutdownReason shutdownReason = RunInternal(out shutdownException, handshake);
@@ -381,6 +388,7 @@ private void HandleServerNodeBuildCommand(ServerNodeBuildCommand command)
Directory.SetCurrentDirectory(command.StartupDirectory);
CommunicationsUtilities.SetEnvironment(command.BuildProcessEnvironment);
+
Traits.UpdateFromEnvironment();
Thread.CurrentThread.CurrentCulture = command.Culture;
diff --git a/src/msbuild/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs b/src/msbuild/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs
index ea47b86f1ca7..a496b6ee37a5 100644
--- a/src/msbuild/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs
+++ b/src/msbuild/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs
@@ -348,6 +348,23 @@ internal ITask CreateTaskInstance(
}
}
+ // Workaround for tasks whose static singleton state would leak across invocations
+ // (e.g., NuGet RestoreTask). In MT mode or when MSBuild server is active, these tasks
+ // must run in a transient (non-sidecar) TaskHost so static state is cleaned up after
+ // each invocation. See https://github.com/dotnet/msbuild/issues/13315
+ bool forceTransientTaskHost = false;
+ if (_loadedType?.Type != null && TaskRouter.RequiresTransientTaskHost(_loadedType.Type))
+ {
+ bool isMultiThreaded = buildComponentHost?.BuildParameters?.MultiThreaded == true;
+ bool isLongLivedHost = buildComponentHost?.BuildParameters?.IsLongLivedHost == true;
+
+ if (isMultiThreaded || isLongLivedHost)
+ {
+ useTaskFactory = true;
+ forceTransientTaskHost = true;
+ }
+ }
+
taskLoggingContext?.TargetLoggingContext?.ProjectLoggingContext?.ProjectTelemetry?.AddTaskExecution(GetType().FullName, isTaskHost: useTaskFactory);
if (useTaskFactory)
@@ -362,7 +379,9 @@ internal ITask CreateTaskInstance(
// If the task host factory is explicitly requested, do not act as a sidecar task host.
// This is important as customers use task host factories for short lived tasks to release
// potential locks.
- bool useSidecarTaskHost = !(_factoryIdentityParameters.TaskHostFactoryExplicitlyRequested ?? false);
+ // Also disable sidecar for tasks that require a transient TaskHost so their
+ // static state is cleaned up between invocations.
+ bool useSidecarTaskHost = !forceTransientTaskHost && !(_factoryIdentityParameters.TaskHostFactoryExplicitlyRequested ?? false);
TaskHostTask task = new(
taskLocation,
diff --git a/src/msbuild/src/Build/Instance/TaskFactories/TaskHostTask.cs b/src/msbuild/src/Build/Instance/TaskFactories/TaskHostTask.cs
index 0cc1e8ba1c39..00cd69ef5a60 100644
--- a/src/msbuild/src/Build/Instance/TaskFactories/TaskHostTask.cs
+++ b/src/msbuild/src/Build/Instance/TaskFactories/TaskHostTask.cs
@@ -355,21 +355,44 @@ public bool Execute()
try
{
+ int hostProcessId;
+ bool wasNewlyCreated;
+ bool effectiveNodeReuse;
+
lock (_taskHostLock)
{
+ effectiveNodeReuse = _buildComponentHost.BuildParameters.EnableNodeReuse && _useSidecarTaskHost;
+
_requiredContext = CommunicationsUtilities.GetHandshakeOptions(
taskHost: true,
// Determine if we should use node reuse based on build parameters or user preferences (comes from UsingTask element).
- nodeReuse: _buildComponentHost.BuildParameters.EnableNodeReuse && _useSidecarTaskHost,
+ nodeReuse: effectiveNodeReuse,
taskHostParameters: _taskHostParameters);
_taskHostNodeKey = new TaskHostNodeKey(_requiredContext, _scheduledNodeId);
- _connectedToTaskHost = _taskHostProvider.AcquireAndSetUpHost(_taskHostNodeKey, this, this, hostConfiguration, _taskHostParameters);
+ _connectedToTaskHost = _taskHostProvider.AcquireAndSetUpHost(
+ _taskHostNodeKey,
+ this,
+ this,
+ hostConfiguration,
+ _taskHostParameters,
+ out hostProcessId,
+ out wasNewlyCreated);
}
if (_connectedToTaskHost)
{
+ _taskLoggingContext.LogComment(
+ MessageImportance.Low,
+ "TaskHostDetails",
+ _taskType.Type.Name,
+ hostProcessId,
+ Process.GetCurrentProcess().Id,
+ wasNewlyCreated,
+ _useSidecarTaskHost,
+ effectiveNodeReuse);
+
try
{
bool taskFinished = false;
diff --git a/src/msbuild/src/Build/Resources/Strings.resx b/src/msbuild/src/Build/Resources/Strings.resx
index 2a73d8bfac6f..551b617d3edf 100644
--- a/src/msbuild/src/Build/Resources/Strings.resx
+++ b/src/msbuild/src/Build/Resources/Strings.resx
@@ -1231,6 +1231,10 @@
MSB4216: Could not run the "{0}" task because MSBuild could not create or connect to a task host with runtime "{1}" and architecture "{2}". Please ensure that (1) the requested runtime and/or architecture are available on the machine, and (2) that the required executable "{3}" exists and can be run.{StrBegin="MSB4216: "}
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+
MSB4221: Could not run the "{0}" task because MSBuild could not create or connect to a task host with runtime "{1}" and architecture "{2}". Please ensure that (1) the requested runtime and/or architecture are available on the machine, and (2) that the required executable "{3}" exists and can be run. Error {4} {5}.{StrBegin="MSB4221: "}
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.cs.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.cs.xlf
index 4e6eadee7b3f..0fd3049764f1 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.cs.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.cs.xlf
@@ -1033,6 +1033,11 @@ Chyby: {3}Sestavení úlohy bylo načteno z{0}, ale požadované umístění bylo{1}.
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.Úloha {0} uvolnila tento počet jader: {1}. Teď používá celkem tento počet jader: {2}
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.de.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.de.xlf
index 66dadcdf8636..1008d2753e78 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.de.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.de.xlf
@@ -1033,6 +1033,11 @@ Fehler: {3}
Die Aufgabenassembly wurde aus „{0}“ geladen, während der gewünschte Speicherort „{1}“ war.
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.Die Aufgabe "{0}" hat {1} Kerne freigegeben und belegt jetzt insgesamt {2} Kerne.
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.es.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.es.xlf
index d6dae7553284..7b7310c4a370 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.es.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.es.xlf
@@ -1033,6 +1033,11 @@ Errores: {3}
El ensamblado de tarea se cargó desde "{0}" mientras que la ubicación deseada era "{1}".
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.La tarea "{0}" liberó {1} núcleos y ahora retiene un total de {2} núcleos.
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.fr.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.fr.xlf
index d4c5f384041c..17e5946a98f6 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.fr.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.fr.xlf
@@ -1033,6 +1033,11 @@ Erreurs : {3}
L’assembly de tâche a été chargé à partir de « {0} » alors que l’emplacement souhaité était « {1} ».
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.La tâche "{0}" a libéré {1} cœur. Elle détient désormais {2} cœurs au total.
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.it.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.it.xlf
index a058739283e3..69c63bdb0e77 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.it.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.it.xlf
@@ -1033,6 +1033,11 @@ Errori: {3}
L'assembly attività è stato caricato da "{0}" mentre era "{1}" il percorso desiderato.
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.L'attività "{0}" ha rilasciato {1} core e ora contiene {2} core in totale.
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.ja.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.ja.xlf
index 8871c2d34fa8..2193e9351d2e 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.ja.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.ja.xlf
@@ -1033,6 +1033,11 @@ Errors: {3}
タスク アセンブリは '{0}' から読み込まれましたが、必要な場所は '{1}' でした。
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.タスク "{0}" では、{1} 個のコアを解放したため、現在合計 {2} 個のコアを保持しています。
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.ko.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.ko.xlf
index d5f8f785a966..b28e9b68b244 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.ko.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.ko.xlf
@@ -1033,6 +1033,11 @@ Errors: {3}
원하는 위치가 '{1}'인 동안 '{0}'에서 작업 어셈블리를 로드했습니다.
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total."{0}" 작업에서 코어 {1}개를 해제했고 지금 총 {2}개의 코어를 보유하고 있습니다.
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.pl.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.pl.xlf
index a85f582f90fd..6eb2174f4a85 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.pl.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.pl.xlf
@@ -1033,6 +1033,11 @@ Błędy: {3}
Zestaw zadania został załadowany z lokalizacji „{0}”, gdy żądana lokalizacja to „{1}”.
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.Zadanie „{0}” zwolniło rdzenie ({1}) i teraz jego łączna liczba rdzeni to {2}.
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.pt-BR.xlf
index e01063294c43..921a8c22d880 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.pt-BR.xlf
@@ -1033,6 +1033,11 @@ Erros: {3}
O assembly da tarefa foi carregado de "{0}" enquanto o local desejado era "{1}".
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.A tarefa "{0}" liberou {1} núcleos e agora contém {2} núcleos no total.
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.ru.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.ru.xlf
index 19f964f69db5..a9bb33d6e423 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.ru.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.ru.xlf
@@ -1033,6 +1033,11 @@ Errors: {3}
Сборка задачи была загружена из "{0}", а нужное расположение — "{1}".
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.Задача "{0}" освободила указанное число ядер ({1}). Теперь общее число ядер, которыми располагает задача, равно {2}.
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.tr.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.tr.xlf
index fe03e0c4cd40..60e715548cc7 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.tr.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.tr.xlf
@@ -1033,6 +1033,11 @@ Hatalar: {3}
İstenilen konum '{1}' iken görev derlemesi '{0}'dan yüklendi.
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total."{0}" görevi {1} çekirdeği serbest bıraktı. Şu anda toplam {2} çekirdek tutuyor.
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.zh-Hans.xlf
index f57171970d0e..74ac33603059 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.zh-Hans.xlf
@@ -1033,6 +1033,11 @@ Errors: {3}
已从“{0}”加载任务程序集,但所需位置为“{1}”。
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.任务“{0}”发布了 {1} 个核心,现总共包含 {2} 个核心。
diff --git a/src/msbuild/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/msbuild/src/Build/Resources/xlf/Strings.zh-Hant.xlf
index c5ce7a635083..e5700513cb38 100644
--- a/src/msbuild/src/Build/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/msbuild/src/Build/Resources/xlf/Strings.zh-Hant.xlf
@@ -1033,6 +1033,11 @@ Errors: {3}
工作組件已從 '{0}' 載入,但 '{1}' 才是所需的位置。
+
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Task "{0}" ran in TaskHost process {1}, spawned by caller process {2}. New TaskHost spawned for this invocation: {3}. TaskHost is a reusable sidecar: {4}. Node reuse: {5}.
+ Diagnostic message logged from TaskHostTask. {0} task name. {1} PID of the spawned TaskHost process. {2} PID of the MSBuild process that spawned it. {3}/{4}/{5} are booleans (True/False). LOCALIZATION: Do NOT localize "TaskHost" — it is an MSBuild feature name.
+ Task "{0}" released {1} cores and now holds {2} cores total.工作 "{0}" 已發行 {1} 個核心,現在共保留 {2} 個核心。
diff --git a/src/msbuild/src/Directory.Build.targets b/src/msbuild/src/Directory.Build.targets
index 7e0d7109693e..e38b805791f0 100644
--- a/src/msbuild/src/Directory.Build.targets
+++ b/src/msbuild/src/Directory.Build.targets
@@ -43,6 +43,8 @@
$(DevDivPackagesDir)
+ $(DefineConstants);XUNIT_V3_4_OR_LATER
+
$(MSBuildThisFileDirectory)Shared\UnitTests\xunit.runner.json$(XUnitDesktopSettingsFile)
diff --git a/src/msbuild/src/MSBuild/MSBuildClientApp.cs b/src/msbuild/src/MSBuild/MSBuildClientApp.cs
index 33100583fe21..1282d4fb9e3f 100644
--- a/src/msbuild/src/MSBuild/MSBuildClientApp.cs
+++ b/src/msbuild/src/MSBuild/MSBuildClientApp.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Globalization;
using System.Threading;
using Microsoft.Build.Experimental;
using Microsoft.Build.Framework.Telemetry;
@@ -70,7 +71,18 @@ public static MSBuildApp.ExitType Execute(string[] commandLineArgs, string msbui
KnownTelemetry.PartialBuildTelemetry.ServerFallbackReason = exitResult.MSBuildClientExitType.ToString();
}
- // Server is busy, fallback to old behavior.
+ // Surface a single user-visible message on stderr when the failure is something
+ // other than the well-understood "another client is racing us for the launch
+ // mutex" case. Without this the user sees no indication that MSBuild Server was
+ // requested but unavailable; previously a connection timeout would even crash
+ // the process (the DOTNET_CLI_USE_MSBUILD_SERVER=true regression in 10.0.300).
+ if (exitResult.MSBuildClientExitType != MSBuildClientExitType.ServerBusy)
+ {
+ string detail = GetServerFallbackDetail(exitResult);
+ Console.Error.WriteLine(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MSBuildServerUnavailable", detail));
+ }
+
+ // Server is busy / unavailable, fallback to old behavior.
return MSBuildApp.Execute(commandLineArgs);
}
@@ -84,5 +96,30 @@ public static MSBuildApp.ExitType Execute(string[] commandLineArgs, string msbui
return MSBuildApp.ExitType.MSBuildClientFailure;
}
+
+ ///
+ /// Picks the most specific localized "why MSBuild server was unavailable" sub-message for
+ /// the user-visible fallback notice. Prefers the "server crashed immediately on launch"
+ /// detail over a generic connect-failure message when the launched server's exit code is
+ /// known.
+ ///
+ private static string GetServerFallbackDetail(MSBuildClientExitResult exitResult)
+ {
+ return exitResult.MSBuildClientExitType switch
+ {
+ MSBuildClientExitType.LaunchError =>
+ ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MSBuildServerLaunchError"),
+ MSBuildClientExitType.UnknownServerState =>
+ ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MSBuildServerStateUnknown"),
+ MSBuildClientExitType.UnableToConnect when exitResult.ServerProcessExitCode is int code =>
+ ResourceUtilities.FormatResourceStringStripCodeAndKeyword(
+ "MSBuildServerCrashedOnLaunch",
+ code.ToString(CultureInfo.InvariantCulture)),
+ // Default: UnableToConnect without a known exit code, or any future MSBuildClientExitType
+ // value the caller forwards here. Wording is deliberately neutral about whether the
+ // underlying failure was a timeout or a non-timeout connect error.
+ _ => ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MSBuildServerConnectFailed"),
+ };
+ }
}
}
diff --git a/src/msbuild/src/MSBuild/Resources/Strings.resx b/src/msbuild/src/MSBuild/Resources/Strings.resx
index b2e5738fac1c..1eae13c5f5b9 100644
--- a/src/msbuild/src/MSBuild/Resources/Strings.resx
+++ b/src/msbuild/src/MSBuild/Resources/Strings.resx
@@ -1736,6 +1736,29 @@
+
+
+ MSBuild server unavailable: {0}. Falling back to an in-process build.
+ LOCALIZATION: {0} is a sub-message describing why the MSBuild server could not be used. It is one of MSBuildServerCrashedOnLaunch / MSBuildServerLaunchError / MSBuildServerStateUnknown / MSBuildServerConnectFailed. The outer template supplies the trailing period after {0}; sub-messages must NOT end with a period or other sentence-final punctuation.
+
+
+ the server process exited with code {0} immediately after launch
+ LOCALIZATION: {0} is the integer process exit code; rendered with InvariantCulture as a decimal integer, do not localize the digits. This message is concatenated mid-sentence after a colon in MSBuildServerUnavailable: lower-case start in English is intentional and the message must NOT end with a period (the outer template adds it).
+
+
+ the server process could not be launched
+ LOCALIZATION: No format arguments. Concatenated mid-sentence after a colon in MSBuildServerUnavailable: lower-case start in English is intentional and the message must NOT end with a period (the outer template adds it).
+
+
+ the current server state could not be determined
+ LOCALIZATION: No format arguments. Concatenated mid-sentence after a colon in MSBuildServerUnavailable: lower-case start in English is intentional and the message must NOT end with a period (the outer template adds it).
+
+
+ could not connect to the server within the timeout window; the server may have failed to start
+ LOCALIZATION: No format arguments. Used for any connect failure when the server's exit code is not known — covers both pipe-connect timeouts and non-timeout I/O errors during the connect attempt. Concatenated mid-sentence after a colon in MSBuildServerUnavailable: lower-case start in English is intentional and the message must NOT end with a period (the outer template adds it).
+
+
+