Build server working end to end

- Added a UseServer switch
- Updated tasks to use the build server in enabled
This commit is contained in:
Ajay Bhargav Baaskaran 2018-01-14 17:16:44 -08:00
parent 5c6ff4366f
commit 4500de5862
20 changed files with 633 additions and 178 deletions

View File

@ -1,7 +1,7 @@
<Project>
<!-- 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>
<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>
<!-- These are just normal MSBuild files that we want to include in the package -->
<Content Include="build\**\*.props" PackagePath="build\"/>
<Content Include="build\**\*.targets" PackagePath="build\"/>
<Content Include="buildMultiTargeting\*.props" PackagePath="buildMultiTargeting\"/>
<Content Include="buildMultiTargeting\*.targets" PackagePath="buildMultiTargeting\"/>
<Content Include="build\**\*.props" PackagePath="build\" />
<Content Include="build\**\*.targets" PackagePath="build\" />
<Content Include="buildMultiTargeting\*.props" PackagePath="buildMultiTargeting\" />
<Content Include="buildMultiTargeting\*.targets" PackagePath="buildMultiTargeting\" />
</ItemGroup>
<!-- This is the tasks project that needs to be included in the package. -->
<ItemGroup>
<TaskProject Include="..\Microsoft.AspNetCore.Razor.Tasks\Microsoft.AspNetCore.Razor.Tasks.csproj"/>
<TaskProject Include="..\Microsoft.AspNetCore.Razor.Tasks\Microsoft.AspNetCore.Razor.Tasks.csproj" />
</ItemGroup>
<!-- These are tools that need to be included in the package. -->
<ItemGroup>
<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.GenerateTool\Microsoft.AspNetCore.Razor.GenerateTool.csproj" />
<ToolProject Include="..\Microsoft.AspNetCore.Razor.TagHelperTool\Microsoft.AspNetCore.Razor.TagHelperTool.csproj" />
<ToolProject Include="..\Microsoft.AspNetCore.Razor.Tools\Microsoft.AspNetCore.Razor.Tools.csproj" />
</ItemGroup>
<!-- 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.
-->
<MSBuild Projects="@(TaskProject)"/>
<MSBuild Projects="@(TaskProject)" />
<MSBuild
Projects="@(TaskProject)"
Properties="TargetFramework=net46"
Targets="GetTargetPath">
<Output TaskParameter="TargetOutputs" ItemName="TaskAssemblyNet46"/>
<MSBuild Projects="@(TaskProject)" Properties="TargetFramework=net46" Targets="GetTargetPath">
<Output TaskParameter="TargetOutputs" ItemName="TaskAssemblyNet46" />
</MSBuild>
<MSBuild
Projects="@(TaskProject)"
Properties="TargetFramework=netstandard2.0"
Targets="GetTargetPath">
<Output TaskParameter="TargetOutputs" ItemName="TaskAssemblyNetStandard"/>
<MSBuild Projects="@(TaskProject)" Properties="TargetFramework=netstandard2.0" Targets="GetTargetPath">
<Output TaskParameter="TargetOutputs" ItemName="TaskAssemblyNetStandard" />
</MSBuild>
<Copy SourceFiles="@(TaskAssemblyNet46)" DestinationFolder="$(OutputPath)\tasks\net46\">
<Output TaskParameter="CopiedFiles" ItemName="FileWrites"/>
<Output TaskParameter="CopiedFiles" ItemName="FileWrites" />
</Copy>
<Copy SourceFiles="@(TaskAssemblyNetStandard)" DestinationFolder="$(OutputPath)\tasks\netstandard2.0\">
<Output TaskParameter="CopiedFiles" ItemName="FileWrites"/>
<Output TaskParameter="CopiedFiles" ItemName="FileWrites" />
</Copy>
<ItemGroup>
<None Include="@(TaskAssemblyNet46)" PackagePath="tasks\net46\" Pack="true"/>
<None Include="@(TaskAssemblyNetStandard)" PackagePath="tasks\netstandard2.0\" Pack="true"/>
<None Include="@(TaskAssemblyNet46)" PackagePath="tasks\net46\" Pack="true" />
<None Include="@(TaskAssemblyNetStandard)" PackagePath="tasks\netstandard2.0\" Pack="true" />
</ItemGroup>
<Error Text="TaskAssemblyNet46 is empty. This is a bug" Condition="'@(TaskAssemblyNet46)'==''"/>
<Error Text="TaskAssemblyNetStandard is empty. This is a bug" Condition="'@(TaskAssemblyNetStandard)'==''"/>
<Error Text="TaskAssemblyNet46 is empty. This is a bug" Condition="'@(TaskAssemblyNet46)'==''" />
<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
all of the output to put in the package.
-->
<RemoveDir Directories="tools\"/>
<MSBuild Projects="@(ToolProject)"/>
<MSBuild
Projects="@(ToolProject)"
Properties="PublishDir=$(MSBuildProjectDirectory)\$(OutputPath)tools\"
Targets="Publish"/>
<RemoveDir Directories="tools\" />
<MSBuild Projects="@(ToolProject)" />
<MSBuild Projects="@(ToolProject)" Properties="PublishDir=$(MSBuildProjectDirectory)\$(OutputPath)tools\" Targets="Publish" />
<ItemGroup>
<_RazorTool Include="$(OutputPath)tools\**\*" Exclude="$(OutputPath)tools\**\*.xml;$(OutputPath)tools\**\*.pdb" />
@ -116,6 +108,6 @@
</None>
</ItemGroup>
<Error Text="_RazorTool is empty. This is a bug" Condition="'@(_RazorTool)'==''"/>
<Error Text="_RazorTool is empty. This is a bug" Condition="'@(_RazorTool)'==''" />
</Target>
</Project>

