Skip to content

Commit ea75a75

Browse files
Expose API for imported projects checks (#10761)
1 parent 8f475c6 commit ea75a75

15 files changed

+188
-89
lines changed

src/Build/BuildCheck/API/IBuildCheckRegistrationContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ public interface IBuildCheckRegistrationContext
1616
void RegisterEnvironmentVariableReadAction(Action<BuildCheckDataContext<EnvironmentVariableCheckData>> environmentVariableAction);
1717

1818
void RegisterBuildFinishedAction(Action<BuildCheckDataContext<BuildFinishedCheckData>> buildFinishedAction);
19+
20+
void RegisterProjectImportedAction(Action<BuildCheckDataContext<ProjectImportedCheckData>> projectImportedAction);
1921
}

src/Build/BuildCheck/Infrastructure/BuildCheckBuildEventHandler.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7-
using System.Text;
8-
using System.Threading.Tasks;
9-
using Microsoft.Build.Experimental.BuildCheck;
107
using Microsoft.Build.Experimental.BuildCheck.Acquisition;
11-
using Microsoft.Build.Experimental.BuildCheck.Utilities;
128
using Microsoft.Build.Framework;
139
using Microsoft.Build.Shared;
1410

@@ -44,6 +40,7 @@ internal BuildCheckBuildEventHandler(
4440
{ typeof(TaskFinishedEventArgs), (BuildEventArgs e) => HandleTaskFinishedEvent((TaskFinishedEventArgs)e) },
4541
{ typeof(TaskParameterEventArgs), (BuildEventArgs e) => HandleTaskParameterEvent((TaskParameterEventArgs)e) },
4642
{ typeof(BuildFinishedEventArgs), (BuildEventArgs e) => HandleBuildFinishedEvent((BuildFinishedEventArgs)e) },
43+
{ typeof(ProjectImportedEventArgs), (BuildEventArgs e) => HandleProjectImportedEvent((ProjectImportedEventArgs)e) },
4744
};
4845

4946
// During restore we'll wait only for restore to be done.
@@ -92,6 +89,7 @@ private void HandleProjectEvaluationStartedEvent(ProjectEvaluationStartedEventAr
9289
BuildCheckDataSource.EventArgs,
9390
checkContext,
9491
eventArgs.ProjectFile!);
92+
9593
_buildCheckManager.ProcessProjectEvaluationStarted(
9694
checkContext,
9795
eventArgs.ProjectFile!);
@@ -141,6 +139,11 @@ private void HandleEnvironmentVariableReadEvent(EnvironmentVariableReadEventArgs
141139
_checkContextFactory.CreateCheckContext(GetBuildEventContext(eventArgs)),
142140
eventArgs);
143141

142+
private void HandleProjectImportedEvent(ProjectImportedEventArgs eventArgs)
143+
=> _buildCheckManager.ProcessProjectImportedEventArgs(
144+
_checkContextFactory.CreateCheckContext(GetBuildEventContext(eventArgs)),
145+
eventArgs);
146+
144147
private bool IsMetaProjFile(string? projectFile) => projectFile?.EndsWith(".metaproj", StringComparison.OrdinalIgnoreCase) == true;
145148

146149
private readonly BuildCheckTracingData _tracingData = new BuildCheckTracingData();

