Refactor command line parser to do early parsing (#11482)

This commit is contained in:
Ilya 2020-08-12 21:58:46 +05:00 committed by GitHub
parent 485ec44005
commit 68d46c1792
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 1571 additions and 445 deletions

View file

@ -4,7 +4,6 @@
#pragma warning disable 1634, 1691
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
@ -12,6 +11,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Configuration;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
@ -70,9 +70,6 @@ namespace Microsoft.PowerShell
/// <param name="helpText">
/// Help text for minishell. This is displayed on 'minishell -?'.
/// </param>
/// <param name = "args">
/// Command line parameters to pwsh.exe
/// </param>
/// <returns>
/// The exit code for the shell.
///
@ -99,7 +96,7 @@ namespace Microsoft.PowerShell
/// Anyone checking the exit code of the shell or monitor can mask off the high word to determine the exit code passed
/// by the script that the shell last executed.
/// </returns>
internal static int Start(string bannerText, string helpText, string[] args)
internal static int Start(string bannerText, string helpText)
{
#if DEBUG
if (Environment.GetEnvironmentVariable("POWERSHELL_DEBUG_STARTUP") != null)
@ -161,15 +158,8 @@ namespace Microsoft.PowerShell
hostException = e;
}
PSHostUserInterface hostUi = s_theConsoleHost?.UI ?? new NullHostUserInterface();
s_cpp = new CommandLineParameterParser(hostUi, bannerText, helpText);
s_cpp.Parse(args);
#if UNIX
// On Unix, logging has to be deferred until after command-line parsing
// completes to allow overriding logging options.
PSEtwLog.LogConsoleStartup();
#endif
PSHostUserInterface hostUI = s_theConsoleHost?.UI ?? new NullHostUserInterface();
s_cpp.ShowErrorHelpBanner(hostUI, bannerText, helpText);
if (s_cpp.ShowVersion)
{
@ -277,7 +267,22 @@ namespace Microsoft.PowerShell
}
}
private static CommandLineParameterParser s_cpp;
internal static void ParseCommandLine(string[] args)
{
s_cpp.Parse(args);
if (s_cpp.SettingsFile is not null)
{
PowerShellConfig.Instance.SetSystemConfigFilePath(s_cpp.SettingsFile);
}
// Check registry setting for a Group Policy ConfigurationName entry and
// use it to override anything set by the user.
// It depends on setting file so 'SetSystemConfigFilePath()' should be called before.
s_cpp.ConfigurationName = CommandLineParameterParser.GetConfigurationNameFromGroupPolicy();
}
private static readonly CommandLineParameterParser s_cpp = new CommandLineParameterParser();
#if UNIX
/// <summary>

View file

@ -1,9 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#nullable enable
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Runtime.CompilerServices;
namespace Microsoft.PowerShell
{
@ -19,7 +20,7 @@ namespace Microsoft.PowerShell
/// <param name="helpText">Help text for minishell. This is displayed on 'minishell -?'.</param>
/// <param name="args">Commandline parameters specified by user.</param>
/// <returns>An integer value which should be used as exit code for the process.</returns>
public static int Start(string bannerText, string helpText, string[] args)
public static int Start(string? bannerText, string? helpText, string?[] args)
{
return Start(InitialSessionState.CreateDefault2(), bannerText, helpText, args);
}
@ -30,7 +31,7 @@ namespace Microsoft.PowerShell
/// <param name="helpText">Help text for the shell.</param>
/// <param name="args">Commandline parameters specified by user.</param>
/// <returns>An integer value which should be used as exit code for the process.</returns>
public static int Start(InitialSessionState initialSessionState, string bannerText, string helpText, string[] args)
public static int Start(InitialSessionState initialSessionState, string? bannerText, string? helpText, string?[] args)
{
if (initialSessionState == null)
{
@ -42,9 +43,10 @@ namespace Microsoft.PowerShell
throw PSTraceSource.NewArgumentNullException(nameof(args));
}
ConsoleHost.ParseCommandLine(args);
ConsoleHost.DefaultInitialSessionState = initialSessionState;
return ConsoleHost.Start(bannerText, helpText, args);
return ConsoleHost.Start(bannerText, helpText);
}
}
}

View file

