Skip to content
Closed
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
19 changes: 19 additions & 0 deletions src/Build/BackEnd/Node/OutOfProcServerNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -69,6 +70,8 @@ public sealed class OutOfProcServerNode : INode, INodePacketFactory, INodePacket
/// </summary>
private bool _cancelRequested = false;
private string _serverBusyMutexName = default!;
private CultureInfo _originalCulture = default!;
private CultureInfo _originalUICulture = default!;

public OutOfProcServerNode(BuildCallback buildFunction)
{
Expand All @@ -93,6 +96,9 @@ public OutOfProcServerNode(BuildCallback buildFunction)
/// <returns>The reason for shutting down.</returns>
public NodeEngineShutdownReason Run(out Exception? shutdownException)
{
_originalCulture = CultureInfo.CurrentCulture;
_originalUICulture = CultureInfo.CurrentUICulture;

ServerNodeHandshake handshake = new(
CommunicationsUtilities.GetHandshakeOptions(taskHost: false, taskHostParameters: TaskHostParameters.Empty, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture()));

Expand Down Expand Up @@ -363,6 +369,19 @@ private void HandleServerNodeBuildCommandAsync(ServerNodeBuildCommand command)
}

private void HandleServerNodeBuildCommand(ServerNodeBuildCommand command)
{
try
{
HandleServerNodeBuildCommandCore(command);
}
finally
{
Thread.CurrentThread.CurrentCulture = _originalCulture;
Thread.CurrentThread.CurrentUICulture = _originalUICulture;
}
}

private void HandleServerNodeBuildCommandCore(ServerNodeBuildCommand command)
{
CommunicationsUtilities.Trace($"Building with MSBuild server with command line {command.CommandLine}");
using var serverBusyMutex = ServerNamedMutex.OpenOrCreateMutex(name: _serverBusyMutexName, createdNew: out var holdsMutex);
Expand Down
86 changes: 86 additions & 0 deletions src/MSBuild.UnitTests/MSBuildServer_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
Expand Down Expand Up @@ -55,6 +56,18 @@ public override bool Execute()
}
}

public class CultureInfoTask : Microsoft.Build.Utilities.Task
{
[Required]
public string OutputFile { get; set; } = string.Empty;

public override bool Execute()
{
File.WriteAllText(OutputFile, string.Join("|", Process.GetCurrentProcess().Id, CultureInfo.CurrentCulture.Name, CultureInfo.CurrentUICulture.Name));
return true;
}
}

public class MSBuildServer_Tests : IDisposable
{
private readonly ITestOutputHelper _output;
Expand All @@ -78,6 +91,13 @@ public class MSBuildServer_Tests : IDisposable
<SleepingTask SleepTime=""100000"" />
</Target>
</Project>";
private static string cultureInfoTaskContents = @$"
<Project>
<UsingTask TaskName=""CultureInfoTask"" AssemblyFile=""{Assembly.GetExecutingAssembly().Location}"" />
<Target Name='RecordCulture'>
<CultureInfoTask OutputFile=""$(CultureOutputFile)"" />
</Target>
</Project>";

public MSBuildServer_Tests(ITestOutputHelper output)
{
Expand Down Expand Up @@ -136,6 +156,43 @@ public void MSBuildServerTest()
pidOfServerProcess.ShouldNotBe(newServerProcessId, "Node used by both the first and second build should not be the same.");
}

[Fact]
public void ServerBuildsUseCultureFromEachRequest()
{
TransientTestFile project = _env.CreateFile("cultureProject.proj", cultureInfoTaskContents);
TransientTestFile firstCultureOutput = _env.ExpectFile();
TransientTestFile secondCultureOutput = _env.ExpectFile();
CultureInfo originalCulture = CultureInfo.CurrentCulture;
CultureInfo originalUICulture = CultureInfo.CurrentUICulture;

try
{
MSBuildClient.ShutdownServer(CancellationToken.None).ShouldBeTrue();

MSBuildClientExitResult firstResult = ExecuteServerBuildWithCulture(project.Path, firstCultureOutput.Path, new CultureInfo("en-US"));
firstResult.MSBuildClientExitType.ShouldBe(MSBuildClientExitType.Success);
firstResult.MSBuildAppExitTypeString.ShouldBe("Success");
CultureRecord firstCultureRecord = ReadCultureRecord(firstCultureOutput.Path);
_env.WithTransientProcess(firstCultureRecord.ProcessId);
firstCultureRecord.Culture.ShouldBe("en-US");
firstCultureRecord.UICulture.ShouldBe("en-US");

MSBuildClientExitResult secondResult = ExecuteServerBuildWithCulture(project.Path, secondCultureOutput.Path, new CultureInfo("fr-FR"));
secondResult.MSBuildClientExitType.ShouldBe(MSBuildClientExitType.Success);
secondResult.MSBuildAppExitTypeString.ShouldBe("Success");
CultureRecord secondCultureRecord = ReadCultureRecord(secondCultureOutput.Path);
secondCultureRecord.ProcessId.ShouldBe(firstCultureRecord.ProcessId, "The second build should reuse the same server process.");
secondCultureRecord.Culture.ShouldBe("fr-FR");
secondCultureRecord.UICulture.ShouldBe("fr-FR");
}
finally
{
Thread.CurrentThread.CurrentCulture = originalCulture;
Thread.CurrentThread.CurrentUICulture = originalUICulture;
MSBuildClient.ShutdownServer(CancellationToken.None);
}
}

[Fact]
public void VerifyMixedLegacyBehavior()
{
Expand Down Expand Up @@ -349,6 +406,35 @@ public void PropertyMSBuildStartupDirectoryOnServer()
output.ShouldContain($@":MSBuildStartupDirectory:{Environment.CurrentDirectory}:");
}

private static MSBuildClientExitResult ExecuteServerBuildWithCulture(string projectPath, string outputFile, CultureInfo culture)
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;

MSBuildClient client = new(
[
BuildEnvironmentHelper.Instance.CurrentMSBuildExePath,
projectPath,
"/t:RecordCulture",
$"/p:CultureOutputFile={outputFile}",
"/nologo",
"/v:m",
],
BuildEnvironmentHelper.Instance.CurrentMSBuildExePath);

return client.Execute(CancellationToken.None);
}

private static CultureRecord ReadCultureRecord(string path)
{
string[] parts = File.ReadAllText(path).Split('|');
parts.Length.ShouldBe(3);

return new CultureRecord(int.Parse(parts[0], CultureInfo.InvariantCulture), parts[1], parts[2]);
}

private readonly record struct CultureRecord(int ProcessId, string Culture, string UICulture);

private int ParseNumber(string searchString, string toFind)
{
Regex regex = new(@$"{toFind}(\d+)");
Expand Down
Loading