src/Build/BuildCheck/Infrastructure/BuildCheckCentralContext.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ private record CallbackRegistry(
2929
List<(CheckWrapper, Action<BuildCheckDataContext<PropertyWriteData>>)> PropertyWriteActions,
3030
List<(CheckWrapper, Action<BuildCheckDataContext<ProjectRequestProcessingDoneData>>)> ProjectRequestProcessingDoneActions,
3131
List<(CheckWrapper, Action<BuildCheckDataContext<BuildFinishedCheckData>>)> BuildFinishedActions,
32-
List<(CheckWrapper, Action<BuildCheckDataContext<EnvironmentVariableCheckData>>)> EnvironmentVariableCheckDataActions)
32+
List<(CheckWrapper, Action<BuildCheckDataContext<EnvironmentVariableCheckData>>)> EnvironmentVariableCheckDataActions,
33+
List<(CheckWrapper, Action<BuildCheckDataContext<ProjectImportedCheckData>>)> ProjectImportedCheckDataActions)
3334
{
3435
public CallbackRegistry()
35-
: this([], [], [], [], [], [], [], [])
36+
: this([], [], [], [], [], [], [], [], [])
3637
{
3738
}
3839

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

6465
internal bool HasPropertyWriteActions => _globalCallbacks.PropertyWriteActions.Count > 0;
66+
6567
internal bool HasBuildFinishedActions => _globalCallbacks.BuildFinishedActions.Count > 0;
6668

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

95+
internal void RegisterProjectImportedAction(CheckWrapper check, Action<BuildCheckDataContext<ProjectImportedCheckData>> projectImportedAction)
96+
=> RegisterAction(check, projectImportedAction, _globalCallbacks.ProjectImportedCheckDataActions);
97+
9398
private void RegisterAction<T>(
9499
CheckWrapper wrappedCheck,
95100
Action<BuildCheckDataContext<T>> handler,
@@ -167,10 +172,14 @@ internal void RunProjectProcessingDoneActions(
167172
internal void RunBuildFinishedActions(
168173
BuildFinishedCheckData buildFinishedCheckData,
169174
ICheckContext checkContext,
170-
Action<CheckWrapper, ICheckContext, CheckConfigurationEffective[], BuildCheckResult>
171-
resultHandler)
172-
=> RunRegisteredActions(_globalCallbacks.BuildFinishedActions, buildFinishedCheckData,
173-
checkContext, resultHandler);
175+
Action<CheckWrapper, ICheckContext, CheckConfigurationEffective[], BuildCheckResult> resultHandler)
176+
=> RunRegisteredActions(_globalCallbacks.BuildFinishedActions, buildFinishedCheckData, checkContext, resultHandler);
177+
178+
internal void RunProjectImportedActions(
179+
ProjectImportedCheckData projectImportedCheckData,
180+
ICheckContext checkContext,
181+
Action<CheckWrapper, ICheckContext, CheckConfigurationEffective[], BuildCheckResult> resultHandler)
182+
=> RunRegisteredActions(_globalCallbacks.ProjectImportedCheckDataActions, projectImportedCheckData, checkContext, resultHandler);
174183

175184
private void RunRegisteredActions<T>(
176185
List<(CheckWrapper, Action<BuildCheckDataContext<T>>)> registeredCallbacks,

src/Build/BuildCheck/Infrastructure/BuildCheckForwardingLogger.cs

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Text;
8-
using System.Threading.Tasks;
9-
using Microsoft.Build.BackEnd.Logging;
10-
using Microsoft.Build.Experimental.BuildCheck.Acquisition;
116
using Microsoft.Build.Framework;
12-
using static Microsoft.Build.Experimental.BuildCheck.Infrastructure.BuildCheckManagerProvider;
137

148
namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;
159

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

3529
/// <summary>
36-
/// Set of events to be forwarded to <see cref="BuildCheckConnectorLogger"/>
30+
/// Set of events to be forwarded to <see cref="BuildCheckConnectorLogger"/>.
3731
/// </summary>
38-
private HashSet<Type> _eventsToForward = new HashSet<Type>
39-
{
32+
private HashSet<Type> _eventsToForward =
33+
[
4034
typeof(EnvironmentVariableReadEventArgs),
4135
typeof(BuildSubmissionStartedEventArgs),
4236
typeof(ProjectEvaluationFinishedEventArgs),
@@ -47,15 +41,13 @@ internal class BuildCheckForwardingLogger : IForwardingLogger
4741
typeof(BuildCheckAcquisitionEventArgs),
4842
typeof(TaskStartedEventArgs),
4943
typeof(TaskFinishedEventArgs),
50-
typeof(TaskParameterEventArgs)
51-
};
44+
typeof(TaskParameterEventArgs),
45+
typeof(ProjectImportedEventArgs),
46+
];
5247

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

55-
public void Initialize(IEventSource eventSource)
56-
{
57-
eventSource.AnyEventRaised += EventSource_AnyEventRaised;
58-
}
50+
public void Initialize(IEventSource eventSource) => eventSource.AnyEventRaised += EventSource_AnyEventRaised;
5951

6052
public void EventSource_AnyEventRaised(object sender, BuildEventArgs buildEvent)
6153
{
@@ -65,5 +57,7 @@ public void EventSource_AnyEventRaised(object sender, BuildEventArgs buildEvent)
6557
}
6658
}
6759

68-
public void Shutdown() { }
60+
public void Shutdown()
61+
{
62+
}
6963
}

src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Collections.Generic;
77
using System.Diagnostics;
88
using System.Linq;
9-
using System.Threading;
109
using Microsoft.Build.BackEnd;
1110
using Microsoft.Build.BackEnd.Logging;
1211
using Microsoft.Build.BuildCheck.Infrastructure;
@@ -59,7 +58,7 @@ public void ShutdownComponent()
5958
{
6059
_instance?.Shutdown();
6160
_instance = null;
62-
}
61+
}
6362