View File

@ -15,6 +15,7 @@
<!-- Used to locate our tools -->
<_RazorGenerateToolAssembly>$(_RazorMSBuildRoot)tools\Microsoft.AspNetCore.Razor.GenerateTool.dll</_RazorGenerateToolAssembly>
<_RazorTagHelperToolAssembly>$(_RazorMSBuildRoot)tools\Microsoft.AspNetCore.Razor.TagHelperTool.dll</_RazorTagHelperToolAssembly>
<_RazorBuildServerAssembly>$(_RazorMSBuildRoot)tools\rzc.dll</_RazorBuildServerAssembly>
<!-- Used to hash file inputs for RazorGenerate -->
<_RazorGenerateInputsHash></_RazorGenerateInputsHash>
@ -74,8 +75,10 @@
Debug="$(_RazorDebugTagHelperTask)"
DebugTool="$(_RazorDebugTagHelperTool)"
ToolAssembly="$(_RazorTagHelperToolAssembly)"
UseServer="$(UseRazorBuildServer)"
ServerAssembly="$(_RazorBuildServerAssembly)"
Assemblies="@(RazorReferencePath)"
ProjectRoot="$(MSBuildProjectDirectory)"
TagHelperManifest="$(_RazorTagHelperOutputCache)">
<Output
TaskParameter="TagHelperManifest"
@ -107,6 +110,8 @@
Debug="$(_RazorDebugGenerateCodeTask)"
DebugTool="$(_RazorDebugGenerateCodeTool)"
ToolAssembly="$(_RazorGenerateToolAssembly)"
UseServer="$(UseRazorBuildServer)"
ServerAssembly="$(_RazorBuildServerAssembly)"
Sources="@(RazorGenerate)"
ProjectRoot="$(MSBuildProjectDirectory)"
TagHelperManifest="$(_RazorTagHelperOutputCache)"

View File

@ -44,7 +44,7 @@
so that it only shows up in supported versions.
-->
<IsRazorCompilerReferenced>true</IsRazorCompilerReferenced>
<!-- Override this to hijack the tasks and targets. Used by tests. -->
<_RazorMSBuildRoot Condition="'$(_RazorMSBuildRoot)'==''">$(MSBuildThisFileDirectory)..\..\</_RazorMSBuildRoot>

View File

@ -56,9 +56,10 @@
<!-- Default to on if MvcRazorCompileOnPublish isn't set for some reason -->
<RazorCompileOnPublish Condition="'$(RazorCompileOnPublish)'==''">true</RazorCompileOnPublish>
<UseRazorBuildServer Condition="'$(UseRazorBuildServer)'==''">false</UseRazorBuildServer>
</PropertyGroup>
<!--
Properties that configure Razor SDK, but need to be defined in targets due to evaluation order.
-->
@ -121,14 +122,14 @@
</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.
-->
<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.
-->

View File

@ -20,8 +20,9 @@ namespace Microsoft.AspNetCore.Razor.TagHelperTool
HelpOption("-?|-h|--help");
TagHelperManifest = Option("-o", "output file", CommandOptionType.SingleValue);
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);
}
@ -30,6 +31,8 @@ namespace Microsoft.AspNetCore.Razor.TagHelperTool
public CommandOption TagHelperManifest { get; }
public CommandOption ProjectRoot { get; }
public new int Execute(params string[] args)
{
try

View File

@ -32,12 +32,15 @@ namespace Microsoft.AspNetCore.Razor.TagHelperTool
}
return ExecuteCore(
projectDirectory: application.ProjectRoot.Value(),
outputFilePath: application.TagHelperManifest.Value(),
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];
for (var i = 0; i < assemblies.Length; i++)
{
@ -147,6 +150,11 @@ namespace Microsoft.AspNetCore.Razor.TagHelperTool
return false;
}
if (string.IsNullOrEmpty(application.ProjectRoot.Value()))
{
application.ProjectRoot.Values.Add(Environment.CurrentDirectory);
}
return true;
}
}

View File

@ -2,16 +2,24 @@
// 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.IO;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Tools;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.CodeAnalysis.CommandLine;
using Microsoft.Extensions.CommandLineUtils;
using Roslyn.Utilities;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public abstract class DotNetToolTask : ToolTask
{
private CancellationTokenSource _razorServerCts;
public bool Debug { get; set; }
public bool DebugTool { get; set; }
@ -19,6 +27,11 @@ namespace Microsoft.AspNetCore.Razor.Tasks
[Required]
public string ToolAssembly { get; set; }
[Required]
public string ServerAssembly { get; set; }
public bool UseServer { get; set; }
protected override string ToolName => "dotnet";
// 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;
internal abstract RequestCommand Command { get; }
protected override string GenerateFullPathToTool()
{
#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 base.Execute();
return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands);
}
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;
}
/// <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();
}
}
}

View File

@ -15,5 +15,30 @@
<PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkPackageVersion)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
<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>
</Project>

View File

@ -3,6 +3,7 @@
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.CodeAnalysis.CommandLine;
namespace Microsoft.AspNetCore.Razor.Tasks
{
@ -20,6 +21,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks
[Required]
public string TagHelperManifest { get; set; }
internal override RequestCommand Command => RequestCommand.RazorGenerate;
protected override string GenerateResponseFileCommands()
{
var builder = new StringBuilder();

View File

@ -5,6 +5,7 @@
using System.IO;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.CodeAnalysis.CommandLine;
namespace Microsoft.AspNetCore.Razor.Tasks
{
@ -17,7 +18,9 @@ namespace Microsoft.AspNetCore.Razor.Tasks
[Required]
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()
{
@ -43,6 +46,9 @@ namespace Microsoft.AspNetCore.Razor.Tasks
builder.AppendLine("-o");
builder.AppendLine(TagHelperManifest);
builder.AppendLine("-p");
builder.AppendLine(ProjectRoot);
return builder.ToString();
}
}

View File

@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
Commands.Add(new DiscoverCommand(this));
Commands.Add(new ServerCommand(this));
Commands.Add(new ShutdownCommand(this));
}
public CancellationToken CancellationToken { get; }

View File

@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CommandLine;
using Microsoft.Extensions.CommandLineUtils;
namespace Microsoft.AspNetCore.Razor.Tools
{
@ -25,7 +27,23 @@ namespace Microsoft.AspNetCore.Razor.Tools
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)
@ -33,14 +51,10 @@ namespace Microsoft.AspNetCore.Razor.Tools
string workingDirectory = 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);
for (var i = 0; i < request.Arguments.Count; i++)
{
args[i] = string.Empty;
var argument = request.Arguments[i];
if (argument.ArgumentId == BuildProtocolConstants.ArgumentId.CurrentDirectory)
{
@ -52,7 +66,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
}
else if (argument.ArgumentId == BuildProtocolConstants.ArgumentId.CommandLineArgument)
{
args[i] = argument.Value;
args.Add(argument.Value);
}
}

View File

@ -23,14 +23,17 @@ namespace Microsoft.AspNetCore.Razor.Tools
public DiscoverCommand(Application parent)
: base(parent, "discover")
{
TagHelperManifest = Option("-o", "output file", CommandOptionType.SingleValue);
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 CommandOption TagHelperManifest { get; }
public CommandOption ProjectDirectory { get; }
protected override bool ValidateArguments()
{
if (string.IsNullOrEmpty(TagHelperManifest.Value()))
@ -45,19 +48,27 @@ namespace Microsoft.AspNetCore.Razor.Tools
return false;
}
if (string.IsNullOrEmpty(ProjectDirectory.Value()))
{
ProjectDirectory.Values.Add(Environment.CurrentDirectory);
}
return true;
}
protected override Task<int> ExecuteCoreAsync()
{
var result = ExecuteCore(
projectDirectory: ProjectDirectory.Value(),
outputFilePath: TagHelperManifest.Value(),
assemblies: Assemblies.Values.ToArray());
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];
for (var i = 0; i < assemblies.Length; i++)
{

View File

@ -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;
}
}
}

View File

@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
return null;
}
return $"{userName}.{isAdmin}.{baseName}";
return $"{userName}.{(isAdmin ? 'T' : 'F')}.{baseName}";
}
private static string ComputeBaseName(string baseDirectory)
@ -59,6 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
{
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(baseDirectory));
return Convert.ToBase64String(bytes)
.Substring(0, 25) // We only have ~50 total characters on Mac, so strip that down
.Replace("/", "_")
.Replace("=", string.Empty);
}

View File

