245 lines
9.3 KiB
C#
245 lines
9.3 KiB
C#
// 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.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Microsoft.CodeAnalysis.Emit;
|
|
using Microsoft.CodeAnalysis.Text;
|
|
using Microsoft.Extensions.CommandLineUtils;
|
|
using Microsoft.Extensions.FileProviders;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
|
|
{
|
|
public class PrecompileRunCommand
|
|
{
|
|
public static readonly string ApplicationNameTemplate = "--applicationName";
|
|
public static readonly string OutputPathTemplate = "--output-path";
|
|
private static readonly ParallelOptions ParalellOptions = new ParallelOptions
|
|
{
|
|
MaxDegreeOfParallelism = 4
|
|
};
|
|
|
|
private CommandOption OutputPathOption { get; set; }
|
|
|
|
private CommandOption ApplicationNameOption { get; set; }
|
|
|
|
private MvcServiceProvider MvcServiceProvider { get; set; }
|
|
|
|
private CommonOptions Options { get; } = new CommonOptions();
|
|
|
|
private StrongNameOptions StrongNameOptions { get; } = new StrongNameOptions();
|
|
|
|
private string ProjectPath { get; set; }
|
|
|
|
public void Configure(CommandLineApplication app)
|
|
{
|
|
Options.Configure(app);
|
|
StrongNameOptions.Configure(app);
|
|
|
|
OutputPathOption = app.Option(
|
|
OutputPathTemplate,
|
|
"Path to the emit the precompiled assembly to.",
|
|
CommandOptionType.SingleValue);
|
|
|
|
ApplicationNameOption = app.Option(
|
|
ApplicationNameTemplate,
|
|
"Name of the application to produce precompiled assembly for.",
|
|
CommandOptionType.SingleValue);
|
|
|
|
app.OnExecute(() => Execute());
|
|
}
|
|
|
|
private int Execute()
|
|
{
|
|
ParseArguments();
|
|
|
|
MvcServiceProvider = new MvcServiceProvider(
|
|
ProjectPath,
|
|
ApplicationNameOption.Value(),
|
|
Options.ContentRootOption.Value(),
|
|
Options.ConfigureCompilationType.Value());
|
|
|
|
Console.WriteLine("Running Razor view precompilation.");
|
|
|
|
var stopWatch = Stopwatch.StartNew();
|
|
var results = GenerateCode();
|
|
var success = true;
|
|
foreach (var result in results)
|
|
{
|
|
if (!result.GeneratorResults.Success)
|
|
{
|
|
success = false;
|
|
foreach (var error in result.GeneratorResults.ParserErrors)
|
|
{
|
|
Console.Error.WriteLine($"{error.Location.FilePath} ({error.Location.LineIndex}): {error.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
var precompileAssemblyName = $"{ApplicationNameOption.Value()}{AssemblyPart.PrecompiledViewsAssemblySuffix}";
|
|
var compilation = CompileViews(results, precompileAssemblyName);
|
|
|
|
var assemblyPath = Path.Combine(OutputPathOption.Value(), precompileAssemblyName + ".dll");
|
|
var emitResult = EmitAssembly(compilation, assemblyPath);
|
|
|
|
if (!emitResult.Success)
|
|
{
|
|
foreach (var diagnostic in emitResult.Diagnostics)
|
|
{
|
|
Console.Error.WriteLine(CSharpDiagnosticFormatter.Instance.Format(diagnostic));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
stopWatch.Stop();
|
|
Console.WriteLine($"Precompiled views emitted to {assemblyPath}.");
|
|
Console.WriteLine($"Successfully compiled {results.Length} Razor views in {stopWatch.ElapsedMilliseconds}ms.");
|
|
return 0;
|
|
}
|
|
|
|
private EmitResult EmitAssembly(CSharpCompilation compilation, string assemblyPath)
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(assemblyPath));
|
|
|
|
EmitResult emitResult;
|
|
using (var assemblyStream = File.OpenWrite(assemblyPath))
|
|
{
|
|
using (var pdbStream = File.OpenWrite(Path.ChangeExtension(assemblyPath, ".pdb")))
|
|
{
|
|
emitResult = compilation.Emit(
|
|
assemblyStream,
|
|
pdbStream,
|
|
options: MvcServiceProvider.Compiler.EmitOptions);
|
|
}
|
|
}
|
|
|
|
return emitResult;
|
|
}
|
|
|
|
private CSharpCompilation CompileViews(ViewCompilationInfo[] results, string assemblyname)
|
|
{
|
|
var compiler = MvcServiceProvider.Compiler;
|
|
var compilation = compiler.CreateCompilation(assemblyname);
|
|
var syntaxTrees = new SyntaxTree[results.Length];
|
|
|
|
Parallel.For(0, results.Length, ParalellOptions, i =>
|
|
{
|
|
var result = results[i];
|
|
var sourceText = SourceText.From(result.GeneratorResults.GeneratedCode, Encoding.UTF8);
|
|
var fileInfo = result.RelativeFileInfo;
|
|
var syntaxTree = compiler.CreateSyntaxTree(sourceText)
|
|
.WithFilePath(fileInfo.FileInfo.PhysicalPath ?? fileInfo.RelativePath);
|
|
syntaxTrees[i] = syntaxTree;
|
|
});
|
|
|
|
compilation = compilation.AddSyntaxTrees(syntaxTrees);
|
|
Parallel.For(0, results.Length, ParalellOptions, i =>
|
|
{
|
|
results[i].TypeName = ReadTypeInfo(compilation, syntaxTrees[i]);
|
|
});
|
|
|
|
// Post process the compilation - run ExpressionRewritter and any user specified callbacks.
|
|
compilation = ExpressionRewriter.Rewrite(compilation);
|
|
var compilationContext = new RoslynCompilationContext(compilation);
|
|
MvcServiceProvider.ViewEngineOptions.CompilationCallback(compilationContext);
|
|
compilation = compilationContext.Compilation;
|
|
|
|
var codeGenerator = new ViewInfoContainerCodeGenerator(compiler, compilation);
|
|
codeGenerator.AddViewFactory(results);
|
|
|
|
var assemblyName = new AssemblyName(ApplicationNameOption.Value());
|
|
assemblyName = Assembly.Load(assemblyName).GetName();
|
|
codeGenerator.AddAssemblyMetadata(assemblyName, StrongNameOptions);
|
|
|
|
return codeGenerator.Compilation;
|
|
}
|
|
|
|
private void ParseArguments()
|
|
{
|
|
ProjectPath = Options.ProjectArgument.Value;
|
|
if (string.IsNullOrEmpty(ProjectPath))
|
|
{
|
|
throw new ArgumentException("Project path not specified.");
|
|
}
|
|
|
|
if (!OutputPathOption.HasValue())
|
|
{
|
|
throw new ArgumentException($"Option {OutputPathTemplate} does not specify a value.");
|
|
}
|
|
|
|
if (!ApplicationNameOption.HasValue())
|
|
{
|
|
throw new ArgumentException($"Option {ApplicationNameTemplate} does not specify a value.");
|
|
}
|
|
}
|
|
|
|
private ViewCompilationInfo[] GenerateCode()
|
|
{
|
|
var files = new List<RelativeFileInfo>();
|
|
GetRazorFiles(MvcServiceProvider.FileProvider, files, root: string.Empty);
|
|
var results = new ViewCompilationInfo[files.Count];
|
|
Parallel.For(0, results.Length, ParalellOptions, i =>
|
|
{
|
|
var fileInfo = files[i];
|
|
using (var fileStream = fileInfo.FileInfo.CreateReadStream())
|
|
{
|
|
var result = MvcServiceProvider.Host.GenerateCode(fileInfo.RelativePath, fileStream);
|
|
results[i] = new ViewCompilationInfo(fileInfo, result);
|
|
}
|
|
});
|
|
|
|
return results;
|
|
}
|
|
|
|
private static void GetRazorFiles(IFileProvider fileProvider, List<RelativeFileInfo> razorFiles, string root)
|
|
{
|
|
foreach (var fileInfo in fileProvider.GetDirectoryContents(root))
|
|
{
|
|
var relativePath = Path.Combine(root, fileInfo.Name);
|
|
if (fileInfo.IsDirectory)
|
|
{
|
|
GetRazorFiles(fileProvider, razorFiles, relativePath);
|
|
}
|
|
else if (fileInfo.Name.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
razorFiles.Add(new RelativeFileInfo(fileInfo, relativePath));
|
|
}
|
|
}
|
|
}
|
|
|
|
private string ReadTypeInfo(CSharpCompilation compilation, SyntaxTree syntaxTree)
|
|
{
|
|
var semanticModel = compilation.GetSemanticModel(syntaxTree, ignoreAccessibility: true);
|
|
var classDeclarations = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>();
|
|
foreach (var declaration in classDeclarations)
|
|
{
|
|
var typeSymbol = semanticModel.GetDeclaredSymbol(declaration);
|
|
if (typeSymbol.ContainingType == null && typeSymbol.DeclaredAccessibility == Accessibility.Public)
|
|
{
|
|
return typeSymbol.ToDisplayString();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|