6463
internal sealed class BuildCheckManager : IBuildCheckManager, IBuildEngineDataRouter, IResultReporter
6564
{
@@ -94,7 +93,7 @@ public void SetDataSource(BuildCheckDataSource buildCheckDataSource)
9493
{
9594
_enabledDataSources[(int)buildCheckDataSource] = true;
9695
RegisterBuiltInChecks(buildCheckDataSource);
97-
}
96+
}
9897
stopwatch.Stop();
9998
_tracingReporter.AddSetDataSourceStats(stopwatch.Elapsed);
10099
}
@@ -344,11 +343,11 @@ public void RemoveThrottledChecks(ICheckContext checkContext)
344343
private void RemoveCheck(CheckFactoryContext checkToRemove)
345344
{
346345
_checkRegistry.Remove(checkToRemove);
347-
346+
348347
if (checkToRemove.MaterializedCheck is not null)
349348
{
350349
_buildCheckCentralContext.DeregisterCheck(checkToRemove.MaterializedCheck);
351-
_ruleTelemetryData.AddRange(checkToRemove.MaterializedCheck.GetRuleTelemetryData());
350+
_ruleTelemetryData.AddRange(checkToRemove.MaterializedCheck.GetRuleTelemetryData());
352351
checkToRemove.MaterializedCheck.Check.Dispose();
353352
}
354353
}
@@ -372,6 +371,18 @@ public void ProcessEvaluationFinishedEventArgs(
372371
FileClassifier.Shared.RegisterKnownImmutableLocations(getPropertyValue);
373372
}
374373

374+
// run it here to avoid the missed imports that can be reported before the checks registration
375+
if (_deferredProjectEvalIdToImportedProjects.TryGetValue(checkContext.BuildEventContext.EvaluationId, out HashSet<string>? importedProjects))
376+
{
377+
if (importedProjects != null && TryGetProjectFullPath(checkContext.BuildEventContext, out string projectPath))
378+
{
379+
foreach (string importedProject in importedProjects)
380+
{
381+
_buildEventsProcessor.ProcessProjectImportedEventArgs(checkContext, projectPath, importedProject);
382+
}
383+
}
384+
}
385+
375386
_buildEventsProcessor
376387
.ProcessEvaluationFinishedEventArgs(checkContext, evaluationFinishedEventArgs, propertiesLookup);
377388
}
@@ -392,6 +403,16 @@ public void ProcessEnvironmentVariableReadEventArgs(ICheckContext checkContext,
392403
}
393404
}
394405

