Skip to content

Commit

Permalink
[Forward Port] Restore Encoding for the .NET SDK upon Exit (#31101)
Browse files Browse the repository at this point in the history
  • Loading branch information
nagilson authored Mar 13, 2023
2 parents 0e9ba20 + 4efefa3 commit e1b15e8
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 7 deletions.
31 changes: 29 additions & 2 deletions src/Cli/Microsoft.DotNet.Cli.Utils/UILanguageOverride.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Microsoft.Win32;

namespace Microsoft.DotNet.Cli.Utils
{
Expand All @@ -25,9 +27,8 @@ public static void Setup()
}

if (
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && // Encoding is only an issue on Windows
!CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.Equals("en", StringComparison.InvariantCultureIgnoreCase) &&
Environment.OSVersion.Version.Major >= 10 // UTF-8 is only officially supported on 10+.
CurrentPlatformIsWindowsAndOfficiallySupportsUTF8Encoding()
)
{
Console.OutputEncoding = DefaultMultilingualEncoding;
Expand Down Expand Up @@ -97,5 +98,31 @@ private static void SetIfNotAlreadySet(string environmentVariableName, int value
{
SetIfNotAlreadySet(environmentVariableName, value.ToString());
}

private static bool CurrentPlatformIsWindowsAndOfficiallySupportsUTF8Encoding()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Major >= 10) // UTF-8 is only officially supported on 10+.
{
try
{
using RegistryKey windowsVersionRegistry = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
var buildNumber = windowsVersionRegistry.GetValue("CurrentBuildNumber").ToString();
const int buildNumberThatOfficialySupportsUTF8 = 18363;
return int.Parse(buildNumber) >= buildNumberThatOfficialySupportsUTF8 || ForceUniversalEncodingOptInEnabled();
}
catch (Exception ex) when (ex is SecurityException || ex is ObjectDisposedException)
{
// We don't want to break those in VS on older versions of Windows with a non-en language.
// Allow those without registry permissions to force the encoding, however.
return ForceUniversalEncodingOptInEnabled();
}
}
return false;
}

private static bool ForceUniversalEncodingOptInEnabled()
{
return String.Equals(Environment.GetEnvironmentVariable("DOTNET_CLI_FORCE_UTF8_ENCODING"), "true", StringComparison.OrdinalIgnoreCase);
}
}
}
65 changes: 65 additions & 0 deletions src/Cli/dotnet/AutomaticEncodingRestorer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System;
using System.IO;
using System.Security;
using System.Text;

namespace Microsoft.DotNet.Cli
{
/// <summary>
/// A program can change the encoding of the console which would affect other programs.
/// We would prefer to have a pattern where the program does not affect encoding of other programs.
/// Create this class in a function akin to Main and let it manage the console encoding resources to return it to the state before execution upon destruction.
/// </summary>
internal class AutomaticEncodingRestorer : IDisposable
{
Encoding _originalOutputEncoding = null;
Encoding _originalInputEncoding = null;

bool outputEncodingAccessible = false;
bool inputEncodingAccessible = false;

public AutomaticEncodingRestorer()
{
try
{
if (!OperatingSystem.IsIOS() && !OperatingSystem.IsAndroid() && !OperatingSystem.IsTvOS()) // Output + Input Encoding are unavailable on these platforms per docs.
{
_originalOutputEncoding = Console.OutputEncoding;
outputEncodingAccessible = true;
if (!OperatingSystem.IsBrowser()) // Input Encoding is also unavailable in this platform.
{
_originalInputEncoding = Console.InputEncoding;
inputEncodingAccessible = true;
}
}
}
catch (Exception ex) when (ex is IOException || ex is SecurityException)
{
// The encoding is unavailable. Do nothing.
}
}

public void Dispose()
{
try
{
if (outputEncodingAccessible)
{
Console.OutputEncoding = _originalOutputEncoding;
}
if (inputEncodingAccessible)
{
Console.InputEncoding = _originalInputEncoding;
}
}
catch (Exception ex) when (ex is IOException || ex is SecurityException)
{
// The encoding is unavailable. Do nothing.
}
}
}
}
12 changes: 7 additions & 5 deletions src/Cli/dotnet/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ public class Program

public static int Main(string[] args)
{
//setting output encoding is not available on those platforms
if (!OperatingSystem.IsIOS() && !OperatingSystem.IsAndroid() && !OperatingSystem.IsTvOS())
using AutomaticEncodingRestorer _ = new();

// Setting output encoding is not available on those platforms
if (!OperatingSystem.IsIOS() && !OperatingSystem.IsAndroid() && !OperatingSystem.IsTvOS() && !OperatingSystem.IsBrowser())
{
//if output is redirected, force encoding to utf-8;
//otherwise the caller may not decode it correctly
Expand Down Expand Up @@ -105,19 +107,19 @@ public static int Main(string[] args)
}
finally
{
if(perLogEventListener != null)
if (perLogEventListener != null)
{
perLogEventListener.Dispose();
}
}
}

internal static int ProcessArgs(string[] args, ITelemetry telemetryClient = null )
internal static int ProcessArgs(string[] args, ITelemetry telemetryClient = null)
{
return ProcessArgs(args, new TimeSpan(0), telemetryClient);
}

internal static int ProcessArgs(string[] args, TimeSpan startupTime, ITelemetry telemetryClient = null )
internal static int ProcessArgs(string[] args, TimeSpan startupTime, ITelemetry telemetryClient = null)
{
Dictionary<string, double> performanceData = new Dictionary<string, double>();

Expand Down
1 change: 1 addition & 0 deletions src/Common/EnvironmentVariableNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ static class EnvironmentVariableNames
public static readonly string WORKLOAD_DISABLE_PACK_GROUPS = "DOTNET_CLI_WORKLOAD_DISABLE_PACK_GROUPS";
public static readonly string DISABLE_PUBLISH_AND_PACK_RELEASE = "DOTNET_CLI_DISABLE_PUBLISH_AND_PACK_RELEASE";
public static readonly string DOTNET_CLI_LAZY_PUBLISH_AND_PACK_RELEASE_FOR_SOLUTIONS = "DOTNET_CLI_LAZY_PUBLISH_AND_PACK_RELEASE_FOR_SOLUTIONS";
public static readonly string DOTNET_CLI_FORCE_UTF8_ENCODING = nameof(DOTNET_CLI_FORCE_UTF8_ENCODING);
public static readonly string TELEMETRY_OPTOUT = "DOTNET_CLI_TELEMETRY_OPTOUT";
public static readonly string DOTNET_ROOT = "DOTNET_ROOT";

Expand Down

0 comments on commit e1b15e8

Please sign in to comment.