Refactor command line parser to do early parsing (#11482)
This commit is contained in:
parent
485ec44005
commit
68d46c1792
File diff suppressed because it is too large
Load diff
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
1183
test/xUnit/csharp/test_CommandLineParser.cs
Normal file
1183
test/xUnit/csharp/test_CommandLineParser.cs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue