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
112 changes: 101 additions & 11 deletions src/Build/BackEnd/Client/MSBuildClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Threading;
using Microsoft.Build.BackEnd;
using Microsoft.Build.BackEnd.Client;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Eventing;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
Expand Down Expand Up @@ -78,6 +79,11 @@ public sealed class MSBuildClient
private int _numConsoleWritePackets;
private long _sizeOfConsoleWritePackets;

/// <summary>
/// Capture configuration of Client Console.
/// </summary>
private TargetConsoleConfiguration? _consoleConfiguration;

/// <summary>
/// Public constructor with parameters.
/// </summary>
Expand Down Expand Up @@ -148,6 +154,8 @@ public MSBuildClientExitResult Execute(string commandLine, CancellationToken can
return _exitResult;
}

ConfigureAndQueryConsoleProperties();

// Send build command.
// Let's send it outside the packet pump so that we easier and quicker deal with possible issues with connection to server.
MSBuildEventSource.Log.MSBuildServerBuildStart(commandLine);
Expand Down Expand Up @@ -177,11 +185,6 @@ public MSBuildClientExitResult Execute(string commandLine, CancellationToken can
packetPump.PacketReceivedEvent
};

if (NativeMethodsShared.IsWindows)
{
SupportVT100();
}

while (!_buildFinished)
{
int index = WaitHandle.WaitAny(waitHandles);
Expand Down Expand Up @@ -223,14 +226,100 @@ public MSBuildClientExitResult Execute(string commandLine, CancellationToken can
return _exitResult;
}

private void SupportVT100()
private void ConfigureAndQueryConsoleProperties()
{
var (acceptAnsiColorCodes, outputIsScreen) = QueryIsScreenAndTryEnableAnsiColorCodes();
int bufferWidth = QueryConsoleBufferWidth();
ConsoleColor backgroundColor = QueryConsoleBackgroundColor();

_consoleConfiguration = new TargetConsoleConfiguration(bufferWidth, acceptAnsiColorCodes, outputIsScreen, backgroundColor);
}

private (bool acceptAnsiColorCodes, bool outputIsScreen) QueryIsScreenAndTryEnableAnsiColorCodes()
{
bool acceptAnsiColorCodes = false;
bool outputIsScreen = false;

if (NativeMethodsShared.IsWindows)
{
try
{
IntPtr stdOut = NativeMethodsShared.GetStdHandle(NativeMethodsShared.STD_OUTPUT_HANDLE);
if (NativeMethodsShared.GetConsoleMode(stdOut, out uint consoleMode))
{
bool success;
if ((consoleMode & NativeMethodsShared.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == NativeMethodsShared.ENABLE_VIRTUAL_TERMINAL_PROCESSING &&
(consoleMode & NativeMethodsShared.DISABLE_NEWLINE_AUTO_RETURN) == NativeMethodsShared.DISABLE_NEWLINE_AUTO_RETURN)
{
// Console is already in required state
success = true;
}
else
{
consoleMode |= NativeMethodsShared.ENABLE_VIRTUAL_TERMINAL_PROCESSING | NativeMethodsShared.DISABLE_NEWLINE_AUTO_RETURN;
success = NativeMethodsShared.SetConsoleMode(stdOut, consoleMode);
}

if (success)
{
acceptAnsiColorCodes = true;
}

uint fileType = NativeMethodsShared.GetFileType(stdOut);
// The std out is a char type(LPT or Console)
outputIsScreen = fileType == NativeMethodsShared.FILE_TYPE_CHAR;
acceptAnsiColorCodes &= outputIsScreen;
}
}
catch (Exception ex)
{
CommunicationsUtilities.Trace("MSBuild client warning: problem during enabling support for VT100: {0}.", ex);
}
}
else
{
// On posix OSes we expect console always supports VT100 coloring unless it is redirected
acceptAnsiColorCodes = outputIsScreen = !Console.IsOutputRedirected;
}

return (acceptAnsiColorCodes: acceptAnsiColorCodes, outputIsScreen: outputIsScreen);
}

private int QueryConsoleBufferWidth()
{
IntPtr stdOut = NativeMethodsShared.GetStdHandle(NativeMethodsShared.STD_OUTPUT_HANDLE);
if (NativeMethodsShared.GetConsoleMode(stdOut, out uint consoleMode))
int consoleBufferWidth = -1;
try
{
consoleMode |= NativeMethodsShared.ENABLE_VIRTUAL_TERMINAL_PROCESSING | NativeMethodsShared.DISABLE_NEWLINE_AUTO_RETURN;
NativeMethodsShared.SetConsoleMode(stdOut, consoleMode);
consoleBufferWidth = Console.BufferWidth;
}
catch (Exception ex)
{
// on Win8 machines while in IDE Console.BufferWidth will throw (while it talks to native console it gets "operation aborted" native error)
// this is probably temporary workaround till we understand what is the reason for that exception
CommunicationsUtilities.Trace("MSBuild client warning: problem during querying console buffer width.", ex);
}

return consoleBufferWidth;
}

/// <summary>
/// Some platforms do not allow getting current background color. There
/// is not way to check, but not-supported exception is thrown. Assume
/// black, but don't crash.
/// </summary>
private ConsoleColor QueryConsoleBackgroundColor()
{
ConsoleColor consoleBackgroundColor;
try
{
consoleBackgroundColor = Console.BackgroundColor;
}
catch (PlatformNotSupportedException)
{
consoleBackgroundColor = ConsoleColor.Black;
}

return consoleBackgroundColor;
}

private bool TrySendPacket(Func<INodePacket> packetResolver)
Expand Down Expand Up @@ -324,7 +413,8 @@ private ServerNodeBuildCommand GetServerNodeBuildCommand(string commandLine)
startupDirectory: Directory.GetCurrentDirectory(),
buildProcessEnvironment: envVars,
CultureInfo.CurrentCulture,
CultureInfo.CurrentUICulture);
CultureInfo.CurrentUICulture,
_consoleConfiguration!);
}

