Build server working end to end
- Added a UseServer switch - Updated tasks to use the build server in enabled
This commit is contained in:
parent
5c6ff4366f
commit
4500de5862
|
|
@ -1,7 +1,7 @@
|
||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<!-- Using explicit SDK imports here because the default way conflicts with the AfterBuild target -->
|
<!-- Using explicit SDK imports here because the default way conflicts with the AfterBuild target -->
|
||||||
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"/>
|
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains MSBuild support for Razor.</Description>
|
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains MSBuild support for Razor.</Description>
|
||||||
|
|
@ -28,21 +28,22 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
||||||
<!-- These are just normal MSBuild files that we want to include in the package -->
|
<!-- These are just normal MSBuild files that we want to include in the package -->
|
||||||
<Content Include="build\**\*.props" PackagePath="build\"/>
|
<Content Include="build\**\*.props" PackagePath="build\" />
|
||||||
<Content Include="build\**\*.targets" PackagePath="build\"/>
|
<Content Include="build\**\*.targets" PackagePath="build\" />
|
||||||
<Content Include="buildMultiTargeting\*.props" PackagePath="buildMultiTargeting\"/>
|
<Content Include="buildMultiTargeting\*.props" PackagePath="buildMultiTargeting\" />
|
||||||
<Content Include="buildMultiTargeting\*.targets" PackagePath="buildMultiTargeting\"/>
|
<Content Include="buildMultiTargeting\*.targets" PackagePath="buildMultiTargeting\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- This is the tasks project that needs to be included in the package. -->
|
<!-- This is the tasks project that needs to be included in the package. -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<TaskProject Include="..\Microsoft.AspNetCore.Razor.Tasks\Microsoft.AspNetCore.Razor.Tasks.csproj"/>
|
<TaskProject Include="..\Microsoft.AspNetCore.Razor.Tasks\Microsoft.AspNetCore.Razor.Tasks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- These are tools that need to be included in the package. -->
|
<!-- These are tools that need to be included in the package. -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ToolProject Include="..\Microsoft.AspNetCore.Razor.GenerateTool\Microsoft.AspNetCore.Razor.GenerateTool.csproj"/>
|
<ToolProject Include="..\Microsoft.AspNetCore.Razor.GenerateTool\Microsoft.AspNetCore.Razor.GenerateTool.csproj" />
|
||||||
<ToolProject Include="..\Microsoft.AspNetCore.Razor.TagHelperTool\Microsoft.AspNetCore.Razor.TagHelperTool.csproj"/>
|
<ToolProject Include="..\Microsoft.AspNetCore.Razor.TagHelperTool\Microsoft.AspNetCore.Razor.TagHelperTool.csproj" />
|
||||||
|
<ToolProject Include="..\Microsoft.AspNetCore.Razor.Tools\Microsoft.AspNetCore.Razor.Tools.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Using explicit SDK imports here because the default way conflicts with the AfterBuild target -->
|
<!-- Using explicit SDK imports here because the default way conflicts with the AfterBuild target -->
|
||||||
|
|
@ -63,47 +64,38 @@
|
||||||
|
|
||||||
First, build the project, then copy it to the ouput directory, then add it as packable content.
|
First, build the project, then copy it to the ouput directory, then add it as packable content.
|
||||||
-->
|
-->
|
||||||
<MSBuild Projects="@(TaskProject)"/>
|
<MSBuild Projects="@(TaskProject)" />
|
||||||
|
|
||||||
<MSBuild
|
<MSBuild Projects="@(TaskProject)" Properties="TargetFramework=net46" Targets="GetTargetPath">
|
||||||
Projects="@(TaskProject)"
|
<Output TaskParameter="TargetOutputs" ItemName="TaskAssemblyNet46" />
|
||||||
Properties="TargetFramework=net46"
|
|
||||||
Targets="GetTargetPath">
|
|
||||||
<Output TaskParameter="TargetOutputs" ItemName="TaskAssemblyNet46"/>
|
|
||||||
</MSBuild>
|
</MSBuild>
|
||||||
|
|
||||||
<MSBuild
|
<MSBuild Projects="@(TaskProject)" Properties="TargetFramework=netstandard2.0" Targets="GetTargetPath">
|
||||||
Projects="@(TaskProject)"
|
<Output TaskParameter="TargetOutputs" ItemName="TaskAssemblyNetStandard" />
|
||||||
Properties="TargetFramework=netstandard2.0"
|
|
||||||
Targets="GetTargetPath">
|
|
||||||
<Output TaskParameter="TargetOutputs" ItemName="TaskAssemblyNetStandard"/>
|
|
||||||
</MSBuild>
|
</MSBuild>
|
||||||
|
|
||||||
<Copy SourceFiles="@(TaskAssemblyNet46)" DestinationFolder="$(OutputPath)\tasks\net46\">
|
<Copy SourceFiles="@(TaskAssemblyNet46)" DestinationFolder="$(OutputPath)\tasks\net46\">
|
||||||
<Output TaskParameter="CopiedFiles" ItemName="FileWrites"/>
|
<Output TaskParameter="CopiedFiles" ItemName="FileWrites" />
|
||||||
</Copy>
|
</Copy>
|
||||||
<Copy SourceFiles="@(TaskAssemblyNetStandard)" DestinationFolder="$(OutputPath)\tasks\netstandard2.0\">
|
<Copy SourceFiles="@(TaskAssemblyNetStandard)" DestinationFolder="$(OutputPath)\tasks\netstandard2.0\">
|
||||||
<Output TaskParameter="CopiedFiles" ItemName="FileWrites"/>
|
<Output TaskParameter="CopiedFiles" ItemName="FileWrites" />
|
||||||
</Copy>
|
</Copy>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="@(TaskAssemblyNet46)" PackagePath="tasks\net46\" Pack="true"/>
|
<None Include="@(TaskAssemblyNet46)" PackagePath="tasks\net46\" Pack="true" />
|
||||||
<None Include="@(TaskAssemblyNetStandard)" PackagePath="tasks\netstandard2.0\" Pack="true"/>
|
<None Include="@(TaskAssemblyNetStandard)" PackagePath="tasks\netstandard2.0\" Pack="true" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Error Text="TaskAssemblyNet46 is empty. This is a bug" Condition="'@(TaskAssemblyNet46)'==''"/>
|
<Error Text="TaskAssemblyNet46 is empty. This is a bug" Condition="'@(TaskAssemblyNet46)'==''" />
|
||||||
<Error Text="TaskAssemblyNetStandard is empty. This is a bug" Condition="'@(TaskAssemblyNetStandard)'==''"/>
|
<Error Text="TaskAssemblyNetStandard is empty. This is a bug" Condition="'@(TaskAssemblyNetStandard)'==''" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Next we need to build the netcoreapp2.0 tools. In this case we need to do a publish, because we need
|
Next we need to build the netcoreapp2.0 tools. In this case we need to do a publish, because we need
|
||||||
all of the output to put in the package.
|
all of the output to put in the package.
|
||||||
-->
|
-->
|
||||||
<RemoveDir Directories="tools\"/>
|
<RemoveDir Directories="tools\" />
|
||||||
<MSBuild Projects="@(ToolProject)"/>
|
<MSBuild Projects="@(ToolProject)" />
|
||||||
<MSBuild
|
<MSBuild Projects="@(ToolProject)" Properties="PublishDir=$(MSBuildProjectDirectory)\$(OutputPath)tools\" Targets="Publish" />
|
||||||
Projects="@(ToolProject)"
|
|
||||||
Properties="PublishDir=$(MSBuildProjectDirectory)\$(OutputPath)tools\"
|
|
||||||
Targets="Publish"/>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<_RazorTool Include="$(OutputPath)tools\**\*" Exclude="$(OutputPath)tools\**\*.xml;$(OutputPath)tools\**\*.pdb" />
|
<_RazorTool Include="$(OutputPath)tools\**\*" Exclude="$(OutputPath)tools\**\*.xml;$(OutputPath)tools\**\*.pdb" />
|
||||||
|
|
@ -116,6 +108,6 @@
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Error Text="_RazorTool is empty. This is a bug" Condition="'@(_RazorTool)'==''"/>
|
<Error Text="_RazorTool is empty. This is a bug" Condition="'@(_RazorTool)'==''" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
<!-- Used to locate our tools -->
|
<!-- Used to locate our tools -->
|
||||||
<_RazorGenerateToolAssembly>$(_RazorMSBuildRoot)tools\Microsoft.AspNetCore.Razor.GenerateTool.dll</_RazorGenerateToolAssembly>
|
<_RazorGenerateToolAssembly>$(_RazorMSBuildRoot)tools\Microsoft.AspNetCore.Razor.GenerateTool.dll</_RazorGenerateToolAssembly>
|
||||||
<_RazorTagHelperToolAssembly>$(_RazorMSBuildRoot)tools\Microsoft.AspNetCore.Razor.TagHelperTool.dll</_RazorTagHelperToolAssembly>
|
<_RazorTagHelperToolAssembly>$(_RazorMSBuildRoot)tools\Microsoft.AspNetCore.Razor.TagHelperTool.dll</_RazorTagHelperToolAssembly>
|
||||||
|
<_RazorBuildServerAssembly>$(_RazorMSBuildRoot)tools\rzc.dll</_RazorBuildServerAssembly>
|
||||||
|
|
||||||
<!-- Used to hash file inputs for RazorGenerate -->
|
<!-- Used to hash file inputs for RazorGenerate -->
|
||||||
<_RazorGenerateInputsHash></_RazorGenerateInputsHash>
|
<_RazorGenerateInputsHash></_RazorGenerateInputsHash>
|
||||||
|
|
@ -74,8 +75,10 @@
|
||||||
Debug="$(_RazorDebugTagHelperTask)"
|
Debug="$(_RazorDebugTagHelperTask)"
|
||||||
DebugTool="$(_RazorDebugTagHelperTool)"
|
DebugTool="$(_RazorDebugTagHelperTool)"
|
||||||
ToolAssembly="$(_RazorTagHelperToolAssembly)"
|
ToolAssembly="$(_RazorTagHelperToolAssembly)"
|
||||||
|
UseServer="$(UseRazorBuildServer)"
|
||||||
ServerAssembly="$(_RazorBuildServerAssembly)"
|
ServerAssembly="$(_RazorBuildServerAssembly)"
|
||||||
Assemblies="@(RazorReferencePath)"
|
Assemblies="@(RazorReferencePath)"
|
||||||
|
ProjectRoot="$(MSBuildProjectDirectory)"
|
||||||
TagHelperManifest="$(_RazorTagHelperOutputCache)">
|
TagHelperManifest="$(_RazorTagHelperOutputCache)">
|
||||||
<Output
|
<Output
|
||||||
TaskParameter="TagHelperManifest"
|
TaskParameter="TagHelperManifest"
|
||||||
|
|
@ -107,6 +110,8 @@
|
||||||
Debug="$(_RazorDebugGenerateCodeTask)"
|
Debug="$(_RazorDebugGenerateCodeTask)"
|
||||||
DebugTool="$(_RazorDebugGenerateCodeTool)"
|
DebugTool="$(_RazorDebugGenerateCodeTool)"
|
||||||
ToolAssembly="$(_RazorGenerateToolAssembly)"
|
ToolAssembly="$(_RazorGenerateToolAssembly)"
|
||||||
|
UseServer="$(UseRazorBuildServer)"
|
||||||
|
ServerAssembly="$(_RazorBuildServerAssembly)"
|
||||||
Sources="@(RazorGenerate)"
|
Sources="@(RazorGenerate)"
|
||||||
ProjectRoot="$(MSBuildProjectDirectory)"
|
ProjectRoot="$(MSBuildProjectDirectory)"
|
||||||
TagHelperManifest="$(_RazorTagHelperOutputCache)"
|
TagHelperManifest="$(_RazorTagHelperOutputCache)"
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
so that it only shows up in supported versions.
|
so that it only shows up in supported versions.
|
||||||
-->
|
-->
|
||||||
<IsRazorCompilerReferenced>true</IsRazorCompilerReferenced>
|
<IsRazorCompilerReferenced>true</IsRazorCompilerReferenced>
|
||||||
|
|
||||||
<!-- Override this to hijack the tasks and targets. Used by tests. -->
|
<!-- Override this to hijack the tasks and targets. Used by tests. -->
|
||||||
<_RazorMSBuildRoot Condition="'$(_RazorMSBuildRoot)'==''">$(MSBuildThisFileDirectory)..\..\</_RazorMSBuildRoot>
|
<_RazorMSBuildRoot Condition="'$(_RazorMSBuildRoot)'==''">$(MSBuildThisFileDirectory)..\..\</_RazorMSBuildRoot>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,10 @@
|
||||||
|
|
||||||
<!-- Default to on if MvcRazorCompileOnPublish isn't set for some reason -->
|
<!-- Default to on if MvcRazorCompileOnPublish isn't set for some reason -->
|
||||||
<RazorCompileOnPublish Condition="'$(RazorCompileOnPublish)'==''">true</RazorCompileOnPublish>
|
<RazorCompileOnPublish Condition="'$(RazorCompileOnPublish)'==''">true</RazorCompileOnPublish>
|
||||||
|
|
||||||
|
<UseRazorBuildServer Condition="'$(UseRazorBuildServer)'==''">false</UseRazorBuildServer>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Properties that configure Razor SDK, but need to be defined in targets due to evaluation order.
|
Properties that configure Razor SDK, but need to be defined in targets due to evaluation order.
|
||||||
-->
|
-->
|
||||||
|
|
@ -121,14 +122,14 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
These are the targets that generate code using Razor, separated for the main file for ease of maintenance.
|
These are the targets that generate code using Razor, separated from the main file for ease of maintenance.
|
||||||
|
|
||||||
Most targets related to Razor code generation are defined there.
|
Most targets related to Razor code generation are defined there.
|
||||||
-->
|
-->
|
||||||
<Import Project="Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets" />
|
<Import Project="Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
These are the targets that actually do compilation using CSC, separated for the main file for ease of maintenance.
|
These are the targets that actually do compilation using CSC, separated from the main file for ease of maintenance.
|
||||||
|
|
||||||
RazorCoreCompile should be defined there.
|
RazorCoreCompile should be defined there.
|
||||||
-->
|
-->
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@ namespace Microsoft.AspNetCore.Razor.TagHelperTool
|
||||||
|
|
||||||
HelpOption("-?|-h|--help");
|
HelpOption("-?|-h|--help");
|
||||||
|
|
||||||
TagHelperManifest = Option("-o", "output file", CommandOptionType.SingleValue);
|
|
||||||
Assemblies = Argument("assemblies", "assemblies to search for tag helpers", multipleValues: true);
|
Assemblies = Argument("assemblies", "assemblies to search for tag helpers", multipleValues: true);
|
||||||
|
TagHelperManifest = Option("-o", "output file", CommandOptionType.SingleValue);
|
||||||
|
ProjectRoot = Option("-p", "project root directory", CommandOptionType.SingleValue);
|
||||||
|
|
||||||
new RunCommand().Configure(this);
|
new RunCommand().Configure(this);
|
||||||
}
|
}
|
||||||
|
|
@ -30,6 +31,8 @@ namespace Microsoft.AspNetCore.Razor.TagHelperTool
|
||||||
|
|
||||||
public CommandOption TagHelperManifest { get; }
|
public CommandOption TagHelperManifest { get; }
|
||||||
|
|
||||||
|
public CommandOption ProjectRoot { get; }
|
||||||
|
|
||||||
public new int Execute(params string[] args)
|
public new int Execute(params string[] args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,15 @@ namespace Microsoft.AspNetCore.Razor.TagHelperTool
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExecuteCore(
|
return ExecuteCore(
|
||||||
|
projectDirectory: application.ProjectRoot.Value(),
|
||||||
outputFilePath: application.TagHelperManifest.Value(),
|
outputFilePath: application.TagHelperManifest.Value(),
|
||||||
assemblies: application.Assemblies.Values.ToArray());
|
assemblies: application.Assemblies.Values.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ExecuteCore(string outputFilePath, string[] assemblies)
|
private int ExecuteCore(string projectDirectory, string outputFilePath, string[] assemblies)
|
||||||
{
|
{
|
||||||
|
outputFilePath = Path.Combine(projectDirectory, outputFilePath);
|
||||||
|
|
||||||
var metadataReferences = new MetadataReference[assemblies.Length];
|
var metadataReferences = new MetadataReference[assemblies.Length];
|
||||||
for (var i = 0; i < assemblies.Length; i++)
|
for (var i = 0; i < assemblies.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -147,6 +150,11 @@ namespace Microsoft.AspNetCore.Razor.TagHelperTool
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(application.ProjectRoot.Value()))
|
||||||
|
{
|
||||||
|
application.ProjectRoot.Values.Add(Environment.CurrentDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,24 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Razor.Tools;
|
||||||
using Microsoft.Build.Framework;
|
using Microsoft.Build.Framework;
|
||||||
using Microsoft.Build.Utilities;
|
using Microsoft.Build.Utilities;
|
||||||
|
using Microsoft.CodeAnalysis.CommandLine;
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
|
using Roslyn.Utilities;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Tasks
|
namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
{
|
{
|
||||||
public abstract class DotNetToolTask : ToolTask
|
public abstract class DotNetToolTask : ToolTask
|
||||||
{
|
{
|
||||||
|
private CancellationTokenSource _razorServerCts;
|
||||||
|
|
||||||
public bool Debug { get; set; }
|
public bool Debug { get; set; }
|
||||||
|
|
||||||
public bool DebugTool { get; set; }
|
public bool DebugTool { get; set; }
|
||||||
|
|
@ -19,6 +27,11 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
[Required]
|
[Required]
|
||||||
public string ToolAssembly { get; set; }
|
public string ToolAssembly { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string ServerAssembly { get; set; }
|
||||||
|
|
||||||
|
public bool UseServer { get; set; }
|
||||||
|
|
||||||
protected override string ToolName => "dotnet";
|
protected override string ToolName => "dotnet";
|
||||||
|
|
||||||
// If we're debugging then make all of the stdout gets logged in MSBuild
|
// If we're debugging then make all of the stdout gets logged in MSBuild
|
||||||
|
|
@ -26,6 +39,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
|
|
||||||
protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High;
|
protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High;
|
||||||
|
|
||||||
|
internal abstract RequestCommand Command { get; }
|
||||||
|
|
||||||
protected override string GenerateFullPathToTool()
|
protected override string GenerateFullPathToTool()
|
||||||
{
|
{
|
||||||
#if NETSTANDARD2_0
|
#if NETSTANDARD2_0
|
||||||
|
|
@ -62,12 +77,19 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryExecuteOnServer(out var result))
|
return base.Execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
|
||||||
|
{
|
||||||
|
if (UseServer &&
|
||||||
|
!string.IsNullOrEmpty(ServerAssembly) &&
|
||||||
|
TryExecuteOnServer(pathToTool, responseFileCommands, commandLineCommands, out var result))
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.Execute();
|
return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LogToolCommand(string message)
|
protected override void LogToolCommand(string message)
|
||||||
|
|
@ -82,10 +104,80 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual bool TryExecuteOnServer(out bool result)
|
public override void Cancel()
|
||||||
{
|
{
|
||||||
result = false;
|
base.Cancel();
|
||||||
|
|
||||||
|
_razorServerCts?.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool TryExecuteOnServer(string pathToTool, string responseFileCommands, string commandLineCommands, out int result)
|
||||||
|
{
|
||||||
|
CompilerServerLogger.Log("Server execution started.");
|
||||||
|
using (_razorServerCts = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
CompilerServerLogger.Log($"CommandLine = '{commandLineCommands}'");
|
||||||
|
CompilerServerLogger.Log($"BuildResponseFile = '{responseFileCommands}'");
|
||||||
|
|
||||||
|
// The server contains the tools for discovering tag helpers and generating Razor code.
|
||||||
|
var clientDir = Path.GetDirectoryName(ServerAssembly);
|
||||||
|
var workingDir = CurrentDirectoryToUse();
|
||||||
|
var tempDir = BuildServerConnection.GetTempPath(workingDir);
|
||||||
|
|
||||||
|
var buildPaths = new BuildPathsAlt(
|
||||||
|
clientDir,
|
||||||
|
// MSBuild doesn't need the .NET SDK directory
|
||||||
|
sdkDir: null,
|
||||||
|
workingDir: workingDir,
|
||||||
|
tempDir: tempDir);
|
||||||
|
|
||||||
|
var responseTask = BuildServerConnection.RunServerCompilation(
|
||||||
|
Command,
|
||||||
|
GetArguments(responseFileCommands),
|
||||||
|
buildPaths,
|
||||||
|
keepAlive: null,
|
||||||
|
cancellationToken: _razorServerCts.Token);
|
||||||
|
|
||||||
|
responseTask.Wait(_razorServerCts.Token);
|
||||||
|
|
||||||
|
var response = responseTask.Result;
|
||||||
|
if (response.Type == BuildResponse.ResponseType.Completed &&
|
||||||
|
response is CompletedBuildResponse completedResponse)
|
||||||
|
{
|
||||||
|
result = completedResponse.ReturnCode;
|
||||||
|
|
||||||
|
CompilerServerLogger.Log($"Server execution completed with return code {result}.");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompilerServerLogger.Log("Server execution failed.");
|
||||||
|
result = -1;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current directory that the compiler should run in.
|
||||||
|
/// </summary>
|
||||||
|
private string CurrentDirectoryToUse()
|
||||||
|
{
|
||||||
|
// ToolTask has a method for this. But it may return null. Use the process directory
|
||||||
|
// if ToolTask didn't override. MSBuild uses the process directory.
|
||||||
|
string workingDirectory = GetWorkingDirectory();
|
||||||
|
if (string.IsNullOrEmpty(workingDirectory))
|
||||||
|
{
|
||||||
|
workingDirectory = Directory.GetCurrentDirectory();
|
||||||
|
}
|
||||||
|
return workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> GetArguments(string responseFileCommands)
|
||||||
|
{
|
||||||
|
var responseFileArguments =
|
||||||
|
CommandLineUtilities.SplitCommandLineIntoArguments(responseFileCommands, removeHashComments: true);
|
||||||
|
return responseFileArguments.ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,30 @@
|
||||||
<PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkPackageVersion)" />
|
<PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
|
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" />
|
||||||
|
|
||||||
|
<Compile Include="..\Microsoft.AspNetCore.Razor.Tools\Roslyn\BuildServerConnection.cs">
|
||||||
|
<Link>Shared\BuildServerConnection.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\Microsoft.AspNetCore.Razor.Tools\Roslyn\NativeMethods.cs">
|
||||||
|
<Link>Shared\NativeMethods.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\Microsoft.AspNetCore.Razor.Tools\Roslyn\CompilerServerLogger.cs">
|
||||||
|
<Link>Shared\CompilerServerLogger.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\Microsoft.AspNetCore.Razor.Tools\Roslyn\PlatformInformation.cs">
|
||||||
|
<Link>Shared\PlatformInformation.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\Microsoft.AspNetCore.Razor.Tools\Roslyn\BuildProtocol.cs">
|
||||||
|
<Link>Shared\BuildProtocol.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\Microsoft.AspNetCore.Razor.Tools\Roslyn\CommandLineUtilities.cs">
|
||||||
|
<Link>Shared\CommandLineUtilities.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\Microsoft.AspNetCore.Razor.Tools\PipeName.cs">
|
||||||
|
<Link>Shared\PipeName.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\Microsoft.AspNetCore.Razor.Tools\MutexName.cs">
|
||||||
|
<Link>Shared\MutexName.cs</Link>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Build.Framework;
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.CodeAnalysis.CommandLine;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Tasks
|
namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
{
|
{
|
||||||
|
|
@ -20,6 +21,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
[Required]
|
[Required]
|
||||||
public string TagHelperManifest { get; set; }
|
public string TagHelperManifest { get; set; }
|
||||||
|
|
||||||
|
internal override RequestCommand Command => RequestCommand.RazorGenerate;
|
||||||
|
|
||||||
protected override string GenerateResponseFileCommands()
|
protected override string GenerateResponseFileCommands()
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Build.Framework;
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.CodeAnalysis.CommandLine;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Tasks
|
namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
{
|
{
|
||||||
|
|
@ -17,7 +18,9 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
[Required]
|
[Required]
|
||||||
public string TagHelperManifest { get; set; }
|
public string TagHelperManifest { get; set; }
|
||||||
|
|
||||||
public string ServerAssembly { get; set; }
|
public string ProjectRoot { get; set; }
|
||||||
|
|
||||||
|
internal override RequestCommand Command => RequestCommand.RazorTagHelper;
|
||||||
|
|
||||||
protected override bool SkipTaskExecution()
|
protected override bool SkipTaskExecution()
|
||||||
{
|
{
|
||||||
|
|
@ -43,6 +46,9 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
builder.AppendLine("-o");
|
builder.AppendLine("-o");
|
||||||
builder.AppendLine(TagHelperManifest);
|
builder.AppendLine(TagHelperManifest);
|
||||||
|
|
||||||
|
builder.AppendLine("-p");
|
||||||
|
builder.AppendLine(ProjectRoot);
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
|
|
||||||
Commands.Add(new DiscoverCommand(this));
|
Commands.Add(new DiscoverCommand(this));
|
||||||
Commands.Add(new ServerCommand(this));
|
Commands.Add(new ServerCommand(this));
|
||||||
|
Commands.Add(new ShutdownCommand(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CancellationToken CancellationToken { get; }
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.CodeAnalysis.CommandLine;
|
using Microsoft.CodeAnalysis.CommandLine;
|
||||||
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Tools
|
namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
{
|
{
|
||||||
|
|
@ -25,7 +27,23 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
return new RejectedBuildResponse();
|
return new RejectedBuildResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
var app = new Application(cancellationToken);
|
||||||
|
var commandArgs = parsed.args.ToArray();
|
||||||
|
|
||||||
|
CommandBase command = null;
|
||||||
|
if (request.Command == RequestCommand.RazorGenerate)
|
||||||
|
{
|
||||||
|
command = new GenerateCommand(app);
|
||||||
|
}
|
||||||
|
else if (request.Command == RequestCommand.RazorTagHelper)
|
||||||
|
{
|
||||||
|
command = new DiscoverCommand(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitCode = command?.Execute(commandArgs) ?? 0;
|
||||||
|
var output = command?.Out.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
return new CompletedBuildResponse(exitCode, utf8output: false, output: output);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryParseArguments(BuildRequest request, out (string workingDirectory, string tempDirectory, string[] args) parsed)
|
private bool TryParseArguments(BuildRequest request, out (string workingDirectory, string tempDirectory, string[] args) parsed)
|
||||||
|
|
@ -33,14 +51,10 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
string workingDirectory = null;
|
string workingDirectory = null;
|
||||||
string tempDirectory = null;
|
string tempDirectory = null;
|
||||||
|
|
||||||
// The parsed arguments will contain 'string.Empty' in place of the arguments that we don't want to pass
|
|
||||||
// to the compiler.
|
|
||||||
var args = new List<string>(request.Arguments.Count);
|
var args = new List<string>(request.Arguments.Count);
|
||||||
|
|
||||||
for (var i = 0; i < request.Arguments.Count; i++)
|
for (var i = 0; i < request.Arguments.Count; i++)
|
||||||
{
|
{
|
||||||
args[i] = string.Empty;
|
|
||||||
|
|
||||||
var argument = request.Arguments[i];
|
var argument = request.Arguments[i];
|
||||||
if (argument.ArgumentId == BuildProtocolConstants.ArgumentId.CurrentDirectory)
|
if (argument.ArgumentId == BuildProtocolConstants.ArgumentId.CurrentDirectory)
|
||||||
{
|
{
|
||||||
|
|
@ -52,7 +66,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
}
|
}
|
||||||
else if (argument.ArgumentId == BuildProtocolConstants.ArgumentId.CommandLineArgument)
|
else if (argument.ArgumentId == BuildProtocolConstants.ArgumentId.CommandLineArgument)
|
||||||
{
|
{
|
||||||
args[i] = argument.Value;
|
args.Add(argument.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,17 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
public DiscoverCommand(Application parent)
|
public DiscoverCommand(Application parent)
|
||||||
: base(parent, "discover")
|
: base(parent, "discover")
|
||||||
{
|
{
|
||||||
TagHelperManifest = Option("-o", "output file", CommandOptionType.SingleValue);
|
|
||||||
Assemblies = Argument("assemblies", "assemblies to search for tag helpers", multipleValues: true);
|
Assemblies = Argument("assemblies", "assemblies to search for tag helpers", multipleValues: true);
|
||||||
|
TagHelperManifest = Option("-o", "output file", CommandOptionType.SingleValue);
|
||||||
|
ProjectDirectory = Option("-p", "project root directory", CommandOptionType.SingleValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandArgument Assemblies { get; }
|
public CommandArgument Assemblies { get; }
|
||||||
|
|
||||||
public CommandOption TagHelperManifest { get; }
|
public CommandOption TagHelperManifest { get; }
|
||||||
|
|
||||||
|
public CommandOption ProjectDirectory { get; }
|
||||||
|
|
||||||
protected override bool ValidateArguments()
|
protected override bool ValidateArguments()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(TagHelperManifest.Value()))
|
if (string.IsNullOrEmpty(TagHelperManifest.Value()))
|
||||||
|
|
@ -45,19 +48,27 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ProjectDirectory.Value()))
|
||||||
|
{
|
||||||
|
ProjectDirectory.Values.Add(Environment.CurrentDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<int> ExecuteCoreAsync()
|
protected override Task<int> ExecuteCoreAsync()
|
||||||
{
|
{
|
||||||
var result = ExecuteCore(
|
var result = ExecuteCore(
|
||||||
|
projectDirectory: ProjectDirectory.Value(),
|
||||||
outputFilePath: TagHelperManifest.Value(),
|
outputFilePath: TagHelperManifest.Value(),
|
||||||
assemblies: Assemblies.Values.ToArray());
|
assemblies: Assemblies.Values.ToArray());
|
||||||
return Task.FromResult(result);
|
return Task.FromResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ExecuteCore(string outputFilePath, string[] assemblies)
|
private int ExecuteCore(string projectDirectory, string outputFilePath, string[] assemblies)
|
||||||
{
|
{
|
||||||
|
outputFilePath = Path.Combine(projectDirectory, outputFilePath);
|
||||||
|
|
||||||
var metadataReferences = new MetadataReference[assemblies.Length];
|
var metadataReferences = new MetadataReference[assemblies.Length];
|
||||||
for (var i = 0; i < assemblies.Length; i++)
|
for (var i = 0; i < assemblies.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,204 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
|
using Microsoft.VisualStudio.LanguageServices.Razor;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
|
{
|
||||||
|
internal class GenerateCommand : CommandBase
|
||||||
|
{
|
||||||
|
public GenerateCommand(Application parent)
|
||||||
|
: base(parent, "generate")
|
||||||
|
{
|
||||||
|
Sources = Argument("sources", ".cshtml files to compile", multipleValues: true);
|
||||||
|
ProjectDirectory = Option("-p", "project root directory", CommandOptionType.SingleValue);
|
||||||
|
OutputDirectory = Option("-o", "output directory", CommandOptionType.SingleValue);
|
||||||
|
TagHelperManifest = Option("-t", "tag helper manifest file", CommandOptionType.SingleValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandArgument Sources { get; }
|
||||||
|
|
||||||
|
public CommandOption OutputDirectory { get; }
|
||||||
|
|
||||||
|
public CommandOption ProjectDirectory { get; }
|
||||||
|
|
||||||
|
public CommandOption TagHelperManifest { get; }
|
||||||
|
|
||||||
|
protected override Task<int> ExecuteCoreAsync()
|
||||||
|
{
|
||||||
|
var result = ExecuteCore(
|
||||||
|
projectDirectory: ProjectDirectory.Value() ?? Environment.CurrentDirectory,
|
||||||
|
outputDirectory: OutputDirectory.Value(),
|
||||||
|
tagHelperManifest: TagHelperManifest.Value(),
|
||||||
|
sources: Sources.Values.ToArray());
|
||||||
|
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool ValidateArguments()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(OutputDirectory.Value()))
|
||||||
|
{
|
||||||
|
Error.WriteLine($"{OutputDirectory.ValueName} not specified.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sources.Values.Count == 0)
|
||||||
|
{
|
||||||
|
Error.WriteLine($"{Sources.Name} should have at least one value.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ExecuteCore(string projectDirectory, string outputDirectory, string tagHelperManifest, string[] sources)
|
||||||
|
{
|
||||||
|
tagHelperManifest = Path.Combine(projectDirectory, tagHelperManifest);
|
||||||
|
outputDirectory = Path.Combine(projectDirectory, outputDirectory);
|
||||||
|
|
||||||
|
var tagHelpers = GetTagHelpers(tagHelperManifest);
|
||||||
|
|
||||||
|
var engine = RazorEngine.Create(b =>
|
||||||
|
{
|
||||||
|
RazorExtensions.Register(b);
|
||||||
|
|
||||||
|
b.Features.Add(new StaticTagHelperFeature() { TagHelpers = tagHelpers, });
|
||||||
|
});
|
||||||
|
|
||||||
|
var templateEngine = new MvcRazorTemplateEngine(engine, RazorProject.Create(projectDirectory));
|
||||||
|
|
||||||
|
var sourceItems = GetRazorFiles(projectDirectory, sources);
|
||||||
|
var results = GenerateCode(templateEngine, sourceItems);
|
||||||
|
|
||||||
|
var success = true;
|
||||||
|
|
||||||
|
foreach (var result in results)
|
||||||
|
{
|
||||||
|
if (result.CSharpDocument.Diagnostics.Count > 0)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
foreach (var error in result.CSharpDocument.Diagnostics)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(error.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputFilePath = Path.Combine(outputDirectory, Path.ChangeExtension(result.ViewFileInfo.ViewEnginePath.Substring(1), ".cs"));
|
||||||
|
File.WriteAllText(outputFilePath, result.CSharpDocument.GeneratedCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<TagHelperDescriptor> GetTagHelpers(string tagHelperManifest)
|
||||||
|
{
|
||||||
|
if (!File.Exists(tagHelperManifest))
|
||||||
|
{
|
||||||
|
return Array.Empty<TagHelperDescriptor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var stream = File.OpenRead(tagHelperManifest))
|
||||||
|
{
|
||||||
|
var reader = new JsonTextReader(new StreamReader(stream));
|
||||||
|
|
||||||
|
var serializer = new JsonSerializer();
|
||||||
|
serializer.Converters.Add(new RazorDiagnosticJsonConverter());
|
||||||
|
serializer.Converters.Add(new TagHelperDescriptorJsonConverter());
|
||||||
|
|
||||||
|
return serializer.Deserialize<IReadOnlyList<TagHelperDescriptor>>(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SourceItem> GetRazorFiles(string projectDirectory, string[] sources)
|
||||||
|
{
|
||||||
|
var trimLength = projectDirectory.EndsWith("/") ? projectDirectory.Length - 1 : projectDirectory.Length;
|
||||||
|
|
||||||
|
var items = new List<SourceItem>(sources.Length);
|
||||||
|
for (var i = 0; i < sources.Length; i++)
|
||||||
|
{
|
||||||
|
var fullPath = Path.Combine(projectDirectory, sources[i]);
|
||||||
|
if (fullPath.StartsWith(projectDirectory, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var viewEnginePath = fullPath.Substring(trimLength).Replace('\\', '/');
|
||||||
|
items.Add(new SourceItem(fullPath, viewEnginePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputItem[] GenerateCode(RazorTemplateEngine templateEngine, IReadOnlyList<SourceItem> sources)
|
||||||
|
{
|
||||||
|
var outputs = new OutputItem[sources.Count];
|
||||||
|
Parallel.For(0, outputs.Length, new ParallelOptions() { MaxDegreeOfParallelism = 4 }, i =>
|
||||||
|
{
|
||||||
|
var source = sources[i];
|
||||||
|
|
||||||
|
var csharpDocument = templateEngine.GenerateCode(source.ViewEnginePath);
|
||||||
|
outputs[i] = new OutputItem(source, csharpDocument);
|
||||||
|
});
|
||||||
|
|
||||||
|
return outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct OutputItem
|
||||||
|
{
|
||||||
|
public OutputItem(
|
||||||
|
SourceItem viewFileInfo,
|
||||||
|
RazorCSharpDocument cSharpDocument)
|
||||||
|
{
|
||||||
|
ViewFileInfo = viewFileInfo;
|
||||||
|
CSharpDocument = cSharpDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceItem ViewFileInfo { get; }
|
||||||
|
|
||||||
|
public RazorCSharpDocument CSharpDocument { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SourceItem
|
||||||
|
{
|
||||||
|
public SourceItem(string fullPath, string viewEnginePath)
|
||||||
|
{
|
||||||
|
FullPath = fullPath;
|
||||||
|
ViewEnginePath = viewEnginePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FullPath { get; }
|
||||||
|
|
||||||
|
public string ViewEnginePath { get; }
|
||||||
|
|
||||||
|
public Stream CreateReadStream()
|
||||||
|
{
|
||||||
|
// We are setting buffer size to 1 to prevent FileStream from allocating it's internal buffer
|
||||||
|
// 0 causes constructor to throw
|
||||||
|
var bufferSize = 1;
|
||||||
|
return new FileStream(
|
||||||
|
FullPath,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.ReadWrite,
|
||||||
|
bufferSize,
|
||||||
|
FileOptions.Asynchronous | FileOptions.SequentialScan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StaticTagHelperFeature : ITagHelperFeature
|
||||||
|
{
|
||||||
|
public RazorEngine Engine { get; set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<TagHelperDescriptor> TagHelpers { get; set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<TagHelperDescriptor> GetDescriptors() => TagHelpers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{userName}.{isAdmin}.{baseName}";
|
return $"{userName}.{(isAdmin ? 'T' : 'F')}.{baseName}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ComputeBaseName(string baseDirectory)
|
private static string ComputeBaseName(string baseDirectory)
|
||||||
|
|
@ -59,6 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
{
|
{
|
||||||
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(baseDirectory));
|
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(baseDirectory));
|
||||||
return Convert.ToBase64String(bytes)
|
return Convert.ToBase64String(bytes)
|
||||||
|
.Substring(0, 25) // We only have ~50 total characters on Mac, so strip that down
|
||||||
.Replace("/", "_")
|
.Replace("/", "_")
|
||||||
.Replace("=", string.Empty);
|
.Replace("=", string.Empty);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -446,10 +446,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
{
|
{
|
||||||
CompilerServerLogger.Log("Begin processing request");
|
CompilerServerLogger.Log("Begin processing request");
|
||||||
|
|
||||||
|
var response = _compilerHost.Execute(buildRequest, cancellationToken);
|
||||||
// TODO: this is where we actually process the request.
|
|
||||||
// Take a look at BuildProtocolUtil
|
|
||||||
var response = (BuildResponse)null;
|
|
||||||
|
|
||||||
CompilerServerLogger.Log("End processing request");
|
CompilerServerLogger.Log("End processing request");
|
||||||
return response;
|
return response;
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,13 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
internal class BuildRequest
|
internal class BuildRequest
|
||||||
{
|
{
|
||||||
public readonly uint ProtocolVersion;
|
public readonly uint ProtocolVersion;
|
||||||
public readonly RequestLanguage Language;
|
public readonly RequestCommand Command;
|
||||||
public readonly ReadOnlyCollection<Argument> Arguments;
|
public readonly ReadOnlyCollection<Argument> Arguments;
|
||||||
|
|
||||||
public BuildRequest(uint protocolVersion, RequestLanguage language, IEnumerable<Argument> arguments)
|
public BuildRequest(uint protocolVersion, RequestCommand command, IEnumerable<Argument> arguments)
|
||||||
{
|
{
|
||||||
ProtocolVersion = protocolVersion;
|
ProtocolVersion = protocolVersion;
|
||||||
Language = language;
|
Command = command;
|
||||||
Arguments = new ReadOnlyCollection<Argument>(arguments.ToList());
|
Arguments = new ReadOnlyCollection<Argument>(arguments.ToList());
|
||||||
|
|
||||||
if (Arguments.Count > ushort.MaxValue)
|
if (Arguments.Count > ushort.MaxValue)
|
||||||
|
|
@ -80,7 +80,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BuildRequest Create(
|
public static BuildRequest Create(
|
||||||
RequestLanguage language,
|
RequestCommand command,
|
||||||
string workingDirectory,
|
string workingDirectory,
|
||||||
string tempDirectory,
|
string tempDirectory,
|
||||||
IList<string> args,
|
IList<string> args,
|
||||||
|
|
@ -115,13 +115,13 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
requestArgs.Add(new Argument(ArgumentId.CommandLineArgument, i, arg));
|
requestArgs.Add(new Argument(ArgumentId.CommandLineArgument, i, arg));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BuildRequest(BuildProtocolConstants.ProtocolVersion, language, requestArgs);
|
return new BuildRequest(BuildProtocolConstants.ProtocolVersion, command, requestArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BuildRequest CreateShutdown()
|
public static BuildRequest CreateShutdown()
|
||||||
{
|
{
|
||||||
var requestArgs = new[] { new Argument(ArgumentId.Shutdown, argumentIndex: 0, value: "") };
|
var requestArgs = new[] { new Argument(ArgumentId.Shutdown, argumentIndex: 0, value: "") };
|
||||||
return new BuildRequest(BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, requestArgs);
|
return new BuildRequest(BuildProtocolConstants.ProtocolVersion, RequestCommand.None, requestArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsShutdownRequest()
|
public bool IsShutdownRequest()
|
||||||
|
|
@ -163,7 +163,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
using (var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode))
|
using (var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode))
|
||||||
{
|
{
|
||||||
var protocolVersion = reader.ReadUInt32();
|
var protocolVersion = reader.ReadUInt32();
|
||||||
var language = (RequestLanguage)reader.ReadUInt32();
|
var command = (RequestCommand)reader.ReadUInt32();
|
||||||
uint argumentCount = reader.ReadUInt32();
|
uint argumentCount = reader.ReadUInt32();
|
||||||
|
|
||||||
var argumentsBuilder = new List<Argument>((int)argumentCount);
|
var argumentsBuilder = new List<Argument>((int)argumentCount);
|
||||||
|
|
@ -175,7 +175,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BuildRequest(protocolVersion,
|
return new BuildRequest(protocolVersion,
|
||||||
language,
|
command,
|
||||||
argumentsBuilder);
|
argumentsBuilder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +191,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
// Format the request.
|
// Format the request.
|
||||||
Log("Formatting request");
|
Log("Formatting request");
|
||||||
writer.Write(ProtocolVersion);
|
writer.Write(ProtocolVersion);
|
||||||
writer.Write((uint)Language);
|
writer.Write((uint)Command);
|
||||||
writer.Write(Arguments.Count);
|
writer.Write(Arguments.Count);
|
||||||
foreach (Argument arg in Arguments)
|
foreach (Argument arg in Arguments)
|
||||||
{
|
{
|
||||||
|
|
@ -506,10 +506,11 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
|
|
||||||
// The id numbers below are just random. It's useful to use id numbers
|
// The id numbers below are just random. It's useful to use id numbers
|
||||||
// that won't occur accidentally for debugging.
|
// that won't occur accidentally for debugging.
|
||||||
internal enum RequestLanguage
|
internal enum RequestCommand
|
||||||
{
|
{
|
||||||
CSharpCompile = 0x44532521,
|
None = 0x44532621,
|
||||||
VisualBasicCompile = 0x44532522,
|
RazorTagHelper = 0x44532622,
|
||||||
|
RazorGenerate = 0x44532623,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,69 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.CodeAnalysis.CommandLine;
|
|
||||||
|
|
||||||
namespace Microsoft.CodeAnalysis.CompilerServer
|
|
||||||
{
|
|
||||||
internal static class BuildProtocolUtil
|
|
||||||
{
|
|
||||||
internal static RunRequest GetRunRequest(BuildRequest req)
|
|
||||||
{
|
|
||||||
string currentDirectory;
|
|
||||||
string libDirectory;
|
|
||||||
string tempDirectory;
|
|
||||||
string[] arguments = GetCommandLineArguments(req, out currentDirectory, out tempDirectory, out libDirectory);
|
|
||||||
string language = "";
|
|
||||||
switch (req.Language)
|
|
||||||
{
|
|
||||||
case RequestLanguage.CSharpCompile:
|
|
||||||
language = LanguageNames.CSharp;
|
|
||||||
break;
|
|
||||||
case RequestLanguage.VisualBasicCompile:
|
|
||||||
language = LanguageNames.VisualBasic;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new RunRequest(language, currentDirectory, tempDirectory, libDirectory, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string[] GetCommandLineArguments(BuildRequest req, out string currentDirectory, out string tempDirectory, out string libDirectory)
|
|
||||||
{
|
|
||||||
currentDirectory = null;
|
|
||||||
libDirectory = null;
|
|
||||||
tempDirectory = null;
|
|
||||||
List<string> commandLineArguments = new List<string>();
|
|
||||||
|
|
||||||
foreach (BuildRequest.Argument arg in req.Arguments)
|
|
||||||
{
|
|
||||||
if (arg.ArgumentId == BuildProtocolConstants.ArgumentId.CurrentDirectory)
|
|
||||||
{
|
|
||||||
currentDirectory = arg.Value;
|
|
||||||
}
|
|
||||||
else if (arg.ArgumentId == BuildProtocolConstants.ArgumentId.TempDirectory)
|
|
||||||
{
|
|
||||||
tempDirectory = arg.Value;
|
|
||||||
}
|
|
||||||
else if (arg.ArgumentId == BuildProtocolConstants.ArgumentId.LibEnvVariable)
|
|
||||||
{
|
|
||||||
libDirectory = arg.Value;
|
|
||||||
}
|
|
||||||
else if (arg.ArgumentId == BuildProtocolConstants.ArgumentId.CommandLineArgument)
|
|
||||||
{
|
|
||||||
int argIndex = arg.ArgumentIndex;
|
|
||||||
while (argIndex >= commandLineArguments.Count)
|
|
||||||
commandLineArguments.Add("");
|
|
||||||
commandLineArguments[argIndex] = arg.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return commandLineArguments.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,12 +8,10 @@ using System.IO;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.AccessControl;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Security.Principal;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Razor.Tools;
|
||||||
using Microsoft.Win32.SafeHandles;
|
using Microsoft.Win32.SafeHandles;
|
||||||
using Roslyn.Utilities;
|
using Roslyn.Utilities;
|
||||||
using static Microsoft.CodeAnalysis.CommandLine.CompilerServerLogger;
|
using static Microsoft.CodeAnalysis.CommandLine.CompilerServerLogger;
|
||||||
|
|
@ -56,8 +54,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
|
|
||||||
internal sealed class BuildServerConnection
|
internal sealed class BuildServerConnection
|
||||||
{
|
{
|
||||||
internal const string ServerNameDesktop = "VBCSCompiler.exe";
|
internal const string ServerNameCoreClr = "rzc.dll";
|
||||||
internal const string ServerNameCoreClr = "VBCSCompiler.dll";
|
|
||||||
|
|
||||||
// Spend up to 1s connecting to existing process (existing processes should be always responsive).
|
// Spend up to 1s connecting to existing process (existing processes should be always responsive).
|
||||||
internal const int TimeOutMsExistingProcess = 1000;
|
internal const int TimeOutMsExistingProcess = 1000;
|
||||||
|
|
@ -65,8 +62,29 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
// Spend up to 20s connecting to a new process, to allow time for it to start.
|
// Spend up to 20s connecting to a new process, to allow time for it to start.
|
||||||
internal const int TimeOutMsNewProcess = 20000;
|
internal const int TimeOutMsNewProcess = 20000;
|
||||||
|
|
||||||
|
public static Task<BuildResponse> RunServerCompilation(
|
||||||
|
RequestCommand command,
|
||||||
|
List<string> arguments,
|
||||||
|
BuildPathsAlt buildPaths,
|
||||||
|
string keepAlive,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var pipeName = PipeName.ComputeDefault();
|
||||||
|
|
||||||
|
return RunServerCompilationCore(
|
||||||
|
command,
|
||||||
|
arguments,
|
||||||
|
buildPaths,
|
||||||
|
pipeName: pipeName,
|
||||||
|
keepAlive: keepAlive,
|
||||||
|
libEnvVariable: null,
|
||||||
|
timeoutOverride: null,
|
||||||
|
tryCreateServerFunc: TryCreateServerCore,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
internal static async Task<BuildResponse> RunServerCompilationCore(
|
internal static async Task<BuildResponse> RunServerCompilationCore(
|
||||||
RequestLanguage language,
|
RequestCommand language,
|
||||||
List<string> arguments,
|
List<string> arguments,
|
||||||
BuildPathsAlt buildPaths,
|
BuildPathsAlt buildPaths,
|
||||||
string pipeName,
|
string pipeName,
|
||||||
|
|
@ -89,7 +107,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
var clientDir = buildPaths.ClientDirectory;
|
var clientDir = buildPaths.ClientDirectory;
|
||||||
var timeoutNewProcess = timeoutOverride ?? TimeOutMsNewProcess;
|
var timeoutNewProcess = timeoutOverride ?? TimeOutMsNewProcess;
|
||||||
var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess;
|
var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess;
|
||||||
var clientMutexName = GetClientMutexName(pipeName);
|
var clientMutexName = MutexName.GetClientMutexName(pipeName);
|
||||||
Task<NamedPipeClientStream> pipeTask = null;
|
Task<NamedPipeClientStream> pipeTask = null;
|
||||||
using (var clientMutex = new Mutex(initiallyOwned: true,
|
using (var clientMutex = new Mutex(initiallyOwned: true,
|
||||||
name: clientMutexName,
|
name: clientMutexName,
|
||||||
|
|
@ -115,8 +133,8 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for an already running server
|
// Check for an already running server
|
||||||
var serverMutexName = GetServerMutexName(pipeName);
|
var serverMutexName = MutexName.GetServerMutexName(pipeName);
|
||||||
bool wasServerRunning = true;
|
bool wasServerRunning = WasServerMutexOpen(serverMutexName);
|
||||||
var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess;
|
var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess;
|
||||||
|
|
||||||
if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName))
|
if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName))
|
||||||
|
|
@ -152,6 +170,18 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
return new RejectedBuildResponse();
|
return new RejectedBuildResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool WasServerMutexOpen(string mutexName)
|
||||||
|
{
|
||||||
|
Mutex mutex;
|
||||||
|
var open = Mutex.TryOpenExisting(mutexName, out mutex);
|
||||||
|
if (open)
|
||||||
|
{
|
||||||
|
mutex.Dispose();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try to compile using the server. Returns a null-containing Task if a response
|
/// Try to compile using the server. Returns a null-containing Task if a response
|
||||||
/// from the server cannot be retrieved.
|
/// from the server cannot be retrieved.
|
||||||
|
|
@ -312,31 +342,17 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
|
|
||||||
internal static bool TryCreateServerCore(string clientDir, string pipeName)
|
internal static bool TryCreateServerCore(string clientDir, string pipeName)
|
||||||
{
|
{
|
||||||
bool isRunningOnCoreClr = true;
|
|
||||||
string expectedPath;
|
string expectedPath;
|
||||||
string processArguments;
|
string processArguments;
|
||||||
if (isRunningOnCoreClr)
|
|
||||||
{
|
|
||||||
// The server should be in the same directory as the client
|
|
||||||
var expectedCompilerPath = Path.Combine(clientDir, ServerNameCoreClr);
|
|
||||||
expectedPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet";
|
|
||||||
processArguments = $@"""{expectedCompilerPath}"" ""-pipename:{pipeName}""";
|
|
||||||
|
|
||||||
if (!File.Exists(expectedCompilerPath))
|
// The server should be in the same directory as the client
|
||||||
{
|
var expectedCompilerPath = Path.Combine(clientDir, ServerNameCoreClr);
|
||||||
return false;
|
expectedPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet";
|
||||||
}
|
processArguments = $@"""{expectedCompilerPath}"" server -p {pipeName}";
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The server should be in the same directory as the client
|
|
||||||
expectedPath = Path.Combine(clientDir, ServerNameDesktop);
|
|
||||||
processArguments = $@"""-pipename:{pipeName}""";
|
|
||||||
|
|
||||||
if (!File.Exists(expectedPath))
|
if (!File.Exists(expectedCompilerPath))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PlatformInformation.IsWindows)
|
if (PlatformInformation.IsWindows)
|
||||||
|
|
@ -456,17 +472,6 @@ namespace Microsoft.CodeAnalysis.CommandLine
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
internal static string GetServerMutexName(string pipeName)
|
|
||||||
{
|
|
||||||
return $"{pipeName}.server";
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string GetClientMutexName(string pipeName)
|
|
||||||
{
|
|
||||||
return $"{pipeName}.client";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value of the temporary path for the current environment assuming the working directory
|
/// Gets the value of the temporary path for the current environment assuming the working directory
|
||||||
/// is <paramref name="workingDir"/>. This function must emulate <see cref="Path.GetTempPath"/> as
|
/// is <paramref name="workingDir"/>. This function must emulate <see cref="Path.GetTempPath"/> as
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
public static IEnumerable<string> SplitCommandLineIntoArguments(string commandLine, bool removeHashComments)
|
||||||
|
{
|
||||||
|
char? unused;
|
||||||
|
return SplitCommandLineIntoArguments(commandLine, removeHashComments, out unused);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> SplitCommandLineIntoArguments(string commandLine, bool removeHashComments, out char? illegalChar)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder(commandLine.Length);
|
||||||
|
var list = new List<string>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue