diff --git a/src/Build.UnitTests/Utilities_Tests.cs b/src/Build.UnitTests/Utilities_Tests.cs
index a29466e852c..880fbe49e59 100644
--- a/src/Build.UnitTests/Utilities_Tests.cs
+++ b/src/Build.UnitTests/Utilities_Tests.cs
@@ -80,17 +80,9 @@ public void CommentsInPreprocessing()
env.SetEnvironmentVariable("MSBUILDLOADALLFILESASWRITEABLE", "1");
-#if FEATURE_GET_COMMANDLINE
- MSBuildApp.Execute(@"c:\bin\msbuild.exe """ + inputFile.Path +
- (NativeMethodsShared.IsUnixLike ? @""" -pp:""" : @""" /pp:""") + outputFile.Path + @"""")
- .ShouldBe(MSBuildApp.ExitType.Success);
-#else
Assert.Equal(
MSBuildApp.ExitType.Success,
- MSBuildApp.Execute(
- new[] { @"c:\bin\msbuild.exe", '"' + inputFile.Path + '"',
- '"' + (NativeMethodsShared.IsUnixLike ? "-pp:" : "/pp:") + outputFile.Path + '"'}));
-#endif
+ MSBuildApp.Execute([ @"c:\bin\msbuild.exe", '"' + inputFile.Path + '"', '"' + (NativeMethodsShared.IsUnixLike ? "-pp:" : "/pp:") + outputFile.Path + '"']));
bool foundDoNotModify = false;
foreach (string line in File.ReadLines(outputFile.Path))
diff --git a/src/Build/BackEnd/Client/MSBuildClient.cs b/src/Build/BackEnd/Client/MSBuildClient.cs
index 9bd05788271..e1df9945e91 100644
--- a/src/Build/BackEnd/Client/MSBuildClient.cs
+++ b/src/Build/BackEnd/Client/MSBuildClient.cs
@@ -48,11 +48,7 @@ public sealed class MSBuildClient
/// The command line to process.
/// The first argument on the command line is assumed to be the name/path of the executable, and is ignored.
///
-#if FEATURE_GET_COMMANDLINE
- private readonly string _commandLine;
-#else
private readonly string[] _commandLine;
-#endif
///
/// The MSBuild client execution result.
@@ -112,13 +108,7 @@ public sealed class MSBuildClient
/// on the command line is assumed to be the name/path of the executable, and is ignored
/// Full path to current MSBuild.exe if executable is MSBuild.exe,
/// or to version of MSBuild.dll found to be associated with the current process.
- public MSBuildClient(
-#if FEATURE_GET_COMMANDLINE
- string commandLine,
-#else
- string[] commandLine,
-#endif
- string msbuildLocation)
+ public MSBuildClient(string[] commandLine, string msbuildLocation)
{
_serverEnvironmentVariables = new();
_exitResult = new();
@@ -162,12 +152,7 @@ private void CreateNodePipeStream()
public MSBuildClientExitResult Execute(CancellationToken cancellationToken)
{
// Command line in one string used only in human readable content.
- string descriptiveCommandLine =
-#if FEATURE_GET_COMMANDLINE
- _commandLine;
-#else
- string.Join(" ", _commandLine);
-#endif
+ string descriptiveCommandLine = string.Join(" ", _commandLine);
CommunicationsUtilities.Trace("Executing build with command line '{0}'", descriptiveCommandLine);
diff --git a/src/Build/BackEnd/Node/OutOfProcServerNode.cs b/src/Build/BackEnd/Node/OutOfProcServerNode.cs
index e5b1e76f412..49ed1d610dd 100644
--- a/src/Build/BackEnd/Node/OutOfProcServerNode.cs
+++ b/src/Build/BackEnd/Node/OutOfProcServerNode.cs
@@ -25,12 +25,7 @@ public sealed class OutOfProcServerNode : INode, INodePacketFactory, INodePacket
///
/// A callback used to execute command line build.
///
- public delegate (int exitCode, string exitType) BuildCallback(
-#if FEATURE_GET_COMMANDLINE
- string commandLine);
-#else
- string[] commandLine);
-#endif
+ public delegate (int exitCode, string exitType) BuildCallback(string[] commandLine);
private readonly BuildCallback _buildFunction;
diff --git a/src/Build/BackEnd/Node/ServerNodeBuildCommand.cs b/src/Build/BackEnd/Node/ServerNodeBuildCommand.cs
index fc5bca7e920..ab067c7d4ad 100644
--- a/src/Build/BackEnd/Node/ServerNodeBuildCommand.cs
+++ b/src/Build/BackEnd/Node/ServerNodeBuildCommand.cs
@@ -14,11 +14,7 @@ namespace Microsoft.Build.BackEnd
///
internal sealed class ServerNodeBuildCommand : INodePacket
{
-#if FEATURE_GET_COMMANDLINE
- private string _commandLine = default!;
-#else
private string[] _commandLine = default!;
-#endif
private string _startupDirectory = default!;
private Dictionary _buildProcessEnvironment = default!;
private CultureInfo _culture = default!;
@@ -34,11 +30,7 @@ internal sealed class ServerNodeBuildCommand : INodePacket
///
/// Command line including arguments
///
-#if FEATURE_GET_COMMANDLINE
- public string CommandLine => _commandLine;
-#else
public string[] CommandLine => _commandLine;
-#endif
///
/// The startup directory
@@ -79,11 +71,7 @@ private ServerNodeBuildCommand()
}
public ServerNodeBuildCommand(
-#if FEATURE_GET_COMMANDLINE
- string commandLine,
-#else
string[] commandLine,
-#endif
string startupDirectory,
Dictionary buildProcessEnvironment,
CultureInfo culture, CultureInfo uiCulture,
diff --git a/src/Build/CompatibilitySuppressions.xml b/src/Build/CompatibilitySuppressions.xml
index 0497d618a92..e1a53b3ea2e 100644
--- a/src/Build/CompatibilitySuppressions.xml
+++ b/src/Build/CompatibilitySuppressions.xml
@@ -1,3 +1,48 @@
-
\ No newline at end of file
+
+
+
+
+ CP0002
+ M:Microsoft.Build.Experimental.MSBuildClient.#ctor(System.String,System.String)
+ lib/net472/Microsoft.Build.dll
+ lib/net472/Microsoft.Build.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Build.Experimental.OutOfProcServerNode.BuildCallback.BeginInvoke(System.String,System.AsyncCallback,System.Object)
+ lib/net472/Microsoft.Build.dll
+ lib/net472/Microsoft.Build.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Build.Experimental.OutOfProcServerNode.BuildCallback.Invoke(System.String)
+ lib/net472/Microsoft.Build.dll
+ lib/net472/Microsoft.Build.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Build.Experimental.MSBuildClient.#ctor(System.String,System.String)
+ ref/net472/Microsoft.Build.dll
+ ref/net472/Microsoft.Build.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Build.Experimental.OutOfProcServerNode.BuildCallback.BeginInvoke(System.String,System.AsyncCallback,System.Object)
+ ref/net472/Microsoft.Build.dll
+ ref/net472/Microsoft.Build.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Build.Experimental.OutOfProcServerNode.BuildCallback.Invoke(System.String)
+ ref/net472/Microsoft.Build.dll
+ ref/net472/Microsoft.Build.dll
+ true
+
+
diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets
index 73b91fe6c96..db68730447e 100644
--- a/src/Directory.BeforeCommon.targets
+++ b/src/Directory.BeforeCommon.targets
@@ -31,7 +31,6 @@
$(DefineConstants);FEATURE_ENVIRONMENT_SYSTEMDIRECTORY
$(DefineConstants);FEATURE_FILE_TRACKER
$(DefineConstants);FEATURE_GAC
- $(DefineConstants);FEATURE_GET_COMMANDLINE
$(DefineConstants);FEATURE_HANDLEPROCESSCORRUPTEDSTATEEXCEPTIONS
$(DefineConstants);FEATURE_HTTP_LISTENER
$(DefineConstants);FEATURE_INSTALLED_MSBUILD
diff --git a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs
index a2de7a8fb1c..88c5d591c53 100644
--- a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs
+++ b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs
@@ -622,7 +622,9 @@ public void FeatureAvailibilitySwitchIdentificationTest(string switchName)
public void TargetsSwitchParameter()
{
CommandLineSwitches switches = new CommandLineSwitches();
- MSBuildApp.GatherCommandLineSwitches(new List() { "/targets:targets.txt" }, switches);
+ CommandLineParser parser = new CommandLineParser();
+
+ parser.GatherCommandLineSwitches(["/targets:targets.txt"], switches);
switches.HaveErrors().ShouldBeFalse();
switches[CommandLineSwitches.ParameterizedSwitch.Targets].ShouldBe(new[] { "targets.txt" });
@@ -632,7 +634,9 @@ public void TargetsSwitchParameter()
public void TargetsSwitchDoesNotSupportMultipleOccurrences()
{
CommandLineSwitches switches = new CommandLineSwitches();
- MSBuildApp.GatherCommandLineSwitches(new List() { "/targets /targets" }, switches);
+ CommandLineParser parser = new CommandLineParser();
+
+ parser.GatherCommandLineSwitches(["/targets /targets"], switches);
switches.HaveErrors().ShouldBeTrue();
}
@@ -709,8 +713,9 @@ public void LowPrioritySwitchIdentificationTests(string lowpriority)
public void GraphBuildSwitchCanHaveParameters()
{
CommandLineSwitches switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List { "/graph", "/graph:true; NoBuild ;; ;", "/graph:foo" }, switches);
+ parser.GatherCommandLineSwitches(["/graph", "/graph:true; NoBuild ;; ;", "/graph:foo"], switches);
switches[CommandLineSwitches.ParameterizedSwitch.GraphBuild].ShouldBe(new[] { "true", " NoBuild ", " ", "foo" });
@@ -721,8 +726,9 @@ public void GraphBuildSwitchCanHaveParameters()
public void GraphBuildSwitchCanBeParameterless()
{
CommandLineSwitches switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List { "/graph" }, switches);
+ parser.GatherCommandLineSwitches(["/graph"], switches);
switches[CommandLineSwitches.ParameterizedSwitch.GraphBuild].ShouldBe(Array.Empty());
@@ -733,8 +739,9 @@ public void GraphBuildSwitchCanBeParameterless()
public void InputResultsCachesSupportsMultipleOccurrence()
{
CommandLineSwitches switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List() { "/irc", "/irc:a;b", "/irc:c;d" }, switches);
+ parser.GatherCommandLineSwitches(["/irc", "/irc:a;b", "/irc:c;d"], switches);
switches[CommandLineSwitches.ParameterizedSwitch.InputResultsCaches].ShouldBe(new[] { null, "a", "b", "c", "d" });
@@ -745,8 +752,9 @@ public void InputResultsCachesSupportsMultipleOccurrence()
public void OutputResultsCache()
{
CommandLineSwitches switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List() { "/orc:a" }, switches);
+ parser.GatherCommandLineSwitches(["/orc:a"], switches);
switches[CommandLineSwitches.ParameterizedSwitch.OutputResultsCache].ShouldBe(new[] { "a" });
@@ -757,8 +765,9 @@ public void OutputResultsCache()
public void OutputResultsCachesDoesNotSupportMultipleOccurrences()
{
CommandLineSwitches switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List() { "/orc:a", "/orc:b" }, switches);
+ parser.GatherCommandLineSwitches(["/orc:a", "/orc:b"], switches);
switches.HaveErrors().ShouldBeTrue();
}
@@ -1288,8 +1297,9 @@ public void ExtractAnyLoggerParameterPickLast()
public void ProcessWarnAsErrorSwitchNotSpecified()
{
CommandLineSwitches commandLineSwitches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List(new[] { "" }), commandLineSwitches);
+ parser.GatherCommandLineSwitches([""], commandLineSwitches);
Assert.Null(MSBuildApp.ProcessWarnAsErrorSwitch(commandLineSwitches));
}
@@ -1303,16 +1313,17 @@ public void ProcessWarnAsErrorSwitchWithCodes()
ISet expectedWarningsAsErrors = new HashSet(StringComparer.OrdinalIgnoreCase) { "a", "B", "c", "D", "e" };
CommandLineSwitches commandLineSwitches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List(new[]
- {
+ parser.GatherCommandLineSwitches(
+ [
"\"/warnaserror: a,B ; c \"", // Leading, trailing, leading and trailing whitespace
"/warnaserror:A,b,C", // Repeats of different case
"\"/warnaserror:, ,,\"", // Empty items
"/err:D,d;E,e", // A different source with new items and uses the short form
"/warnaserror:a", // A different source with a single duplicate
"/warnaserror:a,b", // A different source with multiple duplicates
- }), commandLineSwitches);
+ ], commandLineSwitches);
ISet actualWarningsAsErrors = MSBuildApp.ProcessWarnAsErrorSwitch(commandLineSwitches);
@@ -1328,12 +1339,13 @@ public void ProcessWarnAsErrorSwitchWithCodes()
public void ProcessWarnAsErrorSwitchEmptySwitchClearsSet()
{
CommandLineSwitches commandLineSwitches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List(new[]
- {
+ parser.GatherCommandLineSwitches(
+ [
"/warnaserror:a;b;c",
"/warnaserror",
- }), commandLineSwitches);
+ ], commandLineSwitches);
ISet actualWarningsAsErrors = MSBuildApp.ProcessWarnAsErrorSwitch(commandLineSwitches);
@@ -1351,13 +1363,14 @@ public void ProcessWarnAsErrorSwitchValuesAfterEmptyAddOn()
ISet expectedWarningsAsErors = new HashSet(StringComparer.OrdinalIgnoreCase) { "e", "f", "g" };
CommandLineSwitches commandLineSwitches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List(new[]
- {
+ parser.GatherCommandLineSwitches(
+ [
"/warnaserror:a;b;c",
"/warnaserror",
"/warnaserror:e;f;g",
- }), commandLineSwitches);
+ ], commandLineSwitches);
ISet actualWarningsAsErrors = MSBuildApp.ProcessWarnAsErrorSwitch(commandLineSwitches);
@@ -1373,8 +1386,9 @@ public void ProcessWarnAsErrorSwitchValuesAfterEmptyAddOn()
public void ProcessWarnAsErrorSwitchEmpty()
{
CommandLineSwitches commandLineSwitches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List(new[] { "/warnaserror" }), commandLineSwitches);
+ parser.GatherCommandLineSwitches(["/warnaserror"], commandLineSwitches);
ISet actualWarningsAsErrors = MSBuildApp.ProcessWarnAsErrorSwitch(commandLineSwitches);
@@ -1390,10 +1404,11 @@ public void ProcessWarnAsErrorSwitchEmpty()
public void ProcessWarnAsMessageSwitchEmpty()
{
CommandLineSwitches commandLineSwitches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
// Set "expanded" content to match the placeholder so the verify can use the exact resource string as "expected."
string command = "{0}";
- MSBuildApp.GatherCommandLineSwitches(new List(new[] { "/warnasmessage" }), commandLineSwitches, command);
+ parser.GatherCommandLineSwitches(["/warnasmessage"], commandLineSwitches, command);
VerifySwitchError(commandLineSwitches, "/warnasmessage", AssemblyResources.GetString("MissingWarnAsMessageParameterError"));
}
@@ -1410,13 +1425,15 @@ public void ProcessEnvironmentVariableSwitch()
env.SetEnvironmentVariable("ENVIRONMENTVARIABLE", string.Empty);
CommandLineSwitches commandLineSwitches = new();
+ CommandLineParser parser = new CommandLineParser();
+
string fullCommandLine = "msbuild validProject.csproj %ENVIRONMENTVARIABLE%";
- MSBuildApp.GatherCommandLineSwitches(new List() { "validProject.csproj", "%ENVIRONMENTVARIABLE%" }, commandLineSwitches, fullCommandLine);
+ parser.GatherCommandLineSwitches(["validProject.csproj", "%ENVIRONMENTVARIABLE%"], commandLineSwitches, fullCommandLine);
VerifySwitchError(commandLineSwitches, "%ENVIRONMENTVARIABLE%", String.Format(AssemblyResources.GetString("EnvironmentVariableAsSwitch"), fullCommandLine));
commandLineSwitches = new();
fullCommandLine = "msbuild %ENVIRONMENTVARIABLE% validProject.csproj";
- MSBuildApp.GatherCommandLineSwitches(new List() { "%ENVIRONMENTVARIABLE%", "validProject.csproj" }, commandLineSwitches, fullCommandLine);
+ parser.GatherCommandLineSwitches(["%ENVIRONMENTVARIABLE%", "validProject.csproj"], commandLineSwitches, fullCommandLine);
VerifySwitchError(commandLineSwitches, "%ENVIRONMENTVARIABLE%", String.Format(AssemblyResources.GetString("EnvironmentVariableAsSwitch"), fullCommandLine));
}
}
@@ -1430,16 +1447,17 @@ public void ProcessWarnAsMessageSwitchWithCodes()
ISet expectedWarningsAsMessages = new HashSet(StringComparer.OrdinalIgnoreCase) { "a", "B", "c", "D", "e" };
CommandLineSwitches commandLineSwitches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List(new[]
- {
+ parser.GatherCommandLineSwitches(
+ [
"\"/warnasmessage: a,B ; c \"", // Leading, trailing, leading and trailing whitespace
"/warnasmessage:A,b,C", // Repeats of different case
"\"/warnasmessage:, ,,\"", // Empty items
"/nowarn:D,d;E,e", // A different source with new items and uses the short form
"/warnasmessage:a", // A different source with a single duplicate
"/warnasmessage:a,b", // A different source with multiple duplicates
- }), commandLineSwitches);
+ ], commandLineSwitches);
ISet actualWarningsAsMessages = MSBuildApp.ProcessWarnAsMessageSwitch(commandLineSwitches);
@@ -1455,8 +1473,9 @@ public void ProcessWarnAsMessageSwitchWithCodes()
public void ProcessProfileEvaluationEmpty()
{
CommandLineSwitches commandLineSwitches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- MSBuildApp.GatherCommandLineSwitches(new List(new[] { "/profileevaluation" }), commandLineSwitches);
+ parser.GatherCommandLineSwitches(["/profileevaluation"], commandLineSwitches);
commandLineSwitches[CommandLineSwitches.ParameterizedSwitch.ProfileEvaluation][0].ShouldBe("no-file");
}
@@ -1548,11 +1567,7 @@ public void ProcessInvalidTargetSwitch()
using TestEnvironment testEnvironment = TestEnvironment.Create();
string project = testEnvironment.CreateTestProjectWithFiles("project.proj", projectContent).ProjectFile;
-#if FEATURE_GET_COMMANDLINE
- MSBuildApp.Execute(@"msbuild.exe " + project + " /t:foo.bar").ShouldBe(MSBuildApp.ExitType.SwitchError);
-#else
- MSBuildApp.Execute(new[] { @"msbuild.exe", project, "/t:foo.bar" }).ShouldBe(MSBuildApp.ExitType.SwitchError);
-#endif
+ MSBuildApp.Execute([@"msbuild.exe", project, "/t:foo.bar"]).ShouldBe(MSBuildApp.ExitType.SwitchError);
}
///
diff --git a/src/MSBuild.UnitTests/ProjectSchemaValidationHandler_Tests.cs b/src/MSBuild.UnitTests/ProjectSchemaValidationHandler_Tests.cs
index 7a224860a2f..82fa56a588c 100644
--- a/src/MSBuild.UnitTests/ProjectSchemaValidationHandler_Tests.cs
+++ b/src/MSBuild.UnitTests/ProjectSchemaValidationHandler_Tests.cs
@@ -52,7 +52,7 @@ public void VerifyInvalidProjectSchema()
");
string quotedProjectFilename = "\"" + projectFilename + "\"";
- Assert.Equal(MSBuildApp.ExitType.InitializationError, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFilename + " /validate:\"" + msbuildTempXsdFilenames[0] + "\""));
+ Assert.Equal(MSBuildApp.ExitType.InitializationError, MSBuildApp.Execute([@"c:\foo\msbuild.exe", quotedProjectFilename, $"/validate:\"{msbuildTempXsdFilenames[0]}\""]));
}
finally
{
@@ -95,7 +95,7 @@ public void VerifyInvalidSchemaItself1()
");
string quotedProjectFile = "\"" + projectFilename + "\"";
- Assert.Equal(MSBuildApp.ExitType.InitializationError, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFile + " /validate:\"" + invalidSchemaFile + "\""));
+ Assert.Equal(MSBuildApp.ExitType.InitializationError, MSBuildApp.Execute([@"c:\foo\msbuild.exe", quotedProjectFile, $"/validate:\"{invalidSchemaFile}\""]));
}
finally
{
@@ -155,7 +155,7 @@ public void VerifyInvalidSchemaItself2()
string quotedProjectFile = "\"" + projectFilename + "\"";
- Assert.Equal(MSBuildApp.ExitType.InitializationError, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFile + " /validate:\"" + invalidSchemaFile + "\""));
+ Assert.Equal(MSBuildApp.ExitType.InitializationError, MSBuildApp.Execute([@"c:\foo\msbuild.exe", quotedProjectFile, $"/validate:\"{invalidSchemaFile}\""]));
}
finally
{
@@ -203,7 +203,7 @@ public void VerifyValidProjectSchema()
msbuildTempXsdFilenames = PrepareSchemaFiles();
string quotedProjectFile = "\"" + projectFilename + "\"";
- Assert.Equal(MSBuildApp.ExitType.Success, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFile + " /validate:\"" + msbuildTempXsdFilenames[0] + "\""));
+ Assert.Equal(MSBuildApp.ExitType.Success, MSBuildApp.Execute([@"c:\foo\msbuild.exe", quotedProjectFile, $"/validate:\"{msbuildTempXsdFilenames[0]}\""]));
// ProjectSchemaValidationHandler.VerifyProjectSchema
// (
@@ -256,7 +256,7 @@ public void VerifyInvalidImportNotCaughtBySchema()
msbuildTempXsdFilenames = PrepareSchemaFiles();
string quotedProjectFile = "\"" + projectFilename + "\"";
- Assert.Equal(MSBuildApp.ExitType.Success, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFile + " /validate:\"" + msbuildTempXsdFilenames[0] + "\""));
+ Assert.Equal(MSBuildApp.ExitType.Success, MSBuildApp.Execute([@"c:\foo\msbuild.exe", quotedProjectFile, $"/validate:\"{msbuildTempXsdFilenames[0]}\""]));
// ProjectSchemaValidationHandler.VerifyProjectSchema
// (
diff --git a/src/MSBuild.UnitTests/XMake_Tests.cs b/src/MSBuild.UnitTests/XMake_Tests.cs
index 4173c747739..f36fa01e38b 100644
--- a/src/MSBuild.UnitTests/XMake_Tests.cs
+++ b/src/MSBuild.UnitTests/XMake_Tests.cs
@@ -96,11 +96,9 @@ public XMakeAppTests(ITestOutputHelper output)
public void GatherCommandLineSwitchesTwoProperties()
{
CommandLineSwitches switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- var arguments = new List();
- arguments.AddRange(new[] { "/p:a=b", "/p:c=d" });
-
- MSBuildApp.GatherCommandLineSwitches(arguments, switches);
+ parser.GatherCommandLineSwitches(["/p:a=b", "/p:c=d"], switches);
string[] parameters = switches[CommandLineSwitches.ParameterizedSwitch.Property];
parameters[0].ShouldBe("a=b");
@@ -111,13 +109,9 @@ public void GatherCommandLineSwitchesTwoProperties()
public void GatherCommandLineSwitchesAnyDash()
{
var switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- var arguments = new List {
- "-p:a=b",
- "--p:maxcpucount=8"
- };
-
- MSBuildApp.GatherCommandLineSwitches(arguments, switches);
+ parser.GatherCommandLineSwitches(["-p:a=b", "--p:maxcpucount=8"], switches);
string[] parameters = switches[CommandLineSwitches.ParameterizedSwitch.Property];
parameters[0].ShouldBe("a=b");
@@ -128,11 +122,9 @@ public void GatherCommandLineSwitchesAnyDash()
public void GatherCommandLineSwitchesMaxCpuCountWithArgument()
{
CommandLineSwitches switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- var arguments = new List();
- arguments.AddRange(new[] { "/m:2" });
-
- MSBuildApp.GatherCommandLineSwitches(arguments, switches);
+ parser.GatherCommandLineSwitches(["/m:2"], switches);
string[] parameters = switches[CommandLineSwitches.ParameterizedSwitch.MaxCPUCount];
parameters[0].ShouldBe("2");
@@ -145,11 +137,9 @@ public void GatherCommandLineSwitchesMaxCpuCountWithArgument()
public void GatherCommandLineSwitchesMaxCpuCountWithoutArgument()
{
CommandLineSwitches switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- var arguments = new List();
- arguments.AddRange(new[] { "/m:3", "/m" });
-
- MSBuildApp.GatherCommandLineSwitches(arguments, switches);
+ parser.GatherCommandLineSwitches(["/m:3", "/m"], switches);
string[] parameters = switches[CommandLineSwitches.ParameterizedSwitch.MaxCPUCount];
parameters[1].ShouldBe(Convert.ToString(NativeMethodsShared.GetLogicalCoreCount()));
@@ -165,11 +155,9 @@ public void GatherCommandLineSwitchesMaxCpuCountWithoutArgument()
public void GatherCommandLineSwitchesMaxCpuCountWithoutArgumentButWithColon()
{
CommandLineSwitches switches = new CommandLineSwitches();
+ CommandLineParser parser = new CommandLineParser();
- var arguments = new List();
- arguments.AddRange(new[] { "/m:" });
-
- MSBuildApp.GatherCommandLineSwitches(arguments, switches);
+ parser.GatherCommandLineSwitches(["/m:"], switches);
string[] parameters = switches[CommandLineSwitches.ParameterizedSwitch.MaxCPUCount];
parameters.Length.ShouldBe(0);
@@ -459,44 +447,44 @@ public void ExtractSwitchParametersTest()
{
string commandLineArg = "\"/p:foo=\"bar";
string unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out var doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":\"foo=\"bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":\"foo=\"bar");
doubleQuotesRemovedFromArg.ShouldBe(2);
commandLineArg = "\"/p:foo=bar\"";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar");
doubleQuotesRemovedFromArg.ShouldBe(2);
commandLineArg = "/p:foo=bar";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar");
doubleQuotesRemovedFromArg.ShouldBe(0);
commandLineArg = "\"\"/p:foo=bar\"";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar\"");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar\"");
doubleQuotesRemovedFromArg.ShouldBe(3);
// this test is totally unreal -- we'd never attempt to extract switch parameters if the leading character is not a
// switch indicator (either '-' or '/') -- here the leading character is a double-quote
commandLineArg = "\"\"\"/p:foo=bar\"";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "/p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar\"");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "/p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar\"");
doubleQuotesRemovedFromArg.ShouldBe(3);
commandLineArg = "\"/pr\"operty\":foo=bar";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "property", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "property", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar");
doubleQuotesRemovedFromArg.ShouldBe(3);
commandLineArg = "\"/pr\"op\"\"erty\":foo=bar\"";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "property", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "property", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":foo=bar");
doubleQuotesRemovedFromArg.ShouldBe(6);
commandLineArg = "/p:\"foo foo\"=\"bar bar\";\"baz=onga\"";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":\"foo foo\"=\"bar bar\";\"baz=onga\"");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 1).ShouldBe(":\"foo foo\"=\"bar bar\";\"baz=onga\"");
doubleQuotesRemovedFromArg.ShouldBe(6);
}
@@ -505,37 +493,37 @@ public void ExtractSwitchParametersTestDoubleDash()
{
var commandLineArg = "\"--p:foo=\"bar";
var unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out var doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":\"foo=\"bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":\"foo=\"bar");
doubleQuotesRemovedFromArg.ShouldBe(2);
commandLineArg = "\"--p:foo=bar\"";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar");
doubleQuotesRemovedFromArg.ShouldBe(2);
commandLineArg = "--p:foo=bar";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar");
doubleQuotesRemovedFromArg.ShouldBe(0);
commandLineArg = "\"\"--p:foo=bar\"";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar\"");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar\"");
doubleQuotesRemovedFromArg.ShouldBe(3);
commandLineArg = "\"--pr\"operty\":foo=bar";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "property", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "property", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar");
doubleQuotesRemovedFromArg.ShouldBe(3);
commandLineArg = "\"--pr\"op\"\"erty\":foo=bar\"";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "property", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "property", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":foo=bar");
doubleQuotesRemovedFromArg.ShouldBe(6);
commandLineArg = "--p:\"foo foo\"=\"bar bar\";\"baz=onga\"";
unquotedCommandLineArg = QuotingUtilities.Unquote(commandLineArg, out doubleQuotesRemovedFromArg);
- MSBuildApp.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":\"foo foo\"=\"bar bar\";\"baz=onga\"");
+ CommandLineParser.ExtractSwitchParameters(commandLineArg, unquotedCommandLineArg, doubleQuotesRemovedFromArg, "p", unquotedCommandLineArg.IndexOf(':'), 2).ShouldBe(":\"foo foo\"=\"bar bar\";\"baz=onga\"");
doubleQuotesRemovedFromArg.ShouldBe(6);
}
@@ -548,11 +536,11 @@ public void GetLengthOfSwitchIndicatorTest()
var commandLineSwitchWithNoneOrIncorrectIndicator = "zSwitch";
- MSBuildApp.GetLengthOfSwitchIndicator(commandLineSwitchWithSlash).ShouldBe(1);
- MSBuildApp.GetLengthOfSwitchIndicator(commandLineSwitchWithSingleDash).ShouldBe(1);
- MSBuildApp.GetLengthOfSwitchIndicator(commandLineSwitchWithDoubleDash).ShouldBe(2);
+ CommandLineParser.GetLengthOfSwitchIndicator(commandLineSwitchWithSlash).ShouldBe(1);
+ CommandLineParser.GetLengthOfSwitchIndicator(commandLineSwitchWithSingleDash).ShouldBe(1);
+ CommandLineParser.GetLengthOfSwitchIndicator(commandLineSwitchWithDoubleDash).ShouldBe(2);
- MSBuildApp.GetLengthOfSwitchIndicator(commandLineSwitchWithNoneOrIncorrectIndicator).ShouldBe(0);
+ CommandLineParser.GetLengthOfSwitchIndicator(commandLineSwitchWithNoneOrIncorrectIndicator).ShouldBe(0);
}
[Theory]
@@ -562,12 +550,7 @@ public void GetLengthOfSwitchIndicatorTest()
[InlineData(@"/h")]
public void Help(string indicator)
{
- MSBuildApp.Execute(
-#if FEATURE_GET_COMMANDLINE
- @$"c:\bin\msbuild.exe {indicator} ")
-#else
- new[] { @"c:\bin\msbuild.exe", indicator })
-#endif
+ MSBuildApp.Execute([@"c:\bin\msbuild.exe", indicator])
.ShouldBe(MSBuildApp.ExitType.Success);
}
@@ -660,19 +643,11 @@ public void VersionSwitchDisableChangeWave()
public void ErrorCommandLine()
{
string oldValueForMSBuildLoadMicrosoftTargetsReadOnly = Environment.GetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly");
-#if FEATURE_GET_COMMANDLINE
- MSBuildApp.Execute(@"c:\bin\msbuild.exe -junk").ShouldBe(MSBuildApp.ExitType.SwitchError);
-
- MSBuildApp.Execute(@"msbuild.exe -t").ShouldBe(MSBuildApp.ExitType.SwitchError);
-
- MSBuildApp.Execute(@"msbuild.exe @bogus.rsp").ShouldBe(MSBuildApp.ExitType.InitializationError);
-#else
- MSBuildApp.Execute(new[] { @"c:\bin\msbuild.exe", "-junk" }).ShouldBe(MSBuildApp.ExitType.SwitchError);
- MSBuildApp.Execute(new[] { @"msbuild.exe", "-t" }).ShouldBe(MSBuildApp.ExitType.SwitchError);
+ MSBuildApp.Execute([@"c:\bin\msbuild.exe", "-junk"]).ShouldBe(MSBuildApp.ExitType.SwitchError);
+ MSBuildApp.Execute([@"msbuild.exe", "-t"]).ShouldBe(MSBuildApp.ExitType.SwitchError);
+ MSBuildApp.Execute([@"msbuild.exe", "@bogus.rsp"]).ShouldBe(MSBuildApp.ExitType.InitializationError);
- MSBuildApp.Execute(new[] { @"msbuild.exe", "@bogus.rsp" }).ShouldBe(MSBuildApp.ExitType.InitializationError);
-#endif
Environment.SetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly", oldValueForMSBuildLoadMicrosoftTargetsReadOnly);
}
@@ -1153,11 +1128,7 @@ public void TestEnvironmentTest()
sw.WriteLine(projectString);
}
// Should pass
-#if FEATURE_GET_COMMANDLINE
- MSBuildApp.Execute(@"c:\bin\msbuild.exe " + quotedProjectFileName).ShouldBe(MSBuildApp.ExitType.Success);
-#else
- MSBuildApp.Execute(new[] { @"c:\bin\msbuild.exe", quotedProjectFileName }).ShouldBe(MSBuildApp.ExitType.Success);
-#endif
+ MSBuildApp.Execute([@"c:\bin\msbuild.exe", quotedProjectFileName]).ShouldBe(MSBuildApp.ExitType.Success);
}
finally
{
@@ -1190,21 +1161,15 @@ public void MSBuildEngineLogger()
{
sw.WriteLine(projectString);
}
-#if FEATURE_GET_COMMANDLINE
- // Should pass
- MSBuildApp.Execute(@$"c:\bin\msbuild.exe /logger:FileLogger,""Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"";""LogFile={logFile}"" /verbosity:detailed " + quotedProjectFileName).ShouldBe(MSBuildApp.ExitType.Success);
-
-#else
// Should pass
- MSBuildApp.Execute(
- new[]
- {
+ MSBuildApp
+ .Execute([
NativeMethodsShared.IsWindows ? @"c:\bin\msbuild.exe" : "/msbuild.exe",
@$"/logger:FileLogger,""Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"";""LogFile={logFile}""",
"/verbosity:detailed",
- quotedProjectFileName
- }).ShouldBe(MSBuildApp.ExitType.Success);
-#endif
+ quotedProjectFileName])
+ .ShouldBe(MSBuildApp.ExitType.Success);
+
File.Exists(logFile).ShouldBeTrue();
var logFileContents = File.ReadAllText(logFile);
@@ -2946,11 +2911,7 @@ public void ThrowsWhenMaxCpuCountTooLargeForMultiThreadedAndForceAllTasksOutOfPr
testEnvironment.SetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC", "1");
string project = testEnvironment.CreateTestProjectWithFiles("project.proj", projectContent).ProjectFile;
-#if FEATURE_GET_COMMANDLINE
- MSBuildApp.Execute(@"c:\bin\msbuild.exe " + project + " / m:257 /mt").ShouldBe(MSBuildApp.ExitType.SwitchError);
-#else
- MSBuildApp.Execute(new[] { @"c:\bin\msbuild.exe", project, "/m:257 /mt" }).ShouldBe(MSBuildApp.ExitType.SwitchError);
-#endif
+ MSBuildApp.Execute([@"c:\bin\msbuild.exe", project, "/m:257 /mt"]).ShouldBe(MSBuildApp.ExitType.SwitchError);
}
private string CopyMSBuild()
diff --git a/src/MSBuild/AssemblyInfo.cs b/src/MSBuild/AssemblyInfo.cs
index f93e8a6db00..49249f7e410 100644
--- a/src/MSBuild/AssemblyInfo.cs
+++ b/src/MSBuild/AssemblyInfo.cs
@@ -10,6 +10,7 @@
[assembly: InternalsVisibleTo("Microsoft.Build.CommandLine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")]
[assembly: InternalsVisibleTo("Microsoft.Build.Utilities.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")]
+[assembly: InternalsVisibleTo("dotnet, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
// This will enable passing the SafeDirectories flag to any P/Invoke calls/implementations within the assembly,
// so that we don't run into known security issues with loading libraries from unsafe locations
diff --git a/src/MSBuild/CommandLine/CommandLineParser.cs b/src/MSBuild/CommandLine/CommandLineParser.cs
new file mode 100644
index 00000000000..291f58da04d
--- /dev/null
+++ b/src/MSBuild/CommandLine/CommandLineParser.cs
@@ -0,0 +1,615 @@
+// 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 System.Xml.Linq;
+using Microsoft.Build.Shared;
+using Microsoft.Build.Shared.FileSystem;
+
+#nullable disable
+
+namespace Microsoft.Build.CommandLine
+{
+ internal class CommandLineParser
+ {
+ ///
+ /// String replacement pattern to support paths in response files.
+ ///
+ private const string responseFilePathReplacement = "%MSBuildThisFileDirectory%";
+
+ ///
+ /// The name of an auto-response file to search for in the project directory and above.
+ ///
+ private const string directoryResponseFileName = "Directory.Build.rsp";
+
+ ///
+ /// The name of the auto-response file.
+ ///
+ private const string autoResponseFileName = "MSBuild.rsp";
+
+ ///
+ /// Used to keep track of response files to prevent them from
+ /// being included multiple times (or even recursively).
+ ///
+ private List includedResponseFiles;
+
+ internal IReadOnlyList IncludedResponseFiles => includedResponseFiles ?? (IReadOnlyList)Array.Empty();
+
+ public (CommandLineSwitches commandLineSwitches, CommandLineSwitches responseFileSwitches) Parse(IEnumerable commandLineArgs)
+ {
+ GatherAllSwitches(
+ commandLineArgs,
+ out CommandLineSwitches responseFileSwitches,
+ out CommandLineSwitches commandLineSwitches,
+ out _,
+ out _);
+
+ return (commandLineSwitches, responseFileSwitches);
+ }
+
+ ///
+ /// Gets all specified switches, from the command line, as well as all
+ /// response files, including the auto-response file.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Combined bag of switches.
+ internal void GatherAllSwitches(
+ IEnumerable commandLineArgs,
+ out CommandLineSwitches switchesFromAutoResponseFile,
+ out CommandLineSwitches switchesNotFromAutoResponseFile,
+ out string fullCommandLine,
+ out string exeName)
+ {
+ ResetGatheringSwitchesState();
+
+ // discard the first piece, because that's the path to the executable -- the rest are args
+ commandLineArgs = commandLineArgs.Skip(1);
+
+ exeName = BuildEnvironmentHelper.Instance.CurrentMSBuildExePath;
+
+#if USE_MSBUILD_DLL_EXTN
+ var msbuildExtn = ".dll";
+#else
+ var msbuildExtn = ".exe";
+#endif
+ if (!exeName.EndsWith(msbuildExtn, StringComparison.OrdinalIgnoreCase))
+ {
+ exeName += msbuildExtn;
+ }
+
+ fullCommandLine = $"'{string.Join(" ", commandLineArgs)}'";
+
+ // 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);
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ /// Internal for unit testing only.
+ ///
+ internal void GatherCommandLineSwitches(IEnumerable 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);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ 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 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();
+
+ 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);
+ }
+ }
+
+ ///
+ /// Called when a switch that doesn't take parameters is detected on the command line.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Called when a switch that takes parameters is detected on the command line. This method flags errors and stores the
+ /// switch parameters.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Identifies if there is rsp files near the project file
+ ///
+ /// true if there autoresponse file was found
+ 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;
+ }
+
+ ///
+ /// Extracts a switch's parameters after processing all quoting around the switch.
+ ///
+ ///
+ /// This method is marked "internal" for unit-testing purposes only -- ideally it should be "private".
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The given switch's parameters (with interesting quoting preserved).
+ 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;
+ }
+
+ ///
+ /// Checks whether envVar is an environment variable. MSBuild uses
+ /// Environment.ExpandEnvironmentVariables(string), which only
+ /// considers %-delimited variables.
+ ///
+ /// A possible environment variable
+ /// Whether envVar is an environment variable
+ private static bool IsEnvironmentVariable(string envVar)
+ {
+ return envVar.StartsWith("%") && envVar.EndsWith("%") && envVar.Length > 1;
+ }
+
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+
+ ///
+ /// Checks whether an argument given as a parameter starts with valid indicator,
+ ///
which means, whether switch begins with one of: "/", "-", "--"
+ ///
+ /// Command line argument with beginning indicator (e.g. --help).
+ ///
This argument has to be unquoted, otherwise the first character will always be a quote character "
+ /// true if argument's beginning matches one of possible indicators
+ ///
false if argument's beginning doesn't match any of correct indicator
+ ///
+ private static bool ValidateSwitchIndicatorInUnquotedArgument(string unquotedCommandLineArgument)
+ {
+ return unquotedCommandLineArgument.StartsWith("-", StringComparison.Ordinal) // superset of "--"
+ || unquotedCommandLineArgument.StartsWith("/", StringComparison.Ordinal);
+ }
+
+ ///
+ /// Gets the length of the switch indicator (- or / or --)
+ ///
The length returned from this method is deduced from the beginning sequence of unquoted argument.
+ ///
This way it will "assume" that there's no further error (e.g. // or ---) which would also be considered as a correct indicator.
+ ///
+ /// Unquoted argument with leading indicator and name
+ /// Correct length of used indicator
+ ///
0 if no leading sequence recognized as correct indicator
+ /// 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;
+ }
+ }
+
+ public void ResetGatheringSwitchesState()
+ {
+ includedResponseFiles = new List();
+ CommandLineSwitches.SwitchesFromResponseFiles = new();
+ }
+ }
+}
diff --git a/src/MSBuild/CommandLineSwitchException.cs b/src/MSBuild/CommandLine/CommandLineSwitchException.cs
similarity index 100%
rename from src/MSBuild/CommandLineSwitchException.cs
rename to src/MSBuild/CommandLine/CommandLineSwitchException.cs
diff --git a/src/MSBuild/CommandLineSwitches.cs b/src/MSBuild/CommandLine/CommandLineSwitches.cs
similarity index 100%
rename from src/MSBuild/CommandLineSwitches.cs
rename to src/MSBuild/CommandLine/CommandLineSwitches.cs
diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj
index e43039e8e6c..d923d5bc84f 100644
--- a/src/MSBuild/MSBuild.csproj
+++ b/src/MSBuild/MSBuild.csproj
@@ -131,8 +131,9 @@
-
-
+
+
+
@@ -207,7 +208,7 @@
-
+
diff --git a/src/MSBuild/MSBuildClientApp.cs b/src/MSBuild/MSBuildClientApp.cs
index 3eeb975bc40..33100583fe2 100644
--- a/src/MSBuild/MSBuildClientApp.cs
+++ b/src/MSBuild/MSBuildClientApp.cs
@@ -34,18 +34,12 @@ internal static class MSBuildClientApp
///
/// The locations of msbuild exe/dll and dotnet.exe would be automatically detected if called from dotnet or msbuild cli. Calling this function from other executables might not work.
///
- public static MSBuildApp.ExitType Execute(
-#if FEATURE_GET_COMMANDLINE
- string commandLine,
-#else
- string[] commandLine,
-#endif
- CancellationToken cancellationToken)
+ public static MSBuildApp.ExitType Execute(string[] commandLineArgs, CancellationToken cancellationToken)
{
string msbuildLocation = BuildEnvironmentHelper.Instance.CurrentMSBuildExePath;
return Execute(
- commandLine,
+ commandLineArgs,
msbuildLocation,
cancellationToken);
}
@@ -53,7 +47,7 @@ public static MSBuildApp.ExitType Execute(
///
/// This is the entry point for the MSBuild client.
///
- /// The command line to process. The first argument
+ /// The command line to process. The first argument
/// on the command line is assumed to be the name/path of the executable, and
/// is ignored.
/// Full path to current MSBuild.exe if executable is MSBuild.exe,
@@ -61,16 +55,9 @@ public static MSBuildApp.ExitType Execute(
/// Cancellation token.
/// A value of type that indicates whether the build succeeded,
/// or the manner in which it failed.
- public static MSBuildApp.ExitType Execute(
-#if FEATURE_GET_COMMANDLINE
- string commandLine,
-#else
- string[] commandLine,
-#endif
- string msbuildLocation,
- CancellationToken cancellationToken)
+ public static MSBuildApp.ExitType Execute(string[] commandLineArgs, string msbuildLocation, CancellationToken cancellationToken)
{
- MSBuildClient msbuildClient = new MSBuildClient(commandLine, msbuildLocation);
+ MSBuildClient msbuildClient = new MSBuildClient(commandLineArgs, msbuildLocation);
MSBuildClientExitResult exitResult = msbuildClient.Execute(cancellationToken);
if (exitResult.MSBuildClientExitType == MSBuildClientExitType.ServerBusy ||
@@ -84,7 +71,7 @@ public static MSBuildApp.ExitType Execute(
}
// Server is busy, fallback to old behavior.
- return MSBuildApp.Execute(commandLine);
+ return MSBuildApp.Execute(commandLineArgs);
}
if (exitResult.MSBuildClientExitType == MSBuildClientExitType.Success &&
diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs
index f6bb284bbe3..27ba886afba 100644
--- a/src/MSBuild/XMake.cs
+++ b/src/MSBuild/XMake.cs
@@ -143,6 +143,8 @@ public enum ExitType
private static readonly char[] s_commaSemicolon = { ',', ';' };
+ private static CommandLineParser commandLineParser;
+
///
/// Static constructor
///
@@ -158,6 +160,7 @@ static MSBuildApp()
// any configuration file exceptions can be caught here. //
////////////////////////////////////////////////////////////////////////////////
s_exePath = Path.GetDirectoryName(FileUtilities.ExecutingAssemblyPath);
+ commandLineParser = new CommandLineParser();
s_initialized = true;
}
@@ -233,14 +236,13 @@ private static void HandleConfigurationException(Exception ex)
#if FEATURE_APPDOMAIN
[LoaderOptimization(LoaderOptimization.MultiDomain)]
#endif
-#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter
- public static int Main(
-#if !FEATURE_GET_COMMANDLINE
- string[] args
-#endif
- )
-#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter
+ public static int Main(string[] args)
{
+ // When running on CoreCLR(.NET), insert the command executable path as the first element of the args array.
+ args = BuildEnvironmentHelper.IsRunningOnCoreClr ?
+ [BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, .. args] :
+ QuotingUtilities.SplitUnquoted(Environment.CommandLine).ToArray();
+
// Setup the console UI.
using AutomaticEncodingRestorer _ = new();
SetConsoleUI();
@@ -263,35 +265,18 @@ string[] args
if (
Environment.GetEnvironmentVariable(Traits.UseMSBuildServerEnvVarName) == "1" &&
!Traits.Instance.EscapeHatches.EnsureStdOutForChildNodesIsPrimaryStdout &&
- CanRunServerBasedOnCommandLineSwitches(
-#if FEATURE_GET_COMMANDLINE
- Environment.CommandLine))
-#else
- ConstructArrayArg(args)))
-#endif
+ CanRunServerBasedOnCommandLineSwitches(args))
{
Console.CancelKeyPress += Console_CancelKeyPress;
// Use the client app to execute build in msbuild server. Opt-in feature.
- exitCode = ((s_initialized && MSBuildClientApp.Execute(
-#if FEATURE_GET_COMMANDLINE
- Environment.CommandLine,
-#else
- ConstructArrayArg(args),
-#endif
- s_buildCancellationSource.Token) == ExitType.Success) ? 0 : 1);
+ exitCode = ((s_initialized && MSBuildClientApp.Execute(args, s_buildCancellationSource.Token) == ExitType.Success) ? 0 : 1);
}
else
{
// return 0 on success, non-zero on failure
- exitCode = ((s_initialized && Execute(
-#if FEATURE_GET_COMMANDLINE
- Environment.CommandLine)
-#else
- ConstructArrayArg(args))
-#endif
- == ExitType.Success) ? 0 : 1);
+ exitCode = ((s_initialized && Execute(args) == ExitType.Success) ? 0 : 1);
}
if (Environment.GetEnvironmentVariable("MSBUILDDUMPPROCESSCOUNTERS") == "1")
@@ -310,19 +295,14 @@ string[] args
///
/// Will not throw. If arguments processing fails, we will not run it on server - no reason as it will not run any build anyway.
///
- private static bool CanRunServerBasedOnCommandLineSwitches(
-#if FEATURE_GET_COMMANDLINE
- string commandLine)
-#else
- string[] commandLine)
-#endif
+ private static bool CanRunServerBasedOnCommandLineSwitches(string[] commandLine)
{
bool canRunServer = true;
try
{
- GatherAllSwitches(commandLine, out var switchesFromAutoResponseFile, out var switchesNotFromAutoResponseFile, out string fullCommandLine);
+ commandLineParser.GatherAllSwitches(commandLine, out var switchesFromAutoResponseFile, out var switchesNotFromAutoResponseFile, out string fullCommandLine, out s_exeName);
CommandLineSwitches commandLineSwitches = CombineSwitchesRespectingPriority(switchesFromAutoResponseFile, switchesNotFromAutoResponseFile, fullCommandLine);
- if (CheckAndGatherProjectAutoResponseFile(switchesFromAutoResponseFile, commandLineSwitches, false, fullCommandLine))
+ if (commandLineParser.CheckAndGatherProjectAutoResponseFile(switchesFromAutoResponseFile, commandLineSwitches, false, fullCommandLine))
{
commandLineSwitches = CombineSwitchesRespectingPriority(switchesFromAutoResponseFile, switchesNotFromAutoResponseFile, fullCommandLine);
}
@@ -353,23 +333,6 @@ private static bool CanRunServerBasedOnCommandLineSwitches(
return canRunServer;
}
-#if !FEATURE_GET_COMMANDLINE
- ///
- /// Insert the command executable path as the first element of the args array.
- ///
- ///
- ///
- private static string[] ConstructArrayArg(string[] args)
- {
- string[] newArgArray = new string[args.Length + 1];
-
- newArgArray[0] = BuildEnvironmentHelper.Instance.CurrentMSBuildExePath;
- Array.Copy(args, 0, newArgArray, 1, args.Length);
-
- return newArgArray;
- }
-#endif // !FEATURE_GET_COMMANDLINE
-
///
/// Append output file with elapsedTime
///
@@ -623,12 +586,7 @@ private static void DebuggerLaunchCheck()
/// is ignored.
/// A value of type ExitType that indicates whether the build succeeded,
/// or the manner in which it failed.
- public static ExitType Execute(
-#if FEATURE_GET_COMMANDLINE
- string commandLine)
-#else
- string[] commandLine)
-#endif
+ public static ExitType Execute(string[] commandLine)
{
DebuggerLaunchCheck();
@@ -645,9 +603,7 @@ public static ExitType Execute(
// and those form the great majority of our unnecessary memory use.
Environment.SetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly", "true");
-#if FEATURE_GET_COMMANDLINE
ErrorUtilities.VerifyThrowArgumentLength(commandLine);
-#endif
AppDomain.CurrentDomain.UnhandledException += ExceptionHandling.UnhandledExceptionHandler;
@@ -659,14 +615,11 @@ public static ExitType Execute(
TextWriter targetsWriter = null;
try
{
-#if FEATURE_GET_COMMANDLINE
- MSBuildEventSource.Log.MSBuildExeStart(commandLine);
-#else
if (MSBuildEventSource.Log.IsEnabled())
{
MSBuildEventSource.Log.MSBuildExeStart(string.Join(" ", commandLine));
}
-#endif
+
Console.CancelKeyPress += cancelHandler;
// check the operating system the code is running on
@@ -722,7 +675,7 @@ public static ExitType Execute(
bool reportFileAccesses = false;
#endif
- GatherAllSwitches(commandLine, out var switchesFromAutoResponseFile, out var switchesNotFromAutoResponseFile, out _);
+ commandLineParser.GatherAllSwitches(commandLine, out var switchesFromAutoResponseFile, out var switchesNotFromAutoResponseFile, out _, out s_exeName);
bool buildCanBeInvoked = ProcessCommandLineSwitches(
switchesFromAutoResponseFile,
@@ -769,11 +722,7 @@ public static ExitType Execute(
ref getTargetResult,
ref getResultOutputFile,
recursing: false,
-#if FEATURE_GET_COMMANDLINE
- commandLine);
-#else
- string.Join(' ', commandLine));
-#endif
+ string.Join(" ", commandLine));
CommandLineSwitches.SwitchesFromResponseFiles = null;
@@ -1073,14 +1022,10 @@ public static ExitType Execute(
preprocessWriter?.Dispose();
targetsWriter?.Dispose();
-#if FEATURE_GET_COMMANDLINE
- MSBuildEventSource.Log.MSBuildExeStop(commandLine);
-#else
if (MSBuildEventSource.Log.IsEnabled())
{
MSBuildEventSource.Log.MSBuildExeStop(string.Join(" ", commandLine));
}
-#endif
}
/**********************************************************************************************************************
* WARNING: Do NOT add any more catch blocks above!
@@ -1221,14 +1166,7 @@ private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs
///
private static void ResetBuildState()
{
- ResetGatheringSwitchesState();
- }
-
- private static void ResetGatheringSwitchesState()
- {
- s_includedResponseFiles = new List();
- usingSwitchesFromAutoResponseFile = false;
- CommandLineSwitches.SwitchesFromResponseFiles = new();
+ commandLineParser.ResetGatheringSwitchesState();
}
///
@@ -1305,11 +1243,7 @@ internal static bool BuildProject(
#if FEATURE_REPORTFILEACCESSES
bool reportFileAccesses,
#endif
-#if FEATURE_GET_COMMANDLINE
- string commandLine)
-#else
string[] commandLine)
-#endif
{
// Set limitation for multithreaded and MSBUILDFORCEALLTASKSOUTOFPROC=1. Max is 256 because of unique task host id generation.
if (multiThreaded && Traits.Instance.ForceAllTasksOutOfProcToTaskHost)
@@ -1564,15 +1498,12 @@ .. distributedLoggerRecords.Select(d => d.CentralLogger)
if (!Traits.Instance.EscapeHatches.DoNotSendDeferredMessagesToBuildManager)
{
var commandLineString =
-#if FEATURE_GET_COMMANDLINE
- commandLine;
-#else
string.Join(" ", commandLine);
-#endif
+
messagesToLogInBuildLoggers.AddRange(GetMessagesToLogInBuildLoggers(commandLineString));
// Log a message for every response file and include it in log
- foreach (var responseFilePath in s_includedResponseFiles)
+ foreach (var responseFilePath in commandLineParser.IncludedResponseFiles)
{
messagesToLogInBuildLoggers.Add(
new BuildManager.DeferredBuildMessage(
@@ -1992,514 +1923,11 @@ internal static void SetConsoleUI()
#endif
}
- ///
- /// Gets all specified switches, from the command line, as well as all
- /// response files, including the auto-response file.
- ///
- ///
- ///
- ///
- ///
- /// Combined bag of switches.
- private static void GatherAllSwitches(
-#if FEATURE_GET_COMMANDLINE
- string commandLine,
-#else
- string[] commandLine,
-#endif
- out CommandLineSwitches switchesFromAutoResponseFile, out CommandLineSwitches switchesNotFromAutoResponseFile, out string fullCommandLine)
- {
- ResetGatheringSwitchesState();
-
-#if FEATURE_GET_COMMANDLINE
- // split the command line on (unquoted) whitespace
- var commandLineArgs = QuotingUtilities.SplitUnquoted(commandLine);
-
- s_exeName = FileUtilities.FixFilePath(QuotingUtilities.Unquote(commandLineArgs[0]));
-#else
- var commandLineArgs = new List(commandLine);
-
- s_exeName = BuildEnvironmentHelper.Instance.CurrentMSBuildExePath;
-#endif
-
-#if USE_MSBUILD_DLL_EXTN
- var msbuildExtn = ".dll";
-#else
- var msbuildExtn = ".exe";
-#endif
- if (!s_exeName.EndsWith(msbuildExtn, StringComparison.OrdinalIgnoreCase))
- {
- s_exeName += msbuildExtn;
- }
-
- // discard the first piece, because that's the path to the executable -- the rest are args
- commandLineArgs.RemoveAt(0);
-
-#if FEATURE_GET_COMMANDLINE
- fullCommandLine = $"'{commandLine}'";
-#else
- fullCommandLine = $"'{string.Join(' ', commandLine)}'";
-#endif
-
- // 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])
- {
- GatherAutoResponseFileSwitches(s_exePath, switchesFromAutoResponseFile, fullCommandLine);
- }
- }
-
- ///
- /// 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.
- ///
- ///
- /// Internal for unit testing only.
- ///
- internal static void GatherCommandLineSwitches(List 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);
- }
- }
- }
- }
- }
-
- ///
- /// Extracts a switch's parameters after processing all quoting around the switch.
- ///
- ///
- /// This method is marked "internal" for unit-testing purposes only -- ideally it should be "private".
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- /// The given switch's parameters (with interesting quoting preserved).
- 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;
- }
-
- ///
- /// Used to keep track of response files to prevent them from
- /// being included multiple times (or even recursively).
- ///
- private static List s_includedResponseFiles;
-
- ///
- /// 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.
- ///
- ///
- ///
- private static 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 s_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));
- s_includedResponseFiles.Add(responseFile);
-
- List 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();
-
- 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);
- }
- }
-
- ///
- /// Called when a switch that doesn't take parameters is detected on the command line.
- ///
- ///
- ///
- ///
- ///
- ///
- 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);
- }
- }
-
- ///
- /// Called when a switch that takes parameters is detected on the command line. This method flags errors and stores the
- /// switch parameters.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- 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);
- }
- }
-
- ///
- /// Checks whether envVar is an environment variable. MSBuild uses
- /// Environment.ExpandEnvironmentVariables(string), which only
- /// considers %-delimited variables.
- ///
- /// A possible environment variable
- /// Whether envVar is an environment variable
- private static bool IsEnvironmentVariable(string envVar)
- {
- return envVar.StartsWith("%") && envVar.EndsWith("%") && envVar.Length > 1;
- }
-
- ///
- /// The name of the auto-response file.
- ///
- private const string autoResponseFileName = "MSBuild.rsp";
-
- ///
- /// The name of an auto-response file to search for in the project directory and above.
- ///
- private const string directoryResponseFileName = "Directory.Build.rsp";
-
- ///
- /// String replacement pattern to support paths in response files.
- ///
- private const string responseFilePathReplacement = "%MSBuildThisFileDirectory%";
-
- ///
- /// Whether switches from the auto-response file are being used.
- ///
- internal static bool usingSwitchesFromAutoResponseFile = false;
-
///
/// Indicates that this process is working as a server.
///
private static bool s_isServerNode;
- ///
- /// 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.
- ///
- private static bool GatherAutoResponseFileSwitches(string path, CommandLineSwitches switchesFromAutoResponseFile, string commandLine)
- {
- string autoResponseFile = Path.Combine(path, autoResponseFileName);
- return GatherAutoResponseFileSwitchesFromFullPath(autoResponseFile, switchesFromAutoResponseFile, commandLine);
- }
-
- private static 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);
- }
-
- if (switchesFromAutoResponseFile.HaveAnySwitchesBeenSet())
- {
- // we picked up some switches from the auto-response file
- usingSwitchesFromAutoResponseFile = true;
- }
-
- // Throw errors found in the response file
- switchesFromAutoResponseFile.ThrowErrors();
- }
-
- return found;
- }
-
///
/// Coordinates the processing of all detected switches. It gathers information necessary to invoke the build engine, and
/// performs deeper error checking on the switches and their parameters.
@@ -2575,8 +2003,8 @@ private static bool ProcessCommandLineSwitches(
bool useTerminalLogger = ProcessTerminalLoggerConfiguration(commandLineSwitches, out string aggregatedTerminalLoggerParameters);
// This is temporary until we can remove the need for the environment variable.
- // DO NOT use this environment variable for any new features as it will be removed without further notice.
- Environment.SetEnvironmentVariable("_MSBUILDTLENABLED", useTerminalLogger ? "1" : "0");
+ // DO NOT use this environment variable for any new features as it will be removed without further notice.
+ Environment.SetEnvironmentVariable("_MSBUILDTLENABLED", useTerminalLogger ? "1" : "0");
DisplayVersionMessageIfNeeded(recursing, useTerminalLogger, commandLineSwitches);
@@ -2637,7 +2065,7 @@ private static bool ProcessCommandLineSwitches(
}
else
{
- bool foundProjectAutoResponseFile = CheckAndGatherProjectAutoResponseFile(switchesFromAutoResponseFile, commandLineSwitches, recursing, commandLine);
+ bool foundProjectAutoResponseFile = commandLineParser.CheckAndGatherProjectAutoResponseFile(switchesFromAutoResponseFile, commandLineSwitches, recursing, commandLine);
if (foundProjectAutoResponseFile)
{
@@ -3096,59 +2524,6 @@ private static CommandLineSwitches CombineSwitchesRespectingPriority(CommandLine
return commandLineSwitches;
}
- 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;
- }
-
-
- ///
- /// Identifies if there is rsp files near the project file
- ///
- /// true if there autoresponse file was found
- private static 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)
- if (!string.Equals(projectDirectory, s_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 bool WarningsAsErrorsSwitchIsEmpty(CommandLineSwitches commandLineSwitches)
{
string val = commandLineSwitches.GetParameterizedSwitchCommandLineArg(CommandLineSwitches.ParameterizedSwitch.WarningsAsErrors);
@@ -3751,46 +3126,6 @@ private static void ValidateExtensions(string[] projectExtensionsToIgnore)
}
}
- ///
- /// Checks whether an argument given as a parameter starts with valid indicator,
- ///
which means, whether switch begins with one of: "/", "-", "--"
- ///
- /// Command line argument with beginning indicator (e.g. --help).
- ///
This argument has to be unquoted, otherwise the first character will always be a quote character "
- /// true if argument's beginning matches one of possible indicators
- ///
false if argument's beginning doesn't match any of correct indicator
- ///
- private static bool ValidateSwitchIndicatorInUnquotedArgument(string unquotedCommandLineArgument)
- {
- return unquotedCommandLineArgument.StartsWith("-", StringComparison.Ordinal) // superset of "--"
- || unquotedCommandLineArgument.StartsWith("/", StringComparison.Ordinal);
- }
-
- ///
- /// Gets the length of the switch indicator (- or / or --)
- ///
The length returned from this method is deduced from the beginning sequence of unquoted argument.
- ///
This way it will "assume" that there's no further error (e.g. // or ---) which would also be considered as a correct indicator.
- ///
- /// Unquoted argument with leading indicator and name
- /// Correct length of used indicator
- ///
0 if no leading sequence recognized as correct indicator
- /// 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;
- }
- }
-
///
/// Figures out which targets are to be built.
///
diff --git a/src/Shared/BuildEnvironmentHelper.cs b/src/Shared/BuildEnvironmentHelper.cs
index 3a0c945eb7d..6c9b8a0979a 100644
--- a/src/Shared/BuildEnvironmentHelper.cs
+++ b/src/Shared/BuildEnvironmentHelper.cs
@@ -38,6 +38,13 @@ internal sealed class BuildEnvironmentHelper
///
private static readonly string[] s_msBuildExeNames = { "MSBuild.exe", "MSBuild.dll" };
+ public static bool IsRunningOnCoreClr { get; } =
+#if NET
+ true;
+#else
+ false;
+#endif
+
///
/// Gets the cached Build Environment instance.
///