From 1c5840a9f4b5882dacd3f238ade165ee90392859 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 21 Jul 2016 09:04:25 -0700 Subject: [PATCH 1/7] Add dotnet-razor-precompile tool --- Mvc.NoFun.sln | 32 ++- .../Internal/PrecompileDispatchCommand.cs | 153 ++++++++++++++ ...tCore.Mvc.Razor.Precompilation.Tools.xproj | 15 +- .../Program.cs | 21 ++ .../project.json | 51 +++++ .../Internal/AddResourcesRewriter.cs | 70 +++++++ .../Internal/CompileOutputs.cs | 29 +++ .../Internal/DebugHelper.cs | 25 +++ .../Internal/FIleProviderUtilities.cs | 37 ++++ .../Internal/PrecompilationApplication.cs | 58 ++++++ .../Internal/PrecompilationResult.cs | 22 ++ .../Internal/PrecompileCommandBase.cs | 184 +++++++++++++++++ .../Internal/PrecompileRunCommand.cs | 193 ++++++++++++++++++ .../Internal/RemoveStrongNameRewriter.cs | 29 +++ ....AspNetCore.Mvc.Razor.Precompilation.xproj | 15 +- .../Program.cs | 69 +++++++ .../Properties/AssemblyInfo.cs | 19 ++ .../project.json | 43 ++++ .../Compilation/IRoslynCompilationService.cs | 28 +++ .../Compilation/PrecompiledViewsFeature.cs | 14 ++ .../MvcRazorMvcCoreBuilderExtensions.cs | 7 +- .../Internal/CompiledAssemblyUtility.cs | 41 ++++ .../Internal/DefaultCompilerCacheProvider.cs | 11 +- .../DefaultRoslynCompilationService.cs | 86 ++++---- .../PrecompiledViewsFeatureProvider.cs | 57 ++++++ .../MvcServiceCollectionExtensionsTest.cs | 3 +- 26 files changed, 1249 insertions(+), 63 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs rename test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.xproj => src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj (54%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/AddResourcesRewriter.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/CompileOutputs.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/DebugHelper.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/FIleProviderUtilities.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationApplication.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationResult.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileCommandBase.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileRunCommand.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/RemoveStrongNameRewriter.cs rename test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.xproj => src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj (63%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Program.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/project.json create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IRoslynCompilationService.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompiledAssemblyUtility.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Internal/PrecompiledViewsFeatureProvider.cs diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index f67e635663..a4b5d15af3 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -81,6 +81,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Te EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MvcSandbox", "samples\MvcSandbox\MvcSandbox.xproj", "{14ED4476-9F24-4776-8417-EA6927F6C9C9}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools", "src\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj", "{F8BF7D95-0633-407F-BB0B-02563F13C068}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation", "src\Microsoft.AspNetCore.Mvc.Razor.Precompilation\Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj", "{EBB55C82-6E61-4A0D-8AD8-86915696E86E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -464,6 +468,30 @@ Global {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|x86.ActiveCfg = Release|Any CPU {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|x86.Build.0 = Release|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|x86.ActiveCfg = Debug|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|x86.Build.0 = Debug|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Any CPU.Build.0 = Release|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|x86.ActiveCfg = Release|Any CPU + {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|x86.Build.0 = Release|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|x86.ActiveCfg = Debug|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|x86.Build.0 = Debug|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|Any CPU.Build.0 = Release|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|x86.ActiveCfg = Release|Any CPU + {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -502,5 +530,7 @@ Global {8FC726B5-E766-44E0-8B38-1313B6D8D9A7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {14ED4476-9F24-4776-8417-EA6927F6C9C9} = {DAAE4C74-D06F-4874-A166-33305D2643CE} + {F8BF7D95-0633-407F-BB0B-02563F13C068} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {EBB55C82-6E61-4A0D-8AD8-86915696E86E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs new file mode 100644 index 0000000000..843a01e2d6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs @@ -0,0 +1,153 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.InternalAbstractions; +using Microsoft.DotNet.ProjectModel; +using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.Internal; +using NuGet.Frameworks; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public class PrecompileDispatchCommand : PrecompileCommandBase + { + private CommandOption NoBuildOption { get; set; } + + protected override void Configure(CommandLineApplication app) + { + base.Configure(app); + NoBuildOption = app.Option( + "--no-build", + "Do not build project before compiling views.", + CommandOptionType.NoValue); + } + + protected override int ExecuteCore() + { + if (!NoBuildOption.HasValue()) + { + var exitCode = BuildProject(); + if (exitCode != 0) + { + return exitCode; + } + } + + var dispatchArgs = new List + { + ProjectPath, + "--framework", + TargetFramework.ToString(), + "--configuration", + Configuration, + }; + + string outputPath = null; + if (OutputPathOption.HasValue()) + { + outputPath = OutputPathOption.Value(); + + dispatchArgs.Add("--output"); + dispatchArgs.Add(outputPath); + } + + if (ConfigureCompilationType.HasValue()) + { + dispatchArgs.Add("--configure-compilation-type"); + dispatchArgs.Add(ConfigureCompilationType.Value()); + } + + if (ContentRootOption.HasValue()) + { + dispatchArgs.Add("--content-root"); + dispatchArgs.Add(ContentRootOption.Value()); + } + + if (GeneratePdbOption.HasValue()) + { + dispatchArgs.Add("--generate-pdbs"); + } + + var toolName = typeof(Precompilation.Program).GetTypeInfo().Assembly.GetName().Name; + var dispatchCommand = DotnetToolDispatcher.CreateDispatchCommand( + dispatchArgs, + TargetFramework, + Configuration, + outputPath: outputPath, + buildBasePath: null, + projectDirectory: ProjectPath, + toolName: toolName); + + var commandExitCode = dispatchCommand + .ForwardStdErr(Console.Error) + .ForwardStdOut(Console.Out) + .Execute() + .ExitCode; + + var buildOutputPath = GetBuildOutputPath(); + var updatedBinary = Directory.EnumerateFiles(buildOutputPath, $"*{ModifiedAssemblyExtension}").FirstOrDefault(); + if (updatedBinary == null) + { + Console.Error.WriteLine("Unable to find modified binary with precompiled views."); + return 1; + } + else + { + File.Copy( + updatedBinary, + Path.ChangeExtension(updatedBinary, ".dll"), + overwrite: true); + File.Delete(updatedBinary); + } + + return commandExitCode; + } + + private int BuildProject() + { + var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); + + var projectContext = workspace.GetProjectContext(ProjectPath, NuGetFramework.Parse(FrameworkOption.Value())); + if (projectContext == null) + { + Console.Error.WriteLine($"Project '{ProjectPath}' does not support framework: {FrameworkOption.Value()}"); + return 1; + } + + projectContext = workspace.GetRuntimeContext( + projectContext, + RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers()); + + var arguments = new List + { + ProjectPath, + "--framework", + projectContext.TargetFramework.ToString(), + }; + + if (ConfigurationOption.HasValue()) + { + arguments.Add("--configuration"); + arguments.Add(ConfigurationOption.Value()); + } + + if (OutputPathOption.HasValue()) + { + arguments.Add("--output"); + arguments.Add(OutputPathOption.Value()); + } + + return Command.CreateDotNet("build", arguments, NuGetFramework.Parse(FrameworkOption.Value()), ConfigurationOption.Value()) + .ForwardStdErr(Console.Error) + .ForwardStdOut(Console.Out) + .Execute() + .ExitCode; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.xproj b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj similarity index 54% rename from test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.xproj rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj index 014f4d66cb..9f16787ad9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.xproj +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj @@ -1,20 +1,19 @@  - + - 14.0 + 14.0.25420 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + - 6bb4c20b-24c0-45d6-9e4c-c2620959bdd5 + f8bf7d95-0633-407f-bb0b-02563f13c068 + Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools .\obj .\bin\ + 2.0 - - - - + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs new file mode 100644 index 0000000000..cf3700482e --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs @@ -0,0 +1,21 @@ +// 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 Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools +{ + public class Program + { + public static int Main(string[] args) + { +#if DEBUG + DebugHelper.HandleDebugSwitch(ref args); +#endif + + var app = new PrecompilationApplication(typeof(Program)); + PrecompileCommandBase.Register(app); + return app.Execute(args); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json new file mode 100644 index 0000000000..cb00707f37 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json @@ -0,0 +1,51 @@ +{ + "version": "1.0.0-*", + "description": "Razor precompilation", + "packOptions": { + "repository": { + "type": "git", + "url": "git://github.com/AspNet/Mvc" + }, + "tags": [ + "cshtml", + "razor", + "precompilation", + "aspnetcore" + ] + }, + "buildOptions": { + "outputName": "dotnet-razor-precompile", + "keyFile": "../../tools/Key.snk", + "warningsAsErrors": true, + "emitEntryPoint": true, + "nowarn": [ + "CS1591" + ], + "xmlDoc": true + }, + "dependencies": { + "Microsoft.AspNetCore.Hosting": "1.1.0-*", + "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-*", + "Microsoft.Extensions.CommandLineUtils": "1.1.0-*", + "Microsoft.AspNetCore.Mvc.Razor.Precompilation": "1.0.0-*", + "Microsoft.Extensions.DotnetToolDispatcher.Sources": { + "type": "build", + "version": "1.1.0-*" + } + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.DotNet.ProjectModel.Loader": "1.0.0-*", + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-*" + }, + "System.Diagnostics.Process": "4.1.0-*", + "System.Runtime.Serialization.Primitives": "4.1.1-*" + } + }, + "net46": {} + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/AddResourcesRewriter.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/AddResourcesRewriter.cs new file mode 100644 index 0000000000..78dde6fa68 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/AddResourcesRewriter.cs @@ -0,0 +1,70 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.Cci; +using Microsoft.Cci.MutableCodeModel; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public class AddResourcesRewriter : MetadataRewriter + { + private readonly List _outputs; + private IAssembly _assembly; + + public AddResourcesRewriter(IMetadataHost host, List outputs) + : base(host) + { + _outputs = outputs; + } + + public override IAssembly Rewrite(IAssembly assembly) + { + _assembly = assembly; + return _assembly; + } + + public override List Rewrite(List resourceReferences) + { + if (resourceReferences == null) + { + resourceReferences = new List(); + } + + foreach (var output in _outputs) + { + var dllResource = $"{PrecompiledViewsFeatureProvider.PrecompiledResourcePrefix}{output.RelativePath}.dll"; + resourceReferences.Add(new ResourceReference + { + Name = host.NameTable.GetNameFor(dllResource), + DefiningAssembly = _assembly, + IsPublic = true, + Resource = new Resource + { + Data = output.AssemblyStream.ToArray().ToList(), + IsPublic = true, + DefiningAssembly = _assembly, + } + }); + + var pdbResource = $"{PrecompiledViewsFeatureProvider.PrecompiledResourcePrefix}{output.RelativePath}.pdb"; + resourceReferences.Add(new ResourceReference + { + Name = host.NameTable.GetNameFor(pdbResource), + DefiningAssembly = _assembly, + IsPublic = true, + Resource = new Resource + { + Data = output.PdbStream.ToArray().ToList(), + IsPublic = true, + DefiningAssembly = _assembly, + } + }); + } + + return resourceReferences; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/CompileOutputs.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/CompileOutputs.cs new file mode 100644 index 0000000000..c64988514c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/CompileOutputs.cs @@ -0,0 +1,29 @@ +// 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.IO; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public class CompileOutputs : IDisposable + { + public CompileOutputs(string relativePath, bool generatePdbs) + { + RelativePath = relativePath; + PdbStream = generatePdbs ? new MemoryStream() : null; + } + + public MemoryStream AssemblyStream { get; } = new MemoryStream(); + + public MemoryStream PdbStream { get; } + + public string RelativePath { get; } + + public void Dispose() + { + AssemblyStream.Dispose(); + PdbStream.Dispose(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/DebugHelper.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/DebugHelper.cs new file mode 100644 index 0000000000..ec05c44fc7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/DebugHelper.cs @@ -0,0 +1,25 @@ +// 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. + +#if DEBUG +using System; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public static class DebugHelper + { + public static void HandleDebugSwitch(ref string[] args) + { + if (args.Length > 0 && string.Equals("--debug", args[0], StringComparison.OrdinalIgnoreCase)) + { + args = args.Skip(1).ToArray(); + Console.WriteLine("Waiting for debugger to attach. Press ENTER to continue"); + Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}"); + Console.ReadLine(); + } + } + } +} +#endif \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/FIleProviderUtilities.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/FIleProviderUtilities.cs new file mode 100644 index 0000000000..83754ad305 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/FIleProviderUtilities.cs @@ -0,0 +1,37 @@ +// 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 Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public static class FileProviderUtilities + { + public static IEnumerable GetRazorFiles(IFileProvider fileProvider) + { + var files = new List(); + GetRazorFiles(fileProvider, files, root: string.Empty); + return files; + } + + private static void GetRazorFiles(IFileProvider fileProvider, List 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)); + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationApplication.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationApplication.cs new file mode 100644 index 0000000000..eee30b916a --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationApplication.cs @@ -0,0 +1,58 @@ +// 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.Reflection; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public class PrecompilationApplication : CommandLineApplication + { + private readonly Type _callingType; + + public PrecompilationApplication(Type callingType) + { + _callingType = callingType; + + Name = "razor-tooling"; + FullName = "Microsoft Razor Tooling Utility"; + Description = "Resolves Razor tooling specific information."; + ShortVersionGetter = GetInformationalVersion; + + HelpOption("-?|-h|--help"); + + OnExecute(() => + { + ShowHelp(); + return 2; + }); + } + + public new int Execute(params string[] args) + { + try + { + return base.Execute(args); + } + catch (Exception ex) + { + Console.Error.WriteLine(ex.Message); + return 1; + } + } + + private string GetInformationalVersion() + { + var assembly = _callingType.GetTypeInfo().Assembly; + var attributes = assembly.GetCustomAttributes( + typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute[]; + + var versionAttribute = attributes.Length == 0 ? + assembly.GetName().Version.ToString() : + attributes[0].InformationalVersion; + + return versionAttribute; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationResult.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationResult.cs new file mode 100644 index 0000000000..8588499317 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationResult.cs @@ -0,0 +1,22 @@ +// 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.Collections.Generic; +using Microsoft.AspNetCore.Razor; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public class PrecompilationResult + { + public string OutputPath { get; set; } + + public List RazorErrors { get; } = new List(); + + public List RoslynErrors { get; } = new List(); + + public bool Success => (RazorErrors.Count == 0) && (RoslynErrors.Count == 0); + + public List CompileOutputs { get; } = new List(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileCommandBase.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileCommandBase.cs new file mode 100644 index 0000000000..98df4e5f1b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileCommandBase.cs @@ -0,0 +1,184 @@ +// 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.IO; +using System.Linq; +using Microsoft.DotNet.InternalAbstractions; +using Microsoft.DotNet.ProjectModel; +using Microsoft.Extensions.CommandLineUtils; +using NuGet.Frameworks; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public abstract class PrecompileCommandBase + { + protected static readonly string ModifiedAssemblyExtension = ".precompiled-razor"; + + protected CommandOption OutputPathOption { get; set; } + + protected CommandArgument ProjectArgument { get; set; } + + protected CommandOption FrameworkOption { get; set; } + + protected CommandOption ConfigurationOption { get; set; } + + protected CommandOption ConfigureCompilationType { get; set; } + + protected CommandOption ContentRootOption { get; set; } + + protected CommandOption GeneratePdbOption { get; set; } + + protected string ProjectPath { get; private set; } + + protected NuGetFramework TargetFramework { get; private set; } + + protected string Configuration { get; private set; } + + public static void Register(CommandLineApplication app) where + TPrecompileCommandBase : PrecompileCommandBase, new() + { + var command = new TPrecompileCommandBase(); + command.Configure(app); + } + + protected virtual void Configure(CommandLineApplication app) + { + app.Description = "Precompiles an application."; + app.HelpOption("-?|-h|--help"); + ProjectArgument = app.Argument( + "project", + "The path to the project (project folder or project.json) with precompilation."); + FrameworkOption = app.Option( + "-f|--framework", + "Target Framework", + CommandOptionType.SingleValue); + OutputPathOption = app.Option( + "-o|--output", + "The output (bin or publish) directory.", + CommandOptionType.SingleValue); + + ConfigurationOption = app.Option( + "-c|--configuration", + "Configuration", + CommandOptionType.SingleValue); + + ConfigureCompilationType = app.Option( + "--configure-compilation-type", + "Type with Configure method", + CommandOptionType.SingleValue); + + ContentRootOption = app.Option( + "--content-root", + "The application's content root.", + CommandOptionType.SingleValue); + + GeneratePdbOption = app.Option( + "--generate-pdbs", + "Generate pdbs for views.", + CommandOptionType.NoValue); + + app.OnExecute((Func)Execute); + } + + protected int Execute() + { + if (!ParseArguments()) + { + return 1; + } + + return ExecuteCore(); + } + + protected abstract int ExecuteCore(); + + private bool ParseArguments() + { + if (!string.IsNullOrEmpty(ProjectArgument.Value)) + { + ProjectPath = Path.GetFullPath(ProjectArgument.Value); + if (string.Equals(Path.GetFileName(ProjectPath), "project.json", StringComparison.OrdinalIgnoreCase)) + { + ProjectPath = Path.GetDirectoryName(ProjectPath); + } + + if (!Directory.Exists(ProjectPath)) + { + Console.Error.WriteLine($"Error: Could not find directory {ProjectPath}"); + return false; + } + } + else + { + ProjectPath = Directory.GetCurrentDirectory(); + } + + if (!FrameworkOption.HasValue()) + { + var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); + var projectContexts = workspace.GetProjectContextCollection(ProjectPath) + .FrameworkOnlyContexts + .ToList(); + + if (projectContexts.Count == 0) + { + Console.Error.WriteLine($"Error: Project at {ProjectPath} does not have any specified frameworks."); + return false; + } + else if (projectContexts.Count > 1) + { + Console.Error.WriteLine($"Error: Project at {ProjectPath} supports multiple framework. " + + $"Specify a framework using {FrameworkOption.Template}."); + return false; + } + + TargetFramework = projectContexts[0].TargetFramework; + } + else + { + TargetFramework = NuGetFramework.Parse(FrameworkOption.Value()); + } + + if (ConfigurationOption.HasValue()) + { + Configuration = ConfigurationOption.Value(); + } + else + { + Configuration = "Debug"; + } + + return true; + } + + protected string GetBuildOutputPath() + { + if (OutputPathOption.HasValue()) + { + return OutputPathOption.Value(); + } + + var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); + var projectContext = workspace.GetProjectContext( + ProjectPath, + TargetFramework); + var runtimeContext = workspace.GetRuntimeContext( + projectContext, + RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers()); + + var outputs = runtimeContext.GetOutputPaths(Configuration); + string outputPath; + if (!string.IsNullOrEmpty(runtimeContext.RuntimeIdentifier)) + { + outputPath = outputs.RuntimeOutputPath; + } + else + { + outputPath = outputs.CompilationOutputPath; + } + + return outputPath; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileRunCommand.cs new file mode 100644 index 0000000000..fdbd7bb754 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileRunCommand.cs @@ -0,0 +1,193 @@ +// 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.Diagnostics; +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Razor.CodeGenerators; +using Microsoft.Cci; +using Microsoft.Cci.MutableCodeModel; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.ObjectPool; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public class PrecompileRunCommand : PrecompileCommandBase + { + private const string ConfigureMvcMethod = "ConfigureMvc"; + private const string PrecompiledAssemblyPrefix = "PrecompiledRazor."; + + private IMvcRazorHost Host { get; set; } + + private IRoslynCompilationService CompilationService { get; set; } + + private Action ConfigureMvcAction { get; set; } + + protected override int ExecuteCore() + { + ParseArguments(); + + var services = ConfigureDefaultServices(); + Host = services.GetRequiredService(); + var fileProvider = services.GetRequiredService().FileProvider; + CompilationService = services.GetRequiredService() as IRoslynCompilationService; + if (CompilationService == null) + { + Console.Error.WriteLine( + $"An {typeof(ICompilationService)} of type {typeof(IRoslynCompilationService)} " + + "is required for Razor precompilation."); + } + + var result = CompileViews(fileProvider); + if (!result.Success) + { + foreach (var error in result.RazorErrors) + { + Console.Error.WriteLine($"{error.Location.FilePath} ({error.Location.LineIndex}): {error.Message}"); + } + + foreach (var error in result.RoslynErrors) + { + Console.Error.WriteLine(CSharpDiagnosticFormatter.Instance.Format(error)); + } + + return 1; + } + + var outputPath = GetBuildOutputPath(); + UpdateAssembly(outputPath, result); + return 0; + } + + private bool ParseArguments() + { + if (ConfigureCompilationType.HasValue()) + { + var type = Type.GetType(ConfigureCompilationType.Value()); + if (type == null) + { + Console.Error.WriteLine($"Unable to find type '{type}."); + return false; + } + + var configureMethod = type?.GetMethod(ConfigureMvcMethod, BindingFlags.Public | BindingFlags.Static); + if (configureMethod == null) + { + Console.Error.WriteLine($"Could not find a method named {ConfigureMvcMethod} on {type}."); + return false; + } + + ConfigureMvcAction = (Action)configureMethod.CreateDelegate( + typeof(Action), + target: null); + } + + return true; + } + + private void UpdateAssembly(string outputPath, PrecompilationResult precompilationResult) + { + var host = new PeReader.DefaultHost(); + + var assemblyPath = Path.Combine(outputPath, Path.GetFileName(ProjectPath) + ".dll"); + var assembly = host.LoadUnitFrom(assemblyPath) as IAssembly; + assembly = new MetadataDeepCopier(host).Copy(assembly); + + var rewriters = new MetadataRewriter[] + { + new AddResourcesRewriter(host, precompilationResult.CompileOutputs), + new RemoveStrongNameRewriter(host) + }; + + var updatedAssembly = assembly; + foreach (var rewriter in rewriters) + { + updatedAssembly = rewriter.Rewrite(updatedAssembly); + } + + using (var stream = File.OpenWrite(Path.ChangeExtension(assemblyPath, ModifiedAssemblyExtension))) + { + PeWriter.WritePeToStream(updatedAssembly, host, stream); + } + } + + private PrecompilationResult CompileViews(IFileProvider fileProvider) + { + var precompilationResult = new PrecompilationResult(); + foreach (var fileInfo in FileProviderUtilities.GetRazorFiles(fileProvider)) + { + CompileView(precompilationResult, fileInfo); + } + + return precompilationResult; + } + + private void CompileView(PrecompilationResult result, RelativeFileInfo fileInfo) + { + GeneratorResults generatorResults; + using (var fileStream = fileInfo.FileInfo.CreateReadStream()) + { + generatorResults = Host.GenerateCode(fileInfo.RelativePath, fileStream); + } + + if (!generatorResults.Success) + { + result.RazorErrors.AddRange(generatorResults.ParserErrors); + return; + } + + var compileOutputs = new CompileOutputs(fileInfo.RelativePath, GeneratePdbOption.HasValue()); + var emitResult = CompilationService.EmitAssembly( + PrecompiledAssemblyPrefix + Guid.NewGuid(), + generatorResults.GeneratedCode, + compileOutputs.AssemblyStream, + compileOutputs.PdbStream); + + if (!emitResult.Success) + { + result.RoslynErrors.AddRange(emitResult.Diagnostics); + compileOutputs.Dispose(); + } + else + { + result.CompileOutputs.Add(compileOutputs); + } + } + + private IServiceProvider ConfigureDefaultServices() + { + var services = new ServiceCollection(); + + var applicationName = Path.GetFileName(ProjectPath.TrimEnd('/')); + var contentRoot = ContentRootOption.HasValue() + ? ContentRootOption.Value() + : Directory.GetCurrentDirectory(); + var hostingEnvironment = new HostingEnvironment + { + ApplicationName = applicationName, + WebRootFileProvider = new PhysicalFileProvider(ProjectPath), + ContentRootFileProvider = new PhysicalFileProvider(contentRoot), + ContentRootPath = contentRoot, + }; + var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); + + services + .AddSingleton(hostingEnvironment) + .AddSingleton(diagnosticSource) + .AddLogging() + .AddSingleton(); + + var mvcBuilder = services.AddMvc(); + ConfigureMvcAction?.Invoke(mvcBuilder); + + return services.BuildServiceProvider(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/RemoveStrongNameRewriter.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/RemoveStrongNameRewriter.cs new file mode 100644 index 0000000000..51a61c7e68 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/RemoveStrongNameRewriter.cs @@ -0,0 +1,29 @@ +// 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 Microsoft.Cci; +using Microsoft.Cci.MutableCodeModel; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +{ + public class RemoveStrongNameRewriter : MetadataRewriter + { + public RemoveStrongNameRewriter(IMetadataHost host) + : base(host) + { + } + + public override IAssembly Rewrite(IAssembly assembly) + { + var mutableAssembly = base.Rewrite(assembly) as Assembly; + if (mutableAssembly == null) + { + return assembly; + } + + mutableAssembly.PublicKey = null; + mutableAssembly.StrongNameSigned = false; + return mutableAssembly; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.xproj b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj similarity index 63% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.xproj rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj index 7a89944c5a..f94a824b87 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.xproj +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj @@ -4,17 +4,18 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + + - a8aa326e-8ee8-4f11-b750-23028e0949d7 + ebb55c82-6e61-4a0d-8ad8-86915696e86e + Microsoft.AspNetCore.Mvc.Razor.Precompilation .\obj .\bin\ + v4.6.1 + 2.0 - - - - - \ No newline at end of file + + diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Program.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Program.cs new file mode 100644 index 0000000000..125806a537 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Program.cs @@ -0,0 +1,69 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation +{ + public class Program + { + private readonly static Type ProgramType = typeof(Program); + + public static int Main(string[] args) + { +#if DEBUG + DebugHelper.HandleDebugSwitch(ref args); +#endif + + var app = new PrecompilationApplication(ProgramType); + + EnsureValidDispatchRecipient(ref args); + + PrecompileCommandBase.Register(app); + + return app.Execute(args); + } + + private static void EnsureValidDispatchRecipient(ref string[] args) + { + const string DispatcherVersionArgumentName = "--dispatcher-version"; + + if (!args.Contains(DispatcherVersionArgumentName, StringComparer.OrdinalIgnoreCase)) + { + return; + } + + var dispatcherArgumentIndex = Array.FindIndex( + args, + (value) => string.Equals(value, DispatcherVersionArgumentName, StringComparison.OrdinalIgnoreCase)); + var dispatcherArgumentValueIndex = dispatcherArgumentIndex + 1; + if (dispatcherArgumentValueIndex < args.Length) + { + var dispatcherVersion = args[dispatcherArgumentValueIndex]; + + var thisAssembly = ProgramType.GetTypeInfo().Assembly; + var version = thisAssembly.GetCustomAttribute() + ?.InformationalVersion + ?? thisAssembly.GetName().Version.ToString(); + + if (string.Equals(dispatcherVersion, version, StringComparison.Ordinal)) + { + // Remove dispatcher arguments from + var preDispatcherArgument = args.Take(dispatcherArgumentIndex); + var postDispatcherArgument = args.Skip(dispatcherArgumentIndex + 2); + var newProgramArguments = preDispatcherArgument.Concat(postDispatcherArgument); + args = newProgramArguments.ToArray(); + return; + } + } + + var thisAssemblyName = typeof(Program).GetTypeInfo().Assembly.GetName().Name; + + // Could not validate the dispatcher version. + throw new InvalidOperationException("Could not invoke tool"); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3a67f9108c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.AspNetCore.Mvc.Razor.Precompilation")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ebb55c82-6e61-4a0d-8ad8-86915696e86e")] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/project.json new file mode 100644 index 0000000000..6ee44c64a8 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/project.json @@ -0,0 +1,43 @@ +{ + "version": "1.0.0-*", + "description": "Razor precompilation", + "packOptions": { + "repository": { + "type": "git", + "url": "git://github.com/AspNet/Mvc" + }, + "tags": [ + "cshtml", + "razor", + "precompilation", + "aspnetcore" + ] + }, + "buildOptions": { + "keyFile": "../../tools/Key.snk", + "warningsAsErrors": true, + "emitEntryPoint": true, + "nowarn": [ + "CS1591" + ], + "xmlDoc": true + }, + "dependencies": { + "Microsoft.AspNetCore.Hosting": "1.1.0-*", + "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.Extensions.CommandLineUtils": "1.1.0-*", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.Cci": "4.0.0-rc3-24214-00" + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-*" + } + } + }, + "net46": {} + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IRoslynCompilationService.cs new file mode 100644 index 0000000000..ade4bf4a8e --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IRoslynCompilationService.cs @@ -0,0 +1,28 @@ +// 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.IO; +using Microsoft.CodeAnalysis.Emit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// A that uses Roslyn. + /// + public interface IRoslynCompilationService : ICompilationService + { + /// + /// Produces an from the specified . + /// + /// The assembly name. + /// The content to be compiled. + /// The to write assemblies to. + /// The to write pdbs to. + /// The . + EmitResult EmitAssembly( + string assemblyName, + string compilationContent, + Stream assemblyStream, + Stream pdbStream); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs new file mode 100644 index 0000000000..085ebeb898 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs @@ -0,0 +1,14 @@ +// 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; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class PrecompiledViewsFeature + { + public IDictionary PrecompiledViews { get; } = + new Dictionary(StringComparer.OrdinalIgnoreCase); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index f944874d6f..1dcbfcd6b0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -72,6 +72,11 @@ private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder) { builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); } + + if (!builder.PartManager.FeatureProviders.OfType().Any()) + { + builder.PartManager.FeatureProviders.Add(new PrecompiledViewsFeatureProvider()); + } } /// @@ -165,7 +170,7 @@ internal static void AddRazorViewEngineServices(IServiceCollection services) // creating the singleton RazorViewEngine instance. services.TryAddTransient(); services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddTransient(); // This caches Razor page activation details that are valid for the lifetime of the application. services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompiledAssemblyUtility.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompiledAssemblyUtility.cs new file mode 100644 index 0000000000..97dda92be2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompiledAssemblyUtility.cs @@ -0,0 +1,41 @@ +// 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.IO; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public static class CompiledAssemblyUtility + { + public static Type GetExportedType(Stream assemblyStream, Stream pdbStream) + { + var assembly = +#if NET451 + Assembly.Load(GetBytes(assemblyStream), GetBytes(pdbStream)); +#else + System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, pdbStream); +#endif + return assembly.GetExportedTypes().FirstOrDefault(a => !a.IsNested); + } + + private static byte[] GetBytes(Stream stream) + { + if (stream == null) + { + return null; + } + + var memoryStream = stream as MemoryStream; + if (memoryStream == null) + { + memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + } + + return memoryStream.ToArray(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs index f38ddd4457..527da73747 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs @@ -1,7 +1,7 @@ // 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 Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Mvc.ApplicationParts; namespace Microsoft.AspNetCore.Mvc.Razor.Internal { @@ -13,10 +13,15 @@ public class DefaultCompilerCacheProvider : ICompilerCacheProvider /// /// Initializes a new instance of . /// + /// The /// The . - public DefaultCompilerCacheProvider(IRazorViewEngineFileProviderAccessor fileProviderAccessor) + public DefaultCompilerCacheProvider( + ApplicationPartManager applicationPartManager, + IRazorViewEngineFileProviderAccessor fileProviderAccessor) { - Cache = new CompilerCache(fileProviderAccessor.FileProvider); + var feature = new PrecompiledViewsFeature(); + applicationPartManager.PopulateFeature(feature); + Cache = new CompilerCache(fileProviderAccessor.FileProvider, feature.PrecompiledViews); } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs index 833c541949..daff30e9eb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Text; using System.Threading; using Microsoft.AspNetCore.Diagnostics; @@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal /// /// A type that uses Roslyn to compile C# content. /// - public class DefaultRoslynCompilationService : ICompilationService + public class DefaultRoslynCompilationService : IRoslynCompilationService { // error CS0234: The type or namespace name 'C' does not exist in the namespace 'N' (are you missing // an assembly reference?) @@ -86,7 +85,6 @@ private IList CompilationReferences } } - /// public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationContent) { if (fileInfo == null) @@ -100,37 +98,18 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo } _logger.GeneratedCodeToAssemblyCompilationStart(fileInfo.RelativePath); - var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0; - var assemblyName = Path.GetRandomFileName(); - - var sourceText = SourceText.From(compilationContent, Encoding.UTF8); - var syntaxTree = CSharpSyntaxTree.ParseText( - sourceText, - path: assemblyName, - options: _parseOptions); - - var compilation = CSharpCompilation.Create( - assemblyName, - options: _compilationOptions, - syntaxTrees: new[] { syntaxTree }, - references: CompilationReferences); - - compilation = Rewrite(compilation); - - var compilationContext = new RoslynCompilationContext(compilation); - _compilationCallback(compilationContext); - compilation = compilationContext.Compilation; - using (var assemblyStream = new MemoryStream()) { using (var pdbStream = new MemoryStream()) { - var result = compilation.Emit( + var assemblyName = Path.GetRandomFileName(); + var result = EmitAssembly( + assemblyName, + compilationContent, assemblyStream, - pdbStream, - options: new EmitOptions(debugInformationFormat: _pdbFormat)); + pdbStream); if (!result.Success) { @@ -141,11 +120,8 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo result.Diagnostics); } - assemblyStream.Seek(0, SeekOrigin.Begin); - pdbStream.Seek(0, SeekOrigin.Begin); + var type = CompiledAssemblyUtility.GetExportedType(assemblyStream, pdbStream); - var assembly = LoadStream(assemblyStream, pdbStream); - var type = assembly.GetExportedTypes().FirstOrDefault(a => !a.IsNested); _logger.GeneratedCodeToAssemblyCompilationEnd(fileInfo.RelativePath, startTimestamp); return new CompilationResult(type); @@ -153,6 +129,40 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo } } + /// + public EmitResult EmitAssembly( + string assemblyName, + string compilationContent, + Stream assemblyStream, + Stream pdbStream) + { + var sourceText = SourceText.From(compilationContent, Encoding.UTF8); + var syntaxTree = CSharpSyntaxTree.ParseText( + sourceText, + path: assemblyName, + options: _parseOptions); + + var compilation = CSharpCompilation.Create( + assemblyName, + syntaxTrees: new[] { syntaxTree }, + options: _compilationOptions, + references: CompilationReferences); + + var compilationContext = new RoslynCompilationContext(compilation); + Rewrite(compilationContext); + _compilationCallback(compilationContext); + + var emitResult = compilationContext.Compilation.Emit( + assemblyStream, + pdbStream, + options: new EmitOptions(debugInformationFormat: _pdbFormat)); + + assemblyStream.Seek(0, SeekOrigin.Begin); + pdbStream?.Seek(0, SeekOrigin.Begin); + + return emitResult; + } + /// /// Gets the sequence of instances used for compilation. /// @@ -175,17 +185,9 @@ protected virtual IList GetCompilationReferences() return compilationReferences; } - private Assembly LoadStream(MemoryStream assemblyStream, MemoryStream pdbStream) - { -#if NET451 - return Assembly.Load(assemblyStream.ToArray(), pdbStream.ToArray()); -#else - return System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, pdbStream); -#endif - } - - private CSharpCompilation Rewrite(CSharpCompilation compilation) + private void Rewrite(RoslynCompilationContext compilationContext) { + var compilation = compilationContext.Compilation; var rewrittenTrees = new List(); foreach (var tree in compilation.SyntaxTrees) { @@ -196,7 +198,7 @@ private CSharpCompilation Rewrite(CSharpCompilation compilation) rewrittenTrees.Add(rewrittenTree); } - return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(rewrittenTrees); + compilationContext.Compilation = compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(rewrittenTrees); } // Internal for unit testing diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/PrecompiledViewsFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/PrecompiledViewsFeatureProvider.cs new file mode 100644 index 0000000000..6f92050b80 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/PrecompiledViewsFeatureProvider.cs @@ -0,0 +1,57 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class PrecompiledViewsFeatureProvider : IApplicationFeatureProvider + { + public static readonly string PrecompiledResourcePrefix = "__RazorPrecompiledView__."; + private const string DllExtension = ".dll"; + private const string PdbExtension = ".pdb"; + + public void PopulateFeature(IEnumerable parts, PrecompiledViewsFeature feature) + { + foreach (var assemblyPart in parts.OfType()) + { + AddPrecompiledViews(feature, assemblyPart.Assembly); + } + } + + private void AddPrecompiledViews(PrecompiledViewsFeature feature, Assembly assembly) + { + var resourceNames = new HashSet(assembly.GetManifestResourceNames(), StringComparer.Ordinal); + foreach (var resourceName in resourceNames) + { + if (resourceName.StartsWith(PrecompiledResourcePrefix, StringComparison.Ordinal) && + resourceName.EndsWith(".dll", StringComparison.Ordinal)) + { + var type = ReadResource(assembly, resourceName); + var viewPath = resourceName.Substring( + PrecompiledResourcePrefix.Length, + resourceName.Length - PrecompiledResourcePrefix.Length - DllExtension.Length); + feature.PrecompiledViews[viewPath] = type; + } + } + } + + private Type ReadResource(Assembly assembly, string resourceName) + { + var pdbResourceName = Path.ChangeExtension(resourceName, PdbExtension); + + using (var resourceStream = assembly.GetManifestResourceStream(resourceName)) + { + using (var pdbStream = assembly.GetManifestResourceStream(pdbResourceName)) + { + return CompiledAssemblyUtility.GetExportedType(resourceStream, pdbStream); + } + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index bf8cf2e720..d1756d6a72 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -209,7 +209,8 @@ public void AddMvcTwice_DoesNotAddApplicationFeatureProvidersTwice() feature => Assert.IsType(feature), feature => Assert.IsType(feature), feature => Assert.IsType(feature), - feature => Assert.IsType(feature)); + feature => Assert.IsType(feature), + feature => Assert.IsType(feature)); } [Fact] From 5c66e06cf26a8e48a5ab8e9312b31610b3e71662 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 2 Aug 2016 10:35:28 -0700 Subject: [PATCH 2/7] Changes per PR comments --- Mvc.NoFun.sln | 28 +-- .../ApplicationParts/AssemblyPart.cs | 38 +++- .../IPrecompiledViewsProvider.cs | 12 ++ .../ApplicationParts/PrecompiledViewInfo.cs | 32 +++ .../Internal/AddResourcesRewriter.cs | 31 +-- .../Internal/CommonOptions.cs | 50 +++++ .../Internal/CompileOutputs.cs | 4 +- .../Internal/Constants.cs | 10 + .../Internal/DebugHelper.cs | 2 +- .../Internal/FIleProviderUtilities.cs | 2 +- .../Internal/MvcServicesProvider.cs | 108 ++++++++++ .../Internal/PrecompilationApplication.cs | 11 +- .../Internal/PrecompilationResult.cs | 4 +- .../Internal/PrecompileRunCommand.cs | 142 +++++++++++++ .../Internal/RemoveStrongNameRewriter.cs | 4 +- ...ore.Mvc.Razor.Precompilation.Design.xproj} | 11 +- .../Program.cs | 10 +- .../Properties/AssemblyInfo.cs | 11 + .../project.json | 3 +- .../Internal/PrecompileDispatchCommand.cs | 198 +++++++++++++----- ...tCore.Mvc.Razor.Precompilation.Tools.xproj | 1 - .../Program.cs | 3 +- .../Properties/AssemblyInfo.cs | 11 + .../project.json | 5 +- .../Internal/PrecompileCommandBase.cs | 184 ---------------- .../Internal/PrecompileRunCommand.cs | 193 ----------------- .../Properties/AssemblyInfo.cs | 19 -- .../Compilation/PrecompiledViewsFeature.cs | 2 +- .../PrecompiledViewsFeatureProvider.cs | 38 ++++ .../Internal/DefaultCompilerCacheProvider.cs | 1 + .../DefaultRoslynCompilationService.cs | 2 +- .../PrecompiledViewsFeatureProvider.cs | 57 ----- ...emblyUtility.cs => RazorAssemblyLoader.cs} | 2 +- 33 files changed, 655 insertions(+), 574 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design}/Internal/AddResourcesRewriter.cs (66%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design}/Internal/CompileOutputs.cs (87%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design}/Internal/DebugHelper.cs (91%) rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design}/Internal/FIleProviderUtilities.cs (94%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design}/Internal/PrecompilationApplication.cs (83%) rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design}/Internal/PrecompilationResult.cs (85%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design}/Internal/RemoveStrongNameRewriter.cs (81%) rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation/Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj} (69%) rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design}/Program.cs (92%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs rename src/{Microsoft.AspNetCore.Mvc.Razor.Precompilation => Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design}/project.json (90%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileCommandBase.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileRunCommand.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Internal/PrecompiledViewsFeatureProvider.cs rename src/Microsoft.AspNetCore.Mvc.Razor/Internal/{CompiledAssemblyUtility.cs => RazorAssemblyLoader.cs} (96%) diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index a4b5d15af3..90fabd036d 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -83,7 +83,7 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MvcSandbox", "samples\MvcSa EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools", "src\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj", "{F8BF7D95-0633-407F-BB0B-02563F13C068}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation", "src\Microsoft.AspNetCore.Mvc.Razor.Precompilation\Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj", "{EBB55C82-6E61-4A0D-8AD8-86915696E86E}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design", "src\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj", "{4339FC9B-AEC6-442A-B413-A41555ED76C7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -480,18 +480,18 @@ Global {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Mixed Platforms.Build.0 = Release|Any CPU {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|x86.ActiveCfg = Release|Any CPU {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|x86.Build.0 = Release|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|x86.ActiveCfg = Debug|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Debug|x86.Build.0 = Debug|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|Any CPU.Build.0 = Release|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|x86.ActiveCfg = Release|Any CPU - {EBB55C82-6E61-4A0D-8AD8-86915696E86E}.Release|x86.Build.0 = Release|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|x86.ActiveCfg = Debug|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|x86.Build.0 = Debug|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Any CPU.Build.0 = Release|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|x86.ActiveCfg = Release|Any CPU + {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -531,6 +531,6 @@ Global {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {14ED4476-9F24-4776-8417-EA6927F6C9C9} = {DAAE4C74-D06F-4874-A166-33305D2643CE} {F8BF7D95-0633-407F-BB0B-02563F13C068} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} - {EBB55C82-6E61-4A0D-8AD8-86915696E86E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {4339FC9B-AEC6-442A-B413-A41555ED76C7} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs index 6426753d4b..45112ab492 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Mvc.Core.ApplicationParts; using Microsoft.Extensions.DependencyModel; namespace Microsoft.AspNetCore.Mvc.ApplicationParts @@ -12,8 +14,16 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts /// /// An backed by an . /// - public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider, ICompilationReferencesProvider + public class AssemblyPart : + ApplicationPart, + IApplicationPartTypeProvider, + ICompilationReferencesProvider, + IPrecompiledViewsProvider { + public static readonly string PrecompiledResourcePrefix = "__PrecompiledView__."; + private const string DllExtension = ".dll"; + private const string PdbExtension = ".pdb"; + /// /// Initalizes a new instance. /// @@ -41,6 +51,32 @@ public AssemblyPart(Assembly assembly) /// public IEnumerable Types => Assembly.DefinedTypes; + public IEnumerable PrecompiledViews + { + get + { + var precompiledViews = new List(); + foreach (var resourceName in Assembly.GetManifestResourceNames()) + { + if (resourceName.StartsWith(PrecompiledResourcePrefix, StringComparison.Ordinal) && + resourceName.EndsWith(DllExtension, StringComparison.Ordinal)) + { + var viewPath = resourceName.Substring( + PrecompiledResourcePrefix.Length, + resourceName.Length - PrecompiledResourcePrefix.Length - DllExtension.Length); + + var pdbStreamName = Path.ChangeExtension(viewPath, PdbExtension); + precompiledViews.Add(new PrecompiledViewInfo( + viewPath, + () => Assembly.GetManifestResourceStream(resourceName), + () => Assembly.GetManifestResourceStream(pdbStreamName))); + } + } + + return precompiledViews; + } + } + /// public IEnumerable GetReferencePaths() { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs new file mode 100644 index 0000000000..f3a06097c6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs @@ -0,0 +1,12 @@ +// 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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Core.ApplicationParts +{ + public interface IPrecompiledViewsProvider + { + IEnumerable PrecompiledViews { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs new file mode 100644 index 0000000000..bbc83e0ad7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs @@ -0,0 +1,32 @@ +// 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.IO; + +namespace Microsoft.AspNetCore.Mvc.Core.ApplicationParts +{ + public class PrecompiledViewInfo + { + public PrecompiledViewInfo(string path, Func assemblyStreamFactory) + : this(path, assemblyStreamFactory, pdbStreamFactory: null) + { + } + + public PrecompiledViewInfo( + string path, + Func assemblyStreamFactory, + Func pdbStreamFactory) + { + Path = path; + AssemblyStreamFactory = assemblyStreamFactory; + PdbStreamFactory = pdbStreamFactory; + } + + public string Path { get; } + + public Func AssemblyStreamFactory { get; } + + public Func PdbStreamFactory { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/AddResourcesRewriter.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/AddResourcesRewriter.cs similarity index 66% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/AddResourcesRewriter.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/AddResourcesRewriter.cs index 78dde6fa68..ccecda6d05 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/AddResourcesRewriter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/AddResourcesRewriter.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Cci; using Microsoft.Cci.MutableCodeModel; -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class AddResourcesRewriter : MetadataRewriter { @@ -23,7 +23,7 @@ public AddResourcesRewriter(IMetadataHost host, List outputs) public override IAssembly Rewrite(IAssembly assembly) { _assembly = assembly; - return _assembly; + return base.Rewrite(assembly); } public override List Rewrite(List resourceReferences) @@ -35,7 +35,7 @@ public override List Rewrite(List resour foreach (var output in _outputs) { - var dllResource = $"{PrecompiledViewsFeatureProvider.PrecompiledResourcePrefix}{output.RelativePath}.dll"; + var dllResource = $"{AssemblyPart.PrecompiledResourcePrefix}{output.RelativePath}.dll"; resourceReferences.Add(new ResourceReference { Name = host.NameTable.GetNameFor(dllResource), @@ -49,19 +49,22 @@ public override List Rewrite(List resour } }); - var pdbResource = $"{PrecompiledViewsFeatureProvider.PrecompiledResourcePrefix}{output.RelativePath}.pdb"; - resourceReferences.Add(new ResourceReference + if (output.PdbStream != null) { - Name = host.NameTable.GetNameFor(pdbResource), - DefiningAssembly = _assembly, - IsPublic = true, - Resource = new Resource + var pdbResource = $"{AssemblyPart.PrecompiledResourcePrefix}{output.RelativePath}.pdb"; + resourceReferences.Add(new ResourceReference { - Data = output.PdbStream.ToArray().ToList(), - IsPublic = true, + Name = host.NameTable.GetNameFor(pdbResource), DefiningAssembly = _assembly, - } - }); + IsPublic = true, + Resource = new Resource + { + Data = output.PdbStream.ToArray().ToList(), + IsPublic = true, + DefiningAssembly = _assembly, + } + }); + } } return resourceReferences; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs new file mode 100644 index 0000000000..fc2e25c293 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs @@ -0,0 +1,50 @@ +// 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 Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class CommonOptions + { + public CommandOption OutputPathOption { get; private set; } + + public CommandArgument ProjectArgument { get; private set; } + + public CommandOption ConfigureCompilationType { get; private set; } + + public CommandOption ContentRootOption { get; private set; } + + public CommandOption GeneratePdbOption { get; private set; } + + public void Configure(CommandLineApplication app) + { + app.Description = "Precompiles an application."; + app.HelpOption("-?|-h|--help"); + + ProjectArgument = app.Argument( + "project", + "The path to the project (project folder or project.json) with precompilation."); + + OutputPathOption = app.Option( + "-o|--output", + "The output (bin or publish) directory.", + CommandOptionType.SingleValue); + + ConfigureCompilationType = app.Option( + "--configure-compilation-type", + "Type with Configure method", + CommandOptionType.SingleValue); + + ContentRootOption = app.Option( + "--content-root", + "The application's content root.", + CommandOptionType.SingleValue); + + GeneratePdbOption = app.Option( + "--generate-pdbs", + "Generate pdbs for views.", + CommandOptionType.NoValue); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/CompileOutputs.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CompileOutputs.cs similarity index 87% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/CompileOutputs.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CompileOutputs.cs index c64988514c..5f88dd8c6a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/CompileOutputs.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CompileOutputs.cs @@ -4,7 +4,7 @@ using System; using System.IO; -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class CompileOutputs : IDisposable { @@ -23,7 +23,7 @@ public CompileOutputs(string relativePath, bool generatePdbs) public void Dispose() { AssemblyStream.Dispose(); - PdbStream.Dispose(); + PdbStream?.Dispose(); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs new file mode 100644 index 0000000000..af11b63192 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs @@ -0,0 +1,10 @@ +// 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. + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public static class Constants + { + public static readonly string ModifiedAssemblyExtension = ".precompiled-razor"; + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/DebugHelper.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/DebugHelper.cs similarity index 91% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/DebugHelper.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/DebugHelper.cs index ec05c44fc7..dde371bb33 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/DebugHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/DebugHelper.cs @@ -6,7 +6,7 @@ using System.Diagnostics; using System.Linq; -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public static class DebugHelper { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/FIleProviderUtilities.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/FIleProviderUtilities.cs similarity index 94% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/FIleProviderUtilities.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/FIleProviderUtilities.cs index 83754ad305..3bf002c764 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/FIleProviderUtilities.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/FIleProviderUtilities.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.Extensions.FileProviders; -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public static class FileProviderUtilities { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs new file mode 100644 index 0000000000..649ea4bff4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs @@ -0,0 +1,108 @@ +// 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.Diagnostics; +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.ObjectPool; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class MvcServicesProvider + { + private const string ConfigureMvcMethod = "ConfigureMvc"; + private readonly string _projectPath; + private readonly string _contentRoot; + + public MvcServicesProvider( + string projectPath, + string contentRoot, + string configureCompilationType) + { + _projectPath = projectPath; + _contentRoot = contentRoot; + + var configureCompilationAction = GetConfigureCompilationAction(configureCompilationType); + var serviceProvider = GetProvider(configureCompilationAction); + + Host = serviceProvider.GetRequiredService(); + CompilationService = serviceProvider.GetRequiredService() as IRoslynCompilationService; + if (CompilationService == null) + { + throw new InvalidOperationException( + $"An {typeof(ICompilationService)} of type {typeof(IRoslynCompilationService)} " + + "is required for Razor precompilation."); + } + + FileProvider = serviceProvider.GetRequiredService().FileProvider; + } + + public IMvcRazorHost Host { get; } + + public IRoslynCompilationService CompilationService { get; } + + public IFileProvider FileProvider { get; } + + private static Action GetConfigureCompilationAction(string configureCompilationType) + { + if (!string.IsNullOrEmpty(configureCompilationType)) + { + var type = Type.GetType(configureCompilationType); + if (type == null) + { + throw new InvalidOperationException($"Unable to find type '{type}."); + } + + var configureMethod = type.GetMethod(ConfigureMvcMethod, BindingFlags.Public | BindingFlags.Static); + if (configureMethod == null) + { + throw new InvalidOperationException($"Could not find a method named {ConfigureMvcMethod} on {type}."); + } + + return (Action)configureMethod.CreateDelegate( + typeof(Action), + target: null); + } + + // Todo: Add support for assembly scanning. + return null; + } + + private IServiceProvider GetProvider(Action configureBuilder) + { + var services = new ServiceCollection(); + var applicationName = Path.GetFileName(_projectPath.TrimEnd(Path.DirectorySeparatorChar)); + var hostingEnvironment = new HostingEnvironment + { + ApplicationName = applicationName, + WebRootFileProvider = new PhysicalFileProvider(_projectPath), + ContentRootFileProvider = new PhysicalFileProvider(_contentRoot), + ContentRootPath = _contentRoot, + }; + var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); + + services + .AddSingleton(hostingEnvironment) + .AddSingleton(diagnosticSource) + .AddLogging() + .AddSingleton(); + + var mvcCoreBuilder = services + .AddMvcCore() + .AddRazorViewEngine(); + + var mvcBuilder = new MvcBuilder(mvcCoreBuilder.Services, mvcCoreBuilder.PartManager); + configureBuilder?.Invoke(mvcBuilder); + + return mvcBuilder.Services.BuildServiceProvider(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationApplication.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationApplication.cs similarity index 83% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationApplication.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationApplication.cs index eee30b916a..72e4861055 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationApplication.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationApplication.cs @@ -5,7 +5,7 @@ using System.Reflection; using Microsoft.Extensions.CommandLineUtils; -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class PrecompilationApplication : CommandLineApplication { @@ -15,9 +15,9 @@ public PrecompilationApplication(Type callingType) { _callingType = callingType; - Name = "razor-tooling"; - FullName = "Microsoft Razor Tooling Utility"; - Description = "Resolves Razor tooling specific information."; + Name = "razor-precompile"; + FullName = "Microsoft Razor Precompilation Utility"; + Description = "Precompiles Razor views."; ShortVersionGetter = GetInformationalVersion; HelpOption("-?|-h|--help"); @@ -38,6 +38,9 @@ public PrecompilationApplication(Type callingType) catch (Exception ex) { Console.Error.WriteLine(ex.Message); +#if DEBUG + Console.Error.WriteLine(ex); +#endif return 1; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationResult.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationResult.cs similarity index 85% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationResult.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationResult.cs index 8588499317..b8139017d8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompilationResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationResult.cs @@ -5,12 +5,10 @@ using Microsoft.AspNetCore.Razor; using Microsoft.CodeAnalysis; -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class PrecompilationResult { - public string OutputPath { get; set; } - public List RazorErrors { get; } = new List(); public List RoslynErrors { get; } = new List(); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs new file mode 100644 index 0000000000..5e233d5ff8 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs @@ -0,0 +1,142 @@ +// 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.IO; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Razor.CodeGenerators; +using Microsoft.Cci; +using Microsoft.Cci.MutableCodeModel; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class PrecompileRunCommand + { + private const string PrecompiledAssemblyPrefix = "PrecompiledRazor."; + + private CommandOption OutputFilePath { get; set; } + + private MvcServicesProvider ServicesProvider { get; set; } + + private CommonOptions Options { get; } = new CommonOptions(); + + private string ProjectPath { get; set; } + + public void Configure(CommandLineApplication app) + { + Options.Configure(app); + app.OnExecute(() => Execute()); + } + + private int Execute() + { + ParseArguments(); + + ServicesProvider = new MvcServicesProvider( + ProjectPath, + Options.ContentRootOption.Value(), + Options.ConfigureCompilationType.Value()); + + var result = CompileViews(); + if (!result.Success) + { + foreach (var error in result.RazorErrors) + { + Console.Error.WriteLine($"{error.Location.FilePath} ({error.Location.LineIndex}): {error.Message}"); + } + + foreach (var error in result.RoslynErrors) + { + Console.Error.WriteLine(CSharpDiagnosticFormatter.Instance.Format(error)); + } + + return 1; + } + + var outputPath = Options.OutputPathOption.Value(); + UpdateAssembly(outputPath, result); + return 0; + } + + private void ParseArguments() + { + ProjectPath = Options.ProjectArgument.Value; + if (string.IsNullOrEmpty(ProjectPath)) + { + throw new ArgumentException("Project path not specified."); + } + } + + private void UpdateAssembly(string outputPath, PrecompilationResult precompilationResult) + { + var host = new PeReader.DefaultHost(); + + var assemblyPath = Path.Combine(outputPath, Path.GetFileName(ProjectPath) + ".dll"); + var assembly = host.LoadUnitFrom(assemblyPath) as Cci.IAssembly; + assembly = new MetadataDeepCopier(host).Copy(assembly); + + var rewriters = new MetadataRewriter[] + { + new AddResourcesRewriter(host, precompilationResult.CompileOutputs), + new RemoveStrongNameRewriter(host) + }; + + var updatedAssembly = assembly; + foreach (var rewriter in rewriters) + { + updatedAssembly = rewriter.Rewrite(updatedAssembly); + } + + var outputFilePath = Path.ChangeExtension(assemblyPath, Constants.ModifiedAssemblyExtension); + using (var stream = File.OpenWrite(outputFilePath)) + { + PeWriter.WritePeToStream(updatedAssembly, host, stream); + } + } + + private PrecompilationResult CompileViews() + { + var precompilationResult = new PrecompilationResult(); + foreach (var fileInfo in FileProviderUtilities.GetRazorFiles(ServicesProvider.FileProvider)) + { + CompileView(precompilationResult, fileInfo); + } + + return precompilationResult; + } + + private void CompileView(PrecompilationResult result, RelativeFileInfo fileInfo) + { + GeneratorResults generatorResults; + using (var fileStream = fileInfo.FileInfo.CreateReadStream()) + { + generatorResults = ServicesProvider.Host.GenerateCode(fileInfo.RelativePath, fileStream); + } + + if (!generatorResults.Success) + { + result.RazorErrors.AddRange(generatorResults.ParserErrors); + return; + } + + var compileOutputs = new CompileOutputs(fileInfo.RelativePath, Options.GeneratePdbOption.HasValue()); + var emitResult = ServicesProvider.CompilationService.EmitAssembly( + PrecompiledAssemblyPrefix + Guid.NewGuid(), + generatorResults.GeneratedCode, + compileOutputs.AssemblyStream, + compileOutputs.PdbStream); + + if (!emitResult.Success) + { + result.RoslynErrors.AddRange(emitResult.Diagnostics); + compileOutputs.Dispose(); + } + else + { + result.CompileOutputs.Add(compileOutputs); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/RemoveStrongNameRewriter.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/RemoveStrongNameRewriter.cs similarity index 81% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/RemoveStrongNameRewriter.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/RemoveStrongNameRewriter.cs index 51a61c7e68..98c7aea5e0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/RemoveStrongNameRewriter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/RemoveStrongNameRewriter.cs @@ -4,7 +4,7 @@ using Microsoft.Cci; using Microsoft.Cci.MutableCodeModel; -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class RemoveStrongNameRewriter : MetadataRewriter { @@ -15,7 +15,7 @@ public RemoveStrongNameRewriter(IMetadataHost host) public override IAssembly Rewrite(IAssembly assembly) { - var mutableAssembly = base.Rewrite(assembly) as Assembly; + var mutableAssembly = base.Rewrite(assembly) as Cci.MutableCodeModel.Assembly; if (mutableAssembly == null) { return assembly; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj similarity index 69% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj index f94a824b87..253d7491f8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Microsoft.AspNetCore.Mvc.Razor.Precompilation.xproj +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj @@ -1,21 +1,18 @@  - + - 14.0 + 14.0.25420 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - ebb55c82-6e61-4a0d-8ad8-86915696e86e - Microsoft.AspNetCore.Mvc.Razor.Precompilation + 4339fc9b-aec6-442a-b413-a41555ed76c7 .\obj .\bin\ - v4.6.1 2.0 - + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Program.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Program.cs similarity index 92% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Program.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Program.cs index 125806a537..ee05cf6b9c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Program.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Program.cs @@ -4,9 +4,9 @@ using System; using System.Linq; using System.Reflection; -using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal; +using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal; -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design { public class Program { @@ -18,12 +18,10 @@ public static int Main(string[] args) DebugHelper.HandleDebugSwitch(ref args); #endif - var app = new PrecompilationApplication(ProgramType); - EnsureValidDispatchRecipient(ref args); - PrecompileCommandBase.Register(app); - + var app = new PrecompilationApplication(ProgramType); + new PrecompileRunCommand().Configure(app); return app.Execute(args); } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2dc4003a17 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// 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.Reflection; +using System.Resources; + +[assembly: AssemblyMetadata("Serviceable", "True")] +[assembly: NeutralResourcesLanguage("en-us")] +[assembly: AssemblyCompany("Microsoft Corporation.")] +[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyProduct("Microsoft ASP.NET Core")] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json similarity index 90% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/project.json rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json index 6ee44c64a8..65e67a8515 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/project.json +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json @@ -24,9 +24,8 @@ }, "dependencies": { "Microsoft.AspNetCore.Hosting": "1.1.0-*", - "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.AspNetCore.Mvc.Razor": "1.1.0-*", "Microsoft.Extensions.CommandLineUtils": "1.1.0-*", - "Microsoft.DotNet.ProjectModel": "1.0.0-*", "Microsoft.Cci": "4.0.0-rc3-24214-00" }, "frameworks": { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs index 843a01e2d6..46230f0739 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.InternalAbstractions; using Microsoft.DotNet.ProjectModel; @@ -15,21 +17,55 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal { - public class PrecompileDispatchCommand : PrecompileCommandBase + public class PrecompileDispatchCommand { + private CommonOptions Options { get; } = new CommonOptions(); + private CommandOption NoBuildOption { get; set; } - protected override void Configure(CommandLineApplication app) + public CommandOption FrameworkOption { get; set; } + + public CommandOption ConfigurationOption { get; set; } + + private NuGetFramework TargetFramework { get; set; } + + private ProjectContext RuntimeContext { get; set; } + + + private string OutputPath { get; set; } + + private string ProjectPath { get; set; } + + private string Configuration { get; set; } + + public void Configure(CommandLineApplication app) { - base.Configure(app); + Options.Configure(app); + FrameworkOption = app.Option( + "-f|--framework", + "Target Framework", + CommandOptionType.SingleValue); + + ConfigurationOption = app.Option( + "-c|--configuration", + "Configuration", + CommandOptionType.SingleValue); + NoBuildOption = app.Option( "--no-build", "Do not build project before compiling views.", CommandOptionType.NoValue); + app.OnExecute(() => Execute()); } - protected override int ExecuteCore() + private int Execute() { + ProjectPath = GetProjectPath(); + Configuration = ConfigurationOption.Value() ?? DotNet.Cli.Utils.Constants.DefaultConfiguration; + TargetFramework = GetTargetFramework(); + RuntimeContext = GetRuntimeContext(); + OutputPath = GetBuildOutputPath(); + if (!NoBuildOption.HasValue()) { var exitCode = BuildProject(); @@ -42,44 +78,29 @@ protected override int ExecuteCore() var dispatchArgs = new List { ProjectPath, - "--framework", - TargetFramework.ToString(), - "--configuration", - Configuration, + "--output", + OutputPath, + "--content-root", + Options.ContentRootOption.Value() ?? Directory.GetCurrentDirectory(), }; - string outputPath = null; - if (OutputPathOption.HasValue()) - { - outputPath = OutputPathOption.Value(); - - dispatchArgs.Add("--output"); - dispatchArgs.Add(outputPath); - } - - if (ConfigureCompilationType.HasValue()) + if (Options.ConfigureCompilationType.HasValue()) { dispatchArgs.Add("--configure-compilation-type"); - dispatchArgs.Add(ConfigureCompilationType.Value()); + dispatchArgs.Add(Options.ConfigureCompilationType.Value()); } - if (ContentRootOption.HasValue()) - { - dispatchArgs.Add("--content-root"); - dispatchArgs.Add(ContentRootOption.Value()); - } - - if (GeneratePdbOption.HasValue()) + if (Options.GeneratePdbOption.HasValue()) { dispatchArgs.Add("--generate-pdbs"); } - var toolName = typeof(Precompilation.Program).GetTypeInfo().Assembly.GetName().Name; + var toolName = typeof(Design.Program).GetTypeInfo().Assembly.GetName().Name; var dispatchCommand = DotnetToolDispatcher.CreateDispatchCommand( dispatchArgs, TargetFramework, Configuration, - outputPath: outputPath, + outputPath: OutputPath, buildBasePath: null, projectDirectory: ProjectPath, toolName: toolName); @@ -90,64 +111,129 @@ protected override int ExecuteCore() .Execute() .ExitCode; - var buildOutputPath = GetBuildOutputPath(); - var updatedBinary = Directory.EnumerateFiles(buildOutputPath, $"*{ModifiedAssemblyExtension}").FirstOrDefault(); - if (updatedBinary == null) + if (commandExitCode == 0) + { + var outputAssembly = RuntimeContext.GetOutputPaths(Configuration).RuntimeFiles.Assembly; + var modifiedAssembly = Path.ChangeExtension(outputAssembly, Design.Internal.Constants.ModifiedAssemblyExtension); + + if (File.Exists(outputAssembly)) + { + File.Copy(modifiedAssembly, outputAssembly, overwrite: true); + File.Delete(modifiedAssembly); + } + } + + return commandExitCode; + } + + private string GetProjectPath() + { + string projectPath; + if (!string.IsNullOrEmpty(Options.ProjectArgument.Value)) { - Console.Error.WriteLine("Unable to find modified binary with precompiled views."); - return 1; + projectPath = Path.GetFullPath(Options.ProjectArgument.Value); + if (string.Equals(Path.GetFileName(ProjectPath), "project.json", StringComparison.OrdinalIgnoreCase)) + { + projectPath = Path.GetDirectoryName(ProjectPath); + } + + if (!Directory.Exists(ProjectPath)) + { + throw new InvalidOperationException($"Error: Could not find directory {ProjectPath}"); + } + } else { - File.Copy( - updatedBinary, - Path.ChangeExtension(updatedBinary, ".dll"), - overwrite: true); - File.Delete(updatedBinary); + projectPath = Directory.GetCurrentDirectory(); } - return commandExitCode; + return projectPath; } - private int BuildProject() + private ProjectContext GetRuntimeContext() { var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); - var projectContext = workspace.GetProjectContext(ProjectPath, NuGetFramework.Parse(FrameworkOption.Value())); + var projectContext = workspace.GetProjectContext(ProjectPath, TargetFramework); if (projectContext == null) { - Console.Error.WriteLine($"Project '{ProjectPath}' does not support framework: {FrameworkOption.Value()}"); - return 1; + Debug.Assert(FrameworkOption.HasValue()); + throw new InvalidOperationException($"Project '{ProjectPath}' does not support framework: {FrameworkOption.Value()}"); } - projectContext = workspace.GetRuntimeContext( + return workspace.GetRuntimeContext( projectContext, RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers()); + } + private int BuildProject() + { var arguments = new List { ProjectPath, "--framework", - projectContext.TargetFramework.ToString(), + RuntimeContext.TargetFramework.ToString(), + "--configuration", + Configuration, + "--output", + OutputPath }; - if (ConfigurationOption.HasValue()) + return Command.CreateDotNet("build", arguments, RuntimeContext.TargetFramework, Configuration) + .ForwardStdErr(Console.Error) + .ForwardStdOut(Console.Out) + .Execute() + .ExitCode; + } + + private NuGetFramework GetTargetFramework() + { + if (!FrameworkOption.HasValue()) { - arguments.Add("--configuration"); - arguments.Add(ConfigurationOption.Value()); + var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); + var projectContexts = workspace.GetProjectContextCollection(ProjectPath) + .FrameworkOnlyContexts + .ToList(); + + if (projectContexts.Count == 0) + { + throw new Exception($"Error: Project at {ProjectPath} does not have any specified frameworks."); + + } + else if (projectContexts.Count > 1) + { + throw new Exception($"Error: Project at {ProjectPath} supports multiple framework. " + + $"Specify a framework using {FrameworkOption.Template}."); + } + + return projectContexts[0].TargetFramework; } + else + { + return NuGetFramework.Parse(FrameworkOption.Value()); + } + } - if (OutputPathOption.HasValue()) + private string GetBuildOutputPath() + { + if (Options.OutputPathOption.HasValue()) { - arguments.Add("--output"); - arguments.Add(OutputPathOption.Value()); + return Options.OutputPathOption.Value(); } - return Command.CreateDotNet("build", arguments, NuGetFramework.Parse(FrameworkOption.Value()), ConfigurationOption.Value()) - .ForwardStdErr(Console.Error) - .ForwardStdOut(Console.Out) - .Execute() - .ExitCode; + var outputs = RuntimeContext.GetOutputPaths(Configuration); + string outputPath; + if (!string.IsNullOrEmpty(RuntimeContext.RuntimeIdentifier)) + { + outputPath = outputs.RuntimeOutputPath; + } + else + { + outputPath = outputs.CompilationOutputPath; + } + + return outputPath; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj index 9f16787ad9..6b5961a59f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj @@ -7,7 +7,6 @@ f8bf7d95-0633-407f-bb0b-02563f13c068 - Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools .\obj .\bin\ diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs index cf3700482e..3ca5029d70 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs @@ -1,6 +1,7 @@ // 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 Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal; using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal; namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools @@ -14,7 +15,7 @@ public static int Main(string[] args) #endif var app = new PrecompilationApplication(typeof(Program)); - PrecompileCommandBase.Register(app); + new PrecompileDispatchCommand().Configure(app); return app.Execute(args); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2dc4003a17 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// 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.Reflection; +using System.Resources; + +[assembly: AssemblyMetadata("Serviceable", "True")] +[assembly: NeutralResourcesLanguage("en-us")] +[assembly: AssemblyCompany("Microsoft Corporation.")] +[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyProduct("Microsoft ASP.NET Core")] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json index cb00707f37..18b4bc1cec 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json @@ -21,14 +21,13 @@ "nowarn": [ "CS1591" ], + "xmlDoc": true }, "dependencies": { - "Microsoft.AspNetCore.Hosting": "1.1.0-*", - "Microsoft.AspNetCore.Mvc": "1.1.0-*", + "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design": "1.0.0-*", "Microsoft.DotNet.Cli.Utils": "1.0.0-*", "Microsoft.Extensions.CommandLineUtils": "1.1.0-*", - "Microsoft.AspNetCore.Mvc.Razor.Precompilation": "1.0.0-*", "Microsoft.Extensions.DotnetToolDispatcher.Sources": { "type": "build", "version": "1.1.0-*" diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileCommandBase.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileCommandBase.cs deleted file mode 100644 index 98df4e5f1b..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileCommandBase.cs +++ /dev/null @@ -1,184 +0,0 @@ -// 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.IO; -using System.Linq; -using Microsoft.DotNet.InternalAbstractions; -using Microsoft.DotNet.ProjectModel; -using Microsoft.Extensions.CommandLineUtils; -using NuGet.Frameworks; - -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal -{ - public abstract class PrecompileCommandBase - { - protected static readonly string ModifiedAssemblyExtension = ".precompiled-razor"; - - protected CommandOption OutputPathOption { get; set; } - - protected CommandArgument ProjectArgument { get; set; } - - protected CommandOption FrameworkOption { get; set; } - - protected CommandOption ConfigurationOption { get; set; } - - protected CommandOption ConfigureCompilationType { get; set; } - - protected CommandOption ContentRootOption { get; set; } - - protected CommandOption GeneratePdbOption { get; set; } - - protected string ProjectPath { get; private set; } - - protected NuGetFramework TargetFramework { get; private set; } - - protected string Configuration { get; private set; } - - public static void Register(CommandLineApplication app) where - TPrecompileCommandBase : PrecompileCommandBase, new() - { - var command = new TPrecompileCommandBase(); - command.Configure(app); - } - - protected virtual void Configure(CommandLineApplication app) - { - app.Description = "Precompiles an application."; - app.HelpOption("-?|-h|--help"); - ProjectArgument = app.Argument( - "project", - "The path to the project (project folder or project.json) with precompilation."); - FrameworkOption = app.Option( - "-f|--framework", - "Target Framework", - CommandOptionType.SingleValue); - OutputPathOption = app.Option( - "-o|--output", - "The output (bin or publish) directory.", - CommandOptionType.SingleValue); - - ConfigurationOption = app.Option( - "-c|--configuration", - "Configuration", - CommandOptionType.SingleValue); - - ConfigureCompilationType = app.Option( - "--configure-compilation-type", - "Type with Configure method", - CommandOptionType.SingleValue); - - ContentRootOption = app.Option( - "--content-root", - "The application's content root.", - CommandOptionType.SingleValue); - - GeneratePdbOption = app.Option( - "--generate-pdbs", - "Generate pdbs for views.", - CommandOptionType.NoValue); - - app.OnExecute((Func)Execute); - } - - protected int Execute() - { - if (!ParseArguments()) - { - return 1; - } - - return ExecuteCore(); - } - - protected abstract int ExecuteCore(); - - private bool ParseArguments() - { - if (!string.IsNullOrEmpty(ProjectArgument.Value)) - { - ProjectPath = Path.GetFullPath(ProjectArgument.Value); - if (string.Equals(Path.GetFileName(ProjectPath), "project.json", StringComparison.OrdinalIgnoreCase)) - { - ProjectPath = Path.GetDirectoryName(ProjectPath); - } - - if (!Directory.Exists(ProjectPath)) - { - Console.Error.WriteLine($"Error: Could not find directory {ProjectPath}"); - return false; - } - } - else - { - ProjectPath = Directory.GetCurrentDirectory(); - } - - if (!FrameworkOption.HasValue()) - { - var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); - var projectContexts = workspace.GetProjectContextCollection(ProjectPath) - .FrameworkOnlyContexts - .ToList(); - - if (projectContexts.Count == 0) - { - Console.Error.WriteLine($"Error: Project at {ProjectPath} does not have any specified frameworks."); - return false; - } - else if (projectContexts.Count > 1) - { - Console.Error.WriteLine($"Error: Project at {ProjectPath} supports multiple framework. " + - $"Specify a framework using {FrameworkOption.Template}."); - return false; - } - - TargetFramework = projectContexts[0].TargetFramework; - } - else - { - TargetFramework = NuGetFramework.Parse(FrameworkOption.Value()); - } - - if (ConfigurationOption.HasValue()) - { - Configuration = ConfigurationOption.Value(); - } - else - { - Configuration = "Debug"; - } - - return true; - } - - protected string GetBuildOutputPath() - { - if (OutputPathOption.HasValue()) - { - return OutputPathOption.Value(); - } - - var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); - var projectContext = workspace.GetProjectContext( - ProjectPath, - TargetFramework); - var runtimeContext = workspace.GetRuntimeContext( - projectContext, - RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers()); - - var outputs = runtimeContext.GetOutputPaths(Configuration); - string outputPath; - if (!string.IsNullOrEmpty(runtimeContext.RuntimeIdentifier)) - { - outputPath = outputs.RuntimeOutputPath; - } - else - { - outputPath = outputs.CompilationOutputPath; - } - - return outputPath; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileRunCommand.cs deleted file mode 100644 index fdbd7bb754..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Internal/PrecompileRunCommand.cs +++ /dev/null @@ -1,193 +0,0 @@ -// 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.Diagnostics; -using System.IO; -using System.Reflection; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Internal; -using Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.AspNetCore.Mvc.Razor.Internal; -using Microsoft.AspNetCore.Razor.CodeGenerators; -using Microsoft.Cci; -using Microsoft.Cci.MutableCodeModel; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal -{ - public class PrecompileRunCommand : PrecompileCommandBase - { - private const string ConfigureMvcMethod = "ConfigureMvc"; - private const string PrecompiledAssemblyPrefix = "PrecompiledRazor."; - - private IMvcRazorHost Host { get; set; } - - private IRoslynCompilationService CompilationService { get; set; } - - private Action ConfigureMvcAction { get; set; } - - protected override int ExecuteCore() - { - ParseArguments(); - - var services = ConfigureDefaultServices(); - Host = services.GetRequiredService(); - var fileProvider = services.GetRequiredService().FileProvider; - CompilationService = services.GetRequiredService() as IRoslynCompilationService; - if (CompilationService == null) - { - Console.Error.WriteLine( - $"An {typeof(ICompilationService)} of type {typeof(IRoslynCompilationService)} " + - "is required for Razor precompilation."); - } - - var result = CompileViews(fileProvider); - if (!result.Success) - { - foreach (var error in result.RazorErrors) - { - Console.Error.WriteLine($"{error.Location.FilePath} ({error.Location.LineIndex}): {error.Message}"); - } - - foreach (var error in result.RoslynErrors) - { - Console.Error.WriteLine(CSharpDiagnosticFormatter.Instance.Format(error)); - } - - return 1; - } - - var outputPath = GetBuildOutputPath(); - UpdateAssembly(outputPath, result); - return 0; - } - - private bool ParseArguments() - { - if (ConfigureCompilationType.HasValue()) - { - var type = Type.GetType(ConfigureCompilationType.Value()); - if (type == null) - { - Console.Error.WriteLine($"Unable to find type '{type}."); - return false; - } - - var configureMethod = type?.GetMethod(ConfigureMvcMethod, BindingFlags.Public | BindingFlags.Static); - if (configureMethod == null) - { - Console.Error.WriteLine($"Could not find a method named {ConfigureMvcMethod} on {type}."); - return false; - } - - ConfigureMvcAction = (Action)configureMethod.CreateDelegate( - typeof(Action), - target: null); - } - - return true; - } - - private void UpdateAssembly(string outputPath, PrecompilationResult precompilationResult) - { - var host = new PeReader.DefaultHost(); - - var assemblyPath = Path.Combine(outputPath, Path.GetFileName(ProjectPath) + ".dll"); - var assembly = host.LoadUnitFrom(assemblyPath) as IAssembly; - assembly = new MetadataDeepCopier(host).Copy(assembly); - - var rewriters = new MetadataRewriter[] - { - new AddResourcesRewriter(host, precompilationResult.CompileOutputs), - new RemoveStrongNameRewriter(host) - }; - - var updatedAssembly = assembly; - foreach (var rewriter in rewriters) - { - updatedAssembly = rewriter.Rewrite(updatedAssembly); - } - - using (var stream = File.OpenWrite(Path.ChangeExtension(assemblyPath, ModifiedAssemblyExtension))) - { - PeWriter.WritePeToStream(updatedAssembly, host, stream); - } - } - - private PrecompilationResult CompileViews(IFileProvider fileProvider) - { - var precompilationResult = new PrecompilationResult(); - foreach (var fileInfo in FileProviderUtilities.GetRazorFiles(fileProvider)) - { - CompileView(precompilationResult, fileInfo); - } - - return precompilationResult; - } - - private void CompileView(PrecompilationResult result, RelativeFileInfo fileInfo) - { - GeneratorResults generatorResults; - using (var fileStream = fileInfo.FileInfo.CreateReadStream()) - { - generatorResults = Host.GenerateCode(fileInfo.RelativePath, fileStream); - } - - if (!generatorResults.Success) - { - result.RazorErrors.AddRange(generatorResults.ParserErrors); - return; - } - - var compileOutputs = new CompileOutputs(fileInfo.RelativePath, GeneratePdbOption.HasValue()); - var emitResult = CompilationService.EmitAssembly( - PrecompiledAssemblyPrefix + Guid.NewGuid(), - generatorResults.GeneratedCode, - compileOutputs.AssemblyStream, - compileOutputs.PdbStream); - - if (!emitResult.Success) - { - result.RoslynErrors.AddRange(emitResult.Diagnostics); - compileOutputs.Dispose(); - } - else - { - result.CompileOutputs.Add(compileOutputs); - } - } - - private IServiceProvider ConfigureDefaultServices() - { - var services = new ServiceCollection(); - - var applicationName = Path.GetFileName(ProjectPath.TrimEnd('/')); - var contentRoot = ContentRootOption.HasValue() - ? ContentRootOption.Value() - : Directory.GetCurrentDirectory(); - var hostingEnvironment = new HostingEnvironment - { - ApplicationName = applicationName, - WebRootFileProvider = new PhysicalFileProvider(ProjectPath), - ContentRootFileProvider = new PhysicalFileProvider(contentRoot), - ContentRootPath = contentRoot, - }; - var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); - - services - .AddSingleton(hostingEnvironment) - .AddSingleton(diagnosticSource) - .AddLogging() - .AddSingleton(); - - var mvcBuilder = services.AddMvc(); - ConfigureMvcAction?.Invoke(mvcBuilder); - - return services.BuildServiceProvider(); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Properties/AssemblyInfo.cs deleted file mode 100644 index 3a67f9108c..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Microsoft.AspNetCore.Mvc.Razor.Precompilation")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("ebb55c82-6e61-4a0d-8ad8-86915696e86e")] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs index 085ebeb898..4efeb118fe 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.AspNetCore.Mvc.Razor.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation { public class PrecompiledViewsFeature { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs new file mode 100644 index 0000000000..cf14c2ac3f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs @@ -0,0 +1,38 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Core.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + public class PrecompiledViewsFeatureProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, PrecompiledViewsFeature feature) + { + foreach (var provider in parts.OfType()) + { + var precompiledViews = provider.PrecompiledViews; + foreach (var viewInfo in precompiledViews) + { + AddView(feature, viewInfo); + } + } + } + + private void AddView(PrecompiledViewsFeature feature, PrecompiledViewInfo viewInfo) + { + using (var assemblyStream = viewInfo.AssemblyStreamFactory()) + { + using (var pdbStream = viewInfo.PdbStreamFactory?.Invoke()) + { + var type = RazorAssemblyLoader.GetExportedType(assemblyStream, pdbStream); + feature.PrecompiledViews[viewInfo.Path] = type; + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs index 527da73747..aaa64d1745 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; namespace Microsoft.AspNetCore.Mvc.Razor.Internal { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs index daff30e9eb..5f4b86b55d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs @@ -120,7 +120,7 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo result.Diagnostics); } - var type = CompiledAssemblyUtility.GetExportedType(assemblyStream, pdbStream); + var type = RazorAssemblyLoader.GetExportedType(assemblyStream, pdbStream); _logger.GeneratedCodeToAssemblyCompilationEnd(fileInfo.RelativePath, startTimestamp); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/PrecompiledViewsFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/PrecompiledViewsFeatureProvider.cs deleted file mode 100644 index 6f92050b80..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/PrecompiledViewsFeatureProvider.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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.Linq; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.ApplicationParts; - -namespace Microsoft.AspNetCore.Mvc.Razor.Internal -{ - public class PrecompiledViewsFeatureProvider : IApplicationFeatureProvider - { - public static readonly string PrecompiledResourcePrefix = "__RazorPrecompiledView__."; - private const string DllExtension = ".dll"; - private const string PdbExtension = ".pdb"; - - public void PopulateFeature(IEnumerable parts, PrecompiledViewsFeature feature) - { - foreach (var assemblyPart in parts.OfType()) - { - AddPrecompiledViews(feature, assemblyPart.Assembly); - } - } - - private void AddPrecompiledViews(PrecompiledViewsFeature feature, Assembly assembly) - { - var resourceNames = new HashSet(assembly.GetManifestResourceNames(), StringComparer.Ordinal); - foreach (var resourceName in resourceNames) - { - if (resourceName.StartsWith(PrecompiledResourcePrefix, StringComparison.Ordinal) && - resourceName.EndsWith(".dll", StringComparison.Ordinal)) - { - var type = ReadResource(assembly, resourceName); - var viewPath = resourceName.Substring( - PrecompiledResourcePrefix.Length, - resourceName.Length - PrecompiledResourcePrefix.Length - DllExtension.Length); - feature.PrecompiledViews[viewPath] = type; - } - } - } - - private Type ReadResource(Assembly assembly, string resourceName) - { - var pdbResourceName = Path.ChangeExtension(resourceName, PdbExtension); - - using (var resourceStream = assembly.GetManifestResourceStream(resourceName)) - { - using (var pdbStream = assembly.GetManifestResourceStream(pdbResourceName)) - { - return CompiledAssemblyUtility.GetExportedType(resourceStream, pdbStream); - } - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompiledAssemblyUtility.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorAssemblyLoader.cs similarity index 96% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompiledAssemblyUtility.cs rename to src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorAssemblyLoader.cs index 97dda92be2..ffb76ccfc5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompiledAssemblyUtility.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorAssemblyLoader.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { - public static class CompiledAssemblyUtility + public static class RazorAssemblyLoader { public static Type GetExportedType(Stream assemblyStream, Stream pdbStream) { From 23e6c4ddac4040cd16430d2fd82a7bd55d896b0c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 5 Aug 2016 14:42:18 -0700 Subject: [PATCH 3/7] Addressing more PR comments --- .../ApplicationParts/PrecompiledViewInfo.cs | 18 +++- .../DesignTime/IMvcBuilderConfiguration.cs | 20 ++++ .../Internal/CommonOptions.cs | 7 -- .../Internal/Constants.cs | 2 + .../Internal/FIleProviderUtilities.cs | 37 -------- .../Internal/MvcServicesProvider.cs | 58 ++++++----- .../Internal/PrecompileRunCommand.cs | 87 ++++++++++++++--- .../Internal/PrecompileDispatchCommand.cs | 95 ++++++++++--------- .../Compilation/IRoslynCompilationService.cs | 28 ------ .../DefaultRoslynCompilationService.cs | 2 +- 10 files changed, 197 insertions(+), 157 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/DesignTime/IMvcBuilderConfiguration.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/FIleProviderUtilities.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IRoslynCompilationService.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs index bbc83e0ad7..ad7ab9de66 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs @@ -6,13 +6,17 @@ namespace Microsoft.AspNetCore.Mvc.Core.ApplicationParts { + /// + /// Provides information for precompiled views. + /// public class PrecompiledViewInfo { - public PrecompiledViewInfo(string path, Func assemblyStreamFactory) - : this(path, assemblyStreamFactory, pdbStreamFactory: null) - { - } - + /// + /// Creates a new instance of . + /// + /// The path of the view. + /// Factory that provides the for the view assembly. + /// Factory that provides the for the view pdb. public PrecompiledViewInfo( string path, Func assemblyStreamFactory, @@ -23,8 +27,12 @@ public PrecompiledViewInfo( PdbStreamFactory = pdbStreamFactory; } + /// + /// The path of the view. + /// public string Path { get; } + public Func AssemblyStreamFactory { get; } public Func PdbStreamFactory { get; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DesignTime/IMvcBuilderConfiguration.cs b/src/Microsoft.AspNetCore.Mvc.Core/DesignTime/IMvcBuilderConfiguration.cs new file mode 100644 index 0000000000..49f9a90d6c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DesignTime/IMvcBuilderConfiguration.cs @@ -0,0 +1,20 @@ +// 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 Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.DesignTime +{ + /// + /// Configures the . Implement this interface to enable design-time configuration + /// (for instance during pre-compilation of views) of . + /// + public interface IMvcBuilderConfiguration + { + /// + /// Configures the . + /// + /// + void ConfigureMvc(IMvcBuilder builder); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs index fc2e25c293..8690f993b8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs @@ -7,8 +7,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class CommonOptions { - public CommandOption OutputPathOption { get; private set; } - public CommandArgument ProjectArgument { get; private set; } public CommandOption ConfigureCompilationType { get; private set; } @@ -26,11 +24,6 @@ public void Configure(CommandLineApplication app) "project", "The path to the project (project folder or project.json) with precompilation."); - OutputPathOption = app.Option( - "-o|--output", - "The output (bin or publish) directory.", - CommandOptionType.SingleValue); - ConfigureCompilationType = app.Option( "--configure-compilation-type", "Type with Configure method", diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs index af11b63192..32374daec0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs @@ -6,5 +6,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal public static class Constants { public static readonly string ModifiedAssemblyExtension = ".precompiled-razor"; + + public const string PrecompiledAssemblyNamePrefix = "PrecompiledRazor."; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/FIleProviderUtilities.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/FIleProviderUtilities.cs deleted file mode 100644 index 3bf002c764..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/FIleProviderUtilities.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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 Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.Extensions.FileProviders; - -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal -{ - public static class FileProviderUtilities - { - public static IEnumerable GetRazorFiles(IFileProvider fileProvider) - { - var files = new List(); - GetRazorFiles(fileProvider, files, root: string.Empty); - return files; - } - - private static void GetRazorFiles(IFileProvider fileProvider, List 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)); - } - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs index 649ea4bff4..3044b6a40e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs @@ -4,9 +4,11 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.AspNetCore.Mvc.DesignTime; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Internal; @@ -18,27 +20,29 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class MvcServicesProvider { - private const string ConfigureMvcMethod = "ConfigureMvc"; private readonly string _projectPath; private readonly string _contentRoot; + private readonly string _applicationName; public MvcServicesProvider( string projectPath, + string outputFilePath, string contentRoot, string configureCompilationType) { _projectPath = projectPath; _contentRoot = contentRoot; + _applicationName = Path.GetFileNameWithoutExtension(outputFilePath); - var configureCompilationAction = GetConfigureCompilationAction(configureCompilationType); - var serviceProvider = GetProvider(configureCompilationAction); + var mvcBuilderConfiguration = GetConfigureCompilationAction(configureCompilationType); + var serviceProvider = GetProvider(mvcBuilderConfiguration); Host = serviceProvider.GetRequiredService(); - CompilationService = serviceProvider.GetRequiredService() as IRoslynCompilationService; + CompilationService = serviceProvider.GetRequiredService() as DefaultRoslynCompilationService; if (CompilationService == null) { throw new InvalidOperationException( - $"An {typeof(ICompilationService)} of type {typeof(IRoslynCompilationService)} " + + $"An {typeof(ICompilationService)} of type {typeof(DefaultRoslynCompilationService)} " + "is required for Razor precompilation."); } @@ -47,42 +51,52 @@ public MvcServicesProvider( public IMvcRazorHost Host { get; } - public IRoslynCompilationService CompilationService { get; } + public DefaultRoslynCompilationService CompilationService { get; } public IFileProvider FileProvider { get; } - private static Action GetConfigureCompilationAction(string configureCompilationType) + private IMvcBuilderConfiguration GetConfigureCompilationAction(string configureCompilationType) { + Type type; if (!string.IsNullOrEmpty(configureCompilationType)) { - var type = Type.GetType(configureCompilationType); + type = Type.GetType(configureCompilationType); if (type == null) { throw new InvalidOperationException($"Unable to find type '{type}."); } + } + else + { + var assemblyName = new AssemblyName(_applicationName); + var assembly = Assembly.Load(assemblyName); + type = assembly + .GetExportedTypes() + .FirstOrDefault(typeof(IMvcBuilderConfiguration).IsAssignableFrom); + } - var configureMethod = type.GetMethod(ConfigureMvcMethod, BindingFlags.Public | BindingFlags.Static); - if (configureMethod == null) - { - throw new InvalidOperationException($"Could not find a method named {ConfigureMvcMethod} on {type}."); - } + if (type == null) + { + return null; + } - return (Action)configureMethod.CreateDelegate( - typeof(Action), - target: null); + var instance = Activator.CreateInstance(type) as IMvcBuilderConfiguration; + if (instance == null) + { + throw new InvalidOperationException($"Type {configureCompilationType} does not implement " + + $"{typeof(IMvcBuilderConfiguration)}."); } - // Todo: Add support for assembly scanning. - return null; + return instance; } - private IServiceProvider GetProvider(Action configureBuilder) + private IServiceProvider GetProvider(IMvcBuilderConfiguration mvcBuilderConfiguration) { var services = new ServiceCollection(); - var applicationName = Path.GetFileName(_projectPath.TrimEnd(Path.DirectorySeparatorChar)); + var hostingEnvironment = new HostingEnvironment { - ApplicationName = applicationName, + ApplicationName = _applicationName, WebRootFileProvider = new PhysicalFileProvider(_projectPath), ContentRootFileProvider = new PhysicalFileProvider(_contentRoot), ContentRootPath = _contentRoot, @@ -100,7 +114,7 @@ private IServiceProvider GetProvider(Action configureBuilder) .AddRazorViewEngine(); var mvcBuilder = new MvcBuilder(mvcCoreBuilder.Services, mvcCoreBuilder.PartManager); - configureBuilder?.Invoke(mvcBuilder); + mvcBuilderConfiguration?.ConfigureMvc(mvcBuilder); return mvcBuilder.Services.BuildServiceProvider(); } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs index 5e233d5ff8..64a084447c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs @@ -2,6 +2,7 @@ // 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 Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Razor.CodeGenerators; @@ -9,12 +10,13 @@ using Microsoft.Cci.MutableCodeModel; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class PrecompileRunCommand { - private const string PrecompiledAssemblyPrefix = "PrecompiledRazor."; + private readonly string PrecompiledAssemblyNameSuffix = Guid.NewGuid().ToString(); private CommandOption OutputFilePath { get; set; } @@ -27,6 +29,11 @@ public class PrecompileRunCommand public void Configure(CommandLineApplication app) { Options.Configure(app); + OutputFilePath = app.Option( + "--output-file-path", + "Path to the output binary (assembly or executable).", + CommandOptionType.SingleValue); + app.OnExecute(() => Execute()); } @@ -36,9 +43,12 @@ private int Execute() ServicesProvider = new MvcServicesProvider( ProjectPath, + OutputFilePath.Value(), Options.ContentRootOption.Value(), Options.ConfigureCompilationType.Value()); + Console.WriteLine("Running Razor view precompilation."); + var result = CompileViews(); if (!result.Success) { @@ -55,8 +65,10 @@ private int Execute() return 1; } - var outputPath = Options.OutputPathOption.Value(); - UpdateAssembly(outputPath, result); + Console.WriteLine($"Successfully compiled {result.CompileOutputs.Count} Razor views."); + Console.WriteLine($"Injecting precompiled views into assembly {OutputFilePath.Value()}."); + + UpdateAssembly(result); return 0; } @@ -67,14 +79,19 @@ private void ParseArguments() { throw new ArgumentException("Project path not specified."); } + + if (!OutputFilePath.HasValue()) + { + throw new ArgumentException($"Option {OutputFilePath.Template} does not specify a value."); + } } - private void UpdateAssembly(string outputPath, PrecompilationResult precompilationResult) + private void UpdateAssembly(PrecompilationResult precompilationResult) { var host = new PeReader.DefaultHost(); - var assemblyPath = Path.Combine(outputPath, Path.GetFileName(ProjectPath) + ".dll"); - var assembly = host.LoadUnitFrom(assemblyPath) as Cci.IAssembly; + var outputFilePath = OutputFilePath.Value(); + var assembly = host.LoadUnitFrom(outputFilePath) as Cci.IAssembly; assembly = new MetadataDeepCopier(host).Copy(assembly); var rewriters = new MetadataRewriter[] @@ -89,17 +106,23 @@ private void UpdateAssembly(string outputPath, PrecompilationResult precompilati updatedAssembly = rewriter.Rewrite(updatedAssembly); } - var outputFilePath = Path.ChangeExtension(assemblyPath, Constants.ModifiedAssemblyExtension); - using (var stream = File.OpenWrite(outputFilePath)) + var saveFilePath = Path.ChangeExtension(outputFilePath, Constants.ModifiedAssemblyExtension); + using (var stream = File.OpenWrite(saveFilePath)) { - PeWriter.WritePeToStream(updatedAssembly, host, stream); + var pdbPath = Path.ChangeExtension(outputFilePath, ".pdb"); + using (var pdb = new PdbReaderWriter(host, pdbPath)) + { + PeWriter.WritePeToStream(assembly, host, stream, pdb.PdbReader, localScopeProvider: null, pdbWriter: pdb.PdbWriter); + } } } private PrecompilationResult CompileViews() { + var files = new List(); + GetRazorFiles(ServicesProvider.FileProvider, files, root: string.Empty); var precompilationResult = new PrecompilationResult(); - foreach (var fileInfo in FileProviderUtilities.GetRazorFiles(ServicesProvider.FileProvider)) + foreach (var fileInfo in files) { CompileView(precompilationResult, fileInfo); } @@ -123,7 +146,7 @@ private void CompileView(PrecompilationResult result, RelativeFileInfo fileInfo) var compileOutputs = new CompileOutputs(fileInfo.RelativePath, Options.GeneratePdbOption.HasValue()); var emitResult = ServicesProvider.CompilationService.EmitAssembly( - PrecompiledAssemblyPrefix + Guid.NewGuid(), + Constants.PrecompiledAssemblyNamePrefix + Guid.NewGuid(), generatorResults.GeneratedCode, compileOutputs.AssemblyStream, compileOutputs.PdbStream); @@ -138,5 +161,47 @@ private void CompileView(PrecompilationResult result, RelativeFileInfo fileInfo) result.CompileOutputs.Add(compileOutputs); } } + + private static void GetRazorFiles(IFileProvider fileProvider, List 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 class PdbReaderWriter : IDisposable + { + public PdbReaderWriter(IMetadataHost host, string pdbPath) + { + if (File.Exists(pdbPath)) + { + using (var pdbStream = File.OpenRead(pdbPath)) + { + PdbReader = new PdbReader(pdbStream, host); + } + + PdbWriter = new PdbWriter(pdbPath, PdbReader); + } + } + + public PdbReader PdbReader { get; } + + public PdbWriter PdbWriter { get; } + + public void Dispose() + { + PdbWriter?.Dispose(); + PdbReader?.Dispose(); + } + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs index 46230f0739..7df8707e9a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs @@ -23,21 +23,22 @@ public class PrecompileDispatchCommand private CommandOption NoBuildOption { get; set; } - public CommandOption FrameworkOption { get; set; } + private CommandOption FrameworkOption { get; set; } - public CommandOption ConfigurationOption { get; set; } + private CommandOption ConfigurationOption { get; set; } - private NuGetFramework TargetFramework { get; set; } + private CommandOption OutputPathOption { get; set; } - private ProjectContext RuntimeContext { get; set; } + private NuGetFramework TargetFramework { get; set; } + private CommandOption BuildBasePathOption { get; set; } private string OutputPath { get; set; } private string ProjectPath { get; set; } private string Configuration { get; set; } - + public void Configure(CommandLineApplication app) { Options.Configure(app); @@ -55,6 +56,17 @@ public void Configure(CommandLineApplication app) "--no-build", "Do not build project before compiling views.", CommandOptionType.NoValue); + + OutputPathOption = app.Option( + "-o|--output", + "The output (bin or publish) directory.", + CommandOptionType.SingleValue); + + BuildBasePathOption = app.Option( + "-b|--build-base-path", + "Directory in which to place outputs.", + CommandOptionType.SingleValue); + app.OnExecute(() => Execute()); } @@ -63,8 +75,10 @@ private int Execute() ProjectPath = GetProjectPath(); Configuration = ConfigurationOption.Value() ?? DotNet.Cli.Utils.Constants.DefaultConfiguration; TargetFramework = GetTargetFramework(); - RuntimeContext = GetRuntimeContext(); - OutputPath = GetBuildOutputPath(); + var runtimeIdentifiers = RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers(); + var outputPaths = GetOutputPaths(runtimeIdentifiers); + OutputPath = runtimeIdentifiers.Any() ? outputPaths.RuntimeOutputPath : outputPaths.CompilationOutputPath; + var outputBinary = outputPaths.RuntimeFiles.Assembly; if (!NoBuildOption.HasValue()) { @@ -78,8 +92,8 @@ private int Execute() var dispatchArgs = new List { ProjectPath, - "--output", - OutputPath, + "--output-file-path", + outputBinary, "--content-root", Options.ContentRootOption.Value() ?? Directory.GetCurrentDirectory(), }; @@ -101,7 +115,7 @@ private int Execute() TargetFramework, Configuration, outputPath: OutputPath, - buildBasePath: null, + buildBasePath: BuildBasePathOption.Value(), projectDirectory: ProjectPath, toolName: toolName); @@ -113,12 +127,11 @@ private int Execute() if (commandExitCode == 0) { - var outputAssembly = RuntimeContext.GetOutputPaths(Configuration).RuntimeFiles.Assembly; - var modifiedAssembly = Path.ChangeExtension(outputAssembly, Design.Internal.Constants.ModifiedAssemblyExtension); + var modifiedAssembly = Path.ChangeExtension(outputBinary, Design.Internal.Constants.ModifiedAssemblyExtension); - if (File.Exists(outputAssembly)) + if (File.Exists(outputBinary)) { - File.Copy(modifiedAssembly, outputAssembly, overwrite: true); + File.Copy(modifiedAssembly, outputBinary, overwrite: true); File.Delete(modifiedAssembly); } } @@ -137,11 +150,10 @@ private string GetProjectPath() projectPath = Path.GetDirectoryName(ProjectPath); } - if (!Directory.Exists(ProjectPath)) + if (!Directory.Exists(projectPath)) { - throw new InvalidOperationException($"Error: Could not find directory {ProjectPath}"); + throw new InvalidOperationException($"Error: Could not find directory {projectPath}."); } - } else { @@ -151,7 +163,7 @@ private string GetProjectPath() return projectPath; } - private ProjectContext GetRuntimeContext() + private OutputPaths GetOutputPaths(IEnumerable runtimeIdentifiers) { var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); @@ -162,9 +174,11 @@ private ProjectContext GetRuntimeContext() throw new InvalidOperationException($"Project '{ProjectPath}' does not support framework: {FrameworkOption.Value()}"); } - return workspace.GetRuntimeContext( - projectContext, - RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers()); + var runtimeContext = workspace.GetRuntimeContext(projectContext, runtimeIdentifiers); + return runtimeContext.GetOutputPaths( + Configuration, + BuildBasePathOption.Value(), + OutputPathOption.Value()); } private int BuildProject() @@ -173,14 +187,24 @@ private int BuildProject() { ProjectPath, "--framework", - RuntimeContext.TargetFramework.ToString(), + TargetFramework.ToString(), "--configuration", Configuration, - "--output", - OutputPath }; - return Command.CreateDotNet("build", arguments, RuntimeContext.TargetFramework, Configuration) + if (BuildBasePathOption.HasValue()) + { + arguments.Add("--build-base-path"); + arguments.Add(BuildBasePathOption.Value()); + } + + if (OutputPathOption.HasValue()) + { + arguments.Add("--output"); + arguments.Add(OutputPathOption.Value()); + } + + return Command.CreateDotNet("build", arguments, TargetFramework, Configuration) .ForwardStdErr(Console.Error) .ForwardStdOut(Console.Out) .Execute() @@ -214,26 +238,5 @@ private NuGetFramework GetTargetFramework() return NuGetFramework.Parse(FrameworkOption.Value()); } } - - private string GetBuildOutputPath() - { - if (Options.OutputPathOption.HasValue()) - { - return Options.OutputPathOption.Value(); - } - - var outputs = RuntimeContext.GetOutputPaths(Configuration); - string outputPath; - if (!string.IsNullOrEmpty(RuntimeContext.RuntimeIdentifier)) - { - outputPath = outputs.RuntimeOutputPath; - } - else - { - outputPath = outputs.CompilationOutputPath; - } - - return outputPath; - } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IRoslynCompilationService.cs deleted file mode 100644 index ade4bf4a8e..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IRoslynCompilationService.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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.IO; -using Microsoft.CodeAnalysis.Emit; - -namespace Microsoft.AspNetCore.Mvc.Razor.Compilation -{ - /// - /// A that uses Roslyn. - /// - public interface IRoslynCompilationService : ICompilationService - { - /// - /// Produces an from the specified . - /// - /// The assembly name. - /// The content to be compiled. - /// The to write assemblies to. - /// The to write pdbs to. - /// The . - EmitResult EmitAssembly( - string assemblyName, - string compilationContent, - Stream assemblyStream, - Stream pdbStream); - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs index 5f4b86b55d..2f327a998e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal /// /// A type that uses Roslyn to compile C# content. /// - public class DefaultRoslynCompilationService : IRoslynCompilationService + public class DefaultRoslynCompilationService : ICompilationService { // error CS0234: The type or namespace name 'C' does not exist in the namespace 'N' (are you missing // an assembly reference?) From b2be0d4f3eba5dd361c6038929465a8aaed64827 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 5 Aug 2016 14:42:18 -0700 Subject: [PATCH 4/7] Changes to produce precompiled views in a separate assembly. --- .../ApplicationParts/AssemblyPart.cs | 35 +- .../IPrecompiledViewsProvider.cs | 4 +- .../ApplicationParts/PrecompiledViewInfo.cs | 20 +- .../ApplicationParts/PrecompiledViews.cs | 25 + .../Internal/AddResourcesRewriter.cs | 73 -- .../Internal/CodeGenerator.cs | 68 ++ .../Internal/CommonOptions.cs | 7 - .../Internal/CompileOutputs.cs | 29 - .../Internal/Constants.cs | 12 - .../Internal/MvcServicesProvider.cs | 15 +- .../Internal/PrecompilationResult.cs | 20 - .../Internal/PrecompileRunCommand.cs | 211 ++--- .../Internal/RemoveStrongNameRewriter.cs | 29 - .../Internal/ViewCompilationInfo.cs | 25 + .../project.json | 5 +- .../Internal/PrecompileDispatchCommand.cs | 134 +-- .../project.json | 2 +- .../PrecompiledViewsFeatureProvider.cs | 20 +- .../MvcRazorMvcCoreBuilderExtensions.cs | 2 + .../Internal/CSharpCompiler.cs | 81 ++ .../DefaultRoslynCompilationService.cs | 153 +--- .../Internal/RazorAssemblyLoader.cs | 41 - .../Internal/RazorReferenceManager.cs | 59 ++ .../DefaultRoslynCompilationServiceTest.cs | 796 +++++++++--------- 24 files changed, 870 insertions(+), 996 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/AddResourcesRewriter.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CodeGenerator.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CompileOutputs.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationResult.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/RemoveStrongNameRewriter.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorAssemblyLoader.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorReferenceManager.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs index 45112ab492..b37040afce 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Mvc.Core.ApplicationParts; @@ -20,9 +19,10 @@ public class AssemblyPart : ICompilationReferencesProvider, IPrecompiledViewsProvider { - public static readonly string PrecompiledResourcePrefix = "__PrecompiledView__."; - private const string DllExtension = ".dll"; - private const string PdbExtension = ".pdb"; + + public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews"; + public static readonly string ViewFactoryNamespace = "AspNetCore"; + public static readonly string ViewFactoryTypeName = "__PrecompiledViewFactory"; /// /// Initalizes a new instance. @@ -51,29 +51,22 @@ public AssemblyPart(Assembly assembly) /// public IEnumerable Types => Assembly.DefinedTypes; - public IEnumerable PrecompiledViews + public PrecompiledViews PrecompiledViews { get { - var precompiledViews = new List(); - foreach (var resourceName in Assembly.GetManifestResourceNames()) - { - if (resourceName.StartsWith(PrecompiledResourcePrefix, StringComparison.Ordinal) && - resourceName.EndsWith(DllExtension, StringComparison.Ordinal)) - { - var viewPath = resourceName.Substring( - PrecompiledResourcePrefix.Length, - resourceName.Length - PrecompiledResourcePrefix.Length - DllExtension.Length); + var precompiledAssemblyName = new AssemblyName(Assembly.FullName); + precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix; - var pdbStreamName = Path.ChangeExtension(viewPath, PdbExtension); - precompiledViews.Add(new PrecompiledViewInfo( - viewPath, - () => Assembly.GetManifestResourceStream(resourceName), - () => Assembly.GetManifestResourceStream(pdbStreamName))); - } + var viewFactoryTypeName = $"{ViewFactoryNamespace}.{ViewFactoryTypeName},{precompiledAssemblyName}"; + var viewFactoryType = Type.GetType(viewFactoryTypeName); + + if (viewFactoryType == null) + { + return null; } - return precompiledViews; + return Activator.CreateInstance(viewFactoryType) as PrecompiledViews; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs index f3a06097c6..c7dd08a13a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs @@ -1,12 +1,10 @@ // 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.Collections.Generic; - namespace Microsoft.AspNetCore.Mvc.Core.ApplicationParts { public interface IPrecompiledViewsProvider { - IEnumerable PrecompiledViews { get; } + PrecompiledViews PrecompiledViews { get; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs index ad7ab9de66..3cfc14cfa8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; namespace Microsoft.AspNetCore.Mvc.Core.ApplicationParts { @@ -15,16 +14,11 @@ public class PrecompiledViewInfo /// Creates a new instance of . /// /// The path of the view. - /// Factory that provides the for the view assembly. - /// Factory that provides the for the view pdb. - public PrecompiledViewInfo( - string path, - Func assemblyStreamFactory, - Func pdbStreamFactory) + /// The view . + public PrecompiledViewInfo(string path, Type type) { Path = path; - AssemblyStreamFactory = assemblyStreamFactory; - PdbStreamFactory = pdbStreamFactory; + Type = type; } /// @@ -32,9 +26,9 @@ public PrecompiledViewInfo( /// public string Path { get; } - - public Func AssemblyStreamFactory { get; } - - public Func PdbStreamFactory { get; } + /// + /// The view . + /// + public Type Type { get; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs new file mode 100644 index 0000000000..c551e3d80c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs @@ -0,0 +1,25 @@ +// 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.Collections; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Core.ApplicationParts +{ + /// + /// Provides information for precompiled views. + /// + public abstract class PrecompiledViews : IEnumerable + { + private IEnumerable _precompiledViews; + + protected PrecompiledViews(IEnumerable precompiledViews) + { + _precompiledViews = precompiledViews; + } + + public IEnumerator GetEnumerator() => _precompiledViews.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/AddResourcesRewriter.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/AddResourcesRewriter.cs deleted file mode 100644 index ccecda6d05..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/AddResourcesRewriter.cs +++ /dev/null @@ -1,73 +0,0 @@ -// 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.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.Cci; -using Microsoft.Cci.MutableCodeModel; - -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal -{ - public class AddResourcesRewriter : MetadataRewriter - { - private readonly List _outputs; - private IAssembly _assembly; - - public AddResourcesRewriter(IMetadataHost host, List outputs) - : base(host) - { - _outputs = outputs; - } - - public override IAssembly Rewrite(IAssembly assembly) - { - _assembly = assembly; - return base.Rewrite(assembly); - } - - public override List Rewrite(List resourceReferences) - { - if (resourceReferences == null) - { - resourceReferences = new List(); - } - - foreach (var output in _outputs) - { - var dllResource = $"{AssemblyPart.PrecompiledResourcePrefix}{output.RelativePath}.dll"; - resourceReferences.Add(new ResourceReference - { - Name = host.NameTable.GetNameFor(dllResource), - DefiningAssembly = _assembly, - IsPublic = true, - Resource = new Resource - { - Data = output.AssemblyStream.ToArray().ToList(), - IsPublic = true, - DefiningAssembly = _assembly, - } - }); - - if (output.PdbStream != null) - { - var pdbResource = $"{AssemblyPart.PrecompiledResourcePrefix}{output.RelativePath}.pdb"; - resourceReferences.Add(new ResourceReference - { - Name = host.NameTable.GetNameFor(pdbResource), - DefiningAssembly = _assembly, - IsPublic = true, - Resource = new Resource - { - Data = output.PdbStream.ToArray().ToList(), - IsPublic = true, - DefiningAssembly = _assembly, - } - }); - } - } - - return resourceReferences; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CodeGenerator.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CodeGenerator.cs new file mode 100644 index 0000000000..985950ed3e --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CodeGenerator.cs @@ -0,0 +1,68 @@ +// 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.Collections.Generic; +using System.Reflection; +using System.Text; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Core.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class CodeGenerator + { + public CodeGenerator( + CSharpCompiler compiler, + CSharpCompilation compilation) + { + Compiler = compiler; + Compilation = compilation; + } + + public CSharpCompiler Compiler { get; } + + public CSharpCompilation Compilation { get; private set; } + + public void AddViewFactory(List result) + { + var precompiledViewsArray = new StringBuilder(); + foreach (var item in result) + { + precompiledViewsArray.AppendLine( + $"new {typeof(PrecompiledViewInfo).FullName}(@\"{item.RelativeFileInfo.RelativePath}\", typeof({item.TypeName})),"); + } + + var factoryContent = + $@" +namespace {AssemblyPart.ViewFactoryNamespace} +{{ + public class {AssemblyPart.ViewFactoryTypeName} : {typeof(PrecompiledViews).FullName} + {{ + public {AssemblyPart.ViewFactoryTypeName}() : base(new[] + {{ + {precompiledViewsArray} + }}) + {{ + }} + }} +}}"; + var syntaxTree = Compiler.CreateSyntaxTree(SourceText.From(factoryContent)); + Compilation = Compilation.AddSyntaxTrees(syntaxTree); + } + + public void AddAssemblyMetadata(AssemblyName applicationAssemblyName, string keyFilePath) + { + if (!string.IsNullOrEmpty(keyFilePath)) + { + Compilation = Compilation.WithOptions(Compilation.Options.WithCryptoKeyFile(keyFilePath)); + } + + var assemblyVersionContent = $"[assembly:{typeof(AssemblyVersionAttribute).FullName}(\"{applicationAssemblyName.Version}\")]"; + var syntaxTree = Compiler.CreateSyntaxTree(SourceText.From(assemblyVersionContent)); + Compilation = Compilation.AddSyntaxTrees(syntaxTree); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs index 8690f993b8..c11e392f2d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs @@ -13,8 +13,6 @@ public class CommonOptions public CommandOption ContentRootOption { get; private set; } - public CommandOption GeneratePdbOption { get; private set; } - public void Configure(CommandLineApplication app) { app.Description = "Precompiles an application."; @@ -33,11 +31,6 @@ public void Configure(CommandLineApplication app) "--content-root", "The application's content root.", CommandOptionType.SingleValue); - - GeneratePdbOption = app.Option( - "--generate-pdbs", - "Generate pdbs for views.", - CommandOptionType.NoValue); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CompileOutputs.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CompileOutputs.cs deleted file mode 100644 index 5f88dd8c6a..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CompileOutputs.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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.IO; - -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal -{ - public class CompileOutputs : IDisposable - { - public CompileOutputs(string relativePath, bool generatePdbs) - { - RelativePath = relativePath; - PdbStream = generatePdbs ? new MemoryStream() : null; - } - - public MemoryStream AssemblyStream { get; } = new MemoryStream(); - - public MemoryStream PdbStream { get; } - - public string RelativePath { get; } - - public void Dispose() - { - AssemblyStream.Dispose(); - PdbStream?.Dispose(); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs deleted file mode 100644 index 32374daec0..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/Constants.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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. - -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal -{ - public static class Constants - { - public static readonly string ModifiedAssemblyExtension = ".precompiled-razor"; - - public const string PrecompiledAssemblyNamePrefix = "PrecompiledRazor."; - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs index 3044b6a40e..f9be9aaa37 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Mvc.DesignTime; using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -26,32 +25,26 @@ public class MvcServicesProvider public MvcServicesProvider( string projectPath, - string outputFilePath, + string applicationName, string contentRoot, string configureCompilationType) { _projectPath = projectPath; _contentRoot = contentRoot; - _applicationName = Path.GetFileNameWithoutExtension(outputFilePath); + _applicationName = applicationName; var mvcBuilderConfiguration = GetConfigureCompilationAction(configureCompilationType); var serviceProvider = GetProvider(mvcBuilderConfiguration); Host = serviceProvider.GetRequiredService(); - CompilationService = serviceProvider.GetRequiredService() as DefaultRoslynCompilationService; - if (CompilationService == null) - { - throw new InvalidOperationException( - $"An {typeof(ICompilationService)} of type {typeof(DefaultRoslynCompilationService)} " + - "is required for Razor precompilation."); - } + Compiler = serviceProvider.GetRequiredService(); FileProvider = serviceProvider.GetRequiredService().FileProvider; } public IMvcRazorHost Host { get; } - public DefaultRoslynCompilationService CompilationService { get; } + public CSharpCompiler Compiler { get; } public IFileProvider FileProvider { get; } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationResult.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationResult.cs deleted file mode 100644 index b8139017d8..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationResult.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Collections.Generic; -using Microsoft.AspNetCore.Razor; -using Microsoft.CodeAnalysis; - -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal -{ - public class PrecompilationResult - { - public List RazorErrors { get; } = new List(); - - public List RoslynErrors { get; } = new List(); - - public bool Success => (RazorErrors.Count == 0) && (RoslynErrors.Count == 0); - - public List CompileOutputs { get; } = new List(); - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs index 64a084447c..47e015bb23 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs @@ -4,11 +4,16 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.AspNetCore.Razor.CodeGenerators; -using Microsoft.Cci; -using Microsoft.Cci.MutableCodeModel; +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; @@ -16,9 +21,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class PrecompileRunCommand { - private readonly string PrecompiledAssemblyNameSuffix = Guid.NewGuid().ToString(); + private CommandOption OutputPathOption { get; set; } - private CommandOption OutputFilePath { get; set; } + private CommandOption ApplicationNameOption { get; set; } private MvcServicesProvider ServicesProvider { get; set; } @@ -29,9 +34,14 @@ public class PrecompileRunCommand public void Configure(CommandLineApplication app) { Options.Configure(app); - OutputFilePath = app.Option( - "--output-file-path", - "Path to the output binary (assembly or executable).", + OutputPathOption = app.Option( + "--output-path", + "Path to the emit the precompiled assembly in.", + CommandOptionType.SingleValue); + + ApplicationNameOption = app.Option( + "--application-name", + "Name of the application to produce precompiled assembly for.", CommandOptionType.SingleValue); app.OnExecute(() => Execute()); @@ -43,123 +53,127 @@ private int Execute() ServicesProvider = new MvcServicesProvider( ProjectPath, - OutputFilePath.Value(), + ApplicationNameOption.Value(), Options.ContentRootOption.Value(), Options.ConfigureCompilationType.Value()); Console.WriteLine("Running Razor view precompilation."); - var result = CompileViews(); - if (!result.Success) + var results = ParseViews(); + var success = true; + foreach (var result in results) { - foreach (var error in result.RazorErrors) + if (!result.GeneratorResults.Success) { - Console.Error.WriteLine($"{error.Location.FilePath} ({error.Location.LineIndex}): {error.Message}"); + success = false; + foreach (var error in result.GeneratorResults.ParserErrors) + { + Console.Error.WriteLine($"{error.Location.FilePath} ({error.Location.LineIndex}): {error.Message}"); + } } + } - foreach (var error in result.RoslynErrors) + 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(error)); + Console.Error.WriteLine(CSharpDiagnosticFormatter.Instance.Format(diagnostic)); } return 1; } - Console.WriteLine($"Successfully compiled {result.CompileOutputs.Count} Razor views."); - Console.WriteLine($"Injecting precompiled views into assembly {OutputFilePath.Value()}."); - - UpdateAssembly(result); + Console.WriteLine($"Successfully compiled {results.Count} Razor views."); + Console.WriteLine($"Precompiled views emitted to {assemblyPath}."); return 0; } - private void ParseArguments() + private EmitResult EmitAssembly(CSharpCompilation compilation, string assemblyPath) { - ProjectPath = Options.ProjectArgument.Value; - if (string.IsNullOrEmpty(ProjectPath)) + EmitResult emitResult; + using (var assemblyStream = File.OpenWrite(assemblyPath)) { - throw new ArgumentException("Project path not specified."); + using (var pdbStream = File.OpenWrite(Path.ChangeExtension(assemblyPath, ".pdb"))) + { + emitResult = compilation.Emit( + assemblyStream, + pdbStream, + options: ServicesProvider.Compiler.EmitOptions); + } } - if (!OutputFilePath.HasValue()) - { - throw new ArgumentException($"Option {OutputFilePath.Template} does not specify a value."); - } + return emitResult; } - private void UpdateAssembly(PrecompilationResult precompilationResult) + private CSharpCompilation CompileViews(List results, string assemblyname) { - var host = new PeReader.DefaultHost(); + var compiler = ServicesProvider.Compiler; + var compilation = compiler.CreateCompilation(assemblyname); - var outputFilePath = OutputFilePath.Value(); - var assembly = host.LoadUnitFrom(outputFilePath) as Cci.IAssembly; - assembly = new MetadataDeepCopier(host).Copy(assembly); - - var rewriters = new MetadataRewriter[] - { - new AddResourcesRewriter(host, precompilationResult.CompileOutputs), - new RemoveStrongNameRewriter(host) - }; - - var updatedAssembly = assembly; - foreach (var rewriter in rewriters) + foreach (var result in results) { - updatedAssembly = rewriter.Rewrite(updatedAssembly); + var sourceText = SourceText.From(result.GeneratorResults.GeneratedCode, Encoding.UTF8); + var syntaxTree = compiler.CreateSyntaxTree(sourceText); + compilation = compilation.AddSyntaxTrees(syntaxTree); + result.TypeName = ReadTypeInfo(compilation, syntaxTree); } - var saveFilePath = Path.ChangeExtension(outputFilePath, Constants.ModifiedAssemblyExtension); - using (var stream = File.OpenWrite(saveFilePath)) - { - var pdbPath = Path.ChangeExtension(outputFilePath, ".pdb"); - using (var pdb = new PdbReaderWriter(host, pdbPath)) - { - PeWriter.WritePeToStream(assembly, host, stream, pdb.PdbReader, localScopeProvider: null, pdbWriter: pdb.PdbWriter); - } - } - } + compilation = compiler.ProcessCompilation(compilation); + var codeGenerator = new CodeGenerator(compiler, compilation); + codeGenerator.AddViewFactory(results); - private PrecompilationResult CompileViews() - { - var files = new List(); - GetRazorFiles(ServicesProvider.FileProvider, files, root: string.Empty); - var precompilationResult = new PrecompilationResult(); - foreach (var fileInfo in files) - { - CompileView(precompilationResult, fileInfo); - } + var assemblyName = new AssemblyName(ApplicationNameOption.Value()); + assemblyName = Assembly.Load(assemblyName).GetName(); + codeGenerator.AddAssemblyMetadata(assemblyName, keyFilePath: null); - return precompilationResult; + return codeGenerator.Compilation; } - private void CompileView(PrecompilationResult result, RelativeFileInfo fileInfo) + private void ParseArguments() { - GeneratorResults generatorResults; - using (var fileStream = fileInfo.FileInfo.CreateReadStream()) + ProjectPath = Options.ProjectArgument.Value; + if (string.IsNullOrEmpty(ProjectPath)) { - generatorResults = ServicesProvider.Host.GenerateCode(fileInfo.RelativePath, fileStream); + throw new ArgumentException("Project path not specified."); } - if (!generatorResults.Success) + if (!OutputPathOption.HasValue()) { - result.RazorErrors.AddRange(generatorResults.ParserErrors); - return; + throw new ArgumentException($"Option {OutputPathOption.Template} does not specify a value."); } - var compileOutputs = new CompileOutputs(fileInfo.RelativePath, Options.GeneratePdbOption.HasValue()); - var emitResult = ServicesProvider.CompilationService.EmitAssembly( - Constants.PrecompiledAssemblyNamePrefix + Guid.NewGuid(), - generatorResults.GeneratedCode, - compileOutputs.AssemblyStream, - compileOutputs.PdbStream); - - if (!emitResult.Success) + if (!ApplicationNameOption.HasValue()) { - result.RoslynErrors.AddRange(emitResult.Diagnostics); - compileOutputs.Dispose(); + throw new ArgumentException($"Option {ApplicationNameOption.Template} does not specify a value."); } - else + } + + private List ParseViews() + { + var results = new List(); + var files = new List(); + GetRazorFiles(ServicesProvider.FileProvider, files, root: string.Empty); + foreach (var fileInfo in files) { - result.CompileOutputs.Add(compileOutputs); + using (var fileStream = fileInfo.FileInfo.CreateReadStream()) + { + var result = ServicesProvider.Host.GenerateCode(fileInfo.RelativePath, fileStream); + results.Add(new ViewCompilationInfo(fileInfo, result)); + } } + + return results; } private static void GetRazorFiles(IFileProvider fileProvider, List razorFiles, string root) @@ -178,30 +192,37 @@ private static void GetRazorFiles(IFileProvider fileProvider, List(); + foreach (var declaration in classDeclarations) { - if (File.Exists(pdbPath)) + var typeModel = semanticModel.GetDeclaredSymbol(declaration); + if (typeModel.ContainingType == null && typeModel.DeclaredAccessibility == Accessibility.Public) { - using (var pdbStream = File.OpenRead(pdbPath)) - { - PdbReader = new PdbReader(pdbStream, host); - } - - PdbWriter = new PdbWriter(pdbPath, PdbReader); + return GetFullName(typeModel); } } - public PdbReader PdbReader { get; } + return null; + } - public PdbWriter PdbWriter { get; } + private string GetFullName(INamedTypeSymbol typeModel) + { + var nameBuilder = new StringBuilder(); - public void Dispose() + var containingNamespace = typeModel.ContainingNamespace; + while (!containingNamespace.IsGlobalNamespace) { - PdbWriter?.Dispose(); - PdbReader?.Dispose(); + nameBuilder.Insert(0, "."); + nameBuilder.Insert(0, containingNamespace.MetadataName); + + containingNamespace = containingNamespace.ContainingNamespace; } + + nameBuilder.Append(typeModel.MetadataName); + return nameBuilder.ToString(); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/RemoveStrongNameRewriter.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/RemoveStrongNameRewriter.cs deleted file mode 100644 index 98c7aea5e0..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/RemoveStrongNameRewriter.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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 Microsoft.Cci; -using Microsoft.Cci.MutableCodeModel; - -namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal -{ - public class RemoveStrongNameRewriter : MetadataRewriter - { - public RemoveStrongNameRewriter(IMetadataHost host) - : base(host) - { - } - - public override IAssembly Rewrite(IAssembly assembly) - { - var mutableAssembly = base.Rewrite(assembly) as Cci.MutableCodeModel.Assembly; - if (mutableAssembly == null) - { - return assembly; - } - - mutableAssembly.PublicKey = null; - mutableAssembly.StrongNameSigned = false; - return mutableAssembly; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs new file mode 100644 index 0000000000..f2ac3b44e3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs @@ -0,0 +1,25 @@ +// 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 Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Razor.CodeGenerators; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class ViewCompilationInfo + { + public ViewCompilationInfo( + RelativeFileInfo relativeFileInfo, + GeneratorResults generatorResults) + { + RelativeFileInfo = relativeFileInfo; + GeneratorResults = generatorResults; + } + + public RelativeFileInfo RelativeFileInfo { get; } + + public GeneratorResults GeneratorResults { get; } + + public string TypeName { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json index 65e67a8515..26565cfcec 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json @@ -25,8 +25,7 @@ "dependencies": { "Microsoft.AspNetCore.Hosting": "1.1.0-*", "Microsoft.AspNetCore.Mvc.Razor": "1.1.0-*", - "Microsoft.Extensions.CommandLineUtils": "1.1.0-*", - "Microsoft.Cci": "4.0.0-rc3-24214-00" + "Microsoft.Extensions.CommandLineUtils": "1.1.0-*" }, "frameworks": { "netcoreapp1.0": { @@ -37,6 +36,6 @@ } } }, - "net46": {} + "net451": {} } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs index 7df8707e9a..ababa454f6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs @@ -5,10 +5,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal; -using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.InternalAbstractions; using Microsoft.DotNet.ProjectModel; using Microsoft.Extensions.CommandLineUtils; @@ -21,8 +19,6 @@ public class PrecompileDispatchCommand { private CommonOptions Options { get; } = new CommonOptions(); - private CommandOption NoBuildOption { get; set; } - private CommandOption FrameworkOption { get; set; } private CommandOption ConfigurationOption { get; set; } @@ -33,12 +29,10 @@ public class PrecompileDispatchCommand private CommandOption BuildBasePathOption { get; set; } - private string OutputPath { get; set; } - private string ProjectPath { get; set; } private string Configuration { get; set; } - + public void Configure(CommandLineApplication app) { Options.Configure(app); @@ -52,19 +46,9 @@ public void Configure(CommandLineApplication app) "Configuration", CommandOptionType.SingleValue); - NoBuildOption = app.Option( - "--no-build", - "Do not build project before compiling views.", - CommandOptionType.NoValue); - OutputPathOption = app.Option( - "-o|--output", - "The output (bin or publish) directory.", - CommandOptionType.SingleValue); - - BuildBasePathOption = app.Option( - "-b|--build-base-path", - "Directory in which to place outputs.", + "-o|--output-path", + "Published path of the application.", CommandOptionType.SingleValue); app.OnExecute(() => Execute()); @@ -75,25 +59,17 @@ private int Execute() ProjectPath = GetProjectPath(); Configuration = ConfigurationOption.Value() ?? DotNet.Cli.Utils.Constants.DefaultConfiguration; TargetFramework = GetTargetFramework(); - var runtimeIdentifiers = RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers(); - var outputPaths = GetOutputPaths(runtimeIdentifiers); - OutputPath = runtimeIdentifiers.Any() ? outputPaths.RuntimeOutputPath : outputPaths.CompilationOutputPath; - var outputBinary = outputPaths.RuntimeFiles.Assembly; - - if (!NoBuildOption.HasValue()) - { - var exitCode = BuildProject(); - if (exitCode != 0) - { - return exitCode; - } - } + var outputPaths = GetOutputPaths(); + var applicationName = Path.GetFileNameWithoutExtension(outputPaths.CompilationFiles.Assembly); var dispatchArgs = new List { + "--debug", ProjectPath, - "--output-file-path", - outputBinary, + "--application-name", + applicationName, + "--output-path", + GetOutputPath(), "--content-root", Options.ContentRootOption.Value() ?? Directory.GetCurrentDirectory(), }; @@ -104,18 +80,13 @@ private int Execute() dispatchArgs.Add(Options.ConfigureCompilationType.Value()); } - if (Options.GeneratePdbOption.HasValue()) - { - dispatchArgs.Add("--generate-pdbs"); - } - var toolName = typeof(Design.Program).GetTypeInfo().Assembly.GetName().Name; var dispatchCommand = DotnetToolDispatcher.CreateDispatchCommand( dispatchArgs, TargetFramework, Configuration, - outputPath: OutputPath, - buildBasePath: BuildBasePathOption.Value(), + outputPath: outputPaths.RuntimeOutputPath, + buildBasePath: null, projectDirectory: ProjectPath, toolName: toolName); @@ -125,17 +96,6 @@ private int Execute() .Execute() .ExitCode; - if (commandExitCode == 0) - { - var modifiedAssembly = Path.ChangeExtension(outputBinary, Design.Internal.Constants.ModifiedAssemblyExtension); - - if (File.Exists(outputBinary)) - { - File.Copy(modifiedAssembly, outputBinary, overwrite: true); - File.Delete(modifiedAssembly); - } - } - return commandExitCode; } @@ -152,7 +112,7 @@ private string GetProjectPath() if (!Directory.Exists(projectPath)) { - throw new InvalidOperationException($"Error: Could not find directory {projectPath}."); + throw new InvalidOperationException($"Could not find directory {projectPath}."); } } else @@ -163,7 +123,7 @@ private string GetProjectPath() return projectPath; } - private OutputPaths GetOutputPaths(IEnumerable runtimeIdentifiers) + private OutputPaths GetOutputPaths() { var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); @@ -174,69 +134,31 @@ private OutputPaths GetOutputPaths(IEnumerable runtimeIdentifiers) throw new InvalidOperationException($"Project '{ProjectPath}' does not support framework: {FrameworkOption.Value()}"); } - var runtimeContext = workspace.GetRuntimeContext(projectContext, runtimeIdentifiers); - return runtimeContext.GetOutputPaths( - Configuration, - BuildBasePathOption.Value(), - OutputPathOption.Value()); - } + var runtimeContext = workspace.GetRuntimeContext( + projectContext, + RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers()); - private int BuildProject() - { - var arguments = new List - { - ProjectPath, - "--framework", - TargetFramework.ToString(), - "--configuration", - Configuration, - }; - - if (BuildBasePathOption.HasValue()) - { - arguments.Add("--build-base-path"); - arguments.Add(BuildBasePathOption.Value()); - } - - if (OutputPathOption.HasValue()) - { - arguments.Add("--output"); - arguments.Add(OutputPathOption.Value()); - } - - return Command.CreateDotNet("build", arguments, TargetFramework, Configuration) - .ForwardStdErr(Console.Error) - .ForwardStdOut(Console.Out) - .Execute() - .ExitCode; + return runtimeContext.GetOutputPaths(Configuration); } private NuGetFramework GetTargetFramework() { if (!FrameworkOption.HasValue()) { - var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); - var projectContexts = workspace.GetProjectContextCollection(ProjectPath) - .FrameworkOnlyContexts - .ToList(); - - if (projectContexts.Count == 0) - { - throw new Exception($"Error: Project at {ProjectPath} does not have any specified frameworks."); + throw new Exception($"Option {FrameworkOption.Template} does not have a value."); + } - } - else if (projectContexts.Count > 1) - { - throw new Exception($"Error: Project at {ProjectPath} supports multiple framework. " + - $"Specify a framework using {FrameworkOption.Template}."); - } + return NuGetFramework.Parse(FrameworkOption.Value()); + } - return projectContexts[0].TargetFramework; - } - else + private string GetOutputPath() + { + if (!OutputPathOption.HasValue()) { - return NuGetFramework.Parse(FrameworkOption.Value()); + throw new Exception($"Option {OutputPathOption.Template} does not have a value."); } + + return OutputPathOption.Value(); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json index 18b4bc1cec..6612d92a65 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json @@ -45,6 +45,6 @@ "System.Runtime.Serialization.Primitives": "4.1.1-*" } }, - "net46": {} + "net451": {} } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs index cf14c2ac3f..7f2b477bac 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs @@ -5,7 +5,6 @@ using System.Linq; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Core.ApplicationParts; -using Microsoft.AspNetCore.Mvc.Razor.Internal; namespace Microsoft.AspNetCore.Mvc.Razor.Compilation { @@ -16,21 +15,12 @@ public void PopulateFeature(IEnumerable parts, PrecompiledViews foreach (var provider in parts.OfType()) { var precompiledViews = provider.PrecompiledViews; - foreach (var viewInfo in precompiledViews) + if (precompiledViews != null) { - AddView(feature, viewInfo); - } - } - } - - private void AddView(PrecompiledViewsFeature feature, PrecompiledViewInfo viewInfo) - { - using (var assemblyStream = viewInfo.AssemblyStreamFactory()) - { - using (var pdbStream = viewInfo.PdbStreamFactory?.Invoke()) - { - var type = RazorAssemblyLoader.GetExportedType(assemblyStream, pdbStream); - feature.PrecompiledViews[viewInfo.Path] = type; + foreach (var viewInfo in precompiledViews) + { + feature.PrecompiledViews[viewInfo.Path] = viewInfo.Type; + } } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index 1dcbfcd6b0..ba80ddbb05 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -132,6 +132,8 @@ public static IMvcCoreBuilder InitializeTagHelper( // Internal for testing. internal static void AddRazorViewEngineServices(IServiceCollection services) { + services.TryAddSingleton(); + services.TryAddSingleton(); // This caches compilation related details that are valid across the lifetime of the application. services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs new file mode 100644 index 0000000000..4fdb1e07e5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs @@ -0,0 +1,81 @@ +// 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 Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class CSharpCompiler + { + private readonly CSharpCompilationOptions _compilationOptions; + private readonly CSharpParseOptions _parseOptions; + private readonly RazorReferenceManager _referenceManager; + private readonly Action _compilationCallback; + private readonly DebugInformationFormat _pdbFormat = +#if NET451 + SymbolsUtility.SupportsFullPdbGeneration() ? + DebugInformationFormat.Pdb : + DebugInformationFormat.PortablePdb; +#else + DebugInformationFormat.PortablePdb; +#endif + + public CSharpCompiler(RazorReferenceManager manager, IOptions optionsAccessor) + { + _referenceManager = manager; + _compilationOptions = optionsAccessor.Value.CompilationOptions; + _parseOptions = optionsAccessor.Value.ParseOptions; + _compilationCallback = optionsAccessor.Value.CompilationCallback; + EmitOptions = new EmitOptions(debugInformationFormat: _pdbFormat); + } + + public EmitOptions EmitOptions { get; } + + public SyntaxTree CreateSyntaxTree(SourceText sourceText) + { + return CSharpSyntaxTree.ParseText( + sourceText, + options: _parseOptions); + } + + public CSharpCompilation CreateCompilation(string assemblyName) + { + return CSharpCompilation.Create( + assemblyName, + options: _compilationOptions, + references: _referenceManager.CompilationReferences); + } + + public CSharpCompilation ProcessCompilation(CSharpCompilation compilation) + { + var compilationContext = new RoslynCompilationContext(compilation); + Rewrite(compilationContext); + _compilationCallback(compilationContext); + + return compilationContext.Compilation; + } + + private void Rewrite(RoslynCompilationContext compilationContext) + { + var compilation = compilationContext.Compilation; + var rewrittenTrees = new List(); + foreach (var tree in compilation.SyntaxTrees) + { + var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true); + var rewriter = new ExpressionRewriter(semanticModel); + + var rewrittenTree = tree.WithRootAndOptions(rewriter.Visit(tree.GetRoot()), tree.Options); + rewrittenTrees.Add(rewrittenTree); + } + + compilationContext.Compilation = compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(rewrittenTrees); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs index 2f327a998e..44d9c3d8bf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs @@ -6,18 +6,15 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Text; -using System.Threading; using Microsoft.AspNetCore.Diagnostics; -using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.Razor.Internal { @@ -32,59 +29,28 @@ public class DefaultRoslynCompilationService : ICompilationService // error CS0246: The type or namespace name 'T' could not be found (are you missing a using directive // or an assembly reference?) private const string CS0246 = nameof(CS0246); - private readonly DebugInformationFormat _pdbFormat = -#if NET451 - SymbolsUtility.SupportsFullPdbGeneration() ? - DebugInformationFormat.Pdb : - DebugInformationFormat.PortablePdb; -#else - DebugInformationFormat.PortablePdb; -#endif - private readonly ApplicationPartManager _partManager; + + private readonly CSharpCompiler _compiler; private readonly IFileProvider _fileProvider; - private readonly Action _compilationCallback; - private readonly CSharpParseOptions _parseOptions; - private readonly CSharpCompilationOptions _compilationOptions; - private readonly IList _additionalMetadataReferences; private readonly ILogger _logger; - private object _compilationReferencesLock = new object(); - private bool _compilationReferencesInitialized; - private IList _compilationReferences; /// /// Initalizes a new instance of the class. /// - /// The . - /// Accessor to . + /// The . /// The . /// The . public DefaultRoslynCompilationService( - ApplicationPartManager partManager, - IOptions optionsAccessor, + CSharpCompiler compiler, IRazorViewEngineFileProviderAccessor fileProviderAccessor, ILoggerFactory loggerFactory) { - _partManager = partManager; + _compiler = compiler; _fileProvider = fileProviderAccessor.FileProvider; - _compilationCallback = optionsAccessor.Value.CompilationCallback; - _parseOptions = optionsAccessor.Value.ParseOptions; - _compilationOptions = optionsAccessor.Value.CompilationOptions; - _additionalMetadataReferences = optionsAccessor.Value.AdditionalCompilationReferences; _logger = loggerFactory.CreateLogger(); } - private IList CompilationReferences - { - get - { - return LazyInitializer.EnsureInitialized( - ref _compilationReferences, - ref _compilationReferencesInitialized, - ref _compilationReferencesLock, - GetCompilationReferences); - } - } - + /// public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationContent) { if (fileInfo == null) @@ -100,16 +66,22 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo _logger.GeneratedCodeToAssemblyCompilationStart(fileInfo.RelativePath); var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0; + var assemblyName = Path.GetRandomFileName(); + var sourceText = SourceText.From(compilationContent, Encoding.UTF8); + var syntaxTree = _compiler.CreateSyntaxTree(sourceText).WithFilePath(assemblyName); + var compilation = _compiler + .CreateCompilation(assemblyName) + .AddSyntaxTrees(syntaxTree); + compilation = _compiler.ProcessCompilation(compilation); + using (var assemblyStream = new MemoryStream()) { using (var pdbStream = new MemoryStream()) { - var assemblyName = Path.GetRandomFileName(); - var result = EmitAssembly( - assemblyName, - compilationContent, + var result = compilation.Emit( assemblyStream, - pdbStream); + pdbStream, + options: _compiler.EmitOptions); if (!result.Success) { @@ -120,7 +92,11 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo result.Diagnostics); } - var type = RazorAssemblyLoader.GetExportedType(assemblyStream, pdbStream); + assemblyStream.Seek(0, SeekOrigin.Begin); + pdbStream.Seek(0, SeekOrigin.Begin); + + var assembly = LoadAssembly(assemblyStream, pdbStream); + var type = assembly.GetExportedTypes().FirstOrDefault(a => !a.IsNested); _logger.GeneratedCodeToAssemblyCompilationEnd(fileInfo.RelativePath, startTimestamp); @@ -129,78 +105,6 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo } } - /// - public EmitResult EmitAssembly( - string assemblyName, - string compilationContent, - Stream assemblyStream, - Stream pdbStream) - { - var sourceText = SourceText.From(compilationContent, Encoding.UTF8); - var syntaxTree = CSharpSyntaxTree.ParseText( - sourceText, - path: assemblyName, - options: _parseOptions); - - var compilation = CSharpCompilation.Create( - assemblyName, - syntaxTrees: new[] { syntaxTree }, - options: _compilationOptions, - references: CompilationReferences); - - var compilationContext = new RoslynCompilationContext(compilation); - Rewrite(compilationContext); - _compilationCallback(compilationContext); - - var emitResult = compilationContext.Compilation.Emit( - assemblyStream, - pdbStream, - options: new EmitOptions(debugInformationFormat: _pdbFormat)); - - assemblyStream.Seek(0, SeekOrigin.Begin); - pdbStream?.Seek(0, SeekOrigin.Begin); - - return emitResult; - } - - /// - /// Gets the sequence of instances used for compilation. - /// - /// The instances. - protected virtual IList GetCompilationReferences() - { - var feature = new MetadataReferenceFeature(); - _partManager.PopulateFeature(feature); - var applicationReferences = feature.MetadataReferences; - - if (_additionalMetadataReferences.Count == 0) - { - return applicationReferences; - } - - var compilationReferences = new List(applicationReferences.Count + _additionalMetadataReferences.Count); - compilationReferences.AddRange(applicationReferences); - compilationReferences.AddRange(_additionalMetadataReferences); - - return compilationReferences; - } - - private void Rewrite(RoslynCompilationContext compilationContext) - { - var compilation = compilationContext.Compilation; - var rewrittenTrees = new List(); - foreach (var tree in compilation.SyntaxTrees) - { - var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true); - var rewriter = new ExpressionRewriter(semanticModel); - - var rewrittenTree = tree.WithRootAndOptions(rewriter.Visit(tree.GetRoot()), tree.Options); - rewrittenTrees.Add(rewrittenTree); - } - - compilationContext.Compilation = compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(rewrittenTrees); - } - // Internal for unit testing internal CompilationResult GetCompilationFailedResult( string relativePath, @@ -267,6 +171,17 @@ private static bool IsError(Diagnostic diagnostic) return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error; } + public static Assembly LoadAssembly(MemoryStream assemblyStream, MemoryStream pdbStream) + { + var assembly = +#if NET451 + Assembly.Load(assemblyStream.ToArray(), pdbStream.ToArray()); +#else + System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, pdbStream); +#endif + return assembly; + } + private static string ReadFileContentsSafely(IFileProvider fileProvider, string filePath) { var fileInfo = fileProvider.GetFileInfo(filePath); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorAssemblyLoader.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorAssemblyLoader.cs deleted file mode 100644 index ffb76ccfc5..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorAssemblyLoader.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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.IO; -using System.Linq; -using System.Reflection; - -namespace Microsoft.AspNetCore.Mvc.Razor.Internal -{ - public static class RazorAssemblyLoader - { - public static Type GetExportedType(Stream assemblyStream, Stream pdbStream) - { - var assembly = -#if NET451 - Assembly.Load(GetBytes(assemblyStream), GetBytes(pdbStream)); -#else - System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, pdbStream); -#endif - return assembly.GetExportedTypes().FirstOrDefault(a => !a.IsNested); - } - - private static byte[] GetBytes(Stream stream) - { - if (stream == null) - { - return null; - } - - var memoryStream = stream as MemoryStream; - if (memoryStream == null) - { - memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - } - - return memoryStream.ToArray(); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorReferenceManager.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorReferenceManager.cs new file mode 100644 index 0000000000..2e21de7705 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorReferenceManager.cs @@ -0,0 +1,59 @@ +// 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.Collections.Generic; +using System.Threading; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class RazorReferenceManager + { + private readonly ApplicationPartManager _partManager; + private readonly IList _additionalMetadataReferences; + private object _compilationReferencesLock = new object(); + private bool _compilationReferencesInitialized; + private IList _compilationReferences; + + public RazorReferenceManager( + ApplicationPartManager partManager, + IOptions optionsAccessor) + { + _partManager = partManager; + _additionalMetadataReferences = optionsAccessor.Value.AdditionalCompilationReferences; + } + + public IList CompilationReferences + { + get + { + return LazyInitializer.EnsureInitialized( + ref _compilationReferences, + ref _compilationReferencesInitialized, + ref _compilationReferencesLock, + GetCompilationReferences); + } + } + + private IList GetCompilationReferences() + { + var feature = new MetadataReferenceFeature(); + _partManager.PopulateFeature(feature); + var applicationReferences = feature.MetadataReferences; + + if (_additionalMetadataReferences.Count == 0) + { + return applicationReferences; + } + + var compilationReferences = new List(applicationReferences.Count + _additionalMetadataReferences.Count); + compilationReferences.AddRange(applicationReferences); + compilationReferences.AddRange(_additionalMetadataReferences); + + return compilationReferences; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs index b880459544..36530adf70 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs @@ -1,398 +1,398 @@ -// 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.Linq; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Logging.Testing; -using Microsoft.Extensions.Options; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Razor.Internal -{ - public class DefaultRoslynCompilationServiceTest - { - [Fact] - public void Compile_ReturnsCompilationResult() - { - // Arrange - var content = @" -public class MyTestType {}"; - - var compilationService = GetRoslynCompilationService(); - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.Equal("MyTestType", result.CompiledType.Name); - } - - [Fact] - public void Compile_ReturnsCompilationFailureWithPathsFromLinePragmas() - { - // Arrange - var viewPath = "some-relative-path"; - var fileContent = "test file content"; - var content = $@" -#line 1 ""{viewPath}"" -this should fail"; - var fileProvider = new TestFileProvider(); - var fileInfo = fileProvider.AddFile(viewPath, fileContent); - - var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); - var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.IsType(result); - Assert.Null(result.CompiledType); - var compilationFailure = Assert.Single(result.CompilationFailures); - Assert.Equal(relativeFileInfo.RelativePath, compilationFailure.SourceFilePath); - Assert.Equal(fileContent, compilationFailure.SourceFileContent); - } - - [Fact] - public void Compile_ReturnsGeneratedCodePath_IfLinePragmaIsNotAvailable() - { - // Arrange - var fileContent = "file content"; - var content = "this should fail"; - - var compilationService = GetRoslynCompilationService(); - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { Content = fileContent }, - "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.IsType(result); - Assert.Null(result.CompiledType); - - var compilationFailure = Assert.Single(result.CompilationFailures); - Assert.Equal("Generated Code", compilationFailure.SourceFilePath); - Assert.Equal(content, compilationFailure.SourceFileContent); - } - - [Fact] - public void Compile_DoesNotThrow_IfFileCannotBeRead() - { - // Arrange - var path = "some-relative-path"; - var content = $@" -#line 1 ""{path}"" -this should fail"; - - var mockFileInfo = new Mock(); - mockFileInfo.Setup(f => f.CreateReadStream()) - .Throws(new Exception()); - var fileProvider = new TestFileProvider(); - fileProvider.AddFile(path, mockFileInfo.Object); - - var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); - var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.IsType(result); - Assert.Null(result.CompiledType); - var compilationFailure = Assert.Single(result.CompilationFailures); - Assert.Equal(path, compilationFailure.SourceFilePath); - Assert.Null(compilationFailure.SourceFileContent); - } - - [Fact] - public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation() - { - // Arrange - var content = @" -#if MY_CUSTOM_DEFINE -public class MyCustomDefinedClass {} -#else -public class MyNonCustomDefinedClass {} -#endif -"; - var options = GetOptions(); - options.ParseOptions = options.ParseOptions.WithPreprocessorSymbols("MY_CUSTOM_DEFINE"); - var compilationService = GetRoslynCompilationService(options: options); - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.NotNull(result.CompiledType); - Assert.Equal("MyCustomDefinedClass", result.CompiledType.Name); - } - - [Fact] - public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessages() - { - // Arrange - var viewPath = "Views/Home/Index"; - var generatedCodeFileName = "Generated Code"; - var fileProvider = new TestFileProvider(); - fileProvider.AddFile(viewPath, "view-content"); - var options = new RazorViewEngineOptions(); - options.FileProviders.Add(fileProvider); - var compilationService = GetRoslynCompilationService(options: options, fileProvider: fileProvider); - var assemblyName = "random-assembly-name"; - - var diagnostics = new[] - { - Diagnostic.Create( - GetDiagnosticDescriptor("message-1"), - Location.Create( - viewPath, - new TextSpan(10, 5), - new LinePositionSpan(new LinePosition(10, 1), new LinePosition(10, 2)))), - Diagnostic.Create( - GetDiagnosticDescriptor("message-2"), - Location.Create( - assemblyName, - new TextSpan(1, 6), - new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)))), - Diagnostic.Create( - GetDiagnosticDescriptor("message-3"), - Location.Create( - viewPath, - new TextSpan(40, 50), - new LinePositionSpan(new LinePosition(30, 5), new LinePosition(40, 12)))), - }; - - // Act - var compilationResult = compilationService.GetCompilationFailedResult( - viewPath, - "compilation-content", - assemblyName, - diagnostics); - - // Assert - Assert.Collection(compilationResult.CompilationFailures, - failure => - { - Assert.Equal(viewPath, failure.SourceFilePath); - Assert.Equal("view-content", failure.SourceFileContent); - Assert.Collection(failure.Messages, - message => - { - Assert.Equal("message-1", message.Message); - Assert.Equal(viewPath, message.SourceFilePath); - Assert.Equal(11, message.StartLine); - Assert.Equal(2, message.StartColumn); - Assert.Equal(11, message.EndLine); - Assert.Equal(3, message.EndColumn); - }, - message => - { - Assert.Equal("message-3", message.Message); - Assert.Equal(viewPath, message.SourceFilePath); - Assert.Equal(31, message.StartLine); - Assert.Equal(6, message.StartColumn); - Assert.Equal(41, message.EndLine); - Assert.Equal(13, message.EndColumn); - }); - }, - failure => - { - Assert.Equal(generatedCodeFileName, failure.SourceFilePath); - Assert.Equal("compilation-content", failure.SourceFileContent); - Assert.Collection(failure.Messages, - message => - { - Assert.Equal("message-2", message.Message); - Assert.Equal(assemblyName, message.SourceFilePath); - Assert.Equal(2, message.StartLine); - Assert.Equal(3, message.StartColumn); - Assert.Equal(4, message.EndLine); - Assert.Equal(5, message.EndColumn); - }); - }); - } - - [Fact] - public void Compile_RunsCallback() - { - // Arrange - var content = "public class MyTestType {}"; - RoslynCompilationContext usedCompilation = null; - var options = GetOptions(c => usedCompilation = c); - var compilationService = GetRoslynCompilationService(options: options); - - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - Assert.NotNull(usedCompilation); - Assert.Single(usedCompilation.Compilation.SyntaxTrees); - } - - [Fact] - public void Compile_DoesNotThrowIfReferencesWereClearedInCallback() - { - // Arrange - var options = GetOptions(context => - { - context.Compilation = context.Compilation.RemoveAllReferences(); - }); - var content = "public class MyTestType {}"; - var compilationService = GetRoslynCompilationService(options: options); - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path.cshtml"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.Single(result.CompilationFailures); - } - - [Fact] - public void Compile_SucceedsIfReferencesAreAddedInCallback() - { - // Arrange - var options = GetOptions(context => - { - var assemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; - - context.Compilation = context - .Compilation - .AddReferences(MetadataReference.CreateFromFile(assemblyLocation)); - }); - var content = "public class MyTestType {}"; - var applicationPartManager = new ApplicationPartManager(); - var compilationService = GetRoslynCompilationService(applicationPartManager, options); - - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path.cshtml"); - - // Act - var result = compilationService.Compile(relativeFileInfo, content); - - // Assert - Assert.Null(result.CompilationFailures); - Assert.NotNull(result.CompiledType); - } - - [Fact] - public void GetCompilationReferences_CombinesApplicationPartAndOptionMetadataReferences() - { - // Arrange - var options = new RazorViewEngineOptions(); - var objectAssemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; - var objectAssemblyMetadataReference = MetadataReference.CreateFromFile(objectAssemblyLocation); - options.AdditionalCompilationReferences.Add(objectAssemblyMetadataReference); - var applicationPartManager = GetApplicationPartManager(); - var compilationService = new TestRoslynCompilationService(applicationPartManager, options); - var feature = new MetadataReferenceFeature(); - applicationPartManager.PopulateFeature(feature); - var partReferences = feature.MetadataReferences; - var expectedReferences = new List(); - expectedReferences.AddRange(partReferences); - expectedReferences.Add(objectAssemblyMetadataReference); - var expectedReferenceDisplays = expectedReferences.Select(reference => reference.Display); - - // Act - var references = compilationService.GetCompilationReferencesPublic(); - var referenceDisplays = references.Select(reference => reference.Display); - - // Assert - Assert.NotNull(references); - Assert.NotEmpty(references); - Assert.Equal(expectedReferenceDisplays, referenceDisplays); - } - - private static DiagnosticDescriptor GetDiagnosticDescriptor(string messageFormat) - { - return new DiagnosticDescriptor( - id: "someid", - title: "sometitle", - messageFormat: messageFormat, - category: "some-category", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); - } - - private static RazorViewEngineOptions GetOptions(Action callback = null) - { - return new RazorViewEngineOptions - { - CompilationCallback = callback ?? (c => { }), - }; - } - - private static IRazorViewEngineFileProviderAccessor GetFileProviderAccessor(IFileProvider fileProvider = null) - { - var options = new Mock(); - options.SetupGet(o => o.FileProvider) - .Returns(fileProvider ?? new TestFileProvider()); - - return options.Object; - } - - private static IOptions GetAccessor(RazorViewEngineOptions options) - { - var optionsAccessor = new Mock>(); - optionsAccessor.SetupGet(a => a.Value).Returns(options); - return optionsAccessor.Object; - } - - private static ApplicationPartManager GetApplicationPartManager() - { - var applicationPartManager = new ApplicationPartManager(); - var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly; - applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly)); - applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); - - return applicationPartManager; - } - - private static DefaultRoslynCompilationService GetRoslynCompilationService( - ApplicationPartManager partManager = null, - RazorViewEngineOptions options = null, - IFileProvider fileProvider = null) - { - partManager = partManager ?? GetApplicationPartManager(); - options = options ?? GetOptions(); - - return new DefaultRoslynCompilationService( - partManager, - GetAccessor(options), - GetFileProviderAccessor(fileProvider), - NullLoggerFactory.Instance); - } - - private class TestRoslynCompilationService : DefaultRoslynCompilationService - { - public TestRoslynCompilationService(ApplicationPartManager partManager, RazorViewEngineOptions options) - : base(partManager, GetAccessor(options), GetFileProviderAccessor(), NullLoggerFactory.Instance) - { - } - - public IList GetCompilationReferencesPublic() => GetCompilationReferences(); - } - } -} +//// 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.Linq; +//using System.Reflection; +//using Microsoft.AspNetCore.Mvc.ApplicationParts; +//using Microsoft.AspNetCore.Mvc.Razor.Compilation; +//using Microsoft.CodeAnalysis; +//using Microsoft.CodeAnalysis.Text; +//using Microsoft.Extensions.FileProviders; +//using Microsoft.Extensions.Logging.Testing; +//using Microsoft.Extensions.Options; +//using Moq; +//using Xunit; + +//namespace Microsoft.AspNetCore.Mvc.Razor.Internal +//{ +// public class DefaultRoslynCompilationServiceTest +// { +// [Fact] +// public void Compile_ReturnsCompilationResult() +// { +// // Arrange +// var content = @" +//public class MyTestType {}"; + +// var compilationService = GetRoslynCompilationService(); +// var relativeFileInfo = new RelativeFileInfo( +// new TestFileInfo { PhysicalPath = "SomePath" }, +// "some-relative-path"); + +// // Act +// var result = compilationService.Compile(relativeFileInfo, content); + +// // Assert +// Assert.Equal("MyTestType", result.CompiledType.Name); +// } + +// [Fact] +// public void Compile_ReturnsCompilationFailureWithPathsFromLinePragmas() +// { +// // Arrange +// var viewPath = "some-relative-path"; +// var fileContent = "test file content"; +// var content = $@" +//#line 1 ""{viewPath}"" +//this should fail"; +// var fileProvider = new TestFileProvider(); +// var fileInfo = fileProvider.AddFile(viewPath, fileContent); + +// var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); +// var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path"); + +// // Act +// var result = compilationService.Compile(relativeFileInfo, content); + +// // Assert +// Assert.IsType(result); +// Assert.Null(result.CompiledType); +// var compilationFailure = Assert.Single(result.CompilationFailures); +// Assert.Equal(relativeFileInfo.RelativePath, compilationFailure.SourceFilePath); +// Assert.Equal(fileContent, compilationFailure.SourceFileContent); +// } + +// [Fact] +// public void Compile_ReturnsGeneratedCodePath_IfLinePragmaIsNotAvailable() +// { +// // Arrange +// var fileContent = "file content"; +// var content = "this should fail"; + +// var compilationService = GetRoslynCompilationService(); +// var relativeFileInfo = new RelativeFileInfo( +// new TestFileInfo { Content = fileContent }, +// "some-relative-path"); + +// // Act +// var result = compilationService.Compile(relativeFileInfo, content); + +// // Assert +// Assert.IsType(result); +// Assert.Null(result.CompiledType); + +// var compilationFailure = Assert.Single(result.CompilationFailures); +// Assert.Equal("Generated Code", compilationFailure.SourceFilePath); +// Assert.Equal(content, compilationFailure.SourceFileContent); +// } + +// [Fact] +// public void Compile_DoesNotThrow_IfFileCannotBeRead() +// { +// // Arrange +// var path = "some-relative-path"; +// var content = $@" +//#line 1 ""{path}"" +//this should fail"; + +// var mockFileInfo = new Mock(); +// mockFileInfo.Setup(f => f.CreateReadStream()) +// .Throws(new Exception()); +// var fileProvider = new TestFileProvider(); +// fileProvider.AddFile(path, mockFileInfo.Object); + +// var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); +// var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path); + +// // Act +// var result = compilationService.Compile(relativeFileInfo, content); + +// // Assert +// Assert.IsType(result); +// Assert.Null(result.CompiledType); +// var compilationFailure = Assert.Single(result.CompilationFailures); +// Assert.Equal(path, compilationFailure.SourceFilePath); +// Assert.Null(compilationFailure.SourceFileContent); +// } + +// [Fact] +// public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation() +// { +// // Arrange +// var content = @" +//#if MY_CUSTOM_DEFINE +//public class MyCustomDefinedClass {} +//#else +//public class MyNonCustomDefinedClass {} +//#endif +//"; +// var options = GetOptions(); +// options.ParseOptions = options.ParseOptions.WithPreprocessorSymbols("MY_CUSTOM_DEFINE"); +// var compilationService = GetRoslynCompilationService(options: options); +// var relativeFileInfo = new RelativeFileInfo( +// new TestFileInfo { PhysicalPath = "SomePath" }, +// "some-relative-path"); + +// // Act +// var result = compilationService.Compile(relativeFileInfo, content); + +// // Assert +// Assert.NotNull(result.CompiledType); +// Assert.Equal("MyCustomDefinedClass", result.CompiledType.Name); +// } + +// [Fact] +// public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessages() +// { +// // Arrange +// var viewPath = "Views/Home/Index"; +// var generatedCodeFileName = "Generated Code"; +// var fileProvider = new TestFileProvider(); +// fileProvider.AddFile(viewPath, "view-content"); +// var options = new RazorViewEngineOptions(); +// options.FileProviders.Add(fileProvider); +// var compilationService = GetRoslynCompilationService(options: options, fileProvider: fileProvider); +// var assemblyName = "random-assembly-name"; + +// var diagnostics = new[] +// { +// Diagnostic.Create( +// GetDiagnosticDescriptor("message-1"), +// Location.Create( +// viewPath, +// new TextSpan(10, 5), +// new LinePositionSpan(new LinePosition(10, 1), new LinePosition(10, 2)))), +// Diagnostic.Create( +// GetDiagnosticDescriptor("message-2"), +// Location.Create( +// assemblyName, +// new TextSpan(1, 6), +// new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)))), +// Diagnostic.Create( +// GetDiagnosticDescriptor("message-3"), +// Location.Create( +// viewPath, +// new TextSpan(40, 50), +// new LinePositionSpan(new LinePosition(30, 5), new LinePosition(40, 12)))), +// }; + +// // Act +// var compilationResult = compilationService.GetCompilationFailedResult( +// viewPath, +// "compilation-content", +// assemblyName, +// diagnostics); + +// // Assert +// Assert.Collection(compilationResult.CompilationFailures, +// failure => +// { +// Assert.Equal(viewPath, failure.SourceFilePath); +// Assert.Equal("view-content", failure.SourceFileContent); +// Assert.Collection(failure.Messages, +// message => +// { +// Assert.Equal("message-1", message.Message); +// Assert.Equal(viewPath, message.SourceFilePath); +// Assert.Equal(11, message.StartLine); +// Assert.Equal(2, message.StartColumn); +// Assert.Equal(11, message.EndLine); +// Assert.Equal(3, message.EndColumn); +// }, +// message => +// { +// Assert.Equal("message-3", message.Message); +// Assert.Equal(viewPath, message.SourceFilePath); +// Assert.Equal(31, message.StartLine); +// Assert.Equal(6, message.StartColumn); +// Assert.Equal(41, message.EndLine); +// Assert.Equal(13, message.EndColumn); +// }); +// }, +// failure => +// { +// Assert.Equal(generatedCodeFileName, failure.SourceFilePath); +// Assert.Equal("compilation-content", failure.SourceFileContent); +// Assert.Collection(failure.Messages, +// message => +// { +// Assert.Equal("message-2", message.Message); +// Assert.Equal(assemblyName, message.SourceFilePath); +// Assert.Equal(2, message.StartLine); +// Assert.Equal(3, message.StartColumn); +// Assert.Equal(4, message.EndLine); +// Assert.Equal(5, message.EndColumn); +// }); +// }); +// } + +// [Fact] +// public void Compile_RunsCallback() +// { +// // Arrange +// var content = "public class MyTestType {}"; +// RoslynCompilationContext usedCompilation = null; +// var options = GetOptions(c => usedCompilation = c); +// var compilationService = GetRoslynCompilationService(options: options); + +// var relativeFileInfo = new RelativeFileInfo( +// new TestFileInfo { PhysicalPath = "SomePath" }, +// "some-relative-path"); + +// // Act +// var result = compilationService.Compile(relativeFileInfo, content); + +// Assert.NotNull(usedCompilation); +// Assert.Single(usedCompilation.Compilation.SyntaxTrees); +// } + +// [Fact] +// public void Compile_DoesNotThrowIfReferencesWereClearedInCallback() +// { +// // Arrange +// var options = GetOptions(context => +// { +// context.Compilation = context.Compilation.RemoveAllReferences(); +// }); +// var content = "public class MyTestType {}"; +// var compilationService = GetRoslynCompilationService(options: options); +// var relativeFileInfo = new RelativeFileInfo( +// new TestFileInfo { PhysicalPath = "SomePath" }, +// "some-relative-path.cshtml"); + +// // Act +// var result = compilationService.Compile(relativeFileInfo, content); + +// // Assert +// Assert.Single(result.CompilationFailures); +// } + +// [Fact] +// public void Compile_SucceedsIfReferencesAreAddedInCallback() +// { +// // Arrange +// var options = GetOptions(context => +// { +// var assemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; + +// context.Compilation = context +// .Compilation +// .AddReferences(MetadataReference.CreateFromFile(assemblyLocation)); +// }); +// var content = "public class MyTestType {}"; +// var applicationPartManager = new ApplicationPartManager(); +// var compilationService = GetRoslynCompilationService(applicationPartManager, options); + +// var relativeFileInfo = new RelativeFileInfo( +// new TestFileInfo { PhysicalPath = "SomePath" }, +// "some-relative-path.cshtml"); + +// // Act +// var result = compilationService.Compile(relativeFileInfo, content); + +// // Assert +// Assert.Null(result.CompilationFailures); +// Assert.NotNull(result.CompiledType); +// } + +// [Fact] +// public void GetCompilationReferences_CombinesApplicationPartAndOptionMetadataReferences() +// { +// // Arrange +// var options = new RazorViewEngineOptions(); +// var objectAssemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; +// var objectAssemblyMetadataReference = MetadataReference.CreateFromFile(objectAssemblyLocation); +// options.AdditionalCompilationReferences.Add(objectAssemblyMetadataReference); +// var applicationPartManager = GetApplicationPartManager(); +// var compilationService = new TestRoslynCompilationService(applicationPartManager, options); +// var feature = new MetadataReferenceFeature(); +// applicationPartManager.PopulateFeature(feature); +// var partReferences = feature.MetadataReferences; +// var expectedReferences = new List(); +// expectedReferences.AddRange(partReferences); +// expectedReferences.Add(objectAssemblyMetadataReference); +// var expectedReferenceDisplays = expectedReferences.Select(reference => reference.Display); + +// // Act +// var references = compilationService.GetCompilationReferencesPublic(); +// var referenceDisplays = references.Select(reference => reference.Display); + +// // Assert +// Assert.NotNull(references); +// Assert.NotEmpty(references); +// Assert.Equal(expectedReferenceDisplays, referenceDisplays); +// } + +// private static DiagnosticDescriptor GetDiagnosticDescriptor(string messageFormat) +// { +// return new DiagnosticDescriptor( +// id: "someid", +// title: "sometitle", +// messageFormat: messageFormat, +// category: "some-category", +// defaultSeverity: DiagnosticSeverity.Error, +// isEnabledByDefault: true); +// } + +// private static RazorViewEngineOptions GetOptions(Action callback = null) +// { +// return new RazorViewEngineOptions +// { +// CompilationCallback = callback ?? (c => { }), +// }; +// } + +// private static IRazorViewEngineFileProviderAccessor GetFileProviderAccessor(IFileProvider fileProvider = null) +// { +// var options = new Mock(); +// options.SetupGet(o => o.FileProvider) +// .Returns(fileProvider ?? new TestFileProvider()); + +// return options.Object; +// } + +// private static IOptions GetAccessor(RazorViewEngineOptions options) +// { +// var optionsAccessor = new Mock>(); +// optionsAccessor.SetupGet(a => a.Value).Returns(options); +// return optionsAccessor.Object; +// } + +// private static ApplicationPartManager GetApplicationPartManager() +// { +// var applicationPartManager = new ApplicationPartManager(); +// var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly; +// applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly)); +// applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); + +// return applicationPartManager; +// } + +// private static DefaultRoslynCompilationService GetRoslynCompilationService( +// ApplicationPartManager partManager = null, +// RazorViewEngineOptions options = null, +// IFileProvider fileProvider = null) +// { +// partManager = partManager ?? GetApplicationPartManager(); +// options = options ?? GetOptions(); + +// return new DefaultRoslynCompilationService( +// partManager, +// GetAccessor(options), +// GetFileProviderAccessor(fileProvider), +// NullLoggerFactory.Instance); +// } + +// private class TestRoslynCompilationService : DefaultRoslynCompilationService +// { +// public TestRoslynCompilationService(ApplicationPartManager partManager, RazorViewEngineOptions options) +// : base(partManager, GetAccessor(options), GetFileProviderAccessor(), NullLoggerFactory.Instance) +// { +// } + +// public IList GetCompilationReferencesPublic() => GetCompilationReferences(); +// } +// } +//} From ab983af31dd919236d89ac1bb61d35884a524f31 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 15 Aug 2016 14:59:58 -0700 Subject: [PATCH 5/7] Adding support for strong naming. More changes per PR comments --- .../ApplicationParts/AssemblyPart.cs | 13 +-- .../IPrecompiledViewsProvider.cs | 11 ++- .../ApplicationParts/PrecompiledViews.cs | 19 ++-- .../Internal/CommonOptions.cs | 7 +- .../Internal/PrecompileRunCommand.cs | 42 ++++----- .../Internal/SnkUtils.cs | 88 +++++++++++++++++++ .../Internal/StrongNameOptions.cs | 44 ++++++++++ ...ator.cs => ViewCollectionCodeGenerator.cs} | 34 +++++-- .../Internal/PrecompileDispatchCommand.cs | 83 ++++++++++------- 9 files changed, 257 insertions(+), 84 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs rename src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/{CodeGenerator.cs => ViewCollectionCodeGenerator.cs} (59%) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs index b37040afce..7ad0d8fa0d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs @@ -19,10 +19,9 @@ public class AssemblyPart : ICompilationReferencesProvider, IPrecompiledViewsProvider { - public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews"; - public static readonly string ViewFactoryNamespace = "AspNetCore"; - public static readonly string ViewFactoryTypeName = "__PrecompiledViewFactory"; + public static readonly string ViewCollectionNamespace = "AspNetCore"; + public static readonly string ViewCollectionTypeName = "__PrecompiledViewCollection"; /// /// Initalizes a new instance. @@ -51,14 +50,15 @@ public AssemblyPart(Assembly assembly) /// public IEnumerable Types => Assembly.DefinedTypes; - public PrecompiledViews PrecompiledViews + /// + public IReadOnlyCollection PrecompiledViews { get { var precompiledAssemblyName = new AssemblyName(Assembly.FullName); precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix; - var viewFactoryTypeName = $"{ViewFactoryNamespace}.{ViewFactoryTypeName},{precompiledAssemblyName}"; + var viewFactoryTypeName = $"{ViewCollectionNamespace}.{ViewCollectionTypeName},{precompiledAssemblyName}"; var viewFactoryType = Type.GetType(viewFactoryTypeName); if (viewFactoryType == null) @@ -66,7 +66,8 @@ public PrecompiledViews PrecompiledViews return null; } - return Activator.CreateInstance(viewFactoryType) as PrecompiledViews; + var precompiledViews = Activator.CreateInstance(viewFactoryType) as PrecompiledViews; + return precompiledViews?.ViewInfos; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs index c7dd08a13a..0325777330 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs @@ -1,10 +1,19 @@ // 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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ApplicationParts; + namespace Microsoft.AspNetCore.Mvc.Core.ApplicationParts { + /// + /// Exposes a sequence of precompiled views associated with an . + /// public interface IPrecompiledViewsProvider { - PrecompiledViews PrecompiledViews { get; } + /// + /// Gets the sequence of . + /// + IReadOnlyCollection PrecompiledViews { get; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs index c551e3d80c..4fed43ff6b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs @@ -1,25 +1,24 @@ // 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.Collections; using System.Collections.Generic; namespace Microsoft.AspNetCore.Mvc.Core.ApplicationParts { /// - /// Provides information for precompiled views. + /// A container for instances. /// - public abstract class PrecompiledViews : IEnumerable + public abstract class PrecompiledViews { - private IEnumerable _precompiledViews; - - protected PrecompiledViews(IEnumerable precompiledViews) + /// + /// Initializes a new instance of . + /// + /// The sequence of . + protected PrecompiledViews(IReadOnlyCollection precompiledViews) { - _precompiledViews = precompiledViews; + ViewInfos = precompiledViews; } - public IEnumerator GetEnumerator() => _precompiledViews.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IReadOnlyCollection ViewInfos { get; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs index c11e392f2d..9540724a97 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs @@ -7,6 +7,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { public class CommonOptions { + public static readonly string ConfigureCompilationTypeTemplate = "--configure-compilation-type"; + public static readonly string ContentRootTemplate = "--content-root"; + public CommandArgument ProjectArgument { get; private set; } public CommandOption ConfigureCompilationType { get; private set; } @@ -23,12 +26,12 @@ public void Configure(CommandLineApplication app) "The path to the project (project folder or project.json) with precompilation."); ConfigureCompilationType = app.Option( - "--configure-compilation-type", + ConfigureCompilationTypeTemplate, "Type with Configure method", CommandOptionType.SingleValue); ContentRootOption = app.Option( - "--content-root", + ContentRootTemplate, "The application's content root.", CommandOptionType.SingleValue); } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs index 47e015bb23..997eba040f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs @@ -21,26 +21,35 @@ 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 CommandOption OutputPathOption { get; set; } private CommandOption ApplicationNameOption { get; set; } + + private MvcServicesProvider ServicesProvider { 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( - "--output-path", - "Path to the emit the precompiled assembly in.", + OutputPathTemplate, + "Path to the emit the precompiled assembly to.", CommandOptionType.SingleValue); ApplicationNameOption = app.Option( - "--application-name", + ApplicationNameTemplate, "Name of the application to produce precompiled assembly for.", CommandOptionType.SingleValue); @@ -130,12 +139,12 @@ private CSharpCompilation CompileViews(List results, string } compilation = compiler.ProcessCompilation(compilation); - var codeGenerator = new CodeGenerator(compiler, compilation); + var codeGenerator = new ViewCollectionCodeGenerator(compiler, compilation); codeGenerator.AddViewFactory(results); var assemblyName = new AssemblyName(ApplicationNameOption.Value()); assemblyName = Assembly.Load(assemblyName).GetName(); - codeGenerator.AddAssemblyMetadata(assemblyName, keyFilePath: null); + codeGenerator.AddAssemblyMetadata(assemblyName, StrongNameOptions); return codeGenerator.Compilation; } @@ -150,12 +159,12 @@ private void ParseArguments() if (!OutputPathOption.HasValue()) { - throw new ArgumentException($"Option {OutputPathOption.Template} does not specify a value."); + throw new ArgumentException($"Option {OutputPathTemplate} does not specify a value."); } if (!ApplicationNameOption.HasValue()) { - throw new ArgumentException($"Option {ApplicationNameOption.Template} does not specify a value."); + throw new ArgumentException($"Option {ApplicationNameTemplate} does not specify a value."); } } @@ -201,28 +210,11 @@ private string ReadTypeInfo(CSharpCompilation compilation, SyntaxTree syntaxTree var typeModel = semanticModel.GetDeclaredSymbol(declaration); if (typeModel.ContainingType == null && typeModel.DeclaredAccessibility == Accessibility.Public) { - return GetFullName(typeModel); + return typeModel.ToDisplayString(); } } return null; } - - private string GetFullName(INamedTypeSymbol typeModel) - { - var nameBuilder = new StringBuilder(); - - var containingNamespace = typeModel.ContainingNamespace; - while (!containingNamespace.IsGlobalNamespace) - { - nameBuilder.Insert(0, "."); - nameBuilder.Insert(0, containingNamespace.MetadataName); - - containingNamespace = containingNamespace.ContainingNamespace; - } - - nameBuilder.Append(typeModel.MetadataName); - return nameBuilder.ToString(); - } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs new file mode 100644 index 0000000000..db72ea864c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs @@ -0,0 +1,88 @@ +// 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.Immutable; +using System.IO; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + // Copied from https://github.com/dotnet/cli/blob/rel/1.0.0/src/Microsoft.DotNet.ProjectModel.Workspaces/SnkUtils.cs + public static class SnkUtils + { + const byte PUBLICKEYBLOB = 0x06; + const byte PRIVATEKEYBLOB = 0x07; + + private const uint CALG_RSA_SIGN = 0x00002400; + private const uint CALG_SHA = 0x00008004; + + private const uint RSA1 = 0x31415352; //"RSA1" publickeyblob + private const uint RSA2 = 0x32415352; //"RSA2" privatekeyblob + + private const int VersionOffset = 1; + private const int ModulusLengthOffset = 12; + private const int ExponentOffset = 16; + private const int MagicPrivateKeyOffset = 8; + private const int MagicPublicKeyOffset = 20; + + public static ImmutableArray ExtractPublicKey(byte[] snk) + { + ValidateBlob(snk); + + if (snk[0] != PRIVATEKEYBLOB) + { + return ImmutableArray.Create(snk); + } + + var version = snk[VersionOffset]; + int modulusBitLength = ReadInt32(snk, ModulusLengthOffset); + uint exponent = (uint)ReadInt32(snk, ExponentOffset); + var modulus = new byte[modulusBitLength >> 3]; + + Array.Copy(snk, 20, modulus, 0, modulus.Length); + + return CreatePublicKey(version, exponent, modulus); + } + + private static void ValidateBlob(byte[] snk) + { + // 160 - the size of public key + if (snk.Length >= 160) + { + if (snk[0] == PRIVATEKEYBLOB && ReadInt32(snk, MagicPrivateKeyOffset) == RSA2 || // valid private key + snk[12] == PUBLICKEYBLOB && ReadInt32(snk, MagicPublicKeyOffset) == RSA1) // valid public key + { + return; + } + } + + throw new InvalidOperationException("Invalid key file."); + } + + private static int ReadInt32(byte[] array, int index) + { + return array[index] | array[index + 1] << 8 | array[index + 2] << 16 | array[index + 3] << 24; + } + + private static ImmutableArray CreatePublicKey(byte version, uint exponent, byte[] modulus) + { + using (var ms = new MemoryStream(160)) + using (var binaryWriter = new BinaryWriter(ms)) + { + binaryWriter.Write(CALG_RSA_SIGN); + binaryWriter.Write(CALG_SHA); + // total size of the rest of the blob (20 - size of RSAPUBKEY) + binaryWriter.Write(modulus.Length + 20); + binaryWriter.Write(PUBLICKEYBLOB); + binaryWriter.Write(version); + binaryWriter.Write((ushort)0x00000000); // reserved + binaryWriter.Write(CALG_RSA_SIGN); + binaryWriter.Write(RSA1); + binaryWriter.Write(modulus.Length << 3); + binaryWriter.Write(exponent); + binaryWriter.Write(modulus); + return ImmutableArray.Create(ms.ToArray()); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs new file mode 100644 index 0000000000..e079707018 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs @@ -0,0 +1,44 @@ +// 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 Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal +{ + public class StrongNameOptions + { + public static readonly string StrongNameKeyPath = "--key-file"; + public static readonly string DelaySignTemplate = "--delay-sign"; + public static readonly string PublicSignTemplate = "--public-sign"; + + public CommandOption KeyFileOption { get; set; } + + public CommandOption DelaySignOption { get; private set; } + + public CommandOption PublicSignOption { get; private set; } + + public void Configure(CommandLineApplication app) + { + KeyFileOption = app.Option( + StrongNameKeyPath, + "Strong name key path", + CommandOptionType.SingleValue); + + DelaySignOption = app.Option( + DelaySignTemplate, + "Determines if the precompiled view assembly is to be delay signed.", + CommandOptionType.NoValue); + + PublicSignOption = app.Option( + PublicSignTemplate, + "Determines if the precompiled view assembly is to be public signed.", + CommandOptionType.NoValue); + } + + public string KeyFile => KeyFileOption.Value(); + + public bool DelaySign => DelaySignOption.HasValue(); + + public bool PublicSign => PublicSignOption.HasValue(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CodeGenerator.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs similarity index 59% rename from src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CodeGenerator.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs index 985950ed3e..c18005cb0b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CodeGenerator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs @@ -2,19 +2,22 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.IO; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Core.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal { - public class CodeGenerator + public class ViewCollectionCodeGenerator { - public CodeGenerator( + public ViewCollectionCodeGenerator( CSharpCompiler compiler, CSharpCompilation compilation) { @@ -37,11 +40,11 @@ public void AddViewFactory(List result) var factoryContent = $@" -namespace {AssemblyPart.ViewFactoryNamespace} +namespace {AssemblyPart.ViewCollectionNamespace} {{ - public class {AssemblyPart.ViewFactoryTypeName} : {typeof(PrecompiledViews).FullName} + public class {AssemblyPart.ViewCollectionTypeName} : {typeof(PrecompiledViews).FullName} {{ - public {AssemblyPart.ViewFactoryTypeName}() : base(new[] + public {AssemblyPart.ViewCollectionTypeName}() : base(new[] {{ {precompiledViewsArray} }}) @@ -53,11 +56,26 @@ public class {AssemblyPart.ViewFactoryTypeName} : {typeof(PrecompiledViews).Full Compilation = Compilation.AddSyntaxTrees(syntaxTree); } - public void AddAssemblyMetadata(AssemblyName applicationAssemblyName, string keyFilePath) + public void AddAssemblyMetadata( + AssemblyName applicationAssemblyName, + StrongNameOptions strongNameOptions) { - if (!string.IsNullOrEmpty(keyFilePath)) + if (!string.IsNullOrEmpty(strongNameOptions.KeyFile)) { - Compilation = Compilation.WithOptions(Compilation.Options.WithCryptoKeyFile(keyFilePath)); + var updatedOptions = Compilation.Options.WithStrongNameProvider(new DesktopStrongNameProvider()); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || strongNameOptions.PublicSign) + { + updatedOptions = updatedOptions.WithCryptoPublicKey( + SnkUtils.ExtractPublicKey(File.ReadAllBytes(strongNameOptions.KeyFile))); + } + else + { + updatedOptions = updatedOptions.WithCryptoKeyFile(strongNameOptions.KeyFile) + .WithDelaySign(strongNameOptions.DelaySign); + } + + Compilation = Compilation.WithOptions(updatedOptions); } var assemblyVersionContent = $"[assembly:{typeof(AssemblyVersionAttribute).FullName}(\"{applicationAssemblyName.Version}\")]"; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs index ababa454f6..664cec4e17 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs @@ -33,6 +33,8 @@ public class PrecompileDispatchCommand private string Configuration { get; set; } + private string OutputPath { get; set; } + public void Configure(CommandLineApplication app) { Options.Configure(app); @@ -56,30 +58,50 @@ public void Configure(CommandLineApplication app) private int Execute() { - ProjectPath = GetProjectPath(); - Configuration = ConfigurationOption.Value() ?? DotNet.Cli.Utils.Constants.DefaultConfiguration; - TargetFramework = GetTargetFramework(); + ParseArguments(); - var outputPaths = GetOutputPaths(); + var runtimeContext = GetRuntimeContext(); + + var outputPaths = runtimeContext.GetOutputPaths(Configuration); var applicationName = Path.GetFileNameWithoutExtension(outputPaths.CompilationFiles.Assembly); var dispatchArgs = new List { +#if DEBUG "--debug", +#endif ProjectPath, - "--application-name", + PrecompileRunCommand.ApplicationNameTemplate, applicationName, - "--output-path", - GetOutputPath(), - "--content-root", + PrecompileRunCommand.OutputPathTemplate, + OutputPath, + CommonOptions.ContentRootTemplate, Options.ContentRootOption.Value() ?? Directory.GetCurrentDirectory(), }; if (Options.ConfigureCompilationType.HasValue()) { - dispatchArgs.Add("--configure-compilation-type"); + dispatchArgs.Add(CommonOptions.ConfigureCompilationTypeTemplate); dispatchArgs.Add(Options.ConfigureCompilationType.Value()); } + var compilerOptions = runtimeContext.ProjectFile.GetCompilerOptions(TargetFramework, Configuration); + if (!string.IsNullOrEmpty(compilerOptions.KeyFile)) + { + dispatchArgs.Add(StrongNameOptions.StrongNameKeyPath); + var keyFilePath = Path.GetFullPath(Path.Combine(runtimeContext.ProjectDirectory, compilerOptions.KeyFile)); + dispatchArgs.Add(keyFilePath); + + if (compilerOptions.DelaySign ?? false) + { + dispatchArgs.Add(StrongNameOptions.DelaySignTemplate); + } + + if (compilerOptions.PublicSign ?? false) + { + dispatchArgs.Add(StrongNameOptions.PublicSignTemplate); + } + } + var toolName = typeof(Design.Program).GetTypeInfo().Assembly.GetName().Name; var dispatchCommand = DotnetToolDispatcher.CreateDispatchCommand( dispatchArgs, @@ -99,6 +121,24 @@ private int Execute() return commandExitCode; } + private void ParseArguments() + { + ProjectPath = GetProjectPath(); + Configuration = ConfigurationOption.Value() ?? DotNet.Cli.Utils.Constants.DefaultConfiguration; + + if (!FrameworkOption.HasValue()) + { + throw new Exception($"Option {FrameworkOption.Template} does not have a value."); + } + TargetFramework = NuGetFramework.Parse(FrameworkOption.Value()); + + if (!OutputPathOption.HasValue()) + { + throw new Exception($"Option {OutputPathOption.Template} does not have a value."); + } + OutputPath = OutputPathOption.Value(); + } + private string GetProjectPath() { string projectPath; @@ -123,7 +163,7 @@ private string GetProjectPath() return projectPath; } - private OutputPaths GetOutputPaths() + private ProjectContext GetRuntimeContext() { var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment()); @@ -137,28 +177,7 @@ private OutputPaths GetOutputPaths() var runtimeContext = workspace.GetRuntimeContext( projectContext, RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers()); - - return runtimeContext.GetOutputPaths(Configuration); - } - - private NuGetFramework GetTargetFramework() - { - if (!FrameworkOption.HasValue()) - { - throw new Exception($"Option {FrameworkOption.Template} does not have a value."); - } - - return NuGetFramework.Parse(FrameworkOption.Value()); - } - - private string GetOutputPath() - { - if (!OutputPathOption.HasValue()) - { - throw new Exception($"Option {OutputPathOption.Template} does not have a value."); - } - - return OutputPathOption.Value(); + return runtimeContext; } } } From c4d5e70ee03dcd73a45352fc3d9f254568c73e9c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 15 Aug 2016 15:29:00 -0700 Subject: [PATCH 6/7] Reviving commented out tests --- .../DefaultRoslynCompilationServiceTest.cs | 758 +++++++++--------- .../Internal/ReferenceManagerTest.cs | 61 ++ 2 files changed, 421 insertions(+), 398 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ReferenceManagerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs index 36530adf70..fe691ccbf2 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs @@ -1,398 +1,360 @@ -//// 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.Linq; -//using System.Reflection; -//using Microsoft.AspNetCore.Mvc.ApplicationParts; -//using Microsoft.AspNetCore.Mvc.Razor.Compilation; -//using Microsoft.CodeAnalysis; -//using Microsoft.CodeAnalysis.Text; -//using Microsoft.Extensions.FileProviders; -//using Microsoft.Extensions.Logging.Testing; -//using Microsoft.Extensions.Options; -//using Moq; -//using Xunit; - -//namespace Microsoft.AspNetCore.Mvc.Razor.Internal -//{ -// public class DefaultRoslynCompilationServiceTest -// { -// [Fact] -// public void Compile_ReturnsCompilationResult() -// { -// // Arrange -// var content = @" -//public class MyTestType {}"; - -// var compilationService = GetRoslynCompilationService(); -// var relativeFileInfo = new RelativeFileInfo( -// new TestFileInfo { PhysicalPath = "SomePath" }, -// "some-relative-path"); - -// // Act -// var result = compilationService.Compile(relativeFileInfo, content); - -// // Assert -// Assert.Equal("MyTestType", result.CompiledType.Name); -// } - -// [Fact] -// public void Compile_ReturnsCompilationFailureWithPathsFromLinePragmas() -// { -// // Arrange -// var viewPath = "some-relative-path"; -// var fileContent = "test file content"; -// var content = $@" -//#line 1 ""{viewPath}"" -//this should fail"; -// var fileProvider = new TestFileProvider(); -// var fileInfo = fileProvider.AddFile(viewPath, fileContent); - -// var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); -// var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path"); - -// // Act -// var result = compilationService.Compile(relativeFileInfo, content); - -// // Assert -// Assert.IsType(result); -// Assert.Null(result.CompiledType); -// var compilationFailure = Assert.Single(result.CompilationFailures); -// Assert.Equal(relativeFileInfo.RelativePath, compilationFailure.SourceFilePath); -// Assert.Equal(fileContent, compilationFailure.SourceFileContent); -// } - -// [Fact] -// public void Compile_ReturnsGeneratedCodePath_IfLinePragmaIsNotAvailable() -// { -// // Arrange -// var fileContent = "file content"; -// var content = "this should fail"; - -// var compilationService = GetRoslynCompilationService(); -// var relativeFileInfo = new RelativeFileInfo( -// new TestFileInfo { Content = fileContent }, -// "some-relative-path"); - -// // Act -// var result = compilationService.Compile(relativeFileInfo, content); - -// // Assert -// Assert.IsType(result); -// Assert.Null(result.CompiledType); - -// var compilationFailure = Assert.Single(result.CompilationFailures); -// Assert.Equal("Generated Code", compilationFailure.SourceFilePath); -// Assert.Equal(content, compilationFailure.SourceFileContent); -// } - -// [Fact] -// public void Compile_DoesNotThrow_IfFileCannotBeRead() -// { -// // Arrange -// var path = "some-relative-path"; -// var content = $@" -//#line 1 ""{path}"" -//this should fail"; - -// var mockFileInfo = new Mock(); -// mockFileInfo.Setup(f => f.CreateReadStream()) -// .Throws(new Exception()); -// var fileProvider = new TestFileProvider(); -// fileProvider.AddFile(path, mockFileInfo.Object); - -// var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); -// var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path); - -// // Act -// var result = compilationService.Compile(relativeFileInfo, content); - -// // Assert -// Assert.IsType(result); -// Assert.Null(result.CompiledType); -// var compilationFailure = Assert.Single(result.CompilationFailures); -// Assert.Equal(path, compilationFailure.SourceFilePath); -// Assert.Null(compilationFailure.SourceFileContent); -// } - -// [Fact] -// public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation() -// { -// // Arrange -// var content = @" -//#if MY_CUSTOM_DEFINE -//public class MyCustomDefinedClass {} -//#else -//public class MyNonCustomDefinedClass {} -//#endif -//"; -// var options = GetOptions(); -// options.ParseOptions = options.ParseOptions.WithPreprocessorSymbols("MY_CUSTOM_DEFINE"); -// var compilationService = GetRoslynCompilationService(options: options); -// var relativeFileInfo = new RelativeFileInfo( -// new TestFileInfo { PhysicalPath = "SomePath" }, -// "some-relative-path"); - -// // Act -// var result = compilationService.Compile(relativeFileInfo, content); - -// // Assert -// Assert.NotNull(result.CompiledType); -// Assert.Equal("MyCustomDefinedClass", result.CompiledType.Name); -// } - -// [Fact] -// public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessages() -// { -// // Arrange -// var viewPath = "Views/Home/Index"; -// var generatedCodeFileName = "Generated Code"; -// var fileProvider = new TestFileProvider(); -// fileProvider.AddFile(viewPath, "view-content"); -// var options = new RazorViewEngineOptions(); -// options.FileProviders.Add(fileProvider); -// var compilationService = GetRoslynCompilationService(options: options, fileProvider: fileProvider); -// var assemblyName = "random-assembly-name"; - -// var diagnostics = new[] -// { -// Diagnostic.Create( -// GetDiagnosticDescriptor("message-1"), -// Location.Create( -// viewPath, -// new TextSpan(10, 5), -// new LinePositionSpan(new LinePosition(10, 1), new LinePosition(10, 2)))), -// Diagnostic.Create( -// GetDiagnosticDescriptor("message-2"), -// Location.Create( -// assemblyName, -// new TextSpan(1, 6), -// new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)))), -// Diagnostic.Create( -// GetDiagnosticDescriptor("message-3"), -// Location.Create( -// viewPath, -// new TextSpan(40, 50), -// new LinePositionSpan(new LinePosition(30, 5), new LinePosition(40, 12)))), -// }; - -// // Act -// var compilationResult = compilationService.GetCompilationFailedResult( -// viewPath, -// "compilation-content", -// assemblyName, -// diagnostics); - -// // Assert -// Assert.Collection(compilationResult.CompilationFailures, -// failure => -// { -// Assert.Equal(viewPath, failure.SourceFilePath); -// Assert.Equal("view-content", failure.SourceFileContent); -// Assert.Collection(failure.Messages, -// message => -// { -// Assert.Equal("message-1", message.Message); -// Assert.Equal(viewPath, message.SourceFilePath); -// Assert.Equal(11, message.StartLine); -// Assert.Equal(2, message.StartColumn); -// Assert.Equal(11, message.EndLine); -// Assert.Equal(3, message.EndColumn); -// }, -// message => -// { -// Assert.Equal("message-3", message.Message); -// Assert.Equal(viewPath, message.SourceFilePath); -// Assert.Equal(31, message.StartLine); -// Assert.Equal(6, message.StartColumn); -// Assert.Equal(41, message.EndLine); -// Assert.Equal(13, message.EndColumn); -// }); -// }, -// failure => -// { -// Assert.Equal(generatedCodeFileName, failure.SourceFilePath); -// Assert.Equal("compilation-content", failure.SourceFileContent); -// Assert.Collection(failure.Messages, -// message => -// { -// Assert.Equal("message-2", message.Message); -// Assert.Equal(assemblyName, message.SourceFilePath); -// Assert.Equal(2, message.StartLine); -// Assert.Equal(3, message.StartColumn); -// Assert.Equal(4, message.EndLine); -// Assert.Equal(5, message.EndColumn); -// }); -// }); -// } - -// [Fact] -// public void Compile_RunsCallback() -// { -// // Arrange -// var content = "public class MyTestType {}"; -// RoslynCompilationContext usedCompilation = null; -// var options = GetOptions(c => usedCompilation = c); -// var compilationService = GetRoslynCompilationService(options: options); - -// var relativeFileInfo = new RelativeFileInfo( -// new TestFileInfo { PhysicalPath = "SomePath" }, -// "some-relative-path"); - -// // Act -// var result = compilationService.Compile(relativeFileInfo, content); - -// Assert.NotNull(usedCompilation); -// Assert.Single(usedCompilation.Compilation.SyntaxTrees); -// } - -// [Fact] -// public void Compile_DoesNotThrowIfReferencesWereClearedInCallback() -// { -// // Arrange -// var options = GetOptions(context => -// { -// context.Compilation = context.Compilation.RemoveAllReferences(); -// }); -// var content = "public class MyTestType {}"; -// var compilationService = GetRoslynCompilationService(options: options); -// var relativeFileInfo = new RelativeFileInfo( -// new TestFileInfo { PhysicalPath = "SomePath" }, -// "some-relative-path.cshtml"); - -// // Act -// var result = compilationService.Compile(relativeFileInfo, content); - -// // Assert -// Assert.Single(result.CompilationFailures); -// } - -// [Fact] -// public void Compile_SucceedsIfReferencesAreAddedInCallback() -// { -// // Arrange -// var options = GetOptions(context => -// { -// var assemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; - -// context.Compilation = context -// .Compilation -// .AddReferences(MetadataReference.CreateFromFile(assemblyLocation)); -// }); -// var content = "public class MyTestType {}"; -// var applicationPartManager = new ApplicationPartManager(); -// var compilationService = GetRoslynCompilationService(applicationPartManager, options); - -// var relativeFileInfo = new RelativeFileInfo( -// new TestFileInfo { PhysicalPath = "SomePath" }, -// "some-relative-path.cshtml"); - -// // Act -// var result = compilationService.Compile(relativeFileInfo, content); - -// // Assert -// Assert.Null(result.CompilationFailures); -// Assert.NotNull(result.CompiledType); -// } - -// [Fact] -// public void GetCompilationReferences_CombinesApplicationPartAndOptionMetadataReferences() -// { -// // Arrange -// var options = new RazorViewEngineOptions(); -// var objectAssemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; -// var objectAssemblyMetadataReference = MetadataReference.CreateFromFile(objectAssemblyLocation); -// options.AdditionalCompilationReferences.Add(objectAssemblyMetadataReference); -// var applicationPartManager = GetApplicationPartManager(); -// var compilationService = new TestRoslynCompilationService(applicationPartManager, options); -// var feature = new MetadataReferenceFeature(); -// applicationPartManager.PopulateFeature(feature); -// var partReferences = feature.MetadataReferences; -// var expectedReferences = new List(); -// expectedReferences.AddRange(partReferences); -// expectedReferences.Add(objectAssemblyMetadataReference); -// var expectedReferenceDisplays = expectedReferences.Select(reference => reference.Display); - -// // Act -// var references = compilationService.GetCompilationReferencesPublic(); -// var referenceDisplays = references.Select(reference => reference.Display); - -// // Assert -// Assert.NotNull(references); -// Assert.NotEmpty(references); -// Assert.Equal(expectedReferenceDisplays, referenceDisplays); -// } - -// private static DiagnosticDescriptor GetDiagnosticDescriptor(string messageFormat) -// { -// return new DiagnosticDescriptor( -// id: "someid", -// title: "sometitle", -// messageFormat: messageFormat, -// category: "some-category", -// defaultSeverity: DiagnosticSeverity.Error, -// isEnabledByDefault: true); -// } - -// private static RazorViewEngineOptions GetOptions(Action callback = null) -// { -// return new RazorViewEngineOptions -// { -// CompilationCallback = callback ?? (c => { }), -// }; -// } - -// private static IRazorViewEngineFileProviderAccessor GetFileProviderAccessor(IFileProvider fileProvider = null) -// { -// var options = new Mock(); -// options.SetupGet(o => o.FileProvider) -// .Returns(fileProvider ?? new TestFileProvider()); - -// return options.Object; -// } - -// private static IOptions GetAccessor(RazorViewEngineOptions options) -// { -// var optionsAccessor = new Mock>(); -// optionsAccessor.SetupGet(a => a.Value).Returns(options); -// return optionsAccessor.Object; -// } - -// private static ApplicationPartManager GetApplicationPartManager() -// { -// var applicationPartManager = new ApplicationPartManager(); -// var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly; -// applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly)); -// applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); - -// return applicationPartManager; -// } - -// private static DefaultRoslynCompilationService GetRoslynCompilationService( -// ApplicationPartManager partManager = null, -// RazorViewEngineOptions options = null, -// IFileProvider fileProvider = null) -// { -// partManager = partManager ?? GetApplicationPartManager(); -// options = options ?? GetOptions(); - -// return new DefaultRoslynCompilationService( -// partManager, -// GetAccessor(options), -// GetFileProviderAccessor(fileProvider), -// NullLoggerFactory.Instance); -// } - -// private class TestRoslynCompilationService : DefaultRoslynCompilationService -// { -// public TestRoslynCompilationService(ApplicationPartManager partManager, RazorViewEngineOptions options) -// : base(partManager, GetAccessor(options), GetFileProviderAccessor(), NullLoggerFactory.Instance) -// { -// } - -// public IList GetCompilationReferencesPublic() => GetCompilationReferences(); -// } -// } -//} +// 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.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging.Testing; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class DefaultRoslynCompilationServiceTest + { + [Fact] + public void Compile_ReturnsCompilationResult() + { + // Arrange + var content = @" +public class MyTestType {}"; + + var compilationService = GetRoslynCompilationService(); + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path"); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + // Assert + Assert.Equal("MyTestType", result.CompiledType.Name); + } + + [Fact] + public void Compile_ReturnsCompilationFailureWithPathsFromLinePragmas() + { + // Arrange + var viewPath = "some-relative-path"; + var fileContent = "test file content"; + var content = $@" +#line 1 ""{viewPath}"" +this should fail"; + var fileProvider = new TestFileProvider(); + var fileInfo = fileProvider.AddFile(viewPath, fileContent); + + var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); + var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path"); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + // Assert + Assert.IsType(result); + Assert.Null(result.CompiledType); + var compilationFailure = Assert.Single(result.CompilationFailures); + Assert.Equal(relativeFileInfo.RelativePath, compilationFailure.SourceFilePath); + Assert.Equal(fileContent, compilationFailure.SourceFileContent); + } + + [Fact] + public void Compile_ReturnsGeneratedCodePath_IfLinePragmaIsNotAvailable() + { + // Arrange + var fileContent = "file content"; + var content = "this should fail"; + + var compilationService = GetRoslynCompilationService(); + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { Content = fileContent }, + "some-relative-path"); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + // Assert + Assert.IsType(result); + Assert.Null(result.CompiledType); + + var compilationFailure = Assert.Single(result.CompilationFailures); + Assert.Equal("Generated Code", compilationFailure.SourceFilePath); + Assert.Equal(content, compilationFailure.SourceFileContent); + } + + [Fact] + public void Compile_DoesNotThrow_IfFileCannotBeRead() + { + // Arrange + var path = "some-relative-path"; + var content = $@" +#line 1 ""{path}"" +this should fail"; + + var mockFileInfo = new Mock(); + mockFileInfo.Setup(f => f.CreateReadStream()) + .Throws(new Exception()); + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(path, mockFileInfo.Object); + + var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); + var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + // Assert + Assert.IsType(result); + Assert.Null(result.CompiledType); + var compilationFailure = Assert.Single(result.CompilationFailures); + Assert.Equal(path, compilationFailure.SourceFilePath); + Assert.Null(compilationFailure.SourceFileContent); + } + + [Fact] + public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation() + { + // Arrange + var content = @" +#if MY_CUSTOM_DEFINE +public class MyCustomDefinedClass {} +#else +public class MyNonCustomDefinedClass {} +#endif +"; + var options = GetOptions(); + options.ParseOptions = options.ParseOptions.WithPreprocessorSymbols("MY_CUSTOM_DEFINE"); + var compilationService = GetRoslynCompilationService(options: options); + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path"); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + // Assert + Assert.NotNull(result.CompiledType); + Assert.Equal("MyCustomDefinedClass", result.CompiledType.Name); + } + + [Fact] + public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessages() + { + // Arrange + var viewPath = "Views/Home/Index"; + var generatedCodeFileName = "Generated Code"; + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(viewPath, "view-content"); + var options = new RazorViewEngineOptions(); + options.FileProviders.Add(fileProvider); + var compilationService = GetRoslynCompilationService(options: options, fileProvider: fileProvider); + var assemblyName = "random-assembly-name"; + + var diagnostics = new[] + { + Diagnostic.Create( + GetDiagnosticDescriptor("message-1"), + Location.Create( + viewPath, + new TextSpan(10, 5), + new LinePositionSpan(new LinePosition(10, 1), new LinePosition(10, 2)))), + Diagnostic.Create( + GetDiagnosticDescriptor("message-2"), + Location.Create( + assemblyName, + new TextSpan(1, 6), + new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)))), + Diagnostic.Create( + GetDiagnosticDescriptor("message-3"), + Location.Create( + viewPath, + new TextSpan(40, 50), + new LinePositionSpan(new LinePosition(30, 5), new LinePosition(40, 12)))), + }; + + // Act + var compilationResult = compilationService.GetCompilationFailedResult( + viewPath, + "compilation-content", + assemblyName, + diagnostics); + + // Assert + Assert.Collection(compilationResult.CompilationFailures, + failure => + { + Assert.Equal(viewPath, failure.SourceFilePath); + Assert.Equal("view-content", failure.SourceFileContent); + Assert.Collection(failure.Messages, + message => + { + Assert.Equal("message-1", message.Message); + Assert.Equal(viewPath, message.SourceFilePath); + Assert.Equal(11, message.StartLine); + Assert.Equal(2, message.StartColumn); + Assert.Equal(11, message.EndLine); + Assert.Equal(3, message.EndColumn); + }, + message => + { + Assert.Equal("message-3", message.Message); + Assert.Equal(viewPath, message.SourceFilePath); + Assert.Equal(31, message.StartLine); + Assert.Equal(6, message.StartColumn); + Assert.Equal(41, message.EndLine); + Assert.Equal(13, message.EndColumn); + }); + }, + failure => + { + Assert.Equal(generatedCodeFileName, failure.SourceFilePath); + Assert.Equal("compilation-content", failure.SourceFileContent); + Assert.Collection(failure.Messages, + message => + { + Assert.Equal("message-2", message.Message); + Assert.Equal(assemblyName, message.SourceFilePath); + Assert.Equal(2, message.StartLine); + Assert.Equal(3, message.StartColumn); + Assert.Equal(4, message.EndLine); + Assert.Equal(5, message.EndColumn); + }); + }); + } + + [Fact] + public void Compile_RunsCallback() + { + // Arrange + var content = "public class MyTestType {}"; + RoslynCompilationContext usedCompilation = null; + var options = GetOptions(c => usedCompilation = c); + var compilationService = GetRoslynCompilationService(options: options); + + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path"); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + Assert.NotNull(usedCompilation); + Assert.Single(usedCompilation.Compilation.SyntaxTrees); + } + + [Fact] + public void Compile_DoesNotThrowIfReferencesWereClearedInCallback() + { + // Arrange + var options = GetOptions(context => + { + context.Compilation = context.Compilation.RemoveAllReferences(); + }); + var content = "public class MyTestType {}"; + var compilationService = GetRoslynCompilationService(options: options); + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path.cshtml"); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + // Assert + Assert.Single(result.CompilationFailures); + } + + [Fact] + public void Compile_SucceedsIfReferencesAreAddedInCallback() + { + // Arrange + var options = GetOptions(context => + { + var assemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; + + context.Compilation = context + .Compilation + .AddReferences(MetadataReference.CreateFromFile(assemblyLocation)); + }); + var content = "public class MyTestType {}"; + var applicationPartManager = new ApplicationPartManager(); + var compilationService = GetRoslynCompilationService(applicationPartManager, options); + + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path.cshtml"); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + // Assert + Assert.Null(result.CompilationFailures); + Assert.NotNull(result.CompiledType); + } + + private static DiagnosticDescriptor GetDiagnosticDescriptor(string messageFormat) + { + return new DiagnosticDescriptor( + id: "someid", + title: "sometitle", + messageFormat: messageFormat, + category: "some-category", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + } + + private static RazorViewEngineOptions GetOptions(Action callback = null) + { + return new RazorViewEngineOptions + { + CompilationCallback = callback ?? (c => { }), + }; + } + + private static IRazorViewEngineFileProviderAccessor GetFileProviderAccessor(IFileProvider fileProvider = null) + { + var options = new Mock(); + options.SetupGet(o => o.FileProvider) + .Returns(fileProvider ?? new TestFileProvider()); + + return options.Object; + } + + private static IOptions GetAccessor(RazorViewEngineOptions options) + { + var optionsAccessor = new Mock>(); + optionsAccessor.SetupGet(a => a.Value).Returns(options); + return optionsAccessor.Object; + } + + private static ApplicationPartManager GetApplicationPartManager() + { + var applicationPartManager = new ApplicationPartManager(); + var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly; + applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly)); + applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); + + return applicationPartManager; + } + + private static DefaultRoslynCompilationService GetRoslynCompilationService( + ApplicationPartManager partManager = null, + RazorViewEngineOptions options = null, + IFileProvider fileProvider = null) + { + partManager = partManager ?? GetApplicationPartManager(); + options = options ?? GetOptions(); + var optionsAccessor = GetAccessor(options); + var referenceManager = new RazorReferenceManager(partManager, optionsAccessor); + var compiler = new CSharpCompiler(referenceManager, optionsAccessor); + + return new DefaultRoslynCompilationService( + compiler, + GetFileProviderAccessor(fileProvider), + NullLoggerFactory.Instance); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ReferenceManagerTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ReferenceManagerTest.cs new file mode 100644 index 0000000000..12fe6d81d6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ReferenceManagerTest.cs @@ -0,0 +1,61 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Test.Internal +{ + public class ReferenceManagerTest + { + [Fact] + public void GetCompilationReferences_CombinesApplicationPartAndOptionMetadataReferences() + { + // Arrange + var options = new RazorViewEngineOptions(); + var objectAssemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; + var objectAssemblyMetadataReference = MetadataReference.CreateFromFile(objectAssemblyLocation); + options.AdditionalCompilationReferences.Add(objectAssemblyMetadataReference); + + var applicationPartManager = GetApplicationPartManager(); + var feature = new MetadataReferenceFeature(); + applicationPartManager.PopulateFeature(feature); + var partReferences = feature.MetadataReferences; + var expectedReferenceDisplays = partReferences + .Concat(new[] { objectAssemblyMetadataReference }) + .Select(r => r.Display); + var referenceManager = new RazorReferenceManager(applicationPartManager, GetAccessor(options)); + + // Act + var references = referenceManager.CompilationReferences; + var referenceDisplays = references.Select(reference => reference.Display); + + // Assert + Assert.Equal(expectedReferenceDisplays, referenceDisplays); + } + + private static ApplicationPartManager GetApplicationPartManager() + { + var applicationPartManager = new ApplicationPartManager(); + var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly; + applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly)); + applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); + + return applicationPartManager; + } + + private static IOptions GetAccessor(RazorViewEngineOptions options) + { + var optionsAccessor = new Mock>(); + optionsAccessor.SetupGet(a => a.Value).Returns(options); + return optionsAccessor.Object; + } + } +} From 0b72b38f8d14f7b4ad8597680f71833b842cb1cc Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 16 Aug 2016 14:58:34 -0700 Subject: [PATCH 7/7] Some parallelization to improve perf. --- .../Internal/PrecompileRunCommand.cs | 44 +++++++++++++------ .../Internal/ViewCollectionCodeGenerator.cs | 2 +- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs index 997eba040f..51ec692613 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs @@ -3,10 +3,12 @@ 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.CodeAnalysis; @@ -23,13 +25,15 @@ 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 MvcServicesProvider ServicesProvider { get; set; } private CommonOptions Options { get; } = new CommonOptions(); @@ -68,6 +72,7 @@ private int Execute() Console.WriteLine("Running Razor view precompilation."); + var stopWatch = Stopwatch.StartNew(); var results = ParseViews(); var success = true; foreach (var result in results) @@ -103,13 +108,16 @@ private int Execute() return 1; } - Console.WriteLine($"Successfully compiled {results.Count} Razor views."); + 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)) { @@ -125,17 +133,26 @@ private EmitResult EmitAssembly(CSharpCompilation compilation, string assemblyPa return emitResult; } - private CSharpCompilation CompileViews(List results, string assemblyname) + private CSharpCompilation CompileViews(ViewCompilationInfo[] results, string assemblyname) { var compiler = ServicesProvider.Compiler; var compilation = compiler.CreateCompilation(assemblyname); + var syntaxTrees = new SyntaxTree[results.Length]; - foreach (var result in results) + Parallel.For(0, results.Length, ParalellOptions, i => { + var result = results[i]; var sourceText = SourceText.From(result.GeneratorResults.GeneratedCode, Encoding.UTF8); - var syntaxTree = compiler.CreateSyntaxTree(sourceText); - compilation = compilation.AddSyntaxTrees(syntaxTree); - result.TypeName = ReadTypeInfo(compilation, syntaxTree); + var fileInfo = result.RelativeFileInfo; + var syntaxTree = compiler.CreateSyntaxTree(sourceText) + .WithFilePath(fileInfo.FileInfo.PhysicalPath ?? fileInfo.RelativePath); + syntaxTrees[i] = syntaxTree; + }); + + compilation = compilation.AddSyntaxTrees(syntaxTrees); + for (var i = 0; i < results.Length; i++) + { + results[i].TypeName = ReadTypeInfo(compilation, syntaxTrees[i]); } compilation = compiler.ProcessCompilation(compilation); @@ -168,19 +185,20 @@ private void ParseArguments() } } - private List ParseViews() + private ViewCompilationInfo[] ParseViews() { - var results = new List(); var files = new List(); GetRazorFiles(ServicesProvider.FileProvider, files, root: string.Empty); - foreach (var fileInfo in files) + 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 = ServicesProvider.Host.GenerateCode(fileInfo.RelativePath, fileStream); - results.Add(new ViewCompilationInfo(fileInfo, result)); + results[i] = new ViewCompilationInfo(fileInfo, result); } - } + }); return results; } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs index c18005cb0b..54d642e1c3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs @@ -29,7 +29,7 @@ public ViewCollectionCodeGenerator( public CSharpCompilation Compilation { get; private set; } - public void AddViewFactory(List result) + public void AddViewFactory(IList result) { var precompiledViewsArray = new StringBuilder(); foreach (var item in result)