diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs index 6bcf622d1ddb..658b9d22631d 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs @@ -201,6 +201,7 @@ private int ExecuteCore( if (GenerateDeclaration.HasValue()) { b.Features.Add(new SetSuppressPrimaryMethodBodyOptionFeature()); + b.Features.Add(new SuppressChecksumOptionsFeature()); } if (RootNamespace.HasValue()) @@ -227,6 +228,7 @@ private int ExecuteCore( }); var results = GenerateCode(engine, sourceItems); + var isGeneratingDeclaration = GenerateDeclaration.HasValue(); foreach (var result in results) { @@ -255,6 +257,18 @@ private int ExecuteCore( { // Only output the file if we generated it without errors. var outputFilePath = result.InputItem.OutputPath; + var generatedCode = result.CSharpDocument.GeneratedCode; + if (isGeneratingDeclaration) + { + // When emiting declarations, only write if it the contents are different. + // This allows build incrementalism to kick in when the declaration remains unchanged between builds. + if (File.Exists(outputFilePath) && + string.Equals(File.ReadAllText(outputFilePath), generatedCode, StringComparison.Ordinal)) + { + continue; + } + } + File.WriteAllText(outputFilePath, result.CSharpDocument.GeneratedCode); } } diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/BuildIncrementalismTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/BuildIncrementalismTest.cs index df84c3b7a66b..e651cad69f80 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/BuildIncrementalismTest.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/BuildIncrementalismTest.cs @@ -172,11 +172,61 @@ async Task VerifyError() } } + [Fact] + [InitializeTestProject("MvcWithComponents")] + public async Task BuildComponents_DoesNotRegenerateComponentDefinition_WhenDefinitionIsUnchanged() + { + // Act - 1 + var updatedContent = "Some content"; + var tagHelperOutputCache = Path.Combine(IntermediateOutputPath, "MvcWithComponents.TagHelpers.output.cache"); + + var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs"); + var generatedDefinitionFile = Path.Combine(RazorComponentIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs"); + + // Assert - 1 + var result = await DotnetMSBuild("Build"); + + Assert.BuildPassed(result); + var outputFile = Path.Combine(OutputPath, "MvcWithComponents.dll"); + Assert.FileExists(result, OutputPath, "MvcWithComponents.dll"); + var outputAssemblyThumbprint = GetThumbPrint(outputFile); + + Assert.FileExists(result, generatedDefinitionFile); + var generatedDefinitionThumbprint = GetThumbPrint(generatedDefinitionFile); + Assert.FileExists(result, generatedFile); + var generatedFileThumbprint = GetThumbPrint(generatedFile); + + Assert.FileExists(result, tagHelperOutputCache); + Assert.FileContains( + result, + tagHelperOutputCache, + @"""Name"":""MvcWithComponents.Views.Shared.NavMenu"""); + + var definitionThumbprint = GetThumbPrint(tagHelperOutputCache); + + // Act - 2 + ReplaceContent(updatedContent, "Views", "Shared", "NavMenu.razor"); + result = await DotnetMSBuild("Build"); + + // Assert - 2 + Assert.FileExists(result, generatedDefinitionFile); + // Definition file remains unchanged. + Assert.Equal(generatedDefinitionThumbprint, GetThumbPrint(generatedDefinitionFile)); + Assert.FileExists(result, generatedFile); + // Generated file should change and include the new content. + Assert.NotEqual(generatedFileThumbprint, GetThumbPrint(generatedFile)); + Assert.FileContains(result, generatedFile, updatedContent); + + // TagHelper cache should remain unchanged. + Assert.Equal(definitionThumbprint, GetThumbPrint(tagHelperOutputCache)); + } + [Fact] [InitializeTestProject("MvcWithComponents")] public async Task BuildComponents_RegeneratesComponentDefinition_WhenFilesChange() { // Act - 1 + var updatedContent = "@code { [Parameter] public string AParameter { get; set; } }"; var tagHelperOutputCache = Path.Combine(IntermediateOutputPath, "MvcWithComponents.TagHelpers.output.cache"); var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs"); @@ -204,7 +254,7 @@ public async Task BuildComponents_RegeneratesComponentDefinition_WhenFilesChange var definitionThumbprint = GetThumbPrint(tagHelperOutputCache); // Act - 2 - ReplaceContent("Different things", "Views", "Shared", "NavMenu.razor"); + ReplaceContent(updatedContent, "Views", "Shared", "NavMenu.razor"); result = await DotnetMSBuild("Build"); // Assert - 2 @@ -222,8 +272,12 @@ public async Task BuildComponents_RegeneratesComponentDefinition_WhenFilesChange tagHelperOutputCache, @"""Name"":""MvcWithComponents.Views.Shared.NavMenu"""); - // TODO: - Assert.Equal(definitionThumbprint, GetThumbPrint(tagHelperOutputCache)); + Assert.FileContains( + result, + tagHelperOutputCache, + "AParameter"); + + Assert.NotEqual(definitionThumbprint, GetThumbPrint(tagHelperOutputCache)); } [Fact] diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets index 89e93a3a98a4..922d3fb34ef2 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets @@ -29,6 +29,7 @@ Copyright (c) .NET Foundation. All rights reserved. <_RazorComponentInputHash> <_RazorComponentInputCacheFile>$(IntermediateOutputPath)$(MSBuildProjectName).RazorComponent.input.cache + <_RazorComponentDeclarationOutputCacheFile>$(IntermediateOutputPath)$(MSBuildProjectName).RazorComponent.output.cache @@ -85,7 +86,7 @@ Copyright (c) .NET Foundation. All rights reserved. Name="RazorGenerateComponentDeclaration" DependsOnTargets="$(RazorGenerateComponentDeclarationDependsOn)" Inputs="$(MSBuildAllProjects);@(RazorComponentWithTargetPath);$(_RazorComponentInputCacheFile)" - Outputs="@(_RazorComponentDeclaration)" + Outputs="$(_RazorComponentDeclarationOutputCacheFile)" Condition="'@(RazorComponentWithTargetPath->Count())'!='0'"> @@ -120,8 +121,13 @@ Copyright (c) .NET Foundation. All rights reserved. TagHelperManifest="$(_RazorComponentDeclarationManifest)" GenerateDeclaration="true" /> + + +