@ -446,10 +446,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
{
CompilerServerLogger.Log("Begin processing request");
// TODO: this is where we actually process the request.
// Take a look at BuildProtocolUtil
var response = (BuildResponse)null;
var response = _compilerHost.Execute(buildRequest, cancellationToken);
CompilerServerLogger.Log("End processing request");
return response;

View File

@ -39,13 +39,13 @@ namespace Microsoft.CodeAnalysis.CommandLine
internal class BuildRequest
{
public readonly uint ProtocolVersion;
public readonly RequestLanguage Language;
public readonly RequestCommand Command;
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;
Language = language;
Command = command;
Arguments = new ReadOnlyCollection<Argument>(arguments.ToList());
if (Arguments.Count > ushort.MaxValue)
@ -80,7 +80,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
}
public static BuildRequest Create(
RequestLanguage language,
RequestCommand command,
string workingDirectory,
string tempDirectory,
IList<string> args,
@ -115,13 +115,13 @@ namespace Microsoft.CodeAnalysis.CommandLine
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()
{
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()
@ -163,7 +163,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
using (var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode))
{
var protocolVersion = reader.ReadUInt32();
var language = (RequestLanguage)reader.ReadUInt32();
var command = (RequestCommand)reader.ReadUInt32();
uint argumentCount = reader.ReadUInt32();
var argumentsBuilder = new List<Argument>((int)argumentCount);
@ -175,7 +175,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
}
return new BuildRequest(protocolVersion,
language,
command,
argumentsBuilder);
}
}
@ -191,7 +191,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
// Format the request.
Log("Formatting request");
writer.Write(ProtocolVersion);
writer.Write((uint)Language);
writer.Write((uint)Command);
writer.Write(Arguments.Count);
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
// that won't occur accidentally for debugging.
internal enum RequestLanguage
internal enum RequestCommand
{
CSharpCompile = 0x44532521,
VisualBasicCompile = 0x44532522,
None = 0x44532621,
RazorTagHelper = 0x44532622,
RazorGenerate = 0x44532623,
}
/// <summary>

View File

@ -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();
}
}
}

View File

@ -8,12 +8,10 @@ using System.IO;
using System.IO.Pipes;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Tools;
using Microsoft.Win32.SafeHandles;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CommandLine.CompilerServerLogger;
@ -56,8 +54,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
internal sealed class BuildServerConnection
{
internal const string ServerNameDesktop = "VBCSCompiler.exe";
internal const string ServerNameCoreClr = "VBCSCompiler.dll";
internal const string ServerNameCoreClr = "rzc.dll";
// Spend up to 1s connecting to existing process (existing processes should be always responsive).
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.
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(
RequestLanguage language,
RequestCommand language,
List<string> arguments,
BuildPathsAlt buildPaths,
string pipeName,
@ -89,7 +107,7 @@ namespace Microsoft.CodeAnalysis.CommandLine
var clientDir = buildPaths.ClientDirectory;
var timeoutNewProcess = timeoutOverride ?? TimeOutMsNewProcess;
var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess;
var clientMutexName = GetClientMutexName(pipeName);
var clientMutexName = MutexName.GetClientMutexName(pipeName);
Task<NamedPipeClientStream> pipeTask = null;
using (var clientMutex = new Mutex(initiallyOwned: true,
name: clientMutexName,
@ -115,8 +133,8 @@ namespace Microsoft.CodeAnalysis.CommandLine
}
// Check for an already running server
var serverMutexName = GetServerMutexName(pipeName);
bool wasServerRunning = true;
var serverMutexName = MutexName.GetServerMutexName(pipeName);
bool wasServerRunning = WasServerMutexOpen(serverMutexName);
var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess;
if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName))
@ -152,6 +170,18 @@ namespace Microsoft.CodeAnalysis.CommandLine
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>
/// Try to compile using the server. Returns a null-containing Task if a response
/// from the server cannot be retrieved.
@ -312,31 +342,17 @@ namespace Microsoft.CodeAnalysis.CommandLine
internal static bool TryCreateServerCore(string clientDir, string pipeName)
{
bool isRunningOnCoreClr = true;
string expectedPath;
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))
{
return false;
}
}
else
{
// The server should be in the same directory as the client
expectedPath = Path.Combine(clientDir, ServerNameDesktop);
processArguments = $@"""-pipename:{pipeName}""";
// 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}"" server -p {pipeName}";
if (!File.Exists(expectedPath))
{
return false;
}
if (!File.Exists(expectedCompilerPath))
{
return false;
}
if (PlatformInformation.IsWindows)
@ -456,17 +472,6 @@ namespace Microsoft.CodeAnalysis.CommandLine
}
#endif
internal static string GetServerMutexName(string pipeName)
{
return $"{pipeName}.server";
}
internal static string GetClientMutexName(string pipeName)
{
return $"{pipeName}.client";
}
/// <summary>
/// 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

View File

@ -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;
}
}
}