Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Build Submission Started event #10424

Merged
merged 23 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/Build/BackEnd/BuildManager/BuildManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1376,9 +1376,15 @@ internal void ExecuteSubmission<TRequestData, TResultData>(
where TResultData : BuildResultBase
{
// TODO: here we should add BuildRequestStarted https://github.com/dotnet/msbuild/issues/10145
// BuildEventContext buildEventContext = new BuildEventContext(submission.SubmissionId, 1, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId);
// ((IBuildComponentHost)this).LoggingService.LogBuildEvent()

BuildSubmissionStartedEventArgs submissionStartedEvent = new(
submission.BuildRequestDataBase.GlobalPropertiesLookup,
submission.BuildRequestDataBase.EntryProjectsFullPath,
submission.BuildRequestDataBase.TargetNames,
submission.BuildRequestDataBase.Flags,
submission.BuildResultBase!.SubmissionId);

BuildEventContext buildEventContext = new BuildEventContext(submission.SubmissionId, 1, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId);
((IBuildComponentHost)this).LoggingService.LogBuildEvent(submissionStartedEvent);

if (submission is BuildSubmission buildSubmission)
{
Expand Down
78 changes: 3 additions & 75 deletions src/Build/BackEnd/BuildManager/BuildRequestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,88 +6,16 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.Build.Collections;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;

[assembly: TypeForwardedTo(typeof(Microsoft.Build.Execution.BuildRequestDataFlags))]
namespace Microsoft.Build.Execution
{
/// <summary>
/// Flags providing additional control over the build request
/// </summary>
[Flags]
public enum BuildRequestDataFlags
{
/// <summary>
/// No flags.
/// </summary>
None = 0,

/// <summary>
/// When this flag is present, the existing ProjectInstance in the build will be replaced by this one.
/// </summary>
ReplaceExistingProjectInstance = 1 << 0,

/// <summary>
/// When this flag is present, the <see cref="BuildResult"/> issued in response to this request will
/// include <see cref="BuildResult.ProjectStateAfterBuild"/>.
/// </summary>
ProvideProjectStateAfterBuild = 1 << 1,

/// <summary>
/// When this flag is present and the project has previously been built on a node whose affinity is
/// incompatible with the affinity this request requires, we will ignore the project state (but not
/// target results) that were previously generated.
/// </summary>
/// <remarks>
/// This usually is not desired behavior. It is only provided for those cases where the client
/// knows that the new build request does not depend on project state generated by a previous request. Setting
/// this flag can provide a performance boost in the case of incompatible node affinities, as MSBuild would
/// otherwise have to serialize the project state from one node to another, which may be
/// expensive depending on how much data the project previously generated.
///
/// This flag has no effect on target results, so if a previous request already built a target, the new
/// request will not re-build that target (nor will any of the project state mutations which previously
/// occurred as a consequence of building that target be re-applied.)
/// </remarks>
IgnoreExistingProjectState = 1 << 2,

/// <summary>
/// When this flag is present, caches including the <see cref="ProjectRootElementCacheBase"/> will be cleared
/// after the build request completes. This is used when the build request is known to modify a lot of
/// state such as restoring packages or generating parts of the import graph.
/// </summary>
ClearCachesAfterBuild = 1 << 3,

/// <summary>
/// When this flag is present, the top level target(s) in the build request will be skipped if those targets
/// are not defined in the Project to build. This only applies to this build request (if another target calls
/// the "missing target" at any other point this will still result in an error).
/// </summary>
SkipNonexistentTargets = 1 << 4,

/// <summary>
/// When this flag is present, the <see cref="BuildResult"/> issued in response to this request will
/// include a <see cref="BuildResult.ProjectStateAfterBuild"/> that includes ONLY the
/// explicitly-requested properties, items, and metadata.
/// </summary>
ProvideSubsetOfStateAfterBuild = 1 << 5,

/// <summary>
/// When this flag is present, projects loaded during build will ignore missing imports (<see cref="ProjectLoadSettings.IgnoreMissingImports"/> and <see cref="ProjectLoadSettings.IgnoreInvalidImports"/>).
/// This is especially useful during a restore since some imports might come from packages that haven't been restored yet.
/// </summary>
IgnoreMissingEmptyAndInvalidImports = 1 << 6,

/// <summary>
/// When this flag is present, an unresolved MSBuild project SDK will fail the build. This flag is used to
/// change the <see cref="IgnoreMissingEmptyAndInvalidImports" /> behavior to still fail when an SDK is missing
/// because those are more fatal.
/// </summary>
FailOnUnresolvedSdk = 1 << 7,
}

/// <summary>
/// BuildRequestData encapsulates all the data needed to submit a build request.
/// </summary>
Expand Down
42 changes: 42 additions & 0 deletions src/Build/BackEnd/Components/Logging/EventSourceSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ internal sealed class EventSourceSink :
/// </summary>
public event BuildWarningEventHandler WarningRaised;

/// <summary>
/// This event is raised to log the start of a build submission.
/// </summary>
public event BuildSubmissionStartedEventHandler BuildSubmissionStarted;

/// <summary>
/// this event is raised to log the start of a build
/// </summary>
Expand Down Expand Up @@ -247,6 +252,9 @@ public void Consume(BuildEventArgs buildEvent)
case ProjectFinishedEventArgs projectFinishedEvent:
RaiseProjectFinishedEvent(null, projectFinishedEvent);
break;
case BuildSubmissionStartedEventArgs buildSubmissionStartedEvent:
RaiseBuildSubmissionStartedEvent(null, buildSubmissionStartedEvent);
break;
case BuildStartedEventArgs buildStartedEvent:
HaveLoggedBuildStartedEvent = true;
RaiseBuildStartedEvent(null, buildStartedEvent);
Expand Down Expand Up @@ -447,6 +455,40 @@ private void RaiseWarningEvent(object sender, BuildWarningEventArgs buildEvent)
RaiseAnyEvent(sender, buildEvent);
}

private void RaiseBuildSubmissionStartedEvent(object sender, BuildSubmissionStartedEventArgs buildEvent)
{
if (BuildSubmissionStarted != null)
{
try
{
BuildSubmissionStarted(sender, buildEvent);
}
catch (LoggerException)
{
// if a logger has failed politely, abort immediately
// first unregister all loggers, since other loggers may receive remaining events in unexpected orderings
// if a fellow logger is throwing in an event handler.
this.UnregisterAllEventHandlers();
throw;
}
catch (Exception exception)
{
// first unregister all loggers, since other loggers may receive remaining events in unexpected orderings
// if a fellow logger is throwing in an event handler.
this.UnregisterAllEventHandlers();

if (ExceptionHandling.IsCriticalException(exception))
{
throw;
}

InternalLoggerException.Throw(exception, buildEvent, "FatalErrorWhileLogging", false);
}
}

RaiseStatusEvent(sender, buildEvent);
}

/// <summary>
/// Raises a "build started" event to all registered loggers.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Build/Graph/GraphBuildRequestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using Microsoft.Build.Execution;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;

namespace Microsoft.Build.Graph
Expand Down
15 changes: 15 additions & 0 deletions src/Framework.UnitTests/BuildSubmissionStartedEventAgs_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.Build.Framework.UnitTests
{
internal class BuildSubmissionStartedEventAgs_Tests
{
}
}
87 changes: 87 additions & 0 deletions src/Framework/BuildRequestDataFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// Note: Namespace is within Build.Execution for type forwarding to work correctly
namespace Microsoft.Build.Execution
{
/// <summary>
/// Flags providing additional control over the build request
/// </summary>
[Flags]
public enum BuildRequestDataFlags
{
/// <summary>
/// No flags.
/// </summary>
None = 0,

/// <summary>
/// When this flag is present, the existing ProjectInstance in the build will be replaced by this one.
/// </summary>
ReplaceExistingProjectInstance = 1 << 0,

/// <summary>
/// When this flag is present, the "BuildResult" issued in response to this request will
/// include "BuildResult.ProjectStateAfterBuild".
/// </summary>
ProvideProjectStateAfterBuild = 1 << 1,

/// <summary>
/// When this flag is present and the project has previously been built on a node whose affinity is
/// incompatible with the affinity this request requires, we will ignore the project state (but not
/// target results) that were previously generated.
/// </summary>
/// <remarks>
/// This usually is not desired behavior. It is only provided for those cases where the client
/// knows that the new build request does not depend on project state generated by a previous request. Setting
/// this flag can provide a performance boost in the case of incompatible node affinities, as MSBuild would
/// otherwise have to serialize the project state from one node to another, which may be
/// expensive depending on how much data the project previously generated.
///
/// This flag has no effect on target results, so if a previous request already built a target, the new
/// request will not re-build that target (nor will any of the project state mutations which previously
/// occurred as a consequence of building that target be re-applied.)
/// </remarks>
IgnoreExistingProjectState = 1 << 2,

/// <summary>
/// When this flag is present, caches including the "ProjectRootElementCacheBase" will be cleared
/// after the build request completes. This is used when the build request is known to modify a lot of
/// state such as restoring packages or generating parts of the import graph.
/// </summary>
ClearCachesAfterBuild = 1 << 3,

/// <summary>
/// When this flag is present, the top level target(s) in the build request will be skipped if those targets
/// are not defined in the Project to build. This only applies to this build request (if another target calls
/// the "missing target" at any other point this will still result in an error).
/// </summary>
SkipNonexistentTargets = 1 << 4,

/// <summary>
/// When this flag is present, the "BuildResult" issued in response to this request will
/// include a "BuildResult.ProjectStateAfterBuild" that includes ONLY the
/// explicitly-requested properties, items, and metadata.
/// </summary>
ProvideSubsetOfStateAfterBuild = 1 << 5,

/// <summary>
/// When this flag is present, projects loaded during build will ignore missing imports ("ProjectLoadSettings.IgnoreMissingImports" and "ProjectLoadSettings.IgnoreInvalidImports").
/// This is especially useful during a restore since some imports might come from packages that haven't been restored yet.
/// </summary>
IgnoreMissingEmptyAndInvalidImports = 1 << 6,

/// <summary>
/// When this flag is present, an unresolved MSBuild project SDK will fail the build. This flag is used to
/// change the "IgnoreMissingEmptyAndInvalidImports" behavior to still fail when an SDK is missing
/// because those are more fatal.
/// </summary>
FailOnUnresolvedSdk = 1 << 7,
}
}
49 changes: 49 additions & 0 deletions src/Framework/BuildSubmissionStartedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;

namespace Microsoft.Build.Framework
{
[Serializable]
public class BuildSubmissionStartedEventArgs : BuildStatusEventArgs
{
public IReadOnlyDictionary<string, string?>? GlobalProperties { get; protected set; }

public IEnumerable<string>? EntryProjectsFullPath { get; protected set; }

public ICollection<string>? TargetNames { get; protected set; }

public BuildRequestDataFlags? Flags { get; protected set; }

public int? SubmissionId { get; protected set; }

/// <summary>
/// Default constructor
/// </summary>
protected BuildSubmissionStartedEventArgs()
: base()
{
// do nothing
}

public BuildSubmissionStartedEventArgs(
IReadOnlyDictionary<string, string?>? globalProperties,
IEnumerable<string>? entryProjectsFullPath,
ICollection<string>? targetNames,
BuildRequestDataFlags? flags,
int? submissionId)
: base()
{
GlobalProperties = globalProperties;
EntryProjectsFullPath = entryProjectsFullPath;
TargetNames = targetNames;
Flags = flags;
SubmissionId = submissionId;
}
}
}
5 changes: 5 additions & 0 deletions src/Framework/IEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ namespace Microsoft.Build.Framework
/// </summary>
public delegate void CustomBuildEventHandler(object sender, CustomBuildEventArgs e);

/// <summary>
/// Type of handler for BuildSubmissionStartedEvent events
/// </summary>
public delegate void BuildSubmissionStartedEventHandler(object sender, BuildSubmissionStartedEventArgs e);

/// <summary>
/// Type of handler for BuildStartedEvent events
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Shared/LogMessagePacketBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ internal enum LoggingEventType : int
/// Event is <see cref="BuildCheckAcquisitionEventArgs"/>
/// </summary>
BuildCheckAcquisitionEvent = 39,

/// <summary>
/// Event is <see cref="BuildSubmissionStartedEventArgs"/>
/// </summary>
BuildSubmissionStartedEvent = 40,
}
#endregion

Expand Down Expand Up @@ -622,6 +627,7 @@ private BuildEventArgs GetBuildEventArgFromId()
LoggingEventType.TaskFinishedEvent => new TaskFinishedEventArgs(null, null, null, null, null, false),
LoggingEventType.TaskCommandLineEvent => new TaskCommandLineEventArgs(null, null, MessageImportance.Normal),
LoggingEventType.ResponseFileUsedEvent => new ResponseFileUsedEventArgs(null),
LoggingEventType.BuildSubmissionStartedEvent => new BuildSubmissionStartedEventArgs(null, null, null, null, null),

#if !TASKHOST // MSBuildTaskHost is targeting Microsoft.Build.Framework.dll 3.5
LoggingEventType.AssemblyLoadEvent => new AssemblyLoadBuildEventArgs(),
Expand Down Expand Up @@ -814,6 +820,10 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg)
{
return LoggingEventType.BuildStartedEvent;
}
else if (eventType == typeof(BuildSubmissionStartedEventArgs))
{
return LoggingEventType.BuildSubmissionStartedEvent;
}
else if (eventType == typeof(BuildWarningEventArgs))
{
return LoggingEventType.BuildWarningEvent;
Expand Down