private ServerNodeHandshake GetHandshake()
Expand Down
26 changes: 24 additions & 2 deletions src/Build/BackEnd/Node/OutOfProcServerNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Build.Internal;
using System.Threading.Tasks;
using Microsoft.Build.Execution;
using Microsoft.Build.BackEnd.Logging;

namespace Microsoft.Build.Experimental
{
Expand Down Expand Up @@ -312,13 +313,34 @@ private void HandleServerNodeBuildCommand(ServerNodeBuildCommand command)
return;
}

// set build process context
// Set build process context
Directory.SetCurrentDirectory(command.StartupDirectory);
CommunicationsUtilities.SetEnvironment(command.BuildProcessEnvironment);
Thread.CurrentThread.CurrentCulture = command.Culture;
Thread.CurrentThread.CurrentUICulture = command.UICulture;

// configure console output redirection
// Configure console configuration so Loggers can change their behavior based on Target (client) Console properties.
ConsoleConfiguration.Provider = command.ConsoleConfiguration;

// Also try our best to increase chance custom Loggers which use Console static members will work as expected.
try
{
if (NativeMethodsShared.IsWindows && command.ConsoleConfiguration.BufferWidth > 0)
{
Console.BufferWidth = command.ConsoleConfiguration.BufferWidth;
}

if ((int)command.ConsoleConfiguration.BackgroundColor != -1)
{
Console.BackgroundColor = command.ConsoleConfiguration.BackgroundColor;
}
}
catch (Exception)
{
// Ignore exception, it is best effort only
}

// Configure console output redirection
var oldOut = Console.Out;
var oldErr = Console.Error;
(int exitCode, string exitType) buildResult;
Expand Down
16 changes: 14 additions & 2 deletions src/Build/BackEnd/Node/ServerNodeBuildCommand.cs
Original file line number Diff line number Diff line change
@@ -1,10 +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;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Shared;

namespace Microsoft.Build.BackEnd
{
Expand All @@ -18,6 +19,7 @@ internal sealed class ServerNodeBuildCommand : INodePacket
private Dictionary<string, string> _buildProcessEnvironment = default!;
private CultureInfo _culture = default!;
private CultureInfo _uiCulture = default!;
private TargetConsoleConfiguration _consoleConfiguration = default!;

/// <summary>
/// Retrieves the packet type.
Expand Down Expand Up @@ -49,20 +51,29 @@ internal sealed class ServerNodeBuildCommand : INodePacket
/// </summary>
public CultureInfo UICulture => _uiCulture;

/// <summary>
/// Console configuration of Client.
/// </summary>
public TargetConsoleConfiguration ConsoleConfiguration => _consoleConfiguration;

/// <summary>
/// Private constructor for deserialization
/// </summary>
private ServerNodeBuildCommand()
{
}

public ServerNodeBuildCommand(string commandLine, string startupDirectory, Dictionary<string, string> buildProcessEnvironment, CultureInfo culture, CultureInfo uiCulture)
public ServerNodeBuildCommand(string commandLine, string startupDirectory, Dictionary<string, string> buildProcessEnvironment, CultureInfo culture, CultureInfo uiCulture,
TargetConsoleConfiguration consoleConfiguration)
{
ErrorUtilities.VerifyThrowInternalNull(consoleConfiguration, nameof(consoleConfiguration));

_commandLine = commandLine;
_startupDirectory = startupDirectory;
_buildProcessEnvironment = buildProcessEnvironment;
_culture = culture;
_uiCulture = uiCulture;
_consoleConfiguration = consoleConfiguration;
}

/// <summary>
Expand All @@ -76,6 +87,7 @@ public void Translate(ITranslator translator)
translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase);
translator.TranslateCulture(ref _culture);
translator.TranslateCulture(ref _uiCulture);
translator.Translate(ref _consoleConfiguration, TargetConsoleConfiguration.FactoryForDeserialization);
}

/// <summary>
Expand Down
Loading