diff --git a/src/Microsoft.AspNetCore.Razor.Tasks/DotnetToolTask.cs b/src/Microsoft.AspNetCore.Razor.Tasks/DotnetToolTask.cs index c1df3cd431..5b28b3eca9 100644 --- a/src/Microsoft.AspNetCore.Razor.Tasks/DotnetToolTask.cs +++ b/src/Microsoft.AspNetCore.Razor.Tasks/DotnetToolTask.cs @@ -168,11 +168,10 @@ namespace Microsoft.AspNetCore.Razor.Tasks return workingDirectory; } - private List GetArguments(string responseFileCommands) + private IList GetArguments(string responseFileCommands) { - var responseFileArguments = - CommandLineUtilities.SplitCommandLineIntoArguments(responseFileCommands, removeHashComments: true); - return responseFileArguments.ToList(); + var list = responseFileCommands.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + return list; } protected override bool HandleTaskExecutionErrors() diff --git a/src/Microsoft.AspNetCore.Razor.Tasks/Microsoft.AspNetCore.Razor.Tasks.csproj b/src/Microsoft.AspNetCore.Razor.Tasks/Microsoft.AspNetCore.Razor.Tasks.csproj index ebe30d5d84..b868310b42 100644 --- a/src/Microsoft.AspNetCore.Razor.Tasks/Microsoft.AspNetCore.Razor.Tasks.csproj +++ b/src/Microsoft.AspNetCore.Razor.Tasks/Microsoft.AspNetCore.Razor.Tasks.csproj @@ -22,9 +22,6 @@ Shared\PlatformInformation.cs - - Shared\CommandLineUtilities.cs - Shared\ServerProtocol\%(FileName) diff --git a/src/Microsoft.AspNetCore.Razor.Tools/Roslyn/CommandLineUtilities.cs b/src/Microsoft.AspNetCore.Razor.Tools/Roslyn/CommandLineUtilities.cs deleted file mode 100644 index 441322aaab..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Tools/Roslyn/CommandLineUtilities.cs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Text; - -namespace Roslyn.Utilities -{ - /* - * Copied from https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/InternalUtilities/CommandLineUtilities.cs - */ - internal static class CommandLineUtilities - { - /// - /// Split a command line by the same rules as Main would get the commands except the original - /// state of backslashes and quotes are preserved. For example in normal Windows command line - /// parsing the following command lines would produce equivalent Main arguments: - /// - /// - /r:a,b - /// - /r:"a,b" - /// - /// This method will differ as the latter will have the quotes preserved. The only case where - /// quotes are removed is when the entire argument is surrounded by quotes without any inner - /// quotes. - /// - /// - /// Rules for command line parsing, according to MSDN: - /// - /// Arguments are delimited by white space, which is either a space or a tab. - /// - /// A string surrounded by double quotation marks ("string") is interpreted - /// as a single argument, regardless of white space contained within. - /// A quoted string can be embedded in an argument. - /// - /// A double quotation mark preceded by a backslash (\") is interpreted as a - /// literal double quotation mark character ("). - /// - /// Backslashes are interpreted literally, unless they immediately precede a - /// double quotation mark. - /// - /// If an even number of backslashes is followed by a double quotation mark, - /// one backslash is placed in the argv array for every pair of backslashes, - /// and the double quotation mark is interpreted as a string delimiter. - /// - /// If an odd number of backslashes is followed by a double quotation mark, - /// one backslash is placed in the argv array for every pair of backslashes, - /// and the double quotation mark is "escaped" by the remaining backslash, - /// causing a literal double quotation mark (") to be placed in argv. - /// - public static IEnumerable SplitCommandLineIntoArguments(string commandLine, bool removeHashComments) - { - return SplitCommandLineIntoArguments(commandLine, removeHashComments, out var unused); - } - - public static IEnumerable SplitCommandLineIntoArguments(string commandLine, bool removeHashComments, out char? illegalChar) - { - var builder = new StringBuilder(commandLine.Length); - var list = new List(); - var i = 0; - - illegalChar = null; - while (i < commandLine.Length) - { - while (i < commandLine.Length && char.IsWhiteSpace(commandLine[i])) - { - i++; - } - - if (i == commandLine.Length) - { - break; - } - - if (commandLine[i] == '#' && removeHashComments) - { - break; - } - - var quoteCount = 0; - builder.Length = 0; - while (i < commandLine.Length && (!char.IsWhiteSpace(commandLine[i]) || (quoteCount % 2 != 0))) - { - var current = commandLine[i]; - switch (current) - { - case '\\': - { - var slashCount = 0; - do - { - builder.Append(commandLine[i]); - i++; - slashCount++; - } while (i < commandLine.Length && commandLine[i] == '\\'); - - // Slashes not followed by a quote character can be ignored for now - if (i >= commandLine.Length || commandLine[i] != '"') - { - break; - } - - // If there is an odd number of slashes then it is escaping the quote - // otherwise it is just a quote. - if (slashCount % 2 == 0) - { - quoteCount++; - } - - builder.Append('"'); - i++; - break; - } - - case '"': - builder.Append(current); - quoteCount++; - i++; - break; - - default: - if ((current >= 0x1 && current <= 0x1f) || current == '|') - { - if (illegalChar == null) - { - illegalChar = current; - } - } - else - { - builder.Append(current); - } - - i++; - break; - } - } - - // If the quote string is surrounded by quotes with no interior quotes then - // remove the quotes here. - if (quoteCount == 2 && builder[0] == '"' && builder[builder.Length - 1] == '"') - { - builder.Remove(0, length: 1); - builder.Remove(builder.Length - 1, length: 1); - } - - if (builder.Length > 0) - { - list.Add(builder.ToString()); - } - } - - return list; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Tools/ServerProtocol/ServerConnection.cs b/src/Microsoft.AspNetCore.Razor.Tools/ServerProtocol/ServerConnection.cs index 2518485f6c..b3c7864e7a 100644 --- a/src/Microsoft.AspNetCore.Razor.Tools/ServerProtocol/ServerConnection.cs +++ b/src/Microsoft.AspNetCore.Razor.Tools/ServerProtocol/ServerConnection.cs @@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Razor.Tools public static Task RunOnServer( string pipeName, - List arguments, + IList arguments, ServerPaths buildPaths, CancellationToken cancellationToken, string keepAlive = null, @@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Razor.Tools } private static async Task RunOnServerCore( - List arguments, + IList arguments, ServerPaths buildPaths, string pipeName, string keepAlive, diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildServerIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildServerIntegrationTest.cs index 0d32a1de4f..94607ec259 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildServerIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildServerIntegrationTest.cs @@ -33,5 +33,20 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests Assert.FileExists(result, OutputPath, "SimpleMvc.PrecompiledViews.dll"); Assert.FileExists(result, OutputPath, "SimpleMvc.PrecompiledViews.pdb"); } + + [Fact] + [InitializeTestProject("SimpleMvc", baseDirectory: "Whitespace in path", additionalProjects: new string[] { })] + public async Task Build_AppWithWhitespaceInPath_CanBuildSuccessfully() + { + var result = await DotnetMSBuild( + "Build", + $"/p:RazorCompileOnBuild=true /p:UseRazorBuildServer=true /p:_RazorBuildServerPipeName={_pipeName}"); + + Assert.BuildPassed(result); + Assert.FileExists(result, OutputPath, "SimpleMvc.dll"); + Assert.FileExists(result, OutputPath, "SimpleMvc.pdb"); + Assert.FileExists(result, OutputPath, "SimpleMvc.PrecompiledViews.dll"); + Assert.FileExists(result, OutputPath, "SimpleMvc.PrecompiledViews.pdb"); + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs index 5660c4aefa..17c6fcf8a9 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/InitializeTestProjectAttribute.cs @@ -10,11 +10,18 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests public class InitializeTestProjectAttribute : BeforeAfterTestAttribute { private readonly string _projectName; + private readonly string _baseDirectory; private readonly string[] _additionalProjects; public InitializeTestProjectAttribute(string projectName, params string[] additionalProjects) + : this (projectName, string.Empty, additionalProjects) + { + } + + public InitializeTestProjectAttribute(string projectName, string baseDirectory, string[] additionalProjects) { _projectName = projectName; + _baseDirectory = baseDirectory; _additionalProjects = additionalProjects; } @@ -25,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests throw new InvalidOperationException($"This should be used on a class derived from {typeof(MSBuildIntegrationTestBase)}"); } - MSBuildIntegrationTestBase.Project = ProjectDirectory.Create(_projectName, _additionalProjects); + MSBuildIntegrationTestBase.Project = ProjectDirectory.Create(_projectName, _baseDirectory, _additionalProjects); } public override void After(MethodInfo methodUnderTest) diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/ProjectDirectory.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/ProjectDirectory.cs index 75f12cf3e6..aa9011fb2b 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/ProjectDirectory.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/ProjectDirectory.cs @@ -18,9 +18,9 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests public bool PreserveWorkingDirectory { get; set; } #endif - public static ProjectDirectory Create(string projectName, string[] additionalProjects) + public static ProjectDirectory Create(string projectName, string baseDirectory, string[] additionalProjects) { - var destinationPath = Path.Combine(Path.GetTempPath(), "Razor", Path.GetRandomFileName()); + var destinationPath = Path.Combine(Path.GetTempPath(), "Razor", baseDirectory, Path.GetRandomFileName()); Directory.CreateDirectory(destinationPath); try