From e239862107d59e51e7b726c76322580c9b28344a Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Mon, 15 Dec 2025 13:19:19 -0600 Subject: [PATCH 01/34] Try to prevent direct creation of buildeventcontexts so we can control id propagation --- .../BackEnd/SdkResolverService_Tests.cs | 5 +- .../Evaluation/Expander_Tests.cs | 10 +- .../BackEnd/BuildManager/BuildManager.cs | 20 ++- .../Logging/LoggingServiceLogMethods.cs | 29 ++-- .../Components/Logging/NodeLoggingContext.cs | 2 +- .../Logging/ProjectLoggingContext.cs | 2 +- .../RequestBuilder/RequestBuilder.cs | 3 +- .../BackEnd/Components/Scheduler/Scheduler.cs | 14 +- .../Shared/BuildRequestConfiguration.cs | 9 +- src/Build/Instance/ProjectInstance.cs | 2 +- .../BuildEventContext_FluentAPI_Tests.cs | 127 +++++++++++++++ src/Framework.UnitTests/EventArgs_Tests.cs | 5 +- .../ProjectStartedEventArgs_Tests.cs | 2 +- src/Framework/BuildEventArgs.cs | 2 +- src/Framework/BuildEventContext.cs | 145 ++++++++++++------ src/Framework/ProjectStartedEventArgs.cs | 4 +- 16 files changed, 284 insertions(+), 97 deletions(-) create mode 100644 src/Framework.UnitTests/BuildEventContext_FluentAPI_Tests.cs diff --git a/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs b/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs index 95730b3440a..bc967b4087b 100644 --- a/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs +++ b/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs @@ -38,7 +38,10 @@ public SdkResolverService_Tests() _loggingContext = new MockLoggingContext( loggingService, - new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0, 0)); + BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 0) + .WithProjectInstanceId(0) + .WithTargetId(0) + .WithTaskId(0)); } [Fact] diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs index a7eaef54016..309639cd0c4 100644 --- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs @@ -5239,7 +5239,10 @@ public void FastPathValidationTest(string methodInvocationMetadata) loggingService.RegisterLogger(logger); var loggingContext = new MockLoggingContext( loggingService, - new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0, 0)); + BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 0) + .WithProjectInstanceId(0) + .WithTargetId(0) + .WithTaskId(0)); _ = new Expander( new PropertyDictionary(), @@ -5276,7 +5279,10 @@ public void PropertyFunctionRegisterBuildCheck() loggingService.RegisterLogger(logger); var loggingContext = new MockLoggingContext( loggingService, - new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0, 0)); + BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 0) + .WithProjectInstanceId(0) + .WithTargetId(0) + .WithTaskId(0)); var dummyAssemblyFile = env.CreateFile(env.CreateFolder(), "test.dll"); var result = new Expander(new PropertyDictionary(), FileSystems.Default, loggingContext) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 97c7d449b1f..70768b90eed 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -1588,7 +1588,7 @@ private void LoadSolutionIntoConfiguration(BuildRequestConfiguration config, Bui var buildEventContext = request.BuildEventContext; if (buildEventContext == BuildEventContext.Invalid) { - buildEventContext = new BuildEventContext(request.SubmissionId, 0, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + buildEventContext = CreateErrorLoggingContext(request.SubmissionId, 0); } var instances = ProjectInstance.LoadSolutionForBuild( @@ -1850,13 +1850,21 @@ void LogInvalidProjectFileError(InvalidProjectFileException projectException) { if (!projectException.HasBeenLogged) { - BuildEventContext buildEventContext = new BuildEventContext(submission.SubmissionId, 1, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = CreateErrorLoggingContext(submission.SubmissionId); ((IBuildComponentHost)this).LoggingService.LogInvalidProjectFileError(buildEventContext, projectException); projectException.HasBeenLogged = true; } } } + /// + /// Creates a BuildEventContext suitable for error logging for the given submission. + /// + /// The submission ID + /// The node ID, defaults to 1 for general error logging + /// A BuildEventContext for logging errors + private static BuildEventContext CreateErrorLoggingContext(int submissionId, int nodeId = 1) => BuildEventContext.CreateInitial(submissionId, nodeId); + /// /// Waits to drain all events of logging service. /// This method shall be used carefully because during draining, LoggingService will block all incoming events. @@ -1959,7 +1967,7 @@ void IssueBuildSubmissionToSchedulerImpl(BuildSubmission submission, bool allowM { if (!projectException.HasBeenLogged) { - BuildEventContext projectBuildEventContext = new BuildEventContext(submission.SubmissionId, 1, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + BuildEventContext projectBuildEventContext = CreateErrorLoggingContext(submission.SubmissionId); ((IBuildComponentHost)this).LoggingService.LogInvalidProjectFileError(projectBuildEventContext, projectException); projectException.HasBeenLogged = true; } @@ -1975,7 +1983,7 @@ void IssueBuildSubmissionToSchedulerImpl(BuildSubmission submission, bool allowM if (ex is not InvalidProjectFileException) { - var buildEventContext = new BuildEventContext(submission.SubmissionId, 1, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + var buildEventContext = CreateErrorLoggingContext(submission.SubmissionId); ((IBuildComponentHost)this).LoggingService.LogFatalBuildError(buildEventContext, ex, new BuildEventFileInfo(submission.BuildRequestData.ProjectFullPath)); } } @@ -2601,7 +2609,7 @@ private void HandleNodeShutdown(int node, NodeShutdown shutdownPacket) ILoggingService loggingService = ((IBuildComponentHost)this).GetComponent(BuildComponentType.LoggingService); foreach (BuildSubmissionBase submission in _buildSubmissions.Values) { - BuildEventContext buildEventContext = new BuildEventContext(submission.SubmissionId, BuildEventContext.InvalidNodeId, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = CreateErrorLoggingContext(submission.SubmissionId, BuildEventContext.InvalidNodeId); string exception = ExceptionHandling.ReadAnyExceptionFromFile(_instantiationTimeUtc); loggingService?.LogError(buildEventContext, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, ExceptionHandling.DebugDumpPath, exception); } @@ -2757,7 +2765,7 @@ private void PerformSchedulingActions(IEnumerable responses) if (newNodes?.Count != response.NumberOfNodesToCreate || newNodes.Any(n => n == null)) { - BuildEventContext buildEventContext = new BuildEventContext(0, Scheduler.VirtualNode, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = CreateErrorLoggingContext(0, Scheduler.VirtualNode); ((IBuildComponentHost)this).LoggingService.LogError(buildEventContext, new BuildEventFileInfo(String.Empty), "UnableToCreateNode", response.RequiredNodeType.ToString("G")); throw new BuildAbortedException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("UnableToCreateNode", response.RequiredNodeType.ToString("G"))); diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 31c9f55eeb0..b10864b4276 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -402,7 +402,7 @@ public void LogBuildCanceled() /// public BuildEventContext CreateEvaluationBuildEventContext(int nodeId, int submissionId) - => new BuildEventContext(submissionId, nodeId, NextEvaluationId, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + => BuildEventContext.CreateInitial(submissionId, nodeId).WithEvaluationId(NextEvaluationId); /// public BuildEventContext CreateProjectCacheBuildEventContext( @@ -420,7 +420,10 @@ public BuildEventContext CreateProjectCacheBuildEventContext( // If a invalid node id is used the messages become deferred in the console logger and spit out at the end. int nodeId = Scheduler.InProcNodeId; - return new BuildEventContext(submissionId, nodeId, evaluationId, projectInstanceId, projectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + return BuildEventContext.CreateInitial(submissionId, nodeId) + .WithEvaluationId(evaluationId) + .WithProjectInstanceId(projectInstanceId) + .WithProjectContextId(projectContextId); } /// @@ -569,7 +572,11 @@ public ProjectStartedEventArgs CreateProjectStarted( } } - BuildEventContext projectBuildEventContext = new BuildEventContext(submissionId, nodeBuildEventContext.NodeId, evaluationId, configurationId, projectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + BuildEventContext projectBuildEventContext = nodeBuildEventContext + .WithSubmissionId(submissionId) + .WithEvaluationId(evaluationId) + .WithProjectInstanceId(configurationId) + .WithProjectContextId(projectContextId); ErrorUtilities.VerifyThrow(parentBuildEventContext != null, "Need a parentBuildEventContext"); @@ -641,13 +648,7 @@ public void LogProjectFinished(BuildEventContext projectBuildEventContext, strin public BuildEventContext LogTargetStarted(BuildEventContext projectBuildEventContext, string targetName, string projectFile, string projectFileOfTargetElement, string parentTargetName, TargetBuiltReason buildReason) { ErrorUtilities.VerifyThrow(projectBuildEventContext != null, "projectBuildEventContext is null"); - BuildEventContext targetBuildEventContext = new BuildEventContext( - projectBuildEventContext.SubmissionId, - projectBuildEventContext.NodeId, - projectBuildEventContext.ProjectInstanceId, - projectBuildEventContext.ProjectContextId, - NextTargetId, - BuildEventContext.InvalidTaskId); + BuildEventContext targetBuildEventContext = projectBuildEventContext.WithTargetId(NextTargetId); if (!OnlyLogCriticalEvents) { @@ -738,13 +739,7 @@ public void LogTaskStarted(BuildEventContext taskBuildEventContext, string taskN public BuildEventContext LogTaskStarted2(BuildEventContext targetBuildEventContext, string taskName, string projectFile, string projectFileOfTaskNode, int line, int column, string taskAssemblyLocation) { ErrorUtilities.VerifyThrow(targetBuildEventContext != null, "targetBuildEventContext is null"); - BuildEventContext taskBuildEventContext = new BuildEventContext( - targetBuildEventContext.SubmissionId, - targetBuildEventContext.NodeId, - targetBuildEventContext.ProjectInstanceId, - targetBuildEventContext.ProjectContextId, - targetBuildEventContext.TargetId, - NextTaskId); + BuildEventContext taskBuildEventContext = targetBuildEventContext.WithTaskId(NextTaskId); if (!OnlyLogCriticalEvents) { diff --git a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs index e03c8ed13e7..350e82e9ebc 100644 --- a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs @@ -21,7 +21,7 @@ internal class NodeLoggingContext : BuildLoggingContext /// The /// true if this is an in-process node, otherwise false. internal NodeLoggingContext(ILoggingService loggingService, int nodeId, bool inProcNode) - : base(loggingService, new BuildEventContext(nodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId), inProcNode) + : base(loggingService, BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, nodeId), inProcNode) { ErrorUtilities.VerifyThrow(nodeId != BuildEventContext.InvalidNodeId, "Should not ever be given an invalid NodeId"); diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 6f467e25d9b..664e1ece1fc 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -58,7 +58,7 @@ internal ProjectLoggingContext( BuildRequest request, string projectFullPath, string toolsVersion, - int evaluationId = BuildEventContext.InvalidEvaluationId) + int evaluationId) : this ( nodeLoggingContext, diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 2709e004fcb..c3644666347 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1145,7 +1145,8 @@ private async Task BuildProject() _nodeLoggingContext, _requestEntry.Request, _requestEntry.RequestConfiguration.ProjectFullPath, - _requestEntry.RequestConfiguration.ToolsVersion); + _requestEntry.RequestConfiguration.ToolsVersion, + _requestEntry.RequestConfiguration.Project.EvaluationId); throw; } diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 402ab04ea5d..afe5814d32a 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -600,7 +600,7 @@ public void Reset() public void WriteDetailedSummary(int submissionId) { ILoggingService loggingService = _componentHost.LoggingService; - BuildEventContext context = new BuildEventContext(submissionId, 0, 0, 0, 0, 0); + BuildEventContext context = BuildEventContext.CreateInitial(submissionId, 0); loggingService.LogComment(context, MessageImportance.Normal, "DetailedSummaryHeader"); foreach (SchedulableRequest request in _schedulingData.GetRequestsByHierarchy(null)) @@ -2037,13 +2037,7 @@ private bool CheckIfCacheMissOnReferencedProjectIsAllowedAndErrorIfNot(int nodeF string projectFullPath = _configCache[request.ConfigurationId].ProjectFullPath; string parentProjectFullPath = GetParentConfigurationId(request, _configCache, _schedulingData).ProjectFullPath; _componentHost.LoggingService.LogComment( - new BuildEventContext( - request.SubmissionId, - 1, - BuildEventContext.InvalidProjectInstanceId, - BuildEventContext.InvalidProjectContextId, - BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidTaskId), + BuildEventContext.CreateInitial(request.SubmissionId, 1), MessageImportance.Normal, "SkippedConstraintsOnRequest", parentProjectFullPath, @@ -2950,7 +2944,7 @@ private void DumpRequestSpec(StreamWriter file, SchedulableRequest request, int private void WriteSchedulingPlan(int submissionId) { SchedulingPlan plan = new SchedulingPlan(_configCache, _schedulingData); - plan.WritePlan(submissionId, _componentHost.LoggingService, new BuildEventContext(submissionId, 0, 0, 0, 0, 0)); + plan.WritePlan(submissionId, _componentHost.LoggingService, BuildEventContext.CreateInitial(submissionId, 0)); } /// @@ -2959,7 +2953,7 @@ private void WriteSchedulingPlan(int submissionId) private void ReadSchedulingPlan(int submissionId) { _schedulingPlan = new SchedulingPlan(_configCache, _schedulingData); - _schedulingPlan.ReadPlan(submissionId, _componentHost.LoggingService, new BuildEventContext(submissionId, 0, 0, 0, 0, 0)); + _schedulingPlan.ReadPlan(submissionId, _componentHost.LoggingService, BuildEventContext.CreateInitial(submissionId, 0)); } #endregion diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index 2d40523d648..3505499a49e 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -493,14 +493,7 @@ internal void LoadProjectIntoConfiguration( toolsVersionOverride, componentHost.BuildParameters, componentHost.LoggingService, - new BuildEventContext( - submissionId, - nodeId, - BuildEventContext.InvalidEvaluationId, - BuildEventContext.InvalidProjectInstanceId, - BuildEventContext.InvalidProjectContextId, - BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidTaskId), + BuildEventContext.CreateInitial(submissionId, nodeId), sdkResolverService, submissionId, projectLoadSettings); diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index 662fe6ff516..e602a8f933f 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -633,7 +633,7 @@ internal ProjectInstance(ProjectRootElement xml, IDictionary glo /// internal ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, ILoggingService loggingService, int visualStudioVersionFromSolution, ProjectCollection projectCollection, ISdkResolverService sdkResolverService, int submissionId) { - BuildEventContext buildEventContext = new BuildEventContext(submissionId, 0, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(submissionId, 0); Initialize(xml, globalProperties, toolsVersion, null, visualStudioVersionFromSolution, new BuildParameters(projectCollection), loggingService, buildEventContext, sdkResolverService, submissionId); } diff --git a/src/Framework.UnitTests/BuildEventContext_FluentAPI_Tests.cs b/src/Framework.UnitTests/BuildEventContext_FluentAPI_Tests.cs new file mode 100644 index 00000000000..98a1461711c --- /dev/null +++ b/src/Framework.UnitTests/BuildEventContext_FluentAPI_Tests.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Build.Framework; +using Shouldly; +using Xunit; + +namespace Microsoft.Build.UnitTests +{ + /// + /// Tests for the fluent API of BuildEventContext to ensure ID propagation works correctly. + /// These tests verify that the new fluent API correctly preserves all IDs when creating derived contexts, + /// solving the critical bug where evaluation IDs were lost in target and task contexts. + /// + public class BuildEventContextFluentAPITests + { + [Fact] + public void CreateInitial_SetsCorrectProperties() + { + var context = BuildEventContext.CreateInitial(submissionId: 1, nodeId: 2); + + context.SubmissionId.ShouldBe(1); + context.NodeId.ShouldBe(2); + context.EvaluationId.ShouldBe(BuildEventContext.InvalidEvaluationId); + context.ProjectInstanceId.ShouldBe(BuildEventContext.InvalidProjectInstanceId); + context.ProjectContextId.ShouldBe(BuildEventContext.InvalidProjectContextId); + context.TargetId.ShouldBe(BuildEventContext.InvalidTargetId); + context.TaskId.ShouldBe(BuildEventContext.InvalidTaskId); + } + + [Fact] + public void WithTargetId_PreservesAllOtherIds() + { + // Arrange: Create a project-level context with all IDs set + var projectContext = BuildEventContext.CreateInitial(submissionId: 1, nodeId: 2) + .WithEvaluationId(3) + .WithProjectInstanceId(4) + .WithProjectContextId(5); + + // Act: Create a target context + var targetContext = projectContext.WithTargetId(6); + + // Assert: All previous IDs should be preserved + targetContext.SubmissionId.ShouldBe(1); + targetContext.NodeId.ShouldBe(2); + targetContext.EvaluationId.ShouldBe(3); // This should NOT be InvalidEvaluationId! + targetContext.ProjectInstanceId.ShouldBe(4); + targetContext.ProjectContextId.ShouldBe(5); + targetContext.TargetId.ShouldBe(6); + targetContext.TaskId.ShouldBe(BuildEventContext.InvalidTaskId); + } + + [Fact] + public void WithTaskId_PreservesAllOtherIds() + { + // Arrange: Create a target-level context with all IDs set + var targetContext = BuildEventContext.CreateInitial(submissionId: 1, nodeId: 2) + .WithEvaluationId(3) + .WithProjectInstanceId(4) + .WithProjectContextId(5) + .WithTargetId(6); + + // Act: Create a task context + var taskContext = targetContext.WithTaskId(7); + + // Assert: All previous IDs should be preserved, including evaluation ID + taskContext.SubmissionId.ShouldBe(1); + taskContext.NodeId.ShouldBe(2); + taskContext.EvaluationId.ShouldBe(3); // This should NOT be InvalidEvaluationId! + taskContext.ProjectInstanceId.ShouldBe(4); + taskContext.ProjectContextId.ShouldBe(5); + taskContext.TargetId.ShouldBe(6); + taskContext.TaskId.ShouldBe(7); + } + + [Fact] + public void FluentChaining_WorksCorrectly() + { + var context = BuildEventContext.CreateInitial(1, 2) + .WithEvaluationId(3) + .WithProjectInstanceId(4) + .WithProjectContextId(5) + .WithTargetId(6) + .WithTaskId(7); + + context.SubmissionId.ShouldBe(1); + context.NodeId.ShouldBe(2); + context.EvaluationId.ShouldBe(3); + context.ProjectInstanceId.ShouldBe(4); + context.ProjectContextId.ShouldBe(5); + context.TargetId.ShouldBe(6); + context.TaskId.ShouldBe(7); + } + + [Fact] + public void WithMethods_AreImmutable() + { + var original = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3); + var modified = original.WithTargetId(4); + + // Original should be unchanged + original.TargetId.ShouldBe(BuildEventContext.InvalidTargetId); + original.EvaluationId.ShouldBe(3); + + // Modified should have new target ID but preserve evaluation ID + modified.TargetId.ShouldBe(4); + modified.EvaluationId.ShouldBe(3); + } + + [Fact] + public void ConstructorsAreInternal_ExternalCodeMustUseFluentAPI() + { + // This test documents that all constructors are now internal + // External code cannot directly construct BuildEventContext objects + // They must use CreateInitial() and fluent methods + + var context = BuildEventContext.CreateInitial(1, 2); + context.ShouldNotBeNull(); + context.SubmissionId.ShouldBe(1); + context.NodeId.ShouldBe(2); + + // The Invalid property should also work + BuildEventContext.Invalid.ShouldNotBeNull(); + BuildEventContext.Invalid.NodeId.ShouldBe(BuildEventContext.InvalidNodeId); + } + } +} \ No newline at end of file diff --git a/src/Framework.UnitTests/EventArgs_Tests.cs b/src/Framework.UnitTests/EventArgs_Tests.cs index 451ec654a16..ad693737096 100644 --- a/src/Framework.UnitTests/EventArgs_Tests.cs +++ b/src/Framework.UnitTests/EventArgs_Tests.cs @@ -29,7 +29,10 @@ public class EventArgs_Tests public EventArgs_Tests() { s_baseGenericEvent = new GenericBuildEventArgs("Message", "HelpKeyword", "senderName"); - s_baseGenericEvent.BuildEventContext = new BuildEventContext(9, 8, 7, 6); + s_baseGenericEvent.BuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 9) + .WithProjectContextId(7) + .WithTargetId(8) + .WithTaskId(6); } /// diff --git a/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs b/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs index a8ba7a3286a..882018a645b 100644 --- a/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs +++ b/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs @@ -21,7 +21,7 @@ public class ProjectStartedEventArgs_Tests /// public ProjectStartedEventArgs_Tests() { - BuildEventContext parentBuildEventContext = new BuildEventContext(2, 3, 4, 5); + BuildEventContext parentBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 2)\n .WithProjectContextId(4)\n .WithTargetId(3)\n .WithTaskId(5); } /// diff --git a/src/Framework/BuildEventArgs.cs b/src/Framework/BuildEventArgs.cs index a26e7b26948..b2bc11d18fa 100644 --- a/src/Framework/BuildEventArgs.cs +++ b/src/Framework/BuildEventArgs.cs @@ -245,7 +245,7 @@ internal virtual void CreateFromStream(BinaryReader reader, int version) } else { - buildEventContext = new BuildEventContext(nodeId, targetId, projectContextId, taskId); + buildEventContext = new BuildEventContext(InvalidSubmissionId, nodeId, InvalidEvaluationId, InvalidProjectInstanceId, projectContextId, targetId, taskId); } } } diff --git a/src/Framework/BuildEventContext.cs b/src/Framework/BuildEventContext.cs index 83a7a1f9330..9221ea28768 100644 --- a/src/Framework/BuildEventContext.cs +++ b/src/Framework/BuildEventContext.cs @@ -7,7 +7,11 @@ namespace Microsoft.Build.Framework { /// /// Will provide location information for an event, this is especially - /// needed in a multi processor environment + /// needed in a multi processor environment. + /// + /// BuildEventContext objects should be created using the static CreateInitial method + /// for the root context, then using the fluent WithXxx methods to create derived contexts + /// that preserve all ID properties while updating specific ones. /// [Serializable] public class BuildEventContext @@ -54,49 +58,11 @@ public class BuildEventContext #region Constructor /// - /// This is the original constructor. No one should ever use this except internally for backward compatibility. + /// Constructs a BuildEventContext with all parameters specified. + /// This constructor should only be used internally for serialization/deserialization + /// and by the fluent WithXxx methods. External code should use CreateInitial() and fluent methods. /// - public BuildEventContext( - int nodeId, - int targetId, - int projectContextId, - int taskId) - : this(InvalidSubmissionId, nodeId, InvalidEvaluationId, InvalidProjectInstanceId, projectContextId, targetId, taskId) - { - // UNDONE: This is obsolete. - } - - /// - /// Constructs a BuildEventContext with a specified project instance id. - /// - public BuildEventContext( - int nodeId, - int projectInstanceId, - int projectContextId, - int targetId, - int taskId) - : this(InvalidSubmissionId, nodeId, InvalidEvaluationId, projectInstanceId, projectContextId, targetId, taskId) - { - } - - /// - /// Constructs a BuildEventContext with a specific submission id - /// - public BuildEventContext( - int submissionId, - int nodeId, - int projectInstanceId, - int projectContextId, - int targetId, - int taskId) - : this(submissionId, nodeId, InvalidEvaluationId, projectInstanceId, projectContextId, targetId, taskId) - { - } - - /// - /// Constructs a BuildEventContext - /// - public BuildEventContext( + internal BuildEventContext( int submissionId, int nodeId, int evaluationId, @@ -114,7 +80,25 @@ public BuildEventContext( _projectInstanceId = projectInstanceId; } + /// + /// Creates an initial BuildEventContext for the beginning of a build. + /// + /// The submission ID + /// The node ID + /// A new BuildEventContext with the specified submission and node ID + public static BuildEventContext CreateInitial(int submissionId, int nodeId) + { + return new BuildEventContext( + submissionId, + nodeId, + InvalidEvaluationId, + InvalidProjectInstanceId, + InvalidProjectContextId, + InvalidTargetId, + InvalidTaskId); + } #endregion + internal BuildEventContext WithInstanceIdAndContextId(int projectInstanceId, int projectContextId) { return new BuildEventContext(_submissionId, _nodeId, _evaluationId, projectInstanceId, projectContextId, @@ -126,12 +110,85 @@ internal BuildEventContext WithInstanceIdAndContextId(BuildEventContext other) return WithInstanceIdAndContextId(other.ProjectInstanceId, other.ProjectContextId); } + /// + /// Creates a new BuildEventContext with the specified submission ID, preserving all other IDs. + /// + /// The new submission ID + /// A new BuildEventContext with the updated submission ID + public BuildEventContext WithSubmissionId(int submissionId) + { + return new BuildEventContext(submissionId, _nodeId, _evaluationId, _projectInstanceId, _projectContextId, _targetId, _taskId); + } + + /// + /// Creates a new BuildEventContext with the specified node ID, preserving all other IDs. + /// + /// The new node ID + /// A new BuildEventContext with the updated node ID + public BuildEventContext WithNodeId(int nodeId) + { + return new BuildEventContext(_submissionId, nodeId, _evaluationId, _projectInstanceId, _projectContextId, _targetId, _taskId); + } + + /// + /// Creates a new BuildEventContext with the specified evaluation ID, preserving all other IDs. + /// + /// The new evaluation ID + /// A new BuildEventContext with the updated evaluation ID + public BuildEventContext WithEvaluationId(int evaluationId) + { + return new BuildEventContext(_submissionId, _nodeId, evaluationId, _projectInstanceId, _projectContextId, _targetId, _taskId); + } + + /// + /// Creates a new BuildEventContext with the specified project instance ID, preserving all other IDs. + /// + /// The new project instance ID + /// A new BuildEventContext with the updated project instance ID + public BuildEventContext WithProjectInstanceId(int projectInstanceId) + { + return new BuildEventContext(_submissionId, _nodeId, _evaluationId, projectInstanceId, _projectContextId, _targetId, _taskId); + } + + /// + /// Creates a new BuildEventContext with the specified project context ID, preserving all other IDs. + /// + /// The new project context ID + /// A new BuildEventContext with the updated project context ID + public BuildEventContext WithProjectContextId(int projectContextId) + { + return new BuildEventContext(_submissionId, _nodeId, _evaluationId, _projectInstanceId, projectContextId, _targetId, _taskId); + } + + /// + /// Creates a new BuildEventContext with the specified target ID, preserving all other IDs. + /// + /// The new target ID + /// A new BuildEventContext with the updated target ID + public BuildEventContext WithTargetId(int targetId) + { + return new BuildEventContext(_submissionId, _nodeId, _evaluationId, _projectInstanceId, _projectContextId, targetId, _taskId); + } + + /// + /// Creates a new BuildEventContext with the specified task ID, preserving all other IDs. + /// + /// The new task ID + /// A new BuildEventContext with the updated task ID + public BuildEventContext WithTaskId(int taskId) + { + return new BuildEventContext(_submissionId, _nodeId, _evaluationId, _projectInstanceId, _projectContextId, _targetId, taskId); + } + #region Properties /// /// Returns a default invalid BuildEventContext /// - public static BuildEventContext Invalid { get; } = new BuildEventContext(InvalidNodeId, InvalidTargetId, InvalidProjectContextId, InvalidTaskId); + public static BuildEventContext Invalid { get; } = CreateInitial(InvalidSubmissionId, InvalidNodeId) + .WithProjectContextId(InvalidProjectContextId) + .WithTargetId(InvalidTargetId) + .WithTaskId(InvalidTaskId); /// /// Retrieves the Evaluation id. diff --git a/src/Framework/ProjectStartedEventArgs.cs b/src/Framework/ProjectStartedEventArgs.cs index 6033d11cb53..963afc6bbf0 100644 --- a/src/Framework/ProjectStartedEventArgs.cs +++ b/src/Framework/ProjectStartedEventArgs.cs @@ -429,11 +429,11 @@ internal override void CreateFromStream(BinaryReader reader, int version) { int submissionId = reader.ReadInt32(); int projectInstanceId = reader.ReadInt32(); - parentProjectBuildEventContext = new BuildEventContext(submissionId, nodeId, projectInstanceId, projectContextId, targetId, taskId); + parentProjectBuildEventContext = new BuildEventContext(submissionId, nodeId, BuildEventContext.InvalidEvaluationId, projectInstanceId, projectContextId, targetId, taskId); } else { - parentProjectBuildEventContext = new BuildEventContext(nodeId, targetId, projectContextId, taskId); + parentProjectBuildEventContext = new BuildEventContext(BuildEventContext.InvalidSubmissionId, nodeId, BuildEventContext.InvalidEvaluationId, BuildEventContext.InvalidProjectInstanceId, projectContextId, targetId, taskId); } } From 29a900337049abf5754696c3b1f625100367d2e0 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Mon, 15 Dec 2025 16:28:36 -0600 Subject: [PATCH 02/34] Convert all other callsites to use the builder API --- .../BackEnd/AssemblyTaskFactory_Tests.cs | 2 +- .../BackEnd/BatchingEngine_Tests.cs | 12 +-- .../BackEnd/BuildResult_Tests.cs | 2 +- .../BackEnd/EventSourceSink_Tests.cs | 2 +- .../BackEnd/LoggingService_Tests.cs | 14 ++- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 12 +-- .../BackEnd/MockLoggingService.cs | 10 +- .../BackEnd/NodePackets_Tests.cs | 14 +-- .../BackEnd/TaskExecutionHost_Tests.cs | 6 +- .../BackEnd/TaskRegistry_Tests.cs | 6 +- .../BuildEventArgsSerialization_Tests.cs | 20 ++-- .../ConfigureableForwardingLogger_Tests.cs | 2 +- src/Build.UnitTests/ConsoleLogger_Tests.cs | 102 +++++++++--------- .../Construction/SolutionFilter_Tests.cs | 2 +- .../SolutionProjectGenerator_Tests.cs | 2 +- .../Definition/ToolsVersion_Tests.cs | 14 +-- .../Evaluation/Evaluator_Tests.cs | 4 +- .../Evaluation/Expander_Tests.cs | 8 +- .../Evaluation/ItemSpec_Tests.cs | 2 +- .../UsedUninitializedProperties_Tests.cs | 2 +- src/Build.UnitTests/FileLogger_Tests.cs | 16 +-- .../ProjectInstance_Internal_Tests.cs | 2 +- src/Build.UnitTests/TerminalLogger_Tests.cs | 16 ++- .../BackEnd/BuildManager/BuildManager.cs | 21 +--- .../NodeProviderOutOfProcBase.cs | 2 +- .../BuildCheckBuildEventHandler.cs | 6 +- src/Build/Definition/Project.cs | 2 +- src/Build/Definition/ProjectCollection.cs | 4 +- src/Build/Instance/ProjectInstance.cs | 6 +- .../BinaryLogger/BuildEventArgsReader.cs | 14 ++- .../BuildCheckManagerProviderTests.cs | 2 +- .../CustomEventArgSerialization_Tests.cs | 75 ++++++------- src/Framework.UnitTests/EventArgs_Tests.cs | 26 ++--- .../ExtendedBuildEventArgs_Tests.cs | 10 +- .../ProjectStartedEventArgs_Tests.cs | 5 +- src/Framework/BinaryTranslator.cs | 22 ++-- src/Framework/BuildEventArgs.cs | 12 ++- src/Framework/BuildEventContext.cs | 5 +- src/Framework/ProjectStartedEventArgs.cs | 11 +- src/Shared/BinaryReaderExtensions.cs | 7 +- 40 files changed, 255 insertions(+), 247 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs b/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs index 82e190d1e1d..cb43204e142 100644 --- a/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs +++ b/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs @@ -824,7 +824,7 @@ private void SetupTaskFactory(TaskHostParameters factoryParameters, bool explici factoryParameters = factoryParameters.WithTaskHostFactoryExplicitlyRequested(true); } - _loadedType = _taskFactory.InitializeFactory(_loadInfo, "TaskToTestFactories", new Dictionary(), string.Empty, factoryParameters, explicitlyLaunchTaskHost, new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4)), ElementLocation.Create("NONE"), String.Empty); + _loadedType = _taskFactory.InitializeFactory(_loadInfo, "TaskToTestFactories", new Dictionary(), string.Empty, factoryParameters, explicitlyLaunchTaskHost, new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)), ElementLocation.Create("NONE"), String.Empty); Assert.True(_loadedType.Assembly.Equals(_loadInfo)); // "Expected the AssemblyLoadInfo to be equal" } diff --git a/src/Build.UnitTests/BackEnd/BatchingEngine_Tests.cs b/src/Build.UnitTests/BackEnd/BatchingEngine_Tests.cs index 837ca550c2d..1530dd11d5a 100644 --- a/src/Build.UnitTests/BackEnd/BatchingEngine_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BatchingEngine_Tests.cs @@ -57,7 +57,7 @@ public void GetBuckets() parameters, CreateLookup(itemsByType, properties), MockElementLocation.Instance, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); Assert.Equal(5, buckets.Count); @@ -74,7 +74,7 @@ public void GetBuckets() Directory.GetCurrentDirectory(), MockElementLocation.Instance, FileSystems.Default, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4)))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)))); Assert.Equal("a.doc;b.doc;c.doc;d.doc;e.doc", bucket.Expander.ExpandIntoStringAndUnescape("@(doc)", ExpanderOptions.ExpandItems, MockElementLocation.Instance)); Assert.Equal("unittests.foo", bucket.Expander.ExpandIntoStringAndUnescape("$(bogus)$(UNITTESTS)", ExpanderOptions.ExpandPropertiesAndMetadata, MockElementLocation.Instance)); } @@ -146,7 +146,7 @@ public void ValidUnqualifiedMetadataReference() parameters, CreateLookup(itemsByType, properties), null, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); Assert.Equal(2, buckets.Count); } @@ -184,7 +184,7 @@ public void InvalidUnqualifiedMetadataReference() parameters, CreateLookup(itemsByType, properties), MockElementLocation.Instance, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); }); } /// @@ -209,7 +209,7 @@ public void NoItemsConsumed() parameters, CreateLookup(itemsByType, properties), MockElementLocation.Instance, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); }); } /// @@ -239,7 +239,7 @@ public void Regress_Mutation_DuplicateBatchingBucketsAreFoldedTogether() parameters, CreateLookup(itemsByType, properties), null, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); // If duplicate buckets have been folded correctly, then there will be exactly one bucket here // containing both a.foo and b.foo. diff --git a/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs b/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs index 1b94ac3357f..9469731503b 100644 --- a/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs @@ -309,7 +309,7 @@ public void TestEnumerator() [Fact] public void TestTranslation() { - BuildRequest request = new BuildRequest(1, 1, 2, new string[] { "alpha", "omega" }, null, new BuildEventContext(1, 1, 2, 3, 4, 5), null); + BuildRequest request = new BuildRequest(1, 1, 2, new string[] { "alpha", "omega" }, null, BuildEventContext.CreateInitial(1, 1).WithEvaluationId(2).WithProjectInstanceId(3).WithProjectContextId(4).WithTaskId(5), null); BuildResult result = new BuildResult(request, new BuildAbortedException()); TaskItem fooTaskItem = new TaskItem("foo", "asdf.proj"); diff --git a/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs b/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs index 0f6e34c6449..8a3b305b9fd 100644 --- a/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs +++ b/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs @@ -756,7 +756,7 @@ internal sealed class RaiseEventHelper /// private static BuildWarningEventArgs s_buildWarning = new BuildWarningEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender") { - BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6) + BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6) }; /// diff --git a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs index 4d42f596bcb..84213c7695d 100644 --- a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs @@ -792,7 +792,7 @@ public void VerifyWarningsPromotedToErrorsAreCounted() ls.WarningsAsErrors = new HashSet(); ls.WarningsAsErrors.Add("FOR123"); BuildWarningEventArgs warningArgs = new("abc", "FOR123", "", 0, 0, 0, 0, "warning message", "keyword", "sender"); - warningArgs.BuildEventContext = new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidProjectContextId, 5, 6); + warningArgs.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(BuildEventContext.InvalidProjectContextId).WithProjectContextId(5).WithTaskId(6); ls.LogBuildEvent(warningArgs); ls.HasBuildSubmissionLoggedErrors(1).ShouldBeTrue(); } @@ -952,13 +952,11 @@ private MockLogger GetLoggedEventsWithWarningsAsErrorsOrMessages( { IBuildComponentHost host = new MockHost(); - BuildEventContext buildEventContext = new BuildEventContext( - submissionId: 0, - nodeId: 1, - projectInstanceId: 2, - projectContextId: -1, - targetId: -1, - taskId: -1); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0, 1) + .WithProjectInstanceId(2) + .WithProjectContextId(-1) + .WithTargetId(-1) + .WithTaskId(-1); BuildRequestData buildRequestData = new BuildRequestData("projectFile", new Dictionary(), "Current", new[] { "Build" }, null); diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 3fe5fcd8e6e..60281e6b595 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -33,12 +33,12 @@ public class LoggingServicesLogMethod_Tests /// /// A generic valid build event context which can be used in the tests. /// - private static BuildEventContext s_buildEventContext = new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, 4); + private static BuildEventContext s_buildEventContext = BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(4); /// /// buildevent context for target events, note the invalid taskId, target started and finished events have this. /// - private static BuildEventContext s_targetBuildEventContext = new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, -1); + private static BuildEventContext s_targetBuildEventContext = BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(-1); #endregion #region Event based logging method tests @@ -871,7 +871,7 @@ public void ProjectStartedProvidedProjectContextId() projectCacheBuildEventContext.NodeId.ShouldBe(Scheduler.InProcNodeId); projectCacheBuildEventContext.ProjectContextId.ShouldNotBe(BuildEventContext.InvalidProjectContextId); - BuildEventContext nodeBuildEventContext = new BuildEventContext(Scheduler.InProcNodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + BuildEventContext nodeBuildEventContext = BuildEventContext.CreateInitial(0, Scheduler.InProcNodeId); BuildEventContext projectStartedBuildEventContext = service.LogProjectStarted( nodeBuildEventContext, submissionId: SubmissionId, @@ -906,7 +906,7 @@ public void ProjectStartedProvidedUnknownProjectContextIdInProcNode() BuildRequestConfiguration config = new BuildRequestConfiguration(ConfigurationId, data, "4.0"); cache.AddConfiguration(config); - BuildEventContext nodeBuildEventContext = new BuildEventContext(Scheduler.InProcNodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + BuildEventContext nodeBuildEventContext = BuildEventContext.CreateInitial(0, Scheduler.InProcNodeId); Assert.Throws(() => { service.LogProjectStarted( @@ -947,7 +947,7 @@ public void ProjectStartedProvidedUnknownProjectContextIdOutOfProcNode() BuildRequestConfiguration config = new BuildRequestConfiguration(ConfigurationId, data, "4.0"); cache.AddConfiguration(config); - BuildEventContext nodeBuildEventContext = new BuildEventContext(NodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + BuildEventContext nodeBuildEventContext = BuildEventContext.CreateInitial(0, NodeId); BuildEventContext projectStartedBuildEventContext = service.LogProjectStarted( nodeBuildEventContext, submissionId: SubmissionId, @@ -1422,7 +1422,7 @@ private void TestProjectFinishedEvent(string projectFile, bool success) // Now do it the right way -- with a matching ProjectStarted. BuildEventContext projectContext = service.LogProjectStarted( - new BuildEventContext(1, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId), + BuildEventContext.CreateInitial(0, 1), 1, 2, s_buildEventContext, diff --git a/src/Build.UnitTests/BackEnd/MockLoggingService.cs b/src/Build.UnitTests/BackEnd/MockLoggingService.cs index 10c40c8159a..21b16ae2824 100644 --- a/src/Build.UnitTests/BackEnd/MockLoggingService.cs +++ b/src/Build.UnitTests/BackEnd/MockLoggingService.cs @@ -521,11 +521,11 @@ public void LogBuildCanceled() /// public BuildEventContext CreateEvaluationBuildEventContext(int nodeId, int submissionId) - => new BuildEventContext(0, 0, 0, 0, 0, 0, 0); + => BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); /// public BuildEventContext CreateProjectCacheBuildEventContext(int submissionId, int evaluationId, int projectInstanceId, string projectFile) - => new BuildEventContext(0, 0, 0, 0, 0, 0, 0); + => BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); /// public void LogProjectEvaluationStarted(BuildEventContext eventContext, string projectFile) @@ -560,7 +560,7 @@ public BuildEventContext LogProjectStarted( int evaluationId = BuildEventContext.InvalidEvaluationId, int projectContextId = BuildEventContext.InvalidProjectContextId) { - return new BuildEventContext(0, 0, 0, 0); + return BuildEventContext.CreateInitial(0, 0); } public void LogProjectStarted(ProjectStartedEventArgs args) @@ -609,7 +609,7 @@ public void LogProjectFinished(BuildEventContext projectBuildEventContext, strin /// The build event context for the target public BuildEventContext LogTargetStarted(BuildEventContext projectBuildEventContext, string targetName, string projectFile, string projectFileOfTargetElement, string parentTargetName, TargetBuiltReason buildReason) { - return new BuildEventContext(0, 0, 0, 0); + return BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0); } /// @@ -647,7 +647,7 @@ public void LogTaskStarted(BuildEventContext targetBuildEventContext, string tas /// The task logging context public BuildEventContext LogTaskStarted2(BuildEventContext targetBuildEventContext, string taskName, string projectFile, string projectFileOfTaskNode, int line, int column, string taskAssemblyLocation) { - return new BuildEventContext(0, 0, 0, 0); + return BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0); } /// diff --git a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs index 8ca50416de7..5577f65a441 100644 --- a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs +++ b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs @@ -123,7 +123,7 @@ public void VerifyEventType() private static BuildEventContext CreateBuildEventContext() { - return new BuildEventContext(1, 2, 3, 4, 5, 6, 7); + return BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTargetId(6).WithTaskId(7); } private static ProjectEvaluationStartedEventArgs CreateProjectEvaluationStarted() @@ -262,7 +262,7 @@ public void TestTranslation() { new ResponseFileUsedEventArgs("path"), new UninitializedPropertyReadEventArgs("prop", "message", "help", "sender", MessageImportance.Normal), - new EnvironmentVariableReadEventArgs("env", "message", "file", 0, 0) { BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6) }, + new EnvironmentVariableReadEventArgs("env", "message", "file", 0, 0) { BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTargetId(6) }, new PropertyReassignmentEventArgs("prop", "prevValue", "newValue", "loc", "message", "help", "sender", MessageImportance.Normal), new PropertyInitialValueSetEventArgs("prop", "val", "propsource", "message", "help", "sender", MessageImportance.Normal), new MetaprojectGeneratedEventArgs("metaName", "path", "message"), @@ -294,31 +294,31 @@ public void TestTranslation() { ExtendedData = /*lang=json*/ "{'long-json':'mostly-strings'}", ExtendedMetadata = new Dictionary { { "m1", "v1" }, { "m2", "v2" } }, - BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7) + BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) }, new ExtendedBuildWarningEventArgs("extWarn", "SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender", DateTime.UtcNow, "arg1") { ExtendedData = /*lang=json*/ "{'long-json':'mostly-strings'}", ExtendedMetadata = new Dictionary { { "m1", "v1" }, { "m2", "v2" } }, - BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7) + BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) }, new ExtendedBuildMessageEventArgs("extWarn", "SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender", MessageImportance.Normal, DateTime.UtcNow, "arg1") { ExtendedData = /*lang=json*/ "{'long-json':'mostly-strings'}", ExtendedMetadata = new Dictionary { { "m1", "v1" }, { "m2", "v2" } }, - BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7) + BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) }, new ExtendedCustomBuildEventArgs("extCustom", "message", "help", "sender", DateTime.UtcNow, "arg1") { ExtendedData = /*lang=json*/ "{'long-json':'mostly-strings'}", ExtendedMetadata = new Dictionary { { "m1", "v1" }, { "m2", "v2" } }, - BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7) + BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) }, new ExtendedCriticalBuildMessageEventArgs("extCritMsg", "Subcategory", "Code", "File", 1, 2, 3, 4, "{0}", "HelpKeyword", "Sender", DateTime.Now, "arg1") { ExtendedData = /*lang=json*/ "{'long-json':'mostly-strings'}", ExtendedMetadata = new Dictionary { { "m1", "v1" }, { "m2", "v2" } }, - BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7) + BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) }, new GeneratedFileUsedEventArgs("path", "some content"), }; diff --git a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs index 8be60f5b825..efbb6a8e6d6 100644 --- a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs @@ -981,7 +981,7 @@ public void TestTaskResolutionFailureWithUsingTask() _loggingService = new MockLoggingService(); Dispose(); _host = new TaskExecutionHost(); - TargetLoggingContext tlc = new TargetLoggingContext(_loggingService, new BuildEventContext(1, 1, BuildEventContext.InvalidProjectContextId, 1)); + TargetLoggingContext tlc = new TargetLoggingContext(_loggingService, BuildEventContext.CreateInitial(1, 1).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(1)); ProjectInstance project = CreateTestProject(); _host.InitializeForTask( @@ -1014,7 +1014,7 @@ public void TestTaskResolutionFailureWithNoUsingTask() { Dispose(); _host = new TaskExecutionHost(); - TargetLoggingContext tlc = new TargetLoggingContext(_loggingService, new BuildEventContext(1, 1, BuildEventContext.InvalidProjectContextId, 1)); + TargetLoggingContext tlc = new TargetLoggingContext(_loggingService, BuildEventContext.CreateInitial(1, 1).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(1)); ProjectInstance project = CreateTestProject(); _host.InitializeForTask( @@ -1248,7 +1248,7 @@ private void InitializeHost(bool throwOnExecute) _logger = new MockLogger(); _loggingService.RegisterLogger(_logger); _host = new TaskExecutionHost(); - TargetLoggingContext tlc = new TargetLoggingContext(_loggingService, new BuildEventContext(1, 1, BuildEventContext.InvalidProjectContextId, 1)); + TargetLoggingContext tlc = new TargetLoggingContext(_loggingService, BuildEventContext.CreateInitial(1, 1).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(1)); // Set up a temporary project and add some items to it. ProjectInstance project = CreateTestProject(); diff --git a/src/Build.UnitTests/BackEnd/TaskRegistry_Tests.cs b/src/Build.UnitTests/BackEnd/TaskRegistry_Tests.cs index 6c4b7461b8a..dccf8c936f6 100644 --- a/src/Build.UnitTests/BackEnd/TaskRegistry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskRegistry_Tests.cs @@ -69,7 +69,7 @@ public class TaskRegistry_Tests /// /// Build event context to use when logging /// - private readonly BuildEventContext _loggerContext = new BuildEventContext(2, 2, 2, 2); + private readonly BuildEventContext _loggerContext = BuildEventContext.CreateInitial(2, 2).WithEvaluationId(2).WithProjectInstanceId(2); /// /// Element location to use when logging @@ -1135,7 +1135,7 @@ public void TaskFactoryWithNullTaskTypeLogsError() TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - InvalidProjectFileException exception = Should.Throw(() => registry.GetRegisteredTask("Task1", "none", TaskHostParameters.Empty, false, new TargetLoggingContext(_loggingService, new BuildEventContext(1, 1, BuildEventContext.InvalidProjectContextId, 1)), ElementLocation.Create("none", 1, 2), false)); + InvalidProjectFileException exception = Should.Throw(() => registry.GetRegisteredTask("Task1", "none", TaskHostParameters.Empty, false, new TargetLoggingContext(_loggingService, BuildEventContext.CreateInitial(1, 1).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(1)), ElementLocation.Create("none", 1, 2), false)); exception.ErrorCode.ShouldBe("MSB4175"); @@ -2118,7 +2118,7 @@ internal static Expander GetExpand pg, secondaryItemsByName, FileSystems.Default, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); return expander; } diff --git a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs index 6d0da53ee93..8ac3639a7b6 100644 --- a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs +++ b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs @@ -90,7 +90,7 @@ public void RoundtripBuildFinishedEventArgs() null, succeeded: true, eventTimestamp: DateTime.Parse("12/12/2015 06:11:56 PM")); - args.BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6); + args.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6); Roundtrip(args, e => ToString(e.BuildEventContext), @@ -159,10 +159,10 @@ public void RoundtripProjectStartedEventArgs() targetNames: "Build", properties: new List() { new DictionaryEntry("Key", "Value") }, items: new List() { new DictionaryEntry("Key", new MyTaskItem() { ItemSpec = "TestItemSpec" }) }, - parentBuildEventContext: new BuildEventContext(7, 8, 9, 10, 11, 12), + parentBuildEventContext: BuildEventContext.CreateInitial(7, 8).WithEvaluationId(9).WithProjectInstanceId(10).WithProjectContextId(11).WithTaskId(12), globalProperties: new Dictionary() { { "GlobalKey", "GlobalValue" } }, toolsVersion: "Current"); - args.BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6); + args.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6); Roundtrip(args, e => ToString(e.BuildEventContext), @@ -263,7 +263,7 @@ public void RoundtripTaskStartedEventArgs() public void RoundtripEnvironmentVariableReadEventArgs() { EnvironmentVariableReadEventArgs args = new("VarName", "VarValue", "file", 10, 20); - args.BuildEventContext = new BuildEventContext(4, 5, 6, 7); + args.BuildEventContext = BuildEventContext.CreateInitial(4, 5).WithEvaluationId(6).WithProjectInstanceId(7); Roundtrip(args, e => e.Message, e => e.EnvironmentVariableName, @@ -343,7 +343,7 @@ public void RoundtripExtendedErrorEventArgs_SerializedAsError(bool withOptionalD { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) : null, }; Roundtrip(args, @@ -416,7 +416,7 @@ public void RoundtripExtendedWarningEventArgs_SerializedAsWarning(bool withOptio { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) : null, }; Roundtrip(args, @@ -492,7 +492,7 @@ public void RoundtripExtendedBuildMessageEventArgs_SerializedAsMessage(bool with { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) : null, }; Roundtrip(args, @@ -580,7 +580,7 @@ public void ExtendedCustomBuildEventArgs_SerializedAsMessage(bool withOptionalDa { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) : null, }; @@ -675,7 +675,7 @@ public void RoundtripExtendedCriticalBuildMessageEventArgs(bool withOptionalData { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7) : null, }; @@ -902,7 +902,7 @@ public void RoundtripTargetSkippedEventArgs() SkipReason = TargetSkipReason.PreviouslyBuiltSuccessfully, Condition = "$(condition) == true", EvaluatedCondition = "true == true", - OriginalBuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7), + OriginalBuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTaskId(6).WithTargetId(7), OriginallySucceeded = false, TargetFile = "foo.csproj" }; diff --git a/src/Build.UnitTests/ConfigureableForwardingLogger_Tests.cs b/src/Build.UnitTests/ConfigureableForwardingLogger_Tests.cs index 7ccdbeeb57b..3f945c1089b 100644 --- a/src/Build.UnitTests/ConfigureableForwardingLogger_Tests.cs +++ b/src/Build.UnitTests/ConfigureableForwardingLogger_Tests.cs @@ -49,7 +49,7 @@ protected override void ForwardToCentralLogger(BuildEventArgs e) public ConfigureableForwardingLogger_Tests() { - BuildEventContext context = new BuildEventContext(1, 2, 3, 4); + BuildEventContext context = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); _error.BuildEventContext = context; _warning.BuildEventContext = context; _targetStarted.BuildEventContext = context; diff --git a/src/Build.UnitTests/ConsoleLogger_Tests.cs b/src/Build.UnitTests/ConsoleLogger_Tests.cs index 139e461cc40..9a7df4fc167 100644 --- a/src/Build.UnitTests/ConsoleLogger_Tests.cs +++ b/src/Build.UnitTests/ConsoleLogger_Tests.cs @@ -458,19 +458,19 @@ public void NullEventFields() // Not all parameters are null here, but that's fine, we assume the engine will never // fire a ProjectStarted without a project name, etc. es.Consume(new BuildStartedEventArgs(null, null)); - es.Consume(new ProjectStartedEventArgs(1, null, null, "p", null, null, null, parentBuildEventContext: new BuildEventContext(1, 1, 1, 1)) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new TargetStartedEventArgs(null, null, "t", null, null) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new TaskStartedEventArgs(null, null, null, null, "task") { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new BuildMessageEventArgs(null, null, null, MessageImportance.High) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new BuildWarningEventArgs(null, null, null, 0, 0, 0, 0, null, null, null) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new BuildErrorEventArgs(null, null, null, 0, 0, 0, 0, null, null, null) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new TaskFinishedEventArgs(null, null, null, null, "task", true) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new TargetFinishedEventArgs(null, null, "t", null, null, true) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new ProjectFinishedEventArgs(null, null, "p", true) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new BuildFinishedEventArgs(null, null, true) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new BuildFinishedEventArgs(null, null, true) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new BuildFinishedEventArgs(null, null, true) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); - es.Consume(new MyCustomBuildEventArgs2() { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); + es.Consume(new ProjectStartedEventArgs(1, null, null, "p", null, null, null, parentBuildEventContext: BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1)) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new TargetStartedEventArgs(null, null, "t", null, null) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new TaskStartedEventArgs(null, null, null, null, "task") { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new BuildMessageEventArgs(null, null, null, MessageImportance.High) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new BuildWarningEventArgs(null, null, null, 0, 0, 0, 0, null, null, null) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new BuildErrorEventArgs(null, null, null, 0, 0, 0, 0, null, null, null) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new TaskFinishedEventArgs(null, null, null, null, "task", true) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new TargetFinishedEventArgs(null, null, "t", null, null, true) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new ProjectFinishedEventArgs(null, null, "p", true) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new BuildFinishedEventArgs(null, null, true) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new BuildFinishedEventArgs(null, null, true) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new BuildFinishedEventArgs(null, null, true) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); + es.Consume(new MyCustomBuildEventArgs2() { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); // No exception raised } @@ -483,7 +483,7 @@ public void NullEventFieldsParallel() sc.Write, sc.SetColor, sc.ResetColor); L.Initialize(es, 2); - BuildEventContext buildEventContext = new BuildEventContext(1, 2, 3, 4); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); BuildStartedEventArgs bse = new BuildStartedEventArgs(null, null); bse.BuildEventContext = buildEventContext; @@ -600,7 +600,7 @@ public void SingleMessageTest(LoggerVerbosity loggerVerbosity, MessageImportance BuildMessageEventArgs be = new BuildMessageEventArgs(message, "help", "sender", messageImportance) { - BuildEventContext = new BuildEventContext(1, 2, 3, 4) + BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4) }; eventSourceSink.Consume(be); @@ -656,7 +656,7 @@ public void ColorTest(string expectedMessageType, string expectedColor) throw new InvalidOperationException($"Invalid expectedMessageType '{expectedMessageType}'"); } - buildEventArgs.BuildEventContext = new BuildEventContext(1, 2, 3, 4); + buildEventArgs.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); EventSourceSink eventSourceSink = new EventSourceSink(); SimulatedConsole console = new SimulatedConsole(); @@ -685,13 +685,13 @@ public void TestQuietWithHighMessage() sc.ResetColor); L.Initialize(es); - BuildEventContext buildEventContext = new BuildEventContext(1, 2, 3, 4); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); BuildStartedEventArgs bse = new BuildStartedEventArgs("bs", null); bse.BuildEventContext = buildEventContext; es.Consume(bse); - ProjectStartedEventArgs pse = new ProjectStartedEventArgs(1, "ps", null, "fname", "", null, null, new BuildEventContext(1, 1, 1, 1)); + ProjectStartedEventArgs pse = new ProjectStartedEventArgs(1, "ps", null, "fname", "", null, null, BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1)); pse.BuildEventContext = buildEventContext; es.Consume(pse); @@ -735,13 +735,13 @@ public void TestQuietWithError() sc.Write, sc.SetColor, sc.ResetColor); L.Initialize(es); - BuildEventContext buildEventContext = new BuildEventContext(1, 2, 3, 4); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); BuildStartedEventArgs bse = new BuildStartedEventArgs("bs", null); bse.BuildEventContext = buildEventContext; es.Consume(bse); - ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, new BuildEventContext(1, 2, 3, 4)); + ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)); pse.BuildEventContext = buildEventContext; es.Consume(pse); @@ -796,13 +796,13 @@ public void TestQuietWithWarning() sc.Write, sc.SetColor, sc.ResetColor); L.Initialize(es); - BuildEventContext buildEventContext = new BuildEventContext(1, 2, 3, 4); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); BuildStartedEventArgs bse = new BuildStartedEventArgs("bs", null); bse.BuildEventContext = buildEventContext; es.Consume(bse); - ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, new BuildEventContext(1, 2, 3, 4)); + ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)); pse.BuildEventContext = buildEventContext; es.Consume(pse); @@ -858,13 +858,13 @@ public void TestMinimalWithNormalMessage() sc.ResetColor); L.Initialize(es); - BuildEventContext buildEventContext = new BuildEventContext(1, 2, 3, 4); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); BuildStartedEventArgs bse = new BuildStartedEventArgs("bs", null); bse.BuildEventContext = buildEventContext; es.Consume(bse); - ProjectStartedEventArgs pse = new ProjectStartedEventArgs(1, "ps", null, "fname", "", null, null, new BuildEventContext(1, 1, 1, 1)); + ProjectStartedEventArgs pse = new ProjectStartedEventArgs(1, "ps", null, "fname", "", null, null, BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1)); pse.BuildEventContext = buildEventContext; es.Consume(pse); @@ -911,13 +911,13 @@ public void TestMinimalWithError() sc.Write, sc.SetColor, sc.ResetColor); L.Initialize(es); - BuildEventContext buildEventContext = new BuildEventContext(1, 2, 3, 4); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); BuildStartedEventArgs bse = new BuildStartedEventArgs("bs", null); bse.BuildEventContext = buildEventContext; es.Consume(bse); - ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, new BuildEventContext(1, 2, 3, 4)); + ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)); pse.BuildEventContext = buildEventContext; es.Consume(pse); @@ -971,13 +971,13 @@ public void TestMinimalWithWarning() sc.Write, sc.SetColor, sc.ResetColor); L.Initialize(es); - BuildEventContext buildEventContext = new BuildEventContext(1, 2, 3, 4); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); BuildStartedEventArgs bse = new BuildStartedEventArgs("bs", null); bse.BuildEventContext = buildEventContext; es.Consume(bse); - ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, new BuildEventContext(1, 2, 3, 4)); + ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)); pse.BuildEventContext = buildEventContext; es.Consume(pse); @@ -1032,13 +1032,13 @@ public void TestDirectEventHandlers() sc.Write, sc.SetColor, sc.ResetColor); L.Initialize(es); - BuildEventContext buildEventContext = new BuildEventContext(1, 2, 3, 4); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); BuildStartedEventArgs bse = new BuildStartedEventArgs("bs", null); bse.BuildEventContext = buildEventContext; L.BuildStartedHandler(null, bse); - ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, new BuildEventContext(1, 2, 3, 4)); + ProjectStartedEventArgs pse = new ProjectStartedEventArgs(-1, "ps", null, "fname", "", null, null, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)); pse.BuildEventContext = buildEventContext; L.ProjectStartedHandler(null, pse); @@ -1091,7 +1091,7 @@ public void CustomDisplayedAtDetailed() L.Initialize(es); MyCustomBuildEventArgs c = new MyCustomBuildEventArgs("msg"); - c.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + c.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); es.Consume(c); @@ -1109,7 +1109,7 @@ public void CustomDisplayedAtDiagnosticMP() MyCustomBuildEventArgs c = new MyCustomBuildEventArgs("msg"); - c.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + c.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); es.Consume(c); sc.ToString().ShouldContain("msg"); @@ -1124,7 +1124,7 @@ public void CustomNotDisplayedAtNormal() L.Initialize(es); MyCustomBuildEventArgs c = new MyCustomBuildEventArgs("msg"); - c.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + c.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); es.Consume(c); @@ -1147,7 +1147,7 @@ private void WriteAndValidateProperties(BaseConsoleLogger cl, SimulatedConsole s string prop3; BuildEventArgs buildEvent = new BuildErrorEventArgs("", "", "", 0, 0, 0, 0, "", "", ""); - buildEvent.BuildEventContext = new BuildEventContext(1, 2, 3, 4); + buildEvent.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); ((ParallelConsoleLogger)cl).WriteProperties(buildEvent, properties); prop1 = String.Format(CultureInfo.CurrentCulture, "{0} = {1}", "prop1", "val1"); prop2 = String.Format(CultureInfo.CurrentCulture, "{0} = {1}", "prop2", "val2"); @@ -1352,7 +1352,7 @@ private void WriteAndValidateItems(BaseConsoleLogger cl, SimulatedConsole sc, bo string item3metadatum = string.Empty; BuildEventArgs buildEvent = new BuildErrorEventArgs("", "", "", 0, 0, 0, 0, "", "", ""); - buildEvent.BuildEventContext = new BuildEventContext(1, 2, 3, 4); + buildEvent.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); ((ParallelConsoleLogger)cl).WriteItems(buildEvent, items); item1spec = Environment.NewLine + " spec" + Environment.NewLine; item2spec = Environment.NewLine + " spec2" + Environment.NewLine; @@ -1409,7 +1409,7 @@ public void WriteItemsEmptyList() SimulatedConsole sc = new SimulatedConsole(); BaseConsoleLogger cl = new ParallelConsoleLogger(LoggerVerbosity.Diagnostic, sc.Write, null, null); BuildEventArgs buildEvent = new BuildErrorEventArgs("", "", "", 0, 0, 0, 0, "", "", ""); - buildEvent.BuildEventContext = new BuildEventContext(1, 2, 3, 4); + buildEvent.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); ((ParallelConsoleLogger)cl).WriteItems(buildEvent, items); string log = sc.ToString(); @@ -1429,7 +1429,7 @@ public void WritePropertiesEmptyList() SimulatedConsole sc = new SimulatedConsole(); var cl = new ParallelConsoleLogger(LoggerVerbosity.Diagnostic, sc.Write, null, null); BuildEventArgs buildEvent = new BuildErrorEventArgs("", "", "", 0, 0, 0, 0, "", "", ""); - buildEvent.BuildEventContext = new BuildEventContext(1, 2, 3, 4); + buildEvent.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); cl.WriteProperties(buildEvent, properties); string log = sc.ToString(); @@ -1535,13 +1535,13 @@ public void ResetConsoleLoggerStateTestBasic() // Introduce a warning BuildWarningEventArgs bwea = new BuildWarningEventArgs("VBC", "31415", "file.vb", 42, 0, 0, 0, "Some long message", "help", "sender"); - bwea.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + bwea.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); es.Consume(bwea); // Introduce an error BuildErrorEventArgs beea = new BuildErrorEventArgs("VBC", "31415", "file.vb", 42, 0, 0, 0, "Some long message", "help", "sender"); - beea.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + beea.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); es.Consume(beea); @@ -1622,13 +1622,13 @@ public void ResetConsoleLoggerState_Initialize() // Introduce a warning BuildWarningEventArgs bwea = new BuildWarningEventArgs("VBC", "31415", "file.vb", 42, 0, 0, 0, "Some long message", "help", "sender"); - bwea.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + bwea.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); es.Consume(bwea); // Introduce an error BuildErrorEventArgs beea = new BuildErrorEventArgs("VBC", "31415", "file.vb", 42, 0, 0, 0, "Some long message", "help", "sender"); - beea.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + beea.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); es.Consume(beea); @@ -1704,8 +1704,8 @@ public void ResetConsoleLoggerState_PerformanceCounters() // BuildStarted Event es.Consume(new BuildStartedEventArgs("bs", null)); // Project Started Event - ProjectStartedEventArgs project1Started = new ProjectStartedEventArgs(1, null, null, "p", "t", null, null, new BuildEventContext(BuildEventContext.InvalidNodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId)); - project1Started.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + ProjectStartedEventArgs project1Started = new ProjectStartedEventArgs(1, null, null, "p", "t", null, null, BuildEventContext.CreateInitial(0, BuildEventContext.InvalidNodeId)); + project1Started.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); es.Consume(project1Started); TargetStartedEventArgs targetStarted1 = new TargetStartedEventArgs(null, null, "t", null, null); targetStarted1.BuildEventContext = project1Started.BuildEventContext; @@ -1733,7 +1733,7 @@ public void ResetConsoleLoggerState_PerformanceCounters() ProjectStartedEventArgs project2Started = new ProjectStartedEventArgs(2, null, null, "p2", "t2", null, null, project1Started.BuildEventContext); // Project Started Event - project2Started.BuildEventContext = new BuildEventContext(2, 2, 2, 2); + project2Started.BuildEventContext = BuildEventContext.CreateInitial(2, 2).WithEvaluationId(2).WithProjectInstanceId(2); es.Consume(project2Started); TargetStartedEventArgs targetStarted2 = new TargetStartedEventArgs(null, null, "t2", null, null); targetStarted2.BuildEventContext = project2Started.BuildEventContext; @@ -1818,7 +1818,7 @@ public void DeferredMessages() L.Initialize(es, 2); es.Consume(new BuildStartedEventArgs("bs", null)); TaskCommandLineEventArgs messsage1 = new TaskCommandLineEventArgs("Message", null, MessageImportance.High); - messsage1.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + messsage1.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); // Message Event es.Consume(messsage1); es.Consume(new BuildFinishedEventArgs("bf", null, true)); @@ -1832,7 +1832,7 @@ public void DeferredMessages() L.Initialize(es, 2); es.Consume(new BuildStartedEventArgs("bs", null)); BuildMessageEventArgs messsage2 = new BuildMessageEventArgs("Message", null, null, MessageImportance.High); - messsage2.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + messsage2.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); // Message Event es.Consume(messsage2); es.Consume(new BuildFinishedEventArgs("bf", null, true)); @@ -1846,7 +1846,7 @@ public void DeferredMessages() L.Initialize(es, 2); es.Consume(new BuildStartedEventArgs("bs", null)); messsage2 = new BuildMessageEventArgs("Message", null, null, MessageImportance.High); - messsage2.BuildEventContext = new BuildEventContext(1, 1, 1, 1); + messsage2.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); // Message Event es.Consume(messsage2); ProjectStartedEventArgs project = new ProjectStartedEventArgs(1, "Hello,", "HI", "None", "Build", null, null, messsage1.BuildEventContext); @@ -1878,8 +1878,8 @@ public void VerifyMPLoggerSwitch() L.Initialize(es); } es.Consume(new BuildStartedEventArgs("bs", null)); - BuildEventContext context = new BuildEventContext(1, 1, 1, 1); - BuildEventContext context2 = new BuildEventContext(2, 2, 2, 2); + BuildEventContext context = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); + BuildEventContext context2 = BuildEventContext.CreateInitial(2, 2).WithEvaluationId(2).WithProjectInstanceId(2); ProjectStartedEventArgs project = new ProjectStartedEventArgs(1, "Hello,", "HI", "None", "Build", null, null, context); project.BuildEventContext = context; @@ -1908,8 +1908,8 @@ public void TestPrintTargetNamePerMessage() ConsoleLogger L = new ConsoleLogger(LoggerVerbosity.Normal, sc.Write, sc.SetColor, sc.ResetColor); L.Initialize(es, 2); es.Consume(new BuildStartedEventArgs("bs", null)); - BuildEventContext context = new BuildEventContext(1, 1, 1, 1); - BuildEventContext context2 = new BuildEventContext(2, 2, 2, 2); + BuildEventContext context = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); + BuildEventContext context2 = BuildEventContext.CreateInitial(2, 2).WithEvaluationId(2).WithProjectInstanceId(2); ProjectStartedEventArgs project = new ProjectStartedEventArgs(1, "Hello,", "HI", "None", "Build", null, null, context); project.BuildEventContext = context; diff --git a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs index 593dc8bf5c0..681d55529ac 100644 --- a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs @@ -28,7 +28,7 @@ public class SolutionFilter_Tests : IDisposable { private readonly ITestOutputHelper output; - private static readonly BuildEventContext _buildEventContext = new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0); + private static readonly BuildEventContext _buildEventContext = BuildEventContext.CreateInitial(0, 0).WithProjectContextId(BuildEventContext.InvalidProjectContextId); public SolutionFilter_Tests(ITestOutputHelper output) { diff --git a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs index c57f5ebedb9..a75bec5bf33 100644 --- a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs @@ -36,7 +36,7 @@ public class SolutionProjectGenerator_Tests : IDisposable private string _originalVisualStudioVersion = null; - private static readonly BuildEventContext _buildEventContext = new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0); + private static readonly BuildEventContext _buildEventContext = BuildEventContext.CreateInitial(0, 0).WithProjectContextId(BuildEventContext.InvalidProjectContextId); private const string _longLineString = "a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-"; diff --git a/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs b/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs index 23d5fc1aa63..927e0c163c4 100644 --- a/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs +++ b/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs @@ -35,7 +35,7 @@ public void OverrideTasksAreFoundInOverridePath() using var collection = new ProjectCollection(); Toolset t = new Toolset("toolsversionname", dir, new PropertyDictionary(), collection, new DirectoryGetFiles(this.getFiles), new LoadXmlFromPath(this.loadXmlFromPath), overrideDir, new DirectoryExists(this.directoryExists)); - LoggingContext loggingContext = TestLoggingContext.CreateTestContext(new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, 4)); + LoggingContext loggingContext = TestLoggingContext.CreateTestContext(BuildEventContext.CreateInitial(1, 2).WithEvaluationId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(4)); TaskRegistry taskRegistry = (TaskRegistry)t.GetTaskRegistry(loggingContext, e.ProjectRootElementCache); TaskRegistry taskoverrideRegistry = (TaskRegistry)t.GetOverrideTaskRegistry(loggingContext, e.ProjectRootElementCache); @@ -81,7 +81,7 @@ public void OverrideTaskPathIsRelative() MockLogger mockLogger = new MockLogger(); LoggingService service = (LoggingService)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); service.RegisterLogger(mockLogger); - LoggingContext loggingContext = new TestLoggingContext(service, new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, 4)); + LoggingContext loggingContext = new TestLoggingContext(service, BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(4)); TaskRegistry taskoverrideRegistry = (TaskRegistry)t.GetOverrideTaskRegistry(loggingContext, e.ProjectRootElementCache); @@ -101,7 +101,7 @@ public void OverrideTaskPathHasInvalidChars() MockLogger mockLogger = new MockLogger(); LoggingService service = (LoggingService)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); service.RegisterLogger(mockLogger); - LoggingContext loggingContext = new TestLoggingContext(service, new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, 4)); + LoggingContext loggingContext = new TestLoggingContext(service, BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(4)); TaskRegistry taskoverrideRegistry = (TaskRegistry)t.GetOverrideTaskRegistry(loggingContext, e.ProjectRootElementCache); Assert.NotNull(taskoverrideRegistry); @@ -120,7 +120,7 @@ public void OverrideTaskPathHasTooLongOfAPath() MockLogger mockLogger = new MockLogger(); LoggingService service = (LoggingService)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); service.RegisterLogger(mockLogger); - LoggingContext loggingContext = new TestLoggingContext(service, new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, 4)); + LoggingContext loggingContext = new TestLoggingContext(service, BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(4)); TaskRegistry taskoverrideRegistry = (TaskRegistry)t.GetOverrideTaskRegistry(loggingContext, e.ProjectRootElementCache); Assert.NotNull(taskoverrideRegistry); @@ -140,7 +140,7 @@ public void OverrideTaskPathIsNotFound() MockLogger mockLogger = new MockLogger(); LoggingService service = (LoggingService)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); service.RegisterLogger(mockLogger); - LoggingContext loggingContext = new TestLoggingContext(service, new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, 4)); + LoggingContext loggingContext = new TestLoggingContext(service, BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(4)); TaskRegistry taskoverrideRegistry = (TaskRegistry)t.GetOverrideTaskRegistry(loggingContext, e.ProjectRootElementCache); Assert.NotNull(taskoverrideRegistry); @@ -164,7 +164,7 @@ public void DefaultTasksAreFoundInToolsPath() null, new DirectoryExists(this.directoryExists)); - LoggingContext loggingContext = TestLoggingContext.CreateTestContext(new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, 4)); + LoggingContext loggingContext = TestLoggingContext.CreateTestContext(BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(4)); TaskRegistry taskRegistry = (TaskRegistry)t.GetTaskRegistry(loggingContext, ProjectCollection.GlobalProjectCollection.ProjectRootElementCache); string[] expectedRegisteredTasks = { "a1", "a2", "a3", "a4", "b1", "e1", "g1", "g2", "g3" }; @@ -931,7 +931,7 @@ public void InlineTasksInDotTasksFile() null, new DirectoryExists(directoryExists)); - LoggingContext loggingContext = TestLoggingContext.CreateTestContext(new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, 4)); + LoggingContext loggingContext = TestLoggingContext.CreateTestContext(BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithTaskId(4)); TaskRegistry taskRegistry = (TaskRegistry)t.GetTaskRegistry(loggingContext, ProjectCollection.GlobalProjectCollection.ProjectRootElementCache); diff --git a/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs b/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs index 7c289d90ef0..b3ef3bbda53 100644 --- a/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs @@ -4497,7 +4497,7 @@ public void VerifyConditionEvaluatorResetStateOnFailure() Directory.GetCurrentDirectory(), MockElementLocation.Instance, FileSystems.Default, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); Assert.Fail("Expect exception due to the value of property \"TargetOSFamily\" is not a number."); } catch (InvalidProjectFileException e) @@ -4515,7 +4515,7 @@ public void VerifyConditionEvaluatorResetStateOnFailure() Directory.GetCurrentDirectory(), MockElementLocation.Instance, FileSystems.Default, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4)))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)))); } /// diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs index 309639cd0c4..a166a7fa122 100644 --- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs @@ -98,7 +98,7 @@ public void ExpandAllIntoTaskItems3() pg, itemsByType, FileSystems.Default, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); IList itemsOut = expander.ExpandIntoTaskItemsLeaveEscaped("foo;bar;@(compile);@(resource)", ExpanderOptions.ExpandPropertiesAndItems, MockElementLocation.Instance); @@ -847,7 +847,7 @@ private Expander CreateExpander() pg, ig, FileSystems.Default, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); return expander; } @@ -2366,7 +2366,7 @@ public void PropertyFunctionInCondition() Directory.GetCurrentDirectory(), MockElementLocation.Instance, FileSystems.Default, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4)))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)))); Assert.True( ConditionEvaluator.EvaluateCondition( @"'$(PathRoot.EndsWith(" + Path.DirectorySeparatorChar + "))' == 'false'", @@ -2376,7 +2376,7 @@ public void PropertyFunctionInCondition() Directory.GetCurrentDirectory(), MockElementLocation.Instance, FileSystems.Default, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4)))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4)))); } /// diff --git a/src/Build.UnitTests/Evaluation/ItemSpec_Tests.cs b/src/Build.UnitTests/Evaluation/ItemSpec_Tests.cs index 06b32c45258..68c7b7127fd 100644 --- a/src/Build.UnitTests/Evaluation/ItemSpec_Tests.cs +++ b/src/Build.UnitTests/Evaluation/ItemSpec_Tests.cs @@ -93,7 +93,7 @@ private ProjectInstanceExpander CreateExpander(Dictionary item new PropertyDictionary(), itemDictionary, (IFileSystem)FileSystems.Default, - new TestLoggingContext(null!, new BuildEventContext(1, 2, 3, 4))); + new TestLoggingContext(null!, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); } private static ItemDictionary ToItemDictionary(Dictionary itemTypes) diff --git a/src/Build.UnitTests/Evaluation/UsedUninitializedProperties_Tests.cs b/src/Build.UnitTests/Evaluation/UsedUninitializedProperties_Tests.cs index f9de1bc6144..63ece0629cd 100644 --- a/src/Build.UnitTests/Evaluation/UsedUninitializedProperties_Tests.cs +++ b/src/Build.UnitTests/Evaluation/UsedUninitializedProperties_Tests.cs @@ -15,7 +15,7 @@ public sealed class UsedUninitializedProperties_Tests [Fact] public void Basics() { - PropertiesUseTracker props = new(TestLoggingContext.CreateTestContext(new BuildEventContext(1, 2, 3, 4))); + PropertiesUseTracker props = new(TestLoggingContext.CreateTestContext(BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4))); Assert.False(props.TryGetPropertyElementLocation("Hello", out IElementLocation? elementLocation)); Assert.Null(elementLocation); diff --git a/src/Build.UnitTests/FileLogger_Tests.cs b/src/Build.UnitTests/FileLogger_Tests.cs index 28161a9a783..005d40996f7 100644 --- a/src/Build.UnitTests/FileLogger_Tests.cs +++ b/src/Build.UnitTests/FileLogger_Tests.cs @@ -58,7 +58,7 @@ public void BasicNoExistingFile() try { log = FileUtilities.GetTemporaryFileName(); - SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); + SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); VerifyFileContent(log, "message here"); } finally @@ -84,7 +84,7 @@ public void InvalidFile() try { - SetUpFileLoggerAndLogMessage("logfile=||invalid||", new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); + SetUpFileLoggerAndLogMessage("logfile=||invalid||", new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); } finally { @@ -111,7 +111,7 @@ public void SpecificVerbosity() fl.Parameters = "verbosity=diagnostic;logfile=" + log; // diagnostic specific setting fl.Verbosity = LoggerVerbosity.Quiet; // quiet global setting fl.Initialize(es); - fl.MessageHandler(null, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); + fl.MessageHandler(null, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); fl.Shutdown(); // expect message to appear because diagnostic not quiet verbosity was used @@ -213,7 +213,7 @@ public void ValidEncoding() try { log = FileUtilities.GetTemporaryFileName(); - SetUpFileLoggerAndLogMessage("encoding=utf-16;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); + SetUpFileLoggerAndLogMessage("encoding=utf-16;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); byte[] content = ReadRawBytes(log); // FF FE is the BOM for UTF16 @@ -240,7 +240,7 @@ public void ValidEncoding2() try { log = FileUtilities.GetTemporaryFileName(); - SetUpFileLoggerAndLogMessage("encoding=utf-8;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); + SetUpFileLoggerAndLogMessage("encoding=utf-8;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); byte[] content = ReadRawBytes(log); // EF BB BF is the BOM for UTF8 @@ -290,7 +290,7 @@ public void BasicExistingFileNoAppend() { log = FileUtilities.GetTemporaryFileName(); WriteContentToFile(log); - SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); + SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); VerifyFileContent(log, "message here"); } finally @@ -314,7 +314,7 @@ public void BasicExistingFileAppend() { log = FileUtilities.GetTemporaryFileName(); WriteContentToFile(log); - SetUpFileLoggerAndLogMessage("append;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); + SetUpFileLoggerAndLogMessage("append;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); VerifyFileContent(log, "existing content\nmessage here"); } finally @@ -339,7 +339,7 @@ public void BasicNoExistingDirectory() try { - SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = new BuildEventContext(1, 1, 1, 1) }); + SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High) { BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1) }); VerifyFileContent(log, "message here"); } finally diff --git a/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs b/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs index fd8ba8841d0..40b48bd103b 100644 --- a/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs +++ b/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs @@ -857,7 +857,7 @@ public void GetImportPathsAndImportPathsIncludingDuplicates(bool useDirectConstr using ProjectCollection projectCollection = new ProjectCollection(); BuildParameters buildParameters = new BuildParameters(projectCollection) { ProjectLoadSettings = projectLoadSettings }; - BuildEventContext buildEventContext = new BuildEventContext(0, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0, BuildEventContext.InvalidTargetId).WithEvaluationId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(BuildEventContext.InvalidTaskId); using ProjectRootElementFromString projectRootElementFromString = new(projectFileContent); ProjectRootElement rootElement = projectRootElementFromString.Project; diff --git a/src/Build.UnitTests/TerminalLogger_Tests.cs b/src/Build.UnitTests/TerminalLogger_Tests.cs index 68928d9b783..5aec875e034 100644 --- a/src/Build.UnitTests/TerminalLogger_Tests.cs +++ b/src/Build.UnitTests/TerminalLogger_Tests.cs @@ -249,14 +249,12 @@ public void CreateTerminalOrConsoleLogger_ParsesVerbosity(string? argsString, Lo /// private BuildEventContext MakeBuildEventContext(int evalId = 1, int projectContextId = 1, int nodeId = 1) { - return new BuildEventContext( - submissionId: -1, - nodeId: nodeId, - evaluationId: evalId, - projectInstanceId: -1, - projectContextId: projectContextId, - targetId: 1, - taskId: 1); + return BuildEventContext.CreateInitial(-1, nodeId) + .WithEvaluationId(evalId) + .WithProjectInstanceId(-1) + .WithProjectContextId(projectContextId) + .WithTargetId(1) + .WithTaskId(1); } private BuildStartedEventArgs MakeBuildStartedEventArgs(BuildEventContext? buildEventContext = null) @@ -722,7 +720,7 @@ public Task LogEvaluationErrorFromEngine() "MSB0001", "EvaluationError", "MSBUILD", 0, 0, 0, 0, "An error occurred during evaluation.", null, null) { - BuildEventContext = new BuildEventContext(1, -1, -1, -1) // context that belongs to no project + BuildEventContext = BuildEventContext.CreateInitial(1, -1).WithEvaluationId(-1).WithProjectInstanceId(-1) // context that belongs to no project }); }); diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 70768b90eed..0bfa46fb4da 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -1494,13 +1494,7 @@ internal void ExecuteSubmission( where TResultData : BuildResultBase { // For the current submission we only know the SubmissionId and that it happened on scheduler node - all other BuildEventContext dimensions are unknown now. - BuildEventContext buildEventContext = new BuildEventContext( - submission.SubmissionId, - nodeId: 1, - BuildEventContext.InvalidProjectInstanceId, - BuildEventContext.InvalidProjectContextId, - BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(submission.SubmissionId, nodeId: 1); BuildSubmissionStartedEventArgs submissionStartedEvent = new( submission.BuildRequestDataBase.GlobalPropertiesLookup, @@ -2041,14 +2035,7 @@ private void ExecuteGraphBuildScheduler(GraphBuildSubmission submission) null, _buildParameters, ((IBuildComponentHost)this).LoggingService, - new BuildEventContext( - submission.SubmissionId, - _buildParameters.NodeId, - BuildEventContext.InvalidEvaluationId, - BuildEventContext.InvalidProjectInstanceId, - BuildEventContext.InvalidProjectContextId, - BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidTaskId), + BuildEventContext.CreateInitial(submission.SubmissionId, _buildParameters.NodeId), SdkResolverService, submission.SubmissionId, projectLoadSettings); @@ -2570,7 +2557,9 @@ private void HandleResult(int node, BuildResult result) { BuildEventContext buildEventContext = _projectStartedEvents.TryGetValue(result.SubmissionId, out BuildEventArgs? buildEventArgs) ? buildEventArgs.BuildEventContext! - : new BuildEventContext(result.SubmissionId, node, configuration.Project?.EvaluationId ?? BuildEventContext.InvalidEvaluationId, configuration.ConfigurationId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + : BuildEventContext.CreateInitial(result.SubmissionId, node) + .WithEvaluationId(configuration.Project?.EvaluationId ?? BuildEventContext.InvalidEvaluationId) + .WithProjectInstanceId(configuration.ConfigurationId); try { _projectCacheService.HandleBuildResultAsync(configuration, result, buildEventContext, _executionCancellationTokenSource!.Token).Wait(); diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs index b74013ba0f0..9d145220797 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs @@ -302,7 +302,7 @@ bool TryReuseAnyFromPossibleRunningNodes(int currentProcessId, int nodeId) string msg = ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("NodeReused", nodeId, nodeToReuse.Id); _componentHost.LoggingService.LogBuildEvent(new BuildMessageEventArgs(msg, null, null, MessageImportance.Low) { - BuildEventContext = new BuildEventContext(nodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId) + BuildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, nodeId) }); CreateNodeContext(nodeId, nodeToReuse, nodeStream, result.NegotiatedPacketVersion); diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckBuildEventHandler.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckBuildEventHandler.cs index 0c9dcb341d1..880767c47a6 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckBuildEventHandler.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckBuildEventHandler.cs @@ -182,9 +182,5 @@ private string BuildCsvString(string title, Dictionary rowData => title + Environment.NewLine + String.Join(Environment.NewLine, rowData.Select(a => $"{a.Key},{a.Value}")) + Environment.NewLine; private BuildEventContext GetBuildEventContext(BuildEventArgs e) => e.BuildEventContext - ?? new BuildEventContext( - BuildEventContext.InvalidNodeId, - BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidProjectContextId, - BuildEventContext.InvalidTaskId); + ?? BuildEventContext.Invalid; } diff --git a/src/Build/Definition/Project.cs b/src/Build/Definition/Project.cs index 222d8aa13ea..9355e46e2e5 100644 --- a/src/Build/Definition/Project.cs +++ b/src/Build/Definition/Project.cs @@ -67,7 +67,7 @@ public class Project : ILinkableObject /// /// Context to log messages and events in. /// - private static readonly BuildEventContext s_buildEventContext = new BuildEventContext(0 /* node ID */, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + private static readonly BuildEventContext s_buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); private ProjectLink implementation; private IProjectLinkInternal implementationInternal; diff --git a/src/Build/Definition/ProjectCollection.cs b/src/Build/Definition/ProjectCollection.cs index e68f2e3316e..4da2b181d44 100644 --- a/src/Build/Definition/ProjectCollection.cs +++ b/src/Build/Definition/ProjectCollection.cs @@ -384,7 +384,7 @@ public ProjectCollection(IDictionary globalProperties, IEnumerab } catch (InvalidProjectFileException ex2) { - BuildEventContext buildEventContext = new BuildEventContext(0 /* node ID */, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); LoggingService.LogInvalidProjectFileError(buildEventContext, ex2); throw; } @@ -1261,7 +1261,7 @@ public Project LoadProject(string fileName, IDictionary globalPr } catch (InvalidProjectFileException ex) { - var buildEventContext = new BuildEventContext(0 /* node ID */, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + var buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); LoggingService.LogInvalidProjectFileError(buildEventContext, ex); throw; } diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index e602a8f933f..28d0af55ae3 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -313,7 +313,7 @@ private ProjectInstance(string projectFile, IDictionary globalPr Interactive = interactive }; - BuildEventContext buildEventContext = new BuildEventContext(buildParameters.NodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, buildParameters.NodeId); ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile, globalProperties, toolsVersion, buildParameters.ProjectRootElementCache, true /*Explicitly Loaded*/); Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version provided */, buildParameters, projectCollection.LoggingService, buildEventContext, @@ -543,7 +543,7 @@ static List GetImportFullPathsIncludingDuplicates(ObjectModelRemoting.Pr private ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) { - BuildEventContext buildEventContext = new BuildEventContext(0, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); BuildParameters buildParameters = new BuildParameters(projectCollection) { @@ -618,7 +618,7 @@ internal ProjectInstance(string projectFile, ProjectInstance projectToInheritFro /// A new project instance internal ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, int visualStudioVersionFromSolution, ProjectCollection projectCollection, ISdkResolverService sdkResolverService, int submissionId) { - BuildEventContext buildEventContext = new BuildEventContext(0, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); Initialize(xml, globalProperties, toolsVersion, null, visualStudioVersionFromSolution, new BuildParameters(projectCollection), projectCollection.LoggingService, buildEventContext, sdkResolverService, submissionId); } diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs index 69afeee1674..dd38e66145f 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs @@ -1475,14 +1475,12 @@ private BuildEventContext ReadBuildEventContext() evaluationId = ReadInt32(); } - var result = new BuildEventContext( - submissionId, - nodeId, - evaluationId, - projectInstanceId, - projectContextId, - targetId, - taskId); + var result = BuildEventContext.CreateInitial(submissionId, nodeId) + .WithEvaluationId(evaluationId) + .WithProjectInstanceId(projectInstanceId) + .WithProjectContextId(projectContextId) + .WithTargetId(targetId) + .WithTaskId(taskId); return result; } diff --git a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs index 1cd861e4c98..281441bb16a 100644 --- a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs +++ b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs @@ -42,7 +42,7 @@ public void ProcessCheckAcquisitionTest(bool isCheckRuleExist, string[] expected MockBuildCheckAcquisition(isCheckRuleExist); MockEnabledDataSourcesDefinition(); - _testedInstance.ProcessCheckAcquisition(new CheckAcquisitionData("DummyPath", "ProjectPath"), new CheckLoggingContext(_loggingService, new BuildEventContext(1, 2, 3, 4, 5, 6, 7))); + _testedInstance.ProcessCheckAcquisition(new CheckAcquisitionData("DummyPath", "ProjectPath"), new CheckLoggingContext(_loggingService, BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTargetId(6).WithTaskId(7))); _logger.AllBuildEvents.Where(be => be.GetType() == typeof(BuildMessageEventArgs)).Select(be => be.Message).ToArray() .ShouldBeEquivalentTo(expectedMessages); diff --git a/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs b/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs index 0da144267c5..0b3571954b8 100644 --- a/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs +++ b/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs @@ -16,6 +16,9 @@ namespace Microsoft.Build.UnitTests { public class CustomEventArgSerialization_Tests : IDisposable { + + private static BuildEventContext defaultContext = BuildEventContext.CreateInitial(5, 4).WithEvaluationId(3).WithProjectInstanceId(2); + // Generic build class to test custom serialization of abstract class BuildEventArgs internal sealed class GenericBuildEventArg : BuildEventArgs { @@ -59,7 +62,7 @@ public void TestGenericBuildEventArgs() { // Test using reasonable messages GenericBuildEventArg genericEvent = new GenericBuildEventArg("Message", "HelpKeyword", "SenderName"); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -76,7 +79,7 @@ public void TestGenericBuildEventArgs() // Test using empty strings _stream.Position = 0; genericEvent = new GenericBuildEventArg(string.Empty, string.Empty, string.Empty); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -101,7 +104,7 @@ public void TestGenericBuildEventArgs() // Deserialize and Verify _stream.Position = 0; newGenericEvent = new GenericBuildEventArg(null, null, null); - newGenericEvent.BuildEventContext = new BuildEventContext(1, 3, 4, 5); + newGenericEvent.BuildEventContext = BuildEventContext.CreateInitial(1, 3).WithEvaluationId(4).WithProjectInstanceId(5); newGenericEvent.CreateFromStream(_reader, _eventArgVersion); _stream.Position.ShouldBe(streamWriteEndPosition); // "Stream End Positions Should Match" VerifyGenericEventArg(genericEvent, newGenericEvent); @@ -125,7 +128,7 @@ public void TestBuildErrorEventArgs() { // Test using reasonable messages BuildErrorEventArgs genericEvent = new BuildErrorEventArgs("Subcategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "SenderName"); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -142,7 +145,7 @@ public void TestBuildErrorEventArgs() // Test using empty strings _stream.Position = 0; genericEvent = new BuildErrorEventArgs(string.Empty, string.Empty, string.Empty, 1, 2, 3, 4, string.Empty, string.Empty, string.Empty); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -176,7 +179,7 @@ public void TestBuildErrorEventArgs() // Test using HelpLink _stream.Position = 0; genericEvent = new BuildErrorEventArgs("Subcategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "SenderName", "HelpLink", DateTime.Now); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -210,7 +213,7 @@ public void TestBuildFinishedEventArgs() { // Test using reasonable messages BuildFinishedEventArgs genericEvent = new BuildFinishedEventArgs("Message", "HelpKeyword", true); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -225,7 +228,7 @@ public void TestBuildFinishedEventArgs() // Test using empty strings _stream.Position = 0; genericEvent = new BuildFinishedEventArgs(string.Empty, string.Empty, true); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -258,7 +261,7 @@ public void TestBuildMessageEventArgs() { // Test using reasonable messages BuildMessageEventArgs genericEvent = new BuildMessageEventArgs("Message", "HelpKeyword", "SenderName", MessageImportance.High); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -276,7 +279,7 @@ public void TestBuildMessageEventArgs() _stream.Position = 0; // Make sure empty strings are passed correctly genericEvent = new BuildMessageEventArgs(string.Empty, string.Empty, string.Empty, MessageImportance.Low); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -328,7 +331,7 @@ public void TestBuildMessageEventArgsWithFileInfo() { // Test using reasonable messages BuildMessageEventArgs messageEvent = new BuildMessageEventArgs("SubCategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "SenderName", MessageImportance.High); - messageEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + messageEvent.BuildEventContext = defaultContext; // Serialize messageEvent.WriteToStream(_writer); @@ -345,7 +348,7 @@ public void TestBuildMessageEventArgsWithFileInfo() _stream.Position = 0; // Make sure empty strings are passed correctly messageEvent = new BuildMessageEventArgs(string.Empty, string.Empty, string.Empty, 1, 2, 3, 4, string.Empty, string.Empty, string.Empty, MessageImportance.Low); - messageEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + messageEvent.BuildEventContext = defaultContext; // Serialize messageEvent.WriteToStream(_writer); @@ -381,7 +384,7 @@ public void TestCriticalBuildMessageEventArgs() { // Test using reasonable messages CriticalBuildMessageEventArgs criticalMessageEvent = new CriticalBuildMessageEventArgs("SubCategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "SenderName"); - criticalMessageEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + criticalMessageEvent.BuildEventContext = defaultContext; // Serialize criticalMessageEvent.WriteToStream(_writer); @@ -399,7 +402,7 @@ public void TestCriticalBuildMessageEventArgs() _stream.Position = 0; // Make sure empty strings are passed correctly criticalMessageEvent = new CriticalBuildMessageEventArgs(string.Empty, string.Empty, string.Empty, 1, 2, 3, 4, string.Empty, string.Empty, string.Empty); - criticalMessageEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + criticalMessageEvent.BuildEventContext = defaultContext; // Serialize criticalMessageEvent.WriteToStream(_writer); @@ -435,7 +438,7 @@ public void TestBuildWarningEventArgs() { // Test with reasonable messages BuildWarningEventArgs genericEvent = new BuildWarningEventArgs("Subcategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "SenderName"); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -452,7 +455,7 @@ public void TestBuildWarningEventArgs() // Test with empty strings _stream.Position = 0; genericEvent = new BuildWarningEventArgs(string.Empty, string.Empty, string.Empty, 1, 2, 3, 4, string.Empty, string.Empty, string.Empty); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -486,7 +489,7 @@ public void TestBuildWarningEventArgs() // Test with help link _stream.Position = 0; genericEvent = new BuildWarningEventArgs("Subcategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "SenderName", "HelpLink", DateTime.Now, null); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -520,7 +523,7 @@ public void TestProjectFinishedEventArgs() { // Test with reasonable values ProjectFinishedEventArgs genericEvent = new ProjectFinishedEventArgs("Message", "HelpKeyword", "ProjectFile", true); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -538,7 +541,7 @@ public void TestProjectFinishedEventArgs() // Test with empty strings _stream.Position = 0; genericEvent = new ProjectFinishedEventArgs(string.Empty, string.Empty, string.Empty, true); - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; // Serialize genericEvent.WriteToStream(_writer); @@ -586,8 +589,8 @@ public void TestProjectStartedPropertySerialization() propertyList.Add(new DictionaryEntry("WorkSpaceOwner", "The workspace owner")); propertyList.Add(new DictionaryEntry("IAmBlank", string.Empty)); - ProjectStartedEventArgs genericEvent = new ProjectStartedEventArgs(8, "Message", "HelpKeyword", "ProjectFile", null, propertyList, null, new BuildEventContext(7, 8, 9, 10)); - genericEvent.BuildEventContext = new BuildEventContext(7, 8, 9, 10); + ProjectStartedEventArgs genericEvent = new ProjectStartedEventArgs(8, "Message", "HelpKeyword", "ProjectFile", null, propertyList, null, BuildEventContext.CreateInitial(7, 8).WithEvaluationId(9).WithProjectInstanceId(10)); + genericEvent.BuildEventContext = BuildEventContext.CreateInitial(7, 8).WithEvaluationId(9).WithProjectInstanceId(10); // Serialize genericEvent.WriteToStream(_writer); @@ -650,8 +653,8 @@ private void AssertDictionaryEntry(List entryList, List { { "Key", "Value" } } }; - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; TelemetryEventArgs newGenericEvent = RoundTrip(genericEvent); @@ -994,7 +997,7 @@ public void TestTelemetryEventArgs_NullProperties() { // Test using reasonable values TelemetryEventArgs genericEvent = new TelemetryEventArgs { EventName = "Good", Properties = null }; - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; TelemetryEventArgs newGenericEvent = RoundTrip(genericEvent); @@ -1011,7 +1014,7 @@ public void TestTelemetryEventArgs_NullEventName() { // Test using null event name TelemetryEventArgs genericEvent = new TelemetryEventArgs { EventName = null, Properties = new Dictionary { { "Key", "Value" } } }; - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; TelemetryEventArgs newGenericEvent = RoundTrip(genericEvent); @@ -1024,7 +1027,7 @@ public void TestTelemetryEventArgs_NullPropertyValue() { // Test using null property value name TelemetryEventArgs genericEvent = new TelemetryEventArgs { EventName = "Good", Properties = new Dictionary { { "Key", null } } }; - genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + genericEvent.BuildEventContext = defaultContext; TelemetryEventArgs newGenericEvent = RoundTrip(genericEvent); diff --git a/src/Framework.UnitTests/EventArgs_Tests.cs b/src/Framework.UnitTests/EventArgs_Tests.cs index ad693737096..626089c167a 100644 --- a/src/Framework.UnitTests/EventArgs_Tests.cs +++ b/src/Framework.UnitTests/EventArgs_Tests.cs @@ -60,19 +60,19 @@ public void EventArgsCtors() [Fact] public void ExerciseBuildEventContext() { - BuildEventContext parentBuildEventContext = new BuildEventContext(0, 0, 0, 0, 0, 0, 0); - - BuildEventContext currentBuildEventContext = new BuildEventContext(0, 1, 2, 3, 4, 5, 6); - - BuildEventContext currentBuildEventContextSubmission = new BuildEventContext(1, 0, 0, 0, 0, 0, 0); - BuildEventContext currentBuildEventContextNode = new BuildEventContext(0, 1, 0, 0, 0, 0, 0); - BuildEventContext currentBuildEventContextEvaluation = new BuildEventContext(0, 0, 1, 0, 0, 0, 0); - BuildEventContext currentBuildEventContextProjectInstance = new BuildEventContext(0, 0, 0, 1, 0, 0, 0); - BuildEventContext currentBuildEventProjectContext = new BuildEventContext(0, 0, 0, 0, 1, 0, 0); - BuildEventContext currentBuildEventContextTarget = new BuildEventContext(0, 0, 0, 0, 0, 1, 0); - BuildEventContext currentBuildEventContextTask = new BuildEventContext(0, 0, 0, 0, 0, 0, 1); - BuildEventContext allDifferent = new BuildEventContext(1, 1, 1, 1, 1, 1, 1); - BuildEventContext allSame = new BuildEventContext(0, 0, 0, 0, 0, 0, 0); + BuildEventContext parentBuildEventContext = BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); + + BuildEventContext currentBuildEventContext = BuildEventContext.CreateInitial(0, 1).WithEvaluationId(2).WithProjectInstanceId(3).WithProjectContextId(4).WithTargetId(5).WithTaskId(6); + + BuildEventContext currentBuildEventContextSubmission = BuildEventContext.CreateInitial(1, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); + BuildEventContext currentBuildEventContextNode = BuildEventContext.CreateInitial(0, 1).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); + BuildEventContext currentBuildEventContextEvaluation = BuildEventContext.CreateInitial(0, 0).WithEvaluationId(1).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); + BuildEventContext currentBuildEventContextProjectInstance = BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(1).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); + BuildEventContext currentBuildEventProjectContext = BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(1).WithTargetId(0).WithTaskId(0); + BuildEventContext currentBuildEventContextTarget = BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(1).WithTaskId(0); + BuildEventContext currentBuildEventContextTask = BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(1); + BuildEventContext allDifferent = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1).WithProjectContextId(1).WithTargetId(1).WithTaskId(1); + BuildEventContext allSame = BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); ProjectStartedEventArgs startedEvent = new ProjectStartedEventArgs(-1, "Message", "HELP", "File", "Targets", null, null, parentBuildEventContext); startedEvent.BuildEventContext = currentBuildEventContext; diff --git a/src/Framework.UnitTests/ExtendedBuildEventArgs_Tests.cs b/src/Framework.UnitTests/ExtendedBuildEventArgs_Tests.cs index 47a7bbe5b54..cf36b3856fa 100644 --- a/src/Framework.UnitTests/ExtendedBuildEventArgs_Tests.cs +++ b/src/Framework.UnitTests/ExtendedBuildEventArgs_Tests.cs @@ -26,7 +26,7 @@ public void ExtendedCustomBuildEventArgs_SerializationDeserialization(bool withO { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTargetId(6).WithTaskId(7) : null, }; using MemoryStream stream = new MemoryStream(); @@ -64,7 +64,7 @@ public void ExtendedErrorEventArgs_SerializationDeserialization(bool withOptiona { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTargetId(6).WithTaskId(7) : null, }; using MemoryStream stream = new MemoryStream(); @@ -103,7 +103,7 @@ public void ExtendedWarningEventArgs_SerializationDeserialization(bool withOptio { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTargetId(6).WithTaskId(7) : null, }; using MemoryStream stream = new MemoryStream(); @@ -141,7 +141,7 @@ public void ExtendedMessageEventArgs_SerializationDeserialization(bool withOptio { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTargetId(6).WithTaskId(7) : null, }; using MemoryStream stream = new MemoryStream(); @@ -178,7 +178,7 @@ public void ExtendedCriticalMessageEventArgs_SerializationDeserialization(bool w { ExtendedData = withOptionalData ? /*lang=json*/ "{'long-json':'mostly-strings'}" : null, ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, - BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + BuildEventContext = withOptionalData ? BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4).WithProjectContextId(5).WithTargetId(6).WithTaskId(7) : null, }; using MemoryStream stream = new MemoryStream(); diff --git a/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs b/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs index 882018a645b..d5c1e863b32 100644 --- a/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs +++ b/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs @@ -21,7 +21,10 @@ public class ProjectStartedEventArgs_Tests /// public ProjectStartedEventArgs_Tests() { - BuildEventContext parentBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 2)\n .WithProjectContextId(4)\n .WithTargetId(3)\n .WithTaskId(5); + BuildEventContext parentBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 2) + .WithProjectContextId(4) + .WithTargetId(3) + .WithTaskId(5); } /// diff --git a/src/Framework/BinaryTranslator.cs b/src/Framework/BinaryTranslator.cs index 684929529b0..2bae92f6097 100644 --- a/src/Framework/BinaryTranslator.cs +++ b/src/Framework/BinaryTranslator.cs @@ -490,14 +490,20 @@ public void Translate(ref TimeSpan value) /// The context to be translated. public void Translate(ref BuildEventContext value) { - value = new BuildEventContext( - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32()); + int submissionId = _reader.ReadInt32(); + int nodeId = _reader.ReadInt32(); + int evaluationId = _reader.ReadInt32(); + int projectInstanceId = _reader.ReadInt32(); + int projectContextId = _reader.ReadInt32(); + int targetId = _reader.ReadInt32(); + int taskId = _reader.ReadInt32(); + + value = BuildEventContext.CreateInitial(submissionId, nodeId) + .WithEvaluationId(evaluationId) + .WithProjectInstanceId(projectInstanceId) + .WithProjectContextId(projectContextId) + .WithTargetId(targetId) + .WithTaskId(taskId); } #endif diff --git a/src/Framework/BuildEventArgs.cs b/src/Framework/BuildEventArgs.cs index b2bc11d18fa..ed5672ab1c1 100644 --- a/src/Framework/BuildEventArgs.cs +++ b/src/Framework/BuildEventArgs.cs @@ -241,11 +241,19 @@ internal virtual void CreateFromStream(BinaryReader reader, int version) int submissionId = reader.ReadInt32(); int projectInstanceId = reader.ReadInt32(); int evaluationId = reader.ReadInt32(); - buildEventContext = new BuildEventContext(submissionId, nodeId, evaluationId, projectInstanceId, projectContextId, targetId, taskId); + buildEventContext = BuildEventContext.CreateInitial(submissionId, nodeId) + .WithEvaluationId(evaluationId) + .WithProjectInstanceId(projectInstanceId) + .WithProjectContextId(projectContextId) + .WithTargetId(targetId) + .WithTaskId(taskId); } else { - buildEventContext = new BuildEventContext(InvalidSubmissionId, nodeId, InvalidEvaluationId, InvalidProjectInstanceId, projectContextId, targetId, taskId); + buildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, nodeId) + .WithProjectContextId(projectContextId) + .WithTargetId(targetId) + .WithTaskId(taskId); } } } diff --git a/src/Framework/BuildEventContext.cs b/src/Framework/BuildEventContext.cs index 9221ea28768..717a455a986 100644 --- a/src/Framework/BuildEventContext.cs +++ b/src/Framework/BuildEventContext.cs @@ -185,10 +185,7 @@ public BuildEventContext WithTaskId(int taskId) /// /// Returns a default invalid BuildEventContext /// - public static BuildEventContext Invalid { get; } = CreateInitial(InvalidSubmissionId, InvalidNodeId) - .WithProjectContextId(InvalidProjectContextId) - .WithTargetId(InvalidTargetId) - .WithTaskId(InvalidTaskId); + public static BuildEventContext Invalid { get; } = new(InvalidSubmissionId, InvalidNodeId, InvalidEvaluationId, InvalidProjectInstanceId, InvalidProjectContextId, InvalidTargetId, InvalidTaskId); /// /// Retrieves the Evaluation id. diff --git a/src/Framework/ProjectStartedEventArgs.cs b/src/Framework/ProjectStartedEventArgs.cs index 963afc6bbf0..a0871790a3c 100644 --- a/src/Framework/ProjectStartedEventArgs.cs +++ b/src/Framework/ProjectStartedEventArgs.cs @@ -429,11 +429,18 @@ internal override void CreateFromStream(BinaryReader reader, int version) { int submissionId = reader.ReadInt32(); int projectInstanceId = reader.ReadInt32(); - parentProjectBuildEventContext = new BuildEventContext(submissionId, nodeId, BuildEventContext.InvalidEvaluationId, projectInstanceId, projectContextId, targetId, taskId); + parentProjectBuildEventContext = BuildEventContext.CreateInitial(submissionId, nodeId) + .WithProjectInstanceId(projectInstanceId) + .WithProjectContextId(projectContextId) + .WithTargetId(targetId) + .WithTaskId(taskId); } else { - parentProjectBuildEventContext = new BuildEventContext(BuildEventContext.InvalidSubmissionId, nodeId, BuildEventContext.InvalidEvaluationId, BuildEventContext.InvalidProjectInstanceId, projectContextId, targetId, taskId); + parentProjectBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, nodeId) + .WithProjectContextId(projectContextId) + .WithTargetId(targetId) + .WithTaskId(taskId); } } diff --git a/src/Shared/BinaryReaderExtensions.cs b/src/Shared/BinaryReaderExtensions.cs index 9078401ba2f..c0b65c11bce 100644 --- a/src/Shared/BinaryReaderExtensions.cs +++ b/src/Shared/BinaryReaderExtensions.cs @@ -88,7 +88,12 @@ public static BuildEventContext ReadBuildEventContext(this BinaryReader reader) int projectInstanceId = reader.ReadInt32(); int evaluationId = reader.ReadInt32(); - var buildEventContext = new BuildEventContext(submissionId, nodeId, evaluationId, projectInstanceId, projectContextId, targetId, taskId); + var buildEventContext = BuildEventContext.CreateInitial(submissionId, nodeId) + .WithEvaluationId(evaluationId) + .WithProjectInstanceId(projectInstanceId) + .WithProjectContextId(projectContextId) + .WithTargetId(targetId) + .WithTaskId(taskId); return buildEventContext; } #endif From 7b84cc8611153637cffc16fb3e814f40fb78caf3 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Mon, 15 Dec 2025 16:31:49 -0600 Subject: [PATCH 03/34] update compat baselines - we'll likely need to revert this, but this is an accounting of what's changed and enables the rest of the build to occur --- src/Framework/CompatibilitySuppressions.xml | 175 ++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/src/Framework/CompatibilitySuppressions.xml b/src/Framework/CompatibilitySuppressions.xml index d61bb3c6b1f..a6692f1f847 100644 --- a/src/Framework/CompatibilitySuppressions.xml +++ b/src/Framework/CompatibilitySuppressions.xml @@ -1,6 +1,181 @@  + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + lib/net10.0/Microsoft.Build.Framework.dll + lib/net10.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + lib/net10.0/Microsoft.Build.Framework.dll + lib/net10.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + lib/net10.0/Microsoft.Build.Framework.dll + lib/net10.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) + lib/net10.0/Microsoft.Build.Framework.dll + lib/net10.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + lib/net472/Microsoft.Build.Framework.dll + lib/net472/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + lib/net472/Microsoft.Build.Framework.dll + lib/net472/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + lib/net472/Microsoft.Build.Framework.dll + lib/net472/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) + lib/net472/Microsoft.Build.Framework.dll + lib/net472/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + ref/net10.0/Microsoft.Build.Framework.dll + ref/net10.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + ref/net10.0/Microsoft.Build.Framework.dll + ref/net10.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + ref/net10.0/Microsoft.Build.Framework.dll + ref/net10.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) + ref/net10.0/Microsoft.Build.Framework.dll + ref/net10.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + ref/net472/Microsoft.Build.Framework.dll + ref/net472/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + ref/net472/Microsoft.Build.Framework.dll + ref/net472/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + ref/net472/Microsoft.Build.Framework.dll + ref/net472/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) + ref/net472/Microsoft.Build.Framework.dll + ref/net472/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + ref/netstandard2.0/Microsoft.Build.Framework.dll + ref/netstandard2.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + ref/netstandard2.0/Microsoft.Build.Framework.dll + ref/netstandard2.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) + ref/netstandard2.0/Microsoft.Build.Framework.dll + ref/netstandard2.0/Microsoft.Build.Framework.dll + true + + + CP0002 + M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) + ref/netstandard2.0/Microsoft.Build.Framework.dll + ref/netstandard2.0/Microsoft.Build.Framework.dll + true + + + CP0009 + T:Microsoft.Build.Framework.BuildEventContext + lib/net10.0/Microsoft.Build.Framework.dll + lib/net10.0/Microsoft.Build.Framework.dll + true + + + CP0009 + T:Microsoft.Build.Framework.BuildEventContext + lib/net472/Microsoft.Build.Framework.dll + lib/net472/Microsoft.Build.Framework.dll + true + + + CP0009 + T:Microsoft.Build.Framework.BuildEventContext + ref/net10.0/Microsoft.Build.Framework.dll + ref/net10.0/Microsoft.Build.Framework.dll + true + + + CP0009 + T:Microsoft.Build.Framework.BuildEventContext + ref/net472/Microsoft.Build.Framework.dll + ref/net472/Microsoft.Build.Framework.dll + true + + + CP0009 + T:Microsoft.Build.Framework.BuildEventContext + ref/netstandard2.0/Microsoft.Build.Framework.dll + ref/netstandard2.0/Microsoft.Build.Framework.dll + true + PKV004 .NETCoreApp,Version=v2.0 From 8ae298181ccf85e71adaa930dfb721ddaffaedd0 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 16 Dec 2025 13:00:05 -0600 Subject: [PATCH 04/34] Make the context type a ref struct builder for perf --- src/Framework/BuildEventContext.cs | 326 ++++++++++++++++++++++------- 1 file changed, 255 insertions(+), 71 deletions(-) diff --git a/src/Framework/BuildEventContext.cs b/src/Framework/BuildEventContext.cs index 717a455a986..ca72b2fe7d0 100644 --- a/src/Framework/BuildEventContext.cs +++ b/src/Framework/BuildEventContext.cs @@ -55,8 +55,6 @@ public class BuildEventContext #endregion - #region Constructor - /// /// Constructs a BuildEventContext with all parameters specified. /// This constructor should only be used internally for serialization/deserialization @@ -80,105 +78,84 @@ internal BuildEventContext( _projectInstanceId = projectInstanceId; } + #region Builders + /// /// Creates an initial BuildEventContext for the beginning of a build. + /// Uses the efficient builder pattern to minimize allocations. /// /// The submission ID /// The node ID /// A new BuildEventContext with the specified submission and node ID - public static BuildEventContext CreateInitial(int submissionId, int nodeId) - { - return new BuildEventContext( - submissionId, - nodeId, - InvalidEvaluationId, - InvalidProjectInstanceId, - InvalidProjectContextId, - InvalidTargetId, - InvalidTaskId); - } - #endregion + public static BuildEventContextBuilder CreateInitial(int submissionId, int nodeId) => Builder().AsInitial(submissionId, nodeId); - internal BuildEventContext WithInstanceIdAndContextId(int projectInstanceId, int projectContextId) - { - return new BuildEventContext(_submissionId, _nodeId, _evaluationId, projectInstanceId, projectContextId, - _targetId, _taskId); - } + internal BuildEventContextBuilder WithInstanceIdAndContextId(int projectInstanceId, int projectContextId) => Builder(this).WithInstanceIdAndContextId(projectInstanceId, projectContextId); - internal BuildEventContext WithInstanceIdAndContextId(BuildEventContext other) - { - return WithInstanceIdAndContextId(other.ProjectInstanceId, other.ProjectContextId); - } + internal BuildEventContextBuilder WithInstanceIdAndContextId(BuildEventContext other) => Builder(this).WithInstanceIdAndContextId(other); /// - /// Creates a new BuildEventContext with the specified submission ID, preserving all other IDs. + /// Creates a new builder with the specified submission ID, preserving all other IDs. + /// Returns a builder to enable efficient chaining without intermediate allocations. + /// Call Build() to create the final BuildEventContext. /// /// The new submission ID - /// A new BuildEventContext with the updated submission ID - public BuildEventContext WithSubmissionId(int submissionId) - { - return new BuildEventContext(submissionId, _nodeId, _evaluationId, _projectInstanceId, _projectContextId, _targetId, _taskId); - } + /// A builder with the updated submission ID + public BuildEventContextBuilder WithSubmissionId(int submissionId) => Builder(this).WithSubmissionId(submissionId); /// - /// Creates a new BuildEventContext with the specified node ID, preserving all other IDs. + /// Creates a new builder with the specified node ID, preserving all other IDs. + /// Returns a builder to enable efficient chaining without intermediate allocations. + /// Call Build() to create the final BuildEventContext. /// /// The new node ID - /// A new BuildEventContext with the updated node ID - public BuildEventContext WithNodeId(int nodeId) - { - return new BuildEventContext(_submissionId, nodeId, _evaluationId, _projectInstanceId, _projectContextId, _targetId, _taskId); - } + /// A builder with the updated node ID + public BuildEventContextBuilder WithNodeId(int nodeId) => Builder(this).WithNodeId(nodeId); /// - /// Creates a new BuildEventContext with the specified evaluation ID, preserving all other IDs. + /// Creates a new builder with the specified evaluation ID, preserving all other IDs. + /// Returns a builder to enable efficient chaining without intermediate allocations. + /// Call Build() to create the final BuildEventContext. /// /// The new evaluation ID - /// A new BuildEventContext with the updated evaluation ID - public BuildEventContext WithEvaluationId(int evaluationId) - { - return new BuildEventContext(_submissionId, _nodeId, evaluationId, _projectInstanceId, _projectContextId, _targetId, _taskId); - } + /// A builder with the updated evaluation ID + public BuildEventContextBuilder WithEvaluationId(int evaluationId) => Builder(this).WithEvaluationId(evaluationId); /// - /// Creates a new BuildEventContext with the specified project instance ID, preserving all other IDs. + /// Creates a new builder with the specified project instance ID, preserving all other IDs. + /// Returns a builder to enable efficient chaining without intermediate allocations. + /// Call Build() to create the final BuildEventContext. /// /// The new project instance ID - /// A new BuildEventContext with the updated project instance ID - public BuildEventContext WithProjectInstanceId(int projectInstanceId) - { - return new BuildEventContext(_submissionId, _nodeId, _evaluationId, projectInstanceId, _projectContextId, _targetId, _taskId); - } + /// A builder with the updated project instance ID + public BuildEventContextBuilder WithProjectInstanceId(int projectInstanceId) => Builder(this).WithProjectInstanceId(projectInstanceId); /// - /// Creates a new BuildEventContext with the specified project context ID, preserving all other IDs. + /// Creates a new builder with the specified project context ID, preserving all other IDs. + /// Returns a builder to enable efficient chaining without intermediate allocations. + /// Call Build() to create the final BuildEventContext. /// /// The new project context ID - /// A new BuildEventContext with the updated project context ID - public BuildEventContext WithProjectContextId(int projectContextId) - { - return new BuildEventContext(_submissionId, _nodeId, _evaluationId, _projectInstanceId, projectContextId, _targetId, _taskId); - } + /// A builder with the updated project context ID + public BuildEventContextBuilder WithProjectContextId(int projectContextId) => Builder(this).WithProjectContextId(projectContextId); /// - /// Creates a new BuildEventContext with the specified target ID, preserving all other IDs. + /// Creates a new builder with the specified target ID, preserving all other IDs. + /// Returns a builder to enable efficient chaining without intermediate allocations. + /// Call Build() to create the final BuildEventContext. /// /// The new target ID - /// A new BuildEventContext with the updated target ID - public BuildEventContext WithTargetId(int targetId) - { - return new BuildEventContext(_submissionId, _nodeId, _evaluationId, _projectInstanceId, _projectContextId, targetId, _taskId); - } + /// A builder with the updated target ID + public BuildEventContextBuilder WithTargetId(int targetId) => Builder(this).WithTargetId(targetId); /// - /// Creates a new BuildEventContext with the specified task ID, preserving all other IDs. + /// Creates a new builder with the specified task ID, preserving all other IDs. + /// Returns a builder to enable efficient chaining without intermediate allocations. + /// Call Build() to create the final BuildEventContext. /// /// The new task ID - /// A new BuildEventContext with the updated task ID - public BuildEventContext WithTaskId(int taskId) - { - return new BuildEventContext(_submissionId, _nodeId, _evaluationId, _projectInstanceId, _projectContextId, _targetId, taskId); - } + /// A builder with the updated task ID + public BuildEventContextBuilder WithTaskId(int taskId) => Builder(this).WithTaskId(taskId); + #endregion #region Properties @@ -351,20 +328,227 @@ public override bool Equals(object? obj) /// /// BuildEventContext to compare to this instance /// True if the value fields are the same, false if otherwise - private bool InternalEquals(BuildEventContext buildEventContext) - { - return _nodeId == buildEventContext.NodeId + private bool InternalEquals(BuildEventContext buildEventContext) => _nodeId == buildEventContext.NodeId && _projectContextId == buildEventContext.ProjectContextId && _targetId == buildEventContext.TargetId && _taskId == buildEventContext.TaskId && _evaluationId == buildEventContext._evaluationId && _projectInstanceId == buildEventContext._projectInstanceId; - } #endregion - public override string ToString() + public override string ToString() => $"Node={NodeId} Submission={SubmissionId} ProjectContext={ProjectContextId} ProjectInstance={ProjectInstanceId} Eval={EvaluationId} Target={TargetId} Task={TaskId}"; + + #region Builder Pattern + + /// + /// Creates a new builder for constructing BuildEventContext instances efficiently. + /// The builder uses stack allocation to avoid heap allocations during construction. + /// + /// A new BuildEventContextBuilder + public static BuildEventContextBuilder Builder() => new(); + + /// + /// Creates a new builder initialized from an existing BuildEventContext. + /// This allows for efficient copying and modification of existing contexts. + /// + /// The BuildEventContext to copy values from + /// A new BuildEventContextBuilder initialized with the source values + public static BuildEventContextBuilder Builder(BuildEventContext source) => new(source); + + #endregion + } + + /// + /// A ref struct builder for efficiently constructing BuildEventContext instances. + /// This builder eliminates heap allocations during the building process and provides + /// a fluent API for setting context properties. + /// + /// Usage: + /// var context = BuildEventContext.Builder() + /// .WithSubmissionId(1) + /// .WithNodeId(2) + /// .WithProjectInstanceId(3) + /// .Build(); + /// + public ref struct BuildEventContextBuilder + { + private int _submissionId; + private int _nodeId; + private int _evaluationId; + private int _projectInstanceId; + private int _projectContextId; + private int _targetId; + private int _taskId; + + /// + /// Initializes a new BuildEventContextBuilder with invalid values for all IDs. + /// + public BuildEventContextBuilder() + { + _submissionId = BuildEventContext.InvalidSubmissionId; + _nodeId = BuildEventContext.InvalidNodeId; + _evaluationId = BuildEventContext.InvalidEvaluationId; + _projectInstanceId = BuildEventContext.InvalidProjectInstanceId; + _projectContextId = BuildEventContext.InvalidProjectContextId; + _targetId = BuildEventContext.InvalidTargetId; + _taskId = BuildEventContext.InvalidTaskId; + } + + /// + /// Initializes a new BuildEventContextBuilder with values from an existing BuildEventContext. + /// + /// The BuildEventContext to copy values from + public BuildEventContextBuilder(BuildEventContext source) + { + _submissionId = source.SubmissionId; + _nodeId = source.NodeId; + _evaluationId = source.EvaluationId; + _projectInstanceId = source.ProjectInstanceId; + _projectContextId = source.ProjectContextId; + _targetId = source.TargetId; + _taskId = source.TaskId; + } + + /// + /// Sets the submission ID and returns this builder for chaining. + /// + /// The submission ID + /// This builder instance + public BuildEventContextBuilder WithSubmissionId(int submissionId) + { + _submissionId = submissionId; + return this; + } + + /// + /// Sets the node ID and returns this builder for chaining. + /// + /// The node ID + /// This builder instance + public BuildEventContextBuilder WithNodeId(int nodeId) + { + _nodeId = nodeId; + return this; + } + + /// + /// Sets the evaluation ID and returns this builder for chaining. + /// + /// The evaluation ID + /// This builder instance + public BuildEventContextBuilder WithEvaluationId(int evaluationId) + { + _evaluationId = evaluationId; + return this; + } + + /// + /// Sets the project instance ID and returns this builder for chaining. + /// + /// The project instance ID + /// This builder instance + public BuildEventContextBuilder WithProjectInstanceId(int projectInstanceId) + { + _projectInstanceId = projectInstanceId; + return this; + } + + /// + /// Sets the project context ID and returns this builder for chaining. + /// + /// The project context ID + /// This builder instance + public BuildEventContextBuilder WithProjectContextId(int projectContextId) + { + _projectContextId = projectContextId; + return this; + } + + /// + /// Sets the target ID and returns this builder for chaining. + /// + /// The target ID + /// This builder instance + public BuildEventContextBuilder WithTargetId(int targetId) + { + _targetId = targetId; + return this; + } + + /// + /// Sets the task ID and returns this builder for chaining. + /// + /// The task ID + /// This builder instance + public BuildEventContextBuilder WithTaskId(int taskId) + { + _taskId = taskId; + return this; + } + + /// + /// Sets both the project instance ID and project context ID in a single operation. + /// This is a common operation when creating project-level contexts. + /// + /// The project instance ID + /// The project context ID + /// This builder instance + public BuildEventContextBuilder WithInstanceIdAndContextId(int projectInstanceId, int projectContextId) + { + _projectInstanceId = projectInstanceId; + _projectContextId = projectContextId; + return this; + } + + /// + /// Copies the project instance ID and project context ID from another BuildEventContext. + /// + /// The BuildEventContext to copy IDs from + /// This builder instance + public BuildEventContextBuilder WithInstanceIdAndContextId(BuildEventContext other) => WithInstanceIdAndContextId(other.ProjectInstanceId, other.ProjectContextId); + + /// + /// Creates an initial BuildEventContext with the specified submission and node ID. + /// All other IDs are set to invalid values. + /// + /// The submission ID + /// The node ID + /// This builder instance configured as an initial context + public BuildEventContextBuilder AsInitial(int submissionId, int nodeId) + { + _submissionId = submissionId; + _nodeId = nodeId; + _evaluationId = BuildEventContext.InvalidEvaluationId; + _projectInstanceId = BuildEventContext.InvalidProjectInstanceId; + _projectContextId = BuildEventContext.InvalidProjectContextId; + _targetId = BuildEventContext.InvalidTargetId; + _taskId = BuildEventContext.InvalidTaskId; + return this; + } + + /// + /// Builds the final BuildEventContext instance. + /// This is the only operation that allocates memory on the heap. + /// + /// A new BuildEventContext with the configured values + public readonly BuildEventContext Build() => new BuildEventContext( + _submissionId, + _nodeId, + _evaluationId, + _projectInstanceId, + _projectContextId, + _targetId, + _taskId); + + /// + /// Implicit conversion from builder to BuildEventContext for convenience. + /// This allows the builder to be used directly where a BuildEventContext is expected. + /// + /// The builder to convert + /// A new BuildEventContext built from the builder + public static implicit operator BuildEventContext(BuildEventContextBuilder builder) { - return $"Node={NodeId} Submission={SubmissionId} ProjectContext={ProjectContextId} ProjectInstance={ProjectInstanceId} Eval={EvaluationId} Target={TargetId} Task={TaskId}"; + return builder.Build(); } } } From b112c5abf7e6bb590e1291a56765a4cc73169c76 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 16 Dec 2025 13:00:16 -0600 Subject: [PATCH 05/34] remove useless tests --- .../BuildEventContext_FluentAPI_Tests.cs | 127 ------------------ 1 file changed, 127 deletions(-) delete mode 100644 src/Framework.UnitTests/BuildEventContext_FluentAPI_Tests.cs diff --git a/src/Framework.UnitTests/BuildEventContext_FluentAPI_Tests.cs b/src/Framework.UnitTests/BuildEventContext_FluentAPI_Tests.cs deleted file mode 100644 index 98a1461711c..00000000000 --- a/src/Framework.UnitTests/BuildEventContext_FluentAPI_Tests.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Build.Framework; -using Shouldly; -using Xunit; - -namespace Microsoft.Build.UnitTests -{ - /// - /// Tests for the fluent API of BuildEventContext to ensure ID propagation works correctly. - /// These tests verify that the new fluent API correctly preserves all IDs when creating derived contexts, - /// solving the critical bug where evaluation IDs were lost in target and task contexts. - /// - public class BuildEventContextFluentAPITests - { - [Fact] - public void CreateInitial_SetsCorrectProperties() - { - var context = BuildEventContext.CreateInitial(submissionId: 1, nodeId: 2); - - context.SubmissionId.ShouldBe(1); - context.NodeId.ShouldBe(2); - context.EvaluationId.ShouldBe(BuildEventContext.InvalidEvaluationId); - context.ProjectInstanceId.ShouldBe(BuildEventContext.InvalidProjectInstanceId); - context.ProjectContextId.ShouldBe(BuildEventContext.InvalidProjectContextId); - context.TargetId.ShouldBe(BuildEventContext.InvalidTargetId); - context.TaskId.ShouldBe(BuildEventContext.InvalidTaskId); - } - - [Fact] - public void WithTargetId_PreservesAllOtherIds() - { - // Arrange: Create a project-level context with all IDs set - var projectContext = BuildEventContext.CreateInitial(submissionId: 1, nodeId: 2) - .WithEvaluationId(3) - .WithProjectInstanceId(4) - .WithProjectContextId(5); - - // Act: Create a target context - var targetContext = projectContext.WithTargetId(6); - - // Assert: All previous IDs should be preserved - targetContext.SubmissionId.ShouldBe(1); - targetContext.NodeId.ShouldBe(2); - targetContext.EvaluationId.ShouldBe(3); // This should NOT be InvalidEvaluationId! - targetContext.ProjectInstanceId.ShouldBe(4); - targetContext.ProjectContextId.ShouldBe(5); - targetContext.TargetId.ShouldBe(6); - targetContext.TaskId.ShouldBe(BuildEventContext.InvalidTaskId); - } - - [Fact] - public void WithTaskId_PreservesAllOtherIds() - { - // Arrange: Create a target-level context with all IDs set - var targetContext = BuildEventContext.CreateInitial(submissionId: 1, nodeId: 2) - .WithEvaluationId(3) - .WithProjectInstanceId(4) - .WithProjectContextId(5) - .WithTargetId(6); - - // Act: Create a task context - var taskContext = targetContext.WithTaskId(7); - - // Assert: All previous IDs should be preserved, including evaluation ID - taskContext.SubmissionId.ShouldBe(1); - taskContext.NodeId.ShouldBe(2); - taskContext.EvaluationId.ShouldBe(3); // This should NOT be InvalidEvaluationId! - taskContext.ProjectInstanceId.ShouldBe(4); - taskContext.ProjectContextId.ShouldBe(5); - taskContext.TargetId.ShouldBe(6); - taskContext.TaskId.ShouldBe(7); - } - - [Fact] - public void FluentChaining_WorksCorrectly() - { - var context = BuildEventContext.CreateInitial(1, 2) - .WithEvaluationId(3) - .WithProjectInstanceId(4) - .WithProjectContextId(5) - .WithTargetId(6) - .WithTaskId(7); - - context.SubmissionId.ShouldBe(1); - context.NodeId.ShouldBe(2); - context.EvaluationId.ShouldBe(3); - context.ProjectInstanceId.ShouldBe(4); - context.ProjectContextId.ShouldBe(5); - context.TargetId.ShouldBe(6); - context.TaskId.ShouldBe(7); - } - - [Fact] - public void WithMethods_AreImmutable() - { - var original = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3); - var modified = original.WithTargetId(4); - - // Original should be unchanged - original.TargetId.ShouldBe(BuildEventContext.InvalidTargetId); - original.EvaluationId.ShouldBe(3); - - // Modified should have new target ID but preserve evaluation ID - modified.TargetId.ShouldBe(4); - modified.EvaluationId.ShouldBe(3); - } - - [Fact] - public void ConstructorsAreInternal_ExternalCodeMustUseFluentAPI() - { - // This test documents that all constructors are now internal - // External code cannot directly construct BuildEventContext objects - // They must use CreateInitial() and fluent methods - - var context = BuildEventContext.CreateInitial(1, 2); - context.ShouldNotBeNull(); - context.SubmissionId.ShouldBe(1); - context.NodeId.ShouldBe(2); - - // The Invalid property should also work - BuildEventContext.Invalid.ShouldNotBeNull(); - BuildEventContext.Invalid.NodeId.ShouldBe(BuildEventContext.InvalidNodeId); - } - } -} \ No newline at end of file From 460385f9bf31ad0b661bca5c1d1c4669d76e6d26 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 16 Dec 2025 15:17:33 -0600 Subject: [PATCH 06/34] Resolve review comments about readonly/etc. --- src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs | 4 ++-- src/Build.UnitTests/ConsoleLogger_Tests.cs | 1 - .../Instance/ProjectInstance_Internal_Tests.cs | 2 +- src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs | 2 +- src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs | 4 ---- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 60281e6b595..25e01eb9bf2 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -33,12 +33,12 @@ public class LoggingServicesLogMethod_Tests /// /// A generic valid build event context which can be used in the tests. /// - private static BuildEventContext s_buildEventContext = BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(4); + private static readonly BuildEventContext s_buildEventContext = BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(4); /// /// buildevent context for target events, note the invalid taskId, target started and finished events have this. /// - private static BuildEventContext s_targetBuildEventContext = BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(-1); + private static readonly BuildEventContext s_targetBuildEventContext = BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(-1); #endregion #region Event based logging method tests diff --git a/src/Build.UnitTests/ConsoleLogger_Tests.cs b/src/Build.UnitTests/ConsoleLogger_Tests.cs index 9a7df4fc167..8a82e2ad745 100644 --- a/src/Build.UnitTests/ConsoleLogger_Tests.cs +++ b/src/Build.UnitTests/ConsoleLogger_Tests.cs @@ -1879,7 +1879,6 @@ public void VerifyMPLoggerSwitch() } es.Consume(new BuildStartedEventArgs("bs", null)); BuildEventContext context = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); - BuildEventContext context2 = BuildEventContext.CreateInitial(2, 2).WithEvaluationId(2).WithProjectInstanceId(2); ProjectStartedEventArgs project = new ProjectStartedEventArgs(1, "Hello,", "HI", "None", "Build", null, null, context); project.BuildEventContext = context; diff --git a/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs b/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs index 40b48bd103b..3f2e8f91197 100644 --- a/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs +++ b/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs @@ -857,7 +857,7 @@ public void GetImportPathsAndImportPathsIncludingDuplicates(bool useDirectConstr using ProjectCollection projectCollection = new ProjectCollection(); BuildParameters buildParameters = new BuildParameters(projectCollection) { ProjectLoadSettings = projectLoadSettings }; - BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0, BuildEventContext.InvalidTargetId).WithEvaluationId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(BuildEventContext.InvalidTaskId); + BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0, 0).WithEvaluationId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(BuildEventContext.InvalidTaskId); using ProjectRootElementFromString projectRootElementFromString = new(projectFileContent); ProjectRootElement rootElement = projectRootElementFromString.Project; diff --git a/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs b/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs index 0b3571954b8..a6c0f7536ac 100644 --- a/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs +++ b/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs @@ -17,7 +17,7 @@ namespace Microsoft.Build.UnitTests public class CustomEventArgSerialization_Tests : IDisposable { - private static BuildEventContext defaultContext = BuildEventContext.CreateInitial(5, 4).WithEvaluationId(3).WithProjectInstanceId(2); + private static readonly BuildEventContext defaultContext = BuildEventContext.CreateInitial(5, 4).WithEvaluationId(3).WithProjectInstanceId(2); // Generic build class to test custom serialization of abstract class BuildEventArgs internal sealed class GenericBuildEventArg : BuildEventArgs diff --git a/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs b/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs index d5c1e863b32..067ad3f27db 100644 --- a/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs +++ b/src/Framework.UnitTests/ProjectStartedEventArgs_Tests.cs @@ -21,10 +21,6 @@ public class ProjectStartedEventArgs_Tests /// public ProjectStartedEventArgs_Tests() { - BuildEventContext parentBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 2) - .WithProjectContextId(4) - .WithTargetId(3) - .WithTaskId(5); } /// From 7c99f19e3f09cf38e9572dafaeec92a3e9517d0a Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 16 Dec 2025 16:59:59 -0600 Subject: [PATCH 07/34] Try to stash project evaluation Ids so that we don't lose them for tracking purposes when project configurations are temporarily cached. --- .../Components/Logging/NodeLoggingContext.cs | 8 +------- .../Shared/BuildRequestConfiguration.cs | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs index 350e82e9ebc..7855f096e76 100644 --- a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs @@ -78,13 +78,7 @@ internal ProjectLoggingContext LogProjectStarted(BuildRequestEntry requestEntry) internal ProjectLoggingContext LogProjectStarted(BuildRequest request, BuildRequestConfiguration configuration) { ErrorUtilities.VerifyThrow(this.IsValid, "Build not started."); - - // If we can retrieve the evaluationId from the project, do so. Don't if it's not available or - // if we'd have to retrieve it from the cache in order to access it. - // Order is important here because the Project getter will throw if IsCached. - int evaluationId = (configuration != null && !configuration.IsCached && configuration.Project != null) ? configuration.Project.EvaluationId : BuildEventContext.InvalidEvaluationId; - - return new ProjectLoggingContext(this, request, configuration.ProjectFullPath, configuration.ToolsVersion, evaluationId); + return new ProjectLoggingContext(this, request, configuration.ProjectFullPath, configuration.ToolsVersion, configuration.ProjectEvaluationId); } /// diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index 3505499a49e..37df18ca981 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -140,6 +140,11 @@ internal class BuildRequestConfiguration : IEquatable /// private string _savedCurrentDirectory; + /// + /// Saves the evaluation ID for the project so that it's accessible even when the underlying Project becomes cached + /// + private int _projectEvaluationId = BuildEventContext.InvalidEvaluationId; + #endregion /// @@ -186,6 +191,7 @@ internal BuildRequestConfiguration(int configId, BuildRequestData data, string d _projectInitialTargets = data.ProjectInstance.InitialTargets; _projectDefaultTargets = data.ProjectInstance.DefaultTargets; _projectTargets = GetProjectTargets(data.ProjectInstance.Targets); + _projectEvaluationId = data.ProjectInstance.EvaluationId; if (data.PropertiesToTransfer != null) { _transferredProperties = new List(); @@ -223,6 +229,7 @@ internal BuildRequestConfiguration(int configId, ProjectInstance instance) _projectInitialTargets = instance.InitialTargets; _projectDefaultTargets = instance.DefaultTargets; _projectTargets = GetProjectTargets(instance.Targets); + _projectEvaluationId = instance.EvaluationId; IsCacheable = false; } @@ -247,6 +254,7 @@ private BuildRequestConfiguration(int configId, BuildRequestConfiguration other) IsCacheable = other.IsCacheable; _configId = configId; RequestedTargets = other.RequestedTargets; + _projectEvaluationId = other._projectEvaluationId; } /// @@ -289,6 +297,11 @@ internal BuildRequestConfiguration() /// public bool IsCached { get; private set; } + /// + /// A short + /// + public int ProjectEvaluationId => IsCached ? _projectEvaluationId : _project.EvaluationId; + /// /// Flag indicating if this configuration represents a traversal project. Traversal projects /// are projects which typically do little or no work themselves, but have references to other @@ -423,7 +436,8 @@ private void SetProjectBasedState(ProjectInstance project) _projectDefaultTargets = null; _projectInitialTargets = null; _projectTargets = null; - + + _projectEvaluationId = _project.EvaluationId; ProjectDefaultTargets = _project.DefaultTargets; ProjectInitialTargets = _project.InitialTargets; ProjectTargets = GetProjectTargets(_project.Targets); @@ -933,6 +947,7 @@ public void Translate(ITranslator translator) translator.Translate(ref _transferredProperties, ProjectPropertyInstance.FactoryForDeserialization); translator.Translate(ref _resultsNodeId); translator.Translate(ref _savedCurrentDirectory); + translator.Translate(ref _projectEvaluationId); translator.TranslateDictionary(ref _savedEnvironmentVariables, CommunicationsUtilities.EnvironmentVariableComparer); // if the entire state is translated, then the transferred state represents the full evaluation data @@ -951,6 +966,7 @@ internal void TranslateForFutureUse(ITranslator translator) translator.Translate(ref _projectDefaultTargets); translator.Translate(ref _projectInitialTargets); translator.Translate(ref _projectTargets); + translator.Translate(ref _projectEvaluationId); translator.TranslateDictionary(ref _globalProperties, ProjectPropertyInstance.FactoryForDeserialization); } From b9b82dd26b488d1a424955a2682fe5a65f6b418a Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 16 Dec 2025 17:06:51 -0600 Subject: [PATCH 08/34] Fix this spot too --- src/Build/BackEnd/BuildManager/BuildManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 0bfa46fb4da..73417733f22 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2558,7 +2558,7 @@ private void HandleResult(int node, BuildResult result) BuildEventContext buildEventContext = _projectStartedEvents.TryGetValue(result.SubmissionId, out BuildEventArgs? buildEventArgs) ? buildEventArgs.BuildEventContext! : BuildEventContext.CreateInitial(result.SubmissionId, node) - .WithEvaluationId(configuration.Project?.EvaluationId ?? BuildEventContext.InvalidEvaluationId) + .WithEvaluationId(configuration.ProjectEvaluationId) .WithProjectInstanceId(configuration.ConfigurationId); try { From 713f54375bcf12403de5886b3c6f9ed108b25c14 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 09:42:39 -0600 Subject: [PATCH 09/34] try to rely only on cached data for eval ids --- src/Build/BackEnd/Shared/BuildRequestConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index 37df18ca981..a1e2b082f2c 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -300,7 +300,7 @@ internal BuildRequestConfiguration() /// /// A short /// - public int ProjectEvaluationId => IsCached ? _projectEvaluationId : _project.EvaluationId; + public int ProjectEvaluationId => _projectEvaluationId; /// /// Flag indicating if this configuration represents a traversal project. Traversal projects From 8f98353d3ef83b33ec520a30546dfb941ef75564 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 10:20:40 -0600 Subject: [PATCH 10/34] Add serialization of evaluation id to projectstarted event translation in a version-compatible way --- src/Framework/ProjectStartedEventArgs.cs | 32 +++++++++++++++--------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Framework/ProjectStartedEventArgs.cs b/src/Framework/ProjectStartedEventArgs.cs index a0871790a3c..9a854637d4c 100644 --- a/src/Framework/ProjectStartedEventArgs.cs +++ b/src/Framework/ProjectStartedEventArgs.cs @@ -364,8 +364,11 @@ internal override void WriteToStream(BinaryWriter writer) writer.Write((Int32)parentProjectBuildEventContext.ProjectContextId); writer.Write((Int32)parentProjectBuildEventContext.TargetId); writer.Write((Int32)parentProjectBuildEventContext.TaskId); + // added these in version 20 writer.Write((Int32)parentProjectBuildEventContext.SubmissionId); writer.Write((Int32)parentProjectBuildEventContext.ProjectInstanceId); + // added this in version 36 + writer.Write((Int32)parentProjectBuildEventContext.EvaluationId); } writer.WriteOptionalString(projectFile); @@ -425,23 +428,28 @@ internal override void CreateFromStream(BinaryReader reader, int version) int targetId = reader.ReadInt32(); int taskId = reader.ReadInt32(); - if (version > 20) - { - int submissionId = reader.ReadInt32(); - int projectInstanceId = reader.ReadInt32(); - parentProjectBuildEventContext = BuildEventContext.CreateInitial(submissionId, nodeId) - .WithProjectInstanceId(projectInstanceId) + var builder = + BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, nodeId) .WithProjectContextId(projectContextId) .WithTargetId(targetId) .WithTaskId(taskId); - } - else + + if (version > 20) { - parentProjectBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, nodeId) - .WithProjectContextId(projectContextId) - .WithTargetId(targetId) - .WithTaskId(taskId); + int submissionId = reader.ReadInt32(); + int projectInstanceId = reader.ReadInt32(); + + builder = builder.WithSubmissionId(submissionId) + .WithProjectInstanceId(projectInstanceId); + + if (version >= 36) + { + int evaluationId = reader.ReadInt32(); + builder = builder.WithEvaluationId(evaluationId); + } } + + parentProjectBuildEventContext = builder.Build(); } projectFile = reader.ReadByte() == 0 ? null : reader.ReadString(); From e64739e34c6e2548864ff90de6d0bcfd2f422863 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 10:20:57 -0600 Subject: [PATCH 11/34] use safer accessor for project evaluation id for buildrequestconfigurations --- src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs | 4 ++-- .../BackEnd/Components/ProjectCache/ProjectCacheService.cs | 2 +- src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 664e1ece1fc..d6a3525df4c 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -45,7 +45,7 @@ internal ProjectLoggingContext(NodeLoggingContext nodeLoggingContext, BuildReque requestEntry.RequestConfiguration.Project.PropertiesToBuildWith, requestEntry.RequestConfiguration.Project.ItemsToBuildWith, requestEntry.Request.ParentBuildEventContext, - requestEntry.RequestConfiguration.Project.EvaluationId, + requestEntry.RequestConfiguration.ProjectEvaluationId, requestEntry.Request.ProjectContextId) { } @@ -92,7 +92,7 @@ public static (ProjectStartedEventArgs, ProjectLoggingContext) CreateLoggingCont requestEntry.RequestConfiguration.Project.PropertiesToBuildWith, requestEntry.RequestConfiguration.Project.ItemsToBuildWith, requestEntry.Request.ParentBuildEventContext, - requestEntry.RequestConfiguration.Project.EvaluationId, + requestEntry.RequestConfiguration.ProjectEvaluationId, requestEntry.Request.ProjectContextId); return (args, new ProjectLoggingContext(nodeLoggingContext, args)); diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs index 97f373790bd..16d6ae7dda7 100644 --- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs +++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs @@ -535,7 +535,7 @@ public void PostCacheRequest(CacheRequest cacheRequest, CancellationToken cancel cacheRequest.Submission.BuildRequestData?.TargetNames.ToArray() ?? []); BuildEventContext buildEventContext = _loggingService.CreateProjectCacheBuildEventContext( cacheRequest.Submission.SubmissionId, - evaluationId: cacheRequest.Configuration.Project.EvaluationId, + evaluationId: cacheRequest.Configuration.ProjectEvaluationId, projectInstanceId: cacheRequest.Configuration.ConfigurationId, projectFile: cacheRequest.Configuration.Project.FullPath); diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index c3644666347..bec4cf94381 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1146,7 +1146,7 @@ private async Task BuildProject() _requestEntry.Request, _requestEntry.RequestConfiguration.ProjectFullPath, _requestEntry.RequestConfiguration.ToolsVersion, - _requestEntry.RequestConfiguration.Project.EvaluationId); + _requestEntry.RequestConfiguration.ProjectEvaluationId); throw; } From 1af6b83b8236d45294798cc58571d4b62d77a121 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 10:35:45 -0600 Subject: [PATCH 12/34] Correct some auto-migrated callsite chains so that test invariants are preserved --- src/Build.UnitTests/ConsoleLogger_Tests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Build.UnitTests/ConsoleLogger_Tests.cs b/src/Build.UnitTests/ConsoleLogger_Tests.cs index 8a82e2ad745..c5465da400d 100644 --- a/src/Build.UnitTests/ConsoleLogger_Tests.cs +++ b/src/Build.UnitTests/ConsoleLogger_Tests.cs @@ -600,7 +600,7 @@ public void SingleMessageTest(LoggerVerbosity loggerVerbosity, MessageImportance BuildMessageEventArgs be = new BuildMessageEventArgs(message, "help", "sender", messageImportance) { - BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4) + BuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 1).WithTargetId(2).WithProjectContextId(3).WithTaskId(4) }; eventSourceSink.Consume(be); @@ -656,7 +656,7 @@ public void ColorTest(string expectedMessageType, string expectedColor) throw new InvalidOperationException($"Invalid expectedMessageType '{expectedMessageType}'"); } - buildEventArgs.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(3).WithProjectInstanceId(4); + buildEventArgs.BuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 1).WithTargetId(2).WithProjectContextId(3).WithTaskId(4); EventSourceSink eventSourceSink = new EventSourceSink(); SimulatedConsole console = new SimulatedConsole(); @@ -1818,7 +1818,7 @@ public void DeferredMessages() L.Initialize(es, 2); es.Consume(new BuildStartedEventArgs("bs", null)); TaskCommandLineEventArgs messsage1 = new TaskCommandLineEventArgs("Message", null, MessageImportance.High); - messsage1.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); + messsage1.BuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 1).WithTargetId(1).WithProjectContextId(1).WithTaskId(1); // Message Event es.Consume(messsage1); es.Consume(new BuildFinishedEventArgs("bf", null, true)); @@ -1832,7 +1832,7 @@ public void DeferredMessages() L.Initialize(es, 2); es.Consume(new BuildStartedEventArgs("bs", null)); BuildMessageEventArgs messsage2 = new BuildMessageEventArgs("Message", null, null, MessageImportance.High); - messsage2.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); + messsage2.BuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 1).WithTargetId(2).WithProjectContextId(3).WithTaskId(4); // Message Event es.Consume(messsage2); es.Consume(new BuildFinishedEventArgs("bf", null, true)); @@ -1846,7 +1846,7 @@ public void DeferredMessages() L.Initialize(es, 2); es.Consume(new BuildStartedEventArgs("bs", null)); messsage2 = new BuildMessageEventArgs("Message", null, null, MessageImportance.High); - messsage2.BuildEventContext = BuildEventContext.CreateInitial(1, 1).WithEvaluationId(1).WithProjectInstanceId(1); + messsage2.BuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 1).WithTargetId(2).WithProjectContextId(3).WithTaskId(4); // Message Event es.Consume(messsage2); ProjectStartedEventArgs project = new ProjectStartedEventArgs(1, "Hello,", "HI", "None", "Build", null, null, messsage1.BuildEventContext); From 320682f9d08082219100f3328b3d956ecb7be662 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 10:51:48 -0600 Subject: [PATCH 13/34] Fix a few more missed expectations --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 25e01eb9bf2..d9dba1b50ae 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -33,12 +33,12 @@ public class LoggingServicesLogMethod_Tests /// /// A generic valid build event context which can be used in the tests. /// - private static readonly BuildEventContext s_buildEventContext = BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(4); + private static readonly BuildEventContext s_taskBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 1).WithTargetId(2).WithTaskId(4); /// - /// buildevent context for target events, note the invalid taskId, target started and finished events have this. + /// buildevent context for target events - this will have invalid TaskId, target started and finished events have this. /// - private static readonly BuildEventContext s_targetBuildEventContext = BuildEventContext.CreateInitial(1, 2).WithProjectContextId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(-1); + private static readonly BuildEventContext s_targetBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 2).WithTargetId(2); #endregion #region Event based logging method tests @@ -102,7 +102,7 @@ public void LogErrorNullMessageResource() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogError(s_buildEventContext, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo("foo.cs"), null, "MyTask"); + service.LogError(s_taskBuildEventContext, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo("foo.cs"), null, "MyTask"); }); } @@ -115,7 +115,7 @@ public void LogErrorEmptyMessageResource() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogError(s_buildEventContext, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo("foo.cs"), string.Empty, "MyTask"); + service.LogError(s_taskBuildEventContext, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo("foo.cs"), string.Empty, "MyTask"); }); } @@ -135,7 +135,7 @@ public void LogErrorGoodParameters() ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogError(s_buildEventContext, subcategoryKey, fileInfo, "FatalTaskError", taskName); + service.LogError(s_taskBuildEventContext, subcategoryKey, fileInfo, "FatalTaskError", taskName); VerifyBuildErrorEventArgs(fileInfo, errorCode, helpKeyword, message, service, subcategory); } @@ -165,7 +165,7 @@ public void LogInvalidProjectFileErrorNullException() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogInvalidProjectFileError(s_buildEventContext, null); + service.LogInvalidProjectFileError(s_taskBuildEventContext, null); }); } @@ -182,21 +182,21 @@ public void LogInvalidProjectFileError() // Log the exception for the first time Assert.False(exception.HasBeenLogged); - service.LogInvalidProjectFileError(s_buildEventContext, exception); + service.LogInvalidProjectFileError(s_taskBuildEventContext, exception); Assert.True(exception.HasBeenLogged); BuildEventFileInfo fileInfo = new BuildEventFileInfo(exception.ProjectFile, exception.LineNumber, exception.ColumnNumber, exception.EndLineNumber, exception.EndColumnNumber); VerifyBuildErrorEventArgs(fileInfo, exception.ErrorCode, exception.HelpKeyword, exception.BaseMessage, service, exception.ErrorSubcategory); // Verify when the exception is logged again that it does not actually get logged due to it already being logged service.ResetProcessedBuildEvent(); - service.LogInvalidProjectFileError(s_buildEventContext, exception); + service.LogInvalidProjectFileError(s_taskBuildEventContext, exception); Assert.Null(service.ProcessedBuildEvent); // Reset the HasLogged field and verify OnlyLogCriticalEvents does not effect the logging of the message service.ResetProcessedBuildEvent(); service.OnlyLogCriticalEvents = true; exception.HasBeenLogged = false; - service.LogInvalidProjectFileError(s_buildEventContext, exception); + service.LogInvalidProjectFileError(s_taskBuildEventContext, exception); VerifyBuildErrorEventArgs(fileInfo, exception.ErrorCode, exception.HelpKeyword, exception.BaseMessage, service, exception.ErrorSubcategory); } @@ -226,7 +226,7 @@ public void LogFatalErrorNullFileInfo() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogFatalError(s_buildEventContext, new Exception("SuperException"), null, "FatalTaskError", "TaskName"); + service.LogFatalError(s_taskBuildEventContext, new Exception("SuperException"), null, "FatalTaskError", "TaskName"); }); } @@ -244,7 +244,7 @@ public void LogFatalErrorNullException() string message; GenerateMessageFromExceptionAndResource(null, resourceName, out errorCode, out helpKeyword, out message, parameters); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogFatalError(s_buildEventContext, null, fileInfo, resourceName, parameters); + service.LogFatalError(s_taskBuildEventContext, null, fileInfo, resourceName, parameters); VerifyBuildErrorEventArgs(fileInfo, errorCode, helpKeyword, message, service, null); } @@ -258,7 +258,7 @@ public void LogFatalErrorNullMessageResourceName() { BuildEventFileInfo fileInfo = new BuildEventFileInfo("foo.cs", 1, 2, 3, 4); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogFatalError(s_buildEventContext, new Exception("SuperException"), fileInfo, null); + service.LogFatalError(s_taskBuildEventContext, new Exception("SuperException"), fileInfo, null); }); } @@ -272,7 +272,7 @@ public void LogFatalErrorEmptyMessageResourceName() { BuildEventFileInfo fileInfo = new BuildEventFileInfo("foo.cs", 1, 2, 3, 4); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogFatalError(s_buildEventContext, new Exception("SuperException"), fileInfo, string.Empty, null); + service.LogFatalError(s_taskBuildEventContext, new Exception("SuperException"), fileInfo, string.Empty, null); }); } @@ -293,7 +293,7 @@ public void LogFatalErrorAllGoodInput() ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogFatalError(s_buildEventContext, exception, fileInfo, resourceName, parameter); + service.LogFatalError(s_taskBuildEventContext, exception, fileInfo, resourceName, parameter); VerifyBuildErrorEventArgs(fileInfo, errorCode, helpKeyword, message, service, null); } #endregion @@ -315,7 +315,7 @@ public void LogFatalBuildErrorGoodInput() GenerateMessageFromExceptionAndResource(exception, resourceName, out errorCode, out helpKeyword, out message); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogFatalBuildError(s_buildEventContext, exception, fileInfo); + service.LogFatalBuildError(s_taskBuildEventContext, exception, fileInfo); VerifyBuildErrorEventArgs(fileInfo, errorCode, helpKeyword, message, service, null); } #endregion @@ -332,7 +332,7 @@ public void LogFatalTaskErrorNullTaskNameName() { BuildEventFileInfo fileInfo = new BuildEventFileInfo("foo.cs", 1, 2, 3, 4); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogFatalTaskError(s_buildEventContext, new Exception("SuperException"), fileInfo, null); + service.LogFatalTaskError(s_taskBuildEventContext, new Exception("SuperException"), fileInfo, null); }); } @@ -351,13 +351,13 @@ public void LogFatalTaskError() string message; GenerateMessageFromExceptionAndResource(exception, resourceName, out errorCode, out helpKeyword, out message, parameters); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogFatalTaskError(s_buildEventContext, exception, fileInfo, parameters); + service.LogFatalTaskError(s_taskBuildEventContext, exception, fileInfo, parameters); VerifyBuildErrorEventArgs(fileInfo, errorCode, helpKeyword, message, service, null); // Test when the task name is empty GenerateMessageFromExceptionAndResource(exception, resourceName, out errorCode, out helpKeyword, out message, String.Empty); service.ResetProcessedBuildEvent(); - service.LogFatalTaskError(s_buildEventContext, exception, fileInfo, string.Empty); + service.LogFatalTaskError(s_taskBuildEventContext, exception, fileInfo, string.Empty); VerifyBuildErrorEventArgs(fileInfo, errorCode, helpKeyword, message, service, null); } #endregion @@ -385,7 +385,7 @@ public void LogErrorFromTextNullFileInfo() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogErrorFromText(s_buildEventContext, "SubCategoryForSolutionParsingErrors", "WarningCode", "HelpKeyword", null, "Message"); + service.LogErrorFromText(s_taskBuildEventContext, "SubCategoryForSolutionParsingErrors", "WarningCode", "HelpKeyword", null, "Message"); }); } @@ -509,7 +509,7 @@ public void LogTaskWarningFromExceptionNullTaskName() { BuildEventFileInfo fileInfo = new BuildEventFileInfo("foo.cs", 1, 2, 3, 4); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogTaskWarningFromException(s_buildEventContext, null, fileInfo, null); + service.LogTaskWarningFromException(s_taskBuildEventContext, null, fileInfo, null); }); } @@ -523,7 +523,7 @@ public void LogTaskWarningFromExceptionEmptyTaskName() { BuildEventFileInfo fileInfo = new BuildEventFileInfo("foo.cs", 1, 2, 3, 4); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogTaskWarningFromException(s_buildEventContext, null, fileInfo, null); + service.LogTaskWarningFromException(s_taskBuildEventContext, null, fileInfo, null); }); } @@ -544,14 +544,14 @@ public void LogTaskWarningFromException() // Check with a null exception GenerateMessageFromExceptionAndResource(null, resourceName, out warningCode, out helpKeyword, out message, parameters); - service.LogTaskWarningFromException(s_buildEventContext, null, fileInfo, parameters); + service.LogTaskWarningFromException(s_taskBuildEventContext, null, fileInfo, parameters); VerifyBuildWarningEventArgs(fileInfo, warningCode, helpKeyword, message, service, null); // Check when the exception is not null service.ResetProcessedBuildEvent(); Exception exception = new Exception("SuperException"); GenerateMessageFromExceptionAndResource(exception, resourceName, out warningCode, out helpKeyword, out message, parameters); - service.LogTaskWarningFromException(s_buildEventContext, exception, fileInfo, parameters); + service.LogTaskWarningFromException(s_taskBuildEventContext, exception, fileInfo, parameters); VerifyBuildWarningEventArgs(fileInfo, warningCode, helpKeyword, message, service, null); } #endregion @@ -566,7 +566,7 @@ public void LogWarningNullMessageResource() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogWarning(s_buildEventContext, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo("foo.cs"), null, "MyTask"); + service.LogWarning(s_taskBuildEventContext, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo("foo.cs"), null, "MyTask"); }); } @@ -579,7 +579,7 @@ public void LogWarningEmptyMessageResource() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogWarning(s_buildEventContext, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo("foo.cs"), string.Empty, "MyTask"); + service.LogWarning(s_taskBuildEventContext, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo("foo.cs"), string.Empty, "MyTask"); }); } @@ -619,7 +619,7 @@ public void LogWarningFromTextNullFileInfo() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogWarningFromText(s_buildEventContext, "SubCategoryForSolutionParsingErrors", "WarningCode", "HelpKeyword", null, "Message"); + service.LogWarningFromText(s_taskBuildEventContext, "SubCategoryForSolutionParsingErrors", "WarningCode", "HelpKeyword", null, "Message"); }); } @@ -676,7 +676,7 @@ public void LogCommentNullMessageResourceName() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogComment(s_buildEventContext, MessageImportance.Low, null, null); + service.LogComment(s_taskBuildEventContext, MessageImportance.Low, null, null); }); } @@ -689,7 +689,7 @@ public void LogCommentEmptyMessageResourceName() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogComment(s_buildEventContext, MessageImportance.Low, String.Empty, null); + service.LogComment(s_taskBuildEventContext, MessageImportance.Low, String.Empty, null); }); } @@ -706,13 +706,13 @@ public void LogCommentGoodMessage() ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); // Verify message is logged when OnlyLogCriticalEvents is false - service.LogComment(s_buildEventContext, messageImportance, "BuildFinishedSuccess"); + service.LogComment(s_taskBuildEventContext, messageImportance, "BuildFinishedSuccess"); VerityBuildMessageEventArgs(service, messageImportance, message); // Verify no message is logged when OnlyLogCriticalEvents is true service.ResetProcessedBuildEvent(); service.OnlyLogCriticalEvents = true; - service.LogComment(s_buildEventContext, MessageImportance.Normal, "BuildFinishedSuccess"); + service.LogComment(s_taskBuildEventContext, MessageImportance.Normal, "BuildFinishedSuccess"); Assert.Null(service.ProcessedBuildEvent); } @@ -729,7 +729,7 @@ public void LogCommentFromTextNullMessage() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogCommentFromText(s_buildEventContext, MessageImportance.Low, null); + service.LogCommentFromText(s_taskBuildEventContext, MessageImportance.Low, null); }); } @@ -740,7 +740,7 @@ public void LogCommentFromTextNullMessage() public void LogCommentFromTextEmptyMessage() { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogCommentFromText(s_buildEventContext, MessageImportance.Low, string.Empty); + service.LogCommentFromText(s_taskBuildEventContext, MessageImportance.Low, string.Empty); } /// @@ -766,12 +766,12 @@ public void LogCommentFromTextGoodMessage() string message = ResourceUtilities.GetResourceString("BuildFinishedSuccess"); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogCommentFromText(s_buildEventContext, messageImportance, ResourceUtilities.GetResourceString("BuildFinishedSuccess")); + service.LogCommentFromText(s_taskBuildEventContext, messageImportance, ResourceUtilities.GetResourceString("BuildFinishedSuccess")); VerityBuildMessageEventArgs(service, messageImportance, message); service.ResetProcessedBuildEvent(); service.OnlyLogCriticalEvents = true; - service.LogCommentFromText(s_buildEventContext, MessageImportance.Normal, ResourceUtilities.GetResourceString("BuildFinishedSuccess")); + service.LogCommentFromText(s_taskBuildEventContext, MessageImportance.Normal, ResourceUtilities.GetResourceString("BuildFinishedSuccess")); Assert.Null(service.ProcessedBuildEvent); } #endregion @@ -792,7 +792,7 @@ public void ProjectStartedNullBuildEventContext() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogProjectStarted(null, 1, 2, s_buildEventContext, "ProjectFile", "TargetNames", null, null); + service.LogProjectStarted(null, 1, 2, s_taskBuildEventContext, "ProjectFile", "TargetNames", null, null); }); } @@ -806,7 +806,7 @@ public void ProjectStartedNullParentBuildEventContext() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogProjectStarted(s_buildEventContext, 1, 2, null, "ProjectFile", "TargetNames", null, null); + service.LogProjectStarted(s_taskBuildEventContext, 1, 2, null, "ProjectFile", "TargetNames", null, null); }); } @@ -841,8 +841,8 @@ public void ProjectStartedEventTests(string projectFile, string targetNames) BuildRequestConfiguration config = new BuildRequestConfiguration(2, data, "4.0"); cache.AddConfiguration(config); - BuildEventContext context = service.LogProjectStarted(s_buildEventContext, 1, 2, s_buildEventContext, projectFile, targetNames, null, null); - BuildEventContext parentBuildEventContext = s_buildEventContext; + BuildEventContext context = service.LogProjectStarted(s_taskBuildEventContext, 1, 2, s_taskBuildEventContext, projectFile, targetNames, null, null); + BuildEventContext parentBuildEventContext = s_taskBuildEventContext; VerifyProjectStartedEventArgs(service, context.ProjectContextId, message, projectFile, targetNames, parentBuildEventContext, context); service.ResetProcessedBuildEvent(); @@ -1276,8 +1276,8 @@ public void LogTelemetryTest() TestLogTelemetry(buildEventContext: null, eventName: "no context and no properties", properties: null); TestLogTelemetry(buildEventContext: null, eventName: "no context but with properties", properties: eventProperties); - TestLogTelemetry(buildEventContext: s_buildEventContext, eventName: "event context but no properties", properties: null); - TestLogTelemetry(buildEventContext: s_buildEventContext, eventName: "event context and properties", properties: eventProperties); + TestLogTelemetry(buildEventContext: s_taskBuildEventContext, eventName: "event context but no properties", properties: null); + TestLogTelemetry(buildEventContext: s_taskBuildEventContext, eventName: "event context and properties", properties: eventProperties); } private void TestLogTelemetry(BuildEventContext buildEventContext, string eventName, IDictionary properties) @@ -1348,7 +1348,7 @@ private void TestLogErrorFromText(string errorCode, string helpKeyword, string s } ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogErrorFromText(s_buildEventContext, subcategoryKey, errorCode, helpKeyword, fileInfo, message); + service.LogErrorFromText(s_taskBuildEventContext, subcategoryKey, errorCode, helpKeyword, fileInfo, message); VerifyBuildErrorEventArgs(fileInfo, errorCode, helpKeyword, message, service, subcategory); } @@ -1369,7 +1369,7 @@ private void TestLogWarningFromText(string warningCode, string helpKeyword, stri } ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogWarningFromText(s_buildEventContext, subcategoryKey, warningCode, helpKeyword, fileInfo, message); + service.LogWarningFromText(s_taskBuildEventContext, subcategoryKey, warningCode, helpKeyword, fileInfo, message); VerifyBuildWarningEventArgs(fileInfo, warningCode, helpKeyword, message, service, subcategory); } @@ -1387,7 +1387,7 @@ private void TestLogWarning(string taskName, string subCategoryKey) string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword(out warningCode, out helpKeyword, "FatalTaskError", taskName); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogWarning(s_buildEventContext, subCategoryKey, fileInfo, "FatalTaskError", taskName); + service.LogWarning(s_taskBuildEventContext, subCategoryKey, fileInfo, "FatalTaskError", taskName); VerifyBuildWarningEventArgs(fileInfo, warningCode, helpKeyword, message, service, subcategory); } @@ -1403,11 +1403,11 @@ private void TestProjectFinishedEvent(string projectFile, bool success) ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1, componentHost); try { - service.LogProjectFinished(s_buildEventContext, projectFile, success); + service.LogProjectFinished(s_taskBuildEventContext, projectFile, success); } catch (InternalErrorException ex) { - Assert.Contains("ContextID " + s_buildEventContext.ProjectContextId, ex.Message); + Assert.Contains("ContextID " + s_taskBuildEventContext.ProjectContextId, ex.Message); } finally { @@ -1425,7 +1425,7 @@ private void TestProjectFinishedEvent(string projectFile, bool success) BuildEventContext.CreateInitial(0, 1), 1, 2, - s_buildEventContext, + s_taskBuildEventContext, projectFile, null, null, @@ -1450,12 +1450,12 @@ private void TestTaskStartedEvent(string taskName, string projectFile, string pr string taskAssemblyLocation = Assembly.GetExecutingAssembly().Location; ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogTaskStarted(s_buildEventContext, taskName, projectFile, projectFileOfTask, taskAssemblyLocation); + service.LogTaskStarted(s_taskBuildEventContext, taskName, projectFile, projectFileOfTask, taskAssemblyLocation); VerifyTaskStartedEvent(taskName, projectFile, projectFileOfTask, message, service, taskAssemblyLocation); service.ResetProcessedBuildEvent(); service.OnlyLogCriticalEvents = true; - service.LogTaskStarted(s_buildEventContext, taskName, projectFile, projectFileOfTask, taskAssemblyLocation); + service.LogTaskStarted(s_taskBuildEventContext, taskName, projectFile, projectFileOfTask, taskAssemblyLocation); Assert.Null(service.ProcessedBuildEvent); } @@ -1470,12 +1470,12 @@ private void TestTaskFinished(string taskName, string projectFile, string projec { string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword(succeeded ? "TaskFinishedSuccess" : "TaskFinishedFailure", taskName); ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogTaskFinished(s_buildEventContext, taskName, projectFile, projectFileOfTask, succeeded); + service.LogTaskFinished(s_taskBuildEventContext, taskName, projectFile, projectFileOfTask, succeeded); VerifyTaskFinishedEvent(taskName, projectFile, projectFileOfTask, succeeded, message, service); service.ResetProcessedBuildEvent(); service.OnlyLogCriticalEvents = true; - service.LogTaskFinished(s_buildEventContext, taskName, projectFile, projectFileOfTask, succeeded); + service.LogTaskFinished(s_taskBuildEventContext, taskName, projectFile, projectFileOfTask, succeeded); Assert.Null(service.ProcessedBuildEvent); } @@ -1624,7 +1624,7 @@ private void VerifyTaskFinishedEvent(string taskName, string projectFile, string taskName, succeeded, service.ProcessedBuildEvent.Timestamp); - taskEvent.BuildEventContext = s_buildEventContext; + taskEvent.BuildEventContext = s_taskBuildEventContext; Assert.True(((TaskFinishedEventArgs)service.ProcessedBuildEvent).IsEquivalent(taskEvent)); } @@ -1646,7 +1646,7 @@ private void VerifyTaskStartedEvent(string taskName, string projectFile, string taskName, service.ProcessedBuildEvent.Timestamp, taskAssemblyLocation); - taskEvent.BuildEventContext = s_buildEventContext; + taskEvent.BuildEventContext = s_taskBuildEventContext; Assert.True(((TaskStartedEventArgs)service.ProcessedBuildEvent).IsEquivalent(taskEvent)); } @@ -1710,7 +1710,7 @@ private void VerityBuildMessageEventArgs(ProcessBuildEventHelper service, Messag messageImportance, service.ProcessedBuildEvent.Timestamp); - buildMessageEvent.BuildEventContext = s_buildEventContext; + buildMessageEvent.BuildEventContext = s_taskBuildEventContext; Assert.True(((BuildMessageEventArgs)service.ProcessedBuildEvent).IsEquivalent(buildMessageEvent)); } @@ -1737,7 +1737,7 @@ private void VerifyBuildWarningEventArgs(BuildEventFileInfo fileInfo, string war helpKeyword, "MSBuild", service.ProcessedBuildEvent.Timestamp); - buildEvent.BuildEventContext = s_buildEventContext; + buildEvent.BuildEventContext = s_taskBuildEventContext; Assert.True(buildEvent.IsEquivalent((BuildWarningEventArgs)service.ProcessedBuildEvent)); } @@ -1764,7 +1764,7 @@ private void VerifyBuildErrorEventArgs(BuildEventFileInfo fileInfo, string error helpKeyword, "MSBuild", service.ProcessedBuildEvent.Timestamp); - buildEvent.BuildEventContext = s_buildEventContext; + buildEvent.BuildEventContext = s_taskBuildEventContext; Assert.True(buildEvent.IsEquivalent((BuildErrorEventArgs)service.ProcessedBuildEvent)); } From 7b99811bca54fe2d4c466d98245137d998f6cec3 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 12:25:45 -0600 Subject: [PATCH 14/34] Reduce the direct use of CreateInitial in favor of flowing contexts through the system more directly --- .../BackEnd/BuildManager/BuildManager.cs | 11 ++-- .../Logging/EvaluationLoggingContext.cs | 2 +- .../Components/Logging/ILoggingService.cs | 17 +++--- .../Logging/LoggingServiceLogMethods.cs | 24 +++------ .../Components/Logging/NodeLoggingContext.cs | 5 +- .../ProjectCache/ProjectCacheService.cs | 12 ++--- .../BackEnd/Components/Scheduler/Scheduler.cs | 24 ++++++--- .../Shared/BuildRequestConfiguration.cs | 7 ++- .../BuildCheckManagerProvider.cs | 2 +- src/Build/Definition/Project.cs | 20 ++++--- src/Build/Definition/ProjectCollection.cs | 15 ++++-- src/Build/Instance/ProjectInstance.cs | 20 ++++--- src/Framework/BuildEventContext.cs | 53 +------------------ 13 files changed, 88 insertions(+), 124 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 73417733f22..6c84550e1c7 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -1494,7 +1494,7 @@ internal void ExecuteSubmission( where TResultData : BuildResultBase { // For the current submission we only know the SubmissionId and that it happened on scheduler node - all other BuildEventContext dimensions are unknown now. - BuildEventContext buildEventContext = BuildEventContext.CreateInitial(submission.SubmissionId, nodeId: 1); + BuildEventContext buildEventContext = Scheduler.s_schedulerInProcNodeBuildEventContext.WithSubmissionId(submission.SubmissionId); BuildSubmissionStartedEventArgs submissionStartedEvent = new( submission.BuildRequestDataBase.GlobalPropertiesLookup, @@ -1582,7 +1582,7 @@ private void LoadSolutionIntoConfiguration(BuildRequestConfiguration config, Bui var buildEventContext = request.BuildEventContext; if (buildEventContext == BuildEventContext.Invalid) { - buildEventContext = CreateErrorLoggingContext(request.SubmissionId, 0); + buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(request.SubmissionId); } var instances = ProjectInstance.LoadSolutionForBuild( @@ -1855,9 +1855,8 @@ void LogInvalidProjectFileError(InvalidProjectFileException projectException) /// Creates a BuildEventContext suitable for error logging for the given submission. /// /// The submission ID - /// The node ID, defaults to 1 for general error logging /// A BuildEventContext for logging errors - private static BuildEventContext CreateErrorLoggingContext(int submissionId, int nodeId = 1) => BuildEventContext.CreateInitial(submissionId, nodeId); + private static BuildEventContext CreateErrorLoggingContext(int submissionId) => Scheduler.s_schedulerInProcNodeBuildEventContext.WithSubmissionId(submissionId); /// /// Waits to drain all events of logging service. @@ -2598,7 +2597,7 @@ private void HandleNodeShutdown(int node, NodeShutdown shutdownPacket) ILoggingService loggingService = ((IBuildComponentHost)this).GetComponent(BuildComponentType.LoggingService); foreach (BuildSubmissionBase submission in _buildSubmissions.Values) { - BuildEventContext buildEventContext = CreateErrorLoggingContext(submission.SubmissionId, BuildEventContext.InvalidNodeId); + BuildEventContext buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(submission.SubmissionId); string exception = ExceptionHandling.ReadAnyExceptionFromFile(_instantiationTimeUtc); loggingService?.LogError(buildEventContext, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, ExceptionHandling.DebugDumpPath, exception); } @@ -2754,7 +2753,7 @@ private void PerformSchedulingActions(IEnumerable responses) if (newNodes?.Count != response.NumberOfNodesToCreate || newNodes.Any(n => n == null)) { - BuildEventContext buildEventContext = CreateErrorLoggingContext(0, Scheduler.VirtualNode); + BuildEventContext buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(0); ((IBuildComponentHost)this).LoggingService.LogError(buildEventContext, new BuildEventFileInfo(String.Empty), "UnableToCreateNode", response.RequiredNodeType.ToString("G")); throw new BuildAbortedException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("UnableToCreateNode", response.RequiredNodeType.ToString("G"))); diff --git a/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs b/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs index ff39ef63976..d92f0f212cb 100644 --- a/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs @@ -20,7 +20,7 @@ internal class EvaluationLoggingContext : LoggingContext public EvaluationLoggingContext(ILoggingService loggingService, BuildEventContext buildEventContext, string projectFile) : base( loggingService, - loggingService.CreateEvaluationBuildEventContext(buildEventContext.NodeId, buildEventContext.SubmissionId)) + loggingService.CreateEvaluationBuildEventContext(buildEventContext)) { _projectFile = projectFile; IsValid = true; diff --git a/src/Build/BackEnd/Components/Logging/ILoggingService.cs b/src/Build/BackEnd/Components/Logging/ILoggingService.cs index 6621c2b740f..2ab8dff3a8e 100644 --- a/src/Build/BackEnd/Components/Logging/ILoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/ILoggingService.cs @@ -503,20 +503,17 @@ MessageImportance MinimumRequiredMessageImportance /// /// Create an evaluation context, by generating a new evaluation id. /// - /// The node id - /// The submission id + /// The parent context to derive the new evaluation context from /// - BuildEventContext CreateEvaluationBuildEventContext(int nodeId, int submissionId); + BuildEventContext CreateEvaluationBuildEventContext(BuildEventContext parentContext); /// - /// Create a project cache context, by generating a new project context id. + /// Create a project-level build event context, by generating a new project context id and applying it to a parent context scope. /// - /// The submission id - /// The evaluation id - /// The project instance id + /// The parent context to derive the new project cache context from /// Project file being built /// - BuildEventContext CreateProjectCacheBuildEventContext(int submissionId, int evaluationId, int projectInstanceId, string projectFile); + BuildEventContext CreateProjectCacheBuildEventContext(BuildEventContext parentBuildEventContext, string projectFile); /// /// Logs that a project evaluation has started @@ -581,8 +578,8 @@ ProjectStartedEventArgs CreateProjectStarted( string targetNames, IEnumerable properties, IEnumerable items, - int evaluationId = BuildEventContext.InvalidEvaluationId, - int projectContextId = BuildEventContext.InvalidProjectContextId); + int evaluationId, + int projectContextId); /// /// Log that the project has finished diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index b10864b4276..c5b01993798 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -401,14 +401,12 @@ public void LogBuildCanceled() } /// - public BuildEventContext CreateEvaluationBuildEventContext(int nodeId, int submissionId) - => BuildEventContext.CreateInitial(submissionId, nodeId).WithEvaluationId(NextEvaluationId); + public BuildEventContext CreateEvaluationBuildEventContext(BuildEventContext parentContext) + => parentContext.WithEvaluationId(NextEvaluationId); /// public BuildEventContext CreateProjectCacheBuildEventContext( - int submissionId, - int evaluationId, - int projectInstanceId, + BuildEventContext parentBuildEventContext, string projectFile) { int projectContextId = NextProjectId; @@ -416,13 +414,7 @@ public BuildEventContext CreateProjectCacheBuildEventContext( // In the future if some LogProjectCacheStarted event is created, move this there to align with evaluation and build execution. _projectFileMap[projectContextId] = projectFile; - // Because the project cache runs in the BuildManager, it makes some sense to associate logging with the in-proc node. - // If a invalid node id is used the messages become deferred in the console logger and spit out at the end. - int nodeId = Scheduler.InProcNodeId; - - return BuildEventContext.CreateInitial(submissionId, nodeId) - .WithEvaluationId(evaluationId) - .WithProjectInstanceId(projectInstanceId) + return parentBuildEventContext .WithProjectContextId(projectContextId); } @@ -498,8 +490,8 @@ public BuildEventContext LogProjectStarted( string targetNames, IEnumerable properties, IEnumerable items, - int evaluationId = BuildEventContext.InvalidEvaluationId, - int projectContextId = BuildEventContext.InvalidProjectContextId) + int evaluationId, + int projectContextId) { var args = CreateProjectStarted(nodeBuildEventContext, submissionId, @@ -531,8 +523,8 @@ public ProjectStartedEventArgs CreateProjectStarted( string targetNames, IEnumerable properties, IEnumerable items, - int evaluationId = BuildEventContext.InvalidEvaluationId, - int projectContextId = BuildEventContext.InvalidProjectContextId) + int evaluationId, + int projectContextId) { ErrorUtilities.VerifyThrow(nodeBuildEventContext != null, "Need a nodeBuildEventContext"); diff --git a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs index 7855f096e76..258cc649f7e 100644 --- a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs @@ -18,10 +18,11 @@ internal class NodeLoggingContext : BuildLoggingContext /// Used to create the initial, base logging context for the node. /// /// The logging service to use. + /// The parent build event context to associate this node logging context with /// The /// true if this is an in-process node, otherwise false. - internal NodeLoggingContext(ILoggingService loggingService, int nodeId, bool inProcNode) - : base(loggingService, BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, nodeId), inProcNode) + internal NodeLoggingContext(ILoggingService loggingService, BuildEventContext parentContext, int nodeId, bool inProcNode) + : base(loggingService, parentContext.WithNodeId(nodeId), inProcNode) { ErrorUtilities.VerifyThrow(nodeId != BuildEventContext.InvalidNodeId, "Should not ever be given an invalid NodeId"); diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs index 16d6ae7dda7..a88d77d3afb 100644 --- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs +++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs @@ -533,11 +533,7 @@ public void PostCacheRequest(CacheRequest cacheRequest, CancellationToken cancel BuildRequestData buildRequest = new BuildRequestData( cacheRequest.Configuration.Project, cacheRequest.Submission.BuildRequestData?.TargetNames.ToArray() ?? []); - BuildEventContext buildEventContext = _loggingService.CreateProjectCacheBuildEventContext( - cacheRequest.Submission.SubmissionId, - evaluationId: cacheRequest.Configuration.ProjectEvaluationId, - projectInstanceId: cacheRequest.Configuration.ConfigurationId, - projectFile: cacheRequest.Configuration.Project.FullPath); + BuildEventContext buildEventContext = _loggingService.CreateProjectCacheBuildEventContext(GetCacheRequestBuildEventContext(cacheRequest), projectFile: cacheRequest.Configuration.Project.FullPath); CacheResult cacheResult; try @@ -562,11 +558,11 @@ void EvaluateProjectIfNecessary(BuildSubmission submission, BuildRequestConfigur { if (!configuration.IsLoaded) { + BuildEventContext parentBuildContext = Scheduler.s_schedulerInProcNodeBuildEventContext.WithSubmissionId(submission.SubmissionId); configuration.LoadProjectIntoConfiguration( _buildManager, submission.BuildRequestData!.Flags, - submission.SubmissionId, - Scheduler.InProcNodeId); + parentBuildContext); // If we're taking the time to evaluate, avoid having other nodes to repeat the same evaluation. // Based on the assumption that ProjectInstance serialization is faster than evaluating from scratch. @@ -576,6 +572,8 @@ void EvaluateProjectIfNecessary(BuildSubmission submission, BuildRequestConfigur } } + private BuildEventContext GetCacheRequestBuildEventContext(CacheRequest cacheRequest) => Scheduler.s_schedulerNodeBuildEventContext.WithEvaluationId(cacheRequest.Configuration.ProjectEvaluationId).WithProjectInstanceId(cacheRequest.Configuration.ConfigurationId); + private async ValueTask GetCacheResultAsync(BuildRequestData buildRequest, BuildRequestConfiguration buildRequestConfiguration, BuildEventContext buildEventContext, CancellationToken cancellationToken) { ErrorUtilities.VerifyThrowInternalNull(buildRequest.ProjectInstance, nameof(buildRequest.ProjectInstance)); diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index afe5814d32a..3eacf547425 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -42,7 +42,7 @@ internal class Scheduler : IScheduler internal const int ResultsTransferredId = -2; /// - /// The in-proc node id + /// The in-proc node id for the worker node that performs work the scheduler itself assigns on the same 'logical' node. /// internal const int InProcNodeId = 1; @@ -58,6 +58,16 @@ internal class Scheduler : IScheduler /// private const double DefaultCustomSchedulerForSQLConfigurationLimitMultiplier = 1.1; + /// + /// The build event context for the scheduler node - can be used as a 'root' context for contexts' derived or needed when running scheduler operations + /// + internal static BuildEventContext s_schedulerNodeBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, VirtualNode); + + /// + /// The build event context for the in-proc node - the worker node that executes on the same 'logical' node as the scheduler + /// + internal static BuildEventContext s_schedulerInProcNodeBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, InProcNodeId); + #region Scheduler Data /// @@ -600,7 +610,7 @@ public void Reset() public void WriteDetailedSummary(int submissionId) { ILoggingService loggingService = _componentHost.LoggingService; - BuildEventContext context = BuildEventContext.CreateInitial(submissionId, 0); + BuildEventContext context = s_schedulerNodeBuildEventContext.WithSubmissionId(submissionId); loggingService.LogComment(context, MessageImportance.Normal, "DetailedSummaryHeader"); foreach (SchedulableRequest request in _schedulingData.GetRequestsByHierarchy(null)) @@ -675,7 +685,7 @@ public void InitializeComponent(IBuildComponentHost host) _componentHost = host; _resultsCache = (IResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache); _configCache = (IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache); - _inprocNodeContext = new NodeLoggingContext(_componentHost.LoggingService, InProcNodeId, true); + _inprocNodeContext = new NodeLoggingContext(_componentHost.LoggingService, s_schedulerNodeBuildEventContext, InProcNodeId, true); } /// @@ -2037,7 +2047,7 @@ private bool CheckIfCacheMissOnReferencedProjectIsAllowedAndErrorIfNot(int nodeF string projectFullPath = _configCache[request.ConfigurationId].ProjectFullPath; string parentProjectFullPath = GetParentConfigurationId(request, _configCache, _schedulingData).ProjectFullPath; _componentHost.LoggingService.LogComment( - BuildEventContext.CreateInitial(request.SubmissionId, 1), + s_schedulerNodeBuildEventContext.WithSubmissionId(request.SubmissionId), MessageImportance.Normal, "SkippedConstraintsOnRequest", parentProjectFullPath, @@ -2193,7 +2203,7 @@ private void LogRequestHandledFromCache(BuildRequest request, BuildResult result { BuildRequestConfiguration configuration = _configCache[request.ConfigurationId]; int nodeId = _schedulingData.GetAssignedNodeForRequestConfiguration(request.ConfigurationId); - NodeLoggingContext nodeContext = new NodeLoggingContext(_componentHost.LoggingService, nodeId, true); + NodeLoggingContext nodeContext = new NodeLoggingContext(_componentHost.LoggingService, s_schedulerNodeBuildEventContext, nodeId, true); nodeContext.LogRequestHandledFromCache(request, configuration, result); TraceScheduler( @@ -2944,7 +2954,7 @@ private void DumpRequestSpec(StreamWriter file, SchedulableRequest request, int private void WriteSchedulingPlan(int submissionId) { SchedulingPlan plan = new SchedulingPlan(_configCache, _schedulingData); - plan.WritePlan(submissionId, _componentHost.LoggingService, BuildEventContext.CreateInitial(submissionId, 0)); + plan.WritePlan(submissionId, _componentHost.LoggingService, s_schedulerNodeBuildEventContext.WithSubmissionId(submissionId)); } /// @@ -2953,7 +2963,7 @@ private void WriteSchedulingPlan(int submissionId) private void ReadSchedulingPlan(int submissionId) { _schedulingPlan = new SchedulingPlan(_configCache, _schedulingData); - _schedulingPlan.ReadPlan(submissionId, _componentHost.LoggingService, BuildEventContext.CreateInitial(submissionId, 0)); + _schedulingPlan.ReadPlan(submissionId, _componentHost.LoggingService, s_schedulerNodeBuildEventContext.WithSubmissionId(submissionId)); } #endregion diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index a1e2b082f2c..c3afe11233d 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -455,8 +455,7 @@ private void SetProjectBasedState(ProjectInstance project) internal void LoadProjectIntoConfiguration( IBuildComponentHost componentHost, BuildRequestDataFlags buildRequestDataFlags, - int submissionId, - int nodeId) + BuildEventContext parentBuildEventContext) { ErrorUtilities.VerifyThrow(!IsLoaded, "Already loaded the project for this configuration id {0}.", ConfigurationId); @@ -507,9 +506,9 @@ internal void LoadProjectIntoConfiguration( toolsVersionOverride, componentHost.BuildParameters, componentHost.LoggingService, - BuildEventContext.CreateInitial(submissionId, nodeId), + parentBuildEventContext, sdkResolverService, - submissionId, + parentBuildEventContext.SubmissionId, projectLoadSettings); }); } diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 89998bad255..b48be3bdca8 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -649,7 +649,7 @@ public void StartProjectRequest(ICheckContext checkContext, string projectFullPa { foreach (BuildEventArgs deferredArgs in list) { - deferredArgs.BuildEventContext = deferredArgs.BuildEventContext!.WithInstanceIdAndContextId(buildEventContext); + deferredArgs.BuildEventContext = deferredArgs.BuildEventContext!.WithProjectInstanceId(buildEventContext.ProjectInstanceId).WithProjectContextId(buildEventContext.ProjectContextId); checkContext.DispatchBuildEvent(deferredArgs); } list.Clear(); diff --git a/src/Build/Definition/Project.cs b/src/Build/Definition/Project.cs index 9355e46e2e5..316fbba1a94 100644 --- a/src/Build/Definition/Project.cs +++ b/src/Build/Definition/Project.cs @@ -67,7 +67,11 @@ public class Project : ILinkableObject /// /// Context to log messages and events in. /// - private static readonly BuildEventContext s_buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); + /// + /// This should only be used on pathways that create Projects outside of the MSBuild exe workflow - users directly loading Projects, etc. + /// In such cases we don't have a live set of nodes (yet?), but we still need contexts for message association. + /// + private static readonly BuildEventContext s_errorBuildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(0); private ProjectLink implementation; private IProjectLinkInternal implementationInternal; @@ -1922,7 +1926,7 @@ public ProjectImpl(Project owner, XmlReader xmlReader, IDictionary loggers, IEnum { if (!IsBuildEnabled) { - LoggingService.LogError(s_buildEventContext, new BuildEventFileInfo(FullPath), "SecurityProjectBuildDisabled"); + LoggingService.LogError(s_errorBuildEventContext, new BuildEventFileInfo(FullPath), "SecurityProjectBuildDisabled"); if (LoggingService is LoggingService defaultLoggingService) { defaultLoggingService.WaitForLoggingToProcessEvents(); @@ -3553,7 +3557,7 @@ public string ExpandItemIncludeBestEffortLeaveEscaped(ProjectItemElement renamed _data.Expander, LoggingService, FullPath, - s_buildEventContext); + s_errorBuildEventContext); if (items.Count != 1) { @@ -3620,7 +3624,7 @@ private List AddItemHelper(ProjectItemElement itemElement, string u _data.Expander, LoggingService, FullPath, - s_buildEventContext); + s_errorBuildEventContext); foreach (ProjectItem item in items) { @@ -3708,7 +3712,7 @@ private void ReevaluateIfNecessary( } catch (InvalidProjectFileException ex) { - loggingServiceForEvaluation.LogInvalidProjectFileError(s_buildEventContext, ex); + loggingServiceForEvaluation.LogInvalidProjectFileError(s_errorBuildEventContext, ex); throw; } } @@ -3747,7 +3751,7 @@ private void Reevaluate( ProjectCollection, Owner._directoryCacheFactory, ProjectCollection.ProjectRootElementCache, - s_buildEventContext, + s_errorBuildEventContext, evaluationContext.SdkResolverService, BuildEventContext.InvalidSubmissionId, evaluationContext, diff --git a/src/Build/Definition/ProjectCollection.cs b/src/Build/Definition/ProjectCollection.cs index 4da2b181d44..37c0afafa37 100644 --- a/src/Build/Definition/ProjectCollection.cs +++ b/src/Build/Definition/ProjectCollection.cs @@ -100,6 +100,15 @@ public class ProjectCollection : IToolsetProvider, IBuildComponent, IDisposable /// private static ProjectCollection s_globalProjectCollection; + /// + /// Context to log messages and events in. + /// + /// + /// This should only be used on pathways that create ProjectCollections outside of the MSBuild exe workflow - users directly loading Projects, etc. + /// In such cases we don't have a live set of nodes (yet?), but we still need contexts for message association. + /// + private static readonly BuildEventContext s_errorBuildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(0); + /// /// Gets the file version of the file in which the Engine assembly lies. /// @@ -384,8 +393,7 @@ public ProjectCollection(IDictionary globalProperties, IEnumerab } catch (InvalidProjectFileException ex2) { - BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); - LoggingService.LogInvalidProjectFileError(buildEventContext, ex2); + LoggingService.LogInvalidProjectFileError(s_errorBuildEventContext, ex2); throw; } } @@ -1261,8 +1269,7 @@ public Project LoadProject(string fileName, IDictionary globalPr } catch (InvalidProjectFileException ex) { - var buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); - LoggingService.LogInvalidProjectFileError(buildEventContext, ex); + LoggingService.LogInvalidProjectFileError(s_errorBuildEventContext, ex); throw; } } diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index 28d0af55ae3..2183913a435 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -78,6 +78,16 @@ public enum ProjectInstanceSettings [DebuggerDisplay(@"{FullPath} #Targets={TargetsCount} DefaultTargets={(DefaultTargets == null) ? System.String.Empty : System.String.Join("";"", DefaultTargets.ToArray())} ToolsVersion={Toolset.ToolsVersion} InitialTargets={(InitialTargets == null) ? System.String.Empty : System.String.Join("";"", InitialTargets.ToArray())} #GlobalProperties={GlobalProperties.Count} #Properties={Properties.Count} #ItemTypes={ItemTypes.Count} #Items={Items.Count}")] public class ProjectInstance : IPropertyProvider, IItemProvider, IEvaluatorData, ITranslatable { + + /// + /// Context to log messages and events in. + /// + /// + /// This should only be used on pathways that create ProjectInstances outside of the MSBuild exe workflow - users directly loading Projects, etc. + /// In such cases we don't have a live set of nodes (yet?), but we still need contexts for message association. + /// + private static readonly BuildEventContext s_errorBuildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(0); + /// /// Targets in the project after overrides have been resolved. /// This is an unordered collection keyed by target name. @@ -313,7 +323,7 @@ private ProjectInstance(string projectFile, IDictionary globalPr Interactive = interactive }; - BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, buildParameters.NodeId); + BuildEventContext buildEventContext = s_errorBuildEventContext.WithNodeId(buildParameters.NodeId).WithSubmissionId(0); ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile, globalProperties, toolsVersion, buildParameters.ProjectRootElementCache, true /*Explicitly Loaded*/); Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version provided */, buildParameters, projectCollection.LoggingService, buildEventContext, @@ -543,14 +553,12 @@ static List GetImportFullPathsIncludingDuplicates(ObjectModelRemoting.Pr private ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) { - BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); - BuildParameters buildParameters = new BuildParameters(projectCollection) { Interactive = interactive }; - Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version specified */, buildParameters, projectCollection.LoggingService, buildEventContext, + Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version specified */, buildParameters, projectCollection.LoggingService, s_errorBuildEventContext, projectLoadSettings: projectLoadSettings, evaluationContext: evaluationContext, directoryCacheFactory: directoryCacheFactory); } @@ -619,7 +627,7 @@ internal ProjectInstance(string projectFile, ProjectInstance projectToInheritFro internal ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, int visualStudioVersionFromSolution, ProjectCollection projectCollection, ISdkResolverService sdkResolverService, int submissionId) { BuildEventContext buildEventContext = BuildEventContext.CreateInitial(0 /* submission ID */, 0 /* node ID */); - Initialize(xml, globalProperties, toolsVersion, null, visualStudioVersionFromSolution, new BuildParameters(projectCollection), projectCollection.LoggingService, buildEventContext, sdkResolverService, submissionId); + Initialize(xml, globalProperties, toolsVersion, null, visualStudioVersionFromSolution, new BuildParameters(projectCollection), projectCollection.LoggingService, s_errorBuildEventContext, sdkResolverService, submissionId); } /// @@ -633,7 +641,7 @@ internal ProjectInstance(ProjectRootElement xml, IDictionary glo /// internal ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, ILoggingService loggingService, int visualStudioVersionFromSolution, ProjectCollection projectCollection, ISdkResolverService sdkResolverService, int submissionId) { - BuildEventContext buildEventContext = BuildEventContext.CreateInitial(submissionId, 0); + BuildEventContext buildEventContext = s_errorBuildEventContext.WithSubmissionId(submissionId); Initialize(xml, globalProperties, toolsVersion, null, visualStudioVersionFromSolution, new BuildParameters(projectCollection), loggingService, buildEventContext, sdkResolverService, submissionId); } diff --git a/src/Framework/BuildEventContext.cs b/src/Framework/BuildEventContext.cs index ca72b2fe7d0..329cf53df09 100644 --- a/src/Framework/BuildEventContext.cs +++ b/src/Framework/BuildEventContext.cs @@ -87,11 +87,7 @@ internal BuildEventContext( /// The submission ID /// The node ID /// A new BuildEventContext with the specified submission and node ID - public static BuildEventContextBuilder CreateInitial(int submissionId, int nodeId) => Builder().AsInitial(submissionId, nodeId); - - internal BuildEventContextBuilder WithInstanceIdAndContextId(int projectInstanceId, int projectContextId) => Builder(this).WithInstanceIdAndContextId(projectInstanceId, projectContextId); - - internal BuildEventContextBuilder WithInstanceIdAndContextId(BuildEventContext other) => Builder(this).WithInstanceIdAndContextId(other); + public static BuildEventContextBuilder CreateInitial(int submissionId, int nodeId) => new BuildEventContextBuilder().WithSubmissionId(submissionId).WithNodeId(nodeId); /// /// Creates a new builder with the specified submission ID, preserving all other IDs. @@ -340,13 +336,6 @@ private bool InternalEquals(BuildEventContext buildEventContext) => _nodeId == b #region Builder Pattern - /// - /// Creates a new builder for constructing BuildEventContext instances efficiently. - /// The builder uses stack allocation to avoid heap allocations during construction. - /// - /// A new BuildEventContextBuilder - public static BuildEventContextBuilder Builder() => new(); - /// /// Creates a new builder initialized from an existing BuildEventContext. /// This allows for efficient copying and modification of existing contexts. @@ -486,46 +475,6 @@ public BuildEventContextBuilder WithTaskId(int taskId) return this; } - /// - /// Sets both the project instance ID and project context ID in a single operation. - /// This is a common operation when creating project-level contexts. - /// - /// The project instance ID - /// The project context ID - /// This builder instance - public BuildEventContextBuilder WithInstanceIdAndContextId(int projectInstanceId, int projectContextId) - { - _projectInstanceId = projectInstanceId; - _projectContextId = projectContextId; - return this; - } - - /// - /// Copies the project instance ID and project context ID from another BuildEventContext. - /// - /// The BuildEventContext to copy IDs from - /// This builder instance - public BuildEventContextBuilder WithInstanceIdAndContextId(BuildEventContext other) => WithInstanceIdAndContextId(other.ProjectInstanceId, other.ProjectContextId); - - /// - /// Creates an initial BuildEventContext with the specified submission and node ID. - /// All other IDs are set to invalid values. - /// - /// The submission ID - /// The node ID - /// This builder instance configured as an initial context - public BuildEventContextBuilder AsInitial(int submissionId, int nodeId) - { - _submissionId = submissionId; - _nodeId = nodeId; - _evaluationId = BuildEventContext.InvalidEvaluationId; - _projectInstanceId = BuildEventContext.InvalidProjectInstanceId; - _projectContextId = BuildEventContext.InvalidProjectContextId; - _targetId = BuildEventContext.InvalidTargetId; - _taskId = BuildEventContext.InvalidTaskId; - return this; - } - /// /// Builds the final BuildEventContext instance. /// This is the only operation that allocates memory on the heap. From 6af723fb27d597e4f3f8fcaea3c6cd397732da60 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 13:23:17 -0600 Subject: [PATCH 15/34] Another refactoring to reduce direct usage of CreateInitial and flow more contexts --- .../BackEnd/BuildRequestEngine_Tests.cs | 10 ++++++---- src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs | 4 ++-- src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs | 10 ++++++---- src/Build.UnitTests/BackEnd/LoggingService_Tests.cs | 8 +++++--- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 13 ++++++++----- src/Build.UnitTests/BackEnd/MockLoggingService.cs | 8 ++++---- src/Build.UnitTests/BackEnd/RequestBuilder_Tests.cs | 5 +---- src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs | 2 +- src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs | 2 +- .../Components/RequestBuilder/RequestBuilder.cs | 3 +-- src/Build/BackEnd/Node/InProcNode.cs | 9 ++++++++- src/Build/BackEnd/Node/OutOfProcNode.cs | 9 ++++++++- 12 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs index 3d9c91ca2c4..f8f5da044f1 100644 --- a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs @@ -335,7 +335,7 @@ public void TestEngineShutdownWhileActive() BuildRequest request = CreateNewBuildRequest(1, targets); VerifyEngineStatus(BuildRequestEngineStatus.Uninitialized, true); - _engine.InitializeForBuild(new NodeLoggingContext(_host.LoggingService, 0, false)); + _engine.InitializeForBuild(CreateForTesting(_host)); // We neeed to get the status changed AutoResetEvent returned to the non-signaled state correctly after each status change for verifying the engine status via waiting for a signal next time. // Make sure it returns back to the non-signaled state. VerifyEngineStatus(BuildRequestEngineStatus.Idle); @@ -366,7 +366,7 @@ public void TestSimpleBuildScenario() BuildRequest request = CreateNewBuildRequest(1, targets); VerifyEngineStatus(BuildRequestEngineStatus.Uninitialized, true); - _engine.InitializeForBuild(new NodeLoggingContext(_host.LoggingService, 0, false)); + _engine.InitializeForBuild(CreateForTesting(_host)); VerifyEngineStatus(BuildRequestEngineStatus.Idle); _engine.SubmitBuildRequest(request); @@ -400,7 +400,7 @@ public void TestBuildWithChildren() // Kick it off VerifyEngineStatus(BuildRequestEngineStatus.Uninitialized, true); - _engine.InitializeForBuild(new NodeLoggingContext(_host.LoggingService, 0, false)); + _engine.InitializeForBuild(CreateForTesting(_host)); VerifyEngineStatus(BuildRequestEngineStatus.Idle); _engine.SubmitBuildRequest(request); @@ -455,7 +455,7 @@ public void TestBuildWithNewConfiguration() // Kick it off VerifyEngineStatus(BuildRequestEngineStatus.Uninitialized, true); - _engine.InitializeForBuild(new NodeLoggingContext(_host.LoggingService, 0, false)); + _engine.InitializeForBuild(CreateForTesting(_host)); VerifyEngineStatus(BuildRequestEngineStatus.Idle); _engine.SubmitBuildRequest(request); @@ -501,6 +501,8 @@ public void TestShutdown() { } + private NodeLoggingContext CreateForTesting(MockHost host) => new NodeLoggingContext(host.LoggingService, BuildEventContext.Invalid.WithNodeId(0), 0, false); + private BuildRequest CreateNewBuildRequest(int configurationId, string[] targets) { BuildRequest request = new BuildRequest(1 /* submission id */, _nodeRequestId++, configurationId, targets, null, BuildEventContext.Invalid, null); diff --git a/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs b/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs index 0ae6ed74774..d3c6ff9a0d4 100644 --- a/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs +++ b/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs @@ -3958,7 +3958,7 @@ private static IntrinsicTask CreateIntrinsicTask(string content) ProjectInstance projectInstance = project.CreateProjectInstance(); ProjectTargetInstanceChild targetChild = projectInstance.Targets["t"].Children.First(); - NodeLoggingContext nodeContext = new NodeLoggingContext(new MockLoggingService(), 1, false); + NodeLoggingContext nodeContext = new NodeLoggingContext(new MockLoggingService(), BuildEventContext.Invalid.WithNodeId(1), 1, false); BuildRequestEntry entry = new BuildRequestEntry(new BuildRequest(1 /* submissionId */, 0, 1, new string[] { "t" }, null, BuildEventContext.Invalid, null), new BuildRequestConfiguration(1, new BuildRequestData("projectFile", new Dictionary(), "3.5", Array.Empty(), null), "2.0"), CreateStubTaskEnvironment()); entry.RequestConfiguration.Project = projectInstance; IntrinsicTask task = IntrinsicTask.InstantiateTask( @@ -3993,7 +3993,7 @@ internal static void AssertItemEvaluationFromTarget(string projectContents, stri var projectInstance = project.CreateProjectInstance(); var targetChild = projectInstance.Targets["t"].Children.First(); - var nodeContext = new NodeLoggingContext(new MockLoggingService(), 1, false); + var nodeContext = new NodeLoggingContext(new MockLoggingService(), BuildEventContext.Invalid.WithNodeId(1), 1, false); var entry = new BuildRequestEntry(new BuildRequest(1 /* submissionId */, 0, 1, new string[] { targetName }, null, BuildEventContext.Invalid, null), new BuildRequestConfiguration(1, new BuildRequestData("projectFile", new Dictionary(), "3.5", Array.Empty(), null), "2.0"), CreateStubTaskEnvironment()); entry.RequestConfiguration.Project = projectInstance; var task = IntrinsicTask.InstantiateTask( diff --git a/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs index 020d527ab3f..25a9ebae717 100644 --- a/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs @@ -23,13 +23,15 @@ public LoggingContext_Tests(ITestOutputHelper outputHelper) _output = outputHelper; } + private NodeLoggingContext CreateNodeLoggingContext(int nodeId, bool isInProc) => new NodeLoggingContext(new MockLoggingService(_output.WriteLine), BuildEventContext.Invalid.WithNodeId(nodeId), nodeId, isInProc); + /// /// A few simple tests for NodeLoggingContexts. /// [Fact] public void CreateValidNodeLoggingContexts() { - NodeLoggingContext context = new NodeLoggingContext(new MockLoggingService(_output.WriteLine), 1, true); + NodeLoggingContext context = CreateNodeLoggingContext(1, true); context.IsInProcNode.ShouldBeTrue(); context.IsValid.ShouldBeTrue(); @@ -38,7 +40,7 @@ public void CreateValidNodeLoggingContexts() context.BuildEventContext.NodeId.ShouldBe(1); - NodeLoggingContext context2 = new NodeLoggingContext(new MockLoggingService(_output.WriteLine), 2, false); + NodeLoggingContext context2 = CreateNodeLoggingContext(2, false); context2.IsInProcNode.ShouldBeFalse(); context2.IsValid.ShouldBeTrue(); @@ -58,14 +60,14 @@ public void InvalidNodeIdOnNodeLoggingContext() { Assert.Throws(() => { - _ = new NodeLoggingContext(new MockLoggingService(), -2, true); + _ = CreateNodeLoggingContext(-2, true); }); } [Fact] public void HasLoggedErrors() { - NodeLoggingContext context = new NodeLoggingContext(new MockLoggingService(_output.WriteLine), 1, true); + NodeLoggingContext context = CreateNodeLoggingContext(1, true); context.HasLoggedErrors.ShouldBeFalse(); context.LogCommentFromText(Framework.MessageImportance.High, "Test message"); diff --git a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs index 84213c7695d..2ea7eacfdd3 100644 --- a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs @@ -788,13 +788,15 @@ public void TreatWarningsAsErrorWhenAllSpecified(int loggerMode, int nodeId) [Fact] public void VerifyWarningsPromotedToErrorsAreCounted() { - ILoggingService ls = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); + var submissionId = 1; + var nodeId = 1; + ILoggingService ls = LoggingService.CreateLoggingService(LoggerMode.Synchronous, nodeId); ls.WarningsAsErrors = new HashSet(); ls.WarningsAsErrors.Add("FOR123"); BuildWarningEventArgs warningArgs = new("abc", "FOR123", "", 0, 0, 0, 0, "warning message", "keyword", "sender"); - warningArgs.BuildEventContext = BuildEventContext.CreateInitial(1, 2).WithEvaluationId(BuildEventContext.InvalidProjectContextId).WithProjectInstanceId(BuildEventContext.InvalidProjectContextId).WithProjectContextId(5).WithTaskId(6); + warningArgs.BuildEventContext = BuildEventContext.Invalid.WithSubmissionId(submissionId).WithNodeId(nodeId); ls.LogBuildEvent(warningArgs); - ls.HasBuildSubmissionLoggedErrors(1).ShouldBeTrue(); + ls.HasBuildSubmissionLoggedErrors(submissionId).ShouldBeTrue(); } /// diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index d9dba1b50ae..b7879550574 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -792,7 +792,7 @@ public void ProjectStartedNullBuildEventContext() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogProjectStarted(null, 1, 2, s_taskBuildEventContext, "ProjectFile", "TargetNames", null, null); + service.LogProjectStarted(null, 1, 2, s_taskBuildEventContext, "ProjectFile", "TargetNames", null, null, s_targetBuildEventContext.EvaluationId, s_taskBuildEventContext.ProjectContextId); }); } @@ -806,7 +806,7 @@ public void ProjectStartedNullParentBuildEventContext() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogProjectStarted(s_taskBuildEventContext, 1, 2, null, "ProjectFile", "TargetNames", null, null); + service.LogProjectStarted(s_taskBuildEventContext, 1, 2, null, "ProjectFile", "TargetNames", null, null, s_targetBuildEventContext.EvaluationId, s_taskBuildEventContext.ProjectContextId); }); } @@ -841,7 +841,7 @@ public void ProjectStartedEventTests(string projectFile, string targetNames) BuildRequestConfiguration config = new BuildRequestConfiguration(2, data, "4.0"); cache.AddConfiguration(config); - BuildEventContext context = service.LogProjectStarted(s_taskBuildEventContext, 1, 2, s_taskBuildEventContext, projectFile, targetNames, null, null); + BuildEventContext context = service.LogProjectStarted(s_taskBuildEventContext, 1, 2, s_taskBuildEventContext, projectFile, targetNames, null, null, s_targetBuildEventContext.EvaluationId, s_taskBuildEventContext.ProjectContextId); BuildEventContext parentBuildEventContext = s_taskBuildEventContext; VerifyProjectStartedEventArgs(service, context.ProjectContextId, message, projectFile, targetNames, parentBuildEventContext, context); @@ -857,6 +857,7 @@ public void ProjectStartedProvidedProjectContextId() const int SubmissionId = 1; const int EvaluationId = 2; const int ConfigurationId = 3; + BuildEventContext context = BuildEventContext.CreateInitial(SubmissionId, Scheduler.InProcNodeId).WithEvaluationId(EvaluationId).WithProjectInstanceId(ConfigurationId); const string ProjectFile = "SomeProjectFile"; MockHost componentHost = new MockHost(); @@ -867,7 +868,7 @@ public void ProjectStartedProvidedProjectContextId() BuildRequestConfiguration config = new BuildRequestConfiguration(ConfigurationId, data, "4.0"); cache.AddConfiguration(config); - BuildEventContext projectCacheBuildEventContext = service.CreateProjectCacheBuildEventContext(SubmissionId, EvaluationId, ConfigurationId, ProjectFile); + BuildEventContext projectCacheBuildEventContext = service.CreateProjectCacheBuildEventContext(context, ProjectFile); projectCacheBuildEventContext.NodeId.ShouldBe(Scheduler.InProcNodeId); projectCacheBuildEventContext.ProjectContextId.ShouldNotBe(BuildEventContext.InvalidProjectContextId); @@ -1429,7 +1430,9 @@ private void TestProjectFinishedEvent(string projectFile, bool success) projectFile, null, null, - null); + null, + s_taskBuildEventContext.EvaluationId, + s_taskBuildEventContext.ProjectContextId); service.LogProjectFinished(projectContext, projectFile, success); diff --git a/src/Build.UnitTests/BackEnd/MockLoggingService.cs b/src/Build.UnitTests/BackEnd/MockLoggingService.cs index 21b16ae2824..3b9a2e81dec 100644 --- a/src/Build.UnitTests/BackEnd/MockLoggingService.cs +++ b/src/Build.UnitTests/BackEnd/MockLoggingService.cs @@ -520,12 +520,12 @@ public void LogBuildCanceled() } /// - public BuildEventContext CreateEvaluationBuildEventContext(int nodeId, int submissionId) - => BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); + public BuildEventContext CreateEvaluationBuildEventContext(BuildEventContext parentBuildEventContext) + => parentBuildEventContext.WithEvaluationId(0); /// - public BuildEventContext CreateProjectCacheBuildEventContext(int submissionId, int evaluationId, int projectInstanceId, string projectFile) - => BuildEventContext.CreateInitial(0, 0).WithEvaluationId(0).WithProjectInstanceId(0).WithProjectContextId(0).WithTargetId(0).WithTaskId(0); + public BuildEventContext CreateProjectCacheBuildEventContext(BuildEventContext parentBuildEventContext, string projectFile) + => parentBuildEventContext.WithProjectContextId(0); /// public void LogProjectEvaluationStarted(BuildEventContext eventContext, string projectFile) diff --git a/src/Build.UnitTests/BackEnd/RequestBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/RequestBuilder_Tests.cs index 80e9c990ba6..610c47ce664 100644 --- a/src/Build.UnitTests/BackEnd/RequestBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/RequestBuilder_Tests.cs @@ -336,10 +336,7 @@ private void WaitForEvent(WaitHandle evt, string eventName) } } - private NodeLoggingContext GetNodeLoggingContext() - { - return new NodeLoggingContext(_host, 1, false); - } + private NodeLoggingContext GetNodeLoggingContext() => new NodeLoggingContext(_host, BuildEventContext.Invalid.WithNodeId(1), 1, false); } internal sealed class TestTargetBuilder : ITargetBuilder, IBuildComponent diff --git a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs index 005ef4da7bb..ee8037efc2d 100644 --- a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs @@ -1694,7 +1694,7 @@ private ProjectInstance CreateTestProject(string projectBodyContents, string ini /// The context private ProjectLoggingContext GetProjectLoggingContext(BuildRequestEntry entry) { - return new ProjectLoggingContext(new NodeLoggingContext(_host, 1, false), entry); + return new ProjectLoggingContext(new NodeLoggingContext(_host, BuildEventContext.Invalid.WithNodeId(1), 1, false), entry); } /// diff --git a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs index 8607e64e572..37435d35f88 100644 --- a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs @@ -1086,7 +1086,7 @@ private ProjectInstance CreateTestProject(bool returnsAttributeEnabled) /// The project logging context. private ProjectLoggingContext GetProjectLoggingContext(BuildRequestEntry entry) { - return new ProjectLoggingContext(new NodeLoggingContext(_host, 1, false), entry); + return new ProjectLoggingContext(new NodeLoggingContext(_host, BuildEventContext.Invalid.WithNodeId(1), 1, false), entry); } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index bec4cf94381..70f446c2236 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1119,8 +1119,7 @@ private async Task BuildProject() _requestEntry.RequestConfiguration.LoadProjectIntoConfiguration( _componentHost, RequestEntry.Request.BuildRequestDataFlags, - RequestEntry.Request.SubmissionId, - _nodeLoggingContext.BuildEventContext.NodeId); + _nodeLoggingContext.BuildEventContext.WithSubmissionId(RequestEntry.Request.SubmissionId)); } // Set SDK-resolved environment variables if they haven't been set yet for this configuration diff --git a/src/Build/BackEnd/Node/InProcNode.cs b/src/Build/BackEnd/Node/InProcNode.cs index b86e29247f3..a86ba2a1579 100644 --- a/src/Build/BackEnd/Node/InProcNode.cs +++ b/src/Build/BackEnd/Node/InProcNode.cs @@ -38,6 +38,11 @@ internal class InProcNode : INode, INodePacketFactory /// private string _savedCurrentDirectory; + /// + /// The build event context for this node - will usually only have the node id set. + /// + private BuildEventContext _buildEventContext; + /// /// The node logging context. /// @@ -502,8 +507,10 @@ private void HandleNodeConfiguration(NodeConfiguration configuration) ILoggingService loggingService = _componentHost.LoggingService; loggingService.OnLoggingThreadException += OnLoggingThreadException; + _buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithNodeId(configuration.NodeId); + // Now prep the buildRequestEngine for the build. - _loggingContext = new NodeLoggingContext(loggingService, configuration.NodeId, true /* inProcNode */); + _loggingContext = new NodeLoggingContext(loggingService, _buildEventContext, configuration.NodeId, true /* inProcNode */); _buildRequestEngine.OnEngineException += _engineExceptionEventHandler; _buildRequestEngine.OnNewConfigurationRequest += _newConfigurationRequestEventHandler; diff --git a/src/Build/BackEnd/Node/OutOfProcNode.cs b/src/Build/BackEnd/Node/OutOfProcNode.cs index 7225c1a9988..571491af7d8 100644 --- a/src/Build/BackEnd/Node/OutOfProcNode.cs +++ b/src/Build/BackEnd/Node/OutOfProcNode.cs @@ -65,6 +65,7 @@ public class OutOfProcNode : INode, IBuildComponentHost, INodePacketFactory, INo /// private BuildParameters _buildParameters; + /// /// The logging service. /// @@ -100,6 +101,11 @@ public class OutOfProcNode : INode, IBuildComponentHost, INodePacketFactory, INo /// private NodeConfiguration _currentConfiguration; + /// + /// The build event context for this node, will usually only have the node ID set. + /// + private BuildEventContext _buildEventContext; + /// /// The queue of packets we have received but which have not yet been processed. /// @@ -845,8 +851,9 @@ private void HandleNodeConfiguration(NodeConfiguration configuration) _loggingService.SerializeAllProperties = false; } + _buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithNodeId(configuration.NodeId); // Now prep the buildRequestEngine for the build. - _loggingContext = new NodeLoggingContext(_loggingService, configuration.NodeId, false /* inProcNode */); + _loggingContext = new NodeLoggingContext(_loggingService, _buildEventContext, configuration.NodeId, false /* inProcNode */); if (_shutdownException != null) { From f5ddbf748c157ce929125136fe7a36313404a6f2 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 14:09:21 -0600 Subject: [PATCH 16/34] Make submissions track their own build event context --- src/Build/BackEnd/BuildManager/BuildManager.cs | 5 ++--- src/Build/BackEnd/BuildManager/BuildSubmissionBase.cs | 7 +++++++ .../BackEnd/Components/ProjectCache/ProjectCacheService.cs | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 6c84550e1c7..fe99fc5a18a 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -1494,7 +1494,7 @@ internal void ExecuteSubmission( where TResultData : BuildResultBase { // For the current submission we only know the SubmissionId and that it happened on scheduler node - all other BuildEventContext dimensions are unknown now. - BuildEventContext buildEventContext = Scheduler.s_schedulerInProcNodeBuildEventContext.WithSubmissionId(submission.SubmissionId); + BuildEventContext buildEventContext = submission.BuildEventContext; BuildSubmissionStartedEventArgs submissionStartedEvent = new( submission.BuildRequestDataBase.GlobalPropertiesLookup, @@ -2597,9 +2597,8 @@ private void HandleNodeShutdown(int node, NodeShutdown shutdownPacket) ILoggingService loggingService = ((IBuildComponentHost)this).GetComponent(BuildComponentType.LoggingService); foreach (BuildSubmissionBase submission in _buildSubmissions.Values) { - BuildEventContext buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(submission.SubmissionId); string exception = ExceptionHandling.ReadAnyExceptionFromFile(_instantiationTimeUtc); - loggingService?.LogError(buildEventContext, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, ExceptionHandling.DebugDumpPath, exception); + loggingService?.LogError(submission.BuildEventContext, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, ExceptionHandling.DebugDumpPath, exception); } } else if (shutdownPacket.Reason == NodeShutdownReason.Error && _buildSubmissions.Values.Count == 0) diff --git a/src/Build/BackEnd/BuildManager/BuildSubmissionBase.cs b/src/Build/BackEnd/BuildManager/BuildSubmissionBase.cs index 8195c79aa0e..7b3921c8c04 100644 --- a/src/Build/BackEnd/BuildManager/BuildSubmissionBase.cs +++ b/src/Build/BackEnd/BuildManager/BuildSubmissionBase.cs @@ -3,6 +3,7 @@ using System; using System.Threading; +using Microsoft.Build.BackEnd; using Microsoft.Build.Shared; namespace Microsoft.Build.Execution @@ -43,6 +44,7 @@ protected internal BuildSubmissionBase(BuildManager buildManager, int submission CompletionEvent = new ManualResetEvent(false); LoggingCompleted = false; CompletionInvoked = 0; + BuildEventContext = Framework.BuildEventContext.CreateInitial(submissionId, Scheduler.VirtualNode); } /// @@ -55,6 +57,11 @@ protected internal BuildSubmissionBase(BuildManager buildManager, int submission /// public int SubmissionId { get; } + /// + /// The build event context for this submission. This will have the submission ID set, and a nodeId of the scheduler's virtual node.. + /// + public Framework.BuildEventContext BuildEventContext { get; } + /// /// The asynchronous context provided to , if any. /// diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs index a88d77d3afb..51b128919c3 100644 --- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs +++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs @@ -558,7 +558,7 @@ void EvaluateProjectIfNecessary(BuildSubmission submission, BuildRequestConfigur { if (!configuration.IsLoaded) { - BuildEventContext parentBuildContext = Scheduler.s_schedulerInProcNodeBuildEventContext.WithSubmissionId(submission.SubmissionId); + BuildEventContext parentBuildContext = submission.BuildEventContext; configuration.LoadProjectIntoConfiguration( _buildManager, submission.BuildRequestData!.Flags, From 985b26dd353fa74b5e9fe472106a3197600b82e1 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 17:52:27 -0600 Subject: [PATCH 17/34] Make it possible to send projectstarted events that represent the node the project lived on when sending cached results --- .../Components/Logging/NodeLoggingContext.cs | 2 +- .../Logging/ProjectLoggingContext.cs | 51 +++++++++++++------ .../RequestBuilder/RequestBuilder.cs | 4 +- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs index 258cc649f7e..21fe2435f25 100644 --- a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs @@ -79,7 +79,7 @@ internal ProjectLoggingContext LogProjectStarted(BuildRequestEntry requestEntry) internal ProjectLoggingContext LogProjectStarted(BuildRequest request, BuildRequestConfiguration configuration) { ErrorUtilities.VerifyThrow(this.IsValid, "Build not started."); - return new ProjectLoggingContext(this, request, configuration.ProjectFullPath, configuration.ToolsVersion, configuration.ProjectEvaluationId); + return new ProjectLoggingContext(this, request, configuration); } /// diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index d6a3525df4c..606b0d546b0 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -46,7 +46,8 @@ internal ProjectLoggingContext(NodeLoggingContext nodeLoggingContext, BuildReque requestEntry.RequestConfiguration.Project.ItemsToBuildWith, requestEntry.Request.ParentBuildEventContext, requestEntry.RequestConfiguration.ProjectEvaluationId, - requestEntry.Request.ProjectContextId) + requestEntry.Request.ProjectContextId, + requestEntry.Request.ScheduledNodeId) { } @@ -56,22 +57,22 @@ internal ProjectLoggingContext(NodeLoggingContext nodeLoggingContext, BuildReque internal ProjectLoggingContext( NodeLoggingContext nodeLoggingContext, BuildRequest request, - string projectFullPath, - string toolsVersion, - int evaluationId) + BuildRequestConfiguration configuration) : this ( nodeLoggingContext, request.SubmissionId, request.ConfigurationId, - projectFullPath, + configuration.ProjectFullPath, request.Targets, - toolsVersion, + configuration.ToolsVersion, projectProperties: null, projectItems: null, request.ParentBuildEventContext, - evaluationId, - request.ProjectContextId) + // if the project was built on a different node, the evaluation id will be a lie anyway, make that super clear + configuration.ResultsNodeId != nodeLoggingContext.BuildEventContext.NodeId ? int.MaxValue : configuration.ProjectEvaluationId, + request.ProjectContextId, + configuration.ResultsNodeId) { } @@ -93,7 +94,9 @@ public static (ProjectStartedEventArgs, ProjectLoggingContext) CreateLoggingCont requestEntry.RequestConfiguration.Project.ItemsToBuildWith, requestEntry.Request.ParentBuildEventContext, requestEntry.RequestConfiguration.ProjectEvaluationId, - requestEntry.Request.ProjectContextId); + requestEntry.Request.ProjectContextId, + // in this scenario we are on the same node, so just use the current node id + nodeLoggingContext.BuildEventContext.NodeId); return (args, new ProjectLoggingContext(nodeLoggingContext, args)); } @@ -117,6 +120,18 @@ private ProjectLoggingContext( /// /// Constructs a project logging contexts. /// + /// The node logging context for the currently executing node. + /// The submission id for this project. + /// The configuration id for this project. + /// The full path to the project file. + /// The targets being built in this project. + /// The tools version for this project. + /// The properties in the project. + /// The items in the project. + /// The parent build event context. + /// The evaluation id for this project. + /// The project context id for this project. + /// The node id hosting this project - may be different from that of the nodeLoggingContext if this project was actually started/built on another node private ProjectLoggingContext( NodeLoggingContext nodeLoggingContext, int submissionId, @@ -128,7 +143,8 @@ private ProjectLoggingContext( IItemDictionary projectItems, BuildEventContext parentBuildEventContext, int evaluationId, - int projectContextId) + int projectContextId, + int hostNodeId) : base(nodeLoggingContext, CreateInitialContext(nodeLoggingContext, submissionId, @@ -140,7 +156,8 @@ private ProjectLoggingContext( projectItems, parentBuildEventContext, evaluationId, - projectContextId)) + projectContextId, + hostNodeId)) { _projectFullPath = projectFullPath; @@ -164,7 +181,8 @@ private static BuildEventContext CreateInitialContext( IItemDictionary projectItems, BuildEventContext parentBuildEventContext, int evaluationId, - int projectContextId) + int projectContextId, + int hostNodeId) { ProjectStartedEventArgs args = CreateProjectStarted( nodeLoggingContext, @@ -177,7 +195,8 @@ private static BuildEventContext CreateInitialContext( projectItems, parentBuildEventContext, evaluationId, - projectContextId); + projectContextId, + hostNodeId); nodeLoggingContext.LoggingService.LogProjectStarted(args); @@ -195,7 +214,8 @@ private static ProjectStartedEventArgs CreateProjectStarted( IItemDictionary projectItems, BuildEventContext parentBuildEventContext, int evaluationId, - int projectContextId) + int projectContextId, + int hostNodeId) { IEnumerable properties = null; IEnumerable items = null; @@ -246,7 +266,8 @@ private static ProjectStartedEventArgs CreateProjectStarted( } return loggingService.CreateProjectStarted( - nodeLoggingContext.BuildEventContext, + // adjust the message to come from the node that actually built the project + nodeLoggingContext.BuildEventContext.WithNodeId(hostNodeId), submissionId, configurationId, parentBuildEventContext, diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 70f446c2236..7b71fef8919 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1143,9 +1143,7 @@ private async Task BuildProject() _projectLoggingContext = new ProjectLoggingContext( _nodeLoggingContext, _requestEntry.Request, - _requestEntry.RequestConfiguration.ProjectFullPath, - _requestEntry.RequestConfiguration.ToolsVersion, - _requestEntry.RequestConfiguration.ProjectEvaluationId); + _requestEntry.RequestConfiguration); throw; } From fca1a1a7cf9a575ff881d545ad4adc3f82620635 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 17 Dec 2025 20:16:48 -0600 Subject: [PATCH 18/34] Transfer evaluation ids across nodes too! --- .../BackEnd/BuildManager/BuildManager.cs | 5 ++++ .../BuildRequestEngine/BuildRequestEngine.cs | 1 + .../Logging/ProjectLoggingContext.cs | 2 +- .../RequestBuilder/RequestBuilder.cs | 10 +++++++- .../Shared/BuildRequestConfiguration.cs | 6 ++++- src/Build/BackEnd/Shared/BuildResult.cs | 24 ++++++++++++++++++- 6 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index fe99fc5a18a..3c7ca8128fe 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2537,6 +2537,11 @@ private void HandleResult(int node, BuildResult result) configuration.ProjectDefaultTargets ??= result.DefaultTargets; configuration.ProjectInitialTargets ??= result.InitialTargets; configuration.ProjectTargets ??= result.ProjectTargets; + // Update the evaluation ID if it's valid (not InvalidEvaluationId) + if (result.EvaluationId != BuildEventContext.InvalidEvaluationId) + { + configuration.ProjectEvaluationId = result.EvaluationId; + } } // Only report results to the project cache services if it's the result for a build submission. diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index 8c66af36507..69eda444542 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -852,6 +852,7 @@ private void EvaluateRequestStates() completedEntry.Result.DefaultTargets = configuration.ProjectDefaultTargets; completedEntry.Result.InitialTargets = configuration.ProjectInitialTargets; completedEntry.Result.ProjectTargets = configuration.ProjectTargets; + completedEntry.Result.EvaluationId = configuration.ProjectEvaluationId; } TraceEngine("ERS: Request is now {0}({1}) (nr {2}) has had its builder cleaned up.", completedEntry.Request.GlobalRequestId, completedEntry.Request.ConfigurationId, completedEntry.Request.NodeRequestId); diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 606b0d546b0..3149055cdae 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -70,7 +70,7 @@ internal ProjectLoggingContext( projectItems: null, request.ParentBuildEventContext, // if the project was built on a different node, the evaluation id will be a lie anyway, make that super clear - configuration.ResultsNodeId != nodeLoggingContext.BuildEventContext.NodeId ? int.MaxValue : configuration.ProjectEvaluationId, + configuration.ProjectEvaluationId, request.ProjectContextId, configuration.ResultsNodeId) { diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 7b71fef8919..10393dfa017 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -865,6 +865,8 @@ private async Task RequestThreadProc(bool setThreadParameters) { ErrorUtilities.VerifyThrow(result == null, "Result already set when exception was thrown."); result = new BuildResult(_requestEntry.Request, thrownException); + // Populate the evaluation ID from the configuration for sending to the central node + result.EvaluationId = _requestEntry.RequestConfiguration.ProjectEvaluationId; } ReportResultAndCleanUp(result); @@ -1019,7 +1021,10 @@ private BuildResult[] GetResultsForContinuation(FullyQualifiedBuildRequest[] req results = new Dictionary(); for (int i = 0; i < requests.Length; i++) { - results[i] = new BuildResult(new BuildRequest(), new BuildAbortedException()); + var result = new BuildResult(new BuildRequest(), new BuildAbortedException()); + // Populate the evaluation ID from the configuration for sending to the central node + result.EvaluationId = _requestEntry.RequestConfiguration.ProjectEvaluationId; + results[i] = result; } } @@ -1210,6 +1215,9 @@ private async Task BuildProject() BuildResult result = await _targetBuilder.BuildTargets(_projectLoggingContext, _requestEntry, this, allTargets, _requestEntry.RequestConfiguration.BaseLookup, _cancellationTokenSource.Token); + // Populate the evaluation ID from the configuration for sending to the central node + result.EvaluationId = _requestEntry.RequestConfiguration.ProjectEvaluationId; + UpdateStatisticsPostBuild(); result = _requestEntry.Request.ProxyTargets == null diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index c3afe11233d..114b6f8bb7a 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -300,7 +300,11 @@ internal BuildRequestConfiguration() /// /// A short /// - public int ProjectEvaluationId => _projectEvaluationId; + public int ProjectEvaluationId + { + get => _projectEvaluationId; + internal set => _projectEvaluationId = value; + } /// /// Flag indicating if this configuration represents a traversal project. Traversal projects diff --git a/src/Build/BackEnd/Shared/BuildResult.cs b/src/Build/BackEnd/Shared/BuildResult.cs index 87f110cb8d8..9b3656025ae 100644 --- a/src/Build/BackEnd/Shared/BuildResult.cs +++ b/src/Build/BackEnd/Shared/BuildResult.cs @@ -86,7 +86,8 @@ public class BuildResult : BuildResultBase, INodePacket, IBuildResults /// /// Allows to serialize and deserialize different versions of the build result. /// - private int _version = Traits.Instance.EscapeHatches.DoNotVersionBuildResult ? 0 : 1; + private int _version = + Traits.Instance.EscapeHatches.DoNotVersionBuildResult ? 0 : 2; /// /// The request caused a circular dependency in scheduling. @@ -100,6 +101,11 @@ public class BuildResult : BuildResultBase, INodePacket, IBuildResults /// private Exception? _requestException; + /// + /// The evaluation ID of the project used for this build. + /// + private int _evaluationId = BuildEventContext.InvalidEvaluationId; + /// /// The overall result calculated in the constructor. /// @@ -495,6 +501,17 @@ internal HashSet? ProjectTargets set => _projectTargets = value; } + /// + /// The evaluation ID of the project used for this build. + /// + internal int EvaluationId + { + [DebuggerStepThrough] + get => _evaluationId; + [DebuggerStepThrough] + set => _evaluationId = value; + } + /// /// Container used to transport errors from the scheduler (issued while computing a build result) /// to the TaskHost that has the proper logging context (project id, target id, task id, file location) @@ -695,6 +712,11 @@ void ITranslatable.Translate(ITranslator translator) { translator.TranslateEnum(ref _buildRequestDataFlags, (int)_buildRequestDataFlags); } + + if (_version >= 2) + { + translator.Translate(ref _evaluationId); + } } /// From 61b994f5f585905fbad30974a2e884c283907af7 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Thu, 18 Dec 2025 09:53:04 -0600 Subject: [PATCH 19/34] Try to make explicit pathways for cached and non-cached project start flows --- .../BackEnd/MockLoggingService.cs | 2 +- .../Components/Logging/ILoggingService.cs | 62 +++--- .../Logging/LoggingServiceLogMethods.cs | 206 +++++++++--------- .../Components/Logging/NodeLoggingContext.cs | 4 +- .../Logging/ProjectLoggingContext.cs | 5 +- src/Framework/BuildEventContext.cs | 2 +- src/Framework/ProjectStartedEventArgs.cs | 145 +++++++++++- 7 files changed, 281 insertions(+), 145 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/MockLoggingService.cs b/src/Build.UnitTests/BackEnd/MockLoggingService.cs index 3b9a2e81dec..0517eab80e4 100644 --- a/src/Build.UnitTests/BackEnd/MockLoggingService.cs +++ b/src/Build.UnitTests/BackEnd/MockLoggingService.cs @@ -566,7 +566,7 @@ public BuildEventContext LogProjectStarted( public void LogProjectStarted(ProjectStartedEventArgs args) { } - public ProjectStartedEventArgs CreateProjectStarted( + public ProjectStartedEventArgs CreateProjectStartedForLocalProject( BuildEventContext nodeBuildEventContext, int submissionId, int configurationId, diff --git a/src/Build/BackEnd/Components/Logging/ILoggingService.cs b/src/Build/BackEnd/Components/Logging/ILoggingService.cs index 2ab8dff3a8e..ef0a5df0789 100644 --- a/src/Build/BackEnd/Components/Logging/ILoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/ILoggingService.cs @@ -541,45 +541,47 @@ void LogProjectEvaluationFinished( IEnumerable items, ProfilerResult? profilerResult); + void LogProjectStarted(ProjectStartedEventArgs args); + /// - /// Log that a project has started - /// - /// The logging context of the node which is building this project. - /// The id of the build submission. - /// The id of the project configuration which is about to start - /// The build context of the parent project which asked this project to build - /// The project file path of the project about to be built - /// The entrypoint target names for this project - /// The initial properties of the project - /// The initial items of the project - /// EvaluationId of the project instance - /// The project context id - /// The BuildEventContext to use for this project. - BuildEventContext LogProjectStarted( - BuildEventContext nodeBuildEventContext, - int submissionId, - int configurationId, + /// Creates a ProjectStartedEventArgs for a locally-building project - + /// meaning one that is not served from cache and is building on the current node. + /// + /// The parent build event context for the project that is about to be built. + /// The project configuration of the project that is about to be built. + /// The project file path of the project that is about to be built. + /// The target names to be built. + /// The initial properties for the project instance, if any. + /// The initial items for the project instance, if any. + ProjectStartedEventArgs CreateProjectStartedForLocalProject( BuildEventContext parentBuildEventContext, + BuildRequestConfiguration projectConfiguration, string projectFile, string targetNames, IEnumerable properties, - IEnumerable items, - int evaluationId = BuildEventContext.InvalidEvaluationId, - int projectContextId = BuildEventContext.InvalidProjectContextId); - - void LogProjectStarted(ProjectStartedEventArgs args); - - ProjectStartedEventArgs CreateProjectStarted( - BuildEventContext nodeBuildEventContext, - int submissionId, - int configurationId, + IEnumerable items); + + /// + /// Creates a ProjectStartedEventArgs for a project that was already built on another node, so + /// is being served from cache. + /// + /// The build event context on the current node. + /// The complete evaluation build event context on the remote node. + /// The parent build event context for the project that is already built. + /// The (cached) project configuration of the project that is already built. + /// The project file path of the project that is already built. + /// The target names that were built. + /// The initial properties for the project instance, if any. + /// The initial items for the project instance, if any. + ProjectStartedEventArgs CreateProjectStartedForCachedProject( + BuildEventContext currentNodeBuildEventContext, + BuildEventContext remoteNodeEvaluationBuildEventContext, BuildEventContext parentBuildEventContext, + BuildRequestConfiguration projectConfiguration, string projectFile, string targetNames, IEnumerable properties, - IEnumerable items, - int evaluationId, - int projectContextId); + IEnumerable items); /// /// Log that the project has finished diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index c5b01993798..449b6ec9cc0 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using Microsoft.Build.BackEnd.Shared; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Experimental.BuildCheck.Infrastructure; @@ -465,132 +466,139 @@ public void LogProjectEvaluationFinished( ProcessLoggingEvent(buildEvent); } - /// - /// Logs that a project build has started - /// - /// The event context of the node which is spawning this project. - /// The id of the submission. - /// The id of the project configuration which is about to start - /// BuildEventContext of the project who is requesting "projectFile" to build - /// Project file to build - /// Target names to build - /// Initial property list - /// Initial items list - /// EvaluationId of the project instance - /// The project context id - /// The build event context for the project. - /// parentBuildEventContext is null - /// projectBuildEventContext is null - public BuildEventContext LogProjectStarted( - BuildEventContext nodeBuildEventContext, - int submissionId, - int configurationId, - BuildEventContext parentBuildEventContext, - string projectFile, - string targetNames, - IEnumerable properties, - IEnumerable items, - int evaluationId, - int projectContextId) - { - var args = CreateProjectStarted(nodeBuildEventContext, - submissionId, - configurationId, - parentBuildEventContext, - projectFile, - targetNames, - properties, - items, - evaluationId, - projectContextId); - - this.LogProjectStarted(args); - - return args.BuildEventContext; - } - public void LogProjectStarted(ProjectStartedEventArgs buildEvent) { ProcessLoggingEvent(buildEvent); } - public ProjectStartedEventArgs CreateProjectStarted( - BuildEventContext nodeBuildEventContext, - int submissionId, - int configurationId, + /// + public ProjectStartedEventArgs CreateProjectStartedForLocalProject( BuildEventContext parentBuildEventContext, + BuildRequestConfiguration projectConfiguration, string projectFile, string targetNames, IEnumerable properties, - IEnumerable items, - int evaluationId, - int projectContextId) + IEnumerable items) + { + var projectContextId = GenerateNewProjectContextId(projectFile); + + ErrorUtilities.VerifyThrow(parentBuildEventContext != null, "Need a parentBuildEventContext"); + BuildEventContext projectBuildEventContext = parentBuildEventContext + .WithProjectInstanceId(projectConfiguration.ConfigurationId) + .WithProjectContextId(projectContextId); + + // Always log GlobalProperties on ProjectStarted + // See https://github.com/dotnet/msbuild/issues/6341 for details + IDictionary globalProperties = projectConfiguration.GlobalProperties.ToDictionary(); + + var buildEvent = new ProjectStartedEventArgs( + projectConfiguration.ConfigurationId, + message: null, + helpKeyword: null, + projectFile, + targetNames, + properties, + items, + parentBuildEventContext, + globalProperties, + projectConfiguration.ToolsVersion); + buildEvent.BuildEventContext = projectBuildEventContext; + + return buildEvent; + } + + /// + /// Ensures that the projectContextId is valid and updates the project file map to track which contexts apply to which project files. + /// + /// The project file path to be associated with this project context ID. + /// The project context ID to use (either the provided one or a newly generated one). + private int GenerateNewProjectContextId(string projectFile) { - ErrorUtilities.VerifyThrow(nodeBuildEventContext != null, "Need a nodeBuildEventContext"); + var generatedProjectContextId = NextProjectId; - if (projectContextId == BuildEventContext.InvalidProjectContextId) + // PERF: Not using VerifyThrow to avoid boxing of projectBuildEventContext.ProjectContextId in the non-error case. + if (_projectFileMap.ContainsKey(generatedProjectContextId)) { - projectContextId = NextProjectId; + ErrorUtilities.ThrowInternalError("ContextID {0} for project {1} should not already be in the ID-to-file mapping!", generatedProjectContextId, projectFile); + } - // PERF: Not using VerifyThrow to avoid boxing of projectBuildEventContext.ProjectContextId in the non-error case. - if (_projectFileMap.ContainsKey(projectContextId)) + _projectFileMap[generatedProjectContextId] = projectFile; + return generatedProjectContextId; + } + + /// + /// Validates that a preexisting projectContextId is valid and updates the project file map to track which contexts apply to which project files. + /// + /// The preexisting project context ID to validate. + /// The project file path to be associated with this project context ID. + /// The node ID of the current node - used to validate that only the in-proc scheduler node is re-using cached project context IDs. + private void ValidatePreexistingProjectContextId(int projectContextId, string projectFile, int currentNodeId) + { + // A projectContextId was provided, so use it with some sanity checks + if (_projectFileMap.TryGetValue(projectContextId, out string existingProjectFile)) + { + if (!projectFile.Equals(existingProjectFile, StringComparison.OrdinalIgnoreCase)) { - ErrorUtilities.ThrowInternalError("ContextID {0} for project {1} should not already be in the ID-to-file mapping!", projectContextId, projectFile); + ErrorUtilities.ThrowInternalError("ContextID {0} was already in the ID-to-project file mapping but the project file {1} did not match the provided one {2}!", projectContextId, existingProjectFile, projectFile); } - - _projectFileMap[projectContextId] = projectFile; } else { - // A projectContextId was provided, so use it with some sanity checks - if (_projectFileMap.TryGetValue(projectContextId, out string existingProjectFile)) + // Currently, an existing projectContextId can only be provided in the project cache scenario, which runs on the in-proc node. + // If there was a cache miss and the build was scheduled on a worker node, it may not have seen this projectContextId yet. + // So we only need this sanity check for the in-proc node. + if (currentNodeId == Scheduler.InProcNodeId) { - if (!projectFile.Equals(existingProjectFile, StringComparison.OrdinalIgnoreCase)) - { - ErrorUtilities.ThrowInternalError("ContextID {0} was already in the ID-to-project file mapping but the project file {1} did not match the provided one {2}!", projectContextId, existingProjectFile, projectFile); - } + ErrorUtilities.ThrowInternalError("ContextID {0} should have been in the ID-to-project file mapping but wasn't!", projectContextId); } - else - { - // Currently, an existing projectContextId can only be provided in the project cache scenario, which runs on the in-proc node. - // If there was a cache miss and the build was scheduled on a worker node, it may not have seen this projectContextId yet. - // So we only need this sanity check for the in-proc node. - if (nodeBuildEventContext.NodeId == Scheduler.InProcNodeId) - { - ErrorUtilities.ThrowInternalError("ContextID {0} should have been in the ID-to-project file mapping but wasn't!", projectContextId); - } - - _projectFileMap[projectContextId] = projectFile; - } - } - BuildEventContext projectBuildEventContext = nodeBuildEventContext - .WithSubmissionId(submissionId) - .WithEvaluationId(evaluationId) - .WithProjectInstanceId(configurationId) - .WithProjectContextId(projectContextId); + _projectFileMap[projectContextId] = projectFile; + } + } - ErrorUtilities.VerifyThrow(parentBuildEventContext != null, "Need a parentBuildEventContext"); + /// + public ProjectStartedEventArgs CreateProjectStartedForCachedProject( + BuildEventContext currentNodeBuildEventContext, + BuildEventContext remoteNodeEvaluationBuildEventContext, + BuildEventContext parentBuildEventContext, + BuildRequestConfiguration projectConfiguration, + string projectFile, + string targetNames, + IEnumerable properties, + IEnumerable items) + { + Debug.Assert(currentNodeBuildEventContext.NodeId == Scheduler.InProcNodeId, "Cached project build events should only be logged on the in-proc scheduler node."); + + ValidatePreexistingProjectContextId( + remoteNodeEvaluationBuildEventContext.ProjectContextId, + projectFile, + currentNodeBuildEventContext.NodeId); - ErrorUtilities.VerifyThrow(_configCache.Value.HasConfiguration(configurationId), "Cannot find the project configuration while injecting non-serialized data from out-of-proc node."); - var buildRequestConfiguration = _configCache.Value[configurationId]; + int configurationId = remoteNodeEvaluationBuildEventContext.ProjectInstanceId; // Always log GlobalProperties on ProjectStarted // See https://github.com/dotnet/msbuild/issues/6341 for details - IDictionary globalProperties = buildRequestConfiguration.GlobalProperties.ToDictionary(); + IDictionary globalProperties = projectConfiguration.GlobalProperties.ToDictionary(); - var buildEvent = new ProjectStartedEventArgs( - configurationId, - message: null, - helpKeyword: null, - projectFile, - targetNames, - properties, - items, - parentBuildEventContext, - globalProperties, - buildRequestConfiguration.ToolsVersion); - buildEvent.BuildEventContext = projectBuildEventContext; + BuildEventContextBuilder thisEventBuildEventContext = parentBuildEventContext + .WithProjectInstanceId(configurationId) + .WithProjectContextId(remoteNodeEvaluationBuildEventContext.ProjectContextId); + + ProjectStartedEventArgs buildEvent = new( + projectId: projectConfiguration.ConfigurationId, + message: null, + helpKeyword: null, + projectFile: projectFile, + targetNames: targetNames, + properties: properties, + items: items, + parentBuildEventContext: parentBuildEventContext, + globalProperties: globalProperties, + toolsVersion: projectConfiguration.ToolsVersion, + originalBuildEventContext: remoteNodeEvaluationBuildEventContext) + { + BuildEventContext = thisEventBuildEventContext, + }; return buildEvent; } diff --git a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs index 21fe2435f25..23df592bc36 100644 --- a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs @@ -76,7 +76,7 @@ internal ProjectLoggingContext LogProjectStarted(BuildRequestEntry requestEntry) /// The build request. /// The configuration used to build the request. /// The BuildEventContext to use for this project. - internal ProjectLoggingContext LogProjectStarted(BuildRequest request, BuildRequestConfiguration configuration) + internal ProjectLoggingContext LogProjectStartedFromCache(BuildRequest request, BuildRequestConfiguration configuration) { ErrorUtilities.VerifyThrow(this.IsValid, "Build not started."); return new ProjectLoggingContext(this, request, configuration); @@ -88,7 +88,7 @@ internal ProjectLoggingContext LogProjectStarted(BuildRequest request, BuildRequ /// internal void LogRequestHandledFromCache(BuildRequest request, BuildRequestConfiguration configuration, BuildResult result) { - ProjectLoggingContext projectLoggingContext = LogProjectStarted(request, configuration); + ProjectLoggingContext projectLoggingContext = LogProjectStartedFromCache(request, configuration); // When pulling a request from the cache, we want to make sure we log a target skipped event for any targets which // were used to build the request including default and initial targets. diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 3149055cdae..87368b8b74d 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -69,7 +69,6 @@ internal ProjectLoggingContext( projectProperties: null, projectItems: null, request.ParentBuildEventContext, - // if the project was built on a different node, the evaluation id will be a lie anyway, make that super clear configuration.ProjectEvaluationId, request.ProjectContextId, configuration.ResultsNodeId) @@ -265,9 +264,9 @@ private static ProjectStartedEventArgs CreateProjectStarted( properties = projectPropertiesToSerialize.Select((ProjectPropertyInstance property) => new DictionaryEntry(property.Name, property.EvaluatedValue)); } - return loggingService.CreateProjectStarted( + return loggingService.CreateProjectStartedForLocalProject( // adjust the message to come from the node that actually built the project - nodeLoggingContext.BuildEventContext.WithNodeId(hostNodeId), + nodeLoggingContext.BuildEventContext, submissionId, configurationId, parentBuildEventContext, diff --git a/src/Framework/BuildEventContext.cs b/src/Framework/BuildEventContext.cs index 329cf53df09..05260665f79 100644 --- a/src/Framework/BuildEventContext.cs +++ b/src/Framework/BuildEventContext.cs @@ -186,7 +186,7 @@ internal BuildEventContext( public int TaskId => _taskId; /// - /// Retrieves the project instance id. + /// Retrieves the project instance id, AKA the Configuration Id (AKA BuildRequestConfiguration.Id) /// public int ProjectInstanceId => _projectInstanceId; diff --git a/src/Framework/ProjectStartedEventArgs.cs b/src/Framework/ProjectStartedEventArgs.cs index 9a854637d4c..3b9ac663c0d 100644 --- a/src/Framework/ProjectStartedEventArgs.cs +++ b/src/Framework/ProjectStartedEventArgs.cs @@ -118,6 +118,38 @@ public ProjectStartedEventArgs( this.ToolsVersion = toolsVersion; } + /// + /// This constructor allows event data to be initialized including the original build event context. + /// Sender is assumed to be "MSBuild". + /// + /// project id + /// text message + /// help keyword + /// project name + /// targets we are going to build (empty indicates default targets) + /// list of properties + /// list of items + /// event context info for the parent project + /// An containing global properties. + /// The tools version. + /// original build event context from the remote node. This should contain node, submission, evaluation and projectInstance Ids at minimum. + public ProjectStartedEventArgs( + int projectId, + string message, + string helpKeyword, + string? projectFile, + string? targetNames, + IEnumerable? properties, + IEnumerable? items, + BuildEventContext? parentBuildEventContext, + IDictionary? globalProperties, + string? toolsVersion, + BuildEventContext? originalBuildEventContext) + : this(projectId, message, helpKeyword, projectFile, targetNames, properties, items, parentBuildEventContext, globalProperties, toolsVersion) + { + this.originalBuildEventContext = originalBuildEventContext; + } + /// /// This constructor allows event data to be initialized. Also the timestamp can be set /// Sender is assumed to be "MSBuild". @@ -140,7 +172,7 @@ public ProjectStartedEventArgs( : base(message, helpKeyword, "MSBuild", eventTimestamp) { this.projectFile = projectFile; - this.targetNames = targetNames ?? String.Empty; + this.targetNames = targetNames ?? string.Empty; this.properties = properties; this.items = items; } @@ -174,6 +206,36 @@ public ProjectStartedEventArgs( this.projectId = projectId; } + /// + /// This constructor allows event data to be initialized including the original build event context. + /// Sender is assumed to be "MSBuild". + /// + /// project id + /// text message + /// help keyword + /// project name + /// targets we are going to build (empty indicates default targets) + /// list of properties + /// list of items + /// event context info for the parent project + /// original build event context from the remote node + /// The of the event. + public ProjectStartedEventArgs( + int projectId, + string message, + string helpKeyword, + string? projectFile, + string? targetNames, + IEnumerable? properties, + IEnumerable? items, + BuildEventContext? parentBuildEventContext, + BuildEventContext? originalBuildEventContext, + DateTime eventTimestamp) + : this(projectId, message, helpKeyword, projectFile, targetNames, properties, items, parentBuildEventContext, eventTimestamp) + { + this.originalBuildEventContext = originalBuildEventContext; + } + // ProjectId is only contained in the project started event. // This number indicated the instance id of the project and can be // used when debugging to determine if two projects with the same name @@ -206,6 +268,22 @@ public BuildEventContext? ParentProjectBuildEventContext } } + [OptionalField(VersionAdded = 3)] + private BuildEventContext? originalBuildEventContext; + + /// + /// The (possibly null) from the original project build. + /// This contains the full context data from when the project was first built on the original node, + /// and should be used for evaluation ID tracking and build correlation in distributed scenarios. + /// + public BuildEventContext? OriginalBuildEventContext + { + get + { + return originalBuildEventContext; + } + } + /// /// The name of the project file /// @@ -351,7 +429,7 @@ public IEnumerable? Items internal override void WriteToStream(BinaryWriter writer) { base.WriteToStream(writer); - writer.Write((Int32)projectId); + writer.Write(projectId); if (parentProjectBuildEventContext == null) { @@ -360,15 +438,15 @@ internal override void WriteToStream(BinaryWriter writer) else { writer.Write((byte)1); - writer.Write((Int32)parentProjectBuildEventContext.NodeId); - writer.Write((Int32)parentProjectBuildEventContext.ProjectContextId); - writer.Write((Int32)parentProjectBuildEventContext.TargetId); - writer.Write((Int32)parentProjectBuildEventContext.TaskId); + writer.Write(parentProjectBuildEventContext.NodeId); + writer.Write(parentProjectBuildEventContext.ProjectContextId); + writer.Write(parentProjectBuildEventContext.TargetId); + writer.Write(parentProjectBuildEventContext.TaskId); // added these in version 20 - writer.Write((Int32)parentProjectBuildEventContext.SubmissionId); - writer.Write((Int32)parentProjectBuildEventContext.ProjectInstanceId); + writer.Write(parentProjectBuildEventContext.SubmissionId); + writer.Write(parentProjectBuildEventContext.ProjectInstanceId); // added this in version 36 - writer.Write((Int32)parentProjectBuildEventContext.EvaluationId); + writer.Write(parentProjectBuildEventContext.EvaluationId); } writer.WriteOptionalString(projectFile); @@ -405,6 +483,23 @@ internal override void WriteToStream(BinaryWriter writer) WriteCollection(writer, WarningsAsErrors); WriteCollection(writer, WarningsNotAsErrors); WriteCollection(writer, WarningsAsMessages); + + // Write OriginalBuildEventContext (version 3+ field) + if (originalBuildEventContext == null) + { + writer.Write((byte)0); + } + else + { + writer.Write((byte)1); + writer.Write(originalBuildEventContext.SubmissionId); + writer.Write(originalBuildEventContext.NodeId); + writer.Write(originalBuildEventContext.EvaluationId); + writer.Write(originalBuildEventContext.ProjectInstanceId); + writer.Write(originalBuildEventContext.ProjectContextId); + writer.Write(originalBuildEventContext.TargetId); + writer.Write(originalBuildEventContext.TaskId); + } } /// @@ -489,6 +584,32 @@ internal override void CreateFromStream(BinaryReader reader, int version) WarningsAsErrors = ReadStringSet(reader); WarningsNotAsErrors = ReadStringSet(reader); WarningsAsMessages = ReadStringSet(reader); + + // Read OriginalBuildEventContext (version 3+ field) + if (version >= 3) + { + if (reader.ReadByte() == 0) + { + originalBuildEventContext = null; + } + else + { + int submissionId = reader.ReadInt32(); + int nodeId = reader.ReadInt32(); + int evaluationId = reader.ReadInt32(); + int projectInstanceId = reader.ReadInt32(); + int projectContextId = reader.ReadInt32(); + int targetId = reader.ReadInt32(); + int taskId = reader.ReadInt32(); + + originalBuildEventContext = BuildEventContext.CreateInitial(submissionId, nodeId) + .WithEvaluationId(evaluationId) + .WithProjectInstanceId(projectInstanceId) + .WithProjectContextId(projectContextId) + .WithTargetId(targetId) + .WithTaskId(taskId); + } + } } private static void WriteCollection(BinaryWriter writer, ICollection? collection) @@ -537,6 +658,7 @@ private void SetDefaultsBeforeSerialization(StreamingContext sc) // Don't want to set the default before deserialization is completed to a new event context because // that would most likely be a lot of wasted allocations parentProjectBuildEventContext = null; + originalBuildEventContext = null; } [OnDeserialized] @@ -546,6 +668,11 @@ private void SetDefaultsAfterSerialization(StreamingContext sc) { parentProjectBuildEventContext = BuildEventContext.Invalid; } + + if (originalBuildEventContext == null) + { + originalBuildEventContext = BuildEventContext.Invalid; + } } #endregion From 866f60b58fb0a0d23f25271cd17b59201aeed639 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Thu, 18 Dec 2025 12:16:47 -0600 Subject: [PATCH 20/34] update callsites for use-case-specific constructors --- .../BackEnd/LoggingService_Tests.cs | 4 +- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 95 +---- .../BackEnd/MockLoggingService.cs | 72 ++-- .../BackEnd/TargetBuilder_Tests.cs | 4 +- .../BackEnd/TargetEntry_Tests.cs | 4 +- .../Components/Logging/ILoggingService.cs | 37 +- .../Logging/LoggingServiceLogMethods.cs | 35 +- .../Components/Logging/NodeLoggingContext.cs | 9 +- .../Logging/ProjectLoggingContext.cs | 331 +++++++----------- .../RequestBuilder/RequestBuilder.cs | 8 +- 10 files changed, 239 insertions(+), 360 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs index 2ea7eacfdd3..dc7d9baedec 100644 --- a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs @@ -974,7 +974,9 @@ private MockLogger GetLoggedEventsWithWarningsAsErrorsOrMessages( loggingService.RegisterLogger(logger); - BuildEventContext projectStarted = loggingService.LogProjectStarted(buildEventContext, 0, buildEventContext.ProjectInstanceId, BuildEventContext.Invalid, "projectFile", "Build", Enumerable.Empty(), Enumerable.Empty()); + var projectStartedArgs = loggingService.CreateProjectStartedForLocalProject(BuildEventContext.Invalid, buildEventContext.ProjectInstanceId, "projectFile", "Build", null, Enumerable.Empty(), Enumerable.Empty(), null); + loggingService.LogProjectStarted(projectStartedArgs); + BuildEventContext projectStarted = projectStartedArgs.BuildEventContext; if (warningsAsErrorsForProject != null) { diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index b7879550574..86e54ff620f 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -792,7 +792,9 @@ public void ProjectStartedNullBuildEventContext() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogProjectStarted(null, 1, 2, s_taskBuildEventContext, "ProjectFile", "TargetNames", null, null, s_targetBuildEventContext.EvaluationId, s_taskBuildEventContext.ProjectContextId); + var args = service.CreateProjectStartedForLocalProject(s_taskBuildEventContext, 2, "ProjectFile", "TargetNames", null, null, null, null); + args.BuildEventContext = null; // Force null context for error test + service.LogProjectStarted(args); }); } @@ -806,7 +808,8 @@ public void ProjectStartedNullParentBuildEventContext() Assert.Throws(() => { ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - service.LogProjectStarted(s_taskBuildEventContext, 1, 2, null, "ProjectFile", "TargetNames", null, null, s_targetBuildEventContext.EvaluationId, s_taskBuildEventContext.ProjectContextId); + var args = service.CreateProjectStartedForLocalProject(null, 2, "ProjectFile", "TargetNames", null, null, null, null); + service.LogProjectStarted(args); }); } @@ -841,7 +844,9 @@ public void ProjectStartedEventTests(string projectFile, string targetNames) BuildRequestConfiguration config = new BuildRequestConfiguration(2, data, "4.0"); cache.AddConfiguration(config); - BuildEventContext context = service.LogProjectStarted(s_taskBuildEventContext, 1, 2, s_taskBuildEventContext, projectFile, targetNames, null, null, s_targetBuildEventContext.EvaluationId, s_taskBuildEventContext.ProjectContextId); + var args = service.CreateProjectStartedForLocalProject(s_taskBuildEventContext, 2, projectFile, targetNames, null, null, null, null); + service.LogProjectStarted(args); + BuildEventContext context = args.BuildEventContext; BuildEventContext parentBuildEventContext = s_taskBuildEventContext; VerifyProjectStartedEventArgs(service, context.ProjectContextId, message, projectFile, targetNames, parentBuildEventContext, context); @@ -873,17 +878,10 @@ public void ProjectStartedProvidedProjectContextId() projectCacheBuildEventContext.ProjectContextId.ShouldNotBe(BuildEventContext.InvalidProjectContextId); BuildEventContext nodeBuildEventContext = BuildEventContext.CreateInitial(0, Scheduler.InProcNodeId); - BuildEventContext projectStartedBuildEventContext = service.LogProjectStarted( - nodeBuildEventContext, - submissionId: SubmissionId, - configurationId: ConfigurationId, - parentBuildEventContext: BuildEventContext.Invalid, - projectFile: ProjectFile, - targetNames: "TargetNames", - properties: null, - items: null, - evaluationId: EvaluationId, - projectContextId: projectCacheBuildEventContext.ProjectContextId); + var args = service.CreateProjectStartedForLocalProject(BuildEventContext.Invalid, ConfigurationId, ProjectFile, "TargetNames", null, null, null, null); + args.BuildEventContext = args.BuildEventContext.WithProjectContextId(projectCacheBuildEventContext.ProjectContextId); // Use provided project context ID + service.LogProjectStarted(args); + BuildEventContext projectStartedBuildEventContext = args.BuildEventContext; projectStartedBuildEventContext.ProjectContextId.ShouldBe(projectCacheBuildEventContext.ProjectContextId); } @@ -907,62 +905,15 @@ public void ProjectStartedProvidedUnknownProjectContextIdInProcNode() BuildRequestConfiguration config = new BuildRequestConfiguration(ConfigurationId, data, "4.0"); cache.AddConfiguration(config); - BuildEventContext nodeBuildEventContext = BuildEventContext.CreateInitial(0, Scheduler.InProcNodeId); + BuildEventContext parentBuildEventContext = BuildEventContext.CreateInitial(SubmissionId, Scheduler.InProcNodeId).WithEvaluationId(EvaluationId); + BuildEventContext remoteBuildEventContextWithKnownBadContextOnCurrentNode = parentBuildEventContext.WithProjectContextId(ProjectContextId); Assert.Throws(() => { - service.LogProjectStarted( - nodeBuildEventContext, - submissionId: SubmissionId, - configurationId: ConfigurationId, - parentBuildEventContext: BuildEventContext.Invalid, - projectFile: ProjectFile, - targetNames: "TargetNames", - properties: null, - items: null, - evaluationId: EvaluationId, - projectContextId: ProjectContextId); + var args = service.CreateProjectStartedForCachedProject(parentBuildEventContext, remoteBuildEventContextWithKnownBadContextOnCurrentNode, BuildEventContext.Invalid, null, ProjectFile, "TargetNames", null); + service.LogProjectStarted(args); }); } - /// - /// Expect an unknown project context id to be accepted on an out-of-proc node. - /// - [Fact] - public void ProjectStartedProvidedUnknownProjectContextIdOutOfProcNode() - { - const int SubmissionId = 1; - const int EvaluationId = 2; - const int ConfigurationId = 3; - const string ProjectFile = "SomeProjectFile"; - const int NodeId = 2; - const int ProjectContextId = 123; - - // Ensure we didn't pick the one bad const value - NodeId.ShouldNotBe(Scheduler.InProcNodeId); - - MockHost componentHost = new MockHost(); - ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1, componentHost); - ConfigCache cache = (ConfigCache)componentHost.GetComponent(BuildComponentType.ConfigCache); - - BuildRequestData data = new BuildRequestData(ProjectFile, new Dictionary(StringComparer.OrdinalIgnoreCase), "toolsVersion", Array.Empty(), null); - BuildRequestConfiguration config = new BuildRequestConfiguration(ConfigurationId, data, "4.0"); - cache.AddConfiguration(config); - - BuildEventContext nodeBuildEventContext = BuildEventContext.CreateInitial(0, NodeId); - BuildEventContext projectStartedBuildEventContext = service.LogProjectStarted( - nodeBuildEventContext, - submissionId: SubmissionId, - configurationId: ConfigurationId, - parentBuildEventContext: BuildEventContext.Invalid, - projectFile: ProjectFile, - targetNames: "TargetNames", - properties: null, - items: null, - evaluationId: EvaluationId, - projectContextId: ProjectContextId); - projectStartedBuildEventContext.ProjectContextId.ShouldBe(ProjectContextId); - } - #endregion #region ProjectFinished @@ -1422,17 +1373,9 @@ private void TestProjectFinishedEvent(string projectFile, bool success) cache.AddConfiguration(config); // Now do it the right way -- with a matching ProjectStarted. - BuildEventContext projectContext = service.LogProjectStarted( - BuildEventContext.CreateInitial(0, 1), - 1, - 2, - s_taskBuildEventContext, - projectFile, - null, - null, - null, - s_taskBuildEventContext.EvaluationId, - s_taskBuildEventContext.ProjectContextId); + var projectStartedArgs = service.CreateProjectStartedForLocalProject(s_taskBuildEventContext, 2, projectFile, null, null, null, null, null); + service.LogProjectStarted(projectStartedArgs); + BuildEventContext projectContext = projectStartedArgs.BuildEventContext; service.LogProjectFinished(projectContext, projectFile, success); diff --git a/src/Build.UnitTests/BackEnd/MockLoggingService.cs b/src/Build.UnitTests/BackEnd/MockLoggingService.cs index 0517eab80e4..4e63237b4f1 100644 --- a/src/Build.UnitTests/BackEnd/MockLoggingService.cs +++ b/src/Build.UnitTests/BackEnd/MockLoggingService.cs @@ -545,40 +545,27 @@ public void LogProjectEvaluationFinished( { } - /// - /// Logs a project started event - /// - public BuildEventContext LogProjectStarted( - BuildEventContext nodeBuildEventContext, - int submissionId, - int configurationId, - BuildEventContext parentBuildEventContext, - string projectFile, - string targetNames, - IEnumerable properties, - IEnumerable items, - int evaluationId = BuildEventContext.InvalidEvaluationId, - int projectContextId = BuildEventContext.InvalidProjectContextId) - { - return BuildEventContext.CreateInitial(0, 0); - } - public void LogProjectStarted(ProjectStartedEventArgs args) { } public ProjectStartedEventArgs CreateProjectStartedForLocalProject( - BuildEventContext nodeBuildEventContext, - int submissionId, - int configurationId, BuildEventContext parentBuildEventContext, + int configurationId, string projectFile, string targetNames, + IDictionary globalProperties, IEnumerable properties, IEnumerable items, - int evaluationId = BuildEventContext.InvalidEvaluationId, - int projectContextId = BuildEventContext.InvalidProjectContextId) + string toolsVersion) { - return new ProjectStartedEventArgs( + // Create a mock project context ID for testing + int projectContextId = configurationId; + + BuildEventContext projectBuildEventContext = parentBuildEventContext + .WithProjectInstanceId(configurationId) + .WithProjectContextId(projectContextId); + + var buildEvent = new ProjectStartedEventArgs( configurationId, message: null, helpKeyword: null, @@ -586,7 +573,42 @@ public ProjectStartedEventArgs CreateProjectStartedForLocalProject( targetNames, properties, items, - parentBuildEventContext); + parentBuildEventContext, + globalProperties, + toolsVersion); + + buildEvent.BuildEventContext = projectBuildEventContext; + return buildEvent; + } + + public ProjectStartedEventArgs CreateProjectStartedForCachedProject( + BuildEventContext currentNodeBuildEventContext, + BuildEventContext remoteNodeEvaluationBuildEventContext, + BuildEventContext parentBuildEventContext, + IDictionary globalProperties, + string projectFile, + string targetNames, + string toolsVersion) + { + BuildEventContext projectBuildEventContext = parentBuildEventContext + .WithProjectInstanceId(remoteNodeEvaluationBuildEventContext.ProjectInstanceId) + .WithProjectContextId(remoteNodeEvaluationBuildEventContext.ProjectContextId); + + var buildEvent = new ProjectStartedEventArgs( + remoteNodeEvaluationBuildEventContext.ProjectInstanceId, + message: null, + helpKeyword: null, + projectFile, + targetNames, + null, // No properties for cache scenarios in mock + null, // No items for cache scenarios in mock + parentBuildEventContext, + globalProperties, + toolsVersion, + remoteNodeEvaluationBuildEventContext); // Pass original context + + buildEvent.BuildEventContext = projectBuildEventContext; + return buildEvent; } /// diff --git a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs index ee8037efc2d..1f9085cf7f3 100644 --- a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs @@ -1694,7 +1694,9 @@ private ProjectInstance CreateTestProject(string projectBodyContents, string ini /// The context private ProjectLoggingContext GetProjectLoggingContext(BuildRequestEntry entry) { - return new ProjectLoggingContext(new NodeLoggingContext(_host, BuildEventContext.Invalid.WithNodeId(1), 1, false), entry); + var nodeContext = new NodeLoggingContext(_host, BuildEventContext.Invalid.WithNodeId(1), 1, false); + var (_, context) = ProjectLoggingContext.CreateForLocalBuild(nodeContext, entry); + return context; } /// diff --git a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs index 37435d35f88..c5174969040 100644 --- a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs @@ -1086,7 +1086,9 @@ private ProjectInstance CreateTestProject(bool returnsAttributeEnabled) /// The project logging context. private ProjectLoggingContext GetProjectLoggingContext(BuildRequestEntry entry) { - return new ProjectLoggingContext(new NodeLoggingContext(_host, BuildEventContext.Invalid.WithNodeId(1), 1, false), entry); + var nodeContext = new NodeLoggingContext(_host, BuildEventContext.Invalid.WithNodeId(1), 1, false); + var (_, context) = ProjectLoggingContext.CreateForLocalBuild(nodeContext, entry); + return context; } /// diff --git a/src/Build/BackEnd/Components/Logging/ILoggingService.cs b/src/Build/BackEnd/Components/Logging/ILoggingService.cs index ef0a5df0789..5fde79fd7af 100644 --- a/src/Build/BackEnd/Components/Logging/ILoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/ILoggingService.cs @@ -547,41 +547,52 @@ void LogProjectEvaluationFinished( /// Creates a ProjectStartedEventArgs for a locally-building project - /// meaning one that is not served from cache and is building on the current node. /// - /// The parent build event context for the project that is about to be built. - /// The project configuration of the project that is about to be built. - /// The project file path of the project that is about to be built. - /// The target names to be built. + /// The parent build event context for the project that is starting. + /// The configuration ID of the project that is starting. + /// The project file path of the project that is starting. + /// The target names that are being built. + /// The global properties for the project instance. /// The initial properties for the project instance, if any. /// The initial items for the project instance, if any. + /// The tools version for the project instance. + /// + /// We _could_ pass in BuildRequest/BuildRequestConfiguration for most of this data, but that makes layering/dependency + /// tracking of the namespaces more complex, and we don't really need the full objects here. + /// ProjectStartedEventArgs CreateProjectStartedForLocalProject( BuildEventContext parentBuildEventContext, - BuildRequestConfiguration projectConfiguration, + int configurationId, string projectFile, string targetNames, + IDictionary globalProperties, IEnumerable properties, - IEnumerable items); + IEnumerable items, + string toolsVersion); /// /// Creates a ProjectStartedEventArgs for a project that was already built on another node, so - /// is being served from cache. + /// is being served from cache. Unlike the local-build case, we don't have properties/items + /// because they were not deserialized from the cache. /// /// The build event context on the current node. /// The complete evaluation build event context on the remote node. /// The parent build event context for the project that is already built. - /// The (cached) project configuration of the project that is already built. + /// The global properties for the project instance, from the configuration. /// The project file path of the project that is already built. /// The target names that were built. - /// The initial properties for the project instance, if any. - /// The initial items for the project instance, if any. + /// The tools version for the project instance. + /// + /// We _could_ pass in BuildRequest/BuildRequestConfiguration for most of this data, but that makes layering/dependency + /// tracking of the namespaces more complex, and we don't really need the full objects here. + /// ProjectStartedEventArgs CreateProjectStartedForCachedProject( BuildEventContext currentNodeBuildEventContext, BuildEventContext remoteNodeEvaluationBuildEventContext, BuildEventContext parentBuildEventContext, - BuildRequestConfiguration projectConfiguration, + IDictionary globalProperties, string projectFile, string targetNames, - IEnumerable properties, - IEnumerable items); + string toolsVersion); /// /// Log that the project has finished diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 449b6ec9cc0..6c8df75cefc 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -474,25 +474,23 @@ public void LogProjectStarted(ProjectStartedEventArgs buildEvent) /// public ProjectStartedEventArgs CreateProjectStartedForLocalProject( BuildEventContext parentBuildEventContext, - BuildRequestConfiguration projectConfiguration, + int configurationId, string projectFile, string targetNames, + IDictionary globalProperties, IEnumerable properties, - IEnumerable items) + IEnumerable items, + string toolsVersion) { var projectContextId = GenerateNewProjectContextId(projectFile); ErrorUtilities.VerifyThrow(parentBuildEventContext != null, "Need a parentBuildEventContext"); BuildEventContext projectBuildEventContext = parentBuildEventContext - .WithProjectInstanceId(projectConfiguration.ConfigurationId) + .WithProjectInstanceId(configurationId) .WithProjectContextId(projectContextId); - // Always log GlobalProperties on ProjectStarted - // See https://github.com/dotnet/msbuild/issues/6341 for details - IDictionary globalProperties = projectConfiguration.GlobalProperties.ToDictionary(); - var buildEvent = new ProjectStartedEventArgs( - projectConfiguration.ConfigurationId, + configurationId, message: null, helpKeyword: null, projectFile, @@ -501,7 +499,7 @@ public ProjectStartedEventArgs CreateProjectStartedForLocalProject( items, parentBuildEventContext, globalProperties, - projectConfiguration.ToolsVersion); + toolsVersion); buildEvent.BuildEventContext = projectBuildEventContext; return buildEvent; @@ -561,14 +559,13 @@ public ProjectStartedEventArgs CreateProjectStartedForCachedProject( BuildEventContext currentNodeBuildEventContext, BuildEventContext remoteNodeEvaluationBuildEventContext, BuildEventContext parentBuildEventContext, - BuildRequestConfiguration projectConfiguration, + IDictionary globalProperties, string projectFile, string targetNames, - IEnumerable properties, - IEnumerable items) + string toolsVersion) { Debug.Assert(currentNodeBuildEventContext.NodeId == Scheduler.InProcNodeId, "Cached project build events should only be logged on the in-proc scheduler node."); - + ValidatePreexistingProjectContextId( remoteNodeEvaluationBuildEventContext.ProjectContextId, projectFile, @@ -576,25 +573,21 @@ public ProjectStartedEventArgs CreateProjectStartedForCachedProject( int configurationId = remoteNodeEvaluationBuildEventContext.ProjectInstanceId; - // Always log GlobalProperties on ProjectStarted - // See https://github.com/dotnet/msbuild/issues/6341 for details - IDictionary globalProperties = projectConfiguration.GlobalProperties.ToDictionary(); - BuildEventContextBuilder thisEventBuildEventContext = parentBuildEventContext .WithProjectInstanceId(configurationId) .WithProjectContextId(remoteNodeEvaluationBuildEventContext.ProjectContextId); ProjectStartedEventArgs buildEvent = new( - projectId: projectConfiguration.ConfigurationId, + projectId: configurationId, message: null, helpKeyword: null, projectFile: projectFile, targetNames: targetNames, - properties: properties, - items: items, + properties: null, // Do not log properties on cached project builds - callers must look up from the 'real' ProjectStartedEventArgs if needed + items: null, // Do not log items on cached project builds - callers must look up from the 'real' ProjectStartedEventArgs if needed parentBuildEventContext: parentBuildEventContext, globalProperties: globalProperties, - toolsVersion: projectConfiguration.ToolsVersion, + toolsVersion: toolsVersion, originalBuildEventContext: remoteNodeEvaluationBuildEventContext) { BuildEventContext = thisEventBuildEventContext, diff --git a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs index 23df592bc36..608f946f3fa 100644 --- a/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/NodeLoggingContext.cs @@ -67,19 +67,20 @@ internal ProjectLoggingContext LogProjectStarted(BuildRequestEntry requestEntry) internal (ProjectStartedEventArgs, ProjectLoggingContext) CreateProjectLoggingContext(BuildRequestEntry requestEntry) { ErrorUtilities.VerifyThrow(this.IsValid, "Build not started."); - return ProjectLoggingContext.CreateLoggingContext(this, requestEntry); + return ProjectLoggingContext.CreateForLocalBuild(this, requestEntry); } /// - /// Log that a project has started if it is serviced from the cache + /// Log that a project has started if it is serviced from the cache. + /// Uses cache-specific pathway that immediately logs ProjectStarted with minimal data. /// /// The build request. /// The configuration used to build the request. - /// The BuildEventContext to use for this project. + /// The ProjectLoggingContext for this cached project. internal ProjectLoggingContext LogProjectStartedFromCache(BuildRequest request, BuildRequestConfiguration configuration) { ErrorUtilities.VerifyThrow(this.IsValid, "Build not started."); - return new ProjectLoggingContext(this, request, configuration); + return ProjectLoggingContext.CreateForCacheBuild(this, request, configuration); } /// diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 87368b8b74d..7b01f9ebeef 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; -using System.Linq; using Microsoft.Build.Collections; using Microsoft.Build.Execution; using Microsoft.Build.Framework; @@ -31,251 +30,159 @@ internal class ProjectLoggingContext : BuildLoggingContext private readonly ProjectTelemetry _projectTelemetry = new ProjectTelemetry(); /// - /// Constructs a project logging context. + /// Private constructor - use factory methods instead. /// - internal ProjectLoggingContext(NodeLoggingContext nodeLoggingContext, BuildRequestEntry requestEntry) - : this - ( - nodeLoggingContext, - requestEntry.Request.SubmissionId, - requestEntry.Request.ConfigurationId, - requestEntry.RequestConfiguration.ProjectFullPath, - requestEntry.Request.Targets, - requestEntry.RequestConfiguration.ToolsVersion, - requestEntry.RequestConfiguration.Project.PropertiesToBuildWith, - requestEntry.RequestConfiguration.Project.ItemsToBuildWith, - requestEntry.Request.ParentBuildEventContext, - requestEntry.RequestConfiguration.ProjectEvaluationId, - requestEntry.Request.ProjectContextId, - requestEntry.Request.ScheduledNodeId) - { - } - - /// - /// Constructs a project logging context. - /// - internal ProjectLoggingContext( + private ProjectLoggingContext( NodeLoggingContext nodeLoggingContext, - BuildRequest request, - BuildRequestConfiguration configuration) - : this - ( - nodeLoggingContext, - request.SubmissionId, - request.ConfigurationId, - configuration.ProjectFullPath, - request.Targets, - configuration.ToolsVersion, - projectProperties: null, - projectItems: null, - request.ParentBuildEventContext, - configuration.ProjectEvaluationId, - request.ProjectContextId, - configuration.ResultsNodeId) + BuildEventContext buildEventContext, + string projectFullPath, + string toolsVersion) + : base(nodeLoggingContext, buildEventContext) { + _projectFullPath = projectFullPath; + + // No need to log a redundant message in the common case + if (toolsVersion != "Current") + { + LoggingService.LogComment(BuildEventContext, MessageImportance.Low, "ToolsVersionInEffectForBuild", toolsVersion); + } + + IsValid = true; } /// - /// Creates ProjectLoggingContext, without logging ProjectStartedEventArgs as a side effect. - /// The ProjectStartedEventArgs is returned as well - so that it can be later logged explicitly + /// Creates ProjectLoggingContext for real local project builds. + /// Returns both ProjectStartedEventArgs (for caller to configure) and ProjectLoggingContext. + /// Does NOT log ProjectStarted immediately. /// - public static (ProjectStartedEventArgs, ProjectLoggingContext) CreateLoggingContext( + public static (ProjectStartedEventArgs, ProjectLoggingContext) CreateForLocalBuild( NodeLoggingContext nodeLoggingContext, BuildRequestEntry requestEntry) { - ProjectStartedEventArgs args = CreateProjectStarted( - nodeLoggingContext, - requestEntry.Request.SubmissionId, - requestEntry.Request.ConfigurationId, - requestEntry.RequestConfiguration.ProjectFullPath, - requestEntry.Request.Targets, - requestEntry.RequestConfiguration.ToolsVersion, - requestEntry.RequestConfiguration.Project.PropertiesToBuildWith, - requestEntry.RequestConfiguration.Project.ItemsToBuildWith, - requestEntry.Request.ParentBuildEventContext, - requestEntry.RequestConfiguration.ProjectEvaluationId, - requestEntry.Request.ProjectContextId, - // in this scenario we are on the same node, so just use the current node id - nodeLoggingContext.BuildEventContext.NodeId); + IEnumerable properties = GetProjectProperties( + nodeLoggingContext.LoggingService, + requestEntry.RequestConfiguration.Project?.PropertiesToBuildWith); + IEnumerable items = GetProjectItems( + nodeLoggingContext.LoggingService, + requestEntry.RequestConfiguration.Project?.ItemsToBuildWith); - return (args, new ProjectLoggingContext(nodeLoggingContext, args)); - } + IDictionary globalProperties = requestEntry.RequestConfiguration.GlobalProperties.ToDictionary(); - private ProjectLoggingContext( - NodeLoggingContext nodeLoggingContext, - ProjectStartedEventArgs projectStarted) - : base(nodeLoggingContext, projectStarted.BuildEventContext) - { - _projectFullPath = projectStarted.ProjectFile; + ProjectStartedEventArgs args = nodeLoggingContext.LoggingService.CreateProjectStartedForLocalProject( + requestEntry.Request.ParentBuildEventContext, + requestEntry.RequestConfiguration.ConfigurationId, + requestEntry.RequestConfiguration.ProjectFullPath, + string.Join(";", requestEntry.Request.Targets), + globalProperties, + properties, + items, + requestEntry.RequestConfiguration.ToolsVersion); - // No need to log a redundant message in the common case - if (projectStarted.ToolsVersion != "Current") - { - LoggingService.LogComment(this.BuildEventContext, MessageImportance.Low, "ToolsVersionInEffectForBuild", projectStarted.ToolsVersion); - } + var context = new ProjectLoggingContext( + nodeLoggingContext, + args.BuildEventContext, + args.ProjectFile, + args.ToolsVersion); - this.IsValid = true; + return (args, context); } /// - /// Constructs a project logging contexts. + /// Creates ProjectLoggingContext for cached project builds. + /// Immediately logs ProjectStarted event with minimal data. /// - /// The node logging context for the currently executing node. - /// The submission id for this project. - /// The configuration id for this project. - /// The full path to the project file. - /// The targets being built in this project. - /// The tools version for this project. - /// The properties in the project. - /// The items in the project. - /// The parent build event context. - /// The evaluation id for this project. - /// The project context id for this project. - /// The node id hosting this project - may be different from that of the nodeLoggingContext if this project was actually started/built on another node - private ProjectLoggingContext( + public static ProjectLoggingContext CreateForCacheBuild( NodeLoggingContext nodeLoggingContext, - int submissionId, - int configurationId, - string projectFullPath, - List targets, - string toolsVersion, - PropertyDictionary projectProperties, - IItemDictionary projectItems, - BuildEventContext parentBuildEventContext, - int evaluationId, - int projectContextId, - int hostNodeId) - : base(nodeLoggingContext, - CreateInitialContext(nodeLoggingContext, - submissionId, - configurationId, - projectFullPath, - targets, - toolsVersion, - projectProperties, - projectItems, - parentBuildEventContext, - evaluationId, - projectContextId, - hostNodeId)) + BuildRequest request, + BuildRequestConfiguration configuration) { - _projectFullPath = projectFullPath; - - // No need to log a redundant message in the common case - if (toolsVersion != "Current") - { - LoggingService.LogComment(this.BuildEventContext, MessageImportance.Low, "ToolsVersionInEffectForBuild", toolsVersion); - } + BuildEventContext buildEventContext = CreateAndLogProjectStartedForCache( + nodeLoggingContext, + request, + configuration); - this.IsValid = true; + return new ProjectLoggingContext( + nodeLoggingContext, + buildEventContext, + configuration.ProjectFullPath, + configuration.ToolsVersion); } - private static BuildEventContext CreateInitialContext( + /// + /// Creates BuildEventContext and logs ProjectStarted for cache scenarios. + /// + private static BuildEventContext CreateAndLogProjectStartedForCache( NodeLoggingContext nodeLoggingContext, - int submissionId, - int configurationId, - string projectFullPath, - List targets, - string toolsVersion, - PropertyDictionary projectProperties, - IItemDictionary projectItems, - BuildEventContext parentBuildEventContext, - int evaluationId, - int projectContextId, - int hostNodeId) + BuildRequest request, + BuildRequestConfiguration configuration) { - ProjectStartedEventArgs args = CreateProjectStarted( - nodeLoggingContext, - submissionId, - configurationId, - projectFullPath, - targets, - toolsVersion, - projectProperties, - projectItems, - parentBuildEventContext, - evaluationId, - projectContextId, - hostNodeId); + // Create a remote node evaluation context with the original evaluation ID + BuildEventContext remoteNodeEvaluationBuildEventContext = BuildEventContext.CreateInitial( + configuration.ResultsNodeId, // Use the node that originally built this project configuration + request.SubmissionId) + .WithEvaluationId(configuration.ProjectEvaluationId) + .WithProjectInstanceId(configuration.ConfigurationId) + // TODO: should we keep this data on the 'original' context - it is _very likely_ not the project + // context Id of the request that created the cached result, but it is the only data we have at this point (so far) + .WithProjectContextId(request.ProjectContextId); + + IDictionary globalProperties = configuration.GlobalProperties.ToDictionary(); + + ProjectStartedEventArgs args = nodeLoggingContext.LoggingService.CreateProjectStartedForCachedProject( + nodeLoggingContext.BuildEventContext, // Current node context + remoteNodeEvaluationBuildEventContext, // Original remote node context + request.ParentBuildEventContext, + globalProperties, + configuration.ProjectFullPath, + string.Join(";", request.Targets), + configuration.ToolsVersion); nodeLoggingContext.LoggingService.LogProjectStarted(args); - return args.BuildEventContext; } - private static ProjectStartedEventArgs CreateProjectStarted( - NodeLoggingContext nodeLoggingContext, - int submissionId, - int configurationId, - string projectFullPath, - List targets, - string toolsVersion, - PropertyDictionary projectProperties, - IItemDictionary projectItems, - BuildEventContext parentBuildEventContext, - int evaluationId, - int projectContextId, - int hostNodeId) + /// + /// Gets project properties for logging if appropriate - as determined by the logging service. + /// + private static IEnumerable GetProjectProperties( + ILoggingService loggingService, + PropertyDictionary projectProperties) { - IEnumerable properties = null; - IEnumerable items = null; - - ILoggingService loggingService = nodeLoggingContext.LoggingService; - - string[] propertiesToSerialize = loggingService.PropertiesToSerialize; - - // If we are only logging critical events lets not pass back the items or properties - if (!loggingService.OnlyLogCriticalEvents && - loggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent && - (!loggingService.RunningOnRemoteNode || loggingService.SerializeAllProperties)) + if (projectProperties == null || + loggingService.OnlyLogCriticalEvents || + !loggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent || + (loggingService.RunningOnRemoteNode && !loggingService.SerializeAllProperties)) { - if (projectProperties is null) - { - properties = []; - } - else if (Traits.LogAllEnvironmentVariables) - { - properties = projectProperties.GetCopyOnReadEnumerable(property => new DictionaryEntry(property.Name, property.EvaluatedValue)); - } - else - { - properties = projectProperties.Filter(p => p is not EnvironmentDerivedProjectPropertyInstance || EnvironmentUtilities.IsWellKnownEnvironmentDerivedProperty(p.Name), p => new DictionaryEntry(p.Name, p.EvaluatedValue)); - } - - items = projectItems?.GetCopyOnReadEnumerable(item => new DictionaryEntry(item.ItemType, new TaskItem(item))) ?? []; + return null; } - if (projectProperties != null && - loggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent && - propertiesToSerialize?.Length > 0 && - !loggingService.SerializeAllProperties) + if (Traits.LogAllEnvironmentVariables) + { + return projectProperties.GetCopyOnReadEnumerable(property => new DictionaryEntry(property.Name, property.EvaluatedValue)); + } + else { - PropertyDictionary projectPropertiesToSerialize = new PropertyDictionary(); - foreach (string propertyToGet in propertiesToSerialize) - { - ProjectPropertyInstance instance = projectProperties[propertyToGet]; - { - if (instance != null) - { - projectPropertiesToSerialize.Set(instance); - } - } - } + return projectProperties.Filter( + p => p is not EnvironmentDerivedProjectPropertyInstance || EnvironmentUtilities.IsWellKnownEnvironmentDerivedProperty(p.Name), + p => new DictionaryEntry(p.Name, p.EvaluatedValue)); + } + } - properties = projectPropertiesToSerialize.Select((ProjectPropertyInstance property) => new DictionaryEntry(property.Name, property.EvaluatedValue)); + /// + /// Gets project items for logging if appropriate - as determined by the logging service. + /// + private static IEnumerable GetProjectItems( + ILoggingService loggingService, + IItemDictionary projectItems) + { + if (projectItems == null || + loggingService.OnlyLogCriticalEvents || + !loggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent || + (loggingService.RunningOnRemoteNode && !loggingService.SerializeAllProperties)) + { + return null; } - return loggingService.CreateProjectStartedForLocalProject( - // adjust the message to come from the node that actually built the project - nodeLoggingContext.BuildEventContext, - submissionId, - configurationId, - parentBuildEventContext, - projectFullPath, - string.Join(";", targets), - properties, - items, - evaluationId, - projectContextId); + return projectItems.GetCopyOnReadEnumerable(item => new DictionaryEntry(item.ItemType, new TaskItem(item))); } /// @@ -289,9 +196,9 @@ private static ProjectStartedEventArgs CreateProjectStarted( /// Did the build succeede or not internal void LogProjectFinished(bool success) { - ErrorUtilities.VerifyThrow(this.IsValid, "invalid"); + ErrorUtilities.VerifyThrow(IsValid, "invalid"); LoggingService.LogProjectFinished(BuildEventContext, _projectFullPath, success); - this.IsValid = false; + IsValid = false; } /// @@ -299,7 +206,7 @@ internal void LogProjectFinished(bool success) /// internal TargetLoggingContext LogTargetBatchStarted(string projectFullPath, ProjectTargetInstance target, string parentTargetName, TargetBuiltReason buildReason) { - ErrorUtilities.VerifyThrow(this.IsValid, "invalid"); + ErrorUtilities.VerifyThrow(IsValid, "invalid"); return new TargetLoggingContext(this, projectFullPath, target, parentTargetName, buildReason); } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 10393dfa017..a018dc74d4a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1144,12 +1144,8 @@ private async Task BuildProject() } catch { - // make sure that any errors thrown by a child project are logged in the context of their parent project: create a temporary projectLoggingContext - _projectLoggingContext = new ProjectLoggingContext( - _nodeLoggingContext, - _requestEntry.Request, - _requestEntry.RequestConfiguration); - + // make sure that any errors thrown by a child project are logged in the context of their parent project + _projectLoggingContext = _nodeLoggingContext.LogProjectStarted(_requestEntry); throw; } finally From 2b07c50447aa84904ba3ee12f8cf407fab709472 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Thu, 18 Dec 2025 14:32:03 -0600 Subject: [PATCH 21/34] Fix some subtle invariants around needing new projectContextIds, especially for cached project contexts --- .../Logging/LoggingServiceLogMethods.cs | 32 +++++++++++++------ .../Logging/ProjectLoggingContext.cs | 23 +++++++------ 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 6c8df75cefc..7ddc932145b 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -527,18 +527,27 @@ private int GenerateNewProjectContextId(string projectFile) /// /// Validates that a preexisting projectContextId is valid and updates the project file map to track which contexts apply to which project files. /// - /// The preexisting project context ID to validate. + /// The remote node evaluation context containing the project context ID to validate. /// The project file path to be associated with this project context ID. /// The node ID of the current node - used to validate that only the in-proc scheduler node is re-using cached project context IDs. - private void ValidatePreexistingProjectContextId(int projectContextId, string projectFile, int currentNodeId) + /// Either the same context, if the projectContextId exists in the map already, or a new context with a generated projectContextId if it did not. + private BuildEventContext ValidatePreexistingProjectContextId(BuildEventContext remoteNodeEvaluationContext, string projectFile, int currentNodeId) { + if (remoteNodeEvaluationContext.ProjectContextId == BuildEventContext.InvalidProjectContextId) + { + // No projectContextId was provided, so generate a new one to represent this invocation of the project + int newProjectContextId = GenerateNewProjectContextId(projectFile); + return remoteNodeEvaluationContext.WithProjectContextId(newProjectContextId); + } + // A projectContextId was provided, so use it with some sanity checks - if (_projectFileMap.TryGetValue(projectContextId, out string existingProjectFile)) + else if (_projectFileMap.TryGetValue(remoteNodeEvaluationContext.ProjectContextId, out string existingProjectFile)) { if (!projectFile.Equals(existingProjectFile, StringComparison.OrdinalIgnoreCase)) { - ErrorUtilities.ThrowInternalError("ContextID {0} was already in the ID-to-project file mapping but the project file {1} did not match the provided one {2}!", projectContextId, existingProjectFile, projectFile); + ErrorUtilities.ThrowInternalError("ContextID {0} was already in the ID-to-project file mapping but the project file {1} did not match the provided one {2}!", remoteNodeEvaluationContext.ProjectContextId, existingProjectFile, projectFile); } + return remoteNodeEvaluationContext; } else { @@ -547,10 +556,11 @@ private void ValidatePreexistingProjectContextId(int projectContextId, string pr // So we only need this sanity check for the in-proc node. if (currentNodeId == Scheduler.InProcNodeId) { - ErrorUtilities.ThrowInternalError("ContextID {0} should have been in the ID-to-project file mapping but wasn't!", projectContextId); + ErrorUtilities.ThrowInternalError("ContextID {0} should have been in the ID-to-project file mapping but wasn't!", remoteNodeEvaluationContext.ProjectContextId); } - _projectFileMap[projectContextId] = projectFile; + _projectFileMap[remoteNodeEvaluationContext.ProjectContextId] = projectFile; + return remoteNodeEvaluationContext; } } @@ -566,16 +576,18 @@ public ProjectStartedEventArgs CreateProjectStartedForCachedProject( { Debug.Assert(currentNodeBuildEventContext.NodeId == Scheduler.InProcNodeId, "Cached project build events should only be logged on the in-proc scheduler node."); - ValidatePreexistingProjectContextId( - remoteNodeEvaluationBuildEventContext.ProjectContextId, + BuildEventContext validatedRemoteNodeEvaluationBuildContext = ValidatePreexistingProjectContextId( + remoteNodeEvaluationBuildEventContext, projectFile, currentNodeBuildEventContext.NodeId); int configurationId = remoteNodeEvaluationBuildEventContext.ProjectInstanceId; + // we need to be a valid BuildEventContext for a ProjectLoggingContext out of this, so we need to make sure + // we have ProjectContextId set. BuildEventContextBuilder thisEventBuildEventContext = parentBuildEventContext .WithProjectInstanceId(configurationId) - .WithProjectContextId(remoteNodeEvaluationBuildEventContext.ProjectContextId); + .WithProjectContextId(validatedRemoteNodeEvaluationBuildContext.ProjectContextId); ProjectStartedEventArgs buildEvent = new( projectId: configurationId, @@ -588,7 +600,7 @@ public ProjectStartedEventArgs CreateProjectStartedForCachedProject( parentBuildEventContext: parentBuildEventContext, globalProperties: globalProperties, toolsVersion: toolsVersion, - originalBuildEventContext: remoteNodeEvaluationBuildEventContext) + originalBuildEventContext: validatedRemoteNodeEvaluationBuildContext) { BuildEventContext = thisEventBuildEventContext, }; diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 7b01f9ebeef..4f867503883 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -66,9 +66,13 @@ public static (ProjectStartedEventArgs, ProjectLoggingContext) CreateForLocalBui requestEntry.RequestConfiguration.Project?.ItemsToBuildWith); IDictionary globalProperties = requestEntry.RequestConfiguration.GlobalProperties.ToDictionary(); - + + BuildEventContext parentBuildEventContext = requestEntry.Request.ParentBuildEventContext == BuildEventContext.Invalid + ? nodeLoggingContext.BuildEventContext.WithEvaluationId(requestEntry.RequestConfiguration.ProjectEvaluationId).WithSubmissionId(requestEntry.Request.SubmissionId) + : requestEntry.Request.ParentBuildEventContext; + ProjectStartedEventArgs args = nodeLoggingContext.LoggingService.CreateProjectStartedForLocalProject( - requestEntry.Request.ParentBuildEventContext, + parentBuildEventContext, requestEntry.RequestConfiguration.ConfigurationId, requestEntry.RequestConfiguration.ProjectFullPath, string.Join(";", requestEntry.Request.Targets), @@ -112,28 +116,27 @@ public static ProjectLoggingContext CreateForCacheBuild( /// private static BuildEventContext CreateAndLogProjectStartedForCache( NodeLoggingContext nodeLoggingContext, - BuildRequest request, + BuildRequest newRequestThatWasServedFromCache, BuildRequestConfiguration configuration) { // Create a remote node evaluation context with the original evaluation ID BuildEventContext remoteNodeEvaluationBuildEventContext = BuildEventContext.CreateInitial( configuration.ResultsNodeId, // Use the node that originally built this project configuration - request.SubmissionId) + newRequestThatWasServedFromCache.SubmissionId) .WithEvaluationId(configuration.ProjectEvaluationId) - .WithProjectInstanceId(configuration.ConfigurationId) - // TODO: should we keep this data on the 'original' context - it is _very likely_ not the project - // context Id of the request that created the cached result, but it is the only data we have at this point (so far) - .WithProjectContextId(request.ProjectContextId); + .WithProjectInstanceId(configuration.ConfigurationId); + // we don't know the projectContextId of the remote eval, so we don't set it at all. + // the new request _does not have_ a valid projectContextId to go off of. IDictionary globalProperties = configuration.GlobalProperties.ToDictionary(); ProjectStartedEventArgs args = nodeLoggingContext.LoggingService.CreateProjectStartedForCachedProject( nodeLoggingContext.BuildEventContext, // Current node context remoteNodeEvaluationBuildEventContext, // Original remote node context - request.ParentBuildEventContext, + newRequestThatWasServedFromCache.ParentBuildEventContext, globalProperties, configuration.ProjectFullPath, - string.Join(";", request.Targets), + string.Join(";", newRequestThatWasServedFromCache.Targets), configuration.ToolsVersion); nodeLoggingContext.LoggingService.LogProjectStarted(args); From 694f1e5f3636b8aebefec90b8661245c51d6613c Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Thu, 18 Dec 2025 14:52:04 -0600 Subject: [PATCH 22/34] Remove test that is meaningless due to API forcing good pathways --- .../BackEnd/LoggingServicesLogMethod_Tests.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index 86e54ff620f..7ab72b7448c 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -782,22 +782,6 @@ public void LogCommentFromTextGoodMessage() #region ProjectStarted - /// - /// Expect an exception to be thrown if a null build event context is passed in - /// and OnlyLogCriticalEvents is false - /// - [Fact] - public void ProjectStartedNullBuildEventContext() - { - Assert.Throws(() => - { - ProcessBuildEventHelper service = (ProcessBuildEventHelper)ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1); - var args = service.CreateProjectStartedForLocalProject(s_taskBuildEventContext, 2, "ProjectFile", "TargetNames", null, null, null, null); - args.BuildEventContext = null; // Force null context for error test - service.LogProjectStarted(args); - }); - } - /// /// Expect an exception to be thrown if a null build event context is passed in /// and OnlyLogCriticalEvents is false From cc49b33f714aebe05d240fc79a55ef4aa96de6db Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Mon, 5 Jan 2026 17:25:18 -0600 Subject: [PATCH 23/34] fix assert to check the correct invariant --- .../BackEnd/Components/Logging/LoggingServiceLogMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 7ddc932145b..87bdd515acb 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -574,7 +574,7 @@ public ProjectStartedEventArgs CreateProjectStartedForCachedProject( string targetNames, string toolsVersion) { - Debug.Assert(currentNodeBuildEventContext.NodeId == Scheduler.InProcNodeId, "Cached project build events should only be logged on the in-proc scheduler node."); + Debug.Assert(remoteNodeEvaluationBuildEventContext.NodeId == Scheduler.InProcNodeId, "Cached project build events should only be logged from the in-proc scheduler node."); BuildEventContext validatedRemoteNodeEvaluationBuildContext = ValidatePreexistingProjectContextId( remoteNodeEvaluationBuildEventContext, From 14c0ce4c56caa7cce8d0de24082ea734c17d8199 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Thu, 8 Jan 2026 12:05:18 -0600 Subject: [PATCH 24/34] Fix incorrect node context creation - we correctly only ever log the cache-based projectstarted messages from the scheduler node. Before we were incorrectly saying they came from the remote node. --- .../BackEnd/Components/Logging/LoggingServiceLogMethods.cs | 2 +- src/Build/BackEnd/Components/Scheduler/Scheduler.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 87bdd515acb..9cf574438a8 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -574,7 +574,7 @@ public ProjectStartedEventArgs CreateProjectStartedForCachedProject( string targetNames, string toolsVersion) { - Debug.Assert(remoteNodeEvaluationBuildEventContext.NodeId == Scheduler.InProcNodeId, "Cached project build events should only be logged from the in-proc scheduler node."); + Debug.Assert(currentNodeBuildEventContext.NodeId == Scheduler.VirtualNode, "Cached project build events should only be logged from the scheduler virtual node."); BuildEventContext validatedRemoteNodeEvaluationBuildContext = ValidatePreexistingProjectContextId( remoteNodeEvaluationBuildEventContext, diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 3eacf547425..bf9bed4a57a 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -2202,8 +2202,7 @@ private ScheduleResponse GetResponseForResult(int parentRequestNode, BuildReques private void LogRequestHandledFromCache(BuildRequest request, BuildResult result) { BuildRequestConfiguration configuration = _configCache[request.ConfigurationId]; - int nodeId = _schedulingData.GetAssignedNodeForRequestConfiguration(request.ConfigurationId); - NodeLoggingContext nodeContext = new NodeLoggingContext(_componentHost.LoggingService, s_schedulerNodeBuildEventContext, nodeId, true); + NodeLoggingContext nodeContext = new NodeLoggingContext(_componentHost.LoggingService, s_schedulerNodeBuildEventContext, VirtualNode, true); nodeContext.LogRequestHandledFromCache(request, configuration, result); TraceScheduler( From 90182ba29579d532aa1682c8e0c1c4b7e7a674df Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 21 Jan 2026 12:13:41 -0600 Subject: [PATCH 25/34] remove invalid assert because it was logically incorrect --- .../BackEnd/Components/Logging/LoggingServiceLogMethods.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 9cf574438a8..b49780ac06a 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using Microsoft.Build.BackEnd.Shared; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Experimental.BuildCheck.Infrastructure; @@ -574,8 +573,9 @@ public ProjectStartedEventArgs CreateProjectStartedForCachedProject( string targetNames, string toolsVersion) { - Debug.Assert(currentNodeBuildEventContext.NodeId == Scheduler.VirtualNode, "Cached project build events should only be logged from the scheduler virtual node."); - + // Cached project events can be logged from either: + // 1. The scheduler's virtual node (when the scheduler serves a request from cache) + // 2. A worker node (when BuildRequestEngine satisfies a request from its local cache) BuildEventContext validatedRemoteNodeEvaluationBuildContext = ValidatePreexistingProjectContextId( remoteNodeEvaluationBuildEventContext, projectFile, From dc964249938bda926158da65dbcb72f3e700cb0b Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Thu, 22 Jan 2026 13:00:38 -0600 Subject: [PATCH 26/34] tighten up buildeventcontext creation APIs --- .../BackEnd/BuildManager/BuildManager.cs | 6 ++--- .../Logging/ProjectLoggingContext.cs | 4 +-- .../ProjectCache/ProjectCacheService.cs | 5 +++- .../BackEnd/Components/Scheduler/Scheduler.cs | 8 +++--- src/Build/BackEnd/Node/InProcNode.cs | 2 +- src/Build/BackEnd/Node/OutOfProcNode.cs | 4 +-- src/Build/Definition/Project.cs | 2 +- src/Build/Definition/ProjectCollection.cs | 2 +- src/Build/Instance/ProjectInstance.cs | 2 +- src/Framework/BuildEventContext.cs | 25 +++++++++++++++++++ 10 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 3c7ca8128fe..a9806b47708 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -1582,7 +1582,7 @@ private void LoadSolutionIntoConfiguration(BuildRequestConfiguration config, Bui var buildEventContext = request.BuildEventContext; if (buildEventContext == BuildEventContext.Invalid) { - buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(request.SubmissionId); + buildEventContext = BuildEventContext.CreateForSubmission(request.SubmissionId); } var instances = ProjectInstance.LoadSolutionForBuild( @@ -1856,7 +1856,7 @@ void LogInvalidProjectFileError(InvalidProjectFileException projectException) /// /// The submission ID /// A BuildEventContext for logging errors - private static BuildEventContext CreateErrorLoggingContext(int submissionId) => Scheduler.s_schedulerInProcNodeBuildEventContext.WithSubmissionId(submissionId); + private static BuildEventContext CreateErrorLoggingContext(int submissionId) => BuildEventContext.CreateForSubmission(submissionId); /// /// Waits to drain all events of logging service. @@ -2757,7 +2757,7 @@ private void PerformSchedulingActions(IEnumerable responses) if (newNodes?.Count != response.NumberOfNodesToCreate || newNodes.Any(n => n == null)) { - BuildEventContext buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(0); + BuildEventContext buildEventContext = BuildEventContext.CreateForSubmission(0); ((IBuildComponentHost)this).LoggingService.LogError(buildEventContext, new BuildEventFileInfo(String.Empty), "UnableToCreateNode", response.RequiredNodeType.ToString("G")); throw new BuildAbortedException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("UnableToCreateNode", response.RequiredNodeType.ToString("G"))); diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 4f867503883..379d8eb7823 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -121,8 +121,8 @@ private static BuildEventContext CreateAndLogProjectStartedForCache( { // Create a remote node evaluation context with the original evaluation ID BuildEventContext remoteNodeEvaluationBuildEventContext = BuildEventContext.CreateInitial( - configuration.ResultsNodeId, // Use the node that originally built this project configuration - newRequestThatWasServedFromCache.SubmissionId) + newRequestThatWasServedFromCache.SubmissionId, + configuration.ResultsNodeId) // Use the node that originally built this project configuration .WithEvaluationId(configuration.ProjectEvaluationId) .WithProjectInstanceId(configuration.ConfigurationId); // we don't know the projectContextId of the remote eval, so we don't set it at all. diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs index 51b128919c3..cf224383d0a 100644 --- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs +++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs @@ -572,7 +572,10 @@ void EvaluateProjectIfNecessary(BuildSubmission submission, BuildRequestConfigur } } - private BuildEventContext GetCacheRequestBuildEventContext(CacheRequest cacheRequest) => Scheduler.s_schedulerNodeBuildEventContext.WithEvaluationId(cacheRequest.Configuration.ProjectEvaluationId).WithProjectInstanceId(cacheRequest.Configuration.ConfigurationId); + private BuildEventContext GetCacheRequestBuildEventContext(CacheRequest cacheRequest) => + BuildEventContext.CreateForNode(Scheduler.VirtualNode) + .WithEvaluationId(cacheRequest.Configuration.ProjectEvaluationId) + .WithProjectInstanceId(cacheRequest.Configuration.ConfigurationId); private async ValueTask GetCacheResultAsync(BuildRequestData buildRequest, BuildRequestConfiguration buildRequestConfiguration, BuildEventContext buildEventContext, CancellationToken cancellationToken) { diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index bf9bed4a57a..19702686c94 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -59,14 +59,14 @@ internal class Scheduler : IScheduler private const double DefaultCustomSchedulerForSQLConfigurationLimitMultiplier = 1.1; /// - /// The build event context for the scheduler node - can be used as a 'root' context for contexts' derived or needed when running scheduler operations + /// The build event context for the scheduler node - can be used as a 'root' context for contexts' derived or needed when running scheduler operations. /// - internal static BuildEventContext s_schedulerNodeBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, VirtualNode); + private static BuildEventContext s_schedulerNodeBuildEventContext = BuildEventContext.CreateForNode(VirtualNode); /// - /// The build event context for the in-proc node - the worker node that executes on the same 'logical' node as the scheduler + /// The build event context for the in-proc node - the worker node that executes on the same 'logical' node as the scheduler. /// - internal static BuildEventContext s_schedulerInProcNodeBuildEventContext = BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, InProcNodeId); + private static BuildEventContext s_schedulerInProcNodeBuildEventContext = BuildEventContext.CreateForNode(InProcNodeId); #region Scheduler Data diff --git a/src/Build/BackEnd/Node/InProcNode.cs b/src/Build/BackEnd/Node/InProcNode.cs index a86ba2a1579..dca39dfb826 100644 --- a/src/Build/BackEnd/Node/InProcNode.cs +++ b/src/Build/BackEnd/Node/InProcNode.cs @@ -507,7 +507,7 @@ private void HandleNodeConfiguration(NodeConfiguration configuration) ILoggingService loggingService = _componentHost.LoggingService; loggingService.OnLoggingThreadException += OnLoggingThreadException; - _buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithNodeId(configuration.NodeId); + _buildEventContext = BuildEventContext.CreateForNode(configuration.NodeId); // Now prep the buildRequestEngine for the build. _loggingContext = new NodeLoggingContext(loggingService, _buildEventContext, configuration.NodeId, true /* inProcNode */); diff --git a/src/Build/BackEnd/Node/OutOfProcNode.cs b/src/Build/BackEnd/Node/OutOfProcNode.cs index 571491af7d8..810eb917163 100644 --- a/src/Build/BackEnd/Node/OutOfProcNode.cs +++ b/src/Build/BackEnd/Node/OutOfProcNode.cs @@ -851,9 +851,9 @@ private void HandleNodeConfiguration(NodeConfiguration configuration) _loggingService.SerializeAllProperties = false; } - _buildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithNodeId(configuration.NodeId); + _buildEventContext = BuildEventContext.CreateForNode(configuration.NodeId); // Now prep the buildRequestEngine for the build. - _loggingContext = new NodeLoggingContext(_loggingService, _buildEventContext, configuration.NodeId, false /* inProcNode */); + _loggingContext = new NodeLoggingContext(_loggingService, _buildEventContext, configuration.NodeId, false /* inProcNode */); if (_shutdownException != null) { diff --git a/src/Build/Definition/Project.cs b/src/Build/Definition/Project.cs index 316fbba1a94..f68c012df4d 100644 --- a/src/Build/Definition/Project.cs +++ b/src/Build/Definition/Project.cs @@ -71,7 +71,7 @@ public class Project : ILinkableObject /// This should only be used on pathways that create Projects outside of the MSBuild exe workflow - users directly loading Projects, etc. /// In such cases we don't have a live set of nodes (yet?), but we still need contexts for message association. /// - private static readonly BuildEventContext s_errorBuildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(0); + private static readonly BuildEventContext s_errorBuildEventContext = BuildEventContext.CreateForSubmission(0); private ProjectLink implementation; private IProjectLinkInternal implementationInternal; diff --git a/src/Build/Definition/ProjectCollection.cs b/src/Build/Definition/ProjectCollection.cs index 37c0afafa37..41db6e1251e 100644 --- a/src/Build/Definition/ProjectCollection.cs +++ b/src/Build/Definition/ProjectCollection.cs @@ -107,7 +107,7 @@ public class ProjectCollection : IToolsetProvider, IBuildComponent, IDisposable /// This should only be used on pathways that create ProjectCollections outside of the MSBuild exe workflow - users directly loading Projects, etc. /// In such cases we don't have a live set of nodes (yet?), but we still need contexts for message association. /// - private static readonly BuildEventContext s_errorBuildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(0); + private static readonly BuildEventContext s_errorBuildEventContext = BuildEventContext.CreateForSubmission(0); /// /// Gets the file version of the file in which the Engine assembly lies. diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index 2183913a435..b97af442bce 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -86,7 +86,7 @@ public class ProjectInstance : IPropertyProvider, IItem /// This should only be used on pathways that create ProjectInstances outside of the MSBuild exe workflow - users directly loading Projects, etc. /// In such cases we don't have a live set of nodes (yet?), but we still need contexts for message association. /// - private static readonly BuildEventContext s_errorBuildEventContext = Scheduler.s_schedulerNodeBuildEventContext.WithSubmissionId(0); + private static readonly BuildEventContext s_errorBuildEventContext = BuildEventContext.CreateForSubmission(0); /// /// Targets in the project after overrides have been resolved. diff --git a/src/Framework/BuildEventContext.cs b/src/Framework/BuildEventContext.cs index 05260665f79..e6b7574a913 100644 --- a/src/Framework/BuildEventContext.cs +++ b/src/Framework/BuildEventContext.cs @@ -80,13 +80,38 @@ internal BuildEventContext( #region Builders + /// + /// Creates a root context for a build submission. Used by BuildManager when starting a new submission. + /// The node ID will be set later when the submission is assigned to a node. + /// + /// The submission ID + /// A builder for creating the BuildEventContext + public static BuildEventContextBuilder CreateForSubmission(int submissionId) => + new BuildEventContextBuilder() + .WithSubmissionId(submissionId) + .WithNodeId(InvalidNodeId); + + /// + /// Creates a root context for a node. Used by nodes when initializing their logging context. + /// The submission ID will be set later when processing build requests. + /// + /// The node ID + /// A builder for creating the BuildEventContext + public static BuildEventContextBuilder CreateForNode(int nodeId) => + new BuildEventContextBuilder() + .WithSubmissionId(InvalidSubmissionId) + .WithNodeId(nodeId); + /// /// Creates an initial BuildEventContext for the beginning of a build. /// Uses the efficient builder pattern to minimize allocations. + /// /// /// The submission ID /// The node ID /// A new BuildEventContext with the specified submission and node ID + /// Strongly suggest not using this member directly. Prefer building off of an inherited , + /// or use the more-directed factory functions or and build from there. public static BuildEventContextBuilder CreateInitial(int submissionId, int nodeId) => new BuildEventContextBuilder().WithSubmissionId(submissionId).WithNodeId(nodeId); /// From b58e86f4ea10b7d63bab3b1bee72ca7e7ad7a5a2 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Thu, 22 Jan 2026 14:08:57 -0600 Subject: [PATCH 27/34] Remove unused field. --- src/Build/BackEnd/Components/Scheduler/Scheduler.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 19702686c94..78ea3ac95ab 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -63,10 +63,6 @@ internal class Scheduler : IScheduler /// private static BuildEventContext s_schedulerNodeBuildEventContext = BuildEventContext.CreateForNode(VirtualNode); - /// - /// The build event context for the in-proc node - the worker node that executes on the same 'logical' node as the scheduler. - /// - private static BuildEventContext s_schedulerInProcNodeBuildEventContext = BuildEventContext.CreateForNode(InProcNodeId); #region Scheduler Data From df81edac40cdfe06b33a66dcb366f0e80f6d8204 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 9 Apr 2026 15:53:39 +0200 Subject: [PATCH 28/34] Fix test hangs and failures in BuildEventContext PR Two issues fixed: 1. Cache-hit hang (DisablingCacheResetKeepsInstance and similar): CreateProjectStartedForCachedProject derived the event BuildEventContext from parentBuildEventContext (which is Invalid for top-level requests), losing the submissionId. The OnProjectFinished handler couldn't find the submission to call CompleteLogging(), so the CompletionEvent was never signaled. Fix: derive from validatedRemoteNodeEvaluationBuildContext which preserves submissionId, nodeId, and evaluationId. 2. RequestBuilder_Tests failures (TestSimpleBuildRequest etc.): The new HandleProjectStarted method calls GetWarningsAsErrors/Messages/ NotAsErrors on ILoggingService, but MockLoggingService threw NotImplementedException. Fix: return empty collections. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Build.UnitTests/BackEnd/MockLoggingService.cs | 6 +++--- .../Components/Logging/LoggingServiceLogMethods.cs | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/MockLoggingService.cs b/src/Build.UnitTests/BackEnd/MockLoggingService.cs index 4e63237b4f1..ed25c8b359a 100644 --- a/src/Build.UnitTests/BackEnd/MockLoggingService.cs +++ b/src/Build.UnitTests/BackEnd/MockLoggingService.cs @@ -706,17 +706,17 @@ public void PopulateBuildTelemetryWithErrors(Framework.Telemetry.BuildTelemetry public ICollection GetWarningsAsErrors(BuildEventContext context) { - throw new NotImplementedException(); + return Array.Empty(); } public ICollection GetWarningsNotAsErrors(BuildEventContext context) { - throw new NotImplementedException(); + return Array.Empty(); } public ICollection GetWarningsAsMessages(BuildEventContext context) { - throw new NotImplementedException(); + return Array.Empty(); } public void LogIncludeFile(BuildEventContext buildEventContext, string filePath) diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index b49780ac06a..a4a5dbdbcd4 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -584,10 +584,12 @@ public ProjectStartedEventArgs CreateProjectStartedForCachedProject( int configurationId = remoteNodeEvaluationBuildEventContext.ProjectInstanceId; // we need to be a valid BuildEventContext for a ProjectLoggingContext out of this, so we need to make sure - // we have ProjectContextId set. - BuildEventContextBuilder thisEventBuildEventContext = parentBuildEventContext - .WithProjectInstanceId(configurationId) - .WithProjectContextId(validatedRemoteNodeEvaluationBuildContext.ProjectContextId); + // we have ProjectContextId set. We derive from the validated remote context rather than the parent + // to preserve submissionId, nodeId, and evaluationId - which are needed for the OnProjectFinished + // handler to find the submission and call CompleteLogging(). + BuildEventContextBuilder thisEventBuildEventContext = validatedRemoteNodeEvaluationBuildContext + .WithTargetId(BuildEventContext.InvalidTargetId) + .WithTaskId(BuildEventContext.InvalidTaskId); ProjectStartedEventArgs buildEvent = new( projectId: configurationId, From 5ca89fe01dabff88ebfcc8cd3eb24660ee625f3f Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 9 Apr 2026 16:13:42 +0200 Subject: [PATCH 29/34] Fix merge conflicts with main Convert new BuildEventContext constructor calls from main to use the builder pattern (CreateForSubmission, CreateInitial, submission.BuildEventContext). Add back CreateErrorLoggingContext helper lost in merge. Fix Telemetry_Tests to use builder pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Telemetry/Telemetry_Tests.cs | 5 +- .../BackEnd/BuildManager/BuildManager.cs | 204 +++++++++++++++--- 2 files changed, 177 insertions(+), 32 deletions(-) diff --git a/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs b/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs index 0361e99ca0d..c8e8f7c3dd1 100644 --- a/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs +++ b/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs @@ -493,7 +493,10 @@ public void FinalizeProcessing_AfterMerge_ResetsState() var loggingContext = new MockLoggingContext( loggingService, - new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, 4)); + BuildEventContext.CreateInitial(BuildEventContext.InvalidSubmissionId, 1) + .WithProjectInstanceId(2) + .WithProjectContextId(BuildEventContext.InvalidProjectContextId) + .WithTargetId(4)); // Merge some data. var localData = new WorkerNodeTelemetryData(); diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index a9806b47708..f76c9d63299 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -38,7 +38,7 @@ using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.TelemetryInfra; using Microsoft.NET.StringTools; -using ExceptionHandling = Microsoft.Build.Shared.ExceptionHandling; +using ExceptionHandling = Microsoft.Build.Framework.ExceptionHandling; using ForwardingLoggerRecord = Microsoft.Build.Logging.ForwardingLoggerRecord; using LoggerDescription = Microsoft.Build.Logging.LoggerDescription; @@ -724,13 +724,13 @@ ILoggingService InitializeLoggingService() // VS builds discard many msbuild events so attach a binlogger to capture them all. IEnumerable AppendDebuggingLoggers(IEnumerable loggers) { - if (DebugUtils.ShouldDebugCurrentProcess is false || + if (FrameworkDebugUtils.ShouldDebugCurrentProcess is false || Traits.Instance.DebugEngine is false) { return loggers; } - var binlogPath = DebugUtils.FindNextAvailableDebugFilePath($"{DebugUtils.ProcessInfoString}_BuildManager_{_hostName}.binlog"); + var binlogPath = DebugUtils.FindNextAvailableDebugFilePath($"{FrameworkDebugUtils.ProcessInfoString}_BuildManager_{_hostName}.binlog"); var logger = new BinaryLogger { Parameters = binlogPath }; @@ -811,7 +811,7 @@ private static void AttachDebugger() return; } - if (!DebugUtils.ShouldDebugCurrentProcess) + if (!FrameworkDebugUtils.ShouldDebugCurrentProcess) { return; } @@ -1038,9 +1038,22 @@ public void EndBuild() } } - _noActiveSubmissionsEvent!.WaitOne(); + { + Stopwatch hangWatch = Stopwatch.StartNew(); + while (!_noActiveSubmissionsEvent!.WaitOne(CrashTelemetryRecorder.EndBuildHangDiagnosticsIntervalMs)) + { + EmitEndBuildHangDiagnostics("WaitingForSubmissions", hangWatch); + } + } + ShutdownConnectedNodes(false /* normal termination */); - _noNodesActiveEvent!.WaitOne(); + { + Stopwatch hangWatch = Stopwatch.StartNew(); + while (!_noNodesActiveEvent!.WaitOne(CrashTelemetryRecorder.EndBuildHangDiagnosticsIntervalMs)) + { + EmitEndBuildHangDiagnostics("WaitingForNodes", hangWatch); + } + } // Wait for all of the actions in the work queue to drain. // _workQueue.Completion.Wait() could throw here if there was an unhandled exception in the work queue, @@ -1092,6 +1105,7 @@ public void EndBuild() } TaskRouter.ClearCache(); + ItemSpecModifiers.ClearDefiningProjectCache(); } catch (Exception e) { @@ -1218,7 +1232,15 @@ private void EndBuildTelemetry() /// private void RecordCrashTelemetry(Exception exception, bool isUnhandled) { - string? host = _buildTelemetry?.BuildEngineHost ?? BuildEnvironmentState.GetHostName(); + string? host = _buildTelemetry?.BuildEngineHost ?? BuildEnvironmentState.GetHostName(); + + int? activeNodeCount; + int? submissionCount; + lock (_syncLock) + { + activeNodeCount = _activeNodes?.Count; + submissionCount = _buildSubmissions?.Count; + } CrashTelemetryRecorder.RecordCrashTelemetry( exception, @@ -1227,9 +1249,131 @@ private void RecordCrashTelemetry(Exception exception, bool isUnhandled) ExceptionHandling.IsCriticalException(exception), ProjectCollection.Version?.ToString(), NativeMethodsShared.FrameworkName, - host); + host, + isStandaloneExecution: _buildTelemetry?.IsStandaloneExecution ?? false, + maxNodeCount: _buildParameters?.MaxNodeCount, + activeNodeCount, + submissionCount); } + /// + /// Extracts build state under lock and delegates to + /// for EndBuild hang diagnostic telemetry emission. + /// + private void EmitEndBuildHangDiagnostics(string waitPhase, Stopwatch hangWatch) + { + try + { + var telemetry = new CrashTelemetry + { + ExitType = CrashExitType.EndBuildHang, + EndBuildWaitPhase = waitPhase, + EndBuildWaitDurationMs = hangWatch.ElapsedMilliseconds, + BuildEngineVersion = ProjectCollection.Version?.ToString(), + BuildEngineFrameworkName = NativeMethodsShared.FrameworkName, + IsStandaloneExecution = _buildTelemetry?.IsStandaloneExecution ?? false, + MaxNodeCount = _buildParameters?.MaxNodeCount, + ActiveNodeCount = _activeNodes.Count, + }; + + lock (_syncLock) + { + var submissionDetailParts = new List(_buildSubmissions.Count); + foreach (BuildSubmissionBase submission in _buildSubmissions.Values) + { + if (submission.BuildResultBase is not null && !submission.LoggingCompleted) + { + telemetry.SubmissionsWithResultNoLogging = (telemetry.SubmissionsWithResultNoLogging ?? 0) + 1; + } + + submissionDetailParts.Add(string.Join(":", + submission.SubmissionId, + submission.IsStarted, + submission.BuildResultBase is not null, + submission.BuildResultBase?.Exception is not null, + submission.LoggingCompleted)); + } + + telemetry.PendingSubmissionCount = _buildSubmissions.Count; + telemetry.ThreadExceptionRecorded = _threadException is not null; + telemetry.UnmatchedProjectStartedCount = _projectStartedEvents.Count; + telemetry.BuildEngineHost = _buildTelemetry?.BuildEngineHost ?? BuildEnvironmentState.GetHostName(); + telemetry.IsShuttingDown = _shuttingDown; + telemetry.IsCancellationRequested = _executionCancellationTokenSource?.IsCancellationRequested ?? false; + telemetry.WorkQueueDepth = _workQueue?.InputCount; + + if (submissionDetailParts.Count > 0) + { + telemetry.SubmissionDetails = string.Join(";", submissionDetailParts); + } + + telemetry.EnableNodeReuse = _buildParameters?.EnableNodeReuse; + + if (_activeNodes.Count > 0) + { + telemetry.ActiveNodeIds = string.Join(",", _activeNodes); + + // Collect per-node details: what each stuck node was last executing. + if (_scheduler is not null) + { + var nodeDetails = new List(_activeNodes.Count); + foreach (int nodeId in _activeNodes) + { + try + { + BuildRequest? executingRequest = _scheduler.GetExecutingRequestByNode(nodeId); + if (executingRequest is not null) + { + string? projectFile = _configCache?[executingRequest.ConfigurationId]?.ProjectFullPath; + string projectName = projectFile is not null ? Path.GetFileName(projectFile) : "?"; + nodeDetails.Add($"{nodeId}:{executingRequest.ConfigurationId}:{projectName}"); + } + else + { + nodeDetails.Add($"{nodeId}:idle"); + } + } + catch + { + nodeDetails.Add($"{nodeId}:error"); + } + } + + if (nodeDetails.Count > 0) + { + telemetry.ActiveNodeDetails = string.Join(";", nodeDetails); + } + } + } + } + + try + { + ILoggingService? loggingService = ((IBuildComponentHost)this).LoggingService; + if (loggingService is not null) + { + telemetry.LoggingServiceState = loggingService.ServiceState.ToString(); + telemetry.LoggingEventQueueDepth = loggingService.EventQueueCount; + + ICollection? loggerTypes = loggingService.RegisteredLoggerTypeNames; + if (loggerTypes is { Count: > 0 }) + { + telemetry.RegisteredLoggerTypeNames = string.Join(";", loggerTypes); + } + } + } + catch + { + // Best effort: accessing the logging service may fail during shutdown. + } + + CrashTelemetryRecorder.EmitEndBuildHangDiagnostics(telemetry); + } + catch (Exception) + { + // Best effort: hang diagnostics must never cause EndBuild to fail. + } + } /// /// Convenience method. Submits a lone build request and blocks until results are available. @@ -1693,7 +1837,7 @@ private void ProcessWorkQueue(Action action) { // On the off chance we get an exception from our exception handler (oh, the irony!), we want to know about it (and still not kill this block // which could lead to a somewhat mysterious hang.) - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); } } @@ -1851,13 +1995,6 @@ void LogInvalidProjectFileError(InvalidProjectFileException projectException) } } - /// - /// Creates a BuildEventContext suitable for error logging for the given submission. - /// - /// The submission ID - /// A BuildEventContext for logging errors - private static BuildEventContext CreateErrorLoggingContext(int submissionId) => BuildEventContext.CreateForSubmission(submissionId); - /// /// Waits to drain all events of logging service. /// This method shall be used carefully because during draining, LoggingService will block all incoming events. @@ -1873,6 +2010,13 @@ private void WaitForAllLoggingServiceEventsToBeProcessed() ((LoggingService)((IBuildComponentHost)this).LoggingService).WaitForLoggingToProcessEvents(); } + /// + /// Creates a BuildEventContext suitable for error logging for the given submission. + /// + /// The submission ID + /// A BuildEventContext for logging errors + private static BuildEventContext CreateErrorLoggingContext(int submissionId) => BuildEventContext.CreateForSubmission(submissionId); + private static void AddBuildRequestToSubmission(BuildSubmission submission, int configurationId, int projectContextId = BuildEventContext.InvalidProjectContextId) { submission.BuildRequest = new BuildRequest( @@ -2034,7 +2178,14 @@ private void ExecuteGraphBuildScheduler(GraphBuildSubmission submission) null, _buildParameters, ((IBuildComponentHost)this).LoggingService, - BuildEventContext.CreateInitial(submission.SubmissionId, _buildParameters.NodeId), + new BuildEventContext( + submission.SubmissionId, + _buildParameters.NodeId, + BuildEventContext.InvalidEvaluationId, + BuildEventContext.InvalidProjectInstanceId, + BuildEventContext.InvalidProjectContextId, + BuildEventContext.InvalidTargetId, + BuildEventContext.InvalidTaskId), SdkResolverService, submission.SubmissionId, projectLoadSettings); @@ -2090,7 +2241,7 @@ static void DumpGraph(ProjectGraph graph, IReadOnlyDictionary(BuildComponentType.LoggingService); foreach (BuildSubmissionBase submission in _buildSubmissions.Values) { - string exception = ExceptionHandling.ReadAnyExceptionFromFile(_instantiationTimeUtc); - loggingService?.LogError(submission.BuildEventContext, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, ExceptionHandling.DebugDumpPath, exception); + string exception = DebugUtils.ReadAnyExceptionFromFile(_instantiationTimeUtc); + loggingService?.LogError(submission.BuildEventContext, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, DebugUtils.DebugDumpPath, exception); } } else if (shutdownPacket.Reason == NodeShutdownReason.Error && _buildSubmissions.Values.Count == 0) @@ -2612,7 +2756,7 @@ private void HandleNodeShutdown(int node, NodeShutdown shutdownPacket) if (shutdownPacket.Exception != null) { ILoggingService loggingService = ((IBuildComponentHost)this).GetComponent(BuildComponentType.LoggingService); - loggingService?.LogError(BuildEventContext.Invalid, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, ExceptionHandling.DebugDumpPath, shutdownPacket.Exception.ToString()); + loggingService?.LogError(BuildEventContext.Invalid, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, DebugUtils.DebugDumpPath, shutdownPacket.Exception.ToString()); OnThreadException(shutdownPacket.Exception); } } @@ -2866,9 +3010,7 @@ private void CheckAllSubmissionsComplete(BuildRequestDataFlags? flags) _buildParameters?.ProjectRootElementCache?.Clear(); FileMatcher.ClearCaches(); -#if !CLR2COMPATIBILITY FileUtilities.ClearFileExistenceCache(); -#endif } _noActiveSubmissionsEvent?.Set(); From aa0759d74c3511bed9d1eba9c101ef1baa23176f Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 9 Apr 2026 16:24:09 +0200 Subject: [PATCH 30/34] Add code review findings (review.md) 24-dimension expert MSBuild code review of PR #12946. Flags 3 blocking issues, 2 major, and 4 moderate findings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- review.md | 395 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 review.md diff --git a/review.md b/review.md new file mode 100644 index 00000000000..cc5315275be --- /dev/null +++ b/review.md @@ -0,0 +1,395 @@ +# Code Review: PR #12946 — "Fill holes in BuildEventContext construction and evaluation ID propagation" + +**Reviewer**: Automated Expert MSBuild Code Review +**Branch**: `pr-12946` +**Files changed**: 63 (+1454 / -928) + +--- + +## Summary Verdict + +| # | Dimension | Verdict | +|---|-----------|---------| +| 1 | Backwards Compatibility | 🔴 2 BLOCKING | +| 2 | ChangeWave Discipline | 🟡 1 MODERATE | +| 3 | Performance & Allocation Awareness | ✅ LGTM | +| 4 | Test Coverage & Completeness | 🟡 1 MODERATE | +| 5 | Error Message Quality | 🟡 1 MODERATE | +| 6 | Logging & Diagnostics | 🔴 1 MAJOR | +| 7 | String Comparison Correctness | ✅ LGTM | +| 8 | API Surface Discipline | 🔴 1 BLOCKING, 1 MAJOR | +| 9 | MSBuild Target Authoring Conventions | ✅ LGTM | +| 10 | Design Before Implementation | 🟡 1 MODERATE | +| 11 | Cross-Platform Correctness | ✅ LGTM | +| 12 | Code Simplification | ✅ LGTM | +| 13 | Concurrency & Thread Safety | ✅ LGTM | +| 14 | Naming Precision | 🟡 1 NIT | +| 15 | SDK Integration Boundaries | ✅ LGTM | +| 16 | Idiomatic C# Patterns | ✅ LGTM | +| 17 | File I/O & Path Handling | ✅ LGTM | +| 18 | Documentation Accuracy | 🟡 2 NIT | +| 19 | Build Infrastructure Care | ✅ LGTM | +| 20 | Scope & PR Discipline | 🟡 1 MODERATE | +| 21 | Evaluation Model Integrity | ✅ LGTM | +| 22 | Correctness & Edge Cases | 🔴 1 BLOCKING | +| 23 | Dependency Management | ✅ LGTM | +| 24 | Security Awareness | ✅ LGTM | + +**Overall**: ❌ **REQUEST_CHANGES** — 3 BLOCKING issues require resolution before merge. + +--- + +## BLOCKING Findings + +### FINDING 1 — [BLOCKING] IPC Serialization: `ProjectStartedEventArgs.WriteToStream` unconditionally writes `EvaluationId` for `parentProjectBuildEventContext`, but `CreateFromStream` guards it with `version >= 36` + +**Dimension**: Correctness & Edge Cases (#22), Backwards Compatibility (#1) +**File**: `src/Framework/ProjectStartedEventArgs.cs` +**Lines**: 447-448 (writer), 539-543 (reader) + +**Scenario**: +`WriteToStream` now **unconditionally** writes `parentProjectBuildEventContext.EvaluationId` (line 448): +```csharp +// added this in version 36 +writer.Write(parentProjectBuildEventContext.EvaluationId); +``` + +But `CreateFromStream` guards it with `version >= 36` (line 539): +```csharp +if (version >= 36) +{ + int evaluationId = reader.ReadInt32(); + builder = builder.WithEvaluationId(evaluationId); +} +``` + +These methods are called through `LogMessagePacketBase` for IPC between MSBuild nodes. The `version` parameter is `s_defaultPacketVersion = (Environment.Version.Major * 10) + Environment.Version.Minor`, which is **80** on .NET 8, **90** on .NET 9, **100** on .NET 10. So `version >= 36` is trivially true for all modern .NET runtimes, meaning the deserialization works correctly **for the current codebase**. + +**However**, the comment `// added this in version 36` is misleading and **factually incorrect** — this was never at version 36 before; it's being added NOW. The guard value `36` is arbitrary and suggests a future binlog version bump that doesn't exist. The binary log `FileFormatVersion` is currently **25**. If anyone interprets this version check as relating to the binlog format (like the `version > 20` check above it does), they will be confused. + +More critically, the same `WriteToStream`/`CreateFromStream` pair is also used by `BinaryFormatter` serialization paths. The version semantics differ across these paths, and the unconditional write paired with a conditional read creates fragility. + +**Recommendation**: +1. If the intent is that the EvaluationId is always written in IPC (since nodes are always the same version), remove the `version >= 36` guard on the reader — make the read unconditional to match the write. The comment can note this was added in a specific PR/version. +2. Alternatively, guard BOTH the write and the read with the same condition. +3. Fix the misleading `// added this in version 36` comment — this version number has no defined meaning in the current protocol. + +--- + +### FINDING 2 — [BLOCKING] `BuildRequestConfiguration.Translate()` adds `_projectEvaluationId` without version guard, unlike `BuildResult` + +**Dimension**: Backwards Compatibility (#1) +**File**: `src/Build/BackEnd/Shared/BuildRequestConfiguration.cs` +**Lines**: 953 and 972 + +**Scenario**: +`_projectEvaluationId` is added to **both** `Translate()` (line 953) and `TranslateForFutureUse()` (line 972) without any version guard: + +```csharp +// In Translate(): +translator.Translate(ref _projectEvaluationId); // line 953 + +// In TranslateForFutureUse(): +translator.Translate(ref _projectEvaluationId); // line 972 +``` + +Meanwhile, `BuildResult` correctly introduces a version field and guards the new `_evaluationId`: +```csharp +private int _version = Traits.Instance.EscapeHatches.DoNotVersionBuildResult ? 0 : 2; +// ... +if (_version >= 2) +{ + translator.Translate(ref _evaluationId); +} +``` + +The `ITranslator` serialization for `BuildRequestConfiguration` reads/writes fields sequentially without self-describing boundaries. If these methods are ever used in a context where the reader and writer are different versions (e.g., cached `BuildRequestConfiguration` objects from a previous MSBuild version persisted in the results cache), the stream will be corrupted because the reader won't expect the extra `Int32`. + +In practice, all MSBuild nodes run from the same installation, so scheduler-to-worker IPC is same-version. However, `BuildRequestConfiguration` is also serialized for the results cache (`TranslateForFutureUse`). If results are cached across MSBuild version upgrades (e.g., in MSBuild Server scenarios), this becomes a real bug. + +**Recommendation**: +Add a version guard to `BuildRequestConfiguration` similar to `BuildResult`, or document explicitly why version guarding is unnecessary for this type (and under what conditions it can break). + +--- + +### FINDING 3 — [BLOCKING] New public API members missing from `PublicAPI.Unshipped.txt` + +**Dimension**: API Surface Discipline (#8) +**File**: `src/Framework/BuildEventContext.cs`, `src/Framework/ProjectStartedEventArgs.cs` + +**Scenario**: +This PR adds many new `public` members that are not listed in any `PublicAPI.Unshipped.txt`: + +**On `BuildEventContext`**: +- `public static BuildEventContextBuilder CreateForSubmission(int submissionId)` +- `public static BuildEventContextBuilder CreateForNode(int nodeId)` +- `public static BuildEventContextBuilder CreateInitial(int submissionId, int nodeId)` +- `public BuildEventContextBuilder WithSubmissionId(int submissionId)` +- `public BuildEventContextBuilder WithNodeId(int nodeId)` +- `public BuildEventContextBuilder WithEvaluationId(int evaluationId)` +- `public BuildEventContextBuilder WithProjectInstanceId(int projectInstanceId)` +- `public BuildEventContextBuilder WithProjectContextId(int projectContextId)` +- `public BuildEventContextBuilder WithTargetId(int targetId)` +- `public BuildEventContextBuilder WithTaskId(int taskId)` +- `public static BuildEventContextBuilder Builder(BuildEventContext source)` + +**New public type**: `public ref struct BuildEventContextBuilder` (with all its public members) + +**On `ProjectStartedEventArgs`**: +- New constructor: `ProjectStartedEventArgs(int, string, string, string?, string?, IEnumerable?, IEnumerable?, BuildEventContext?, IDictionary?, string?, BuildEventContext?)` +- New constructor: `ProjectStartedEventArgs(int, string, string, string?, string?, IEnumerable?, IEnumerable?, BuildEventContext?, BuildEventContext?, DateTime)` +- `public BuildEventContext? OriginalBuildEventContext { get; }` + +These must be added to `PublicAPI.Unshipped.txt` for the API analyzer to track them properly. Without this, the API surface change is undocumented and won't be caught by API diff tooling. + +**Recommendation**: +Add all new public members to the appropriate `PublicAPI.Unshipped.txt` files. If `PublicAPI.Unshipped.txt` isn't used in this repo, verify that the compat suppressions in `CompatibilitySuppressions.xml` fully cover the additions (they currently only cover CP0002/CP0009 for the **removed** constructors, not the **added** API). + +--- + +## MAJOR Findings + +### FINDING 4 — [MAJOR] Binary log does NOT capture `OriginalBuildEventContext` + +**Dimension**: Logging & Diagnostics Rigor (#6) +**File**: `src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs` (unchanged), `src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs` + +**Scenario**: +`ProjectStartedEventArgs.OriginalBuildEventContext` is a new public field intended for "evaluation ID tracking and build correlation in distributed scenarios." However: + +1. `BuildEventArgsWriter.Write(ProjectStartedEventArgs)` (lines 386-413) does NOT serialize `OriginalBuildEventContext`. +2. `BuildEventArgsReader.ReadProjectStartedEventArgs()` (lines 733-775) does NOT deserialize it. + +This means the new field is **invisible in binary logs**. Binary logs are the primary diagnostic tool for MSBuild builds. If this field carries meaningful correlation data for distributed/cached builds, it should be in the binlog. + +The `WriteToStream`/`CreateFromStream` pair on `ProjectStartedEventArgs` does serialize it (for IPC), creating an inconsistency where IPC gets the data but binlog doesn't. + +**Recommendation**: +Either: +1. Add `OriginalBuildEventContext` to `BuildEventArgsWriter.Write(ProjectStartedEventArgs)` and `BuildEventArgsReader.ReadProjectStartedEventArgs()`, bumping `FileFormatVersion` from 25 to 26, OR +2. Document explicitly why this field is intentionally excluded from binlogs (e.g., if it's purely internal plumbing). + +--- + +### FINDING 5 — [MAJOR] Truncated XML doc comment on `BuildRequestConfiguration.ProjectEvaluationId` + +**Dimension**: API Surface Discipline (#8), Documentation Accuracy (#18) +**File**: `src/Build/BackEnd/Shared/BuildRequestConfiguration.cs` +**Lines**: 300-302 + +**Scenario**: +```csharp +/// +/// A short +/// +public int ProjectEvaluationId +``` + +The XML doc comment is truncated — `"A short"` is clearly incomplete. This is a `public` property that will be visible in IntelliSense and API documentation. + +**Recommendation**: +Complete the XML doc comment, e.g.: +```csharp +/// +/// Gets the evaluation ID of the project associated with this configuration. +/// This value is preserved even when the underlying Project becomes cached. +/// +``` + +--- + +## MODERATE Findings + +### FINDING 6 — [MODERATE] No ChangeWave gate for public constructor removal + +**Dimension**: ChangeWave Discipline (#2) +**File**: `src/Framework/BuildEventContext.cs` + +**Scenario**: +Four `public` constructors were made `internal`. While compat suppressions are added, downstream consumers who construct `BuildEventContext` directly (e.g., custom loggers, build tools, NuGet packages that depend on `Microsoft.Build.Framework`) will get compile errors when they upgrade. This is a source-breaking change. + +The compat suppressions (CP0002, CP0009) handle the package validation check, but don't help external consumers. + +**Recommendation**: +Consider keeping at least the most commonly used constructor as `[Obsolete("Use CreateInitial(...).Build() instead")]` `public` for one release cycle, rather than making it immediately `internal`. This gives downstream consumers time to migrate. + +--- + +### FINDING 7 — [MODERATE] `OnDeserialized` sets `originalBuildEventContext` to `BuildEventContext.Invalid` when null + +**Dimension**: Correctness & Edge Cases (#22) +**File**: `src/Framework/ProjectStartedEventArgs.cs` +**Lines**: 671-674 + +**Scenario**: +```csharp +[OnDeserialized] +private void SetDefaultsAfterSerialization(StreamingContext sc) +{ + if (originalBuildEventContext == null) + { + originalBuildEventContext = BuildEventContext.Invalid; + } +} +``` + +When `originalBuildEventContext` is `null` (not set), it's silently replaced with `BuildEventContext.Invalid`. This makes it impossible for consumers to distinguish between "no original context" and "invalid original context". If `OriginalBuildEventContext` is null for non-cached builds (which it should be), the property will return `BuildEventContext.Invalid` after deserialization, which is misleading. + +The property getter is: +```csharp +public BuildEventContext? OriginalBuildEventContext +{ + get { return originalBuildEventContext; } +} +``` + +The return type is `BuildEventContext?` (nullable), suggesting `null` is a valid value. But the `OnDeserialized` callback replaces `null` with `Invalid`, making the nullable return type a lie. + +**Recommendation**: +Either: +1. Keep `null` as the default (remove the `OnDeserialized` normalization for this field), OR +2. Change the return type to non-nullable and document that `BuildEventContext.Invalid` means "not applicable" + +--- + +### FINDING 8 — [MODERATE] Test coverage gaps for new serialization paths + +**Dimension**: Test Coverage & Completeness (#4) +**File**: Various test files + +**Scenario**: +The PR makes significant serialization changes: +1. New `OriginalBuildEventContext` field serialized in IPC via `WriteToStream`/`CreateFromStream` +2. New `EvaluationId` for `parentProjectBuildEventContext` in IPC +3. New `_projectEvaluationId` in `BuildRequestConfiguration.Translate()` +4. New `_evaluationId` in `BuildResult` (version 2) + +While existing tests are updated to use the new API, I don't see dedicated round-trip serialization tests that verify: +- `ProjectStartedEventArgs` with non-null `OriginalBuildEventContext` serializes/deserializes correctly via `WriteToStream`/`CreateFromStream` +- `ProjectStartedEventArgs` with null `OriginalBuildEventContext` doesn't corrupt the stream +- `BuildResult` version 2 with `EvaluationId` round-trips correctly +- `BuildRequestConfiguration` with `_projectEvaluationId` round-trips correctly + +**Recommendation**: +Add explicit serialization round-trip tests for each new field, covering both set and unset states. + +--- + +### FINDING 9 — [MODERATE] PR mixes multiple concerns + +**Dimension**: Scope & PR Discipline (#20) + +**Scenario**: +This PR combines at least 4 distinct changes: +1. **BuildEventContext builder pattern** (API refactoring) +2. **Evaluation ID propagation** (new feature/fix) +3. **ProjectLoggingContext refactoring** (local/cache split) +4. **OriginalBuildEventContext** (new field + serialization) + +Each could be a separate PR. The builder pattern refactoring is purely mechanical and low-risk; the serialization changes are high-risk. Mixing them makes review harder and increases the blast radius if a rollback is needed. + +**Recommendation**: +Consider splitting into: +1. Builder pattern refactoring (purely mechanical, no behavioral change) +2. Evaluation ID propagation + serialization changes +3. OriginalBuildEventContext field addition + +--- + +## NIT Findings + +### FINDING 10 — [NIT] Builder doc comment has alignment issue + +**Dimension**: Documentation Accuracy (#18) +**File**: `src/Framework/BuildEventContext.cs` +**Lines**: 383-385 + +```csharp +/// var context = BuildEventContext.Builder() +/// .WithSubmissionId(1) +/// .WithNodeId(2) +/// .WithProjectInstanceId(3) +/// .Build(); +``` + +The `Builder()` method doesn't exist as a parameterless static method. It's `Builder(BuildEventContext source)`. The example should use `CreateInitial` or show the `Builder(context)` call pattern. + +Also, the indentation at line 384 has one fewer space than the other lines: +``` +/// .WithSubmissionId(1) // 4 spaces (line 382) +/// .WithNodeId(2) // 4 spaces +/// .WithProjectInstanceId(3) +/// .Build(); // 3 spaces (line 385) ← alignment error +``` + +**Recommendation**: Fix the doc example and alignment. + +--- + +### FINDING 11 — [NIT] Naming: `s_schedulerNodeBuildEventContext` should be `s_schedulerBuildEventContext` or have a comment clarifying "node" + +**Dimension**: Naming Precision (#14) +**File**: `src/Build/BackEnd/Components/Scheduler/Scheduler.cs` +**Line**: 63 + +```csharp +private static BuildEventContext s_schedulerNodeBuildEventContext = BuildEventContext.CreateForNode(VirtualNode); +``` + +The name `schedulerNodeBuildEventContext` is slightly confusing because the scheduler runs on the "virtual node" (not a real node). The existing `VirtualNode` constant is clear, but the field name suggests it's a real node context. Consider `s_schedulerVirtualNodeContext` or just `s_schedulerBuildEventContext`. + +Also, there's a double blank line after the declaration (line 65-66). + +--- + +### FINDING 12 — [NIT] `ProjectCacheService.GetCacheRequestBuildEventContext` doesn't include `SubmissionId` + +**Dimension**: Correctness & Edge Cases (#22) +**File**: `src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs` +**Lines**: 575-579 + +```csharp +private BuildEventContext GetCacheRequestBuildEventContext(CacheRequest cacheRequest) => + BuildEventContext.CreateForNode(Scheduler.VirtualNode) + .WithEvaluationId(cacheRequest.Configuration.ProjectEvaluationId) + .WithProjectInstanceId(cacheRequest.Configuration.ConfigurationId); +``` + +The old code passed `cacheRequest.Submission.SubmissionId` to the context. The new code starts from `CreateForNode(VirtualNode)` which sets `SubmissionId` to `InvalidSubmissionId` and never adds it. This may lose the submission association for project cache logging events. + +**Recommendation**: Add `.WithSubmissionId(cacheRequest.Submission.SubmissionId)` unless the omission is intentional. + +--- + +## Dimension-Specific Analysis + +### Concurrency & Thread Safety — ✅ LGTM + +The `s_schedulerNodeBuildEventContext` static field is initialized at static-init time and never reassigned. `BuildEventContext` is immutable (all fields are `readonly`). The `.WithXxx()` methods create new instances via the builder. The `_projectFileMap` in `LoggingService` is a `ConcurrentDictionary`. The `s_defaultPacketVersion` in `LogMessagePacketBase` is also static readonly. No concurrency issues found. + +### Performance & Allocation Awareness — ✅ LGTM + +The `BuildEventContextBuilder` is a `ref struct`, which eliminates heap allocations during the builder chain. Only the final `Build()` call (or implicit conversion) allocates a `BuildEventContext` on the heap. The `BinaryTranslator` changes from direct constructor calls to builder chains add a few stack copies but no heap allocations. The builder pattern is well-suited for this use case. + +### Evaluation Model Integrity — ✅ LGTM + +The evaluation ID propagation preserves the correct flow: evaluation ID is set during project evaluation, stored in `BuildRequestConfiguration._projectEvaluationId`, and propagated through `BuildResult.EvaluationId` back to the scheduler. The `ProjectLoggingContext.CreateForLocalBuild` correctly derives the parent context with the evaluation ID from the configuration. No changes to evaluation ordering or property resolution. + +### Cross-Platform Correctness — ✅ LGTM + +No platform-specific changes. The serialization uses `BinaryWriter.Write(int)` which is portable. The `BuildEventContextBuilder` is a `ref struct` available on all target frameworks. Path handling is unchanged. + +--- + +## Checklist + +- [ ] **Backwards Compatibility** — Public constructors removed without deprecation period; IPC serialization mismatch +- [ ] **API Surface** — New public members not in `PublicAPI.Unshipped.txt`; truncated doc comment +- [ ] **Correctness** — Write/read mismatch in `ProjectStartedEventArgs` IPC serialization +- [x] Concurrency — Thread-safe +- [x] Performance — Builder pattern is allocation-efficient +- [x] Cross-Platform — No issues +- [x] Evaluation Model — Correct propagation +- [ ] **Logging** — `OriginalBuildEventContext` missing from binary log +- [ ] **Test Coverage** — Missing serialization round-trip tests +- [ ] **Scope** — Multiple concerns mixed in one PR From 24ad176521137f93cdacc42ce7a44ea5c4f661d4 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 13 Apr 2026 10:31:22 +0200 Subject: [PATCH 31/34] Fix child project evaluationId/nodeId in BuildEventContext CreateForLocalBuild derived the child project's BuildEventContext from parentBuildEventContext, inheriting the parent's evaluationId and nodeId. The old code explicitly used the child's evaluationId (from configuration) and the current node's nodeId. This broke the console logger's propertyOutputMap lookup via GetEvaluationKey(nodeId, -evaluationId), causing ProjectConfigurationDescription items to not display in error/warning messages (e.g. [project.proj::Number=1]). Fix: override evaluationId and nodeId on the child's BuildEventContext after creation, using the child's ProjectEvaluationId and the current node's nodeId. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../BackEnd/Components/Logging/ProjectLoggingContext.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 379d8eb7823..0b9748dbe46 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -81,6 +81,12 @@ public static (ProjectStartedEventArgs, ProjectLoggingContext) CreateForLocalBui items, requestEntry.RequestConfiguration.ToolsVersion); + // The child project's BuildEventContext must use the child's evaluationId and current node's nodeId, + // not the parent's. The parent context is only used as the parentBuildEventContext field on the event args. + args.BuildEventContext = args.BuildEventContext + .WithEvaluationId(requestEntry.RequestConfiguration.ProjectEvaluationId) + .WithNodeId(nodeLoggingContext.BuildEventContext.NodeId); + var context = new ProjectLoggingContext( nodeLoggingContext, args.BuildEventContext, From a3ea370305953b77b965821f262b76da354aa3fb Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 13 Apr 2026 11:56:48 +0200 Subject: [PATCH 32/34] Restore PropertiesToSerialize filtering in GetProjectProperties The old CreateProjectStarted had a second pass that applied PropertiesToSerialize filtering (MsBuildForwardPropertiesFromChild) even on OOP nodes where RunningOnRemoteNode=true would normally skip all properties. The new GetProjectProperties method was missing this fallback, causing forwarded properties to be empty. Fix: check PropertiesToSerialize before the RunningOnRemoteNode early return, matching the old behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Logging/ProjectLoggingContext.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 0b9748dbe46..eeb80c08f56 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -158,8 +158,30 @@ private static IEnumerable GetProjectProperties( { if (projectProperties == null || loggingService.OnlyLogCriticalEvents || - !loggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent || - (loggingService.RunningOnRemoteNode && !loggingService.SerializeAllProperties)) + !loggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent) + { + return null; + } + + // When PropertiesToSerialize is set (e.g. MsBuildForwardPropertiesFromChild), + // filter to only those properties regardless of RunningOnRemoteNode. + string[] propertiesToSerialize = loggingService.PropertiesToSerialize; + if (propertiesToSerialize?.Length > 0 && !loggingService.SerializeAllProperties) + { + PropertyDictionary filtered = new(); + foreach (string propertyToGet in propertiesToSerialize) + { + ProjectPropertyInstance instance = projectProperties[propertyToGet]; + if (instance is not null) + { + filtered.Set(instance); + } + } + + return filtered.GetCopyOnReadEnumerable(p => new DictionaryEntry(p.Name, p.EvaluatedValue)); + } + + if (loggingService.RunningOnRemoteNode && !loggingService.SerializeAllProperties) { return null; } From eae6a864cceb6c6f9e166f4b8c706a45f997ac76 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 13 Apr 2026 13:37:59 +0200 Subject: [PATCH 33/34] Restore public BuildEventContext constructors, add BannedApiAnalyzer Re-add the 4 original public constructors that were made internal: - (int nodeId, int targetId, int projectContextId, int taskId) - (int nodeId, int projectInstanceId, int projectContextId, int targetId, int taskId) - (int submissionId, int nodeId, int projectInstanceId, int projectContextId, int targetId, int taskId) - (int submissionId, int nodeId, int evaluationId, int projectInstanceId, int projectContextId, int targetId, int taskId) These are kept public for backward compatibility (1P and 3P consumers may create these types), but are discouraged: - [EditorBrowsable(Never)] hides them from IntelliSense - XML doc comments direct users to the new builder pattern - BannedApiAnalyzer (RS0030) prevents internal MSBuild code from using them via eng/BannedSymbols.txt wired through Directory.Build.props - Legitimate internal uses (#pragma warning disable RS0030) are limited to: constructor chaining, BuildEventContext.Invalid, BuildEventContextBuilder.Build(), and binary deserialization Also removes the 25 CompatibilitySuppressions.xml entries (CP0002/CP0009) that were added for the removed constructors, and removes review.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.props | 5 + eng/BannedSymbols.txt | 7 + review.md | 395 ------------------ .../BackEnd/TargetResult_Tests.cs | 7 +- src/Build.UnitTests/ConsoleLogger_Tests.cs | 1 + .../BackEnd/BuildManager/BuildManager.cs | 13 +- src/Framework/BinaryReaderExtensions.cs | 7 +- src/Framework/BuildEventContext.cs | 102 ++++- src/Framework/CompatibilitySuppressions.xml | 175 -------- 9 files changed, 126 insertions(+), 586 deletions(-) create mode 100644 eng/BannedSymbols.txt delete mode 100644 review.md diff --git a/Directory.Build.props b/Directory.Build.props index c0fdacba18a..e4e343c03b5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -83,4 +83,9 @@ ates https://learn.microsoft.com/en-gb/dotnet/fundamentals/syslib-diagnostics/sy true + + + + + diff --git a/eng/BannedSymbols.txt b/eng/BannedSymbols.txt new file mode 100644 index 00000000000..0c817f442fc --- /dev/null +++ b/eng/BannedSymbols.txt @@ -0,0 +1,7 @@ +// Banned BuildEventContext constructors - use CreateInitial/CreateForSubmission/CreateForNode + WithXxx() builder methods instead. +// These constructors make it easy to accidentally drop evaluation IDs and other critical context data. +// See BuildEventContext.cs for the recommended builder pattern. +M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32);Use BuildEventContext.CreateInitial/CreateForNode + WithXxx() builder methods instead +M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32);Use BuildEventContext.CreateInitial/CreateForNode + WithXxx() builder methods instead +M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32);Use BuildEventContext.CreateInitial + WithXxx() builder methods instead +M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32);Use BuildEventContext.CreateInitial + WithXxx() builder methods instead diff --git a/review.md b/review.md deleted file mode 100644 index cc5315275be..00000000000 --- a/review.md +++ /dev/null @@ -1,395 +0,0 @@ -# Code Review: PR #12946 — "Fill holes in BuildEventContext construction and evaluation ID propagation" - -**Reviewer**: Automated Expert MSBuild Code Review -**Branch**: `pr-12946` -**Files changed**: 63 (+1454 / -928) - ---- - -## Summary Verdict - -| # | Dimension | Verdict | -|---|-----------|---------| -| 1 | Backwards Compatibility | 🔴 2 BLOCKING | -| 2 | ChangeWave Discipline | 🟡 1 MODERATE | -| 3 | Performance & Allocation Awareness | ✅ LGTM | -| 4 | Test Coverage & Completeness | 🟡 1 MODERATE | -| 5 | Error Message Quality | 🟡 1 MODERATE | -| 6 | Logging & Diagnostics | 🔴 1 MAJOR | -| 7 | String Comparison Correctness | ✅ LGTM | -| 8 | API Surface Discipline | 🔴 1 BLOCKING, 1 MAJOR | -| 9 | MSBuild Target Authoring Conventions | ✅ LGTM | -| 10 | Design Before Implementation | 🟡 1 MODERATE | -| 11 | Cross-Platform Correctness | ✅ LGTM | -| 12 | Code Simplification | ✅ LGTM | -| 13 | Concurrency & Thread Safety | ✅ LGTM | -| 14 | Naming Precision | 🟡 1 NIT | -| 15 | SDK Integration Boundaries | ✅ LGTM | -| 16 | Idiomatic C# Patterns | ✅ LGTM | -| 17 | File I/O & Path Handling | ✅ LGTM | -| 18 | Documentation Accuracy | 🟡 2 NIT | -| 19 | Build Infrastructure Care | ✅ LGTM | -| 20 | Scope & PR Discipline | 🟡 1 MODERATE | -| 21 | Evaluation Model Integrity | ✅ LGTM | -| 22 | Correctness & Edge Cases | 🔴 1 BLOCKING | -| 23 | Dependency Management | ✅ LGTM | -| 24 | Security Awareness | ✅ LGTM | - -**Overall**: ❌ **REQUEST_CHANGES** — 3 BLOCKING issues require resolution before merge. - ---- - -## BLOCKING Findings - -### FINDING 1 — [BLOCKING] IPC Serialization: `ProjectStartedEventArgs.WriteToStream` unconditionally writes `EvaluationId` for `parentProjectBuildEventContext`, but `CreateFromStream` guards it with `version >= 36` - -**Dimension**: Correctness & Edge Cases (#22), Backwards Compatibility (#1) -**File**: `src/Framework/ProjectStartedEventArgs.cs` -**Lines**: 447-448 (writer), 539-543 (reader) - -**Scenario**: -`WriteToStream` now **unconditionally** writes `parentProjectBuildEventContext.EvaluationId` (line 448): -```csharp -// added this in version 36 -writer.Write(parentProjectBuildEventContext.EvaluationId); -``` - -But `CreateFromStream` guards it with `version >= 36` (line 539): -```csharp -if (version >= 36) -{ - int evaluationId = reader.ReadInt32(); - builder = builder.WithEvaluationId(evaluationId); -} -``` - -These methods are called through `LogMessagePacketBase` for IPC between MSBuild nodes. The `version` parameter is `s_defaultPacketVersion = (Environment.Version.Major * 10) + Environment.Version.Minor`, which is **80** on .NET 8, **90** on .NET 9, **100** on .NET 10. So `version >= 36` is trivially true for all modern .NET runtimes, meaning the deserialization works correctly **for the current codebase**. - -**However**, the comment `// added this in version 36` is misleading and **factually incorrect** — this was never at version 36 before; it's being added NOW. The guard value `36` is arbitrary and suggests a future binlog version bump that doesn't exist. The binary log `FileFormatVersion` is currently **25**. If anyone interprets this version check as relating to the binlog format (like the `version > 20` check above it does), they will be confused. - -More critically, the same `WriteToStream`/`CreateFromStream` pair is also used by `BinaryFormatter` serialization paths. The version semantics differ across these paths, and the unconditional write paired with a conditional read creates fragility. - -**Recommendation**: -1. If the intent is that the EvaluationId is always written in IPC (since nodes are always the same version), remove the `version >= 36` guard on the reader — make the read unconditional to match the write. The comment can note this was added in a specific PR/version. -2. Alternatively, guard BOTH the write and the read with the same condition. -3. Fix the misleading `// added this in version 36` comment — this version number has no defined meaning in the current protocol. - ---- - -### FINDING 2 — [BLOCKING] `BuildRequestConfiguration.Translate()` adds `_projectEvaluationId` without version guard, unlike `BuildResult` - -**Dimension**: Backwards Compatibility (#1) -**File**: `src/Build/BackEnd/Shared/BuildRequestConfiguration.cs` -**Lines**: 953 and 972 - -**Scenario**: -`_projectEvaluationId` is added to **both** `Translate()` (line 953) and `TranslateForFutureUse()` (line 972) without any version guard: - -```csharp -// In Translate(): -translator.Translate(ref _projectEvaluationId); // line 953 - -// In TranslateForFutureUse(): -translator.Translate(ref _projectEvaluationId); // line 972 -``` - -Meanwhile, `BuildResult` correctly introduces a version field and guards the new `_evaluationId`: -```csharp -private int _version = Traits.Instance.EscapeHatches.DoNotVersionBuildResult ? 0 : 2; -// ... -if (_version >= 2) -{ - translator.Translate(ref _evaluationId); -} -``` - -The `ITranslator` serialization for `BuildRequestConfiguration` reads/writes fields sequentially without self-describing boundaries. If these methods are ever used in a context where the reader and writer are different versions (e.g., cached `BuildRequestConfiguration` objects from a previous MSBuild version persisted in the results cache), the stream will be corrupted because the reader won't expect the extra `Int32`. - -In practice, all MSBuild nodes run from the same installation, so scheduler-to-worker IPC is same-version. However, `BuildRequestConfiguration` is also serialized for the results cache (`TranslateForFutureUse`). If results are cached across MSBuild version upgrades (e.g., in MSBuild Server scenarios), this becomes a real bug. - -**Recommendation**: -Add a version guard to `BuildRequestConfiguration` similar to `BuildResult`, or document explicitly why version guarding is unnecessary for this type (and under what conditions it can break). - ---- - -### FINDING 3 — [BLOCKING] New public API members missing from `PublicAPI.Unshipped.txt` - -**Dimension**: API Surface Discipline (#8) -**File**: `src/Framework/BuildEventContext.cs`, `src/Framework/ProjectStartedEventArgs.cs` - -**Scenario**: -This PR adds many new `public` members that are not listed in any `PublicAPI.Unshipped.txt`: - -**On `BuildEventContext`**: -- `public static BuildEventContextBuilder CreateForSubmission(int submissionId)` -- `public static BuildEventContextBuilder CreateForNode(int nodeId)` -- `public static BuildEventContextBuilder CreateInitial(int submissionId, int nodeId)` -- `public BuildEventContextBuilder WithSubmissionId(int submissionId)` -- `public BuildEventContextBuilder WithNodeId(int nodeId)` -- `public BuildEventContextBuilder WithEvaluationId(int evaluationId)` -- `public BuildEventContextBuilder WithProjectInstanceId(int projectInstanceId)` -- `public BuildEventContextBuilder WithProjectContextId(int projectContextId)` -- `public BuildEventContextBuilder WithTargetId(int targetId)` -- `public BuildEventContextBuilder WithTaskId(int taskId)` -- `public static BuildEventContextBuilder Builder(BuildEventContext source)` - -**New public type**: `public ref struct BuildEventContextBuilder` (with all its public members) - -**On `ProjectStartedEventArgs`**: -- New constructor: `ProjectStartedEventArgs(int, string, string, string?, string?, IEnumerable?, IEnumerable?, BuildEventContext?, IDictionary?, string?, BuildEventContext?)` -- New constructor: `ProjectStartedEventArgs(int, string, string, string?, string?, IEnumerable?, IEnumerable?, BuildEventContext?, BuildEventContext?, DateTime)` -- `public BuildEventContext? OriginalBuildEventContext { get; }` - -These must be added to `PublicAPI.Unshipped.txt` for the API analyzer to track them properly. Without this, the API surface change is undocumented and won't be caught by API diff tooling. - -**Recommendation**: -Add all new public members to the appropriate `PublicAPI.Unshipped.txt` files. If `PublicAPI.Unshipped.txt` isn't used in this repo, verify that the compat suppressions in `CompatibilitySuppressions.xml` fully cover the additions (they currently only cover CP0002/CP0009 for the **removed** constructors, not the **added** API). - ---- - -## MAJOR Findings - -### FINDING 4 — [MAJOR] Binary log does NOT capture `OriginalBuildEventContext` - -**Dimension**: Logging & Diagnostics Rigor (#6) -**File**: `src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs` (unchanged), `src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs` - -**Scenario**: -`ProjectStartedEventArgs.OriginalBuildEventContext` is a new public field intended for "evaluation ID tracking and build correlation in distributed scenarios." However: - -1. `BuildEventArgsWriter.Write(ProjectStartedEventArgs)` (lines 386-413) does NOT serialize `OriginalBuildEventContext`. -2. `BuildEventArgsReader.ReadProjectStartedEventArgs()` (lines 733-775) does NOT deserialize it. - -This means the new field is **invisible in binary logs**. Binary logs are the primary diagnostic tool for MSBuild builds. If this field carries meaningful correlation data for distributed/cached builds, it should be in the binlog. - -The `WriteToStream`/`CreateFromStream` pair on `ProjectStartedEventArgs` does serialize it (for IPC), creating an inconsistency where IPC gets the data but binlog doesn't. - -**Recommendation**: -Either: -1. Add `OriginalBuildEventContext` to `BuildEventArgsWriter.Write(ProjectStartedEventArgs)` and `BuildEventArgsReader.ReadProjectStartedEventArgs()`, bumping `FileFormatVersion` from 25 to 26, OR -2. Document explicitly why this field is intentionally excluded from binlogs (e.g., if it's purely internal plumbing). - ---- - -### FINDING 5 — [MAJOR] Truncated XML doc comment on `BuildRequestConfiguration.ProjectEvaluationId` - -**Dimension**: API Surface Discipline (#8), Documentation Accuracy (#18) -**File**: `src/Build/BackEnd/Shared/BuildRequestConfiguration.cs` -**Lines**: 300-302 - -**Scenario**: -```csharp -/// -/// A short -/// -public int ProjectEvaluationId -``` - -The XML doc comment is truncated — `"A short"` is clearly incomplete. This is a `public` property that will be visible in IntelliSense and API documentation. - -**Recommendation**: -Complete the XML doc comment, e.g.: -```csharp -/// -/// Gets the evaluation ID of the project associated with this configuration. -/// This value is preserved even when the underlying Project becomes cached. -/// -``` - ---- - -## MODERATE Findings - -### FINDING 6 — [MODERATE] No ChangeWave gate for public constructor removal - -**Dimension**: ChangeWave Discipline (#2) -**File**: `src/Framework/BuildEventContext.cs` - -**Scenario**: -Four `public` constructors were made `internal`. While compat suppressions are added, downstream consumers who construct `BuildEventContext` directly (e.g., custom loggers, build tools, NuGet packages that depend on `Microsoft.Build.Framework`) will get compile errors when they upgrade. This is a source-breaking change. - -The compat suppressions (CP0002, CP0009) handle the package validation check, but don't help external consumers. - -**Recommendation**: -Consider keeping at least the most commonly used constructor as `[Obsolete("Use CreateInitial(...).Build() instead")]` `public` for one release cycle, rather than making it immediately `internal`. This gives downstream consumers time to migrate. - ---- - -### FINDING 7 — [MODERATE] `OnDeserialized` sets `originalBuildEventContext` to `BuildEventContext.Invalid` when null - -**Dimension**: Correctness & Edge Cases (#22) -**File**: `src/Framework/ProjectStartedEventArgs.cs` -**Lines**: 671-674 - -**Scenario**: -```csharp -[OnDeserialized] -private void SetDefaultsAfterSerialization(StreamingContext sc) -{ - if (originalBuildEventContext == null) - { - originalBuildEventContext = BuildEventContext.Invalid; - } -} -``` - -When `originalBuildEventContext` is `null` (not set), it's silently replaced with `BuildEventContext.Invalid`. This makes it impossible for consumers to distinguish between "no original context" and "invalid original context". If `OriginalBuildEventContext` is null for non-cached builds (which it should be), the property will return `BuildEventContext.Invalid` after deserialization, which is misleading. - -The property getter is: -```csharp -public BuildEventContext? OriginalBuildEventContext -{ - get { return originalBuildEventContext; } -} -``` - -The return type is `BuildEventContext?` (nullable), suggesting `null` is a valid value. But the `OnDeserialized` callback replaces `null` with `Invalid`, making the nullable return type a lie. - -**Recommendation**: -Either: -1. Keep `null` as the default (remove the `OnDeserialized` normalization for this field), OR -2. Change the return type to non-nullable and document that `BuildEventContext.Invalid` means "not applicable" - ---- - -### FINDING 8 — [MODERATE] Test coverage gaps for new serialization paths - -**Dimension**: Test Coverage & Completeness (#4) -**File**: Various test files - -**Scenario**: -The PR makes significant serialization changes: -1. New `OriginalBuildEventContext` field serialized in IPC via `WriteToStream`/`CreateFromStream` -2. New `EvaluationId` for `parentProjectBuildEventContext` in IPC -3. New `_projectEvaluationId` in `BuildRequestConfiguration.Translate()` -4. New `_evaluationId` in `BuildResult` (version 2) - -While existing tests are updated to use the new API, I don't see dedicated round-trip serialization tests that verify: -- `ProjectStartedEventArgs` with non-null `OriginalBuildEventContext` serializes/deserializes correctly via `WriteToStream`/`CreateFromStream` -- `ProjectStartedEventArgs` with null `OriginalBuildEventContext` doesn't corrupt the stream -- `BuildResult` version 2 with `EvaluationId` round-trips correctly -- `BuildRequestConfiguration` with `_projectEvaluationId` round-trips correctly - -**Recommendation**: -Add explicit serialization round-trip tests for each new field, covering both set and unset states. - ---- - -### FINDING 9 — [MODERATE] PR mixes multiple concerns - -**Dimension**: Scope & PR Discipline (#20) - -**Scenario**: -This PR combines at least 4 distinct changes: -1. **BuildEventContext builder pattern** (API refactoring) -2. **Evaluation ID propagation** (new feature/fix) -3. **ProjectLoggingContext refactoring** (local/cache split) -4. **OriginalBuildEventContext** (new field + serialization) - -Each could be a separate PR. The builder pattern refactoring is purely mechanical and low-risk; the serialization changes are high-risk. Mixing them makes review harder and increases the blast radius if a rollback is needed. - -**Recommendation**: -Consider splitting into: -1. Builder pattern refactoring (purely mechanical, no behavioral change) -2. Evaluation ID propagation + serialization changes -3. OriginalBuildEventContext field addition - ---- - -## NIT Findings - -### FINDING 10 — [NIT] Builder doc comment has alignment issue - -**Dimension**: Documentation Accuracy (#18) -**File**: `src/Framework/BuildEventContext.cs` -**Lines**: 383-385 - -```csharp -/// var context = BuildEventContext.Builder() -/// .WithSubmissionId(1) -/// .WithNodeId(2) -/// .WithProjectInstanceId(3) -/// .Build(); -``` - -The `Builder()` method doesn't exist as a parameterless static method. It's `Builder(BuildEventContext source)`. The example should use `CreateInitial` or show the `Builder(context)` call pattern. - -Also, the indentation at line 384 has one fewer space than the other lines: -``` -/// .WithSubmissionId(1) // 4 spaces (line 382) -/// .WithNodeId(2) // 4 spaces -/// .WithProjectInstanceId(3) -/// .Build(); // 3 spaces (line 385) ← alignment error -``` - -**Recommendation**: Fix the doc example and alignment. - ---- - -### FINDING 11 — [NIT] Naming: `s_schedulerNodeBuildEventContext` should be `s_schedulerBuildEventContext` or have a comment clarifying "node" - -**Dimension**: Naming Precision (#14) -**File**: `src/Build/BackEnd/Components/Scheduler/Scheduler.cs` -**Line**: 63 - -```csharp -private static BuildEventContext s_schedulerNodeBuildEventContext = BuildEventContext.CreateForNode(VirtualNode); -``` - -The name `schedulerNodeBuildEventContext` is slightly confusing because the scheduler runs on the "virtual node" (not a real node). The existing `VirtualNode` constant is clear, but the field name suggests it's a real node context. Consider `s_schedulerVirtualNodeContext` or just `s_schedulerBuildEventContext`. - -Also, there's a double blank line after the declaration (line 65-66). - ---- - -### FINDING 12 — [NIT] `ProjectCacheService.GetCacheRequestBuildEventContext` doesn't include `SubmissionId` - -**Dimension**: Correctness & Edge Cases (#22) -**File**: `src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs` -**Lines**: 575-579 - -```csharp -private BuildEventContext GetCacheRequestBuildEventContext(CacheRequest cacheRequest) => - BuildEventContext.CreateForNode(Scheduler.VirtualNode) - .WithEvaluationId(cacheRequest.Configuration.ProjectEvaluationId) - .WithProjectInstanceId(cacheRequest.Configuration.ConfigurationId); -``` - -The old code passed `cacheRequest.Submission.SubmissionId` to the context. The new code starts from `CreateForNode(VirtualNode)` which sets `SubmissionId` to `InvalidSubmissionId` and never adds it. This may lose the submission association for project cache logging events. - -**Recommendation**: Add `.WithSubmissionId(cacheRequest.Submission.SubmissionId)` unless the omission is intentional. - ---- - -## Dimension-Specific Analysis - -### Concurrency & Thread Safety — ✅ LGTM - -The `s_schedulerNodeBuildEventContext` static field is initialized at static-init time and never reassigned. `BuildEventContext` is immutable (all fields are `readonly`). The `.WithXxx()` methods create new instances via the builder. The `_projectFileMap` in `LoggingService` is a `ConcurrentDictionary`. The `s_defaultPacketVersion` in `LogMessagePacketBase` is also static readonly. No concurrency issues found. - -### Performance & Allocation Awareness — ✅ LGTM - -The `BuildEventContextBuilder` is a `ref struct`, which eliminates heap allocations during the builder chain. Only the final `Build()` call (or implicit conversion) allocates a `BuildEventContext` on the heap. The `BinaryTranslator` changes from direct constructor calls to builder chains add a few stack copies but no heap allocations. The builder pattern is well-suited for this use case. - -### Evaluation Model Integrity — ✅ LGTM - -The evaluation ID propagation preserves the correct flow: evaluation ID is set during project evaluation, stored in `BuildRequestConfiguration._projectEvaluationId`, and propagated through `BuildResult.EvaluationId` back to the scheduler. The `ProjectLoggingContext.CreateForLocalBuild` correctly derives the parent context with the evaluation ID from the configuration. No changes to evaluation ordering or property resolution. - -### Cross-Platform Correctness — ✅ LGTM - -No platform-specific changes. The serialization uses `BinaryWriter.Write(int)` which is portable. The `BuildEventContextBuilder` is a `ref struct` available on all target frameworks. Path handling is unchanged. - ---- - -## Checklist - -- [ ] **Backwards Compatibility** — Public constructors removed without deprecation period; IPC serialization mismatch -- [ ] **API Surface** — New public members not in `PublicAPI.Unshipped.txt`; truncated doc comment -- [ ] **Correctness** — Write/read mismatch in `ProjectStartedEventArgs` IPC serialization -- [x] Concurrency — Thread-safe -- [x] Performance — Builder pattern is allocation-efficient -- [x] Cross-Platform — No issues -- [x] Evaluation Model — Correct propagation -- [ ] **Logging** — `OriginalBuildEventContext` missing from binary log -- [ ] **Test Coverage** — Missing serialization round-trip tests -- [ ] **Scope** — Multiple concerns mixed in one PR diff --git a/src/Build.UnitTests/BackEnd/TargetResult_Tests.cs b/src/Build.UnitTests/BackEnd/TargetResult_Tests.cs index d9fb204fb78..b3d57ff8d79 100644 --- a/src/Build.UnitTests/BackEnd/TargetResult_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetResult_Tests.cs @@ -90,7 +90,12 @@ public void TestTranslationNoException() { TaskItem item = new TaskItem("foo", "bar.proj"); item.SetMetadata("a", "b"); - var buildEventContext = new Framework.BuildEventContext(1, 2, 3, 4, 5, 6, 7); + var buildEventContext = Framework.BuildEventContext.CreateInitial(1, 2) + .WithEvaluationId(3) + .WithProjectInstanceId(4) + .WithProjectContextId(5) + .WithTargetId(6) + .WithTaskId(7); TargetResult result = new TargetResult( new TaskItem[] { item }, diff --git a/src/Build.UnitTests/ConsoleLogger_Tests.cs b/src/Build.UnitTests/ConsoleLogger_Tests.cs index c5465da400d..72781e7c7bc 100644 --- a/src/Build.UnitTests/ConsoleLogger_Tests.cs +++ b/src/Build.UnitTests/ConsoleLogger_Tests.cs @@ -219,6 +219,7 @@ public void WarningMessage() new BuildRequestData(p.CreateProjectInstance(), new[] { "Spawn" })); p.Build().ShouldBeTrue(); + System.IO.File.WriteAllText(@"C:\Users\janprovaznik\console_output.txt", sc.ToString()); sc.ToString().ShouldContain("source_of_warning : warning : Hello from project 1 [" + project.ProjectFile + "::Number=1]"); sc.ToString().ShouldContain("source_of_warning : warning : Hello from project 2 [" + project.ProjectFile + "::Number=2]"); } diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index f76c9d63299..b2fcaa2e02a 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2178,14 +2178,7 @@ private void ExecuteGraphBuildScheduler(GraphBuildSubmission submission) null, _buildParameters, ((IBuildComponentHost)this).LoggingService, - new BuildEventContext( - submission.SubmissionId, - _buildParameters.NodeId, - BuildEventContext.InvalidEvaluationId, - BuildEventContext.InvalidProjectInstanceId, - BuildEventContext.InvalidProjectContextId, - BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidTaskId), + BuildEventContext.CreateInitial(submission.SubmissionId, _buildParameters.NodeId), SdkResolverService, submission.SubmissionId, projectLoadSettings); @@ -2707,7 +2700,9 @@ private void HandleResult(int node, BuildResult result) { BuildEventContext buildEventContext = _projectStartedEvents.TryGetValue(result.SubmissionId, out BuildEventArgs? buildEventArgs) ? buildEventArgs.BuildEventContext! - : new BuildEventContext(result.SubmissionId, node, configuration.Project?.EvaluationId ?? BuildEventContext.InvalidEvaluationId, configuration.ConfigurationId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); + : BuildEventContext.CreateInitial(result.SubmissionId, node) + .WithEvaluationId(configuration.ProjectEvaluationId) + .WithProjectInstanceId(configuration.ConfigurationId); try { _projectCacheService.HandleBuildResultAsync(configuration, result, buildEventContext, _executionCancellationTokenSource!.Token).Wait(); diff --git a/src/Framework/BinaryReaderExtensions.cs b/src/Framework/BinaryReaderExtensions.cs index 8cc81b03988..98d73e4f153 100644 --- a/src/Framework/BinaryReaderExtensions.cs +++ b/src/Framework/BinaryReaderExtensions.cs @@ -78,7 +78,12 @@ public static BuildEventContext ReadBuildEventContext(this BinaryReader reader) int projectInstanceId = reader.ReadInt32(); int evaluationId = reader.ReadInt32(); - var buildEventContext = new BuildEventContext(submissionId, nodeId, evaluationId, projectInstanceId, projectContextId, targetId, taskId); + var buildEventContext = BuildEventContext.CreateInitial(submissionId, nodeId) + .WithEvaluationId(evaluationId) + .WithProjectInstanceId(projectInstanceId) + .WithProjectContextId(projectContextId) + .WithTargetId(targetId) + .WithTaskId(taskId); return buildEventContext; } diff --git a/src/Framework/BuildEventContext.cs b/src/Framework/BuildEventContext.cs index e6b7574a913..a9f5d4c6e28 100644 --- a/src/Framework/BuildEventContext.cs +++ b/src/Framework/BuildEventContext.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.ComponentModel; namespace Microsoft.Build.Framework { @@ -56,11 +57,95 @@ public class BuildEventContext #endregion /// - /// Constructs a BuildEventContext with all parameters specified. - /// This constructor should only be used internally for serialization/deserialization - /// and by the fluent WithXxx methods. External code should use CreateInitial() and fluent methods. + /// This is the original constructor. No one should ever use this except internally for backward compatibility. + /// Use , , or and the fluent WithXxx methods instead. + /// + /// + /// This constructor is obsolete and will be removed in a future version. + /// It does not set , , or , + /// making it easy to accidentally lose important context data. + /// Prefer the builder pattern: BuildEventContext.CreateInitial(submissionId, nodeId).WithProjectContextId(...).WithTargetId(...).WithTaskId(...) + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public BuildEventContext( + int nodeId, + int targetId, + int projectContextId, + int taskId) + { + _submissionId = InvalidSubmissionId; + _nodeId = nodeId; + _evaluationId = InvalidEvaluationId; + _projectInstanceId = InvalidProjectInstanceId; + _projectContextId = projectContextId; + _targetId = targetId; + _taskId = taskId; + } + + /// + /// Constructs a BuildEventContext with a specified project instance id. + /// Use , , or and the fluent WithXxx methods instead. + /// + /// + /// This constructor is obsolete and will be removed in a future version. + /// It does not set or , + /// making it easy to accidentally lose important context data. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public BuildEventContext( + int nodeId, + int projectInstanceId, + int projectContextId, + int targetId, + int taskId) + { + _submissionId = InvalidSubmissionId; + _nodeId = nodeId; + _evaluationId = InvalidEvaluationId; + _projectInstanceId = projectInstanceId; + _projectContextId = projectContextId; + _targetId = targetId; + _taskId = taskId; + } + + /// + /// Constructs a BuildEventContext with a specific submission id. + /// Use , , or and the fluent WithXxx methods instead. /// - internal BuildEventContext( + /// + /// This constructor is obsolete and will be removed in a future version. + /// It does not set , + /// making it easy to accidentally lose important context data. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public BuildEventContext( + int submissionId, + int nodeId, + int projectInstanceId, + int projectContextId, + int targetId, + int taskId) + { + _submissionId = submissionId; + _nodeId = nodeId; + _evaluationId = InvalidEvaluationId; + _projectInstanceId = projectInstanceId; + _projectContextId = projectContextId; + _targetId = targetId; + _taskId = taskId; + } + + /// + /// Constructs a BuildEventContext with all parameters specified. + /// Use , , or and the fluent WithXxx methods instead. + /// + /// + /// This constructor is obsolete and will be removed in a future version. + /// Prefer the builder pattern which makes it impossible to accidentally drop ID values: + /// BuildEventContext.CreateInitial(submissionId, nodeId).WithEvaluationId(...).WithProjectInstanceId(...) + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public BuildEventContext( int submissionId, int nodeId, int evaluationId, @@ -183,7 +268,12 @@ public static BuildEventContextBuilder CreateForNode(int nodeId) => /// /// Returns a default invalid BuildEventContext /// - public static BuildEventContext Invalid { get; } = new(InvalidSubmissionId, InvalidNodeId, InvalidEvaluationId, InvalidProjectInstanceId, InvalidProjectContextId, InvalidTargetId, InvalidTaskId); + public static BuildEventContext Invalid { get; } = CreateInitial(InvalidSubmissionId, InvalidNodeId) + .WithEvaluationId(InvalidEvaluationId) + .WithProjectInstanceId(InvalidProjectInstanceId) + .WithProjectContextId(InvalidProjectContextId) + .WithTargetId(InvalidTargetId) + .WithTaskId(InvalidTaskId); /// /// Retrieves the Evaluation id. @@ -505,6 +595,7 @@ public BuildEventContextBuilder WithTaskId(int taskId) /// This is the only operation that allocates memory on the heap. /// /// A new BuildEventContext with the configured values +#pragma warning disable RS0030 // Banned API - Build() is the only sanctioned way to create BuildEventContext from builder public readonly BuildEventContext Build() => new BuildEventContext( _submissionId, _nodeId, @@ -513,6 +604,7 @@ public BuildEventContextBuilder WithTaskId(int taskId) _projectContextId, _targetId, _taskId); +#pragma warning restore RS0030 /// /// Implicit conversion from builder to BuildEventContext for convenience. diff --git a/src/Framework/CompatibilitySuppressions.xml b/src/Framework/CompatibilitySuppressions.xml index a6692f1f847..d61bb3c6b1f 100644 --- a/src/Framework/CompatibilitySuppressions.xml +++ b/src/Framework/CompatibilitySuppressions.xml @@ -1,181 +1,6 @@  - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - lib/net10.0/Microsoft.Build.Framework.dll - lib/net10.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - lib/net10.0/Microsoft.Build.Framework.dll - lib/net10.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - lib/net10.0/Microsoft.Build.Framework.dll - lib/net10.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) - lib/net10.0/Microsoft.Build.Framework.dll - lib/net10.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - lib/net472/Microsoft.Build.Framework.dll - lib/net472/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - lib/net472/Microsoft.Build.Framework.dll - lib/net472/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - lib/net472/Microsoft.Build.Framework.dll - lib/net472/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) - lib/net472/Microsoft.Build.Framework.dll - lib/net472/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - ref/net10.0/Microsoft.Build.Framework.dll - ref/net10.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - ref/net10.0/Microsoft.Build.Framework.dll - ref/net10.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - ref/net10.0/Microsoft.Build.Framework.dll - ref/net10.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) - ref/net10.0/Microsoft.Build.Framework.dll - ref/net10.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - ref/net472/Microsoft.Build.Framework.dll - ref/net472/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - ref/net472/Microsoft.Build.Framework.dll - ref/net472/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - ref/net472/Microsoft.Build.Framework.dll - ref/net472/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) - ref/net472/Microsoft.Build.Framework.dll - ref/net472/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - ref/netstandard2.0/Microsoft.Build.Framework.dll - ref/netstandard2.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - ref/netstandard2.0/Microsoft.Build.Framework.dll - ref/netstandard2.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32) - ref/netstandard2.0/Microsoft.Build.Framework.dll - ref/netstandard2.0/Microsoft.Build.Framework.dll - true - - - CP0002 - M:Microsoft.Build.Framework.BuildEventContext.#ctor(System.Int32,System.Int32,System.Int32,System.Int32) - ref/netstandard2.0/Microsoft.Build.Framework.dll - ref/netstandard2.0/Microsoft.Build.Framework.dll - true - - - CP0009 - T:Microsoft.Build.Framework.BuildEventContext - lib/net10.0/Microsoft.Build.Framework.dll - lib/net10.0/Microsoft.Build.Framework.dll - true - - - CP0009 - T:Microsoft.Build.Framework.BuildEventContext - lib/net472/Microsoft.Build.Framework.dll - lib/net472/Microsoft.Build.Framework.dll - true - - - CP0009 - T:Microsoft.Build.Framework.BuildEventContext - ref/net10.0/Microsoft.Build.Framework.dll - ref/net10.0/Microsoft.Build.Framework.dll - true - - - CP0009 - T:Microsoft.Build.Framework.BuildEventContext - ref/net472/Microsoft.Build.Framework.dll - ref/net472/Microsoft.Build.Framework.dll - true - - - CP0009 - T:Microsoft.Build.Framework.BuildEventContext - ref/netstandard2.0/Microsoft.Build.Framework.dll - ref/netstandard2.0/Microsoft.Build.Framework.dll - true - PKV004 .NETCoreApp,Version=v2.0 From 0f382c3d4045237d1a01020bc518fdfc083e2a6c Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 13 Apr 2026 15:20:06 +0200 Subject: [PATCH 34/34] Remove debug File.WriteAllText left in ConsoleLogger test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Build.UnitTests/ConsoleLogger_Tests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Build.UnitTests/ConsoleLogger_Tests.cs b/src/Build.UnitTests/ConsoleLogger_Tests.cs index 72781e7c7bc..c5465da400d 100644 --- a/src/Build.UnitTests/ConsoleLogger_Tests.cs +++ b/src/Build.UnitTests/ConsoleLogger_Tests.cs @@ -219,7 +219,6 @@ public void WarningMessage() new BuildRequestData(p.CreateProjectInstance(), new[] { "Spawn" })); p.Build().ShouldBeTrue(); - System.IO.File.WriteAllText(@"C:\Users\janprovaznik\console_output.txt", sc.ToString()); sc.ToString().ShouldContain("source_of_warning : warning : Hello from project 1 [" + project.ProjectFile + "::Number=1]"); sc.ToString().ShouldContain("source_of_warning : warning : Hello from project 2 [" + project.ProjectFile + "::Number=2]"); }