406+
public void ProcessProjectImportedEventArgs(ICheckContext checkContext, ProjectImportedEventArgs projectImportedEventArgs)
407+
{
408+
if (string.IsNullOrEmpty(projectImportedEventArgs.ImportedProjectFile))
409+
{
410+
return;
411+
}
412+
413+
PropagateImport(checkContext.BuildEventContext.EvaluationId, projectImportedEventArgs.ProjectFile, projectImportedEventArgs.ImportedProjectFile);
414+
}
415+
395416
public void ProcessTaskStartedEventArgs(
396417
ICheckContext checkContext,
397418
TaskStartedEventArgs taskStartedEventArgs)
@@ -414,6 +435,7 @@ public void ProcessTaskParameterEventArgs(
414435
.ProcessTaskParameterEventArgs(checkContext, taskParameterEventArgs);
415436

416437
private readonly List<BuildCheckRuleTelemetryData> _ruleTelemetryData = [];
438+
417439
public BuildCheckTracingData CreateCheckTracingStats()
418440
{
419441
foreach (CheckFactoryContext checkFactoryContext in _checkRegistry)
@@ -445,6 +467,8 @@ public void FinalizeProcessing(LoggingContext loggingContext)
445467
private readonly ConcurrentDictionary<int, string> _projectsByInstanceId = new();
446468
private readonly ConcurrentDictionary<int, string> _projectsByEvaluationId = new();
447469

470+
private readonly Dictionary<int, HashSet<string>> _deferredProjectEvalIdToImportedProjects = new();
471+
448472
/// <summary>
449473
/// This method fetches the project full path from the context id.
450474
/// This is needed because the full path is needed for configuration and later for fetching configured checks
@@ -515,6 +539,10 @@ public void ProcessProjectEvaluationStarted(
515539
string projectFullPath)
516540
{
517541
_projectsByEvaluationId[checkContext.BuildEventContext.EvaluationId] = projectFullPath;
542+
if (!_deferredProjectEvalIdToImportedProjects.ContainsKey(checkContext.BuildEventContext.EvaluationId))
543+
{
544+
_deferredProjectEvalIdToImportedProjects.Add(checkContext.BuildEventContext.EvaluationId, [projectFullPath]);
545+
}
518546
}
519547

520548
/*
@@ -523,7 +551,6 @@ public void ProcessProjectEvaluationStarted(
523551
*
524552
*/
525553

526-
527554
public void EndProjectEvaluation(BuildEventContext buildEventContext)
528555
{
529556
}
@@ -548,6 +575,24 @@ public void StartProjectRequest(ICheckContext checkContext, string projectFullPa
548575
}
549576

550577
private readonly Dictionary<int, List<BuildEventArgs>> _deferredEvalDiagnostics = new();
578+
579+
/// <summary>
580+
/// Propagates a newly imported project file to all projects that import the original project file.
581+
/// This method ensures that if Project A imports Project B, and Project B now imports Project C,
582+
/// then Project A will also show Project C as an import.
583+
/// </summary>
584+
/// <param name="evaluationId">The evaluation id is associated with the root project path.</param>
585+
/// <param name="originalProjectFile">The path of the project file that is importing a new project.</param>
586+
/// <param name="newImportedProjectFile">The path of the newly imported project file.</param>
587+
private void PropagateImport(int evaluationId, string originalProjectFile, string newImportedProjectFile)
588+
{
589+
if (_deferredProjectEvalIdToImportedProjects.TryGetValue(evaluationId, out HashSet<string>? importedProjects)
590+
&& importedProjects.Contains(originalProjectFile))
591+
{
592+
importedProjects.Add(newImportedProjectFile);
593+
}
594+
}
595+
551596
void IResultReporter.ReportResult(BuildEventArgs eventArgs, ICheckContext checkContext)
552597
{
553598
// If we do not need to decide on promotability/demotability of warnings or we are ready to decide on those

src/Build/BuildCheck/Infrastructure/BuildEventsProcessor.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ internal void ProcessEnvironmentVariableReadEventArgs(ICheckContext checkContext
9696
_buildCheckCentralContext.RunEnvironmentVariableActions(checkData, checkContext, ReportResult);
9797
}
9898

99+
/// <summary>
100+
/// The method handles events associated with the ProjectImportedEventArgs.
101+
/// </summary>
102+
internal void ProcessProjectImportedEventArgs(ICheckContext checkContext, string projectPath, string importedProjectPath)
103+
{
104+
ProjectImportedCheckData checkData = new(importedProjectPath, projectPath, checkContext.BuildEventContext?.ProjectInstanceId);
105+
106+
_buildCheckCentralContext.RunProjectImportedActions(checkData, checkContext, ReportResult);
107+
}
108+
99109
internal void ProcessBuildDone(ICheckContext checkContext)
100110
{
101111
if (!_buildCheckCentralContext.HasBuildFinishedActions)

src/Build/BuildCheck/Infrastructure/CheckRegistrationContext.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using System.Threading;
6-
using Microsoft.Build.Experimental.BuildCheck;
75
using Microsoft.Build.Experimental.BuildCheck.Checks;
86

97
namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;
108

11-
internal sealed class CheckRegistrationContext(CheckWrapper checkWrapper, BuildCheckCentralContext buildCheckCentralContext) : IInternalCheckRegistrationContext
9+
internal sealed class CheckRegistrationContext(CheckWrapper checkWrapper, BuildCheckCentralContext buildCheckCentralContext)
10+
: IInternalCheckRegistrationContext
1211
{
1312
public void RegisterEnvironmentVariableReadAction(Action<BuildCheckDataContext<EnvironmentVariableCheckData>> environmentVariableAction) =>
1413
buildCheckCentralContext.RegisterEnvironmentVariableReadAction(checkWrapper, environmentVariableAction);
@@ -33,4 +32,7 @@ public void RegisterProjectRequestProcessingDoneAction(Action<BuildCheckDataCont
3332

3433
public void RegisterBuildFinishedAction(Action<BuildCheckDataContext<BuildFinishedCheckData>> buildFinishedAction)
3534
=> buildCheckCentralContext.RegisterBuildFinishedAction(checkWrapper, buildFinishedAction);
35+
36+
public void RegisterProjectImportedAction(Action<BuildCheckDataContext<ProjectImportedCheckData>> projectImportedAction) =>
37+
buildCheckCentralContext.RegisterProjectImportedAction(checkWrapper, projectImportedAction);
3638
}

0 commit comments

Comments
 (0)