Repository navigation
Fix binlog replay with MSBuild 18.x and normalize obsolete constructor path - #329
mscottford wants to merge 1 commit into
Conversation
…r path Three related fixes for reading binlogs produced by the .NET 10 SDK: 1. AnalyzerManager.Analyze: replace StructuredLogger.BinLogReader with Microsoft.Build.Logging.BinaryLogReplayEventSource. The official MSBuild reader handles all 18.x event types (AssemblyLoadBuildEventArgs etc.), whereas BinLogReader from MSBuild.StructuredLogger may stop replaying mid-stream when it encounters event types it does not recognise, leaving _evalulationResults empty so ProjectStarted falls back to null propertiesAndItems and produces no results. 2. EventProcessor.ProjectStarted: treat an empty e.Properties collection the same as null. BinLogReader creates ProjectStartedEventArgs with Properties set to a non-null but empty collection for binlog format 14+ events, so the existing `e.Properties is null` check always fell through to the old-format path, using the empty collection instead of the populated evaluation-phase results. 3. AnalyzerManager obsolete string constructor: wrap the path in Path.GetFullPath() before passing it to IOPath.Parse(), restoring the behaviour of the previous NormalizePath implementation that was removed when IOPath was introduced. Regression tests added for fixes 1 and 2. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix binlog replay with MSBuild 18.x and normalize obsolete constructor path. See Buildalyzer#329.
There was a problem hiding this comment.
For readablilty, I suggest you create an file
Extensions/System.Collections.IEnumerable.cs, containing te following:
namespace System.Collections;
internal static class EnumerableExtensions
{
extension(IEnumerable? enumerable)
{
public bool HasNone
=> enumerable?.GetEnumerator().MoveNext() is not true;
}
}Than you don't need the explanation at all, als e.Proparties.HasNone, tells the story.
Note, that <LangVersion>14</LangVersion> has to be added, which is a good thing anyway.
There was a problem hiding this comment.
After some consideration: Lets first merge #331 what does part the changes you want, and then follow with the rest.
| /// </summary> | ||
| [Test] | ||
| public void SdkProjectWithProjectReferenceGetsReferencesFromBinaryLog( | ||
| [ValueSource(nameof(Preferences))] EnvironmentPreference preference) |
There was a problem hiding this comment.
Use TestTools.Context.ForProject
Corniel
left a comment
There was a problem hiding this comment.
Nice improvements, and good spots! I'd like some (ecstatic) changes, but nothing to dramatic.
There was a problem hiding this comment.
Pull request overview
Fixes Buildalyzer’s binary log replay against newer MSBuild (18.x) binlogs and restores expected path normalization behavior, with integration regression tests to prevent regressions when analyzing from binlogs.
Changes:
- Switch binlog replay in
AnalyzerManager.AnalyzetoMicrosoft.Build.Logging.BinaryLogReplayEventSourcefor MSBuild 18.x event compatibility. - In
EventProcessor.ProjectStarted, fall back to evaluation-phase properties/items whenProjectStartedEventArgs.Propertiesis null or empty. - Normalize the obsolete
AnalyzerManager(string)constructor path viaPath.GetFullPath()beforeIOPath.Parse(), and add regression integration tests for binlog replay.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| tests/Buildalyzer.Tests/Integration/SimpleProjectsFixture.cs | Adds regression integration tests validating ProjectReferences survive binlog replay (host-side and child-side logging). |
| src/Buildalyzer/Logging/EventProcessor.cs | Treats empty ProjectStartedEventArgs.Properties like null to correctly use evaluation results for newer binlog formats. |
| src/Buildalyzer/AnalyzerManager.cs | Uses BinaryLogReplayEventSource for replay and restores full-path normalization in the obsolete string constructor. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| string binLogPath = Path.ChangeExtension(Path.GetTempFileName(), ".binlog"); | ||
| analyzer.AddBinaryLogger(binLogPath); |
There was a problem hiding this comment.
Path.GetTempFileName() creates a real temp file on disk. Using Path.ChangeExtension(...) means the original .tmp file is left behind and never deleted. Prefer generating a unique path under Path.GetTempPath() (e.g., with a GUID) to avoid leaking temp files, or delete the original temp file as well.
| // Given | ||
| StringWriter log = new StringWriter(); | ||
| IProjectAnalyzer analyzer = GetProjectAnalyzer(@"SdkNetCore2ProjectWithReference\SdkNetCore2ProjectWithReference.csproj", log); | ||
| string binLogPath = Path.ChangeExtension(Path.GetTempFileName(), ".binlog"); |
There was a problem hiding this comment.
Same temp-file issue as above: Path.GetTempFileName() creates a .tmp file that is not deleted when you switch extensions. Consider generating a .binlog path without creating the .tmp file, or ensure the original temp file is removed.
| string binLogPath = Path.ChangeExtension(Path.GetTempFileName(), ".binlog"); | |
| string binLogPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".binlog"); |
| Preference = preference | ||
| }; | ||
| // Child-side binary logging: the build process itself writes the binlog | ||
| options.Arguments.Add($"/bl:{binLogPath}"); |
There was a problem hiding this comment.
/bl: paths containing spaces need quoting. Since EnvironmentOptions.Arguments are concatenated via string.Join(" ", ...) without automatic quoting, an unquoted temp path like C:\Users\First Last\... can make this test flaky. Consider using /bl:"{binLogPath}" (or equivalent) so MSBuild receives the full path as one argument.
| options.Arguments.Add($"/bl:{binLogPath}"); | |
| options.Arguments.Add($"/bl:\"{binLogPath}\""); |
There was a problem hiding this comment.
AnalyzerManager.Analyze no longer uses MSBuild.StructuredLogger, but this file still has extern alias StructuredLogger; and using StructuredLogger::... at the top. These are now unused and can produce warnings/confusion; consider removing the alias/using (and any related package aliasing) now that replay uses Microsoft.Build.Logging.BinaryLogReplayEventSource.

Summary
Three related fixes for reading binlogs produced by the .NET 10 SDK (MSBuild 18.x):
AnalyzerManager.Analyze: replaceStructuredLogger.BinLogReaderwithMicrosoft.Build.Logging.BinaryLogReplayEventSource. The official MSBuild reader handles all 18.x event types (includingAssemblyLoadBuildEventArgs), whereasBinLogReaderfromMSBuild.StructuredLoggermay stop replaying mid-stream when it encounters unrecognised event types, leaving_evalulationResultsempty soProjectStartedfalls back tonullpropertiesAndItems and produces no results.EventProcessor.ProjectStarted: treat an emptye.Propertiescollection the same asnull.BinLogReadercreatesProjectStartedEventArgswithPropertiesset to a non-null but empty collection for binlog format 14+ events, so the existinge.Properties is nullcheck always fell through to the old-format path, using the empty collection instead of the populated evaluation-phase results. This leftProjectReferencesempty after a binlog round-trip.AnalyzerManagerobsolete string constructor: wrap the path inPath.GetFullPath()before passing it toIOPath.Parse(), restoring the behaviour of the previousNormalizePathimplementation that was lost whenIOPathwas introduced.Regression tests are included for the first two fixes.
Test plan
SdkProjectWithProjectReferenceGetsReferencesFromBinaryLog— builds a project with a project reference, replays the binlog, assertsProjectReferencesare populatedSdkProjectWithProjectReferenceGetsReferencesFromChildSideBinaryLog— same but using child-side/bl:logging🤖 Generated with Claude Code