Skip to content

Commit bd0b1e4

Browse files
Merge pull request #10014 from YuliiaKovalova/dev/ykovalova/analyzers_aquisition_experience
Analyzers acquisition experience
2 parents 47ba51c + b2d984e commit bd0b1e4

37 files changed

+781
-176
lines changed

src/Build.UnitTests/Evaluation/Expander_Tests.cs

+25-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
using System.Threading;
1313
using System.Xml;
1414
using Microsoft.Build.BackEnd;
15+
using Microsoft.Build.BackEnd.Logging;
1516
using Microsoft.Build.Collections;
17+
using Microsoft.Build.Engine.UnitTests;
1618
using Microsoft.Build.Evaluation;
1719
using Microsoft.Build.Execution;
20+
using Microsoft.Build.Experimental.BuildCheck;
1821
using Microsoft.Build.Framework;
1922
using Microsoft.Build.Internal;
2023
using Microsoft.Build.Shared;
@@ -23,7 +26,6 @@
2326
using Microsoft.Win32;
2427
using Shouldly;
2528
using Xunit;
26-
using Xunit.NetCore.Extensions;
2729
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
2830
using ProjectHelpers = Microsoft.Build.UnitTests.BackEnd.ProjectHelpers;
2931
using ProjectItemInstanceFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.ProjectItemInstanceFactory;
@@ -5055,5 +5057,27 @@ private static bool ICUModeAvailable()
50555057
int version = bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0];
50565058
return version != 0 && version == sortVersion.FullVersion;
50575059
}
5060+
5061+
[Fact]
5062+
public void PropertyFunctionRegisterAnalyzer()
5063+
{
5064+
using (var env = TestEnvironment.Create())
5065+
{
5066+
var logger = new MockLogger();
5067+
ILoggingService loggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);
5068+
loggingService.RegisterLogger(logger);
5069+
var loggingContext = new MockLoggingContext(
5070+
loggingService,
5071+
new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0, 0));
5072+
var dummyAssemblyFile = env.CreateFile(env.CreateFolder(), "test.dll");
5073+
5074+
var result = new Expander<ProjectPropertyInstance, ProjectItemInstance>(new PropertyDictionary<ProjectPropertyInstance>(), FileSystems.Default)
5075+
.ExpandIntoStringLeaveEscaped($"$([MSBuild]::RegisterAnalyzer({dummyAssemblyFile.Path}))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance, loggingContext);
5076+
5077+
result.ShouldBe(Boolean.TrueString);
5078+
_ = logger.AllBuildEvents.Select(be => be.ShouldBeOfType<BuildCheckAcquisitionEventArgs>());
5079+
logger.AllBuildEvents.Count.ShouldBe(1);
5080+
}
5081+
}
50585082
}
50595083
}