@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#nullable enable
using System;
using System.ComponentModel;
using System.Globalization;
@ -30,20 +32,43 @@ namespace Microsoft.PowerShell
/// <param name="argc">
/// Length of the passed in argument array.
/// </param>
public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)]string[] args, int argc)
[Obsolete("Callers should now use UnmanagedPSEntry.Start(string[], int)", error: true)]
public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)]string?[] args, int argc)
{
return Start(args, argc);
}
/// <summary>
/// Starts managed MSH.
/// </summary>
/// <param name="args">
/// Command line arguments to the managed MSH
/// </param>
/// <param name="argc">
/// Length of the passed in argument array.
/// </param>
public static int Start([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 1)]string?[] args, int argc)
{
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
#if DEBUG
if (args.Length > 0 && !string.IsNullOrEmpty(args[0]) && args[0]!.Equals("-isswait", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Attach the debugger to continue...");
while (!System.Diagnostics.Debugger.IsAttached)
{
Thread.Sleep(100);
}
System.Diagnostics.Debugger.Break();
}
#endif
// Warm up some components concurrently on background threads.
EarlyStartup.Init();
// We need to read the settings file before we create the console host
CommandLineParameterParser.EarlyParse(args);
#if !UNIX
// NOTE: On Unix, logging has to be deferred until after command-line parsing
// complete. On Windows, deferring the call is not needed.
PSEtwLog.LogConsoleStartup();
#endif
// Windows Vista and later support non-traditional UI fallback ie., a
// user on an Arabic machine can choose either French or English(US) as
// UI fallback language.
@ -54,18 +79,13 @@ namespace Microsoft.PowerShell
Thread.CurrentThread.CurrentUICulture = NativeCultureResolver.UICulture;
Thread.CurrentThread.CurrentCulture = NativeCultureResolver.Culture;
#if DEBUG
if (args.Length > 0 && !string.IsNullOrEmpty(args[0]) && args[0].Equals("-isswait", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Attach the debugger to continue...");
while (!System.Diagnostics.Debugger.IsAttached)
{
Thread.Sleep(100);
}
ConsoleHost.ParseCommandLine(args);
// NOTE: On Unix, logging depends on a command line parsing
// and must be just after ConsoleHost.ParseCommandLine(args)
// to allow overriding logging options.
PSEtwLog.LogConsoleStartup();
System.Diagnostics.Debugger.Break();
}
#endif
int exitCode = 0;
try
{
@ -74,14 +94,14 @@ namespace Microsoft.PowerShell
ManagedEntranceStrings.ShellBannerNonWindowsPowerShell,
PSVersionInfo.GitCommitId);
exitCode = ConsoleShell.Start(banner, ManagedEntranceStrings.UsageHelp, args);
ConsoleHost.DefaultInitialSessionState = InitialSessionState.CreateDefault2();
exitCode = ConsoleHost.Start(banner, ManagedEntranceStrings.UsageHelp);
}
catch (HostException e)
{
if (e.InnerException != null && e.InnerException.GetType() == typeof(Win32Exception))
if (e.InnerException is Win32Exception win32e)
{
Win32Exception win32e = e.InnerException as Win32Exception;
// These exceptions are caused by killing conhost.exe
// 1236, network connection aborted by local system
// 0x6, invalid console handle
@ -102,4 +122,3 @@ namespace Microsoft.PowerShell
}
}
}

View file

@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#nullable enable
using System;
using System.IO;
using System.Reflection;
@ -66,7 +68,7 @@ namespace Microsoft.PowerShell
#if UNIX
AttemptExecPwshLogin(args);
#endif
return UnmanagedPSEntry.Start(string.Empty, args, args.Length);
return UnmanagedPSEntry.Start(args, args.Length);
}
#if UNIX
@ -93,7 +95,7 @@ namespace Microsoft.PowerShell
byte procNameFirstByte;
// The path to the executable this process was started from
string pwshPath;
string? pwshPath;
// On Linux, we can simply use the /proc filesystem
if (isLinux)
@ -116,6 +118,11 @@ namespace Microsoft.PowerShell
pwshPath = Marshal.PtrToStringAnsi(linkPathPtr, (int)bufSize);
Marshal.FreeHGlobal(linkPathPtr);
if (pwshPath == null)
{
throw new ArgumentNullException(nameof(pwshPath));
}
// exec pwsh
ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: false));
return;
@ -200,6 +207,11 @@ namespace Microsoft.PowerShell
// Get the pwshPath from exec_path
pwshPath = Marshal.PtrToStringAnsi(executablePathPtr);
if (pwshPath == null)
{
throw new ArgumentNullException(nameof(pwshPath));
}
// exec pwsh
ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: true));
}
@ -281,20 +293,20 @@ namespace Microsoft.PowerShell
int quotedPwshPathLength = GetQuotedPathLength(pwshPath);
string pwshInvocation = string.Create(
quotedPwshPathLength + 10, // exec '{pwshPath}' "$@"
quotedPwshPathLength + 10, // exec '{pwshPath}' "$@"
(pwshPath, quotedPwshPathLength),
CreatePwshInvocation);
// Set up the arguments for '/bin/sh'.
// We need to add 5 slots for the '/bin/sh' invocation parts, plus 1 slot for the null terminator at the end
var execArgs = new string[args.Length + 6];
var execArgs = new string?[args.Length + 6];
// The command arguments
// First argument is the command name.
// Even when executing 'zsh', we want to set this to '/bin/sh'
// because this tells 'zsh' to run in sh emulation mode (it examines $0)
execArgs[0] = "/bin/sh";
execArgs[0] = "/bin/sh";
execArgs[1] = "-l"; // Login flag
execArgs[2] = "-c"; // Command parameter
@ -433,7 +445,7 @@ namespace Microsoft.PowerShell
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
SetLastError = true)]
private static extern int Exec(string path, string[] args);
private static extern int Exec(string path, string?[] args);
/// <summary>
/// The `readlink` POSIX syscall we use to read the symlink from /proc/self/exe

File diff suppressed because it is too large Load diff