Skip to content

Commit

Permalink
Expose API for imported projects checks (#10761)
Browse files Browse the repository at this point in the history
  • Loading branch information
YuliiaKovalova authored Oct 19, 2024
1 parent 8f475c6 commit ea75a75
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 89 deletions.
2 changes: 2 additions & 0 deletions src/Build/BuildCheck/API/IBuildCheckRegistrationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ public interface IBuildCheckRegistrationContext
void RegisterEnvironmentVariableReadAction(Action<BuildCheckDataContext<EnvironmentVariableCheckData>> environmentVariableAction);

void RegisterBuildFinishedAction(Action<BuildCheckDataContext<BuildFinishedCheckData>> buildFinishedAction);

void RegisterProjectImportedAction(Action<BuildCheckDataContext<ProjectImportedCheckData>> projectImportedAction);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Experimental.BuildCheck.Acquisition;
using Microsoft.Build.Experimental.BuildCheck.Utilities;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;

Expand Down Expand Up @@ -44,6 +40,7 @@ internal BuildCheckBuildEventHandler(
{ 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.
Expand Down Expand Up @@ -92,6 +89,7 @@ private void HandleProjectEvaluationStartedEvent(ProjectEvaluationStartedEventAr
BuildCheckDataSource.EventArgs,
checkContext,
eventArgs.ProjectFile!);

_buildCheckManager.ProcessProjectEvaluationStarted(
checkContext,
eventArgs.ProjectFile!);
Expand Down Expand Up @@ -141,6 +139,11 @@ private void HandleEnvironmentVariableReadEvent(EnvironmentVariableReadEventArgs
_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();
Expand Down
21 changes: 15 additions & 6 deletions src/Build/BuildCheck/Infrastructure/BuildCheckCentralContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ private record CallbackRegistry(
List<(CheckWrapper, Action<BuildCheckDataContext<PropertyWriteData>>)> PropertyWriteActions,
List<(CheckWrapper, Action<BuildCheckDataContext<ProjectRequestProcessingDoneData>>)> ProjectRequestProcessingDoneActions,
List<(CheckWrapper, Action<BuildCheckDataContext<BuildFinishedCheckData>>)> BuildFinishedActions,
List<(CheckWrapper, Action<BuildCheckDataContext<EnvironmentVariableCheckData>>)> EnvironmentVariableCheckDataActions)
List<(CheckWrapper, Action<BuildCheckDataContext<EnvironmentVariableCheckData>>)> EnvironmentVariableCheckDataActions,
List<(CheckWrapper, Action<BuildCheckDataContext<ProjectImportedCheckData>>)> ProjectImportedCheckDataActions)
{
public CallbackRegistry()
: this([], [], [], [], [], [], [], [])
: this([], [], [], [], [], [], [], [], [])
{
}

Expand Down Expand Up @@ -62,6 +63,7 @@ internal void DeregisterCheck(CheckWrapper check)
internal bool HasPropertyReadActions => _globalCallbacks.PropertyReadActions.Count > 0;

internal bool HasPropertyWriteActions => _globalCallbacks.PropertyWriteActions.Count > 0;

internal bool HasBuildFinishedActions => _globalCallbacks.BuildFinishedActions.Count > 0;

internal void RegisterEnvironmentVariableReadAction(CheckWrapper check, Action<BuildCheckDataContext<EnvironmentVariableCheckData>> environmentVariableAction)
Expand Down Expand Up @@ -90,6 +92,9 @@ internal void RegisterProjectRequestProcessingDoneAction(CheckWrapper check, Act
internal void RegisterBuildFinishedAction(CheckWrapper check, Action<BuildCheckDataContext<BuildFinishedCheckData>> buildFinishedAction)
=> RegisterAction(check, buildFinishedAction, _globalCallbacks.BuildFinishedActions);

internal void RegisterProjectImportedAction(CheckWrapper check, Action<BuildCheckDataContext<ProjectImportedCheckData>> projectImportedAction)
=> RegisterAction(check, projectImportedAction, _globalCallbacks.ProjectImportedCheckDataActions);

private void RegisterAction<T>(
CheckWrapper wrappedCheck,
Action<BuildCheckDataContext<T>> handler,
Expand Down Expand Up @@ -167,10 +172,14 @@ internal void RunProjectProcessingDoneActions(
internal void RunBuildFinishedActions(
BuildFinishedCheckData buildFinishedCheckData,
ICheckContext checkContext,
Action<CheckWrapper, ICheckContext, CheckConfigurationEffective[], BuildCheckResult>
resultHandler)
=> RunRegisteredActions(_globalCallbacks.BuildFinishedActions, buildFinishedCheckData,
checkContext, resultHandler);
Action<CheckWrapper, ICheckContext, CheckConfigurationEffective[], BuildCheckResult> resultHandler)
=> RunRegisteredActions(_globalCallbacks.BuildFinishedActions, buildFinishedCheckData, checkContext, resultHandler);

internal void RunProjectImportedActions(
ProjectImportedCheckData projectImportedCheckData,
ICheckContext checkContext,
Action<CheckWrapper, ICheckContext, CheckConfigurationEffective[], BuildCheckResult> resultHandler)
=> RunRegisteredActions(_globalCallbacks.ProjectImportedCheckDataActions, projectImportedCheckData, checkContext, resultHandler);

private void RunRegisteredActions<T>(
List<(CheckWrapper, Action<BuildCheckDataContext<T>>)> registeredCallbacks,
Expand Down
28 changes: 11 additions & 17 deletions src/Build/BuildCheck/Infrastructure/BuildCheckForwardingLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Experimental.BuildCheck.Acquisition;
using Microsoft.Build.Framework;
using static Microsoft.Build.Experimental.BuildCheck.Infrastructure.BuildCheckManagerProvider;

namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;

Expand All @@ -20,7 +14,7 @@ namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;
/// In the future we may need more specific behavior.
/// </summary>
/// <remarks>
/// Ensure that events filtering is in sync with <see cref="BuildCheckConnectorLogger"/>
/// Ensure that events filtering is in sync with <see cref="BuildCheckConnectorLogger"/>.
/// </remarks>
internal class BuildCheckForwardingLogger : IForwardingLogger
{
Expand All @@ -33,10 +27,10 @@ internal class BuildCheckForwardingLogger : IForwardingLogger
public string? Parameters { get; set; }

/// <summary>
/// Set of events to be forwarded to <see cref="BuildCheckConnectorLogger"/>
/// Set of events to be forwarded to <see cref="BuildCheckConnectorLogger"/>.
/// </summary>
private HashSet<Type> _eventsToForward = new HashSet<Type>
{
private HashSet<Type> _eventsToForward =
[
typeof(EnvironmentVariableReadEventArgs),
typeof(BuildSubmissionStartedEventArgs),
typeof(ProjectEvaluationFinishedEventArgs),
Expand All @@ -47,15 +41,13 @@ internal class BuildCheckForwardingLogger : IForwardingLogger
typeof(BuildCheckAcquisitionEventArgs),
typeof(TaskStartedEventArgs),
typeof(TaskFinishedEventArgs),
typeof(TaskParameterEventArgs)
};
typeof(TaskParameterEventArgs),
typeof(ProjectImportedEventArgs),
];

public void Initialize(IEventSource eventSource, int nodeCount) => Initialize(eventSource);

public void Initialize(IEventSource eventSource)
{
eventSource.AnyEventRaised += EventSource_AnyEventRaised;
}
public void Initialize(IEventSource eventSource) => eventSource.AnyEventRaised += EventSource_AnyEventRaised;

public void EventSource_AnyEventRaised(object sender, BuildEventArgs buildEvent)
{
Expand All @@ -65,5 +57,7 @@ public void EventSource_AnyEventRaised(object sender, BuildEventArgs buildEvent)
}
}

public void Shutdown() { }
public void Shutdown()
{
}
}
57 changes: 51 additions & 6 deletions src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.Build.BackEnd;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.BuildCheck.Infrastructure;
Expand Down Expand Up @@ -59,7 +58,7 @@ public void ShutdownComponent()
{
_instance?.Shutdown();
_instance = null;
}
}

internal sealed class BuildCheckManager : IBuildCheckManager, IBuildEngineDataRouter, IResultReporter
{
Expand Down Expand Up @@ -94,7 +93,7 @@ public void SetDataSource(BuildCheckDataSource buildCheckDataSource)
{
_enabledDataSources[(int)buildCheckDataSource] = true;
RegisterBuiltInChecks(buildCheckDataSource);
}
}
stopwatch.Stop();
_tracingReporter.AddSetDataSourceStats(stopwatch.Elapsed);
}
Expand Down Expand Up @@ -344,11 +343,11 @@ public void RemoveThrottledChecks(ICheckContext checkContext)
private void RemoveCheck(CheckFactoryContext checkToRemove)
{
_checkRegistry.Remove(checkToRemove);

if (checkToRemove.MaterializedCheck is not null)
{
_buildCheckCentralContext.DeregisterCheck(checkToRemove.MaterializedCheck);
_ruleTelemetryData.AddRange(checkToRemove.MaterializedCheck.GetRuleTelemetryData());
_ruleTelemetryData.AddRange(checkToRemove.MaterializedCheck.GetRuleTelemetryData());
checkToRemove.MaterializedCheck.Check.Dispose();
}
}
Expand All @@ -372,6 +371,18 @@ public void ProcessEvaluationFinishedEventArgs(
FileClassifier.Shared.RegisterKnownImmutableLocations(getPropertyValue);
}

// run it here to avoid the missed imports that can be reported before the checks registration
if (_deferredProjectEvalIdToImportedProjects.TryGetValue(checkContext.BuildEventContext.EvaluationId, out HashSet<string>? importedProjects))
{
if (importedProjects != null && TryGetProjectFullPath(checkContext.BuildEventContext, out string projectPath))
{
foreach (string importedProject in importedProjects)
{
_buildEventsProcessor.ProcessProjectImportedEventArgs(checkContext, projectPath, importedProject);
}
}
}

_buildEventsProcessor
.ProcessEvaluationFinishedEventArgs(checkContext, evaluationFinishedEventArgs, propertiesLookup);
}
Expand All @@ -392,6 +403,16 @@ public void ProcessEnvironmentVariableReadEventArgs(ICheckContext checkContext,
}
}

public void ProcessProjectImportedEventArgs(ICheckContext checkContext, ProjectImportedEventArgs projectImportedEventArgs)
{
if (string.IsNullOrEmpty(projectImportedEventArgs.ImportedProjectFile))
{
return;
}

PropagateImport(checkContext.BuildEventContext.EvaluationId, projectImportedEventArgs.ProjectFile, projectImportedEventArgs.ImportedProjectFile);
}

public void ProcessTaskStartedEventArgs(
ICheckContext checkContext,
TaskStartedEventArgs taskStartedEventArgs)
Expand All @@ -414,6 +435,7 @@ public void ProcessTaskParameterEventArgs(
.ProcessTaskParameterEventArgs(checkContext, taskParameterEventArgs);

private readonly List<BuildCheckRuleTelemetryData> _ruleTelemetryData = [];

public BuildCheckTracingData CreateCheckTracingStats()
{
foreach (CheckFactoryContext checkFactoryContext in _checkRegistry)
Expand Down Expand Up @@ -445,6 +467,8 @@ public void FinalizeProcessing(LoggingContext loggingContext)
private readonly ConcurrentDictionary<int, string> _projectsByInstanceId = new();
private readonly ConcurrentDictionary<int, string> _projectsByEvaluationId = new();

private readonly Dictionary<int, HashSet<string>> _deferredProjectEvalIdToImportedProjects = new();

/// <summary>
/// This method fetches the project full path from the context id.
/// This is needed because the full path is needed for configuration and later for fetching configured checks
Expand Down Expand Up @@ -515,6 +539,10 @@ public void ProcessProjectEvaluationStarted(
string projectFullPath)
{
_projectsByEvaluationId[checkContext.BuildEventContext.EvaluationId] = projectFullPath;
if (!_deferredProjectEvalIdToImportedProjects.ContainsKey(checkContext.BuildEventContext.EvaluationId))
{
_deferredProjectEvalIdToImportedProjects.Add(checkContext.BuildEventContext.EvaluationId, [projectFullPath]);
}
}

/*
Expand All @@ -523,7 +551,6 @@ public void ProcessProjectEvaluationStarted(
*
*/


public void EndProjectEvaluation(BuildEventContext buildEventContext)
{
}
Expand All @@ -548,6 +575,24 @@ public void StartProjectRequest(ICheckContext checkContext, string projectFullPa
}

private readonly Dictionary<int, List<BuildEventArgs>> _deferredEvalDiagnostics = new();

/// <summary>
/// Propagates a newly imported project file to all projects that import the original project file.
/// This method ensures that if Project A imports Project B, and Project B now imports Project C,
/// then Project A will also show Project C as an import.
/// </summary>
/// <param name="evaluationId">The evaluation id is associated with the root project path.</param>
/// <param name="originalProjectFile">The path of the project file that is importing a new project.</param>
/// <param name="newImportedProjectFile">The path of the newly imported project file.</param>
private void PropagateImport(int evaluationId, string originalProjectFile, string newImportedProjectFile)
{
if (_deferredProjectEvalIdToImportedProjects.TryGetValue(evaluationId, out HashSet<string>? importedProjects)
&& importedProjects.Contains(originalProjectFile))
{
importedProjects.Add(newImportedProjectFile);
}
}

void IResultReporter.ReportResult(BuildEventArgs eventArgs, ICheckContext checkContext)
{
// If we do not need to decide on promotability/demotability of warnings or we are ready to decide on those
Expand Down
10 changes: 10 additions & 0 deletions src/Build/BuildCheck/Infrastructure/BuildEventsProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ internal void ProcessEnvironmentVariableReadEventArgs(ICheckContext checkContext
_buildCheckCentralContext.RunEnvironmentVariableActions(checkData, checkContext, ReportResult);
}

/// <summary>
/// The method handles events associated with the ProjectImportedEventArgs.
/// </summary>
internal void ProcessProjectImportedEventArgs(ICheckContext checkContext, string projectPath, string importedProjectPath)
{
ProjectImportedCheckData checkData = new(importedProjectPath, projectPath, checkContext.BuildEventContext?.ProjectInstanceId);

_buildCheckCentralContext.RunProjectImportedActions(checkData, checkContext, ReportResult);
}

internal void ProcessBuildDone(ICheckContext checkContext)
{
if (!_buildCheckCentralContext.HasBuildFinishedActions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Threading;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Experimental.BuildCheck.Checks;

namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;

internal sealed class CheckRegistrationContext(CheckWrapper checkWrapper, BuildCheckCentralContext buildCheckCentralContext) : IInternalCheckRegistrationContext
internal sealed class CheckRegistrationContext(CheckWrapper checkWrapper, BuildCheckCentralContext buildCheckCentralContext)
: IInternalCheckRegistrationContext
{
public void RegisterEnvironmentVariableReadAction(Action<BuildCheckDataContext<EnvironmentVariableCheckData>> environmentVariableAction) =>
buildCheckCentralContext.RegisterEnvironmentVariableReadAction(checkWrapper, environmentVariableAction);
Expand All @@ -33,4 +32,7 @@ public void RegisterProjectRequestProcessingDoneAction(Action<BuildCheckDataCont

public void RegisterBuildFinishedAction(Action<BuildCheckDataContext<BuildFinishedCheckData>> buildFinishedAction)
=> buildCheckCentralContext.RegisterBuildFinishedAction(checkWrapper, buildFinishedAction);

public void RegisterProjectImportedAction(Action<BuildCheckDataContext<ProjectImportedCheckData>> projectImportedAction) =>
buildCheckCentralContext.RegisterProjectImportedAction(checkWrapper, projectImportedAction);
}
Loading

0 comments on commit ea75a75

Please sign in to comment.