src/Build/AssemblyInfo.cs

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
[assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
2525
[assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Unittest, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")]
2626
[assembly: InternalsVisibleTo("Microsoft.Build.Tasks.Cop, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")]
27+
[assembly: InternalsVisibleTo("Microsoft.Build.BuildCheck.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")]
2728
// DO NOT expose Internals to "Microsoft.Build.UnitTests.OM.OrcasCompatibility" as this assembly is supposed to only see public interface
2829

2930
// This will enable passing the SafeDirectories flag to any P/Invoke calls/implementations within the assembly,

src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs

+8-4
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
namespace Microsoft.Build.BackEnd.Components.Logging
1313
{
1414
/// <summary>
15-
/// Logging context and helpers for evaluation logging
15+
/// Logging context and helpers for evaluation logging.
1616
/// </summary>
1717
internal class EvaluationLoggingContext : LoggingContext
1818
{
1919
private readonly string _projectFile;
2020

21-
public EvaluationLoggingContext(ILoggingService loggingService, BuildEventContext buildEventContext, string projectFile) :
22-
base(
21+
public EvaluationLoggingContext(ILoggingService loggingService, BuildEventContext buildEventContext, string projectFile)
22+
: base(
2323
loggingService,
2424
loggingService.CreateEvaluationBuildEventContext(buildEventContext.NodeId, buildEventContext.SubmissionId))
2525
{
@@ -33,8 +33,12 @@ public void LogProjectEvaluationStarted()
3333
}
3434

3535
/// <summary>
36-
/// Log that the project has finished
36+
/// Logs that the project evaluation has finished.
3737
/// </summary>
38+
/// <param name="globalProperties">Global properties used in the project evaluation.</param>
39+
/// <param name="properties">Properties used in the project evaluation.</param>
40+
/// <param name="items">Items used in the project evaluation.</param>
41+
/// <param name="profilerResult">Parameter contains the profiler result of the project evaluation.</param>
3842
internal void LogProjectEvaluationFinished(IEnumerable globalProperties, IEnumerable properties, IEnumerable items, ProfilerResult? profilerResult)
3943
{
4044
ErrorUtilities.VerifyThrow(IsValid, "invalid");

src/Build/BackEnd/Components/Logging/EventSourceSink.cs

+1
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ internal void UnregisterAllEventHandlers()
311311
StatusEventRaised = null;
312312
AnyEventRaised = null;
313313
TelemetryLogged = null;
314+
BuildCheckEventRaised = null;
314315
}
315316

316317
#endregion

src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ namespace Microsoft.Build.BuildCheck.Acquisition;
1313
// https://github.com/dotnet/msbuild/issues/9633
1414
// Acquisition
1515
// define the data that will be passed to the acquisition module (and remoted if needed)
16-
internal class AnalyzerAcquisitionData(string data)
16+
internal class AnalyzerAcquisitionData(string assemblyPath)
1717
{
18-
public string Data { get; init; } = data;
18+
public string AssemblyPath { get; init; } = assemblyPath;
1919
}
2020

2121
internal static class AnalyzerAcquisitionDataExtensions
2222
{
2323
public static AnalyzerAcquisitionData ToAnalyzerAcquisitionData(this BuildCheckAcquisitionEventArgs eventArgs) =>
24-
new(eventArgs.AcquisitionData);
24+
new(eventArgs.AcquisitionPath);
2525

26-
public static BuildCheckAcquisitionEventArgs ToBuildEventArgs(this AnalyzerAcquisitionData data) => new(data.Data);
26+
public static BuildCheckAcquisitionEventArgs ToBuildEventArgs(this AnalyzerAcquisitionData data) => new(data.AssemblyPath);
2727
}

src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs

+62-8
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,73 @@
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.BuildCheck.Analyzers;
7+
using System.Reflection;
8+
using Microsoft.Build.BackEnd.Logging;
109
using Microsoft.Build.BuildCheck.Infrastructure;
10+
using Microsoft.Build.Experimental.BuildCheck;
11+
using Microsoft.Build.Framework;
12+
using Microsoft.Build.Shared;
1113

1214
namespace Microsoft.Build.BuildCheck.Acquisition;
1315

14-
internal class BuildCheckAcquisitionModule
16+
internal class BuildCheckAcquisitionModule : IBuildCheckAcquisitionModule
1517
{
16-
private static T Construct<T>() where T : new() => new();
17-
public BuildAnalyzerFactory CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData)
18+
private readonly ILoggingService _loggingService;
19+
20+
internal BuildCheckAcquisitionModule(ILoggingService loggingService) => _loggingService = loggingService;
21+
22+
#if FEATURE_ASSEMBLYLOADCONTEXT
23+
/// <summary>
24+
/// AssemblyContextLoader used to load DLLs outside of msbuild.exe directory.
25+
/// </summary>
26+
private static readonly CoreClrAssemblyLoader s_coreClrAssemblyLoader = new();
27+
#endif
28+
29+
/// <summary>
30+
/// Creates a list of factory delegates for building analyzer rules instances from a given assembly path.
31+
/// </summary>
32+
public List<BuildAnalyzerFactory> CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext)
1833
{
19-
// Acquisition module - https://github.com/dotnet/msbuild/issues/9633
20-
return Construct<SharedOutputPathAnalyzer>;
34+
var analyzersFactories = new List<BuildAnalyzerFactory>();
35+
36+
try
37+
{
38+
Assembly? assembly = null;
39+
#if FEATURE_ASSEMBLYLOADCONTEXT
40+
assembly = s_coreClrAssemblyLoader.LoadFromPath(analyzerAcquisitionData.AssemblyPath);
41+
#else
42+
assembly = Assembly.LoadFrom(analyzerAcquisitionData.AssemblyPath);
43+
#endif
44+
45+
IEnumerable<Type> analyzerTypes = assembly.GetExportedTypes().Where(t => typeof(BuildAnalyzer).IsAssignableFrom(t));
46+
47+
foreach (Type analyzerType in analyzerTypes)
48+
{
49+
if (Activator.CreateInstance(analyzerType) is BuildAnalyzer instance)
50+
{
51+
analyzersFactories.Add(() => instance);
52+
}
53+
else
54+
{
55+
throw new InvalidOperationException($"Failed to create an instance of type {analyzerType.FullName} as BuildAnalyzer.");
56+
}
57+
}
58+
}
59+
catch (ReflectionTypeLoadException ex)
60+
{
61+
if (ex.LoaderExceptions.Length != 0)
62+
{
63+
foreach (Exception? loaderException in ex.LoaderExceptions)
64+
{
65+
_loggingService.LogComment(buildEventContext, MessageImportance.Normal, "CustomAnalyzerFailedRuleLoading", loaderException?.Message);
66+
}
67+
}
68+
}
69+
catch (Exception ex)
70+
{
71+
_loggingService.LogComment(buildEventContext, MessageImportance.Normal, "CustomAnalyzerFailedRuleLoading", ex?.Message);
72+
}
73+
74+
return analyzersFactories;
2175
}
2276
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.Build.BuildCheck.Infrastructure;
6+
using Microsoft.Build.Framework;
7+
8+
namespace Microsoft.Build.BuildCheck.Acquisition;
9+
10+
internal interface IBuildCheckAcquisitionModule
11+
{
12+
/// <summary>
13+
/// Creates a list of factory delegates for building analyzer rules instances from a given assembly path.
14+
/// </summary>
15+
List<BuildAnalyzerFactory> CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext);
16+
}

src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs

+58-48
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,32 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Diagnostics;
76
using System.Linq;
87
using Microsoft.Build.BackEnd.Logging;
98
using Microsoft.Build.BuildCheck.Acquisition;
10-
using Microsoft.Build.BuildCheck.Logging;
119
using Microsoft.Build.Experimental.BuildCheck;
1210
using Microsoft.Build.Framework;
11+
using static Microsoft.Build.BuildCheck.Infrastructure.BuildCheckManagerProvider;
1312

1413
namespace Microsoft.Build.BuildCheck.Infrastructure;
15-
internal sealed class BuildCheckConnectorLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory, IBuildCheckManager buildCheckManager)
16-
: ILogger
14+
15+
internal sealed class BuildCheckConnectorLogger : ILogger
1716
{
17+
private readonly Dictionary<Type, Action<BuildEventArgs>> _eventHandlers;
18+
private readonly IBuildCheckManager _buildCheckManager;
19+
private readonly IBuildAnalysisLoggingContextFactory _loggingContextFactory;
20+
21+
internal BuildCheckConnectorLogger(
22+
IBuildAnalysisLoggingContextFactory loggingContextFactory,
23+
IBuildCheckManager buildCheckManager)
24+
{
25+
_buildCheckManager = buildCheckManager;
26+
_loggingContextFactory = loggingContextFactory;
27+
_eventHandlers = GetBuildEventHandlers();
28+
}
29+
1830
public LoggerVerbosity Verbosity { get; set; }
31+
1932
public string? Parameters { get; set; }
2033

2134
public void Initialize(IEventSource eventSource)
@@ -29,70 +42,67 @@ public void Initialize(IEventSource eventSource)
2942
}
3043
}
3144

32-
private void EventSource_AnyEventRaised(object sender, BuildEventArgs e)
45+
public void Shutdown()
3346
{
34-
if (e is ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs)
35-
{
36-
if (projectEvaluationFinishedEventArgs.ProjectFile?.EndsWith(".metaproj") ?? false)
37-
{
38-
return;
39-
}
40-
41-
buildCheckManager.ProcessEvaluationFinishedEventArgs(
42-
loggingContextFactory.CreateLoggingContext(e.BuildEventContext!),
43-
projectEvaluationFinishedEventArgs);
47+
}
4448

45-
buildCheckManager.EndProjectEvaluation(BuildCheckDataSource.EventArgs, e.BuildEventContext!);
46-
}
47-
else if (e is ProjectEvaluationStartedEventArgs projectEvaluationStartedEventArgs)
48-
{
49-
// Skip autogenerated transient projects (as those are not user projects to be analyzed)
50-
if (projectEvaluationStartedEventArgs.ProjectFile?.EndsWith(".metaproj") ?? false)
51-
{
52-
return;
53-
}
54-
55-
buildCheckManager.StartProjectEvaluation(BuildCheckDataSource.EventArgs, e.BuildEventContext!,
56-
projectEvaluationStartedEventArgs.ProjectFile!);
57-
}
58-
else if (e is ProjectStartedEventArgs projectStartedEvent)
49+
private void HandleProjectEvaluationFinishedEvent(ProjectEvaluationFinishedEventArgs eventArgs)
50+
{
51+
if (!IsMetaProjFile(eventArgs.ProjectFile))
5952
{
60-
buildCheckManager.StartProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!);
53+
_buildCheckManager.ProcessEvaluationFinishedEventArgs(
54+
_loggingContextFactory.CreateLoggingContext(eventArgs.BuildEventContext!),
55+
eventArgs);
56+
57+
_buildCheckManager.EndProjectEvaluation(BuildCheckDataSource.EventArgs, eventArgs.BuildEventContext!);
6158
}
62-
else if (e is ProjectFinishedEventArgs projectFinishedEventArgs)
59+
}
60+
61+
private void HandleProjectEvaluationStartedEvent(ProjectEvaluationStartedEventArgs eventArgs)
62+
{
63+
if (!IsMetaProjFile(eventArgs.ProjectFile))
6364
{
64-
buildCheckManager.EndProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!);
65+
_buildCheckManager.StartProjectEvaluation(BuildCheckDataSource.EventArgs, eventArgs.BuildEventContext!, eventArgs.ProjectFile!);
6566
}
66-
else if (e is BuildCheckEventArgs buildCheckBuildEventArgs)
67+
}
68+
69+
private bool IsMetaProjFile(string? projectFile) => !string.IsNullOrEmpty(projectFile) && projectFile!.EndsWith(".metaproj", StringComparison.OrdinalIgnoreCase);
70+
71+
private void EventSource_AnyEventRaised(object sender, BuildEventArgs e)
72+
{
73+
if (_eventHandlers.TryGetValue(e.GetType(), out Action<BuildEventArgs>? handler))
6774
{
68-
if (buildCheckBuildEventArgs is BuildCheckTracingEventArgs tracingEventArgs)
69-
{
70-
_stats.Merge(tracingEventArgs.TracingData, (span1, span2) => span1 + span2);
71-
}
72-
else if (buildCheckBuildEventArgs is BuildCheckAcquisitionEventArgs acquisitionEventArgs)
73-
{
74-
buildCheckManager.ProcessAnalyzerAcquisition(acquisitionEventArgs.ToAnalyzerAcquisitionData());
75-
}
75+
handler(e);
7676
}
7777
}
7878

