From a3fef5eeaa0463b739ea5d0b1c61d894ba5ef548 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 13 Dec 2017 16:14:44 -0800 Subject: [PATCH] Adds support for Razor compilation at build-time This PR adds two new tools as well as a tasks project. None of these projects produce a package and they ship as part of Microsoft.AspNetCore.Razor.Design. For now this is a 'fat' package that contains all of the dependencies, but we plan to strip them out in the future. The support for compilation at build-time will start as **off** by default. The immediate goal here is to get this to flow through the build so that we can test it as part of the inner loop effort. We will enable this feature by default once we've done more thorough testing. Since this is mostly a code dump, I plan to address blocking and minor feedback only. If there are design issues that are non-critical, I will open follow up items. The next step will be to start adding more detailed tests. --- build/sources.props | 1 + .../Microsoft.AspNetCore.Razor.Design.csproj | 106 +++++++- .../Microsoft.AspNetCore.Razor.Design.props | 10 - .../Microsoft.AspNetCore.Razor.Design.targets | 11 - .../Microsoft.AspNetCore.Razor.Design.props | 37 +++ .../Microsoft.AspNetCore.Razor.Design.targets | 238 ++++++++++++++++++ .../Microsoft.AspNetCore.Razor.Design.props | 2 +- .../Microsoft.AspNetCore.Razor.Design.targets | 2 +- .../RunCommand.cs | 2 +- .../IntegrationTests/Assert.cs | 43 ++++ .../CleanProjectIntegrationTest.cs | 4 +- .../MSBuildIntegrationTestBase.cs | 5 +- .../IntegrationTests/MSBuildProcessManager.cs | 6 +- .../IntegrationTests/MSBuildResult.cs | 7 +- .../IntegrationTests/ProjectDirectory.cs | 40 ++- ...rosoft.AspNetCore.Razor.Design.Test.csproj | 16 +- .../RazorPage.cs | 28 +++ test/testapps/SimpleMvc/Program.cs | 3 + test/testapps/SimpleMvc/SimpleMvc.csproj | 1 + 19 files changed, 515 insertions(+), 47 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Razor.Design/build/Microsoft.AspNetCore.Razor.Design.props delete mode 100644 src/Microsoft.AspNetCore.Razor.Design/build/Microsoft.AspNetCore.Razor.Design.targets create mode 100644 src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.props create mode 100644 src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.targets diff --git a/build/sources.props b/build/sources.props index 0c10e573e6..b97c0c5cfb 100644 --- a/build/sources.props +++ b/build/sources.props @@ -7,6 +7,7 @@ $(RestoreSources); https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; + https://dotnet.myget.org/F/msbuild/api/v3/index.json; https://dotnet.myget.org/F/roslyn/api/v3/index.json; https://vside.myget.org/F/vssdk/api/v3/index.json; https://vside.myget.org/F/vsmac/api/v3/index.json diff --git a/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj b/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj index 809845ddcb..af022ebf2b 100644 --- a/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj +++ b/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj @@ -1,9 +1,109 @@ - + + + + Razor is a markup syntax for adding server-side logic to web pages. This package contains MSBuild support for Razor. - net46;netstandard2.0 + + + netstandard2.0 + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + $(GenerateNuspecDependsOn);_BuildDependencyProjects + _BuildDependencyProjects;$(BuildDependsOn) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Design/build/Microsoft.AspNetCore.Razor.Design.props b/src/Microsoft.AspNetCore.Razor.Design/build/Microsoft.AspNetCore.Razor.Design.props deleted file mode 100644 index 3855de69a6..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Design/build/Microsoft.AspNetCore.Razor.Design.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Design/build/Microsoft.AspNetCore.Razor.Design.targets b/src/Microsoft.AspNetCore.Razor.Design/build/Microsoft.AspNetCore.Razor.Design.targets deleted file mode 100644 index bfba5fdf50..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Design/build/Microsoft.AspNetCore.Razor.Design.targets +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.props b/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.props new file mode 100644 index 0000000000..f40a34bdc1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.props @@ -0,0 +1,37 @@ + + + + + + _RazorResolveTagHelpers;RazorCoreGenerate + _RazorResolveTagHelpers;RazorCoreGenerate;RazorCoreCompile + + + + + false + + + + + <_RazorMSBuildRoot Condition="'$(_RazorMSBuildRoot)'==''">$(MSBuildThisFileDirectory)..\..\ + + netstandard2.0 + net46 + $(_RazorMSBuildRoot)\tasks\$(TaskFolder)\Microsoft.AspNetCore.Razor.Tasks.dll + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.targets b/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.targets new file mode 100644 index 0000000000..0c1f1b5f4e --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.targets @@ -0,0 +1,238 @@ + + + + + + + + + + $(BuildDependsOn);RazorCompile + + + + + + $(IntermediateOutputPath)Razor\ + + + $(TargetName).PrecompiledViews + + + + + + <_RazorTagHelperInputCache>$(IntermediateOutputPath)$(TargetName).TagHelpers.input.cache + <_RazorTagHelperOutputCache>$(IntermediateOutputPath)$(TargetName).TagHelpers.output.cache + + + <_RazorIntermediateAssembly>$(IntermediateOutputPath)$(RazorTargetName).dll + <_RazorIntermediatePdb>$(IntermediateOutputPath)$(RazorTargetName).pdb + + + <_RazorGenerateToolAssembly>$(_RazorMSBuildRoot)tools\Microsoft.AspNetCore.Razor.GenerateTool.dll + <_RazorTagHelperToolAssembly>$(_RazorMSBuildRoot)tools\Microsoft.AspNetCore.Razor.TagHelperTool.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + <_RazorGenerated Include="@(RazorCompile->'$(RazorGenerateOutputPath)%(RelativeDir)%(Filename).cs')"> + %(Identity) + + + + + + + + + + + + + + + + + + + + + + + $(NoWarn);1701;1702 + + + + + $(NoWarn);2008 + + + + + + + + + + + $(AppConfig) + + + + + false + + + + true + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.props b/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.props index 6f078c3657..e4a047fc18 100644 --- a/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.props +++ b/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.props @@ -1,3 +1,3 @@  - + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.targets b/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.targets index 22c725f0f2..168039a525 100644 --- a/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.targets +++ b/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.targets @@ -1,3 +1,3 @@  - + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.GenerateTool/RunCommand.cs b/src/Microsoft.AspNetCore.Razor.GenerateTool/RunCommand.cs index 8236213ce2..9ce95b5f14 100644 --- a/src/Microsoft.AspNetCore.Razor.GenerateTool/RunCommand.cs +++ b/src/Microsoft.AspNetCore.Razor.GenerateTool/RunCommand.cs @@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Razor.GenerateTool Parallel.For(0, outputs.Length, new ParallelOptions() { MaxDegreeOfParallelism = 4 }, i => { var source = sources[i]; - + var csharpDocument = templateEngine.GenerateCode(source.ViewEnginePath); outputs[i] = new OutputItem(source, csharpDocument); }); diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/Assert.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/Assert.cs index 3f04c34e3c..98792803b5 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/Assert.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/Assert.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 System.IO; using System.Text; namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests @@ -17,6 +18,17 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests } } + public static void FileExists(MSBuildResult result, string filePath) + { + NotNull(result); + NotNull(filePath); + + if (!File.Exists(Path.Combine(result.Project.DirectoryPath, filePath))) + { + throw new FileMissingException(result, filePath); + } + } + private class BuildFailedException : Xunit.Sdk.XunitException { public BuildFailedException(MSBuildResult result) @@ -42,5 +54,36 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests } } } + + private class FileMissingException : Xunit.Sdk.XunitException + { + public FileMissingException(MSBuildResult result, string filePath) + { + Result = result; + FilePath = filePath; + } + + public string FilePath { get; } + + public MSBuildResult Result { get; } + + public override string Message + { + get + { + var message = new StringBuilder(); + message.Append($"File: '{FilePath}' was not found."); + message.Append(Result.FileName); + message.Append(" "); + message.Append(Result.Arguments); + message.AppendLine(); + message.AppendLine(); + message.Append(Result.Output); + return message.ToString(); + + ; + } + } + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/CleanProjectIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/CleanProjectIntegrationTest.cs index 87c179fe33..83f34292c5 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/CleanProjectIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/CleanProjectIntegrationTest.cs @@ -12,9 +12,11 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests [InitializeTestProject("SimpleMvc")] public async Task CleanProject_RunBuild() { - var result = await DotnetMSBuild("Restore;Build"); // Equivalent to dotnet build + var result = await DotnetMSBuild("Restore;Build", "/p:RazorCompileOnBuild=true"); Assert.BuildPassed(result); + Assert.FileExists(result, @"bin/Debug/netcoreapp2.0/SimpleMvc.dll"); + Assert.FileExists(result, @"bin/Debug/netcoreapp2.0/SimpleMvc.PrecompiledViews.dll"); } } } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs index 6384071810..452ed625ad 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildIntegrationTestBase.cs @@ -47,9 +47,10 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests #endif } - internal Task DotnetMSBuild(string target) + internal Task DotnetMSBuild(string target, string args = null, bool debug = false) { - return MSBuildProcessManager.RunProcessAsync($"/t:{target}", Project.DirectoryPath); + var timeout = debug ? (TimeSpan?)Timeout.InfiniteTimeSpan : null; + return MSBuildProcessManager.RunProcessAsync(Project, $"/t:{target} {args}", timeout); } } } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildProcessManager.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildProcessManager.cs index 622313a6f8..634d9c6e77 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildProcessManager.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildProcessManager.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests { internal static class MSBuildProcessManager { - public static Task RunProcessAsync(string arguments, string workingDirectory, TimeSpan? timeout = null) + public static Task RunProcessAsync(ProjectDirectory project, string arguments, TimeSpan? timeout = null) { timeout = timeout ?? TimeSpan.FromSeconds(30); @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests { FileName = "dotnet", Arguments = "msbuild " + arguments, - WorkingDirectory = workingDirectory, + WorkingDirectory = project.DirectoryPath, UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests void Process_Exited(object sender, EventArgs e) { - var result = new MSBuildResult(process.StartInfo.FileName, process.StartInfo.Arguments, process.ExitCode, output.ToString()); + var result = new MSBuildResult(project, process.StartInfo.FileName, process.StartInfo.Arguments, process.ExitCode, output.ToString()); completionSource.SetResult(result); } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildResult.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildResult.cs index dc815354e3..a409c06e47 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildResult.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/MSBuildResult.cs @@ -5,20 +5,23 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests { internal class MSBuildResult { - public MSBuildResult(string fileName, string arguments, int exitCode, string output) + public MSBuildResult(ProjectDirectory project, string fileName, string arguments, int exitCode, string output) { + Project = project; FileName = fileName; Arguments = arguments; ExitCode = exitCode; Output = output; } + public ProjectDirectory Project { get; } + public string Arguments { get; } public string FileName { get; } public int ExitCode { get; } - + public string Output { get; } } } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/ProjectDirectory.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/ProjectDirectory.cs index 4ba465412d..1b2dfbb2a9 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/ProjectDirectory.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/ProjectDirectory.cs @@ -37,10 +37,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests CopyDirectory(new DirectoryInfo(projectRoot), new DirectoryInfo(destinationPath)); - foreach (var project in Directory.EnumerateFiles(destinationPath, "*.csproj")) - { - RewriteCsproj(projectRoot, project); - } + CreateDirectoryProps(projectRoot, destinationPath); + CreateDirectoryTargets(destinationPath); return new ProjectDirectory(destinationPath); } @@ -71,13 +69,35 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests } } - void RewriteCsproj(string originalProjectRoot, string filePath) + void CreateDirectoryProps(string originalProjectRoot, string projectRoot) { - // We need to replace $(OriginalProjectRoot) with the path to the original directory - // that way relative references will resolve. - var text = File.ReadAllText(filePath); - text = text.Replace("$(OriginalProjectRoot)", originalProjectRoot); - File.WriteAllText(filePath, text); +#if DEBUG + var configuration = "Debug"; +#elif RELEASE + var configuration = "Release"; +#else +#error Unknown Configuration +#endif + var text = $@" + + + {originalProjectRoot} + <_RazorMSBuildRoot>$(OriginalProjectRoot)\..\..\..\src\Microsoft.AspNetCore.Razor.Design\bin\{configuration}\netstandard2.0\ + + + +"; + File.WriteAllText(Path.Combine(projectRoot, "Directory.Build.props"), text); + } + + void CreateDirectoryTargets(string projectRoot) + { + var text = $@" + + + +"; + File.WriteAllText(Path.Combine(projectRoot, "Directory.Build.targets"), text); } } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj b/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj index ee6b42beb0..951ccec788 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/Microsoft.AspNetCore.Razor.Design.Test.csproj @@ -1,9 +1,14 @@ - $(StandardTestTfms) + + netcoreapp2.0 true - $(DefaultItemExcludes);TestFiles\** @@ -18,4 +23,11 @@ + + + + false + + + diff --git a/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs b/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs index 7666a63722..2b7b7450cd 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs +++ b/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs @@ -1,6 +1,9 @@ // 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.Threading.Tasks; +using Microsoft.AspNetCore.Html; + namespace Microsoft.AspNetCore.Mvc.Razor { public abstract class RazorPage : RazorPageBase @@ -16,5 +19,30 @@ namespace Microsoft.AspNetCore.Mvc.Razor public override void EnsureRenderedBodyOrSections() { } + + protected virtual IHtmlContent RenderBody() + { + return null; + } + + public HtmlString RenderSection(string name) + { + return null; + } + + public HtmlString RenderSection(string name, bool required) + { + return null; + } + + public Task RenderSectionAsync(string name) + { + return null; + } + + public Task RenderSectionAsync(string name, bool required) + { + return null; + } } } diff --git a/test/testapps/SimpleMvc/Program.cs b/test/testapps/SimpleMvc/Program.cs index 3e58606bdf..e62b11efe5 100644 --- a/test/testapps/SimpleMvc/Program.cs +++ b/test/testapps/SimpleMvc/Program.cs @@ -5,6 +5,9 @@ namespace SimpleMvc { public static void Main(string[] args) { + // Just make sure we have a reference to the MvcShim + var t = typeof(Microsoft.AspNetCore.Mvc.IActionResult); + System.Console.WriteLine(t.FullName); } } } diff --git a/test/testapps/SimpleMvc/SimpleMvc.csproj b/test/testapps/SimpleMvc/SimpleMvc.csproj index 266f837eb9..3e5e49458c 100644 --- a/test/testapps/SimpleMvc/SimpleMvc.csproj +++ b/test/testapps/SimpleMvc/SimpleMvc.csproj @@ -4,6 +4,7 @@ +