|
// 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 Microsoft.Build.Experimental.BuildCheck.Acquisition;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;
internal class BuildCheckBuildEventHandler
{
private readonly IBuildCheckManager _buildCheckManager;
private readonly ICheckContextFactory _checkContextFactory;
private Dictionary<Type, Action<BuildEventArgs>> _eventHandlers;
private readonly Dictionary<Type, Action<BuildEventArgs>> _eventHandlersFull;
private readonly Dictionary<Type, Action<BuildEventArgs>> _eventHandlersRestore;
internal BuildCheckBuildEventHandler(
ICheckContextFactory checkContextFactory,
IBuildCheckManager buildCheckManager)
{
_buildCheckManager = buildCheckManager;
_checkContextFactory = checkContextFactory;
_eventHandlersFull = new()
{
{ typeof(BuildSubmissionStartedEventArgs), (BuildEventArgs e) => HandleBuildSubmissionStartedEvent((BuildSubmissionStartedEventArgs)e) },
{ typeof(ProjectEvaluationFinishedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationFinishedEvent((ProjectEvaluationFinishedEventArgs)e) },
{ typeof(ProjectEvaluationStartedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationStartedEvent((ProjectEvaluationStartedEventArgs)e) },
{ typeof(EnvironmentVariableReadEventArgs), (BuildEventArgs e) => HandleEnvironmentVariableReadEvent((EnvironmentVariableReadEventArgs)e) },
{ typeof(ProjectStartedEventArgs), (BuildEventArgs e) => HandleProjectStartedRequest((ProjectStartedEventArgs)e) },
{ typeof(ProjectFinishedEventArgs), (BuildEventArgs e) => HandleProjectFinishedRequest((ProjectFinishedEventArgs)e) },
{ typeof(BuildCheckTracingEventArgs), (BuildEventArgs e) => HandleBuildCheckTracingEvent((BuildCheckTracingEventArgs)e) },
{ typeof(BuildCheckAcquisitionEventArgs), (BuildEventArgs e) => HandleBuildCheckAcquisitionEvent((BuildCheckAcquisitionEventArgs)e) },
{ typeof(TaskStartedEventArgs), (BuildEventArgs e) => HandleTaskStartedEvent((TaskStartedEventArgs)e) },
{ typeof(TaskFinishedEventArgs), (BuildEventArgs e) => HandleTaskFinishedEvent((TaskFinishedEventArgs)e) },
{ typeof(TaskParameterEventArgs), (BuildEventArgs e) => HandleTaskParameterEvent((TaskParameterEventArgs)e) },
{ typeof(BuildFinishedEventArgs), (BuildEventArgs e) => HandleBuildFinishedEvent((BuildFinishedEventArgs)e) },
{ typeof(ProjectImportedEventArgs), (BuildEventArgs e) => HandleProjectImportedEvent((ProjectImportedEventArgs)e) },
};
// During restore we'll wait only for restore to be done.
_eventHandlersRestore = new()
{
{ typeof(BuildSubmissionStartedEventArgs), (BuildEventArgs e) => HandleBuildSubmissionStartedEvent((BuildSubmissionStartedEventArgs)e) },
};
_eventHandlers = _eventHandlersFull;
}
public void HandleBuildEvent(BuildEventArgs e)
{
if (_eventHandlers.TryGetValue(e.GetType(), out Action<BuildEventArgs>? handler))
{
handler(e);
}
}
private void HandleBuildSubmissionStartedEvent(BuildSubmissionStartedEventArgs eventArgs)
{
eventArgs.GlobalProperties.TryGetValue(MSBuildConstants.MSBuildIsRestoring, out string? restoreProperty);
bool isRestoring = restoreProperty is not null && Convert.ToBoolean(restoreProperty);
_eventHandlers = isRestoring ? _eventHandlersRestore : _eventHandlersFull;
}
private void HandleProjectEvaluationFinishedEvent(ProjectEvaluationFinishedEventArgs eventArgs)
{
if (!IsMetaProjFile(eventArgs.ProjectFile))
{
_buildCheckManager.ProcessEvaluationFinishedEventArgs(
_checkContextFactory.CreateCheckContext(eventArgs.BuildEventContext!),
eventArgs);
_buildCheckManager.EndProjectEvaluation(eventArgs.BuildEventContext!);
}
}
private void HandleProjectEvaluationStartedEvent(ProjectEvaluationStartedEventArgs eventArgs)
{
if (!IsMetaProjFile(eventArgs.ProjectFile))
{
var checkContext = _checkContextFactory.CreateCheckContext(eventArgs.BuildEventContext!);
_buildCheckManager.ProjectFirstEncountered(
BuildCheckDataSource.EventArgs,
checkContext,
eventArgs.ProjectFile!);
_buildCheckManager.ProcessProjectEvaluationStarted(
BuildCheckDataSource.EventArgs,
checkContext,
eventArgs.ProjectFile!);
}
}
private void HandleProjectStartedRequest(ProjectStartedEventArgs eventArgs)
=> _buildCheckManager.StartProjectRequest(
_checkContextFactory.CreateCheckContext(eventArgs.BuildEventContext!),
eventArgs!.ProjectFile!);
private void HandleProjectFinishedRequest(ProjectFinishedEventArgs eventArgs)
=> _buildCheckManager.EndProjectRequest(
_checkContextFactory.CreateCheckContext(eventArgs.BuildEventContext!),
eventArgs!.ProjectFile!);
private void HandleBuildCheckTracingEvent(BuildCheckTracingEventArgs eventArgs)
{
if (!eventArgs.IsAggregatedGlobalReport)
{
_tracingData.MergeIn(eventArgs.TracingData);
}
}
private void HandleTaskStartedEvent(TaskStartedEventArgs eventArgs)
=> _buildCheckManager.ProcessTaskStartedEventArgs(
_checkContextFactory.CreateCheckContext(eventArgs.BuildEventContext!),
eventArgs);
private void HandleTaskFinishedEvent(TaskFinishedEventArgs eventArgs)
=> _buildCheckManager.ProcessTaskFinishedEventArgs(
_checkContextFactory.CreateCheckContext(eventArgs.BuildEventContext!),
eventArgs);
private void HandleTaskParameterEvent(TaskParameterEventArgs eventArgs)
=> _buildCheckManager.ProcessTaskParameterEventArgs(
_checkContextFactory.CreateCheckContext(eventArgs.BuildEventContext!),
eventArgs);
private void HandleBuildCheckAcquisitionEvent(BuildCheckAcquisitionEventArgs eventArgs)
=> _buildCheckManager.ProcessCheckAcquisition(
eventArgs.ToCheckAcquisitionData(),
_checkContextFactory.CreateCheckContext(GetBuildEventContext(eventArgs)));
private void HandleEnvironmentVariableReadEvent(EnvironmentVariableReadEventArgs eventArgs)
=> _buildCheckManager.ProcessEnvironmentVariableReadEventArgs(
_checkContextFactory.CreateCheckContext(GetBuildEventContext(eventArgs)),
eventArgs);
private void HandleProjectImportedEvent(ProjectImportedEventArgs eventArgs)
=> _buildCheckManager.ProcessProjectImportedEventArgs(
_checkContextFactory.CreateCheckContext(GetBuildEventContext(eventArgs)),
eventArgs);
private bool IsMetaProjFile(string? projectFile) => projectFile?.EndsWith(".metaproj", StringComparison.OrdinalIgnoreCase) == true;
private readonly BuildCheckTracingData _tracingData = new BuildCheckTracingData();
private void HandleBuildFinishedEvent(BuildFinishedEventArgs eventArgs)
{
_buildCheckManager.ProcessBuildFinished(_checkContextFactory.CreateCheckContext(eventArgs.BuildEventContext!));
_tracingData.MergeIn(_buildCheckManager.CreateCheckTracingStats());
LogCheckStats(_checkContextFactory.CreateCheckContext(GetBuildEventContext(eventArgs)));
}
private void LogCheckStats(ICheckContext checkContext)
{
Dictionary<string, TimeSpan> infraStats = _tracingData.InfrastructureTracingData;
// Stats are per rule, while runtime is per check - and check can have multiple rules.
// In case of multi-rule check, the runtime stats are duplicated for each rule.
Dictionary<string, TimeSpan> checkStats = _tracingData.ExtractCheckStats();
BuildCheckTracingEventArgs statEvent = new BuildCheckTracingEventArgs(_tracingData, true)
{ BuildEventContext = checkContext.BuildEventContext };
checkContext.DispatchBuildEvent(statEvent);
checkContext.DispatchAsCommentFromText(MessageImportance.Low, $"BuildCheck run times{Environment.NewLine}");
string infraData = BuildCsvString("Infrastructure run times", infraStats);
checkContext.DispatchAsCommentFromText(MessageImportance.Low, infraData);
string checkData = BuildCsvString("Checks run times", checkStats);
checkContext.DispatchAsCommentFromText(MessageImportance.Low, checkData);
checkContext.DispatchTelemetry(_tracingData);
}
private string BuildCsvString(string title, Dictionary<string, TimeSpan> 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);
}
|