diff --git a/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets b/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets index 7e33684669..7d771e7c0d 100644 --- a/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets +++ b/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets @@ -69,11 +69,21 @@ Copyright (c) .NET Foundation. All rights reserved. _RazorAddDebugSymbolsProjectOutputGroupOutput + + $(PrepareForBuildDependsOn); + ResolveRazorGenerateInputs + + _RazorPrepareForRun; $(PrepareForRunDependsOn) + + _RazorGetCopyToOutputDirectoryItems; + $(GetCopyToOutputDirectoryItems) + + <_RazorDebugSymbolsIntermediatePath Condition="'$(_RazorDebugSymbolsProduced)'=='true'" Include="$(IntermediateOutputPath)$(RazorTargetName).pdb" /> - - + + + + + + + + + + + + + @@ -384,7 +427,7 @@ Copyright (c) .NET Foundation. All rights reserved. --> + + + diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/Assert.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/Assert.cs index 55791a7284..74bfdb1992 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/Assert.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/Assert.cs @@ -100,6 +100,30 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests throw new BuildOutputMissingException(result, match); } + public static void BuildOutputDoesNotContainLine(MSBuildResult result, string match) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + // We don't really need to search line by line, I'm doing this so that it's possible/easy to debug. + var lines = result.Output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i].Trim(); + if (line == match) + { + throw new BuildOutputContainsLineException(result, match); + } + } + } + public static void FileContains(MSBuildResult result, string filePath, string match) { if (result == null) @@ -432,6 +456,19 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests protected override string Heading => $"Build did not contain the line: '{Match}'."; } + private class BuildOutputContainsLineException : MSBuildXunitException + { + public BuildOutputContainsLineException(MSBuildResult result, string match) + : base(result) + { + Match = match; + } + + public string Match { get; } + + protected override string Heading => $"Build output contains the line: '{Match}'."; + } + private class FileContentFoundException : MSBuildXunitException { public FileContentFoundException(MSBuildResult result, string filePath, string content, string match) diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIncrementalismTest.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIncrementalismTest.cs index 288c46cf6a..f6b4c7ac4e 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIncrementalismTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIncrementalismTest.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; using System.Collections.Generic; using System.IO; using System.Linq; @@ -136,5 +137,83 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests Assert.FileDoesNotExist(result, IntermediateOutputPath, "Razor", "Views", "Home", "Index.cshtml.g.cs"); } } + + [Fact] + [InitializeTestProject("AppWithP2PReference", additionalProjects: "ClassLibrary")] + public async Task IncrementalBuild_WithP2P_WorksWhenBuildProjectReferencesIsDisabled() + { + // Simulates building the same way VS does by setting BuildProjectReferences=false. + // With this flag, the only target called is GetCopyToOutputDirectoryItems on the referenced project. + // We need to ensure that we continue providing Razor binaries and symbols as files to be copied over. + var result = await DotnetMSBuild(target: default); + + Assert.BuildPassed(result); + + Assert.FileExists(result, OutputPath, "AppWithP2PReference.dll"); + Assert.FileExists(result, OutputPath, "AppWithP2PReference.Views.dll"); + Assert.FileExists(result, OutputPath, "ClassLibrary.dll"); + Assert.FileExists(result, OutputPath, "ClassLibrary.Views.dll"); + Assert.FileExists(result, OutputPath, "ClassLibrary.Views.pdb"); + + result = await DotnetMSBuild(target: "Clean", "/p:BuildProjectReferences=false", suppressRestore: true); + Assert.BuildPassed(result); + + Assert.FileDoesNotExist(result, OutputPath, "AppWithP2PReference.dll"); + Assert.FileDoesNotExist(result, OutputPath, "AppWithP2PReference.Views.dll"); + Assert.FileDoesNotExist(result, OutputPath, "ClassLibrary.dll"); + Assert.FileDoesNotExist(result, OutputPath, "ClassLibrary.Views.dll"); + Assert.FileDoesNotExist(result, OutputPath, "ClassLibrary.Views.pdb"); + + // dotnet msbuild /p:BuildProjectReferences=false + result = await DotnetMSBuild(target: default, "/p:BuildProjectReferences=false", suppressRestore: true); + + Assert.BuildPassed(result); + Assert.FileExists(result, OutputPath, "AppWithP2PReference.dll"); + Assert.FileExists(result, OutputPath, "AppWithP2PReference.Views.dll"); + Assert.FileExists(result, OutputPath, "ClassLibrary.dll"); + Assert.FileExists(result, OutputPath, "ClassLibrary.Views.dll"); + Assert.FileExists(result, OutputPath, "ClassLibrary.Views.pdb"); + } + + [Fact] + [InitializeTestProject("ClassLibrary")] + public async Task Build_TouchesUpToDateMarkerFile() + { + var classLibraryDll = Path.Combine(IntermediateOutputPath, "ClassLibrary.dll"); + var classLibraryViewsDll = Path.Combine(IntermediateOutputPath, "ClassLibrary.Views.dll"); + var markerFile = Path.Combine(IntermediateOutputPath, "ClassLibrary.csproj.CopyComplete"); + + var result = await DotnetMSBuild("Build"); + Assert.BuildPassed(result); + + Assert.FileExists(result, classLibraryDll); + Assert.FileExists(result, classLibraryViewsDll); + Assert.FileExists(result, markerFile); + + // Gather thumbprints before incremental build. + var classLibraryThumbPrint = GetThumbPrint(classLibraryDll); + var classLibraryViewsThumbPrint = GetThumbPrint(classLibraryViewsDll); + var markerFileThumbPrint = GetThumbPrint(markerFile); + + result = await DotnetMSBuild("Build"); + Assert.BuildPassed(result); + + // Verify thumbprint file is unchanged between true incremental builds + Assert.Equal(classLibraryThumbPrint, GetThumbPrint(classLibraryDll)); + Assert.Equal(classLibraryViewsThumbPrint, GetThumbPrint(classLibraryViewsDll)); + // In practice, this should remain unchanged. However, since our tests reference + // binaries from other projects, this file gets updated by Microsoft.Common.targets + Assert.NotEqual(markerFileThumbPrint, GetThumbPrint(markerFile)); + + // Change a cshtml file and verify ClassLibrary.Views.dll and marker file are updated + File.AppendAllText(Path.Combine(Project.DirectoryPath, "Views", "_ViewImports.cshtml"), Environment.NewLine); + + result = await DotnetMSBuild("Build"); + Assert.BuildPassed(result); + + Assert.Equal(classLibraryThumbPrint, GetThumbPrint(classLibraryDll)); + Assert.NotEqual(classLibraryViewsThumbPrint, GetThumbPrint(classLibraryViewsDll)); + Assert.NotEqual(markerFileThumbPrint, GetThumbPrint(markerFile)); + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntrospectionTest.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntrospectionTest.cs index 1ddc59c75a..f19401a140 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntrospectionTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/BuildIntrospectionTest.cs @@ -18,11 +18,12 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests [InitializeTestProject("SimpleMvc")] public async Task RazorSdk_AddsCshtmlFilesToUpToDateCheckInput() { - var result = await DotnetMSBuild("_IntrospectUpToDateCheckInput"); + var result = await DotnetMSBuild("_IntrospectUpToDateCheck"); Assert.BuildPassed(result); Assert.BuildOutputContainsLine(result, $"UpToDateCheckInput: {Path.Combine("Views", "Home", "Index.cshtml")}"); Assert.BuildOutputContainsLine(result, $"UpToDateCheckInput: {Path.Combine("Views", "_ViewStart.cshtml")}"); + Assert.BuildOutputContainsLine(result, $"UpToDateCheckBuilt: {Path.Combine(IntermediateOutputPath, "SimpleMvc.Views.dll")}"); } [Fact] @@ -45,5 +46,31 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests Assert.BuildPassed(result); Assert.BuildOutputContainsLine(result, "UseRazorBuildServer: false"); } + + [Fact] + [InitializeTestProject("ClassLibrary")] + public async Task GetCopyToOutputDirectoryItems_WhenNoFileIsPresent_ReturnsEmptySequence() + { + var result = await DotnetMSBuild(target: default); + + Assert.BuildPassed(result); + + Assert.FileExists(result, OutputPath, "ClassLibrary.dll"); + Assert.FileExists(result, OutputPath, "ClassLibrary.Views.dll"); + + result = await DotnetMSBuild(target: "GetCopyToOutputDirectoryItems", "/t:_IntrospectGetCopyToOutputDirectoryItems /p:BuildProjectReferences=false", suppressRestore: true); + Assert.BuildPassed(result); + Assert.BuildOutputContainsLine(result, "AllItemsFullPathWithTargetPath: ClassLibrary.Views.dll"); + Assert.BuildOutputContainsLine(result, "AllItemsFullPathWithTargetPath: ClassLibrary.Views.pdb"); + + // Remove all views from the class library + Directory.Delete(Path.Combine(Project.DirectoryPath, "Views"), recursive: true); + + // dotnet msbuild /p:BuildProjectReferences=false + result = await DotnetMSBuild(target: "GetCopyToOutputDirectoryItems", "/t:_IntrospectGetCopyToOutputDirectoryItems /p:BuildProjectReferences=false", suppressRestore: true); + + Assert.BuildOutputDoesNotContainLine(result, "AllItemsFullPathWithTargetPath: ClassLibrary.Views.dll"); + Assert.BuildOutputDoesNotContainLine(result, "AllItemsFullPathWithTargetPath: ClassLibrary.Views.pdb"); + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/PublishIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/PublishIntegrationTest.cs index be6b1fed29..0931cfd43b 100644 --- a/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/PublishIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Design.Test/IntegrationTests/PublishIntegrationTest.cs @@ -27,6 +27,10 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests Assert.FileExists(result, PublishOutputPath, "SimpleMvc.Views.dll"); Assert.FileExists(result, PublishOutputPath, "SimpleMvc.Views.pdb"); + // Verify assets get published + Assert.FileExists(result, PublishOutputPath, "wwwroot", "js", "SimpleMvc.js"); + Assert.FileExists(result, PublishOutputPath, "wwwroot", "css", "site.css"); + // By default refs and .cshtml files will not be copied on publish Assert.FileCountEquals(result, 0, Path.Combine(PublishOutputPath, "refs"), "*.dll"); Assert.FileCountEquals(result, 0, Path.Combine(PublishOutputPath, "Views"), "*.cshtml"); @@ -294,6 +298,11 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests Assert.FileExists(result, PublishOutputPath, "ClassLibrary.pdb"); Assert.FileExists(result, PublishOutputPath, "ClassLibrary.Views.dll"); Assert.FileExists(result, PublishOutputPath, "ClassLibrary.Views.pdb"); + + // Verify fix for https://github.com/aspnet/Razor/issues/2295. No cshtml files should be published from the app + // or the ClassLibrary. + Assert.FileCountEquals(result, 0, Path.Combine(PublishOutputPath, "refs"), "*.dll"); + Assert.FileCountEquals(result, 0, Path.Combine(PublishOutputPath, "Views"), "*.cshtml"); } [Fact] @@ -310,5 +319,40 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests Assert.FileDoesNotExist(result, OutputPath, "SimpleMvcFSharp.Views.dll"); Assert.FileDoesNotExist(result, OutputPath, "SimpleMvcFSharp.Views.pdb"); } + + [Fact] + [InitializeTestProject("SimpleMvc")] + public async Task Publish_DoesNotPublishCustomRazorGenerateItems() + { + var additionalProjectContent = @" + + false + + + + + +"; + AddProjectFileContent(additionalProjectContent); + var result = await DotnetMSBuild("Publish"); + + Assert.BuildPassed(result); + + Assert.FileExists(result, PublishOutputPath, "SimpleMvc.dll"); + Assert.FileExists(result, PublishOutputPath, "SimpleMvc.pdb"); + Assert.FileExists(result, PublishOutputPath, "SimpleMvc.Views.dll"); + Assert.FileExists(result, PublishOutputPath, "SimpleMvc.Views.pdb"); + + // Verify assets get published + Assert.FileExists(result, PublishOutputPath, "wwwroot", "js", "SimpleMvc.js"); + Assert.FileExists(result, PublishOutputPath, "wwwroot", "css", "site.css"); + + // By default refs and .cshtml files will not be copied on publish + Assert.FileCountEquals(result, 0, Path.Combine(PublishOutputPath, "refs"), "*.dll"); + // Custom RazorGenerate item does not get published + Assert.FileDoesNotExist(result, PublishOutputPath, "Views", "Home", "Home.cshtml"); + // cshtml Content item that's not part of RazorGenerate gets published. + Assert.FileExists(result, PublishOutputPath, "Views", "Home", "About.cshtml"); + } } } diff --git a/test/testapps/RazorTest.Introspection.targets b/test/testapps/RazorTest.Introspection.targets index 575be8ef15..a22622b0d6 100644 --- a/test/testapps/RazorTest.Introspection.targets +++ b/test/testapps/RazorTest.Introspection.targets @@ -7,8 +7,11 @@ - + + + + @@ -18,4 +21,8 @@ + + + + diff --git a/test/testapps/SimpleMvc/wwwroot/css/site.css b/test/testapps/SimpleMvc/wwwroot/css/site.css new file mode 100644 index 0000000000..6b0e6c3ab2 --- /dev/null +++ b/test/testapps/SimpleMvc/wwwroot/css/site.css @@ -0,0 +1,37 @@ +/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification\ +for details on configuring this project to bundle and minify static web assets. */ +body { + padding-top: 50px; + padding-bottom: 20px; +} + +/* Wrapping element */ +/* Set some basic padding to keep content from hitting the edges */ +.body-content { + padding-left: 15px; + padding-right: 15px; +} + +/* Carousel */ +.carousel-caption p { + font-size: 20px; + line-height: 1.4; +} + +/* Make .svg files in the carousel display properly in older browsers */ +.carousel-inner .item img[src$=".svg"] { + width: 100%; +} + +/* QR code generator */ +#qrCode { + margin: 15px; +} + +/* Hide/rearrange for smaller screens */ +@media screen and (max-width: 767px) { + /* Hide captions */ + .carousel-caption { + display: none; + } +} diff --git a/test/testapps/SimpleMvc/wwwroot/js/SimpleMvc.js b/test/testapps/SimpleMvc/wwwroot/js/SimpleMvc.js new file mode 100644 index 0000000000..ad51101cd1 --- /dev/null +++ b/test/testapps/SimpleMvc/wwwroot/js/SimpleMvc.js @@ -0,0 +1 @@ +// This is a test file