From ea48c028cf85b21ee794e43d472ab54eba44352f Mon Sep 17 00:00:00 2001 From: David Federman Date: Fri, 24 Apr 2026 14:44:58 -0700 Subject: [PATCH] Make CollectDeclaredReferences support incremental builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the single CollectDeclaredReferences target into a pipeline of four targets to enable MSBuild Inputs/Outputs incremental skip: 1. _PrepareCollectDeclaredReferences (always runs) — computes properties, filters item groups, hashes non-file inputs (item groups, properties) into a cache file via Hash + WriteLinesToFile WriteOnlyWhenDifferent, builds _CollectDeclaredReferencesInputs, registers AdditionalFiles. 2. CollectDeclaredReferences (incremental via Inputs/Outputs) — runs the task which always writes the raw .tsv output. 3. _StabilizeCollectDeclaredReferences (incremental) — copies the raw .tsv to a stable .tsv via ReadLinesFromFile + WriteLinesToFile with WriteOnlyWhenDifferent. The stable file is what the compiler sees as AdditionalFiles, so CoreCompile only re-runs when content actually changes (not on timestamp-only changes). 4. _EmbedReferenceTrimmerDiagnosticsInBinlog (AfterTargets=CoreCompile) — embeds the raw .tsv in binlog. Additional cleanups: - Remove unused MSBuildProjectFile task property - Simplify SaveDeclaredReferences to write directly via StreamWriter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Package/build/ReferenceTrimmer.targets | 88 ++++++++++++++++++---- src/Tasks/CollectDeclaredReferencesTask.cs | 29 ++----- 2 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/Package/build/ReferenceTrimmer.targets b/src/Package/build/ReferenceTrimmer.targets index 393076d..0cf944b 100644 --- a/src/Package/build/ReferenceTrimmer.targets +++ b/src/Package/build/ReferenceTrimmer.targets @@ -3,18 +3,22 @@ - $(CoreCompileDependsOn);CollectDeclaredReferences + $(CoreCompileDependsOn);_StabilizeCollectDeclaredReferences - + - <_ReferenceTrimmerDeclaredReferencesFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)_ReferenceTrimmer_DeclaredReferences.tsv')) + <_ReferenceTrimmerDeclaredReferencesFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)_ReferenceTrimmer_DeclaredReferences.raw.tsv')) <_ReferenceTrimmerUsedReferencesFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)_ReferenceTrimmer_UsedReferences.log')) <_ReferenceTrimmerUnusedReferencesFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)_ReferenceTrimmer_UnusedReferences.log')) + <_ReferenceTrimmerCollectInputsCacheFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)_ReferenceTrimmer_CollectDeclaredReferences.cache')) + <_ReferenceTrimmerStableDeclaredReferencesFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)_ReferenceTrimmer_DeclaredReferences.tsv')) + <_CollectDeclaredReferencesHashInputs Include="@(_ReferenceTrimmerReferences -> 'REF=%(Identity)')" /> + <_CollectDeclaredReferencesHashInputs Include="@(PackageReference -> 'PKG=%(Identity)')" /> + <_CollectDeclaredReferencesHashInputs Include="@(ReferenceTrimmerIgnorePackageBuildFiles -> 'IGN=%(Identity)')" /> + <_CollectDeclaredReferencesHashInputs Include="TFM=$(ReferringTargetFrameworkForProjectReferences)" /> + <_CollectDeclaredReferencesHashInputs Include="TPM=$(TargetPlatformMoniker)" /> + <_CollectDeclaredReferencesHashInputs Include="RID=$(RuntimeIdentifier)" /> + <_CollectDeclaredReferencesHashInputs Include="NPR=$(NuGetPackageRoot)" /> + + + + + + + + + + + <_CollectDeclaredReferencesInputs Include="$(ProjectAssetsFile)" Condition="Exists('$(ProjectAssetsFile)')" /> + <_CollectDeclaredReferencesInputs Include="@(_ReferenceTrimmerResolvedReferences)" /> + <_CollectDeclaredReferencesInputs Include="@(_ReferenceTrimmerProjectReferences)" /> + + <_CollectDeclaredReferencesInputs Include="$(_ReferenceTrimmerCollectInputsCacheFile)" /> + + + + + + + + + - - - - - - + + + + + + + - + Condition="'$(EnableReferenceTrimmer)' != 'false'"> + + + + - + diff --git a/src/Tasks/CollectDeclaredReferencesTask.cs b/src/Tasks/CollectDeclaredReferencesTask.cs index 00e3c6a..63ed8f4 100644 --- a/src/Tasks/CollectDeclaredReferencesTask.cs +++ b/src/Tasks/CollectDeclaredReferencesTask.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Text; using System.Xml.Linq; using Microsoft.Build.Framework; using NuGet.Common; @@ -30,9 +29,6 @@ public sealed class CollectDeclaredReferencesTask : MSBuildTask [Required] public string? OutputFile { get; set; } - [Required] - public string? MSBuildProjectFile { get; set; } - public ITaskItem[]? References { get; set; } public ITaskItem[]? ResolvedReferences { get; set; } @@ -482,11 +478,11 @@ private static void SaveDeclaredReferences(IReadOnlyList decl { const char fieldDelimiter = '\t'; - StringBuilder writer = new(); + using StreamWriter writer = new(filePath); foreach (DeclaredReference reference in declaredReferences) { - writer.Append(reference.AssemblyPath); - writer.Append(fieldDelimiter); + writer.Write(reference.AssemblyPath); + writer.Write(fieldDelimiter); string kindString = reference.Kind switch { DeclaredReferenceKind.Reference => nameof(DeclaredReferenceKind.Reference), @@ -494,23 +490,10 @@ private static void SaveDeclaredReferences(IReadOnlyList decl DeclaredReferenceKind.PackageReference => nameof(DeclaredReferenceKind.PackageReference), _ => throw new InvalidDataException($"Unknown reference kind '{reference.Kind}'."), }; - writer.Append(kindString); - writer.Append(fieldDelimiter); - writer.Append(reference.Spec); - writer.AppendLine(); + writer.Write(kindString); + writer.Write(fieldDelimiter); + writer.WriteLine(reference.Spec); } - - string newContent = writer.ToString(); - if (File.Exists(filePath)) - { - string existing = File.ReadAllText(filePath); - if (string.Equals(existing, newContent, StringComparison.OrdinalIgnoreCase)) - { - return; - } - } - - File.WriteAllText(filePath, newContent); } private sealed class PackageInfoBuilder