|
// 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.IO;
using System.Linq;
using System.Runtime;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
#nullable disable
namespace Microsoft.Build.CommandLine
{
internal class CommandLineParser
{
/// <summary>
/// Used to keep track of response files to prevent them from
/// being included multiple times (or even recursively).
/// </summary>
private List<string> includedResponseFiles;
/// <summary>
/// The name of the auto-response file.
/// </summary>
private const string autoResponseFileName = "MSBuild.rsp";
/// <summary>
/// The name of an auto-response file to search for in the project directory and above.
/// </summary>
private const string directoryResponseFileName = "Directory.Build.rsp";
/// <summary>
/// String replacement pattern to support paths in response files.
/// </summary>
private const string responseFilePathReplacement = "%MSBuildThisFileDirectory%";
internal IReadOnlyList<string> IncludedResponseFiles => includedResponseFiles ?? (IReadOnlyList<string>)Array.Empty<string>();
public (CommandLineSwitches commandLineSwitches, CommandLineSwitches responseFileSwitches) Parse(IEnumerable<string> commandLineArgs)
{
GatherAllSwitches(
commandLineArgs,
$"'{string.Join(" ", commandLineArgs)}'",
out CommandLineSwitches responseFileSwitches,
out CommandLineSwitches commandLineSwitches);
return (commandLineSwitches, responseFileSwitches);
}
internal void GatherAllSwitches(
string commandLine,
out CommandLineSwitches switchesFromAutoResponseFile,
out CommandLineSwitches switchesNotFromAutoResponseFile,
out string fullCommandLine,
out string exeName)
{
// split the command line on (unquoted) whitespace
List<string> commandLineArgs = QuotingUtilities.SplitUnquoted(commandLine);
exeName = FileUtilities.FixFilePath(QuotingUtilities.Unquote(commandLineArgs[0]));
#if USE_MSBUILD_DLL_EXTN
var msbuildExtn = ".dll";
#else
var msbuildExtn = ".exe";
#endif
if (!exeName.EndsWith(msbuildExtn, StringComparison.OrdinalIgnoreCase))
{
exeName += msbuildExtn;
}
fullCommandLine = $"'{commandLine}'";
GatherAllSwitches(
commandLineArgs,
fullCommandLine,
out switchesFromAutoResponseFile,
out switchesNotFromAutoResponseFile);
}
/// <summary>
/// Gets all specified switches, from the command line, as well as all
/// response files, including the auto-response file.
/// </summary>
/// <param name="commandLine"></param>
/// <param name="switchesFromAutoResponseFile"></param>
/// <param name="switchesNotFromAutoResponseFile"></param>
/// <param name="fullCommandLine"></param>
/// <returns>Combined bag of switches.</returns>
private void GatherAllSwitches(
IEnumerable<string> commandLineArgs,
string fullCommandLine,
out CommandLineSwitches switchesFromAutoResponseFile,
out CommandLineSwitches switchesNotFromAutoResponseFile)
{
ResetGatheringSwitchesState();
// discard the first piece, because that's the path to the executable -- the rest are args
commandLineArgs = commandLineArgs.Skip(1);
// parse the command line, and flag syntax errors and obvious switch errors
switchesNotFromAutoResponseFile = new CommandLineSwitches();
GatherCommandLineSwitches(commandLineArgs, switchesNotFromAutoResponseFile, fullCommandLine);
// parse the auto-response file (if "/noautoresponse" is not specified), and combine those switches with the
// switches on the command line
switchesFromAutoResponseFile = new CommandLineSwitches();
if (!switchesNotFromAutoResponseFile[CommandLineSwitches.ParameterlessSwitch.NoAutoResponse])
{
string exePath = Path.GetDirectoryName(FileUtilities.ExecutingAssemblyPath); // Copied from XMake
GatherAutoResponseFileSwitches(exePath, switchesFromAutoResponseFile, fullCommandLine);
}
}
/// <summary>
/// Coordinates the parsing of the command line. It detects switches on the command line, gathers their parameters, and
/// flags syntax errors, and other obvious switch errors.
/// </summary>
/// <remarks>
/// Internal for unit testing only.
/// </remarks>
internal void GatherCommandLineSwitches(IEnumerable<string> commandLineArgs, CommandLineSwitches commandLineSwitches, string commandLine = "")
{
foreach (string commandLineArg in commandLineArgs)
{
string unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out var doubleQuotesRemovedFromArg);
if (unquotedCommandLineArg.Length > 0)
{
// response file switch starts with @
if (unquotedCommandLineArg.StartsWith("@", StringComparison.Ordinal))
{
GatherResponseFileSwitch(unquotedCommandLineArg, commandLineSwitches, commandLine);
}
else
{
string switchName;
string switchParameters;
// all switches should start with - or / or -- unless a project is being specified
if (!ValidateSwitchIndicatorInUnquotedArgument(unquotedCommandLineArg) || FileUtilities.LooksLikeUnixFilePath(unquotedCommandLineArg))
{
switchName = null;
// add a (fake) parameter indicator for later parsing
switchParameters = $":{commandLineArg}";
}
else
{
// check if switch has parameters (look for the : parameter indicator)
int switchParameterIndicator = unquotedCommandLineArg.IndexOf(':');
// get the length of the beginning sequence considered as a switch indicator (- or / or --)
int switchIndicatorsLength = GetLengthOfSwitchIndicator(unquotedCommandLineArg);
// extract the switch name and parameters -- the name is sandwiched between the switch indicator (the
// leading - or / or --) and the parameter indicator (if the switch has parameters); the parameters (if any)
// follow the parameter indicator
if (switchParameterIndicator == -1)
{
switchName = unquotedCommandLineArg.Substring(switchIndicatorsLength);
switchParameters = string.Empty;
}
else
{
switchName = unquotedCommandLineArg.Substring(switchIndicatorsLength, switchParameterIndicator - switchIndicatorsLength);
switchParameters = ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, switchName, switchParameterIndicator, switchIndicatorsLength);
}
}
// Special case: for the switches "/m" (or "/maxCpuCount") and "/bl" (or "/binarylogger") we wish to pretend we saw a default argument
// This allows a subsequent /m:n on the command line to override it.
// We could create a new kind of switch with optional parameters, but it's a great deal of churn for this single case.
// Note that if no "/m" or "/maxCpuCount" switch -- either with or without parameters -- is present, then we still default to 1 cpu
// for backwards compatibility.
if (string.IsNullOrEmpty(switchParameters))
{
if (string.Equals(switchName, "m", StringComparison.OrdinalIgnoreCase) ||
string.Equals(switchName, "maxcpucount", StringComparison.OrdinalIgnoreCase))
{
int numberOfCpus = NativeMethodsShared.GetLogicalCoreCount();
switchParameters = $":{numberOfCpus}";
}
else if (string.Equals(switchName, "bl", StringComparison.OrdinalIgnoreCase) ||
string.Equals(switchName, "binarylogger", StringComparison.OrdinalIgnoreCase))
{
// we have to specify at least one parameter otherwise it's impossible to distinguish the situation
// where /bl is not specified at all vs. where /bl is specified without the file name.
switchParameters = ":msbuild.binlog";
}
else if (string.Equals(switchName, "prof", StringComparison.OrdinalIgnoreCase) ||
string.Equals(switchName, "profileevaluation", StringComparison.OrdinalIgnoreCase))
{
switchParameters = ":no-file";
}
}
if (CommandLineSwitches.IsParameterlessSwitch(switchName, out var parameterlessSwitch, out var duplicateSwitchErrorMessage))
{
GatherParameterlessCommandLineSwitch(commandLineSwitches, parameterlessSwitch, switchParameters, duplicateSwitchErrorMessage, unquotedCommandLineArg, commandLine);
}
else if (CommandLineSwitches.IsParameterizedSwitch(switchName, out var parameterizedSwitch, out duplicateSwitchErrorMessage, out var multipleParametersAllowed, out var missingParametersErrorMessage, out var unquoteParameters, out var allowEmptyParameters))
{
GatherParameterizedCommandLineSwitch(commandLineSwitches, parameterizedSwitch, switchParameters, duplicateSwitchErrorMessage, multipleParametersAllowed, missingParametersErrorMessage, unquoteParameters, unquotedCommandLineArg, allowEmptyParameters, commandLine);
}
else
{
commandLineSwitches.SetUnknownSwitchError(unquotedCommandLineArg, commandLine);
}
}
}
}
}
/// <summary>
/// Identifies if there is rsp files near the project file
/// </summary>
/// <returns>true if there autoresponse file was found</returns>
internal bool CheckAndGatherProjectAutoResponseFile(CommandLineSwitches switchesFromAutoResponseFile, CommandLineSwitches commandLineSwitches, bool recursing, string commandLine)
{
bool found = false;
var projectDirectory = GetProjectDirectory(commandLineSwitches[CommandLineSwitches.ParameterizedSwitch.Project]);
if (!recursing && !commandLineSwitches[CommandLineSwitches.ParameterlessSwitch.NoAutoResponse])
{
// gather any switches from the first Directory.Build.rsp found in the project directory or above
string directoryResponseFile = FileUtilities.GetPathOfFileAbove(directoryResponseFileName, projectDirectory);
found = !string.IsNullOrWhiteSpace(directoryResponseFile) && GatherAutoResponseFileSwitchesFromFullPath(directoryResponseFile, switchesFromAutoResponseFile, commandLine);
// Don't look for more response files if it's only in the same place we already looked (next to the exe)
string exePath = Path.GetDirectoryName(FileUtilities.ExecutingAssemblyPath); // Copied from XMake
if (!string.Equals(projectDirectory, exePath, StringComparison.OrdinalIgnoreCase))
{
// this combines any found, with higher precedence, with the switches from the original auto response file switches
found |= GatherAutoResponseFileSwitches(projectDirectory, switchesFromAutoResponseFile, commandLine);
}
}
return found;
}
private static string GetProjectDirectory(string[] projectSwitchParameters)
{
string projectDirectory = ".";
ErrorUtilities.VerifyThrow(projectSwitchParameters.Length <= 1, "Expect exactly one project at a time.");
if (projectSwitchParameters.Length == 1)
{
var projectFile = FileUtilities.FixFilePath(projectSwitchParameters[0]);
if (FileSystems.Default.DirectoryExists(projectFile))
{
// the provided argument value is actually the directory
projectDirectory = projectFile;
}
else
{
InitializationException.VerifyThrow(FileSystems.Default.FileExists(projectFile), "ProjectNotFoundError", projectFile);
projectDirectory = Path.GetDirectoryName(Path.GetFullPath(projectFile));
}
}
return projectDirectory;
}
/// <summary>
/// Extracts a switch's parameters after processing all quoting around the switch.
/// </summary>
/// <remarks>
/// This method is marked "internal" for unit-testing purposes only -- ideally it should be "private".
/// </remarks>
/// <param name="commandLineArg"></param>
/// <param name="unquotedCommandLineArg"></param>
/// <param name="doubleQuotesRemovedFromArg"></param>
/// <param name="switchName"></param>
/// <param name="switchParameterIndicator"></param>
/// <param name="switchIndicatorsLength"></param>
/// <returns>The given switch's parameters (with interesting quoting preserved).</returns>
internal static string ExtractSwitchParameters(
string commandLineArg,
string unquotedCommandLineArg,
int doubleQuotesRemovedFromArg,
string switchName,
int switchParameterIndicator,
int switchIndicatorsLength)
{
// find the parameter indicator again using the quoted arg
// NOTE: since the parameter indicator cannot be part of a switch name, quoting around it is not relevant, because a
// parameter indicator cannot be escaped or made into a literal
int quotedSwitchParameterIndicator = commandLineArg.IndexOf(':');
// check if there is any quoting in the name portion of the switch
string unquotedSwitchIndicatorAndName = QuotingUtilities.Unquote(commandLineArg.Substring(0, quotedSwitchParameterIndicator), out var doubleQuotesRemovedFromSwitchIndicatorAndName);
ErrorUtilities.VerifyThrow(switchName == unquotedSwitchIndicatorAndName.Substring(switchIndicatorsLength),
"The switch name extracted from either the partially or completely unquoted arg should be the same.");
ErrorUtilities.VerifyThrow(doubleQuotesRemovedFromArg >= doubleQuotesRemovedFromSwitchIndicatorAndName,
"The name portion of the switch cannot contain more quoting than the arg itself.");
string switchParameters;
// if quoting in the name portion of the switch was terminated
if ((doubleQuotesRemovedFromSwitchIndicatorAndName % 2) == 0)
{
// get the parameters exactly as specified on the command line i.e. including quoting
switchParameters = commandLineArg.Substring(quotedSwitchParameterIndicator);
}
else
{
// if quoting was not terminated in the name portion of the switch, and the terminal double-quote (if any)
// terminates the switch parameters
int terminalDoubleQuote = commandLineArg.IndexOf('"', quotedSwitchParameterIndicator + 1);
if (((doubleQuotesRemovedFromArg - doubleQuotesRemovedFromSwitchIndicatorAndName) <= 1) &&
((terminalDoubleQuote == -1) || (terminalDoubleQuote == (commandLineArg.Length - 1))))
{
// then the parameters are not quoted in any interesting way, so use the unquoted parameters
switchParameters = unquotedCommandLineArg.Substring(switchParameterIndicator);
}
else
{
// otherwise, use the quoted parameters, after compensating for the quoting that was started in the name
// portion of the switch
switchParameters = $":\"{commandLineArg.Substring(quotedSwitchParameterIndicator + 1)}";
}
}
ErrorUtilities.VerifyThrow(switchParameters != null, "We must be able to extract the switch parameters.");
return switchParameters;
}
/// <summary>
/// Called when a response file switch is detected on the command line. It loads the specified response file, and parses
/// each line in it like a command line. It also prevents multiple (or recursive) inclusions of the same response file.
/// </summary>
/// <param name="unquotedCommandLineArg"></param>
/// <param name="commandLineSwitches"></param>
private void GatherResponseFileSwitch(string unquotedCommandLineArg, CommandLineSwitches commandLineSwitches, string commandLine)
{
try
{
string responseFile = FileUtilities.FixFilePath(unquotedCommandLineArg.Substring(1));
if (responseFile.Length == 0)
{
commandLineSwitches.SetSwitchError("MissingResponseFileError", unquotedCommandLineArg, commandLine);
}
else if (!FileSystems.Default.FileExists(responseFile))
{
commandLineSwitches.SetParameterError("ResponseFileNotFoundError", unquotedCommandLineArg, commandLine);
}
else
{
// normalize the response file path to help catch multiple (or recursive) inclusions
responseFile = Path.GetFullPath(responseFile);
// NOTE: for network paths or mapped paths, normalization is not guaranteed to work
bool isRepeatedResponseFile = false;
foreach (string includedResponseFile in includedResponseFiles)
{
if (string.Equals(responseFile, includedResponseFile, StringComparison.OrdinalIgnoreCase))
{
commandLineSwitches.SetParameterError("RepeatedResponseFileError", unquotedCommandLineArg, commandLine);
isRepeatedResponseFile = true;
break;
}
}
if (!isRepeatedResponseFile)
{
var responseFileDirectory = FileUtilities.EnsureTrailingSlash(Path.GetDirectoryName(responseFile));
includedResponseFiles.Add(responseFile);
List<string> argsFromResponseFile;
#if FEATURE_ENCODING_DEFAULT
using (StreamReader responseFileContents = new StreamReader(responseFile, Encoding.Default)) // HIGHCHAR: If response files have no byte-order marks, then assume ANSI rather than ASCII.
#else
using (StreamReader responseFileContents = FileUtilities.OpenRead(responseFile)) // HIGHCHAR: If response files have no byte-order marks, then assume ANSI rather than ASCII.
#endif
{
argsFromResponseFile = new List<string>();
while (responseFileContents.Peek() != -1)
{
// ignore leading whitespace on each line
string responseFileLine = responseFileContents.ReadLine().TrimStart();
// skip comment lines beginning with #
if (!responseFileLine.StartsWith("#", StringComparison.Ordinal))
{
// Allow special case to support a path relative to the .rsp file being processed.
responseFileLine = Regex.Replace(responseFileLine, responseFilePathReplacement,
responseFileDirectory, RegexOptions.IgnoreCase);
// treat each line of the response file like a command line i.e. args separated by whitespace
argsFromResponseFile.AddRange(QuotingUtilities.SplitUnquoted(Environment.ExpandEnvironmentVariables(responseFileLine)));
}
}
}
CommandLineSwitches.SwitchesFromResponseFiles.Add((responseFile, string.Join(" ", argsFromResponseFile)));
GatherCommandLineSwitches(argsFromResponseFile, commandLineSwitches, commandLine);
}
}
}
catch (NotSupportedException e)
{
commandLineSwitches.SetParameterError("ReadResponseFileError", unquotedCommandLineArg, e, commandLine);
}
catch (SecurityException e)
{
commandLineSwitches.SetParameterError("ReadResponseFileError", unquotedCommandLineArg, e, commandLine);
}
catch (UnauthorizedAccessException e)
{
commandLineSwitches.SetParameterError("ReadResponseFileError", unquotedCommandLineArg, e, commandLine);
}
catch (IOException e)
{
commandLineSwitches.SetParameterError("ReadResponseFileError", unquotedCommandLineArg, e, commandLine);
}
}
/// <summary>
/// Called when a switch that doesn't take parameters is detected on the command line.
/// </summary>
/// <param name="commandLineSwitches"></param>
/// <param name="parameterlessSwitch"></param>
/// <param name="switchParameters"></param>
/// <param name="duplicateSwitchErrorMessage"></param>
/// <param name="unquotedCommandLineArg"></param>
private static void GatherParameterlessCommandLineSwitch(
CommandLineSwitches commandLineSwitches,
CommandLineSwitches.ParameterlessSwitch parameterlessSwitch,
string switchParameters,
string duplicateSwitchErrorMessage,
string unquotedCommandLineArg,
string commandLine)
{
// switch should not have any parameters
if (switchParameters.Length == 0)
{
// check if switch is duplicated, and if that's allowed
if (!commandLineSwitches.IsParameterlessSwitchSet(parameterlessSwitch) ||
(duplicateSwitchErrorMessage == null))
{
commandLineSwitches.SetParameterlessSwitch(parameterlessSwitch, unquotedCommandLineArg);
}
else
{
commandLineSwitches.SetSwitchError(duplicateSwitchErrorMessage, unquotedCommandLineArg, commandLine);
}
}
else
{
commandLineSwitches.SetUnexpectedParametersError(unquotedCommandLineArg, commandLine);
}
}
/// <summary>
/// Called when a switch that takes parameters is detected on the command line. This method flags errors and stores the
/// switch parameters.
/// </summary>
/// <param name="commandLineSwitches"></param>
/// <param name="parameterizedSwitch"></param>
/// <param name="switchParameters"></param>
/// <param name="duplicateSwitchErrorMessage"></param>
/// <param name="multipleParametersAllowed"></param>
/// <param name="missingParametersErrorMessage"></param>
/// <param name="unquoteParameters"></param>
/// <param name="unquotedCommandLineArg"></param>
private static void GatherParameterizedCommandLineSwitch(
CommandLineSwitches commandLineSwitches,
CommandLineSwitches.ParameterizedSwitch parameterizedSwitch,
string switchParameters,
string duplicateSwitchErrorMessage,
bool multipleParametersAllowed,
string missingParametersErrorMessage,
bool unquoteParameters,
string unquotedCommandLineArg,
bool allowEmptyParameters,
string commandLine)
{
if (// switch must have parameters
(switchParameters.Length > 1) ||
// unless the parameters are optional
(missingParametersErrorMessage == null))
{
// skip the parameter indicator (if any)
if (switchParameters.Length > 0)
{
switchParameters = switchParameters.Substring(1);
}
if (parameterizedSwitch == CommandLineSwitches.ParameterizedSwitch.Project && IsEnvironmentVariable(switchParameters))
{
commandLineSwitches.SetSwitchError("EnvironmentVariableAsSwitch", unquotedCommandLineArg, commandLine);
}
// check if switch is duplicated, and if that's allowed
if (!commandLineSwitches.IsParameterizedSwitchSet(parameterizedSwitch) ||
(duplicateSwitchErrorMessage == null))
{
// save the parameters after unquoting and splitting them if necessary
if (!commandLineSwitches.SetParameterizedSwitch(parameterizedSwitch, unquotedCommandLineArg, switchParameters, multipleParametersAllowed, unquoteParameters, allowEmptyParameters))
{
// if parsing revealed there were no real parameters, flag an error, unless the parameters are optional
if (missingParametersErrorMessage != null)
{
commandLineSwitches.SetSwitchError(missingParametersErrorMessage, unquotedCommandLineArg, commandLine);
}
}
}
else
{
commandLineSwitches.SetSwitchError(duplicateSwitchErrorMessage, unquotedCommandLineArg, commandLine);
}
}
else
{
commandLineSwitches.SetSwitchError(missingParametersErrorMessage, unquotedCommandLineArg, commandLine);
}
}
/// <summary>
/// Checks whether envVar is an environment variable. MSBuild uses
/// Environment.ExpandEnvironmentVariables(string), which only
/// considers %-delimited variables.
/// </summary>
/// <param name="envVar">A possible environment variable</param>
/// <returns>Whether envVar is an environment variable</returns>
private static bool IsEnvironmentVariable(string envVar)
{
return envVar.StartsWith("%") && envVar.EndsWith("%") && envVar.Length > 1;
}
/// <summary>
/// Parses the auto-response file (assumes the "/noautoresponse" switch is not specified on the command line), and combines the
/// switches from the auto-response file with the switches passed in.
/// Returns true if the response file was found.
/// </summary>
private bool GatherAutoResponseFileSwitches(string path, CommandLineSwitches switchesFromAutoResponseFile, string commandLine)
{
string autoResponseFile = Path.Combine(path, autoResponseFileName);
return GatherAutoResponseFileSwitchesFromFullPath(autoResponseFile, switchesFromAutoResponseFile, commandLine);
}
private bool GatherAutoResponseFileSwitchesFromFullPath(string autoResponseFile, CommandLineSwitches switchesFromAutoResponseFile, string commandLine)
{
bool found = false;
// if the auto-response file does not exist, only use the switches on the command line
if (FileSystems.Default.FileExists(autoResponseFile))
{
found = true;
GatherResponseFileSwitch($"@{autoResponseFile}", switchesFromAutoResponseFile, commandLine);
// if the "/noautoresponse" switch was set in the auto-response file, flag an error
if (switchesFromAutoResponseFile[CommandLineSwitches.ParameterlessSwitch.NoAutoResponse])
{
switchesFromAutoResponseFile.SetSwitchError("CannotAutoDisableAutoResponseFile",
switchesFromAutoResponseFile.GetParameterlessSwitchCommandLineArg(CommandLineSwitches.ParameterlessSwitch.NoAutoResponse), commandLine);
}
// Throw errors found in the response file
switchesFromAutoResponseFile.ThrowErrors();
}
return found;
}
/// <summary>
/// Checks whether an argument given as a parameter starts with valid indicator,
/// <br/>which means, whether switch begins with one of: "/", "-", "--"
/// </summary>
/// <param name="unquotedCommandLineArgument">Command line argument with beginning indicator (e.g. --help).
/// <br/>This argument has to be unquoted, otherwise the first character will always be a quote character "</param>
/// <returns>true if argument's beginning matches one of possible indicators
/// <br/>false if argument's beginning doesn't match any of correct indicator
/// </returns>
private static bool ValidateSwitchIndicatorInUnquotedArgument(string unquotedCommandLineArgument)
{
return unquotedCommandLineArgument.StartsWith("-", StringComparison.Ordinal) // superset of "--"
|| unquotedCommandLineArgument.StartsWith("/", StringComparison.Ordinal);
}
/// <summary>
/// Gets the length of the switch indicator (- or / or --)
/// <br/>The length returned from this method is deduced from the beginning sequence of unquoted argument.
/// <br/>This way it will "assume" that there's no further error (e.g. // or ---) which would also be considered as a correct indicator.
/// </summary>
/// <param name="unquotedSwitch">Unquoted argument with leading indicator and name</param>
/// <returns>Correct length of used indicator
/// <br/>0 if no leading sequence recognized as correct indicator</returns>
/// Internal for testing purposes
internal static int GetLengthOfSwitchIndicator(string unquotedSwitch)
{
if (unquotedSwitch.StartsWith("--", StringComparison.Ordinal))
{
return 2;
}
else if (unquotedSwitch.StartsWith("-", StringComparison.Ordinal) || unquotedSwitch.StartsWith("/", StringComparison.Ordinal))
{
return 1;
}
else
{
return 0;
}
}
internal void ResetGatheringSwitchesState()
{
includedResponseFiles = new List<string>();
CommandLineSwitches.SwitchesFromResponseFiles = new();
}
}
}
|