Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore Encoding for the .NET SDK upon Exit #30963

Merged
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
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+.
nagilson marked this conversation as resolved.
Show resolved Hide resolved
nagilson marked this conversation as resolved.
Show resolved Hide resolved
{
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.
nagilson marked this conversation as resolved.
Show resolved Hide resolved
/// 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