7979
private readonly Dictionary<string, TimeSpan> _stats = new Dictionary<string, TimeSpan>();
8080

8181
private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e)
8282
{
83-
_stats.Merge(buildCheckManager.CreateTracingStats(), (span1, span2) => span1 + span2);
83+
_stats.Merge(_buildCheckManager.CreateTracingStats(), (span1, span2) => span1 + span2);
8484
string msg = string.Join(Environment.NewLine, _stats.Select(a => a.Key + ": " + a.Value));
8585

86-
BuildEventContext buildEventContext = e.BuildEventContext ?? new BuildEventContext(
87-
BuildEventContext.InvalidNodeId, BuildEventContext.InvalidTargetId,
88-
BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId);
86+
BuildEventContext buildEventContext = e.BuildEventContext
87+
?? new BuildEventContext(
88+
BuildEventContext.InvalidNodeId,
89+
BuildEventContext.InvalidTargetId,
90+
BuildEventContext.InvalidProjectContextId,
91+
BuildEventContext.InvalidTaskId);
8992

90-
LoggingContext loggingContext = loggingContextFactory.CreateLoggingContext(buildEventContext);
93+
LoggingContext loggingContext = _loggingContextFactory.CreateLoggingContext(buildEventContext);
9194

9295
// Tracing: https://github.com/dotnet/msbuild/issues/9629
9396
loggingContext.LogCommentFromText(MessageImportance.High, msg);
9497
}
9598

96-
public void Shutdown()
97-
{ }
99+
private Dictionary<Type, Action<BuildEventArgs>> GetBuildEventHandlers() => new()
100+
{
101+
{ typeof(ProjectEvaluationFinishedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationFinishedEvent((ProjectEvaluationFinishedEventArgs) e) },
102+
{ typeof(ProjectEvaluationStartedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationStartedEvent((ProjectEvaluationStartedEventArgs) e) },
103+
{ typeof(ProjectStartedEventArgs), (BuildEventArgs e) => _buildCheckManager.StartProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) },
104+
{ typeof(ProjectFinishedEventArgs), (BuildEventArgs e) => _buildCheckManager.EndProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) },
105+
{ typeof(BuildCheckTracingEventArgs), (BuildEventArgs e) => _stats.Merge(((BuildCheckTracingEventArgs)e).TracingData, (span1, span2) => span1 + span2) },
106+
{ typeof(BuildCheckAcquisitionEventArgs), (BuildEventArgs e) => _buildCheckManager.ProcessAnalyzerAcquisition(((BuildCheckAcquisitionEventArgs)e).ToAnalyzerAcquisitionData(), e.BuildEventContext!) },
107+
};
98108
}

0 commit comments

Comments
 (0)