Repository navigation
Update packages and add net10.0 target; drop net6.0 - #330
mscottford wants to merge 3 commits 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.
- Buildalyzer, Buildalyzer.Workspaces: drop net6.0, add net10.0 target - Microsoft.Build / Microsoft.Build.Tasks.Core: 17.14.28 → 18.4.0 - Microsoft.Build.Utilities.Core (Logger): 14.3.0 → 18.4.0 - MSBuild.StructuredLogger: 2.2.158 → 2.3.154 - Microsoft.Extensions.Logging: 6.0.0 → 10.0.6 - NuGet.Frameworks: 6.9.1 → 7.3.1 - Microsoft.CodeAnalysis.CSharp/VisualBasic.Workspaces: 4.10.0 → 5.3.0 - AwesomeAssertions: 9.3.0 → 9.4.0 - NUnit: 4.4.0 → 4.5.1 - Microsoft.SourceLink.GitHub: 8.0.0 → 10.0.202 - Microsoft.Sbom.Targets: 4.1.4 → 4.1.5 - Version bump: 1.0.0 → 8.1.0 - Suppress NU1701 for net8+/net10 consumers of MSBuild 18.x assets - Pin System.Security.Cryptography.Xml 10.0.6 to suppress NU1903 warnings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update packages and add net10.0 target; drop net6.0. See Buildalyzer#330.
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.10.0" /> | ||
| <PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="4.10.0" /> | ||
| <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.3.0" /> |
There was a problem hiding this comment.
I've think we should only go for [5.0.0,), I guess we facilitate people who are stuck to older Roslyn versions.
| /// The fix is to treat an empty <c>e.Properties</c> the same as null. | ||
| /// </summary> | ||
| [Test] | ||
| public void SdkProjectWithProjectReferenceGetsReferencesFromBinaryLog( |
There was a problem hiding this comment.
Can you use Buildalyzer.TestTools.Context.ForProject instead? Is should lead to a cleaner test.
There was a problem hiding this comment.
Pull request overview
Updates Buildalyzer to align with the .NET 10 / MSBuild 18 toolchain by adjusting target frameworks, refreshing key dependencies, and improving binlog replay behavior (plus adding regression coverage around project references when replaying binlogs).
Changes:
- Drop
net6.0and add/standardizenet10.0targeting (plus version bump to8.1.0). - Upgrade MSBuild-related packages and adjust binlog replay to handle newer MSBuild 18.x event types reliably.
- Add binlog replay regression tests to ensure
ProjectReferencessurvive a build → binlog → replay round-trip.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Buildalyzer.Workspaces.Tests/Buildalyzer.Workspaces.Tests.csproj | Updates test dependency versions (AwesomeAssertions, NUnit). |
| tests/Buildalyzer.Tests/Buildalyzer.Tests.csproj | Updates test dependency versions (AwesomeAssertions, NUnit). |
| tests/Buildalyzer.Tests/Integration/SimpleProjectsFixture.cs | Adds regression tests for ProjectReferences when replaying binlogs. |
| src/Buildalyzer/Logging/EventProcessor.cs | Treats empty ProjectStartedEventArgs.Properties as equivalent to null for evaluation fallback. |
| src/Buildalyzer/AnalyzerManager.cs | Switches binlog replay to BinaryLogReplayEventSource and normalizes obsolete ctor input paths. |
| src/Buildalyzer/Buildalyzer.csproj | Drops net6.0, adds net10.0, upgrades core packages, and suppresses NU1701. |
| src/Buildalyzer.Workspaces/Buildalyzer.Workspaces.csproj | Drops net6.0, keeps net8.0 + net10.0, upgrades Roslyn Workspaces packages, suppresses NU1701. |
| src/Buildalyzer.Logger/Buildalyzer.Logger.csproj | Updates MSBuild Utilities dependency to align with MSBuild 18.x. |
| package.props | Minor build-time package bump (SBOM targets). |
| Directory.Build.props | Bumps version to 8.1.0, upgrades SourceLink, pins System.Security.Cryptography.Xml to address audit warnings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // stops replaying mid-stream when it encounters unknown event types in newer binlogs, | ||
| // causing ProjectEvaluationFinishedEventArgs to never fire and leaving _evalulationResults | ||
| // empty, so ProjectStarted falls back to null propertiesAndItems and no results are produced. | ||
| var reader = new Microsoft.Build.Logging.BinaryLogReplayEventSource(); |
There was a problem hiding this comment.
BinaryLogReplayEventSource holds unmanaged resources/streams and should be disposed after replay. Wrap reader in a using (or try/finally) so the binlog file handle is reliably released even if Replay throws, and to satisfy IDisposable analyzers.
| var reader = new Microsoft.Build.Logging.BinaryLogReplayEventSource(); | |
| using var reader = new Microsoft.Build.Logging.BinaryLogReplayEventSource(); |
| // Treat an empty e.Properties the same as null: BinLogReader (MSBuild.StructuredLogger) | ||
| // creates ProjectStartedEventArgs with Properties set to a non-null but empty collection | ||
| // rather than null for binlog format 14+ events. The empty collection carries no useful |
There was a problem hiding this comment.
This comment (and the related tests) still refers to BinLogReader from MSBuild.StructuredLogger, but Analyze now replays binlogs via BinaryLogReplayEventSource. Update the comment to describe the current replay mechanism (or generalize it to “binlog replay event sources”) so the rationale stays accurate over time.
| // Treat an empty e.Properties the same as null: BinLogReader (MSBuild.StructuredLogger) | |
| // creates ProjectStartedEventArgs with Properties set to a non-null but empty collection | |
| // rather than null for binlog format 14+ events. The empty collection carries no useful | |
| // Treat an empty e.Properties the same as null: some binlog replay event sources | |
| // create ProjectStartedEventArgs with Properties set to a non-null but empty collection | |
| // rather than null for binlog format 14+ events. The empty collection carries no useful |
| StringWriter log = new StringWriter(); | ||
| IProjectAnalyzer analyzer = GetProjectAnalyzer(@"SdkNetCore2ProjectWithReference\SdkNetCore2ProjectWithReference.csproj", log); | ||
| EnvironmentOptions options = new EnvironmentOptions { Preference = preference }; | ||
| string binLogPath = Path.ChangeExtension(Path.GetTempFileName(), ".binlog"); |
There was a problem hiding this comment.
Path.GetTempFileName() creates a real temp file; changing the extension leaves the original .tmp file behind, so these tests will leak temp files over time. Use Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".binlog") (or delete the original temp file returned by GetTempFileName) instead.
| 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: is being passed without quoting the path. Since BuildEnvironment.Arguments are concatenated into a single command-line string, a temp path containing spaces can break MSBuild argument parsing and make the test flaky on machines with spaced user profiles. Quote the path (e.g., /bl:"...") or ensure the argument builder quotes paths automatically.
| options.Arguments.Add($"/bl:{binLogPath}"); | |
| options.Arguments.Add($"/bl:\"{binLogPath}\""); |
| // 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 leak as above: Path.GetTempFileName() creates a .tmp file that is never deleted when you only delete the .binlog path. Prefer a non-creating temp filename (random name under GetTempPath) or delete the original temp file too.
| string binLogPath = Path.ChangeExtension(Path.GetTempFileName(), ".binlog"); | |
| string binLogPath = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".binlog")); |
| /// Root cause: <see cref="Buildalyzer.Logging.EventProcessor"/> checks | ||
| /// <c>e.Properties is null</c> to detect the "new-format" binlog (format 14+) and fall | ||
| /// back to evaluation-phase items. However, <c>BinLogReader</c> from | ||
| /// MSBuild.StructuredLogger creates <c>ProjectStartedEventArgs</c> with | ||
| /// <c>Properties</c> set to an empty non-null collection rather than <c>null</c>, | ||
| /// so the check always resolves to the old-format path — using the empty | ||
| /// <c>e.Properties</c>/<c>e.Items</c> instead of the populated evaluation results. | ||
| /// The fix is to treat an empty <c>e.Properties</c> the same as null. |
There was a problem hiding this comment.
The XML doc comment still describes the replay path as going through BinLogReader and EventProcessor’s e.Properties is null logic, but the implementation now uses BinaryLogReplayEventSource in AnalyzerManager.Analyze. Update the comment to reflect the current code path so future debugging doesn’t chase the wrong component.
| /// Root cause: <see cref="Buildalyzer.Logging.EventProcessor"/> checks | |
| /// <c>e.Properties is null</c> to detect the "new-format" binlog (format 14+) and fall | |
| /// back to evaluation-phase items. However, <c>BinLogReader</c> from | |
| /// MSBuild.StructuredLogger creates <c>ProjectStartedEventArgs</c> with | |
| /// <c>Properties</c> set to an empty non-null collection rather than <c>null</c>, | |
| /// so the check always resolves to the old-format path — using the empty | |
| /// <c>e.Properties</c>/<c>e.Items</c> instead of the populated evaluation results. | |
| /// The fix is to treat an empty <c>e.Properties</c> the same as null. | |
| /// This test exercises the current replay path used by <see cref="IAnalyzerManager.Analyze"/>, | |
| /// which replays the binlog through <c>BinaryLogReplayEventSource</c>. Keep this comment in | |
| /// sync with that code path so future debugging does not chase older implementations such as | |
| /// <c>BinLogReader</c> or the previous <c>EventProcessor</c> <c>e.Properties</c> handling. |
| /// it from the build process itself. Replaying that log via | ||
| /// <see cref="IAnalyzerManager.Analyze"/> triggers the | ||
| /// <c>BinLogReader</c> path in <c>EventProcessor</c> where | ||
| /// <c>ProjectStartedEventArgs.Properties</c> is a non-null but empty collection. | ||
| /// Because <see cref="Logging.EventProcessor"/> checks <c>e.Properties is null</c> | ||
| /// it never falls back to the evaluation-phase items, leaving |
There was a problem hiding this comment.
Same as the previous doc block: this comment explains the failure in terms of a BinLogReader code path, but Analyze now replays binlogs using BinaryLogReplayEventSource. Please update the comment to match the current implementation so it remains a useful regression note.
| /// it from the build process itself. Replaying that log via | |
| /// <see cref="IAnalyzerManager.Analyze"/> triggers the | |
| /// <c>BinLogReader</c> path in <c>EventProcessor</c> where | |
| /// <c>ProjectStartedEventArgs.Properties</c> is a non-null but empty collection. | |
| /// Because <see cref="Logging.EventProcessor"/> checks <c>e.Properties is null</c> | |
| /// it never falls back to the evaluation-phase items, leaving | |
| /// it from the build process itself. Replaying that log via | |
| /// <see cref="IAnalyzerManager.Analyze"/> uses <c>BinaryLogReplayEventSource</c>, and in | |
| /// this scenario <c>ProjectStartedEventArgs.Properties</c> can be a non-null but empty | |
| /// collection. Because <see cref="Logging.EventProcessor"/> checks | |
| /// <c>e.Properties is null</c>, it never falls back to the evaluation-phase items, leaving |
|
@mscottford Lets merge #332 first. That should make continuing with this easier (at least, I hope so). |

Summary
net6.0target, addnet10.0toBuildalyzerandBuildalyzer.WorkspacesMSBuild.StructuredLoggerto 2.3.154 (required to parse binlog format 25 produced by MSBuild 18.x)NU1701for net8+/net10 consumers of MSBuild 18.x (which only shipsnet472assets but works fine via compatibility fallback)System.Security.Cryptography.Xml10.0.6 to clearNU1903vulnerability warningsDependencies
Test plan
🤖 Generated with Claude Code