From a59fe1e1ba7394a72aef92b6bacee0e2240b4de4 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Mon, 12 Feb 2024 13:38:00 +0100 Subject: [PATCH 01/58] Initial demonstration version --- MSBuild.sln | 50 +++++++ src/Analyzers/API/BuildAnalysisContext.cs | 28 ++++ .../API/BuildAnalysisLoggerFactory.cs | 20 +++ src/Analyzers/API/BuildAnalysisResult.cs | 115 ++++++++++++++++ .../API/BuildAnalysisResultSeverity.cs | 11 ++ src/Analyzers/API/BuildAnalysisRule.cs | 27 ++++ src/Analyzers/API/BuildAnalyzer.cs | 15 +++ .../API/BuildAnalyzerConfiguration.cs | 26 ++++ src/Analyzers/API/ConfigurationContext.cs | 16 +++ src/Analyzers/API/EvaluationAnalysisScope.cs | 12 ++ src/Analyzers/API/InvocationConcurrency.cs | 10 ++ src/Analyzers/API/LifeTimeScope.cs | 11 ++ src/Analyzers/API/PerformanceWeightClass.cs | 11 ++ .../Analyzers/SharedOutputPathAnalyzer.cs | 99 ++++++++++++++ .../AnalyzersConnectorLogger.cs | 52 ++++++++ .../Infrastructure/BuildAnalysisManager.cs | 111 ++++++++++++++++ .../BuildAnalyzerConfigurationInternal.cs | 24 ++++ .../Infrastructure/BuildAnalyzerContext.cs | 99 ++++++++++++++ .../Infrastructure/ConfigurationProvider.cs | 114 ++++++++++++++++ .../Microsoft.Build.Analyzers.csproj | 21 +++ .../OM/EvaluatedPropertiesContext.cs | 20 +++ src/Analyzers/OM/ParsedItemsContext.cs | 56 ++++++++ src/Analyzers/README.md | 3 + src/Analyzers/Utilities/IsExternalInit.cs | 7 + .../Analyzers/EndToEndTests.cs | 124 ++++++++++++++++++ src/Build/AssemblyInfo.cs | 1 + .../BackEnd/BuildManager/BuildManager.cs | 19 ++- .../BackEnd/BuildManager/BuildParameters.cs | 7 + src/Build/ElementLocation/ElementLocation.cs | 2 +- .../Analyzers/AnalyzerLoggingContext.cs | 27 ++++ .../AnalyzerLoggingContextFactory.cs | 18 +++ src/Build/Microsoft.Build.csproj | 8 +- .../Analyzers/IBuildAnalysisLoggerFactory.cs | 11 ++ .../Analyzers/IBuildAnalysisLoggingContext.cs | 7 + .../IBuildAnalysisLoggingContextFactory.cs | 11 ++ .../Analyzers/IBuildAnalysisManager.cs | 18 +++ src/Framework/Properties/AssemblyInfo.cs | 1 + .../CommandLineSwitches_Tests.cs | 1 + src/MSBuild/MSBuild.csproj | 1 + src/MSBuild/XMake.cs | 17 +++ src/UnitTests.Shared/RunnerUtilities.cs | 21 ++- 41 files changed, 1244 insertions(+), 8 deletions(-) create mode 100644 src/Analyzers/API/BuildAnalysisContext.cs create mode 100644 src/Analyzers/API/BuildAnalysisLoggerFactory.cs create mode 100644 src/Analyzers/API/BuildAnalysisResult.cs create mode 100644 src/Analyzers/API/BuildAnalysisResultSeverity.cs create mode 100644 src/Analyzers/API/BuildAnalysisRule.cs create mode 100644 src/Analyzers/API/BuildAnalyzer.cs create mode 100644 src/Analyzers/API/BuildAnalyzerConfiguration.cs create mode 100644 src/Analyzers/API/ConfigurationContext.cs create mode 100644 src/Analyzers/API/EvaluationAnalysisScope.cs create mode 100644 src/Analyzers/API/InvocationConcurrency.cs create mode 100644 src/Analyzers/API/LifeTimeScope.cs create mode 100644 src/Analyzers/API/PerformanceWeightClass.cs create mode 100644 src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs create mode 100644 src/Analyzers/Infrastructure/AnalyzersConnectorLogger.cs create mode 100644 src/Analyzers/Infrastructure/BuildAnalysisManager.cs create mode 100644 src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs create mode 100644 src/Analyzers/Infrastructure/BuildAnalyzerContext.cs create mode 100644 src/Analyzers/Infrastructure/ConfigurationProvider.cs create mode 100644 src/Analyzers/Microsoft.Build.Analyzers.csproj create mode 100644 src/Analyzers/OM/EvaluatedPropertiesContext.cs create mode 100644 src/Analyzers/OM/ParsedItemsContext.cs create mode 100644 src/Analyzers/README.md create mode 100644 src/Analyzers/Utilities/IsExternalInit.cs create mode 100644 src/Build.UnitTests/Analyzers/EndToEndTests.cs create mode 100644 src/Build/Logging/Analyzers/AnalyzerLoggingContext.cs create mode 100644 src/Build/Logging/Analyzers/AnalyzerLoggingContextFactory.cs create mode 100644 src/Framework/Analyzers/IBuildAnalysisLoggerFactory.cs create mode 100644 src/Framework/Analyzers/IBuildAnalysisLoggingContext.cs create mode 100644 src/Framework/Analyzers/IBuildAnalysisLoggingContextFactory.cs create mode 100644 src/Framework/Analyzers/IBuildAnalysisManager.cs diff --git a/MSBuild.sln b/MSBuild.sln index 5c8326406f6..e441e1779ed 100644 --- a/MSBuild.sln +++ b/MSBuild.sln @@ -80,6 +80,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSBuild.VSSetup.Arm64", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.UnitTests.Shared", "src\UnitTests.Shared\Microsoft.Build.UnitTests.Shared.csproj", "{52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Analyzers", "src\Analyzers\Microsoft.Build.Analyzers.csproj", "{512E01F7-2899-433B-93E2-D63B43AF0420}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -864,6 +866,54 @@ Global {52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}.Release|x64.Build.0 = Release|x64 {52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}.Release|x86.ActiveCfg = Release|Any CPU {52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}.Release|x86.Build.0 = Release|Any CPU + {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|Any CPU.ActiveCfg = Release-MONO|Any CPU + {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|Any CPU.Build.0 = Release-MONO|Any CPU + {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|ARM64.ActiveCfg = Release-MONO|arm64 + {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|ARM64.Build.0 = Release-MONO|arm64 + {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|x64.ActiveCfg = Release-MONO|x64 + {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|x64.Build.0 = Release-MONO|x64 + {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|x86.ActiveCfg = Release-MONO|Any CPU + {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|x86.Build.0 = Release-MONO|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|Any CPU.Build.0 = Debug|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|ARM64.ActiveCfg = Debug|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|ARM64.Build.0 = Debug|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|x64.ActiveCfg = Debug|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|x64.Build.0 = Debug|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|x86.ActiveCfg = Debug|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|x86.Build.0 = Debug|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|Any CPU.ActiveCfg = Debug-MONO|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|Any CPU.Build.0 = Debug-MONO|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|ARM64.ActiveCfg = Debug-MONO|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|ARM64.Build.0 = Debug-MONO|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|x64.ActiveCfg = Debug-MONO|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|x64.Build.0 = Debug-MONO|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|x86.ActiveCfg = Debug-MONO|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|x86.Build.0 = Debug-MONO|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|Any CPU.ActiveCfg = MachineIndependent|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|Any CPU.Build.0 = MachineIndependent|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|ARM64.ActiveCfg = MachineIndependent|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|ARM64.Build.0 = MachineIndependent|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|x64.ActiveCfg = MachineIndependent|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|x64.Build.0 = MachineIndependent|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|x86.ActiveCfg = MachineIndependent|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|x86.Build.0 = MachineIndependent|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|Any CPU.ActiveCfg = Release|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|Any CPU.Build.0 = Release|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|ARM64.ActiveCfg = Release|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|ARM64.Build.0 = Release|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|x64.ActiveCfg = Release|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|x64.Build.0 = Release|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|x86.ActiveCfg = Release|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|x86.Build.0 = Release|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|Any CPU.ActiveCfg = Release-MONO|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|Any CPU.Build.0 = Release-MONO|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|ARM64.ActiveCfg = Release-MONO|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|ARM64.Build.0 = Release-MONO|arm64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x64.ActiveCfg = Release-MONO|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x64.Build.0 = Release-MONO|x64 + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x86.ActiveCfg = Release-MONO|Any CPU + {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x86.Build.0 = Release-MONO|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Analyzers/API/BuildAnalysisContext.cs b/src/Analyzers/API/BuildAnalysisContext.cs new file mode 100644 index 00000000000..098ae5470f6 --- /dev/null +++ b/src/Analyzers/API/BuildAnalysisContext.cs @@ -0,0 +1,28 @@ +// 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; +using Microsoft.Build.Analyzers.Infrastructure; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Experimental; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Experimental; + +public class BuildAnalysisContext +{ + private protected readonly LoggingContext _loggingContext; + + internal BuildAnalysisContext(LoggingContext loggingContext) => _loggingContext = loggingContext; + + public void ReportResult(BuildAnalysisResult result) + { + BuildEventArgs eventArgs = result.ToEventArgs(ConfigurationProvider.GetMergedConfiguration(result.BuildAnalysisRule).Severity); + eventArgs.BuildEventContext = _loggingContext.BuildEventContext; + _loggingContext.LogBuildEvent(eventArgs); + } +} diff --git a/src/Analyzers/API/BuildAnalysisLoggerFactory.cs b/src/Analyzers/API/BuildAnalysisLoggerFactory.cs new file mode 100644 index 00000000000..4ed8d920770 --- /dev/null +++ b/src/Analyzers/API/BuildAnalysisLoggerFactory.cs @@ -0,0 +1,20 @@ +// 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; +using Microsoft.Build.Analyzers.Infrastructure; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Experimental; + +public class BuildAnalysisLoggerFactory : IBuildAnalysisLoggerFactory +{ + public ILogger CreateBuildAnalysisLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory) + { + return new AnalyzersConnectorLogger(loggingContextFactory, BuildAnalysisManager.Instance); + } +} diff --git a/src/Analyzers/API/BuildAnalysisResult.cs b/src/Analyzers/API/BuildAnalysisResult.cs new file mode 100644 index 00000000000..0b5fc893d06 --- /dev/null +++ b/src/Analyzers/API/BuildAnalysisResult.cs @@ -0,0 +1,115 @@ +// 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.IO; +using Microsoft.Build.Construction; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Experimental; + +public class BuildAnalysisResult +{ + public static BuildAnalysisResult Create(BuildAnalysisRule rule, ElementLocation location, params string[] messageArgs) + { + return new BuildAnalysisResult(rule, location, messageArgs); + } + + public BuildAnalysisResult(BuildAnalysisRule buildAnalysisRule, ElementLocation location, string[] messageArgs) + { + BuildAnalysisRule = buildAnalysisRule; + Location = location; + MessageArgs = messageArgs; + } + + internal BuildEventArgs ToEventArgs(BuildAnalysisResultSeverity severity) + => severity switch + { + BuildAnalysisResultSeverity.Info => new BuildAnalysisResultMessage(this), + BuildAnalysisResultSeverity.Warning => new BuildAnalysisResultWarning(this), + BuildAnalysisResultSeverity.Error => new BuildAnalysisResultError(this), + _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null), + }; + + public BuildAnalysisRule BuildAnalysisRule { get; } + public ElementLocation Location { get; } + public string[] MessageArgs { get; } + + private string? _message; + public string Message => _message ??= $"{(Equals(Location ?? ElementLocation.EmptyLocation, ElementLocation.EmptyLocation) ? string.Empty : (Location!.LocationString + ": "))}{BuildAnalysisRule.Id}: {string.Format(BuildAnalysisRule.MessageFormat, MessageArgs)}"; +} + +public sealed class BuildAnalysisResultWarning : BuildWarningEventArgs +{ + public BuildAnalysisResultWarning(BuildAnalysisResult result) + { + this.Message = result.Message; + } + + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + + writer.Write(Message!); + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + + Message = reader.ReadString(); + } + + public override string? Message { get; protected set; } +} + +public sealed class BuildAnalysisResultError : BuildErrorEventArgs +{ + public BuildAnalysisResultError(BuildAnalysisResult result) + { + this.Message = result.Message; + } + + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + + writer.Write(Message!); + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + + Message = reader.ReadString(); + } + + public override string? Message { get; protected set; } +} + +public sealed class BuildAnalysisResultMessage : BuildMessageEventArgs +{ + public BuildAnalysisResultMessage(BuildAnalysisResult result) + { + this.Message = result.Message; + } + + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + + writer.Write(Message!); + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + + Message = reader.ReadString(); + } + + public override string? Message { get; protected set; } +} diff --git a/src/Analyzers/API/BuildAnalysisResultSeverity.cs b/src/Analyzers/API/BuildAnalysisResultSeverity.cs new file mode 100644 index 00000000000..d3eeb7c7bd1 --- /dev/null +++ b/src/Analyzers/API/BuildAnalysisResultSeverity.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public enum BuildAnalysisResultSeverity +{ + Info, + Warning, + Error, +} diff --git a/src/Analyzers/API/BuildAnalysisRule.cs b/src/Analyzers/API/BuildAnalysisRule.cs new file mode 100644 index 00000000000..8858b8dc44a --- /dev/null +++ b/src/Analyzers/API/BuildAnalysisRule.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public class BuildAnalysisRule +{ + public BuildAnalysisRule(string id, string title, string description, string category, string messageFormat, + BuildAnalyzerConfiguration defaultConfiguration) + { + Id = id; + Title = title; + Description = description; + Category = category; + MessageFormat = messageFormat; + DefaultConfiguration = defaultConfiguration; + } + + public string Id { get; } + public string Title { get; } + public string Description { get; } + + // or maybe enum? eval, syntax, etc + public string Category { get; } + public string MessageFormat { get; } + public BuildAnalyzerConfiguration DefaultConfiguration { get; } +} diff --git a/src/Analyzers/API/BuildAnalyzer.cs b/src/Analyzers/API/BuildAnalyzer.cs new file mode 100644 index 00000000000..cbe39d9c8a6 --- /dev/null +++ b/src/Analyzers/API/BuildAnalyzer.cs @@ -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.Collections.Immutable; + +namespace Microsoft.Build.Experimental; + +public abstract class BuildAnalyzer +{ + public abstract string FriendlyName { get; } + public abstract ImmutableArray SupportedRules { get; } + public abstract void Initialize(ConfigurationContext configurationContext); + + public abstract void RegisterActions(IBuildAnalyzerContext context); +} diff --git a/src/Analyzers/API/BuildAnalyzerConfiguration.cs b/src/Analyzers/API/BuildAnalyzerConfiguration.cs new file mode 100644 index 00000000000..b9479d88cff --- /dev/null +++ b/src/Analyzers/API/BuildAnalyzerConfiguration.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public class BuildAnalyzerConfiguration +{ + public static BuildAnalyzerConfiguration Default { get; } = new() + { + LifeTimeScope = Experimental.LifeTimeScope.PerProject, + SupportedInvocationConcurrency = InvocationConcurrency.Parallel, + PerformanceWeightClass = Experimental.PerformanceWeightClass.Normal, + EvaluationAnalysisScope = Experimental.EvaluationAnalysisScope.AnalyzedProjectOnly, + Severity = BuildAnalysisResultSeverity.Info, + IsEnabled = false, + }; + + public static BuildAnalyzerConfiguration Null { get; } = new(); + + public LifeTimeScope? LifeTimeScope { get; internal init; } + public InvocationConcurrency? SupportedInvocationConcurrency { get; internal init; } + public PerformanceWeightClass? PerformanceWeightClass { get; internal init; } + public EvaluationAnalysisScope? EvaluationAnalysisScope { get; internal init; } + public BuildAnalysisResultSeverity? Severity { get; internal init; } + public bool? IsEnabled { get; internal init; } +} diff --git a/src/Analyzers/API/ConfigurationContext.cs b/src/Analyzers/API/ConfigurationContext.cs new file mode 100644 index 00000000000..69e2ec43e28 --- /dev/null +++ b/src/Analyzers/API/ConfigurationContext.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Build.Experimental; + +/// +/// Holder of an optional configuration from .editorconfig file (not recognized by infrastructure) +/// +public class ConfigurationContext +{ + public static ConfigurationContext Null { get; } = new(); + + public IReadOnlyDictionary? ConfigurationData { get; init; } +} diff --git a/src/Analyzers/API/EvaluationAnalysisScope.cs b/src/Analyzers/API/EvaluationAnalysisScope.cs new file mode 100644 index 00000000000..3fafe62e4d6 --- /dev/null +++ b/src/Analyzers/API/EvaluationAnalysisScope.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public enum EvaluationAnalysisScope +{ + AnalyzedProjectOnly, + AnalyzedProjectWithImportsFromCurrentWorkTree, + AnalyzedProjectWithImportsWithoutSdks, + AnalyzedProjectWithAllImports, +} diff --git a/src/Analyzers/API/InvocationConcurrency.cs b/src/Analyzers/API/InvocationConcurrency.cs new file mode 100644 index 00000000000..f4e8bc61ad6 --- /dev/null +++ b/src/Analyzers/API/InvocationConcurrency.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public enum InvocationConcurrency +{ + Sequential, + Parallel, +} diff --git a/src/Analyzers/API/LifeTimeScope.cs b/src/Analyzers/API/LifeTimeScope.cs new file mode 100644 index 00000000000..34f85355050 --- /dev/null +++ b/src/Analyzers/API/LifeTimeScope.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public enum LifeTimeScope +{ + Stateless, + PerProject, + PerBuild, +} diff --git a/src/Analyzers/API/PerformanceWeightClass.cs b/src/Analyzers/API/PerformanceWeightClass.cs new file mode 100644 index 00000000000..ea2d5aa2469 --- /dev/null +++ b/src/Analyzers/API/PerformanceWeightClass.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public enum PerformanceWeightClass +{ + Lightweight, + Normal, + Heavyweight, +} diff --git a/src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs b/src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs new file mode 100644 index 00000000000..a49e4769ed9 --- /dev/null +++ b/src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs @@ -0,0 +1,99 @@ +// 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.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using Microsoft.Build.Construction; +using Microsoft.Build.Experimental; + +namespace Microsoft.Build.Analyzers.Analyzers; + +// Some background on ids: +// * https://github.com/dotnet/roslyn-analyzers/blob/main/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +// * https://github.com/dotnet/roslyn/issues/40351 +// +// quick suggestion now - let's force external ids to start with 'X', for ours - avoid 'MSB' +// maybe - BS - build styling; BA - build authoring; BE - build execution/environment; BC - build configuration + +internal sealed class SharedOutputPathAnalyzer : BuildAnalyzer +{ + public static BuildAnalysisRule SupportedRule = new BuildAnalysisRule("BC0101", "ConflictingOutputPath", + "Two projects should not share their OutputPath nor IntermediateOutputPath locations", "Configuration", + "Projects {0} and {1} have conflicting output paths: {2}.", + new BuildAnalyzerConfiguration() { Severity = BuildAnalysisResultSeverity.Warning, IsEnabled = true }); + + public override string FriendlyName => "MSBuild.SharedOutputPathAnalyzer"; + + public override ImmutableArray SupportedRules { get; } =[SupportedRule]; + + public override void Initialize(ConfigurationContext configurationContext) + { + /* This is it - no custom configuration */ + } + + public override void RegisterActions(IBuildAnalyzerContext context) + { + context.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction); + } + + private readonly Dictionary _projectsPerOutputPath = new(StringComparer.CurrentCultureIgnoreCase); + private readonly HashSet _projects = new(StringComparer.CurrentCultureIgnoreCase); + + private void EvaluatedPropertiesAction(EvaluatedPropertiesContext context) + { + if (!_projects.Add(context.ProjectFilePath)) + { + return; + } + + string? binPath, objPath; + + context.EvaluatedProperties.TryGetValue("OutputPath", out binPath); + context.EvaluatedProperties.TryGetValue("IntermediateOutputPath", out objPath); + + string? absoluteBinPath = CheckAndAddFullOutputPath(binPath, context); + if ( + !string.IsNullOrEmpty(objPath) && !string.IsNullOrEmpty(absoluteBinPath) && + !objPath.Equals(binPath, StringComparison.CurrentCultureIgnoreCase) + && !objPath.Equals(absoluteBinPath, StringComparison.CurrentCultureIgnoreCase) + ) + { + CheckAndAddFullOutputPath(objPath, context); + } + } + + private string? CheckAndAddFullOutputPath(string? path, EvaluatedPropertiesContext context) + { + if (string.IsNullOrEmpty(path)) + { + return path; + } + + string projectPath = context.ProjectFilePath; + + if (!Path.IsPathRooted(path)) + { + path = Path.Combine(Path.GetDirectoryName(projectPath)!, path); + } + + if (_projectsPerOutputPath.TryGetValue(path!, out string? conflictingProject)) + { + context.ReportResult(BuildAnalysisResult.Create( + SupportedRule, + // TODO: let's support transmitting locations of specific properties + ElementLocation.EmptyLocation, + Path.GetFileName(projectPath), + Path.GetFileName(conflictingProject), + path!)); + } + else + { + _projectsPerOutputPath[path!] = projectPath; + } + + return path; + } +} diff --git a/src/Analyzers/Infrastructure/AnalyzersConnectorLogger.cs b/src/Analyzers/Infrastructure/AnalyzersConnectorLogger.cs new file mode 100644 index 00000000000..eb3170f1d52 --- /dev/null +++ b/src/Analyzers/Infrastructure/AnalyzersConnectorLogger.cs @@ -0,0 +1,52 @@ +// 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.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Experimental; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Analyzers.Infrastructure; +internal sealed class AnalyzersConnectorLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory, IBuildAnalysisManager buildAnalysisManager) + : ILogger +{ + public LoggerVerbosity Verbosity { get; set; } + public string? Parameters { get; set; } + + public void Initialize(IEventSource eventSource) + { + eventSource.AnyEventRaised += EventSource_AnyEventRaised; + } + + private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) + { + if (e is ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs && + !(projectEvaluationFinishedEventArgs.ProjectFile?.EndsWith(".metaproj") ?? false)) + { + // Debugger.Launch(); + + try + { + buildAnalysisManager.ProcessEvaluationFinishedEventArgs( + loggingContextFactory.CreateLoggingContext(e.BuildEventContext!), + projectEvaluationFinishedEventArgs); + } + catch (Exception exception) + { + Debugger.Launch(); + Console.WriteLine(exception); + throw; + } + } + // here handling of other event types + } + + public void Shutdown() + { + // TODO: here flush the tracing stats: https://github.com/dotnet/msbuild/issues/9629 + } +} diff --git a/src/Analyzers/Infrastructure/BuildAnalysisManager.cs b/src/Analyzers/Infrastructure/BuildAnalysisManager.cs new file mode 100644 index 00000000000..3693dec4f44 --- /dev/null +++ b/src/Analyzers/Infrastructure/BuildAnalysisManager.cs @@ -0,0 +1,111 @@ +// 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.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Analyzers.Analyzers; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using Microsoft.Build.Experimental; +using Microsoft.Build.Framework; +using Microsoft.Build.Logging.Analyzers; + +namespace Microsoft.Build.Analyzers.Infrastructure; + +internal sealed class BuildAnalysisManager : IBuildAnalysisManager +{ + private readonly List _analyzers = new(); + private readonly CentralBuildAnalyzerContext _centralContext = new(); + + private BuildAnalysisManager() { } + + internal static IBuildAnalysisManager Instance => CreateBuildAnalysisManager(); + + public void RegisterAnalyzer(BuildAnalyzer analyzer) + { + if (!analyzer.SupportedRules.Any()) + { + // error out + return; + } + + IEnumerable configuration = analyzer.SupportedRules.Select(ConfigurationProvider.GetMergedConfiguration); + + if (configuration.All(c => !c.IsEnabled)) + { + return; + } + + // TODO: the config module should return any possible user configurations per rule + ConfigurationContext configurationContext = ConfigurationContext.Null; + analyzer.Initialize(configurationContext); + var wrappedAnalyzer = new BuildAnalyzerTracingWrapper(analyzer); + var wrappedContext = new BuildAnalyzerContext(wrappedAnalyzer, _centralContext); + analyzer.RegisterActions(wrappedContext); + _analyzers.Add(wrappedAnalyzer); + } + + // TODO: all this processing should be queued and done async. We might even want to run analyzers in parallel + + private SimpleProjectRootElementCache _cache = new SimpleProjectRootElementCache(); + + // This requires MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION set to 1 + public void ProcessEvaluationFinishedEventArgs(IBuildAnalysisLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs evaluationFinishedEventArgs) + { + LoggingContext? loggingContext = (buildAnalysisContext as AnalyzerLoggingContext)!; + + if (loggingContext == null) + { + // error out + return; + } + + Dictionary propertiesLookup = new Dictionary(); + Internal.Utilities.EnumerateProperties(evaluationFinishedEventArgs.Properties, propertiesLookup, + static (dict, kvp) => dict.Add(kvp.Key, kvp.Value)); + + EvaluatedPropertiesContext context = new EvaluatedPropertiesContext(loggingContext, + new ReadOnlyDictionary(propertiesLookup), + evaluationFinishedEventArgs.ProjectFile!); + + _centralContext.RunEvaluatedPropertiesActions(context); + + if (_centralContext.HasParsedItemsActions) + { + ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(evaluationFinishedEventArgs.ProjectFile!, /*unused*/ + null, /*unused*/null, _cache, false /*Not explicitly loaded - unused*/); + + ParsedItemsContext parsedItemsContext = new ParsedItemsContext(loggingContext, + new ItemsHolder(xml.Items, xml.ItemGroups)); + + _centralContext.RunParsedItemsActions(parsedItemsContext); + } + } + + // TODO: tracing: https://github.com/dotnet/msbuild/issues/9629 + // should have infra as well, should log to AnalyzersConnectorLogger upon shutdown (if requested) + internal string CreateTracingStats() + { + return string.Join(Environment.NewLine, + _analyzers.Select(a => GetAnalyzerDescriptor(a.BuildAnalyzer) + ": " + a.Elapsed)); + + string GetAnalyzerDescriptor(BuildAnalyzer buildAnalyzer) + => buildAnalyzer.FriendlyName + " (" + buildAnalyzer.GetType() + ")"; + } + + internal static BuildAnalysisManager CreateBuildAnalysisManager() + { + var buildAnalysisManager = new BuildAnalysisManager(); + buildAnalysisManager.RegisterAnalyzer(new SharedOutputPathAnalyzer()); + // ... Register other internal analyzers + return buildAnalysisManager; + } +} diff --git a/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs b/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs new file mode 100644 index 00000000000..cef5165db2f --- /dev/null +++ b/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs @@ -0,0 +1,24 @@ +// 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; +using Microsoft.Build.Experimental; + +namespace Microsoft.Build.Analyzers.Infrastructure; + +/// +/// Counterpart type for BuildAnalyzerConfiguration - with all properties non-nullable +/// +internal sealed class BuildAnalyzerConfigurationInternal +{ + public LifeTimeScope LifeTimeScope { get; internal init; } + public InvocationConcurrency SupportedInvocationConcurrency { get; internal init; } + public PerformanceWeightClass PerformanceWeightClass { get; internal init; } + public EvaluationAnalysisScope EvaluationAnalysisScope { get; internal init; } + public BuildAnalysisResultSeverity Severity { get; internal init; } + public bool IsEnabled { get; internal init; } +} diff --git a/src/Analyzers/Infrastructure/BuildAnalyzerContext.cs b/src/Analyzers/Infrastructure/BuildAnalyzerContext.cs new file mode 100644 index 00000000000..832bb94872a --- /dev/null +++ b/src/Analyzers/Infrastructure/BuildAnalyzerContext.cs @@ -0,0 +1,99 @@ +// 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.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Build.Experimental; + +public delegate void EvaluatedPropertiesAction(EvaluatedPropertiesContext context); +public delegate void ParsedItemsAction(ParsedItemsContext context); + +internal sealed class CentralBuildAnalyzerContext +{ + private EvaluatedPropertiesAction? _evaluatedPropertiesActions; + private ParsedItemsAction? _parsedItemsActions; + + // This we can potentially use to subscribe for receiving evaluated props in the + // build event args. However - this needs to be done early on, when analyzers might not be known yet + internal bool HasEvaluatedPropertiesActions => _evaluatedPropertiesActions != null; + internal bool HasParsedItemsActions => _parsedItemsActions != null; + + internal void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction) + { + // Here we might want to communicate to node that props need to be sent. + // (it was being communicated via MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION) + _evaluatedPropertiesActions += evaluatedPropertiesAction; + } + + internal void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction) + { + _parsedItemsActions += parsedItemsAction; + } + + internal void RunEvaluatedPropertiesActions(EvaluatedPropertiesContext evaluatedPropertiesContext) + { + _evaluatedPropertiesActions?.Invoke(evaluatedPropertiesContext); + } + + internal void RunParsedItemsActions(ParsedItemsContext parsedItemsContext) + { + _parsedItemsActions?.Invoke(parsedItemsContext); + } +} + +internal sealed class BuildAnalyzerTracingWrapper +{ + private readonly Stopwatch _stopwatch = new Stopwatch(); + + public BuildAnalyzerTracingWrapper(BuildAnalyzer buildAnalyzer) + => BuildAnalyzer = buildAnalyzer; + + internal BuildAnalyzer BuildAnalyzer { get; } + + internal TimeSpan Elapsed => _stopwatch.Elapsed; + + internal IDisposable StartSpan() + { + _stopwatch.Start(); + return new CleanupScope(_stopwatch.Stop); + } + + internal readonly struct CleanupScope(Action disposeAction) : IDisposable + { + public void Dispose() => disposeAction(); + } +} + +public interface IBuildAnalyzerContext +{ + void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction); + void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction); +} + +internal sealed class BuildAnalyzerContext(BuildAnalyzerTracingWrapper analyzer, CentralBuildAnalyzerContext centralContext) : IBuildAnalyzerContext +{ + public void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction) + { + void WrappedEvaluatedPropertiesAction(EvaluatedPropertiesContext context) + { + using var _ = analyzer.StartSpan(); + evaluatedPropertiesAction(context); + } + + centralContext.RegisterEvaluatedPropertiesAction(WrappedEvaluatedPropertiesAction); + } + + public void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction) + { + void WrappedParsedItemsAction(ParsedItemsContext context) + { + using var _ = analyzer.StartSpan(); + parsedItemsAction(context); + } + + centralContext.RegisterParsedItemsAction(WrappedParsedItemsAction); + } +} diff --git a/src/Analyzers/Infrastructure/ConfigurationProvider.cs b/src/Analyzers/Infrastructure/ConfigurationProvider.cs new file mode 100644 index 00000000000..f0935938997 --- /dev/null +++ b/src/Analyzers/Infrastructure/ConfigurationProvider.cs @@ -0,0 +1,114 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Build.Experimental; + +namespace Microsoft.Build.Analyzers.Infrastructure; + +// TODO: https://github.com/dotnet/msbuild/issues/9628 +internal static class ConfigurationProvider +{ + // TODO: This module should have a mechanism for removing unneeded configurations + // (disabled rules and analyzers that need to run in different node) + private static readonly Dictionary _editorConfig = LoadConfiguration(); + + // This is just a testing implementation for quicker unblock of testing. + // Real implementation will use .editorconfig file. + // Sample json: + /////*lang=json,strict*/ + ////""" + //// { + //// "ABC123": { + //// "IsEnabled": true, + //// "Severity": "Info" + //// }, + //// "COND0543": { + //// "IsEnabled": false, + //// "Severity": "Error", + //// "EvaluationAnalysisScope": "AnalyzedProjectOnly", + //// "CustomSwitch": "QWERTY" + //// }, + //// "BLA": { + //// "IsEnabled": false + //// } + //// } + //// """ + // + // Plus there will need to be a mechanism of distinguishing different configs in different folders + // - e.g. - what to do if we analyze two projects (not sharing output path) and they have different .editorconfig files? + private static Dictionary LoadConfiguration() + { + const string configFileName = "editorconfig.json"; + string configPath = configFileName; + + if (!File.Exists(configPath)) + { + // TODO: pass the current project path + var dir = Environment.CurrentDirectory; + configPath = Path.Combine(dir, configFileName); + + if (!File.Exists(configPath)) + { + return new Dictionary(); + } + } + + var json = File.ReadAllText(configPath); + var DeserializationOptions = new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } }; + return JsonSerializer.Deserialize>(json, DeserializationOptions) ?? + new Dictionary(); + } + + /// + /// Gets effective configuration for the given analyzer rule. + /// The configuration values are guaranteed to be non-null upon this merge operation. + /// + /// + /// + public static BuildAnalyzerConfigurationInternal GetMergedConfiguration(BuildAnalysisRule analyzerRule) + { + if (!_editorConfig.TryGetValue(analyzerRule.Id, out BuildAnalyzerConfiguration? editorConfig)) + { + editorConfig = BuildAnalyzerConfiguration.Null; + } + + var defaultConfig = analyzerRule.DefaultConfiguration; + + return new BuildAnalyzerConfigurationInternal() + { + SupportedInvocationConcurrency = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.SupportedInvocationConcurrency), + EvaluationAnalysisScope = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.EvaluationAnalysisScope), + IsEnabled = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.IsEnabled), + LifeTimeScope = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.LifeTimeScope), + PerformanceWeightClass = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.PerformanceWeightClass), + Severity = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.Severity) + }; + + T GetConfigValue( + BuildAnalyzerConfiguration editorConfigValue, + BuildAnalyzerConfiguration defaultValue, + Func propertyGetter) where T : struct + => propertyGetter(editorConfigValue) ?? + propertyGetter(defaultValue) ?? + EnsureNonNull(propertyGetter(BuildAnalyzerConfiguration.Default)); + + T EnsureNonNull(T? value) where T : struct + { + if (value is null) + { + throw new InvalidOperationException("Value is null"); + } + + return value.Value; + } + } +} diff --git a/src/Analyzers/Microsoft.Build.Analyzers.csproj b/src/Analyzers/Microsoft.Build.Analyzers.csproj new file mode 100644 index 00000000000..96a3bf34bb5 --- /dev/null +++ b/src/Analyzers/Microsoft.Build.Analyzers.csproj @@ -0,0 +1,21 @@ + + + $(FullFrameworkTFM);$(LatestDotNetCoreForMSBuild) + $(RuntimeOutputTargetFrameworks) + Microsoft.Build.Analyzers + Microsoft.Build.Analyzers + This package contains the $(AssemblyName) assembly which contains build analyzers logic and API. + true + AnyCPU + true + + + + + + + + + + + diff --git a/src/Analyzers/OM/EvaluatedPropertiesContext.cs b/src/Analyzers/OM/EvaluatedPropertiesContext.cs new file mode 100644 index 00000000000..282b9a73ddb --- /dev/null +++ b/src/Analyzers/OM/EvaluatedPropertiesContext.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.Build.BackEnd.Logging; + +namespace Microsoft.Build.Experimental; +public class EvaluatedPropertiesContext : BuildAnalysisContext +{ + internal EvaluatedPropertiesContext( + LoggingContext loggingContext, + IReadOnlyDictionary evaluatedProperties, + string projectFilePath) : + base(loggingContext) => (EvaluatedProperties, ProjectFilePath) = + (evaluatedProperties, projectFilePath); + + public IReadOnlyDictionary EvaluatedProperties { get; } + + public string ProjectFilePath { get; } +} diff --git a/src/Analyzers/OM/ParsedItemsContext.cs b/src/Analyzers/OM/ParsedItemsContext.cs new file mode 100644 index 00000000000..5a29a628627 --- /dev/null +++ b/src/Analyzers/OM/ParsedItemsContext.cs @@ -0,0 +1,56 @@ +// 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; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Construction; + +namespace Microsoft.Build.Experimental; +public enum ItemType +{ + ProjectReference, + PackageReference, + Compile, + EmbeddedResource +} + +public static class ItemTypeExtensions +{ + public static IEnumerable GetItemsOfType(this IEnumerable items, ItemType itemType) + => GetItemsOfType(items, itemType.ToString()); + + public static IEnumerable GetItemsOfType(this IEnumerable items, + string itemType) + { + return items.Where(i => + i.ItemType.Equals(itemType, StringComparison.CurrentCultureIgnoreCase)); + } +} + +public class ItemsHolder(IEnumerable items, IEnumerable itemGroups) +{ + public IEnumerable Items { get; } = items; + public IEnumerable ItemGroups { get; } = itemGroups; + + public IEnumerable GetItemsOfType(ItemType itemType) + => Items.GetItemsOfType(itemType); + + public IEnumerable GetItemsOfType(string itemType) + { + return Items.GetItemsOfType(itemType); + } +} + +public class ParsedItemsContext : BuildAnalysisContext +{ + internal ParsedItemsContext( + LoggingContext loggingContext, + ItemsHolder itemsHolder) : + base(loggingContext) => ItemsHolder = itemsHolder; + + public ItemsHolder ItemsHolder { get; } +} diff --git a/src/Analyzers/README.md b/src/Analyzers/README.md new file mode 100644 index 00000000000..e09acbf1bcf --- /dev/null +++ b/src/Analyzers/README.md @@ -0,0 +1,3 @@ +# Microsoft.Build.Analyzers + +TBD \ No newline at end of file diff --git a/src/Analyzers/Utilities/IsExternalInit.cs b/src/Analyzers/Utilities/IsExternalInit.cs new file mode 100644 index 00000000000..92d5c4c320a --- /dev/null +++ b/src/Analyzers/Utilities/IsExternalInit.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit { } +} diff --git a/src/Build.UnitTests/Analyzers/EndToEndTests.cs b/src/Build.UnitTests/Analyzers/EndToEndTests.cs new file mode 100644 index 00000000000..79ecd1addc9 --- /dev/null +++ b/src/Build.UnitTests/Analyzers/EndToEndTests.cs @@ -0,0 +1,124 @@ +// 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 System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.UnitTests; +using Microsoft.Build.UnitTests.Shared; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Build.Engine.UnitTests.Analyzers +{ + public class EndToEndTests + { + private readonly TestEnvironment _env; + public EndToEndTests(ITestOutputHelper output) + { + _env = TestEnvironment.Create(output); + + // this is needed to ensure the binary logger does not pollute the environment + _env.WithEnvironmentInvariant(); + } + + [Fact] + // WARNING!: this test is using a bootstrap - so a build.cmd needs to be run prior this test can properly capture the + // changes in the production code + public void SampleAnalyzerIntegrationTest() + { + using (TestEnvironment env = TestEnvironment.Create()) + { + string contents = $""" + + + + Exe + net8.0 + enable + enable + + + + Test + + + + + + + + + + + + """; + + string contents2 = $""" + + + + Exe + net8.0 + enable + enable + + + + Test + + + + + + + + + + + + """; + TransientTestFolder workFolder = env.CreateFolder(createFolder: true); + TransientTestFile projectFile = env.CreateFile(workFolder, "FooBar.csproj", contents); + TransientTestFile projectFile2 = env.CreateFile(workFolder, "FooBar-Copy.csproj", contents2); + + // var cache = new SimpleProjectRootElementCache(); + // ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile.Path, /*unused*/null, /*unused*/null, cache, false /*Not explicitly loaded - unused*/); + + + TransientTestFile config = env.CreateFile(workFolder, "editorconfig.json", + /*lang=json,strict*/ + """ + { + "BC0101": { + "IsEnabled": true, + "Severity": "Error" + }, + "COND0543": { + "IsEnabled": false, + "Severity": "Error", + "EvaluationAnalysisScope": "AnalyzedProjectOnly", + "CustomSwitch": "QWERTY" + }, + "BLA": { + "IsEnabled": false + } + } + """); + + // env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1"); + env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); + // string output = RunnerUtilities.ExecMSBuild($"{projectFile.Path} /m:1 -nr:False", out bool success); + string output = RunnerUtilities.ExecBootstrapedMSBuild($"{projectFile.Path} /m:1 -nr:False -restore", out bool success); + _env.Output.WriteLine(output); + success.ShouldBeTrue(); + // The conflicting outputs warning appears + output.ShouldContain("BC0101"); + } + } + } +} diff --git a/src/Build/AssemblyInfo.cs b/src/Build/AssemblyInfo.cs index 6e57337863d..fc70072ee54 100644 --- a/src/Build/AssemblyInfo.cs +++ b/src/Build/AssemblyInfo.cs @@ -21,6 +21,7 @@ [assembly: InternalsVisibleTo("Microsoft.Build.Engine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.UnitTests.Shared, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.Build.Analyzers, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Unittest, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.Tasks.Cop, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] // DO NOT expose Internals to "Microsoft.Build.UnitTests.OM.OrcasCompatibility" as this assembly is supposed to only see public interface diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 3d2088d264c..8a8cb91f608 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -32,6 +32,7 @@ using Microsoft.Build.Graph; using Microsoft.Build.Internal; using Microsoft.Build.Logging; +using Microsoft.Build.Logging.Analyzers; using Microsoft.Build.Shared; using Microsoft.Build.Shared.Debugging; using Microsoft.NET.StringTools; @@ -628,6 +629,7 @@ ILoggingService InitializeLoggingService() { ILoggingService loggingService = CreateLoggingService( AppendDebuggingLoggers(_buildParameters.Loggers), + _buildParameters.BuildAnalysisLoggerFactory, _buildParameters.ForwardingLoggers, _buildParameters.WarningsAsErrors, _buildParameters.WarningsNotAsErrors, @@ -2943,7 +2945,13 @@ private void OnProjectStarted(object sender, ProjectStartedEventArgs e) /// /// Creates a logging service around the specified set of loggers. /// - private ILoggingService CreateLoggingService(IEnumerable loggers, IEnumerable forwardingLoggers, ISet warningsAsErrors, ISet warningsNotAsErrors, ISet warningsAsMessages) + private ILoggingService CreateLoggingService( + IEnumerable loggers, + IBuildAnalysisLoggerFactory buildAnalysisLoggerFactory, + IEnumerable forwardingLoggers, + ISet warningsAsErrors, + ISet warningsNotAsErrors, + ISet warningsAsMessages) { Debug.Assert(Monitor.IsEntered(_syncLock)); @@ -2967,6 +2975,15 @@ private ILoggingService CreateLoggingService(IEnumerable loggers, IEnum loggingService.WarningsNotAsErrors = warningsNotAsErrors; loggingService.WarningsAsMessages = warningsAsMessages; + if (buildAnalysisLoggerFactory != null) + { + loggers = (loggers ?? Enumerable.Empty()).Concat(new[] + { + buildAnalysisLoggerFactory.CreateBuildAnalysisLogger( + new AnalyzerLoggingContextFactory(loggingService)) + }); + } + try { if (loggers != null) diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index 582532e5795..697387a5f73 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -10,6 +10,7 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; +using Microsoft.Build.Experimental; using Microsoft.Build.Experimental.ProjectCache; using Microsoft.Build.Framework; using Microsoft.Build.Graph; @@ -309,6 +310,7 @@ internal BuildParameters(BuildParameters other, bool resetEnvironment = false) DiscardBuildResults = other.DiscardBuildResults; LowPriority = other.LowPriority; Question = other.Question; + BuildAnalysisLoggerFactory = other.BuildAnalysisLoggerFactory; ProjectCacheDescriptor = other.ProjectCacheDescriptor; } @@ -834,6 +836,11 @@ public bool Question set => _question = value; } + /// + /// Gets or sets a factory for build analysis infrastructure logger + /// + public IBuildAnalysisLoggerFactory BuildAnalysisLoggerFactory { get; set; } + /// /// Gets or sets the project cache description to use for all or /// in addition to any potential project caches described in each project. diff --git a/src/Build/ElementLocation/ElementLocation.cs b/src/Build/ElementLocation/ElementLocation.cs index 2dd2bb89a95..c9bf42b48b2 100644 --- a/src/Build/ElementLocation/ElementLocation.cs +++ b/src/Build/ElementLocation/ElementLocation.cs @@ -80,7 +80,7 @@ public string LocationString /// It is to be used for the project location when the project has not been given a name. /// In that case, it exists, but can't have a specific location. /// - internal static ElementLocation EmptyLocation + public static ElementLocation EmptyLocation { get { return s_emptyElementLocation; } } diff --git a/src/Build/Logging/Analyzers/AnalyzerLoggingContext.cs b/src/Build/Logging/Analyzers/AnalyzerLoggingContext.cs new file mode 100644 index 00000000000..efb07dcffed --- /dev/null +++ b/src/Build/Logging/Analyzers/AnalyzerLoggingContext.cs @@ -0,0 +1,27 @@ +// 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; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Experimental; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Logging.Analyzers; + +internal class AnalyzerLoggingContext : LoggingContext, IBuildAnalysisLoggingContext +{ + public AnalyzerLoggingContext(ILoggingService loggingService, BuildEventContext eventContext) + : base(loggingService, eventContext) + { + IsValid = true; + } + + public AnalyzerLoggingContext(LoggingContext baseContext) : base(baseContext) + { + IsValid = true; + } +} diff --git a/src/Build/Logging/Analyzers/AnalyzerLoggingContextFactory.cs b/src/Build/Logging/Analyzers/AnalyzerLoggingContextFactory.cs new file mode 100644 index 00000000000..ae60ac0d061 --- /dev/null +++ b/src/Build/Logging/Analyzers/AnalyzerLoggingContextFactory.cs @@ -0,0 +1,18 @@ +// 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; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Experimental; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Logging.Analyzers; +internal class AnalyzerLoggingContextFactory(ILoggingService loggingService) : IBuildAnalysisLoggingContextFactory +{ + public IBuildAnalysisLoggingContext CreateLoggingContext(BuildEventContext eventContext) => + new AnalyzerLoggingContext(loggingService, eventContext); +} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 6d41abd7891..68c4aa736e3 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -160,6 +160,8 @@ + + @@ -682,11 +684,7 @@ - + <Namespace Prefix='ns' Uri='urn:schemas-microsoft-com:asm.v1' /> /configuration/runtime/ns:assemblyBinding/* diff --git a/src/Framework/Analyzers/IBuildAnalysisLoggerFactory.cs b/src/Framework/Analyzers/IBuildAnalysisLoggerFactory.cs new file mode 100644 index 00000000000..2f7fae150aa --- /dev/null +++ b/src/Framework/Analyzers/IBuildAnalysisLoggerFactory.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Experimental; + +public interface IBuildAnalysisLoggerFactory +{ + ILogger CreateBuildAnalysisLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory); +} diff --git a/src/Framework/Analyzers/IBuildAnalysisLoggingContext.cs b/src/Framework/Analyzers/IBuildAnalysisLoggingContext.cs new file mode 100644 index 00000000000..ef15d1b48a2 --- /dev/null +++ b/src/Framework/Analyzers/IBuildAnalysisLoggingContext.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public interface IBuildAnalysisLoggingContext +{ } diff --git a/src/Framework/Analyzers/IBuildAnalysisLoggingContextFactory.cs b/src/Framework/Analyzers/IBuildAnalysisLoggingContextFactory.cs new file mode 100644 index 00000000000..da1a99b6ddb --- /dev/null +++ b/src/Framework/Analyzers/IBuildAnalysisLoggingContextFactory.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Experimental; + +public interface IBuildAnalysisLoggingContextFactory +{ + IBuildAnalysisLoggingContext CreateLoggingContext(BuildEventContext eventContext); +} diff --git a/src/Framework/Analyzers/IBuildAnalysisManager.cs b/src/Framework/Analyzers/IBuildAnalysisManager.cs new file mode 100644 index 00000000000..c49fa515708 --- /dev/null +++ b/src/Framework/Analyzers/IBuildAnalysisManager.cs @@ -0,0 +1,18 @@ +// 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; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Experimental; + +public interface IBuildAnalysisManager +{ + internal void ProcessEvaluationFinishedEventArgs( + IBuildAnalysisLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs); +} diff --git a/src/Framework/Properties/AssemblyInfo.cs b/src/Framework/Properties/AssemblyInfo.cs index 1f0b9011081..6dbcdaaf37e 100644 --- a/src/Framework/Properties/AssemblyInfo.cs +++ b/src/Framework/Properties/AssemblyInfo.cs @@ -39,6 +39,7 @@ [assembly: InternalsVisibleTo("Microsoft.Build.Framework.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.Tasks.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.Build.Analyzers, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Utilities.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Tasks.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("MSBuild, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs index 53e4555fcf2..e680e3f20c5 100644 --- a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs +++ b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs @@ -1185,6 +1185,7 @@ public void InvalidToolsVersionErrors() graphBuildOptions: null, lowPriority: false, question: false, + buildAnalysisLoggerFactory: null, inputResultsCaches: null, outputResultsCache: null, saveProjectResult: false, diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index 0415842c6b7..eb8736e53fb 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -196,6 +196,7 @@ + diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 545fdbc9844..3e33bfd9e96 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -19,6 +19,7 @@ using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading; +using Microsoft.Build.Analyzers.Infrastructure; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Eventing; @@ -717,6 +718,7 @@ public static ExitType Execute( string[] inputResultsCaches = null; string outputResultsCache = null; bool question = false; + IBuildAnalysisLoggerFactory buildAnalysisLoggerFactory = null; string[] getProperty = Array.Empty(); string[] getItem = Array.Empty(); string[] getTargetResult = Array.Empty(); @@ -763,6 +765,7 @@ public static ExitType Execute( #endif ref lowPriority, ref question, + ref buildAnalysisLoggerFactory, ref getProperty, ref getItem, ref getTargetResult, @@ -863,6 +866,7 @@ public static ExitType Execute( graphBuildOptions, lowPriority, question, + buildAnalysisLoggerFactory, inputResultsCaches, outputResultsCache, saveProjectResult: outputPropertiesItemsOrTargetResults, @@ -1244,6 +1248,7 @@ internal static bool BuildProject( GraphBuildOptions graphBuildOptions, bool lowPriority, bool question, + IBuildAnalysisLoggerFactory buildAnalysisLoggerFactory, string[] inputResultsCaches, string outputResultsCache, bool saveProjectResult, @@ -1445,6 +1450,7 @@ internal static bool BuildProject( parameters.InputResultsCacheFiles = inputResultsCaches; parameters.OutputResultsCacheFile = outputResultsCache; parameters.Question = question; + parameters.BuildAnalysisLoggerFactory = buildAnalysisLoggerFactory; #if FEATURE_REPORTFILEACCESSES parameters.ReportFileAccesses = reportFileAccesses; #endif @@ -2416,6 +2422,7 @@ private static bool ProcessCommandLineSwitches( #endif ref bool lowPriority, ref bool question, + ref IBuildAnalysisLoggerFactory buildAnalysisLoggerFactory, ref string[] getProperty, ref string[] getItem, ref string[] getTargetResult, @@ -2557,6 +2564,7 @@ private static bool ProcessCommandLineSwitches( #endif ref lowPriority, ref question, + ref buildAnalysisLoggerFactory, ref getProperty, ref getItem, ref getTargetResult, @@ -2638,6 +2646,8 @@ private static bool ProcessCommandLineSwitches( question = commandLineSwitches.IsParameterizedSwitchSet(CommandLineSwitches.ParameterizedSwitch.Question); + buildAnalysisLoggerFactory = ProcessBuildAnalysisLoggerFactorySwitch(commandLineSwitches); + inputResultsCaches = ProcessInputResultsCaches(commandLineSwitches); outputResultsCache = ProcessOutputResultsCache(commandLineSwitches); @@ -2712,6 +2722,13 @@ private static bool ProcessCommandLineSwitches( return invokeBuild; } + private static IBuildAnalysisLoggerFactory ProcessBuildAnalysisLoggerFactorySwitch(CommandLineSwitches commandLineSwitches) + { + // todo: opt-in behavior: https://github.com/dotnet/msbuild/issues/9723 + bool isAnalysisEnabled = true; + return isAnalysisEnabled ? new BuildAnalysisLoggerFactory() : null; + } + private static bool ProcessTerminalLoggerConfiguration(CommandLineSwitches commandLineSwitches, out string aggregatedParameters) { aggregatedParameters = AggregateParameters(commandLineSwitches); diff --git a/src/UnitTests.Shared/RunnerUtilities.cs b/src/UnitTests.Shared/RunnerUtilities.cs index c1e96f7a6cb..f836a7be6bd 100644 --- a/src/UnitTests.Shared/RunnerUtilities.cs +++ b/src/UnitTests.Shared/RunnerUtilities.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using Microsoft.Build.Shared; +using System.IO; using Xunit.Abstractions; #nullable disable @@ -32,6 +33,24 @@ public static string ExecMSBuild(string msbuildParameters, out bool successfulEx return ExecMSBuild(PathToCurrentlyRunningMsBuildExe, msbuildParameters, out successfulExit, outputHelper: outputHelper); } + /// + /// Invoke the currently running msbuild and return the stdout, stderr, and process exit status. + /// This method may invoke msbuild via other runtimes. + /// + public static string ExecBootstrapedMSBuild(string msbuildParameters, out bool successfulExit, bool shellExecute = false, ITestOutputHelper outputHelper = null) + { + // TODO: use proper way of passing the bootsrtrap location: https://github.com/dotnet/msbuild/issues/9729 + string basePath = PathToCurrentlyRunningMsBuildExe.Substring(0, PathToCurrentlyRunningMsBuildExe.IndexOf(@"artifacts\bin", StringComparison.InvariantCultureIgnoreCase)); +#if NET + string pathToExecutable = EnvironmentProvider.GetDotnetExePath(); + msbuildParameters = Path.Combine(basePath, @"artifacts\bin\bootstrap\net8.0\MSBuild\msbuild.dll") + " " + msbuildParameters; +#else + string pathToExecutable = + Path.Combine(basePath, @"artifacts\bin\bootstrap\net472\MSBuild\Current\Bin\amd64\msbuild.exe"); +#endif + return RunProcessAndGetOutput(pathToExecutable, msbuildParameters, out successfulExit, shellExecute, outputHelper); + } + /// /// Invoke msbuild.exe with the given parameters and return the stdout, stderr, and process exit status. /// This method may invoke msbuild via other runtimes. @@ -108,7 +127,7 @@ public static string RunProcessAndGetOutput(string process, string parameters, o p.BeginErrorReadLine(); p.StandardInput.Dispose(); - if (!p.WaitForExit(30_000)) + if (!p.WaitForExit(30_000_000)) { // Let's not create a unit test for which we need more than 30 sec to execute. // Please consider carefully if you would like to increase the timeout. From 628394dd0cd40fd187fffe70ce0f8a987c776bb8 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Tue, 13 Feb 2024 12:31:04 +0100 Subject: [PATCH 02/58] Make analyzer test use bootstrap properly (#9733) --- MSBuild.sln | 42 +++++++++ eng/BootStrapMSBuild.props | 21 +++++ eng/BootStrapMSBuild.targets | 6 +- src/Analyzers.UnitTests/AssemblyInfo.cs | 14 +++ src/Analyzers.UnitTests/BootstrapRunner.cs | 37 ++++++++ .../EndToEndTests.cs | 7 +- ...Microsoft.Build.Analyzers.UnitTests.csproj | 94 +++++++++++++++++++ src/Framework/Properties/AssemblyInfo.cs | 1 + src/UnitTests.Shared/RunnerUtilities.cs | 18 ---- 9 files changed, 214 insertions(+), 26 deletions(-) create mode 100644 eng/BootStrapMSBuild.props create mode 100644 src/Analyzers.UnitTests/AssemblyInfo.cs create mode 100644 src/Analyzers.UnitTests/BootstrapRunner.cs rename src/{Build.UnitTests/Analyzers => Analyzers.UnitTests}/EndToEndTests.cs (94%) create mode 100644 src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj diff --git a/MSBuild.sln b/MSBuild.sln index e441e1779ed..7c89de6dea6 100644 --- a/MSBuild.sln +++ b/MSBuild.sln @@ -82,6 +82,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.UnitTests.S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Analyzers", "src\Analyzers\Microsoft.Build.Analyzers.csproj", "{512E01F7-2899-433B-93E2-D63B43AF0420}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Analyzers.UnitTests", "src\Analyzers.UnitTests\Microsoft.Build.Analyzers.UnitTests.csproj", "{B18BAE17-D78F-4F89-B7A4-808C05E64D73}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -914,6 +916,46 @@ Global {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x64.Build.0 = Release-MONO|x64 {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x86.ActiveCfg = Release-MONO|Any CPU {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x86.Build.0 = Release-MONO|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|ARM64.ActiveCfg = Debug|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|ARM64.Build.0 = Debug|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x64.ActiveCfg = Debug|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x64.Build.0 = Debug|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x86.ActiveCfg = Debug|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x86.Build.0 = Debug|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|Any CPU.ActiveCfg = Debug-MONO|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|Any CPU.Build.0 = Debug-MONO|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|ARM64.ActiveCfg = Debug-MONO|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|ARM64.Build.0 = Debug-MONO|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|x64.ActiveCfg = Debug-MONO|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|x64.Build.0 = Debug-MONO|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|x86.ActiveCfg = Debug-MONO|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|x86.Build.0 = Debug-MONO|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|Any CPU.ActiveCfg = MachineIndependent|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|Any CPU.Build.0 = MachineIndependent|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|ARM64.ActiveCfg = MachineIndependent|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|ARM64.Build.0 = MachineIndependent|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|x64.ActiveCfg = MachineIndependent|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|x64.Build.0 = MachineIndependent|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|x86.ActiveCfg = MachineIndependent|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|x86.Build.0 = MachineIndependent|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|Any CPU.Build.0 = Release|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|ARM64.ActiveCfg = Release|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|ARM64.Build.0 = Release|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x64.ActiveCfg = Release|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x64.Build.0 = Release|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x86.ActiveCfg = Release|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x86.Build.0 = Release|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|Any CPU.ActiveCfg = Release-MONO|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|Any CPU.Build.0 = Release-MONO|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|ARM64.ActiveCfg = Release-MONO|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|ARM64.Build.0 = Release-MONO|arm64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|x64.ActiveCfg = Release-MONO|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|x64.Build.0 = Release-MONO|x64 + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|x86.ActiveCfg = Release-MONO|Any CPU + {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|x86.Build.0 = Release-MONO|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/eng/BootStrapMSBuild.props b/eng/BootStrapMSBuild.props new file mode 100644 index 00000000000..e70bcb3489d --- /dev/null +++ b/eng/BootStrapMSBuild.props @@ -0,0 +1,21 @@ + + + + + + $(ArtifactsBinDir)bootstrap\ + $(BootstrapDestination)$(Platform)\ + $(BootstrapDestination)$(TargetFramework.ToLowerInvariant())\MSBuild\ + + + + $(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin + + + + $(BootstrapDestination) + + diff --git a/eng/BootStrapMSBuild.targets b/eng/BootStrapMSBuild.targets index 3aafd190c94..cf294aa801e 100644 --- a/eng/BootStrapMSBuild.targets +++ b/eng/BootStrapMSBuild.targets @@ -1,5 +1,7 @@ + + - $(ArtifactsBinDir)bootstrap\ - $(BootstrapDestination)$(Platform)\ - $(BootstrapDestination)$(TargetFramework.ToLowerInvariant())\MSBuild\ - BootstrapFull BootstrapNetCore diff --git a/src/Analyzers.UnitTests/AssemblyInfo.cs b/src/Analyzers.UnitTests/AssemblyInfo.cs new file mode 100644 index 00000000000..0f119a6530d --- /dev/null +++ b/src/Analyzers.UnitTests/AssemblyInfo.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +global using NativeMethodsShared = Microsoft.Build.Framework.NativeMethods; + +namespace Microsoft.Build.Analyzers.UnitTests; + +[System.AttributeUsage(System.AttributeTargets.Assembly)] +internal sealed class BootstrapLocationAttribute(string bootstrapRoot, string bootstrapMsbuildBinaryLocation) + : System.Attribute +{ + public string BootstrapRoot { get; } = bootstrapRoot; + public string BootstrapMsbuildBinaryLocation { get; } = bootstrapMsbuildBinaryLocation; +} diff --git a/src/Analyzers.UnitTests/BootstrapRunner.cs b/src/Analyzers.UnitTests/BootstrapRunner.cs new file mode 100644 index 00000000000..540abc8ac63 --- /dev/null +++ b/src/Analyzers.UnitTests/BootstrapRunner.cs @@ -0,0 +1,37 @@ +// 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 System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.UnitTests.Shared; +using Xunit.Abstractions; + +namespace Microsoft.Build.Analyzers.UnitTests +{ + internal static class BootstrapRunner + { + // This should ideally be part of RunnerUtilities - however then we'd need to enforce + // all test projects to import the BootStrapMSBuild.props file and declare the BootstrapLocationAttribute. + // Better solution would be to have a single test utility project - instead of linked code files. + public static string ExecBootstrapedMSBuild(string msbuildParameters, out bool successfulExit, bool shellExecute = false, ITestOutputHelper outputHelper = null) + { + var binaryFolder = Assembly.GetExecutingAssembly() + .GetCustomAttribute()! + .BootstrapMsbuildBinaryLocation; + +#if NET + string pathToExecutable = EnvironmentProvider.GetDotnetExePath()!; + msbuildParameters = Path.Combine(binaryFolder, "msbuild.dll") + " " + msbuildParameters; +#else + string pathToExecutable = + Path.Combine(binaryFolder, "msbuild.exe"); +#endif + return RunnerUtilities.RunProcessAndGetOutput(pathToExecutable, msbuildParameters, out successfulExit, shellExecute, outputHelper); + } + } +} diff --git a/src/Build.UnitTests/Analyzers/EndToEndTests.cs b/src/Analyzers.UnitTests/EndToEndTests.cs similarity index 94% rename from src/Build.UnitTests/Analyzers/EndToEndTests.cs rename to src/Analyzers.UnitTests/EndToEndTests.cs index 79ecd1addc9..76a5479ebaa 100644 --- a/src/Build.UnitTests/Analyzers/EndToEndTests.cs +++ b/src/Analyzers.UnitTests/EndToEndTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; using Microsoft.Build.UnitTests; @@ -13,7 +14,7 @@ using Xunit; using Xunit.Abstractions; -namespace Microsoft.Build.Engine.UnitTests.Analyzers +namespace Microsoft.Build.Analyzers.UnitTests { public class EndToEndTests { @@ -27,8 +28,6 @@ public EndToEndTests(ITestOutputHelper output) } [Fact] - // WARNING!: this test is using a bootstrap - so a build.cmd needs to be run prior this test can properly capture the - // changes in the production code public void SampleAnalyzerIntegrationTest() { using (TestEnvironment env = TestEnvironment.Create()) @@ -113,7 +112,7 @@ public void SampleAnalyzerIntegrationTest() // env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1"); env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); // string output = RunnerUtilities.ExecMSBuild($"{projectFile.Path} /m:1 -nr:False", out bool success); - string output = RunnerUtilities.ExecBootstrapedMSBuild($"{projectFile.Path} /m:1 -nr:False -restore", out bool success); + string output = BootstrapRunner.ExecBootstrapedMSBuild($"{projectFile.Path} /m:1 -nr:False -restore", out bool success); _env.Output.WriteLine(output); success.ShouldBeTrue(); // The conflicting outputs warning appears diff --git a/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj b/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj new file mode 100644 index 00000000000..dab550407fd --- /dev/null +++ b/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj @@ -0,0 +1,94 @@ + + + + + + + $(RuntimeOutputTargetFrameworks) + $(RuntimeOutputPlatformTarget) + false + True + Microsoft.Build.Analyzers.UnitTests + Microsoft.Build.Analyzers.UnitTests + + + + + + + + + + + + + + + + + + + + + + + Shared\TestEnvironment.cs + + + + Shared\FileUtilities.cs + + + Shared\TempFileUtilities.cs + + + Shared\ErrorUtilities.cs + + + Shared\EscapingUtilities.cs + + + Shared\BuildEnvironmentHelper.cs + + + Shared\ProcessExtensions.cs + + + Shared\ResourceUtilities.cs + + + Shared\ExceptionHandling.cs + + + Shared\FileUtilitiesRegex.cs + + + Shared\AssemblyResources.cs + + + Shared\RunnerUtilities.cs + + + Shared\EnvironmentProvider.cs + + + + + + App.config + Designer + + + PreserveNewest + + + + + + + + <_Parameter1>$(ArtifactsBinDir) + <_Parameter2>$(BootstrapBinaryDestination) + + + diff --git a/src/Framework/Properties/AssemblyInfo.cs b/src/Framework/Properties/AssemblyInfo.cs index 6dbcdaaf37e..6c40f679898 100644 --- a/src/Framework/Properties/AssemblyInfo.cs +++ b/src/Framework/Properties/AssemblyInfo.cs @@ -46,6 +46,7 @@ [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Engine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] +[assembly: InternalsVisibleTo("Microsoft.Build.Analyzers.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] // Ideally we wouldn't need to IVT to OM.UnitTests, which is supposed to test // only the public surface area of Microsoft.Build. However, there's a bunch // of shared code in Framework that's used there, and we can still avoid IVT diff --git a/src/UnitTests.Shared/RunnerUtilities.cs b/src/UnitTests.Shared/RunnerUtilities.cs index f836a7be6bd..3eb1026dfd2 100644 --- a/src/UnitTests.Shared/RunnerUtilities.cs +++ b/src/UnitTests.Shared/RunnerUtilities.cs @@ -33,24 +33,6 @@ public static string ExecMSBuild(string msbuildParameters, out bool successfulEx return ExecMSBuild(PathToCurrentlyRunningMsBuildExe, msbuildParameters, out successfulExit, outputHelper: outputHelper); } - /// - /// Invoke the currently running msbuild and return the stdout, stderr, and process exit status. - /// This method may invoke msbuild via other runtimes. - /// - public static string ExecBootstrapedMSBuild(string msbuildParameters, out bool successfulExit, bool shellExecute = false, ITestOutputHelper outputHelper = null) - { - // TODO: use proper way of passing the bootsrtrap location: https://github.com/dotnet/msbuild/issues/9729 - string basePath = PathToCurrentlyRunningMsBuildExe.Substring(0, PathToCurrentlyRunningMsBuildExe.IndexOf(@"artifacts\bin", StringComparison.InvariantCultureIgnoreCase)); -#if NET - string pathToExecutable = EnvironmentProvider.GetDotnetExePath(); - msbuildParameters = Path.Combine(basePath, @"artifacts\bin\bootstrap\net8.0\MSBuild\msbuild.dll") + " " + msbuildParameters; -#else - string pathToExecutable = - Path.Combine(basePath, @"artifacts\bin\bootstrap\net472\MSBuild\Current\Bin\amd64\msbuild.exe"); -#endif - return RunProcessAndGetOutput(pathToExecutable, msbuildParameters, out successfulExit, shellExecute, outputHelper); - } - /// /// Invoke msbuild.exe with the given parameters and return the stdout, stderr, and process exit status. /// This method may invoke msbuild via other runtimes. From 59ab931843eaa81145f590fd25ce8338e877f546 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Tue, 13 Feb 2024 19:58:42 +0100 Subject: [PATCH 03/58] Hook analyzers stats stub --- eng/BootStrapMSBuild.targets | 2 +- src/Analyzers.UnitTests/BootstrapRunner.cs | 2 +- .../Microsoft.Build.Analyzers.UnitTests.csproj | 4 ++++ .../Infrastructure/AnalyzersConnectorLogger.cs | 17 ++++++++++++++--- .../Infrastructure/BuildAnalysisManager.cs | 10 ++-------- .../Analyzers/AnalyzerLoggingContextFactory.cs | 1 - .../BuildAnalysisLoggingContextExtensions.cs | 15 +++++++++++++++ src/Build/Microsoft.Build.csproj | 1 + .../Analyzers/IBuildAnalysisManager.cs | 4 +++- src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj | 5 +++-- 10 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 src/Build/Logging/Analyzers/BuildAnalysisLoggingContextExtensions.cs diff --git a/eng/BootStrapMSBuild.targets b/eng/BootStrapMSBuild.targets index cf294aa801e..1c42a35048c 100644 --- a/eng/BootStrapMSBuild.targets +++ b/eng/BootStrapMSBuild.targets @@ -1,6 +1,6 @@ - + @@ -43,8 +44,8 @@ - - + + From 92e3795badecd4b5c8f44643c4a5a3bd17624c87 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Wed, 14 Feb 2024 12:00:37 +0100 Subject: [PATCH 04/58] Fix unit tests by explicitly opting into analysis --- src/Analyzers.UnitTests/EndToEndTests.cs | 2 +- src/MSBuild.UnitTests/XMake_Tests.cs | 5 +++++ src/MSBuild/CommandLineSwitches.cs | 2 ++ src/MSBuild/XMake.cs | 3 +-- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Analyzers.UnitTests/EndToEndTests.cs b/src/Analyzers.UnitTests/EndToEndTests.cs index 76a5479ebaa..d2198fe4708 100644 --- a/src/Analyzers.UnitTests/EndToEndTests.cs +++ b/src/Analyzers.UnitTests/EndToEndTests.cs @@ -112,7 +112,7 @@ public void SampleAnalyzerIntegrationTest() // env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1"); env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); // string output = RunnerUtilities.ExecMSBuild($"{projectFile.Path} /m:1 -nr:False", out bool success); - string output = BootstrapRunner.ExecBootstrapedMSBuild($"{projectFile.Path} /m:1 -nr:False -restore", out bool success); + string output = BootstrapRunner.ExecBootstrapedMSBuild($"{projectFile.Path} /m:1 -nr:False -restore -analyze", out bool success); _env.Output.WriteLine(output); success.ShouldBeTrue(); // The conflicting outputs warning appears diff --git a/src/MSBuild.UnitTests/XMake_Tests.cs b/src/MSBuild.UnitTests/XMake_Tests.cs index ceaf38e0326..c7f8375d536 100644 --- a/src/MSBuild.UnitTests/XMake_Tests.cs +++ b/src/MSBuild.UnitTests/XMake_Tests.cs @@ -2589,6 +2589,11 @@ public override bool Execute() [InlineData("/v:normal /bl", MessageImportance.Low)] // v:normal but with binary logger so everything must be logged [InlineData("/v:minimal /bl", MessageImportance.Low)] // v:minimal but with binary logger so everything must be logged [InlineData("/v:quiet /bl", MessageImportance.Low)] // v:quiet but with binary logger so everything must be logged + [InlineData("/v:diagnostic /analyze", MessageImportance.Low)] + [InlineData("/v:detailed /analyze", MessageImportance.Low)] + [InlineData("/v:normal /analyze", MessageImportance.Low)] // v:normal but with analyzers so everything must be logged + [InlineData("/v:minimal /analyze", MessageImportance.Low)] // v:minimal but with analyzers so everything must be logged + [InlineData("/v:quiet /analyze", MessageImportance.Low)] // v:quiet but with analyzers so everything must be logged public void EndToEndMinimumMessageImportance(string arguments, MessageImportance expectedMinimumMessageImportance) { using TestEnvironment testEnvironment = UnitTests.TestEnvironment.Create(); diff --git a/src/MSBuild/CommandLineSwitches.cs b/src/MSBuild/CommandLineSwitches.cs index 3a53be7303a..6308ed1a357 100644 --- a/src/MSBuild/CommandLineSwitches.cs +++ b/src/MSBuild/CommandLineSwitches.cs @@ -99,6 +99,7 @@ internal enum ParameterizedSwitch WarningsNotAsErrors, WarningsAsMessages, BinaryLogger, + Analyze, Restore, ProfileEvaluation, RestoreProperty, @@ -266,6 +267,7 @@ internal ParameterizedSwitchInfo( new ParameterizedSwitchInfo( new string[] { "warnnotaserror", "noerr" }, ParameterizedSwitch.WarningsNotAsErrors, null, true, "MissingWarnNotAsErrorParameterError", true, false), new ParameterizedSwitchInfo( new string[] { "warnasmessage", "nowarn" }, ParameterizedSwitch.WarningsAsMessages, null, true, "MissingWarnAsMessageParameterError", true, false), new ParameterizedSwitchInfo( new string[] { "binarylogger", "bl" }, ParameterizedSwitch.BinaryLogger, null, false, null, true, false), + new ParameterizedSwitchInfo( new string[] { "analyze", "al" }, ParameterizedSwitch.Analyze, null, false, null, true, false), new ParameterizedSwitchInfo( new string[] { "restore", "r" }, ParameterizedSwitch.Restore, null, false, null, true, false), new ParameterizedSwitchInfo( new string[] { "profileevaluation", "prof" }, ParameterizedSwitch.ProfileEvaluation, null, false, "MissingProfileParameterError", true, false), new ParameterizedSwitchInfo( new string[] { "restoreproperty", "rp" }, ParameterizedSwitch.RestoreProperty, null, true, "MissingPropertyError", true, false), diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 3e33bfd9e96..59db32fed19 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -19,7 +19,6 @@ using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading; -using Microsoft.Build.Analyzers.Infrastructure; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Eventing; @@ -2725,7 +2724,7 @@ private static bool ProcessCommandLineSwitches( private static IBuildAnalysisLoggerFactory ProcessBuildAnalysisLoggerFactorySwitch(CommandLineSwitches commandLineSwitches) { // todo: opt-in behavior: https://github.com/dotnet/msbuild/issues/9723 - bool isAnalysisEnabled = true; + bool isAnalysisEnabled = commandLineSwitches.IsParameterizedSwitchSet(CommandLineSwitches.ParameterizedSwitch.Analyze); return isAnalysisEnabled ? new BuildAnalysisLoggerFactory() : null; } From 33c4d2e1671b69671af7f94dfc34bcdbcb3be488 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Thu, 15 Feb 2024 10:35:07 +0100 Subject: [PATCH 05/58] Disable build acceleration for MSBuild.Bootstrap --- eng/BootStrapMSBuild.targets | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eng/BootStrapMSBuild.targets b/eng/BootStrapMSBuild.targets index 1c42a35048c..1bfa14e0f34 100644 --- a/eng/BootStrapMSBuild.targets +++ b/eng/BootStrapMSBuild.targets @@ -14,6 +14,9 @@ false + + + false Date: Thu, 15 Feb 2024 17:40:15 +0100 Subject: [PATCH 06/58] Make EndToEndTests disposable --- src/Analyzers.UnitTests/EndToEndTests.cs | 52 ++++++++++++------------ 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/Analyzers.UnitTests/EndToEndTests.cs b/src/Analyzers.UnitTests/EndToEndTests.cs index d2198fe4708..708abfb129a 100644 --- a/src/Analyzers.UnitTests/EndToEndTests.cs +++ b/src/Analyzers.UnitTests/EndToEndTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.Build.Analyzers.UnitTests { - public class EndToEndTests + public class EndToEndTests : IDisposable { private readonly TestEnvironment _env; public EndToEndTests(ITestOutputHelper output) @@ -27,6 +27,8 @@ public EndToEndTests(ITestOutputHelper output) _env.WithEnvironmentInvariant(); } + public void Dispose() => _env.Dispose(); + [Fact] public void SampleAnalyzerIntegrationTest() { @@ -43,44 +45,44 @@ public void SampleAnalyzerIntegrationTest() - Test - + Test + - - + + - - + + """; string contents2 = $""" - + - - Exe - net8.0 - enable - enable - + + Exe + net8.0 + enable + enable + - - Test - + + Test + - - - + + + - - - + + + - - """; + + """; TransientTestFolder workFolder = env.CreateFolder(createFolder: true); TransientTestFile projectFile = env.CreateFile(workFolder, "FooBar.csproj", contents); TransientTestFile projectFile2 = env.CreateFile(workFolder, "FooBar-Copy.csproj", contents2); From 2db509fa6470841d395d311202f1b22f4f4210eb Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Thu, 15 Feb 2024 17:45:49 +0100 Subject: [PATCH 07/58] Support running Analyzers.UnitTests from stage1 --- eng/cibuild_bootstrapped_msbuild.ps1 | 3 +++ eng/cibuild_bootstrapped_msbuild.sh | 3 +++ src/Analyzers.UnitTests/BootstrapRunner.cs | 17 ++++++++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/eng/cibuild_bootstrapped_msbuild.ps1 b/eng/cibuild_bootstrapped_msbuild.ps1 index 83c0a808a68..21c676af220 100644 --- a/eng/cibuild_bootstrapped_msbuild.ps1 +++ b/eng/cibuild_bootstrapped_msbuild.ps1 @@ -112,6 +112,9 @@ try { # Opt into performance logging. https://github.com/dotnet/msbuild/issues/5900 $env:DOTNET_PERFLOG_DIR=$PerfLogDir + # Expose stage 1 path so unit tests can find the bootstrapped MSBuild. + $env:MSBUILD_BOOTSTRAPPED_BINDIR=$Stage1BinDir + # When using bootstrapped MSBuild: # - Turn off node reuse (so that bootstrapped MSBuild processes don't stay running and lock files) # - Do run tests diff --git a/eng/cibuild_bootstrapped_msbuild.sh b/eng/cibuild_bootstrapped_msbuild.sh index 50b23e83c14..e3b283402bc 100755 --- a/eng/cibuild_bootstrapped_msbuild.sh +++ b/eng/cibuild_bootstrapped_msbuild.sh @@ -66,6 +66,9 @@ mv $ArtifactsDir $Stage1Dir # Ensure that debug bits fail fast, rather than hanging waiting for a debugger attach. export MSBUILDDONOTLAUNCHDEBUGGER=true +# Expose stage 1 path so unit tests can find the bootstrapped MSBuild. +export MSBUILD_BOOTSTRAPPED_BINDIR="$Stage1Dir/bin" + # Opt into performance logging. export DOTNET_PERFLOG_DIR=$PerfLogDir diff --git a/src/Analyzers.UnitTests/BootstrapRunner.cs b/src/Analyzers.UnitTests/BootstrapRunner.cs index 5ac2402511b..02805ab8897 100644 --- a/src/Analyzers.UnitTests/BootstrapRunner.cs +++ b/src/Analyzers.UnitTests/BootstrapRunner.cs @@ -11,6 +11,10 @@ using Microsoft.Build.UnitTests.Shared; using Xunit.Abstractions; +#if FEATURE_MSIOREDIST +using Path = Microsoft.IO.Path; +#endif + namespace Microsoft.Build.Analyzers.UnitTests { internal static class BootstrapRunner @@ -20,10 +24,17 @@ internal static class BootstrapRunner // Better solution would be to have a single test utility project - instead of linked code files. public static string ExecBootstrapedMSBuild(string msbuildParameters, out bool successfulExit, bool shellExecute = false, ITestOutputHelper? outputHelper = null) { - var binaryFolder = Assembly.GetExecutingAssembly() - .GetCustomAttribute()! - .BootstrapMsbuildBinaryLocation; + BootstrapLocationAttribute attribute = Assembly.GetExecutingAssembly().GetCustomAttribute() + ?? throw new InvalidOperationException("This test assembly does not have the BootstrapLocationAttribute"); + string binaryFolder = attribute.BootstrapMsbuildBinaryLocation; + string? bindirOverride = Environment.GetEnvironmentVariable("MSBUILD_BOOTSTRAPPED_BINDIR"); + if (!string.IsNullOrEmpty(bindirOverride)) + { + // The bootstrap environment has moved to another location. Assume the same relative layout and adjust the path. + string relativePath = Path.GetRelativePath(attribute.BootstrapRoot, binaryFolder); + binaryFolder = Path.GetFullPath(relativePath, bindirOverride); + } #if NET string pathToExecutable = EnvironmentProvider.GetDotnetExePath()!; msbuildParameters = Path.Combine(binaryFolder, "msbuild.dll") + " " + msbuildParameters; From 4cdd9037e5a5b0fce48d3170ec1974ea4c183198 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Thu, 15 Feb 2024 18:43:13 +0100 Subject: [PATCH 08/58] Fix MSBuild.dll casing --- src/Analyzers.UnitTests/BootstrapRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers.UnitTests/BootstrapRunner.cs b/src/Analyzers.UnitTests/BootstrapRunner.cs index 02805ab8897..edd3cbf5467 100644 --- a/src/Analyzers.UnitTests/BootstrapRunner.cs +++ b/src/Analyzers.UnitTests/BootstrapRunner.cs @@ -37,7 +37,7 @@ public static string ExecBootstrapedMSBuild(string msbuildParameters, out bool s } #if NET string pathToExecutable = EnvironmentProvider.GetDotnetExePath()!; - msbuildParameters = Path.Combine(binaryFolder, "msbuild.dll") + " " + msbuildParameters; + msbuildParameters = Path.Combine(binaryFolder, "MSBuild.dll") + " " + msbuildParameters; #else string pathToExecutable = Path.Combine(binaryFolder, "msbuild.exe"); From 05450bb3c439526af4e23015ecd59c15cbbc10fa Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 16 Feb 2024 10:14:05 +0100 Subject: [PATCH 09/58] Don't run netfx Analyzer.UnitTests in Windows Core builds --- .../Microsoft.Build.Analyzers.UnitTests.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj b/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj index 3582a02f151..c7fc6ed59fc 100644 --- a/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj +++ b/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj @@ -4,7 +4,10 @@ - $(RuntimeOutputTargetFrameworks) + + $(LatestDotNetCoreForMSBuild) + $(FullFrameworkTFM);$(TargetFrameworks) + $(RuntimeOutputPlatformTarget) false True From 7e12ef07b6e693e2f6f4c3394b90a3e4a8e0d827 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 16 Feb 2024 11:46:01 +0100 Subject: [PATCH 10/58] Fix Analyzers.UnitTests on Mac --- src/Analyzers.UnitTests/EndToEndTests.cs | 27 +++++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Analyzers.UnitTests/EndToEndTests.cs b/src/Analyzers.UnitTests/EndToEndTests.cs index 708abfb129a..0b98ec87369 100644 --- a/src/Analyzers.UnitTests/EndToEndTests.cs +++ b/src/Analyzers.UnitTests/EndToEndTests.cs @@ -111,14 +111,25 @@ public void SampleAnalyzerIntegrationTest() } """); - // env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1"); - env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); - // string output = RunnerUtilities.ExecMSBuild($"{projectFile.Path} /m:1 -nr:False", out bool success); - string output = BootstrapRunner.ExecBootstrapedMSBuild($"{projectFile.Path} /m:1 -nr:False -restore -analyze", out bool success); - _env.Output.WriteLine(output); - success.ShouldBeTrue(); - // The conflicting outputs warning appears - output.ShouldContain("BC0101"); + // OSX links /var into /private, which makes Path.GetTempPath() return "/var..." but Directory.GetCurrentDirectory return "/private/var...". + // This discrepancy breaks path equality checks in analyzers if we pass to MSBuild full path to the initial project. + // TODO: See if there is a way of fixing it in the engine. + TransientTestState testState = _env.SetCurrentDirectory(Path.GetDirectoryName(projectFile.Path)); + try + { + // env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1"); + env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); + // string output = RunnerUtilities.ExecMSBuild($"{projectFile.Path} /m:1 -nr:False", out bool success); + string output = BootstrapRunner.ExecBootstrapedMSBuild($"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -analyze", out bool success); + _env.Output.WriteLine(output); + success.ShouldBeTrue(); + // The conflicting outputs warning appears + output.ShouldContain("BC0101"); + } + finally + { + testState.Revert(); + } } } } From fdda2cd5694dadb729b26a12e86ed5ae718f1c25 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Fri, 16 Feb 2024 14:09:50 +0100 Subject: [PATCH 11/58] Renaming changes --- src/Analyzers/API/BuildAnalysisContext.cs | 4 +- src/Analyzers/API/BuildAnalyzer.cs | 4 +- .../API/BuildAnalyzerConfiguration.cs | 4 +- ...alysisResult.cs => BuildAnalyzerResult.cs} | 28 +++--- ...rity.cs => BuildAnalyzerResultSeverity.cs} | 2 +- ...ldAnalysisRule.cs => BuildAnalyzerRule.cs} | 4 +- ...gerFactory.cs => BuildCopLoggerFactory.cs} | 4 +- .../Analyzers/SharedOutputPathAnalyzer.cs | 12 +-- .../Infrastructure/AnalyzersDelegates.cs | 8 ++ .../BuildAnalyzerConfigurationInternal.cs | 2 +- .../Infrastructure/BuildAnalyzerContext.cs | 99 ------------------- .../BuildAnalyzerTracingWrapper.cs | 30 ++++++ .../Infrastructure/BuildCopCentralContext.cs | 37 +++++++ ...orLogger.cs => BuildCopConnectorLogger.cs} | 9 +- .../Infrastructure/BuildCopContext.cs | 32 ++++++ ...dAnalysisManager.cs => BuildCopManager.cs} | 22 ++--- .../Infrastructure/ConfigurationProvider.cs | 2 +- .../Infrastructure/IBuildCopContext.cs | 10 ++ .../BackEnd/BuildManager/BuildManager.cs | 8 +- .../BackEnd/BuildManager/BuildParameters.cs | 4 +- ...erFactory.cs => IBuildCopLoggerFactory.cs} | 2 +- ...AnalysisManager.cs => IBuildCopManager.cs} | 2 +- .../CommandLineSwitches_Tests.cs | 2 +- src/MSBuild/XMake.cs | 20 ++-- 24 files changed, 186 insertions(+), 165 deletions(-) rename src/Analyzers/API/{BuildAnalysisResult.cs => BuildAnalyzerResult.cs} (73%) rename src/Analyzers/API/{BuildAnalysisResultSeverity.cs => BuildAnalyzerResultSeverity.cs} (84%) rename src/Analyzers/API/{BuildAnalysisRule.cs => BuildAnalyzerRule.cs} (89%) rename src/Analyzers/API/{BuildAnalysisLoggerFactory.cs => BuildCopLoggerFactory.cs} (74%) create mode 100644 src/Analyzers/Infrastructure/AnalyzersDelegates.cs delete mode 100644 src/Analyzers/Infrastructure/BuildAnalyzerContext.cs create mode 100644 src/Analyzers/Infrastructure/BuildAnalyzerTracingWrapper.cs create mode 100644 src/Analyzers/Infrastructure/BuildCopCentralContext.cs rename src/Analyzers/Infrastructure/{AnalyzersConnectorLogger.cs => BuildCopConnectorLogger.cs} (84%) create mode 100644 src/Analyzers/Infrastructure/BuildCopContext.cs rename src/Analyzers/Infrastructure/{BuildAnalysisManager.cs => BuildCopManager.cs} (82%) create mode 100644 src/Analyzers/Infrastructure/IBuildCopContext.cs rename src/Framework/Analyzers/{IBuildAnalysisLoggerFactory.cs => IBuildCopLoggerFactory.cs} (87%) rename src/Framework/Analyzers/{IBuildAnalysisManager.cs => IBuildCopManager.cs} (93%) diff --git a/src/Analyzers/API/BuildAnalysisContext.cs b/src/Analyzers/API/BuildAnalysisContext.cs index 098ae5470f6..8dbb5ae5041 100644 --- a/src/Analyzers/API/BuildAnalysisContext.cs +++ b/src/Analyzers/API/BuildAnalysisContext.cs @@ -19,9 +19,9 @@ public class BuildAnalysisContext internal BuildAnalysisContext(LoggingContext loggingContext) => _loggingContext = loggingContext; - public void ReportResult(BuildAnalysisResult result) + public void ReportResult(BuildAnalyzerResult result) { - BuildEventArgs eventArgs = result.ToEventArgs(ConfigurationProvider.GetMergedConfiguration(result.BuildAnalysisRule).Severity); + BuildEventArgs eventArgs = result.ToEventArgs(ConfigurationProvider.GetMergedConfiguration(result.BuildAnalyzerRule).Severity); eventArgs.BuildEventContext = _loggingContext.BuildEventContext; _loggingContext.LogBuildEvent(eventArgs); } diff --git a/src/Analyzers/API/BuildAnalyzer.cs b/src/Analyzers/API/BuildAnalyzer.cs index cbe39d9c8a6..f05c452521d 100644 --- a/src/Analyzers/API/BuildAnalyzer.cs +++ b/src/Analyzers/API/BuildAnalyzer.cs @@ -8,8 +8,8 @@ namespace Microsoft.Build.Experimental; public abstract class BuildAnalyzer { public abstract string FriendlyName { get; } - public abstract ImmutableArray SupportedRules { get; } + public abstract ImmutableArray SupportedRules { get; } public abstract void Initialize(ConfigurationContext configurationContext); - public abstract void RegisterActions(IBuildAnalyzerContext context); + public abstract void RegisterActions(IBuildCopContext context); } diff --git a/src/Analyzers/API/BuildAnalyzerConfiguration.cs b/src/Analyzers/API/BuildAnalyzerConfiguration.cs index b9479d88cff..7a5b75a506c 100644 --- a/src/Analyzers/API/BuildAnalyzerConfiguration.cs +++ b/src/Analyzers/API/BuildAnalyzerConfiguration.cs @@ -11,7 +11,7 @@ public class BuildAnalyzerConfiguration SupportedInvocationConcurrency = InvocationConcurrency.Parallel, PerformanceWeightClass = Experimental.PerformanceWeightClass.Normal, EvaluationAnalysisScope = Experimental.EvaluationAnalysisScope.AnalyzedProjectOnly, - Severity = BuildAnalysisResultSeverity.Info, + Severity = BuildAnalyzerResultSeverity.Info, IsEnabled = false, }; @@ -21,6 +21,6 @@ public class BuildAnalyzerConfiguration public InvocationConcurrency? SupportedInvocationConcurrency { get; internal init; } public PerformanceWeightClass? PerformanceWeightClass { get; internal init; } public EvaluationAnalysisScope? EvaluationAnalysisScope { get; internal init; } - public BuildAnalysisResultSeverity? Severity { get; internal init; } + public BuildAnalyzerResultSeverity? Severity { get; internal init; } public bool? IsEnabled { get; internal init; } } diff --git a/src/Analyzers/API/BuildAnalysisResult.cs b/src/Analyzers/API/BuildAnalyzerResult.cs similarity index 73% rename from src/Analyzers/API/BuildAnalysisResult.cs rename to src/Analyzers/API/BuildAnalyzerResult.cs index 0b5fc893d06..88291ce2c67 100644 --- a/src/Analyzers/API/BuildAnalysisResult.cs +++ b/src/Analyzers/API/BuildAnalyzerResult.cs @@ -8,40 +8,40 @@ namespace Microsoft.Build.Experimental; -public class BuildAnalysisResult +public class BuildAnalyzerResult { - public static BuildAnalysisResult Create(BuildAnalysisRule rule, ElementLocation location, params string[] messageArgs) + public static BuildAnalyzerResult Create(BuildAnalyzerRule rule, ElementLocation location, params string[] messageArgs) { - return new BuildAnalysisResult(rule, location, messageArgs); + return new BuildAnalyzerResult(rule, location, messageArgs); } - public BuildAnalysisResult(BuildAnalysisRule buildAnalysisRule, ElementLocation location, string[] messageArgs) + public BuildAnalyzerResult(BuildAnalyzerRule buildAnalyzerRule, ElementLocation location, string[] messageArgs) { - BuildAnalysisRule = buildAnalysisRule; + BuildAnalyzerRule = buildAnalyzerRule; Location = location; MessageArgs = messageArgs; } - internal BuildEventArgs ToEventArgs(BuildAnalysisResultSeverity severity) + internal BuildEventArgs ToEventArgs(BuildAnalyzerResultSeverity severity) => severity switch { - BuildAnalysisResultSeverity.Info => new BuildAnalysisResultMessage(this), - BuildAnalysisResultSeverity.Warning => new BuildAnalysisResultWarning(this), - BuildAnalysisResultSeverity.Error => new BuildAnalysisResultError(this), + BuildAnalyzerResultSeverity.Info => new BuildAnalysisResultMessage(this), + BuildAnalyzerResultSeverity.Warning => new BuildAnalysisResultWarning(this), + BuildAnalyzerResultSeverity.Error => new BuildAnalysisResultError(this), _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null), }; - public BuildAnalysisRule BuildAnalysisRule { get; } + public BuildAnalyzerRule BuildAnalyzerRule { get; } public ElementLocation Location { get; } public string[] MessageArgs { get; } private string? _message; - public string Message => _message ??= $"{(Equals(Location ?? ElementLocation.EmptyLocation, ElementLocation.EmptyLocation) ? string.Empty : (Location!.LocationString + ": "))}{BuildAnalysisRule.Id}: {string.Format(BuildAnalysisRule.MessageFormat, MessageArgs)}"; + public string Message => _message ??= $"{(Equals(Location ?? ElementLocation.EmptyLocation, ElementLocation.EmptyLocation) ? string.Empty : (Location!.LocationString + ": "))}{BuildAnalyzerRule.Id}: {string.Format(BuildAnalyzerRule.MessageFormat, MessageArgs)}"; } public sealed class BuildAnalysisResultWarning : BuildWarningEventArgs { - public BuildAnalysisResultWarning(BuildAnalysisResult result) + public BuildAnalysisResultWarning(BuildAnalyzerResult result) { this.Message = result.Message; } @@ -66,7 +66,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) public sealed class BuildAnalysisResultError : BuildErrorEventArgs { - public BuildAnalysisResultError(BuildAnalysisResult result) + public BuildAnalysisResultError(BuildAnalyzerResult result) { this.Message = result.Message; } @@ -91,7 +91,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) public sealed class BuildAnalysisResultMessage : BuildMessageEventArgs { - public BuildAnalysisResultMessage(BuildAnalysisResult result) + public BuildAnalysisResultMessage(BuildAnalyzerResult result) { this.Message = result.Message; } diff --git a/src/Analyzers/API/BuildAnalysisResultSeverity.cs b/src/Analyzers/API/BuildAnalyzerResultSeverity.cs similarity index 84% rename from src/Analyzers/API/BuildAnalysisResultSeverity.cs rename to src/Analyzers/API/BuildAnalyzerResultSeverity.cs index d3eeb7c7bd1..3e067db3cf8 100644 --- a/src/Analyzers/API/BuildAnalysisResultSeverity.cs +++ b/src/Analyzers/API/BuildAnalyzerResultSeverity.cs @@ -3,7 +3,7 @@ namespace Microsoft.Build.Experimental; -public enum BuildAnalysisResultSeverity +public enum BuildAnalyzerResultSeverity { Info, Warning, diff --git a/src/Analyzers/API/BuildAnalysisRule.cs b/src/Analyzers/API/BuildAnalyzerRule.cs similarity index 89% rename from src/Analyzers/API/BuildAnalysisRule.cs rename to src/Analyzers/API/BuildAnalyzerRule.cs index 8858b8dc44a..5c9f739497c 100644 --- a/src/Analyzers/API/BuildAnalysisRule.cs +++ b/src/Analyzers/API/BuildAnalyzerRule.cs @@ -3,9 +3,9 @@ namespace Microsoft.Build.Experimental; -public class BuildAnalysisRule +public class BuildAnalyzerRule { - public BuildAnalysisRule(string id, string title, string description, string category, string messageFormat, + public BuildAnalyzerRule(string id, string title, string description, string category, string messageFormat, BuildAnalyzerConfiguration defaultConfiguration) { Id = id; diff --git a/src/Analyzers/API/BuildAnalysisLoggerFactory.cs b/src/Analyzers/API/BuildCopLoggerFactory.cs similarity index 74% rename from src/Analyzers/API/BuildAnalysisLoggerFactory.cs rename to src/Analyzers/API/BuildCopLoggerFactory.cs index 4ed8d920770..accd1db9c2e 100644 --- a/src/Analyzers/API/BuildAnalysisLoggerFactory.cs +++ b/src/Analyzers/API/BuildCopLoggerFactory.cs @@ -11,10 +11,10 @@ namespace Microsoft.Build.Experimental; -public class BuildAnalysisLoggerFactory : IBuildAnalysisLoggerFactory +public class BuildCopLoggerFactory : IBuildCopLoggerFactory { public ILogger CreateBuildAnalysisLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory) { - return new AnalyzersConnectorLogger(loggingContextFactory, BuildAnalysisManager.Instance); + return new BuildCopConnectorLogger(loggingContextFactory, BuildCopManager.Instance); } } diff --git a/src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs b/src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs index a49e4769ed9..2cddcf3da27 100644 --- a/src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs +++ b/src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs @@ -16,25 +16,25 @@ namespace Microsoft.Build.Analyzers.Analyzers; // * https://github.com/dotnet/roslyn/issues/40351 // // quick suggestion now - let's force external ids to start with 'X', for ours - avoid 'MSB' -// maybe - BS - build styling; BA - build authoring; BE - build execution/environment; BC - build configuration +// maybe - BT - build static/styling; BA - build authoring; BE - build execution/environment; BC - build configuration internal sealed class SharedOutputPathAnalyzer : BuildAnalyzer { - public static BuildAnalysisRule SupportedRule = new BuildAnalysisRule("BC0101", "ConflictingOutputPath", + public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule("BC0101", "ConflictingOutputPath", "Two projects should not share their OutputPath nor IntermediateOutputPath locations", "Configuration", "Projects {0} and {1} have conflicting output paths: {2}.", - new BuildAnalyzerConfiguration() { Severity = BuildAnalysisResultSeverity.Warning, IsEnabled = true }); + new BuildAnalyzerConfiguration() { Severity = BuildAnalyzerResultSeverity.Warning, IsEnabled = true }); public override string FriendlyName => "MSBuild.SharedOutputPathAnalyzer"; - public override ImmutableArray SupportedRules { get; } =[SupportedRule]; + public override ImmutableArray SupportedRules { get; } =[SupportedRule]; public override void Initialize(ConfigurationContext configurationContext) { /* This is it - no custom configuration */ } - public override void RegisterActions(IBuildAnalyzerContext context) + public override void RegisterActions(IBuildCopContext context) { context.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction); } @@ -81,7 +81,7 @@ private void EvaluatedPropertiesAction(EvaluatedPropertiesContext context) if (_projectsPerOutputPath.TryGetValue(path!, out string? conflictingProject)) { - context.ReportResult(BuildAnalysisResult.Create( + context.ReportResult(BuildAnalyzerResult.Create( SupportedRule, // TODO: let's support transmitting locations of specific properties ElementLocation.EmptyLocation, diff --git a/src/Analyzers/Infrastructure/AnalyzersDelegates.cs b/src/Analyzers/Infrastructure/AnalyzersDelegates.cs new file mode 100644 index 00000000000..1eecd0c5c9d --- /dev/null +++ b/src/Analyzers/Infrastructure/AnalyzersDelegates.cs @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public delegate void EvaluatedPropertiesAction(EvaluatedPropertiesContext context); + +public delegate void ParsedItemsAction(ParsedItemsContext context); diff --git a/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs b/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs index cef5165db2f..fdffcb98a27 100644 --- a/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs +++ b/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs @@ -19,6 +19,6 @@ internal sealed class BuildAnalyzerConfigurationInternal public InvocationConcurrency SupportedInvocationConcurrency { get; internal init; } public PerformanceWeightClass PerformanceWeightClass { get; internal init; } public EvaluationAnalysisScope EvaluationAnalysisScope { get; internal init; } - public BuildAnalysisResultSeverity Severity { get; internal init; } + public BuildAnalyzerResultSeverity Severity { get; internal init; } public bool IsEnabled { get; internal init; } } diff --git a/src/Analyzers/Infrastructure/BuildAnalyzerContext.cs b/src/Analyzers/Infrastructure/BuildAnalyzerContext.cs deleted file mode 100644 index 832bb94872a..00000000000 --- a/src/Analyzers/Infrastructure/BuildAnalyzerContext.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Microsoft.Build.Experimental; - -public delegate void EvaluatedPropertiesAction(EvaluatedPropertiesContext context); -public delegate void ParsedItemsAction(ParsedItemsContext context); - -internal sealed class CentralBuildAnalyzerContext -{ - private EvaluatedPropertiesAction? _evaluatedPropertiesActions; - private ParsedItemsAction? _parsedItemsActions; - - // This we can potentially use to subscribe for receiving evaluated props in the - // build event args. However - this needs to be done early on, when analyzers might not be known yet - internal bool HasEvaluatedPropertiesActions => _evaluatedPropertiesActions != null; - internal bool HasParsedItemsActions => _parsedItemsActions != null; - - internal void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction) - { - // Here we might want to communicate to node that props need to be sent. - // (it was being communicated via MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION) - _evaluatedPropertiesActions += evaluatedPropertiesAction; - } - - internal void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction) - { - _parsedItemsActions += parsedItemsAction; - } - - internal void RunEvaluatedPropertiesActions(EvaluatedPropertiesContext evaluatedPropertiesContext) - { - _evaluatedPropertiesActions?.Invoke(evaluatedPropertiesContext); - } - - internal void RunParsedItemsActions(ParsedItemsContext parsedItemsContext) - { - _parsedItemsActions?.Invoke(parsedItemsContext); - } -} - -internal sealed class BuildAnalyzerTracingWrapper -{ - private readonly Stopwatch _stopwatch = new Stopwatch(); - - public BuildAnalyzerTracingWrapper(BuildAnalyzer buildAnalyzer) - => BuildAnalyzer = buildAnalyzer; - - internal BuildAnalyzer BuildAnalyzer { get; } - - internal TimeSpan Elapsed => _stopwatch.Elapsed; - - internal IDisposable StartSpan() - { - _stopwatch.Start(); - return new CleanupScope(_stopwatch.Stop); - } - - internal readonly struct CleanupScope(Action disposeAction) : IDisposable - { - public void Dispose() => disposeAction(); - } -} - -public interface IBuildAnalyzerContext -{ - void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction); - void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction); -} - -internal sealed class BuildAnalyzerContext(BuildAnalyzerTracingWrapper analyzer, CentralBuildAnalyzerContext centralContext) : IBuildAnalyzerContext -{ - public void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction) - { - void WrappedEvaluatedPropertiesAction(EvaluatedPropertiesContext context) - { - using var _ = analyzer.StartSpan(); - evaluatedPropertiesAction(context); - } - - centralContext.RegisterEvaluatedPropertiesAction(WrappedEvaluatedPropertiesAction); - } - - public void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction) - { - void WrappedParsedItemsAction(ParsedItemsContext context) - { - using var _ = analyzer.StartSpan(); - parsedItemsAction(context); - } - - centralContext.RegisterParsedItemsAction(WrappedParsedItemsAction); - } -} diff --git a/src/Analyzers/Infrastructure/BuildAnalyzerTracingWrapper.cs b/src/Analyzers/Infrastructure/BuildAnalyzerTracingWrapper.cs new file mode 100644 index 00000000000..21895de6818 --- /dev/null +++ b/src/Analyzers/Infrastructure/BuildAnalyzerTracingWrapper.cs @@ -0,0 +1,30 @@ +// 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.Diagnostics; + +namespace Microsoft.Build.Experimental; + +internal sealed class BuildAnalyzerTracingWrapper +{ + private readonly Stopwatch _stopwatch = new Stopwatch(); + + public BuildAnalyzerTracingWrapper(BuildAnalyzer buildAnalyzer) + => BuildAnalyzer = buildAnalyzer; + + internal BuildAnalyzer BuildAnalyzer { get; } + + internal TimeSpan Elapsed => _stopwatch.Elapsed; + + internal IDisposable StartSpan() + { + _stopwatch.Start(); + return new CleanupScope(_stopwatch.Stop); + } + + internal readonly struct CleanupScope(Action disposeAction) : IDisposable + { + public void Dispose() => disposeAction(); + } +} diff --git a/src/Analyzers/Infrastructure/BuildCopCentralContext.cs b/src/Analyzers/Infrastructure/BuildCopCentralContext.cs new file mode 100644 index 00000000000..f3f2476abcd --- /dev/null +++ b/src/Analyzers/Infrastructure/BuildCopCentralContext.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +internal sealed class BuildCopCentralContext +{ + private EvaluatedPropertiesAction? _evaluatedPropertiesActions; + private ParsedItemsAction? _parsedItemsActions; + + // This we can potentially use to subscribe for receiving evaluated props in the + // build event args. However - this needs to be done early on, when analyzers might not be known yet + internal bool HasEvaluatedPropertiesActions => _evaluatedPropertiesActions != null; + internal bool HasParsedItemsActions => _parsedItemsActions != null; + + internal void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction) + { + // Here we might want to communicate to node that props need to be sent. + // (it was being communicated via MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION) + _evaluatedPropertiesActions += evaluatedPropertiesAction; + } + + internal void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction) + { + _parsedItemsActions += parsedItemsAction; + } + + internal void RunEvaluatedPropertiesActions(EvaluatedPropertiesContext evaluatedPropertiesContext) + { + _evaluatedPropertiesActions?.Invoke(evaluatedPropertiesContext); + } + + internal void RunParsedItemsActions(ParsedItemsContext parsedItemsContext) + { + _parsedItemsActions?.Invoke(parsedItemsContext); + } +} diff --git a/src/Analyzers/Infrastructure/AnalyzersConnectorLogger.cs b/src/Analyzers/Infrastructure/BuildCopConnectorLogger.cs similarity index 84% rename from src/Analyzers/Infrastructure/AnalyzersConnectorLogger.cs rename to src/Analyzers/Infrastructure/BuildCopConnectorLogger.cs index 2ed68e9c706..2999bd2218c 100644 --- a/src/Analyzers/Infrastructure/AnalyzersConnectorLogger.cs +++ b/src/Analyzers/Infrastructure/BuildCopConnectorLogger.cs @@ -4,6 +4,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System.Linq; using System.Text; using System.Threading.Tasks; @@ -13,7 +16,7 @@ using Microsoft.Build.Logging.Analyzers; namespace Microsoft.Build.Analyzers.Infrastructure; -internal sealed class AnalyzersConnectorLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory, IBuildAnalysisManager buildAnalysisManager) +internal sealed class BuildCopConnectorLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory, IBuildCopManager buildCopManager) : ILogger { public LoggerVerbosity Verbosity { get; set; } @@ -32,7 +35,7 @@ private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) { try { - buildAnalysisManager.ProcessEvaluationFinishedEventArgs( + buildCopManager.ProcessEvaluationFinishedEventArgs( loggingContextFactory.CreateLoggingContext(e.BuildEventContext!), projectEvaluationFinishedEventArgs); } @@ -55,7 +58,7 @@ private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e) LoggingContext loggingContext = loggingContextFactory.CreateLoggingContext(buildEventContext).ToLoggingContext(); // TODO: here flush the tracing stats: https://github.com/dotnet/msbuild/issues/9629 - loggingContext.LogCommentFromText(MessageImportance.High, buildAnalysisManager.CreateTracingStats()); + loggingContext.LogCommentFromText(MessageImportance.High, buildCopManager.CreateTracingStats()); } public void Shutdown() diff --git a/src/Analyzers/Infrastructure/BuildCopContext.cs b/src/Analyzers/Infrastructure/BuildCopContext.cs new file mode 100644 index 00000000000..c97fc9af7f7 --- /dev/null +++ b/src/Analyzers/Infrastructure/BuildCopContext.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Microsoft.Build.Experimental; + +internal sealed class BuildCopContext(BuildAnalyzerTracingWrapper analyzer, BuildCopCentralContext buildCopCentralContext) : IBuildCopContext +{ + public void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction) + { + void WrappedEvaluatedPropertiesAction(EvaluatedPropertiesContext context) + { + using var _ = analyzer.StartSpan(); + evaluatedPropertiesAction(context); + } + + buildCopCentralContext.RegisterEvaluatedPropertiesAction(WrappedEvaluatedPropertiesAction); + } + + public void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction) + { + void WrappedParsedItemsAction(ParsedItemsContext context) + { + using var _ = analyzer.StartSpan(); + parsedItemsAction(context); + } + + buildCopCentralContext.RegisterParsedItemsAction(WrappedParsedItemsAction); + } +} diff --git a/src/Analyzers/Infrastructure/BuildAnalysisManager.cs b/src/Analyzers/Infrastructure/BuildCopManager.cs similarity index 82% rename from src/Analyzers/Infrastructure/BuildAnalysisManager.cs rename to src/Analyzers/Infrastructure/BuildCopManager.cs index 113186bac9d..2153125e9a7 100644 --- a/src/Analyzers/Infrastructure/BuildAnalysisManager.cs +++ b/src/Analyzers/Infrastructure/BuildCopManager.cs @@ -19,14 +19,14 @@ namespace Microsoft.Build.Analyzers.Infrastructure; -internal sealed class BuildAnalysisManager : IBuildAnalysisManager +internal sealed class BuildCopManager : IBuildCopManager { private readonly List _analyzers = new(); - private readonly CentralBuildAnalyzerContext _centralContext = new(); + private readonly BuildCopCentralContext _buildCopCentralContext = new(); - private BuildAnalysisManager() { } + private BuildCopManager() { } - internal static IBuildAnalysisManager Instance => CreateBuildAnalysisManager(); + internal static IBuildCopManager Instance => CreateBuildAnalysisManager(); public void RegisterAnalyzer(BuildAnalyzer analyzer) { @@ -47,7 +47,7 @@ public void RegisterAnalyzer(BuildAnalyzer analyzer) ConfigurationContext configurationContext = ConfigurationContext.Null; analyzer.Initialize(configurationContext); var wrappedAnalyzer = new BuildAnalyzerTracingWrapper(analyzer); - var wrappedContext = new BuildAnalyzerContext(wrappedAnalyzer, _centralContext); + var wrappedContext = new BuildCopContext(wrappedAnalyzer, _buildCopCentralContext); analyzer.RegisterActions(wrappedContext); _analyzers.Add(wrappedAnalyzer); } @@ -70,9 +70,9 @@ public void ProcessEvaluationFinishedEventArgs(IBuildAnalysisLoggingContext buil new ReadOnlyDictionary(propertiesLookup), evaluationFinishedEventArgs.ProjectFile!); - _centralContext.RunEvaluatedPropertiesActions(context); + _buildCopCentralContext.RunEvaluatedPropertiesActions(context); - if (_centralContext.HasParsedItemsActions) + if (_buildCopCentralContext.HasParsedItemsActions) { ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(evaluationFinishedEventArgs.ProjectFile!, /*unused*/ null, /*unused*/null, _cache, false /*Not explicitly loaded - unused*/); @@ -80,12 +80,12 @@ public void ProcessEvaluationFinishedEventArgs(IBuildAnalysisLoggingContext buil ParsedItemsContext parsedItemsContext = new ParsedItemsContext(loggingContext, new ItemsHolder(xml.Items, xml.ItemGroups)); - _centralContext.RunParsedItemsActions(parsedItemsContext); + _buildCopCentralContext.RunParsedItemsActions(parsedItemsContext); } } // TODO: tracing: https://github.com/dotnet/msbuild/issues/9629 - // should have infra as well, should log to AnalyzersConnectorLogger upon shutdown (if requested) + // should have infra as well, should log to BuildCopConnectorLogger upon shutdown (if requested) public string CreateTracingStats() { return string.Join(Environment.NewLine, @@ -95,9 +95,9 @@ string GetAnalyzerDescriptor(BuildAnalyzer buildAnalyzer) => buildAnalyzer.FriendlyName + " (" + buildAnalyzer.GetType() + ")"; } - internal static BuildAnalysisManager CreateBuildAnalysisManager() + internal static BuildCopManager CreateBuildAnalysisManager() { - var buildAnalysisManager = new BuildAnalysisManager(); + var buildAnalysisManager = new BuildCopManager(); buildAnalysisManager.RegisterAnalyzer(new SharedOutputPathAnalyzer()); // ... Register other internal analyzers return buildAnalysisManager; diff --git a/src/Analyzers/Infrastructure/ConfigurationProvider.cs b/src/Analyzers/Infrastructure/ConfigurationProvider.cs index f0935938997..11abc7a75a1 100644 --- a/src/Analyzers/Infrastructure/ConfigurationProvider.cs +++ b/src/Analyzers/Infrastructure/ConfigurationProvider.cs @@ -74,7 +74,7 @@ private static Dictionary LoadConfiguration( /// /// /// - public static BuildAnalyzerConfigurationInternal GetMergedConfiguration(BuildAnalysisRule analyzerRule) + public static BuildAnalyzerConfigurationInternal GetMergedConfiguration(BuildAnalyzerRule analyzerRule) { if (!_editorConfig.TryGetValue(analyzerRule.Id, out BuildAnalyzerConfiguration? editorConfig)) { diff --git a/src/Analyzers/Infrastructure/IBuildCopContext.cs b/src/Analyzers/Infrastructure/IBuildCopContext.cs new file mode 100644 index 00000000000..4c6ac35a4ec --- /dev/null +++ b/src/Analyzers/Infrastructure/IBuildCopContext.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Experimental; + +public interface IBuildCopContext +{ + void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction); + void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction); +} diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 8a8cb91f608..a8e59cf6f38 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -629,7 +629,7 @@ ILoggingService InitializeLoggingService() { ILoggingService loggingService = CreateLoggingService( AppendDebuggingLoggers(_buildParameters.Loggers), - _buildParameters.BuildAnalysisLoggerFactory, + _buildParameters.BuildCopLoggerFactory, _buildParameters.ForwardingLoggers, _buildParameters.WarningsAsErrors, _buildParameters.WarningsNotAsErrors, @@ -2947,7 +2947,7 @@ private void OnProjectStarted(object sender, ProjectStartedEventArgs e) /// private ILoggingService CreateLoggingService( IEnumerable loggers, - IBuildAnalysisLoggerFactory buildAnalysisLoggerFactory, + IBuildCopLoggerFactory buildCopLoggerFactory, IEnumerable forwardingLoggers, ISet warningsAsErrors, ISet warningsNotAsErrors, @@ -2975,11 +2975,11 @@ private ILoggingService CreateLoggingService( loggingService.WarningsNotAsErrors = warningsNotAsErrors; loggingService.WarningsAsMessages = warningsAsMessages; - if (buildAnalysisLoggerFactory != null) + if (buildCopLoggerFactory != null) { loggers = (loggers ?? Enumerable.Empty()).Concat(new[] { - buildAnalysisLoggerFactory.CreateBuildAnalysisLogger( + buildCopLoggerFactory.CreateBuildAnalysisLogger( new AnalyzerLoggingContextFactory(loggingService)) }); } diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index 697387a5f73..525a335dc37 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -310,7 +310,7 @@ internal BuildParameters(BuildParameters other, bool resetEnvironment = false) DiscardBuildResults = other.DiscardBuildResults; LowPriority = other.LowPriority; Question = other.Question; - BuildAnalysisLoggerFactory = other.BuildAnalysisLoggerFactory; + BuildCopLoggerFactory = other.BuildCopLoggerFactory; ProjectCacheDescriptor = other.ProjectCacheDescriptor; } @@ -839,7 +839,7 @@ public bool Question /// /// Gets or sets a factory for build analysis infrastructure logger /// - public IBuildAnalysisLoggerFactory BuildAnalysisLoggerFactory { get; set; } + public IBuildCopLoggerFactory BuildCopLoggerFactory { get; set; } /// /// Gets or sets the project cache description to use for all or diff --git a/src/Framework/Analyzers/IBuildAnalysisLoggerFactory.cs b/src/Framework/Analyzers/IBuildCopLoggerFactory.cs similarity index 87% rename from src/Framework/Analyzers/IBuildAnalysisLoggerFactory.cs rename to src/Framework/Analyzers/IBuildCopLoggerFactory.cs index 2f7fae150aa..4d7c3d76d81 100644 --- a/src/Framework/Analyzers/IBuildAnalysisLoggerFactory.cs +++ b/src/Framework/Analyzers/IBuildCopLoggerFactory.cs @@ -5,7 +5,7 @@ namespace Microsoft.Build.Experimental; -public interface IBuildAnalysisLoggerFactory +public interface IBuildCopLoggerFactory { ILogger CreateBuildAnalysisLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory); } diff --git a/src/Framework/Analyzers/IBuildAnalysisManager.cs b/src/Framework/Analyzers/IBuildCopManager.cs similarity index 93% rename from src/Framework/Analyzers/IBuildAnalysisManager.cs rename to src/Framework/Analyzers/IBuildCopManager.cs index 8a1a7c0d215..9cc66a52525 100644 --- a/src/Framework/Analyzers/IBuildAnalysisManager.cs +++ b/src/Framework/Analyzers/IBuildCopManager.cs @@ -10,7 +10,7 @@ namespace Microsoft.Build.Experimental; -public interface IBuildAnalysisManager +public interface IBuildCopManager { void ProcessEvaluationFinishedEventArgs( IBuildAnalysisLoggingContext buildAnalysisContext, diff --git a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs index e680e3f20c5..35d2241c072 100644 --- a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs +++ b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs @@ -1185,7 +1185,7 @@ public void InvalidToolsVersionErrors() graphBuildOptions: null, lowPriority: false, question: false, - buildAnalysisLoggerFactory: null, + buildCopLoggerFactory: null, inputResultsCaches: null, outputResultsCache: null, saveProjectResult: false, diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 59db32fed19..1cea300db64 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -717,7 +717,7 @@ public static ExitType Execute( string[] inputResultsCaches = null; string outputResultsCache = null; bool question = false; - IBuildAnalysisLoggerFactory buildAnalysisLoggerFactory = null; + IBuildCopLoggerFactory buildCopLoggerFactory = null; string[] getProperty = Array.Empty(); string[] getItem = Array.Empty(); string[] getTargetResult = Array.Empty(); @@ -764,7 +764,7 @@ public static ExitType Execute( #endif ref lowPriority, ref question, - ref buildAnalysisLoggerFactory, + ref buildCopLoggerFactory, ref getProperty, ref getItem, ref getTargetResult, @@ -865,7 +865,7 @@ public static ExitType Execute( graphBuildOptions, lowPriority, question, - buildAnalysisLoggerFactory, + buildCopLoggerFactory, inputResultsCaches, outputResultsCache, saveProjectResult: outputPropertiesItemsOrTargetResults, @@ -1247,7 +1247,7 @@ internal static bool BuildProject( GraphBuildOptions graphBuildOptions, bool lowPriority, bool question, - IBuildAnalysisLoggerFactory buildAnalysisLoggerFactory, + IBuildCopLoggerFactory buildCopLoggerFactory, string[] inputResultsCaches, string outputResultsCache, bool saveProjectResult, @@ -1449,7 +1449,7 @@ internal static bool BuildProject( parameters.InputResultsCacheFiles = inputResultsCaches; parameters.OutputResultsCacheFile = outputResultsCache; parameters.Question = question; - parameters.BuildAnalysisLoggerFactory = buildAnalysisLoggerFactory; + parameters.BuildCopLoggerFactory = buildCopLoggerFactory; #if FEATURE_REPORTFILEACCESSES parameters.ReportFileAccesses = reportFileAccesses; #endif @@ -2421,7 +2421,7 @@ private static bool ProcessCommandLineSwitches( #endif ref bool lowPriority, ref bool question, - ref IBuildAnalysisLoggerFactory buildAnalysisLoggerFactory, + ref IBuildCopLoggerFactory buildCopLoggerFactory, ref string[] getProperty, ref string[] getItem, ref string[] getTargetResult, @@ -2563,7 +2563,7 @@ private static bool ProcessCommandLineSwitches( #endif ref lowPriority, ref question, - ref buildAnalysisLoggerFactory, + ref buildCopLoggerFactory, ref getProperty, ref getItem, ref getTargetResult, @@ -2645,7 +2645,7 @@ private static bool ProcessCommandLineSwitches( question = commandLineSwitches.IsParameterizedSwitchSet(CommandLineSwitches.ParameterizedSwitch.Question); - buildAnalysisLoggerFactory = ProcessBuildAnalysisLoggerFactorySwitch(commandLineSwitches); + buildCopLoggerFactory = ProcessBuildAnalysisLoggerFactorySwitch(commandLineSwitches); inputResultsCaches = ProcessInputResultsCaches(commandLineSwitches); @@ -2721,11 +2721,11 @@ private static bool ProcessCommandLineSwitches( return invokeBuild; } - private static IBuildAnalysisLoggerFactory ProcessBuildAnalysisLoggerFactorySwitch(CommandLineSwitches commandLineSwitches) + private static IBuildCopLoggerFactory ProcessBuildAnalysisLoggerFactorySwitch(CommandLineSwitches commandLineSwitches) { // todo: opt-in behavior: https://github.com/dotnet/msbuild/issues/9723 bool isAnalysisEnabled = commandLineSwitches.IsParameterizedSwitchSet(CommandLineSwitches.ParameterizedSwitch.Analyze); - return isAnalysisEnabled ? new BuildAnalysisLoggerFactory() : null; + return isAnalysisEnabled ? new BuildCopLoggerFactory() : null; } private static bool ProcessTerminalLoggerConfiguration(CommandLineSwitches commandLineSwitches, out string aggregatedParameters) From 72749c4bea44a32a597ab781f2fa9341c6877f48 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Fri, 16 Feb 2024 14:16:00 +0100 Subject: [PATCH 12/58] Renaming for clarity (#9754) From 93141216d60beaf2517ed718eb0a71ec6e5b3cf5 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Tue, 20 Feb 2024 14:59:31 +0100 Subject: [PATCH 13/58] Removing unnecessary types --- src/Analyzers/API/BuildAnalyzerConfiguration.cs | 4 ---- src/Analyzers/API/InvocationConcurrency.cs | 10 ---------- src/Analyzers/API/PerformanceWeightClass.cs | 11 ----------- .../BuildAnalyzerConfigurationInternal.cs | 2 -- .../Infrastructure/ConfigurationProvider.cs | 2 -- src/Analyzers/OM/ParsedItemsContext.cs | 13 ------------- 6 files changed, 42 deletions(-) delete mode 100644 src/Analyzers/API/InvocationConcurrency.cs delete mode 100644 src/Analyzers/API/PerformanceWeightClass.cs diff --git a/src/Analyzers/API/BuildAnalyzerConfiguration.cs b/src/Analyzers/API/BuildAnalyzerConfiguration.cs index 7a5b75a506c..77e2feca512 100644 --- a/src/Analyzers/API/BuildAnalyzerConfiguration.cs +++ b/src/Analyzers/API/BuildAnalyzerConfiguration.cs @@ -8,8 +8,6 @@ public class BuildAnalyzerConfiguration public static BuildAnalyzerConfiguration Default { get; } = new() { LifeTimeScope = Experimental.LifeTimeScope.PerProject, - SupportedInvocationConcurrency = InvocationConcurrency.Parallel, - PerformanceWeightClass = Experimental.PerformanceWeightClass.Normal, EvaluationAnalysisScope = Experimental.EvaluationAnalysisScope.AnalyzedProjectOnly, Severity = BuildAnalyzerResultSeverity.Info, IsEnabled = false, @@ -18,8 +16,6 @@ public class BuildAnalyzerConfiguration public static BuildAnalyzerConfiguration Null { get; } = new(); public LifeTimeScope? LifeTimeScope { get; internal init; } - public InvocationConcurrency? SupportedInvocationConcurrency { get; internal init; } - public PerformanceWeightClass? PerformanceWeightClass { get; internal init; } public EvaluationAnalysisScope? EvaluationAnalysisScope { get; internal init; } public BuildAnalyzerResultSeverity? Severity { get; internal init; } public bool? IsEnabled { get; internal init; } diff --git a/src/Analyzers/API/InvocationConcurrency.cs b/src/Analyzers/API/InvocationConcurrency.cs deleted file mode 100644 index f4e8bc61ad6..00000000000 --- a/src/Analyzers/API/InvocationConcurrency.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Build.Experimental; - -public enum InvocationConcurrency -{ - Sequential, - Parallel, -} diff --git a/src/Analyzers/API/PerformanceWeightClass.cs b/src/Analyzers/API/PerformanceWeightClass.cs deleted file mode 100644 index ea2d5aa2469..00000000000 --- a/src/Analyzers/API/PerformanceWeightClass.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Build.Experimental; - -public enum PerformanceWeightClass -{ - Lightweight, - Normal, - Heavyweight, -} diff --git a/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs b/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs index fdffcb98a27..0195b2273e0 100644 --- a/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs +++ b/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs @@ -16,8 +16,6 @@ namespace Microsoft.Build.Analyzers.Infrastructure; internal sealed class BuildAnalyzerConfigurationInternal { public LifeTimeScope LifeTimeScope { get; internal init; } - public InvocationConcurrency SupportedInvocationConcurrency { get; internal init; } - public PerformanceWeightClass PerformanceWeightClass { get; internal init; } public EvaluationAnalysisScope EvaluationAnalysisScope { get; internal init; } public BuildAnalyzerResultSeverity Severity { get; internal init; } public bool IsEnabled { get; internal init; } diff --git a/src/Analyzers/Infrastructure/ConfigurationProvider.cs b/src/Analyzers/Infrastructure/ConfigurationProvider.cs index 11abc7a75a1..0ba5c1d2381 100644 --- a/src/Analyzers/Infrastructure/ConfigurationProvider.cs +++ b/src/Analyzers/Infrastructure/ConfigurationProvider.cs @@ -85,11 +85,9 @@ public static BuildAnalyzerConfigurationInternal GetMergedConfiguration(BuildAna return new BuildAnalyzerConfigurationInternal() { - SupportedInvocationConcurrency = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.SupportedInvocationConcurrency), EvaluationAnalysisScope = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.EvaluationAnalysisScope), IsEnabled = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.IsEnabled), LifeTimeScope = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.LifeTimeScope), - PerformanceWeightClass = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.PerformanceWeightClass), Severity = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.Severity) }; diff --git a/src/Analyzers/OM/ParsedItemsContext.cs b/src/Analyzers/OM/ParsedItemsContext.cs index 5a29a628627..f9a4038599d 100644 --- a/src/Analyzers/OM/ParsedItemsContext.cs +++ b/src/Analyzers/OM/ParsedItemsContext.cs @@ -10,19 +10,9 @@ using Microsoft.Build.Construction; namespace Microsoft.Build.Experimental; -public enum ItemType -{ - ProjectReference, - PackageReference, - Compile, - EmbeddedResource -} public static class ItemTypeExtensions { - public static IEnumerable GetItemsOfType(this IEnumerable items, ItemType itemType) - => GetItemsOfType(items, itemType.ToString()); - public static IEnumerable GetItemsOfType(this IEnumerable items, string itemType) { @@ -36,9 +26,6 @@ public class ItemsHolder(IEnumerable items, IEnumerable Items { get; } = items; public IEnumerable ItemGroups { get; } = itemGroups; - public IEnumerable GetItemsOfType(ItemType itemType) - => Items.GetItemsOfType(itemType); - public IEnumerable GetItemsOfType(string itemType) { return Items.GetItemsOfType(itemType); From 886c483721408fd2e27ba45d5b98db72d7f3d62c Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Tue, 20 Feb 2024 15:48:04 +0100 Subject: [PATCH 14/58] Code move --- MSBuild.sln | 66 ------------------- ...Microsoft.Build.Analyzers.UnitTests.csproj | 35 ++++------ .../Microsoft.Build.Analyzers.csproj | 21 ------ src/Analyzers/README.md | 3 - src/Build/AssemblyInfo.cs | 1 - .../BuildCop}/API/BuildAnalysisContext.cs | 0 .../BuildCop}/API/BuildAnalyzer.cs | 0 .../API/BuildAnalyzerConfiguration.cs | 0 .../BuildCop}/API/BuildAnalyzerResult.cs | 0 .../API/BuildAnalyzerResultSeverity.cs | 0 .../BuildCop}/API/BuildAnalyzerRule.cs | 0 .../BuildCop}/API/BuildCopLoggerFactory.cs | 0 .../BuildCop}/API/ConfigurationContext.cs | 0 .../BuildCop}/API/EvaluationAnalysisScope.cs | 0 .../BuildCop}/API/LifeTimeScope.cs | 0 .../Analyzers/SharedOutputPathAnalyzer.cs | 0 .../Infrastructure/AnalyzersDelegates.cs | 0 .../BuildAnalyzerConfigurationInternal.cs | 0 .../BuildAnalyzerTracingWrapper.cs | 0 .../Infrastructure/BuildCopCentralContext.cs | 0 .../Infrastructure/BuildCopConnectorLogger.cs | 0 .../Infrastructure/BuildCopContext.cs | 0 .../Infrastructure/BuildCopManager.cs | 0 .../Infrastructure/ConfigurationProvider.cs | 0 .../Infrastructure/IBuildCopContext.cs | 0 .../Logging}/AnalyzerLoggingContext.cs | 0 .../Logging}/AnalyzerLoggingContextFactory.cs | 0 .../BuildAnalysisLoggingContextExtensions.cs | 0 .../OM/EvaluatedPropertiesContext.cs | 0 .../BuildCop}/OM/ParsedItemsContext.cs | 0 .../BuildCop}/Utilities/IsExternalInit.cs | 0 src/Build/Microsoft.Build.csproj | 29 +++++++- src/Framework/Properties/AssemblyInfo.cs | 1 - .../MSBuild.Bootstrap.csproj | 1 - src/MSBuild/MSBuild.csproj | 1 - src/UnitTests.Shared/IsExternalInit.cs | 7 ++ 36 files changed, 46 insertions(+), 119 deletions(-) delete mode 100644 src/Analyzers/Microsoft.Build.Analyzers.csproj delete mode 100644 src/Analyzers/README.md rename src/{Analyzers => Build/BuildCop}/API/BuildAnalysisContext.cs (100%) rename src/{Analyzers => Build/BuildCop}/API/BuildAnalyzer.cs (100%) rename src/{Analyzers => Build/BuildCop}/API/BuildAnalyzerConfiguration.cs (100%) rename src/{Analyzers => Build/BuildCop}/API/BuildAnalyzerResult.cs (100%) rename src/{Analyzers => Build/BuildCop}/API/BuildAnalyzerResultSeverity.cs (100%) rename src/{Analyzers => Build/BuildCop}/API/BuildAnalyzerRule.cs (100%) rename src/{Analyzers => Build/BuildCop}/API/BuildCopLoggerFactory.cs (100%) rename src/{Analyzers => Build/BuildCop}/API/ConfigurationContext.cs (100%) rename src/{Analyzers => Build/BuildCop}/API/EvaluationAnalysisScope.cs (100%) rename src/{Analyzers => Build/BuildCop}/API/LifeTimeScope.cs (100%) rename src/{Analyzers => Build/BuildCop}/Analyzers/SharedOutputPathAnalyzer.cs (100%) rename src/{Analyzers => Build/BuildCop}/Infrastructure/AnalyzersDelegates.cs (100%) rename src/{Analyzers => Build/BuildCop}/Infrastructure/BuildAnalyzerConfigurationInternal.cs (100%) rename src/{Analyzers => Build/BuildCop}/Infrastructure/BuildAnalyzerTracingWrapper.cs (100%) rename src/{Analyzers => Build/BuildCop}/Infrastructure/BuildCopCentralContext.cs (100%) rename src/{Analyzers => Build/BuildCop}/Infrastructure/BuildCopConnectorLogger.cs (100%) rename src/{Analyzers => Build/BuildCop}/Infrastructure/BuildCopContext.cs (100%) rename src/{Analyzers => Build/BuildCop}/Infrastructure/BuildCopManager.cs (100%) rename src/{Analyzers => Build/BuildCop}/Infrastructure/ConfigurationProvider.cs (100%) rename src/{Analyzers => Build/BuildCop}/Infrastructure/IBuildCopContext.cs (100%) rename src/Build/{Logging/Analyzers => BuildCop/Logging}/AnalyzerLoggingContext.cs (100%) rename src/Build/{Logging/Analyzers => BuildCop/Logging}/AnalyzerLoggingContextFactory.cs (100%) rename src/Build/{Logging/Analyzers => BuildCop/Logging}/BuildAnalysisLoggingContextExtensions.cs (100%) rename src/{Analyzers => Build/BuildCop}/OM/EvaluatedPropertiesContext.cs (100%) rename src/{Analyzers => Build/BuildCop}/OM/ParsedItemsContext.cs (100%) rename src/{Analyzers => Build/BuildCop}/Utilities/IsExternalInit.cs (100%) create mode 100644 src/UnitTests.Shared/IsExternalInit.cs diff --git a/MSBuild.sln b/MSBuild.sln index 7c89de6dea6..2254de84835 100644 --- a/MSBuild.sln +++ b/MSBuild.sln @@ -80,8 +80,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSBuild.VSSetup.Arm64", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.UnitTests.Shared", "src\UnitTests.Shared\Microsoft.Build.UnitTests.Shared.csproj", "{52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Analyzers", "src\Analyzers\Microsoft.Build.Analyzers.csproj", "{512E01F7-2899-433B-93E2-D63B43AF0420}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Analyzers.UnitTests", "src\Analyzers.UnitTests\Microsoft.Build.Analyzers.UnitTests.csproj", "{B18BAE17-D78F-4F89-B7A4-808C05E64D73}" EndProject Global @@ -868,54 +866,6 @@ Global {52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}.Release|x64.Build.0 = Release|x64 {52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}.Release|x86.ActiveCfg = Release|Any CPU {52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}.Release|x86.Build.0 = Release|Any CPU - {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|Any CPU.ActiveCfg = Release-MONO|Any CPU - {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|Any CPU.Build.0 = Release-MONO|Any CPU - {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|ARM64.ActiveCfg = Release-MONO|arm64 - {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|ARM64.Build.0 = Release-MONO|arm64 - {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|x64.ActiveCfg = Release-MONO|x64 - {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|x64.Build.0 = Release-MONO|x64 - {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|x86.ActiveCfg = Release-MONO|Any CPU - {71E59632-D644-491B-AF93-22BC93167C56}.Release-MONO|x86.Build.0 = Release-MONO|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|Any CPU.Build.0 = Debug|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|ARM64.ActiveCfg = Debug|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|ARM64.Build.0 = Debug|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|x64.ActiveCfg = Debug|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|x64.Build.0 = Debug|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|x86.ActiveCfg = Debug|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug|x86.Build.0 = Debug|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|Any CPU.ActiveCfg = Debug-MONO|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|Any CPU.Build.0 = Debug-MONO|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|ARM64.ActiveCfg = Debug-MONO|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|ARM64.Build.0 = Debug-MONO|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|x64.ActiveCfg = Debug-MONO|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|x64.Build.0 = Debug-MONO|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|x86.ActiveCfg = Debug-MONO|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Debug-MONO|x86.Build.0 = Debug-MONO|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|Any CPU.ActiveCfg = MachineIndependent|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|Any CPU.Build.0 = MachineIndependent|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|ARM64.ActiveCfg = MachineIndependent|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|ARM64.Build.0 = MachineIndependent|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|x64.ActiveCfg = MachineIndependent|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|x64.Build.0 = MachineIndependent|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|x86.ActiveCfg = MachineIndependent|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.MachineIndependent|x86.Build.0 = MachineIndependent|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|Any CPU.ActiveCfg = Release|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|Any CPU.Build.0 = Release|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|ARM64.ActiveCfg = Release|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|ARM64.Build.0 = Release|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|x64.ActiveCfg = Release|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|x64.Build.0 = Release|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|x86.ActiveCfg = Release|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release|x86.Build.0 = Release|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|Any CPU.ActiveCfg = Release-MONO|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|Any CPU.Build.0 = Release-MONO|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|ARM64.ActiveCfg = Release-MONO|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|ARM64.Build.0 = Release-MONO|arm64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x64.ActiveCfg = Release-MONO|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x64.Build.0 = Release-MONO|x64 - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x86.ActiveCfg = Release-MONO|Any CPU - {512E01F7-2899-433B-93E2-D63B43AF0420}.Release-MONO|x86.Build.0 = Release-MONO|Any CPU {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|Any CPU.Build.0 = Debug|Any CPU {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|ARM64.ActiveCfg = Debug|arm64 @@ -924,14 +874,6 @@ Global {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x64.Build.0 = Debug|x64 {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x86.ActiveCfg = Debug|Any CPU {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x86.Build.0 = Debug|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|Any CPU.ActiveCfg = Debug-MONO|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|Any CPU.Build.0 = Debug-MONO|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|ARM64.ActiveCfg = Debug-MONO|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|ARM64.Build.0 = Debug-MONO|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|x64.ActiveCfg = Debug-MONO|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|x64.Build.0 = Debug-MONO|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|x86.ActiveCfg = Debug-MONO|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug-MONO|x86.Build.0 = Debug-MONO|Any CPU {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|Any CPU.ActiveCfg = MachineIndependent|Any CPU {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|Any CPU.Build.0 = MachineIndependent|Any CPU {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|ARM64.ActiveCfg = MachineIndependent|arm64 @@ -948,14 +890,6 @@ Global {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x64.Build.0 = Release|x64 {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x86.ActiveCfg = Release|Any CPU {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x86.Build.0 = Release|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|Any CPU.ActiveCfg = Release-MONO|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|Any CPU.Build.0 = Release-MONO|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|ARM64.ActiveCfg = Release-MONO|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|ARM64.Build.0 = Release-MONO|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|x64.ActiveCfg = Release-MONO|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|x64.Build.0 = Release-MONO|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|x86.ActiveCfg = Release-MONO|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release-MONO|x86.Build.0 = Release-MONO|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj b/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj index c7fc6ed59fc..876d03d2e07 100644 --- a/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj +++ b/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj @@ -16,9 +16,10 @@ - + + - + false false @@ -38,46 +39,36 @@ - - Shared\TestEnvironment.cs - - - + Shared\FileUtilities.cs - + Shared\TempFileUtilities.cs - + Shared\ErrorUtilities.cs - + Shared\EscapingUtilities.cs - + Shared\BuildEnvironmentHelper.cs - + Shared\ProcessExtensions.cs - + Shared\ResourceUtilities.cs - + Shared\ExceptionHandling.cs - + Shared\FileUtilitiesRegex.cs - + Shared\AssemblyResources.cs - - Shared\RunnerUtilities.cs - - - Shared\EnvironmentProvider.cs - diff --git a/src/Analyzers/Microsoft.Build.Analyzers.csproj b/src/Analyzers/Microsoft.Build.Analyzers.csproj deleted file mode 100644 index 96a3bf34bb5..00000000000 --- a/src/Analyzers/Microsoft.Build.Analyzers.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - $(FullFrameworkTFM);$(LatestDotNetCoreForMSBuild) - $(RuntimeOutputTargetFrameworks) - Microsoft.Build.Analyzers - Microsoft.Build.Analyzers - This package contains the $(AssemblyName) assembly which contains build analyzers logic and API. - true - AnyCPU - true - - - - - - - - - - - diff --git a/src/Analyzers/README.md b/src/Analyzers/README.md deleted file mode 100644 index e09acbf1bcf..00000000000 --- a/src/Analyzers/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Microsoft.Build.Analyzers - -TBD \ No newline at end of file diff --git a/src/Build/AssemblyInfo.cs b/src/Build/AssemblyInfo.cs index fc70072ee54..6e57337863d 100644 --- a/src/Build/AssemblyInfo.cs +++ b/src/Build/AssemblyInfo.cs @@ -21,7 +21,6 @@ [assembly: InternalsVisibleTo("Microsoft.Build.Engine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.UnitTests.Shared, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] -[assembly: InternalsVisibleTo("Microsoft.Build.Analyzers, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Unittest, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.Tasks.Cop, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] // DO NOT expose Internals to "Microsoft.Build.UnitTests.OM.OrcasCompatibility" as this assembly is supposed to only see public interface diff --git a/src/Analyzers/API/BuildAnalysisContext.cs b/src/Build/BuildCop/API/BuildAnalysisContext.cs similarity index 100% rename from src/Analyzers/API/BuildAnalysisContext.cs rename to src/Build/BuildCop/API/BuildAnalysisContext.cs diff --git a/src/Analyzers/API/BuildAnalyzer.cs b/src/Build/BuildCop/API/BuildAnalyzer.cs similarity index 100% rename from src/Analyzers/API/BuildAnalyzer.cs rename to src/Build/BuildCop/API/BuildAnalyzer.cs diff --git a/src/Analyzers/API/BuildAnalyzerConfiguration.cs b/src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs similarity index 100% rename from src/Analyzers/API/BuildAnalyzerConfiguration.cs rename to src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs diff --git a/src/Analyzers/API/BuildAnalyzerResult.cs b/src/Build/BuildCop/API/BuildAnalyzerResult.cs similarity index 100% rename from src/Analyzers/API/BuildAnalyzerResult.cs rename to src/Build/BuildCop/API/BuildAnalyzerResult.cs diff --git a/src/Analyzers/API/BuildAnalyzerResultSeverity.cs b/src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs similarity index 100% rename from src/Analyzers/API/BuildAnalyzerResultSeverity.cs rename to src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs diff --git a/src/Analyzers/API/BuildAnalyzerRule.cs b/src/Build/BuildCop/API/BuildAnalyzerRule.cs similarity index 100% rename from src/Analyzers/API/BuildAnalyzerRule.cs rename to src/Build/BuildCop/API/BuildAnalyzerRule.cs diff --git a/src/Analyzers/API/BuildCopLoggerFactory.cs b/src/Build/BuildCop/API/BuildCopLoggerFactory.cs similarity index 100% rename from src/Analyzers/API/BuildCopLoggerFactory.cs rename to src/Build/BuildCop/API/BuildCopLoggerFactory.cs diff --git a/src/Analyzers/API/ConfigurationContext.cs b/src/Build/BuildCop/API/ConfigurationContext.cs similarity index 100% rename from src/Analyzers/API/ConfigurationContext.cs rename to src/Build/BuildCop/API/ConfigurationContext.cs diff --git a/src/Analyzers/API/EvaluationAnalysisScope.cs b/src/Build/BuildCop/API/EvaluationAnalysisScope.cs similarity index 100% rename from src/Analyzers/API/EvaluationAnalysisScope.cs rename to src/Build/BuildCop/API/EvaluationAnalysisScope.cs diff --git a/src/Analyzers/API/LifeTimeScope.cs b/src/Build/BuildCop/API/LifeTimeScope.cs similarity index 100% rename from src/Analyzers/API/LifeTimeScope.cs rename to src/Build/BuildCop/API/LifeTimeScope.cs diff --git a/src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs b/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs similarity index 100% rename from src/Analyzers/Analyzers/SharedOutputPathAnalyzer.cs rename to src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs diff --git a/src/Analyzers/Infrastructure/AnalyzersDelegates.cs b/src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs similarity index 100% rename from src/Analyzers/Infrastructure/AnalyzersDelegates.cs rename to src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs diff --git a/src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs b/src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs similarity index 100% rename from src/Analyzers/Infrastructure/BuildAnalyzerConfigurationInternal.cs rename to src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs diff --git a/src/Analyzers/Infrastructure/BuildAnalyzerTracingWrapper.cs b/src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs similarity index 100% rename from src/Analyzers/Infrastructure/BuildAnalyzerTracingWrapper.cs rename to src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs diff --git a/src/Analyzers/Infrastructure/BuildCopCentralContext.cs b/src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs similarity index 100% rename from src/Analyzers/Infrastructure/BuildCopCentralContext.cs rename to src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs diff --git a/src/Analyzers/Infrastructure/BuildCopConnectorLogger.cs b/src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs similarity index 100% rename from src/Analyzers/Infrastructure/BuildCopConnectorLogger.cs rename to src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs diff --git a/src/Analyzers/Infrastructure/BuildCopContext.cs b/src/Build/BuildCop/Infrastructure/BuildCopContext.cs similarity index 100% rename from src/Analyzers/Infrastructure/BuildCopContext.cs rename to src/Build/BuildCop/Infrastructure/BuildCopContext.cs diff --git a/src/Analyzers/Infrastructure/BuildCopManager.cs b/src/Build/BuildCop/Infrastructure/BuildCopManager.cs similarity index 100% rename from src/Analyzers/Infrastructure/BuildCopManager.cs rename to src/Build/BuildCop/Infrastructure/BuildCopManager.cs diff --git a/src/Analyzers/Infrastructure/ConfigurationProvider.cs b/src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs similarity index 100% rename from src/Analyzers/Infrastructure/ConfigurationProvider.cs rename to src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs diff --git a/src/Analyzers/Infrastructure/IBuildCopContext.cs b/src/Build/BuildCop/Infrastructure/IBuildCopContext.cs similarity index 100% rename from src/Analyzers/Infrastructure/IBuildCopContext.cs rename to src/Build/BuildCop/Infrastructure/IBuildCopContext.cs diff --git a/src/Build/Logging/Analyzers/AnalyzerLoggingContext.cs b/src/Build/BuildCop/Logging/AnalyzerLoggingContext.cs similarity index 100% rename from src/Build/Logging/Analyzers/AnalyzerLoggingContext.cs rename to src/Build/BuildCop/Logging/AnalyzerLoggingContext.cs diff --git a/src/Build/Logging/Analyzers/AnalyzerLoggingContextFactory.cs b/src/Build/BuildCop/Logging/AnalyzerLoggingContextFactory.cs similarity index 100% rename from src/Build/Logging/Analyzers/AnalyzerLoggingContextFactory.cs rename to src/Build/BuildCop/Logging/AnalyzerLoggingContextFactory.cs diff --git a/src/Build/Logging/Analyzers/BuildAnalysisLoggingContextExtensions.cs b/src/Build/BuildCop/Logging/BuildAnalysisLoggingContextExtensions.cs similarity index 100% rename from src/Build/Logging/Analyzers/BuildAnalysisLoggingContextExtensions.cs rename to src/Build/BuildCop/Logging/BuildAnalysisLoggingContextExtensions.cs diff --git a/src/Analyzers/OM/EvaluatedPropertiesContext.cs b/src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs similarity index 100% rename from src/Analyzers/OM/EvaluatedPropertiesContext.cs rename to src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs diff --git a/src/Analyzers/OM/ParsedItemsContext.cs b/src/Build/BuildCop/OM/ParsedItemsContext.cs similarity index 100% rename from src/Analyzers/OM/ParsedItemsContext.cs rename to src/Build/BuildCop/OM/ParsedItemsContext.cs diff --git a/src/Analyzers/Utilities/IsExternalInit.cs b/src/Build/BuildCop/Utilities/IsExternalInit.cs similarity index 100% rename from src/Analyzers/Utilities/IsExternalInit.cs rename to src/Build/BuildCop/Utilities/IsExternalInit.cs diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index cdb72622441..5c9fec8bc7b 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -153,6 +153,29 @@ + + + + + + + + + + + + + + + + + + + + + + + @@ -160,9 +183,9 @@ - - - + + + diff --git a/src/Framework/Properties/AssemblyInfo.cs b/src/Framework/Properties/AssemblyInfo.cs index 6c40f679898..790d9898146 100644 --- a/src/Framework/Properties/AssemblyInfo.cs +++ b/src/Framework/Properties/AssemblyInfo.cs @@ -39,7 +39,6 @@ [assembly: InternalsVisibleTo("Microsoft.Build.Framework.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.Tasks.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] -[assembly: InternalsVisibleTo("Microsoft.Build.Analyzers, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Utilities.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Tasks.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("MSBuild, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj b/src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj index c513a46876e..655ac537189 100644 --- a/src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj +++ b/src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj @@ -16,7 +16,6 @@ - diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index eb8736e53fb..0415842c6b7 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -196,7 +196,6 @@ - diff --git a/src/UnitTests.Shared/IsExternalInit.cs b/src/UnitTests.Shared/IsExternalInit.cs new file mode 100644 index 00000000000..92d5c4c320a --- /dev/null +++ b/src/UnitTests.Shared/IsExternalInit.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit { } +} From 60e01a7c955e134bdffc958e2a0c97eaeeb00d24 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Tue, 20 Feb 2024 16:00:45 +0100 Subject: [PATCH 15/58] Adjust namespaces --- src/Build/BackEnd/BuildManager/BuildManager.cs | 3 ++- src/Build/BackEnd/BuildManager/BuildParameters.cs | 1 + src/Build/BuildCop/API/BuildAnalysisContext.cs | 4 ++-- src/Build/BuildCop/API/BuildAnalyzer.cs | 3 ++- .../BuildCop/API/BuildAnalyzerConfiguration.cs | 6 +++--- src/Build/BuildCop/API/BuildAnalyzerResult.cs | 2 +- .../BuildCop/API/BuildAnalyzerResultSeverity.cs | 2 +- src/Build/BuildCop/API/BuildAnalyzerRule.cs | 2 +- src/Build/BuildCop/API/BuildCopLoggerFactory.cs | 4 ++-- src/Build/BuildCop/API/ConfigurationContext.cs | 2 +- src/Build/BuildCop/API/EvaluationAnalysisScope.cs | 2 +- src/Build/BuildCop/API/LifeTimeScope.cs | 2 +- .../Analyzers/SharedOutputPathAnalyzer.cs | 6 +++--- .../BuildCop/Infrastructure/AnalyzersDelegates.cs | 2 +- .../BuildAnalyzerConfigurationInternal.cs | 9 ++------- .../Infrastructure/BuildAnalyzerTracingWrapper.cs | 3 ++- .../Infrastructure/BuildCopCentralContext.cs | 4 +++- .../Infrastructure/BuildCopConnectorLogger.cs | 15 +++++---------- .../BuildCop/Infrastructure/BuildCopContext.cs | 5 ++--- .../BuildCop/Infrastructure/BuildCopManager.cs | 12 ++++-------- .../Infrastructure/ConfigurationProvider.cs | 5 ++--- .../BuildCop/Infrastructure/IBuildCopContext.cs | 4 +++- .../BuildCop/Logging/AnalyzerLoggingContext.cs | 9 ++------- .../Logging/AnalyzerLoggingContextFactory.cs | 8 ++------ .../BuildAnalysisLoggingContextExtensions.cs | 4 ++-- .../BuildCop/OM/EvaluatedPropertiesContext.cs | 2 +- src/Build/BuildCop/OM/ParsedItemsContext.cs | 2 +- .../IBuildAnalysisLoggingContext.cs | 2 +- .../IBuildAnalysisLoggingContextFactory.cs | 2 +- .../IBuildCopLoggerFactory.cs | 2 +- .../{Analyzers => BuildCop}/IBuildCopManager.cs | 2 +- src/MSBuild/XMake.cs | 1 + 32 files changed, 58 insertions(+), 74 deletions(-) rename src/Framework/{Analyzers => BuildCop}/IBuildAnalysisLoggingContext.cs (79%) rename src/Framework/{Analyzers => BuildCop}/IBuildAnalysisLoggingContextFactory.cs (86%) rename src/Framework/{Analyzers => BuildCop}/IBuildCopLoggerFactory.cs (86%) rename src/Framework/{Analyzers => BuildCop}/IBuildCopManager.cs (91%) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index a8e59cf6f38..63a9b0c7ca8 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -21,10 +21,12 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BackEnd.SdkResolution; +using Microsoft.Build.BuildCop.Logging; using Microsoft.Build.Evaluation; using Microsoft.Build.Eventing; using Microsoft.Build.Exceptions; using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Experimental.ProjectCache; using Microsoft.Build.FileAccesses; using Microsoft.Build.Framework; @@ -32,7 +34,6 @@ using Microsoft.Build.Graph; using Microsoft.Build.Internal; using Microsoft.Build.Logging; -using Microsoft.Build.Logging.Analyzers; using Microsoft.Build.Shared; using Microsoft.Build.Shared.Debugging; using Microsoft.NET.StringTools; diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index 525a335dc37..a08d68a1d45 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -11,6 +11,7 @@ using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Experimental.ProjectCache; using Microsoft.Build.Framework; using Microsoft.Build.Graph; diff --git a/src/Build/BuildCop/API/BuildAnalysisContext.cs b/src/Build/BuildCop/API/BuildAnalysisContext.cs index 8dbb5ae5041..4a9f578eca4 100644 --- a/src/Build/BuildCop/API/BuildAnalysisContext.cs +++ b/src/Build/BuildCop/API/BuildAnalysisContext.cs @@ -6,12 +6,12 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.Build.Analyzers.Infrastructure; using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.Experimental; using Microsoft.Build.Framework; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public class BuildAnalysisContext { diff --git a/src/Build/BuildCop/API/BuildAnalyzer.cs b/src/Build/BuildCop/API/BuildAnalyzer.cs index f05c452521d..f7ff0b948c2 100644 --- a/src/Build/BuildCop/API/BuildAnalyzer.cs +++ b/src/Build/BuildCop/API/BuildAnalyzer.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; +using Microsoft.Build.BuildCop.Infrastructure; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public abstract class BuildAnalyzer { diff --git a/src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs b/src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs index 77e2feca512..f7a45e08031 100644 --- a/src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs +++ b/src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs @@ -1,14 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public class BuildAnalyzerConfiguration { public static BuildAnalyzerConfiguration Default { get; } = new() { - LifeTimeScope = Experimental.LifeTimeScope.PerProject, - EvaluationAnalysisScope = Experimental.EvaluationAnalysisScope.AnalyzedProjectOnly, + LifeTimeScope = BuildCop.LifeTimeScope.PerProject, + EvaluationAnalysisScope = BuildCop.EvaluationAnalysisScope.AnalyzedProjectOnly, Severity = BuildAnalyzerResultSeverity.Info, IsEnabled = false, }; diff --git a/src/Build/BuildCop/API/BuildAnalyzerResult.cs b/src/Build/BuildCop/API/BuildAnalyzerResult.cs index 88291ce2c67..7c348f7fa2b 100644 --- a/src/Build/BuildCop/API/BuildAnalyzerResult.cs +++ b/src/Build/BuildCop/API/BuildAnalyzerResult.cs @@ -6,7 +6,7 @@ using Microsoft.Build.Construction; using Microsoft.Build.Framework; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public class BuildAnalyzerResult { diff --git a/src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs b/src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs index 3e067db3cf8..345c0bfbcd6 100644 --- a/src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs +++ b/src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public enum BuildAnalyzerResultSeverity { diff --git a/src/Build/BuildCop/API/BuildAnalyzerRule.cs b/src/Build/BuildCop/API/BuildAnalyzerRule.cs index 5c9f739497c..991bee27edb 100644 --- a/src/Build/BuildCop/API/BuildAnalyzerRule.cs +++ b/src/Build/BuildCop/API/BuildAnalyzerRule.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public class BuildAnalyzerRule { diff --git a/src/Build/BuildCop/API/BuildCopLoggerFactory.cs b/src/Build/BuildCop/API/BuildCopLoggerFactory.cs index accd1db9c2e..d0d9c6a2e08 100644 --- a/src/Build/BuildCop/API/BuildCopLoggerFactory.cs +++ b/src/Build/BuildCop/API/BuildCopLoggerFactory.cs @@ -6,10 +6,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.Build.Analyzers.Infrastructure; +using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.Framework; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public class BuildCopLoggerFactory : IBuildCopLoggerFactory { diff --git a/src/Build/BuildCop/API/ConfigurationContext.cs b/src/Build/BuildCop/API/ConfigurationContext.cs index 69e2ec43e28..0aba0f87344 100644 --- a/src/Build/BuildCop/API/ConfigurationContext.cs +++ b/src/Build/BuildCop/API/ConfigurationContext.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; /// /// Holder of an optional configuration from .editorconfig file (not recognized by infrastructure) diff --git a/src/Build/BuildCop/API/EvaluationAnalysisScope.cs b/src/Build/BuildCop/API/EvaluationAnalysisScope.cs index 3fafe62e4d6..843826723dc 100644 --- a/src/Build/BuildCop/API/EvaluationAnalysisScope.cs +++ b/src/Build/BuildCop/API/EvaluationAnalysisScope.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public enum EvaluationAnalysisScope { diff --git a/src/Build/BuildCop/API/LifeTimeScope.cs b/src/Build/BuildCop/API/LifeTimeScope.cs index 34f85355050..485da9c1781 100644 --- a/src/Build/BuildCop/API/LifeTimeScope.cs +++ b/src/Build/BuildCop/API/LifeTimeScope.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public enum LifeTimeScope { diff --git a/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs b/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs index 2cddcf3da27..298643a7ced 100644 --- a/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs +++ b/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.IO; +using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.Construction; -using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; -namespace Microsoft.Build.Analyzers.Analyzers; +namespace Microsoft.Build.BuildCop.Analyzers; // Some background on ids: // * https://github.com/dotnet/roslyn-analyzers/blob/main/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt diff --git a/src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs b/src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs index 1eecd0c5c9d..46bac4c5f64 100644 --- a/src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs +++ b/src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public delegate void EvaluatedPropertiesAction(EvaluatedPropertiesContext context); diff --git a/src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs b/src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs index 0195b2273e0..7d8f6d9ebcf 100644 --- a/src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs +++ b/src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs @@ -1,14 +1,9 @@ // 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; -using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; -namespace Microsoft.Build.Analyzers.Infrastructure; +namespace Microsoft.Build.BuildCop.Infrastructure; /// /// Counterpart type for BuildAnalyzerConfiguration - with all properties non-nullable diff --git a/src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs b/src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs index 21895de6818..da2e5181af9 100644 --- a/src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs +++ b/src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs @@ -3,8 +3,9 @@ using System; using System.Diagnostics; +using Microsoft.Build.Experimental.BuildCop; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.BuildCop.Infrastructure; internal sealed class BuildAnalyzerTracingWrapper { diff --git a/src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs b/src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs index f3f2476abcd..6e970ab6557 100644 --- a/src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs +++ b/src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; + +namespace Microsoft.Build.BuildCop.Infrastructure; internal sealed class BuildCopCentralContext { diff --git a/src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs b/src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs index 2999bd2218c..174bb24373f 100644 --- a/src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs +++ b/src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs @@ -2,20 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Diagnostics; -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Experimental; +using Microsoft.Build.BuildCop.Logging; +using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Framework; -using Microsoft.Build.Logging.Analyzers; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Analyzers.Infrastructure; +namespace Microsoft.Build.BuildCop.Infrastructure; internal sealed class BuildCopConnectorLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory, IBuildCopManager buildCopManager) : ILogger { diff --git a/src/Build/BuildCop/Infrastructure/BuildCopContext.cs b/src/Build/BuildCop/Infrastructure/BuildCopContext.cs index c97fc9af7f7..240468b1700 100644 --- a/src/Build/BuildCop/Infrastructure/BuildCopContext.cs +++ b/src/Build/BuildCop/Infrastructure/BuildCopContext.cs @@ -1,10 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Concurrent; -using System.Collections.Generic; +using Microsoft.Build.Experimental.BuildCop; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.BuildCop.Infrastructure; internal sealed class BuildCopContext(BuildAnalyzerTracingWrapper analyzer, BuildCopCentralContext buildCopCentralContext) : IBuildCopContext { diff --git a/src/Build/BuildCop/Infrastructure/BuildCopManager.cs b/src/Build/BuildCop/Infrastructure/BuildCopManager.cs index 2153125e9a7..984bb2dfb71 100644 --- a/src/Build/BuildCop/Infrastructure/BuildCopManager.cs +++ b/src/Build/BuildCop/Infrastructure/BuildCopManager.cs @@ -4,20 +4,16 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Build.Analyzers.Analyzers; using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Analyzers; +using Microsoft.Build.BuildCop.Logging; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; -using Microsoft.Build.Execution; -using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Framework; -using Microsoft.Build.Logging.Analyzers; -namespace Microsoft.Build.Analyzers.Infrastructure; +namespace Microsoft.Build.BuildCop.Infrastructure; internal sealed class BuildCopManager : IBuildCopManager { diff --git a/src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs b/src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs index 0ba5c1d2381..a1fe319f80c 100644 --- a/src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs +++ b/src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs @@ -9,10 +9,9 @@ using System.Text; using System.Text.Json.Serialization; using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; -namespace Microsoft.Build.Analyzers.Infrastructure; +namespace Microsoft.Build.BuildCop.Infrastructure; // TODO: https://github.com/dotnet/msbuild/issues/9628 internal static class ConfigurationProvider diff --git a/src/Build/BuildCop/Infrastructure/IBuildCopContext.cs b/src/Build/BuildCop/Infrastructure/IBuildCopContext.cs index 4c6ac35a4ec..22cefa9e976 100644 --- a/src/Build/BuildCop/Infrastructure/IBuildCopContext.cs +++ b/src/Build/BuildCop/Infrastructure/IBuildCopContext.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; + +namespace Microsoft.Build.BuildCop.Infrastructure; public interface IBuildCopContext { diff --git a/src/Build/BuildCop/Logging/AnalyzerLoggingContext.cs b/src/Build/BuildCop/Logging/AnalyzerLoggingContext.cs index efb07dcffed..ddf799afded 100644 --- a/src/Build/BuildCop/Logging/AnalyzerLoggingContext.cs +++ b/src/Build/BuildCop/Logging/AnalyzerLoggingContext.cs @@ -1,16 +1,11 @@ // 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; using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Framework; -namespace Microsoft.Build.Logging.Analyzers; +namespace Microsoft.Build.BuildCop.Logging; internal class AnalyzerLoggingContext : LoggingContext, IBuildAnalysisLoggingContext { diff --git a/src/Build/BuildCop/Logging/AnalyzerLoggingContextFactory.cs b/src/Build/BuildCop/Logging/AnalyzerLoggingContextFactory.cs index dc09ae76f30..70a8126b7c6 100644 --- a/src/Build/BuildCop/Logging/AnalyzerLoggingContextFactory.cs +++ b/src/Build/BuildCop/Logging/AnalyzerLoggingContextFactory.cs @@ -1,15 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Framework; -namespace Microsoft.Build.Logging.Analyzers; +namespace Microsoft.Build.BuildCop.Logging; internal class AnalyzerLoggingContextFactory(ILoggingService loggingService) : IBuildAnalysisLoggingContextFactory { public IBuildAnalysisLoggingContext CreateLoggingContext(BuildEventContext eventContext) => diff --git a/src/Build/BuildCop/Logging/BuildAnalysisLoggingContextExtensions.cs b/src/Build/BuildCop/Logging/BuildAnalysisLoggingContextExtensions.cs index 04a7733f74b..c115c649b55 100644 --- a/src/Build/BuildCop/Logging/BuildAnalysisLoggingContextExtensions.cs +++ b/src/Build/BuildCop/Logging/BuildAnalysisLoggingContextExtensions.cs @@ -3,9 +3,9 @@ using System; using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; -namespace Microsoft.Build.Logging.Analyzers; +namespace Microsoft.Build.BuildCop.Logging; internal static class BuildAnalysisLoggingContextExtensions { diff --git a/src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs b/src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs index 282b9a73ddb..d219e825c2e 100644 --- a/src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs +++ b/src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using Microsoft.Build.BackEnd.Logging; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public class EvaluatedPropertiesContext : BuildAnalysisContext { internal EvaluatedPropertiesContext( diff --git a/src/Build/BuildCop/OM/ParsedItemsContext.cs b/src/Build/BuildCop/OM/ParsedItemsContext.cs index f9a4038599d..e65a176f3ca 100644 --- a/src/Build/BuildCop/OM/ParsedItemsContext.cs +++ b/src/Build/BuildCop/OM/ParsedItemsContext.cs @@ -9,7 +9,7 @@ using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Construction; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public static class ItemTypeExtensions { diff --git a/src/Framework/Analyzers/IBuildAnalysisLoggingContext.cs b/src/Framework/BuildCop/IBuildAnalysisLoggingContext.cs similarity index 79% rename from src/Framework/Analyzers/IBuildAnalysisLoggingContext.cs rename to src/Framework/BuildCop/IBuildAnalysisLoggingContext.cs index ef15d1b48a2..79d0ad32876 100644 --- a/src/Framework/Analyzers/IBuildAnalysisLoggingContext.cs +++ b/src/Framework/BuildCop/IBuildAnalysisLoggingContext.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public interface IBuildAnalysisLoggingContext { } diff --git a/src/Framework/Analyzers/IBuildAnalysisLoggingContextFactory.cs b/src/Framework/BuildCop/IBuildAnalysisLoggingContextFactory.cs similarity index 86% rename from src/Framework/Analyzers/IBuildAnalysisLoggingContextFactory.cs rename to src/Framework/BuildCop/IBuildAnalysisLoggingContextFactory.cs index da1a99b6ddb..ea959fa3db9 100644 --- a/src/Framework/Analyzers/IBuildAnalysisLoggingContextFactory.cs +++ b/src/Framework/BuildCop/IBuildAnalysisLoggingContextFactory.cs @@ -3,7 +3,7 @@ using Microsoft.Build.Framework; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public interface IBuildAnalysisLoggingContextFactory { diff --git a/src/Framework/Analyzers/IBuildCopLoggerFactory.cs b/src/Framework/BuildCop/IBuildCopLoggerFactory.cs similarity index 86% rename from src/Framework/Analyzers/IBuildCopLoggerFactory.cs rename to src/Framework/BuildCop/IBuildCopLoggerFactory.cs index 4d7c3d76d81..2fb4e2d0ffb 100644 --- a/src/Framework/Analyzers/IBuildCopLoggerFactory.cs +++ b/src/Framework/BuildCop/IBuildCopLoggerFactory.cs @@ -3,7 +3,7 @@ using Microsoft.Build.Framework; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public interface IBuildCopLoggerFactory { diff --git a/src/Framework/Analyzers/IBuildCopManager.cs b/src/Framework/BuildCop/IBuildCopManager.cs similarity index 91% rename from src/Framework/Analyzers/IBuildCopManager.cs rename to src/Framework/BuildCop/IBuildCopManager.cs index 9cc66a52525..30e076b2395 100644 --- a/src/Framework/Analyzers/IBuildCopManager.cs +++ b/src/Framework/BuildCop/IBuildCopManager.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.Build.Framework; -namespace Microsoft.Build.Experimental; +namespace Microsoft.Build.Experimental.BuildCop; public interface IBuildCopManager { diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 1cea300db64..433d06e9011 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -25,6 +25,7 @@ using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; using Microsoft.Build.Experimental; +using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Experimental.ProjectCache; using Microsoft.Build.Framework; using Microsoft.Build.Framework.Telemetry; From b474fa12e159f1d573b50689e25593d6c7309983 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Wed, 21 Feb 2024 10:49:21 +0100 Subject: [PATCH 16/58] Simplify TestEnvironments in EndToEndTests --- src/Analyzers.UnitTests/EndToEndTests.cs | 157 +++++++++++------------ 1 file changed, 74 insertions(+), 83 deletions(-) diff --git a/src/Analyzers.UnitTests/EndToEndTests.cs b/src/Analyzers.UnitTests/EndToEndTests.cs index 0b98ec87369..5347a09b19e 100644 --- a/src/Analyzers.UnitTests/EndToEndTests.cs +++ b/src/Analyzers.UnitTests/EndToEndTests.cs @@ -32,105 +32,96 @@ public EndToEndTests(ITestOutputHelper output) [Fact] public void SampleAnalyzerIntegrationTest() { - using (TestEnvironment env = TestEnvironment.Create()) - { - string contents = $""" - + string contents = $""" + - - Exe - net8.0 - enable - enable - + + Exe + net8.0 + enable + enable + - - Test - + + Test + - - - + + + - - - + + + - - """; + + """; - string contents2 = $""" - + string contents2 = $""" + - - Exe - net8.0 - enable - enable - + + Exe + net8.0 + enable + enable + - - Test - + + Test + - - - + + + - - - + + + - - """; - TransientTestFolder workFolder = env.CreateFolder(createFolder: true); - TransientTestFile projectFile = env.CreateFile(workFolder, "FooBar.csproj", contents); - TransientTestFile projectFile2 = env.CreateFile(workFolder, "FooBar-Copy.csproj", contents2); + + """; + TransientTestFolder workFolder = _env.CreateFolder(createFolder: true); + TransientTestFile projectFile = _env.CreateFile(workFolder, "FooBar.csproj", contents); + TransientTestFile projectFile2 = _env.CreateFile(workFolder, "FooBar-Copy.csproj", contents2); - // var cache = new SimpleProjectRootElementCache(); - // ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile.Path, /*unused*/null, /*unused*/null, cache, false /*Not explicitly loaded - unused*/); + // var cache = new SimpleProjectRootElementCache(); + // ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile.Path, /*unused*/null, /*unused*/null, cache, false /*Not explicitly loaded - unused*/); - TransientTestFile config = env.CreateFile(workFolder, "editorconfig.json", - /*lang=json,strict*/ - """ - { - "BC0101": { - "IsEnabled": true, - "Severity": "Error" - }, - "COND0543": { - "IsEnabled": false, - "Severity": "Error", - "EvaluationAnalysisScope": "AnalyzedProjectOnly", - "CustomSwitch": "QWERTY" - }, - "BLA": { - "IsEnabled": false - } - } - """); - - // OSX links /var into /private, which makes Path.GetTempPath() return "/var..." but Directory.GetCurrentDirectory return "/private/var...". - // This discrepancy breaks path equality checks in analyzers if we pass to MSBuild full path to the initial project. - // TODO: See if there is a way of fixing it in the engine. - TransientTestState testState = _env.SetCurrentDirectory(Path.GetDirectoryName(projectFile.Path)); - try - { - // env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1"); - env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); - // string output = RunnerUtilities.ExecMSBuild($"{projectFile.Path} /m:1 -nr:False", out bool success); - string output = BootstrapRunner.ExecBootstrapedMSBuild($"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -analyze", out bool success); - _env.Output.WriteLine(output); - success.ShouldBeTrue(); - // The conflicting outputs warning appears - output.ShouldContain("BC0101"); - } - finally + TransientTestFile config = _env.CreateFile(workFolder, "editorconfig.json", + /*lang=json,strict*/ + """ { - testState.Revert(); + "BC0101": { + "IsEnabled": true, + "Severity": "Error" + }, + "COND0543": { + "IsEnabled": false, + "Severity": "Error", + "EvaluationAnalysisScope": "AnalyzedProjectOnly", + "CustomSwitch": "QWERTY" + }, + "BLA": { + "IsEnabled": false + } } - } + """); + + // OSX links /var into /private, which makes Path.GetTempPath() return "/var..." but Directory.GetCurrentDirectory return "/private/var...". + // This discrepancy breaks path equality checks in analyzers if we pass to MSBuild full path to the initial project. + // TODO: See if there is a way of fixing it in the engine. + _env.SetCurrentDirectory(Path.GetDirectoryName(projectFile.Path)); + + // env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1"); + _env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); + // string output = RunnerUtilities.ExecMSBuild($"{projectFile.Path} /m:1 -nr:False", out bool success); + string output = BootstrapRunner.ExecBootstrapedMSBuild($"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -analyze", out bool success); + _env.Output.WriteLine(output); + success.ShouldBeTrue(); + // The conflicting outputs warning appears + output.ShouldContain("BC0101"); } } } From ac76b77ef77a47d0926d214c3310ce46b10acb4a Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Wed, 28 Feb 2024 15:45:49 +0100 Subject: [PATCH 17/58] Support for per-project configuration, Acquisition mounting, etc --- src/Analyzers.UnitTests/EndToEndTests.cs | 8 +- .../BackEnd/BuildManager/BuildManager.cs | 12 +- .../BackEnd/BuildManager/BuildParameters.cs | 11 +- .../BuildComponentFactoryCollection.cs | 4 + .../BuildRequestEngine/BuildRequestEngine.cs | 4 + .../BackEnd/Components/IBuildComponentHost.cs | 5 + .../Components/Logging/EventSourceSink.cs | 45 +++ .../RequestBuilder/RequestBuilder.cs | 24 ++ .../Shared/BuildRequestConfiguration.cs | 4 + .../BuildCop/API/BuildAnalysisContext.cs | 28 -- src/Build/BuildCop/API/BuildAnalyzer.cs | 9 +- .../API/BuildAnalyzerConfiguration.cs | 27 +- src/Build/BuildCop/API/BuildAnalyzerResult.cs | 115 ------ .../BuildCop/API/BuildCopLoggerFactory.cs | 20 - src/Build/BuildCop/API/BuildCopResult.cs | 52 +++ .../BuildCop/API/ConfigurationContext.cs | 30 +- .../BuildCop/API/EvaluationAnalysisScope.cs | 21 + src/Build/BuildCop/API/LifeTimeScope.cs | 11 - .../Acquisition/AnalyzerAcquisitionData.cs | 26 ++ .../Acquisition/BuildCopAcquisitionModule.cs | 22 ++ .../Analyzers/SharedOutputPathAnalyzer.cs | 17 +- .../Infrastructure/AnalyzersDelegates.cs | 8 - .../BuildAnalyzerConfigurationInternal.cs | 25 +- .../BuildAnalyzerTracingWrapper.cs | 31 -- .../Infrastructure/BuildAnalyzerWrapper.cs | 72 ++++ .../Infrastructure/BuildCopCentralContext.cs | 124 +++++- .../BuildCopConfigurationException.cs | 21 + .../Infrastructure/BuildCopConnectorLogger.cs | 54 ++- .../Infrastructure/BuildCopContext.cs | 27 +- .../Infrastructure/BuildCopManager.cs | 101 ----- .../Infrastructure/BuildCopManagerProvider.cs | 366 ++++++++++++++++++ .../Infrastructure/BuildEventsProcessor.cs | 88 +++++ .../Infrastructure/ConfigurationProvider.cs | 151 ++++++-- .../Infrastructure/CustomConfigurationData.cs | 45 +++ .../Infrastructure/IBuildCopContext.cs | 5 +- .../Infrastructure/IBuildCopManager.cs | 51 +++ .../Infrastructure/NullBuildCopManager.cs | 50 +++ .../Infrastructure/TracingReporter.cs | 28 ++ .../Logging}/IBuildAnalysisLoggingContext.cs | 0 .../IBuildAnalysisLoggingContextFactory.cs | 0 src/Build/BuildCop/OM/BuildAnalysisContext.cs | 60 +++ .../OM/EvaluatedPropertiesAnalysisData.cs | 16 + .../BuildCop/OM/EvaluatedPropertiesContext.cs | 20 - ...sContext.cs => ParsedItemsAnalysisData.cs} | 8 +- .../Utilities/EnumerableExtensions.cs | 65 ++++ src/Build/Microsoft.Build.csproj | 26 +- src/Framework/BuildCop/BuildCopEventArgs.cs | 150 +++++++ .../BuildCop/IBuildCopLoggerFactory.cs | 11 - src/Framework/BuildCop/IBuildCopManager.cs | 20 - src/Framework/BuildCop/IBuildCopResult.cs | 25 ++ src/Framework/IEventSource.cs | 7 + .../CommandLineSwitches_Tests.cs | 2 +- src/MSBuild/XMake.cs | 21 +- src/Shared/LogMessagePacketBase.cs | 55 +++ 54 files changed, 1749 insertions(+), 479 deletions(-) delete mode 100644 src/Build/BuildCop/API/BuildAnalysisContext.cs delete mode 100644 src/Build/BuildCop/API/BuildAnalyzerResult.cs delete mode 100644 src/Build/BuildCop/API/BuildCopLoggerFactory.cs create mode 100644 src/Build/BuildCop/API/BuildCopResult.cs delete mode 100644 src/Build/BuildCop/API/LifeTimeScope.cs create mode 100644 src/Build/BuildCop/Acquisition/AnalyzerAcquisitionData.cs create mode 100644 src/Build/BuildCop/Acquisition/BuildCopAcquisitionModule.cs delete mode 100644 src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs delete mode 100644 src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs create mode 100644 src/Build/BuildCop/Infrastructure/BuildAnalyzerWrapper.cs create mode 100644 src/Build/BuildCop/Infrastructure/BuildCopConfigurationException.cs delete mode 100644 src/Build/BuildCop/Infrastructure/BuildCopManager.cs create mode 100644 src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs create mode 100644 src/Build/BuildCop/Infrastructure/BuildEventsProcessor.cs create mode 100644 src/Build/BuildCop/Infrastructure/CustomConfigurationData.cs create mode 100644 src/Build/BuildCop/Infrastructure/IBuildCopManager.cs create mode 100644 src/Build/BuildCop/Infrastructure/NullBuildCopManager.cs create mode 100644 src/Build/BuildCop/Infrastructure/TracingReporter.cs rename src/{Framework/BuildCop => Build/BuildCop/Logging}/IBuildAnalysisLoggingContext.cs (100%) rename src/{Framework/BuildCop => Build/BuildCop/Logging}/IBuildAnalysisLoggingContextFactory.cs (100%) create mode 100644 src/Build/BuildCop/OM/BuildAnalysisContext.cs create mode 100644 src/Build/BuildCop/OM/EvaluatedPropertiesAnalysisData.cs delete mode 100644 src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs rename src/Build/BuildCop/OM/{ParsedItemsContext.cs => ParsedItemsAnalysisData.cs} (86%) create mode 100644 src/Build/BuildCop/Utilities/EnumerableExtensions.cs create mode 100644 src/Framework/BuildCop/BuildCopEventArgs.cs delete mode 100644 src/Framework/BuildCop/IBuildCopLoggerFactory.cs delete mode 100644 src/Framework/BuildCop/IBuildCopManager.cs create mode 100644 src/Framework/BuildCop/IBuildCopResult.cs diff --git a/src/Analyzers.UnitTests/EndToEndTests.cs b/src/Analyzers.UnitTests/EndToEndTests.cs index 5347a09b19e..7a573b23b3f 100644 --- a/src/Analyzers.UnitTests/EndToEndTests.cs +++ b/src/Analyzers.UnitTests/EndToEndTests.cs @@ -29,8 +29,10 @@ public EndToEndTests(ITestOutputHelper output) public void Dispose() => _env.Dispose(); - [Fact] - public void SampleAnalyzerIntegrationTest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode) { string contents = $""" @@ -114,7 +116,7 @@ public void SampleAnalyzerIntegrationTest() // TODO: See if there is a way of fixing it in the engine. _env.SetCurrentDirectory(Path.GetDirectoryName(projectFile.Path)); - // env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1"); + _env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", buildInOutOfProcessNode ? "1" : "0"); _env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); // string output = RunnerUtilities.ExecMSBuild($"{projectFile.Path} /m:1 -nr:False", out bool success); string output = BootstrapRunner.ExecBootstrapedMSBuild($"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -analyze", out bool success); diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 63a9b0c7ca8..aa8310d26a7 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -21,6 +21,7 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BackEnd.SdkResolution; +using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.BuildCop.Logging; using Microsoft.Build.Evaluation; using Microsoft.Build.Eventing; @@ -630,7 +631,6 @@ ILoggingService InitializeLoggingService() { ILoggingService loggingService = CreateLoggingService( AppendDebuggingLoggers(_buildParameters.Loggers), - _buildParameters.BuildCopLoggerFactory, _buildParameters.ForwardingLoggers, _buildParameters.WarningsAsErrors, _buildParameters.WarningsNotAsErrors, @@ -2948,7 +2948,6 @@ private void OnProjectStarted(object sender, ProjectStartedEventArgs e) /// private ILoggingService CreateLoggingService( IEnumerable loggers, - IBuildCopLoggerFactory buildCopLoggerFactory, IEnumerable forwardingLoggers, ISet warningsAsErrors, ISet warningsNotAsErrors, @@ -2976,12 +2975,15 @@ private ILoggingService CreateLoggingService( loggingService.WarningsNotAsErrors = warningsNotAsErrors; loggingService.WarningsAsMessages = warningsAsMessages; - if (buildCopLoggerFactory != null) + var buildCopManagerProvider = + ((IBuildComponentHost)this).GetComponent(BuildComponentType.BuildCop) as BuildCopManagerProvider; + buildCopManagerProvider!.Instance.SetDataSource(BuildCopDataSource.EventArgs); + + if (((IBuildComponentHost)this).BuildParameters.IsBuildCopEnabled) { loggers = (loggers ?? Enumerable.Empty()).Concat(new[] { - buildCopLoggerFactory.CreateBuildAnalysisLogger( - new AnalyzerLoggingContextFactory(loggingService)) + new BuildCopConnectorLogger(new AnalyzerLoggingContextFactory(loggingService), buildCopManagerProvider.Instance) }); } diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index a08d68a1d45..43683f7e658 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -209,6 +209,8 @@ public class BuildParameters : ITranslatable private bool _question; + private bool _isBuildCopEnabled; + /// /// The settings used to load the project under build /// @@ -311,7 +313,7 @@ internal BuildParameters(BuildParameters other, bool resetEnvironment = false) DiscardBuildResults = other.DiscardBuildResults; LowPriority = other.LowPriority; Question = other.Question; - BuildCopLoggerFactory = other.BuildCopLoggerFactory; + IsBuildCopEnabled = other.IsBuildCopEnabled; ProjectCacheDescriptor = other.ProjectCacheDescriptor; } @@ -840,7 +842,11 @@ public bool Question /// /// Gets or sets a factory for build analysis infrastructure logger /// - public IBuildCopLoggerFactory BuildCopLoggerFactory { get; set; } + public bool IsBuildCopEnabled + { + get => _isBuildCopEnabled; + set => _isBuildCopEnabled = value; + } /// /// Gets or sets the project cache description to use for all or @@ -906,6 +912,7 @@ void ITranslatable.Translate(ITranslator translator) translator.TranslateEnum(ref _projectLoadSettings, (int)_projectLoadSettings); translator.Translate(ref _interactive); translator.Translate(ref _question); + translator.Translate(ref _isBuildCopEnabled); translator.TranslateEnum(ref _projectIsolationMode, (int)_projectIsolationMode); translator.Translate(ref _reportFileAccesses); diff --git a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs index 70a4bf0aeef..16a51cfb086 100644 --- a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs +++ b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using Microsoft.Build.BackEnd.Components.Caching; using Microsoft.Build.BackEnd.SdkResolution; +using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.FileAccesses; using Microsoft.Build.Shared; @@ -76,6 +78,8 @@ public void RegisterDefaultFactories() // NodeEndpoint, _componentEntriesByType[BuildComponentType.LoggingService] = new BuildComponentEntry(BuildComponentType.LoggingService, null); _componentEntriesByType[BuildComponentType.RequestBuilder] = new BuildComponentEntry(BuildComponentType.RequestBuilder, RequestBuilder.CreateComponent, CreationPattern.CreateAlways); + // This conditionally registers real or no-op implementation based on BuildParameters + _componentEntriesByType[BuildComponentType.BuildCop] = new BuildComponentEntry(BuildComponentType.BuildCop, BuildCopManagerProvider.CreateComponent, CreationPattern.Singleton); _componentEntriesByType[BuildComponentType.TargetBuilder] = new BuildComponentEntry(BuildComponentType.TargetBuilder, TargetBuilder.CreateComponent, CreationPattern.CreateAlways); _componentEntriesByType[BuildComponentType.TaskBuilder] = new BuildComponentEntry(BuildComponentType.TaskBuilder, TaskBuilder.CreateComponent, CreationPattern.CreateAlways); _componentEntriesByType[BuildComponentType.RegisteredTaskObjectCache] = new BuildComponentEntry(BuildComponentType.RegisteredTaskObjectCache, RegisteredTaskObjectCache.CreateComponent, CreationPattern.Singleton); diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index 297ac265e0e..6b880af201a 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks.Dataflow; using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -281,6 +282,9 @@ public void CleanupForBuild() TraceEngine("CFB: Rethrowing shutdown exceptions"); throw new AggregateException(deactivateExceptions); } + + var buildCopManager = (_componentHost.GetComponent(BuildComponentType.BuildCop) as BuildCopManagerProvider)!.Instance; + buildCopManager.FinalizeProcessing(_nodeLoggingContext); }, isLastTask: true); diff --git a/src/Build/BackEnd/Components/IBuildComponentHost.cs b/src/Build/BackEnd/Components/IBuildComponentHost.cs index 5ae9d947906..e7392d7d42c 100644 --- a/src/Build/BackEnd/Components/IBuildComponentHost.cs +++ b/src/Build/BackEnd/Components/IBuildComponentHost.cs @@ -142,6 +142,11 @@ internal enum BuildComponentType /// The component which launches new MSBuild nodes. /// NodeLauncher, + + /// + /// The Build Analyzer Manager. + /// + BuildCop, } /// diff --git a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs index 340dfafc495..5ac9a885806 100644 --- a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs +++ b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Build.BuildCop.Infrastructure; +using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -98,6 +100,11 @@ internal sealed class EventSourceSink : /// This event is raised to log telemetry. /// public event TelemetryEventHandler TelemetryLogged; + + /// + /// This event is raised to log build cop events. + /// + public event BuildCopEventHandler BuildCopEventRaised; #endregion #region Properties @@ -263,6 +270,10 @@ public void Consume(BuildEventArgs buildEvent) case TelemetryEventArgs telemetryEvent: RaiseTelemetryEvent(null, telemetryEvent); break; + case BuildCopEventArgs buildCopEvent: + RaiseBuildCopEvent(null, buildCopEvent); + break; + default: ErrorUtilities.ThrowInternalError("Unknown event args type."); break; @@ -848,6 +859,40 @@ private void RaiseStatusEvent(object sender, BuildStatusEventArgs buildEvent) RaiseAnyEvent(sender, buildEvent); } + private void RaiseBuildCopEvent(object sender, BuildCopEventArgs buildEvent) + { + if (BuildCopEventRaised != null) + { + try + { + BuildCopEventRaised(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); + } + } + + RaiseAnyEvent(sender, buildEvent); + } + /// /// Raises a catch-all build event to all registered loggers. /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 0d98b32f5a8..d1e4318702f 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -10,11 +10,13 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Eventing; using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; +using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; @@ -1116,6 +1118,11 @@ private void SetProjectCurrentDirectory() /// private async Task BuildProject() { + // We consider this the entrypoint for the project build for purposes of BuildCop processing + + var buildCopManager = (_componentHost.GetComponent(BuildComponentType.BuildCop) as BuildCopManagerProvider)!.Instance; + buildCopManager.SetDataSource(BuildCopDataSource.BuildExecution); + ErrorUtilities.VerifyThrow(_targetBuilder != null, "Target builder is null"); // Make sure it is null before loading the configuration into the request, because if there is a problem @@ -1130,11 +1137,21 @@ private async Task BuildProject() // Load the project if (!_requestEntry.RequestConfiguration.IsLoaded) { + buildCopManager.StartProjectEvaluation( + BuildCopDataSource.BuildExecution, + _requestEntry.Request.ParentBuildEventContext, + _requestEntry.RequestConfiguration.ProjectFullPath); + _requestEntry.RequestConfiguration.LoadProjectIntoConfiguration( _componentHost, RequestEntry.Request.BuildRequestDataFlags, RequestEntry.Request.SubmissionId, _nodeLoggingContext.BuildEventContext.NodeId); + + // todo: in a using scope (autocleanup) + buildCopManager.EndProjectEvaluation( + BuildCopDataSource.BuildExecution, + _requestEntry.Request.ParentBuildEventContext); } } catch @@ -1150,6 +1167,9 @@ private async Task BuildProject() } _projectLoggingContext = _nodeLoggingContext.LogProjectStarted(_requestEntry); + buildCopManager.StartProjectRequest( + BuildCopDataSource.BuildExecution, + _requestEntry.Request.ParentBuildEventContext); // Now that the project has started, parse a few known properties which indicate warning codes to treat as errors or messages // @@ -1202,6 +1222,10 @@ private async Task BuildProject() MSBuildEventSource.Log.BuildProjectStop(_requestEntry.RequestConfiguration.ProjectFullPath, string.Join(", ", allTargets)); } + buildCopManager.EndProjectEvaluation( + BuildCopDataSource.BuildExecution, + _requestEntry.Request.ParentBuildEventContext); + return result; BuildResult CopyTargetResultsFromProxyTargetsToRealTargets(BuildResult resultFromTargetBuilder) diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index eda42874f86..75abeacda63 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -475,6 +475,10 @@ internal void LoadProjectIntoConfiguration( { projectLoadSettings |= ProjectLoadSettings.FailOnUnresolvedSdk; } + + // Here - is we'll have in-execution analysis and it'll need DOM from Project, + // this is the place for Project creation. + return new ProjectInstance( ProjectFullPath, globalProperties, diff --git a/src/Build/BuildCop/API/BuildAnalysisContext.cs b/src/Build/BuildCop/API/BuildAnalysisContext.cs deleted file mode 100644 index 4a9f578eca4..00000000000 --- a/src/Build/BuildCop/API/BuildAnalysisContext.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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; -using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.BuildCop.Infrastructure; -using Microsoft.Build.Experimental; -using Microsoft.Build.Framework; - -namespace Microsoft.Build.Experimental.BuildCop; - -public class BuildAnalysisContext -{ - private protected readonly LoggingContext _loggingContext; - - internal BuildAnalysisContext(LoggingContext loggingContext) => _loggingContext = loggingContext; - - public void ReportResult(BuildAnalyzerResult result) - { - BuildEventArgs eventArgs = result.ToEventArgs(ConfigurationProvider.GetMergedConfiguration(result.BuildAnalyzerRule).Severity); - eventArgs.BuildEventContext = _loggingContext.BuildEventContext; - _loggingContext.LogBuildEvent(eventArgs); - } -} diff --git a/src/Build/BuildCop/API/BuildAnalyzer.cs b/src/Build/BuildCop/API/BuildAnalyzer.cs index f7ff0b948c2..1eac8835fa3 100644 --- a/src/Build/BuildCop/API/BuildAnalyzer.cs +++ b/src/Build/BuildCop/API/BuildAnalyzer.cs @@ -1,16 +1,21 @@ // 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.Collections.Immutable; using Microsoft.Build.BuildCop.Infrastructure; namespace Microsoft.Build.Experimental.BuildCop; -public abstract class BuildAnalyzer +public abstract class BuildAnalyzer : IDisposable { public abstract string FriendlyName { get; } - public abstract ImmutableArray SupportedRules { get; } + public abstract IReadOnlyList SupportedRules { get; } public abstract void Initialize(ConfigurationContext configurationContext); public abstract void RegisterActions(IBuildCopContext context); + + public virtual void Dispose() + { } } diff --git a/src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs b/src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs index f7a45e08031..19ab210e097 100644 --- a/src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs +++ b/src/Build/BuildCop/API/BuildAnalyzerConfiguration.cs @@ -3,11 +3,19 @@ namespace Microsoft.Build.Experimental.BuildCop; +/// +/// Configuration for a build analyzer. +/// Default values can be specified by the Analyzer in code. +/// Users can overwrite the defaults by explicit settings in the .editorconfig file. +/// Each rule can have its own configuration, which can differ per each project. +/// The setting must be same for all rules in the same analyzer (but can differ between projects) +/// public class BuildAnalyzerConfiguration { + // Defaults to be used if any configuration property is not specified neither as default + // nor in the editorconfig configuration file. public static BuildAnalyzerConfiguration Default { get; } = new() { - LifeTimeScope = BuildCop.LifeTimeScope.PerProject, EvaluationAnalysisScope = BuildCop.EvaluationAnalysisScope.AnalyzedProjectOnly, Severity = BuildAnalyzerResultSeverity.Info, IsEnabled = false, @@ -15,8 +23,23 @@ public class BuildAnalyzerConfiguration public static BuildAnalyzerConfiguration Null { get; } = new(); - public LifeTimeScope? LifeTimeScope { get; internal init; } + /// + /// This applies only to specific events, that can distinguish whether they are directly inferred from + /// the current project, or from some import. If supported it can help tuning the level of detail or noise from analysis. + /// + /// If not supported by the data source - then the setting is ignored + /// public EvaluationAnalysisScope? EvaluationAnalysisScope { get; internal init; } + + /// + /// The severity of the result for the rule. + /// public BuildAnalyzerResultSeverity? Severity { get; internal init; } + + /// + /// Whether the analyzer rule is enabled. + /// If all rules within the analyzer are not enabled, it will not be run. + /// If some rules are enabled and some are not, the analyzer will be run and reports will be post-filtered. + /// public bool? IsEnabled { get; internal init; } } diff --git a/src/Build/BuildCop/API/BuildAnalyzerResult.cs b/src/Build/BuildCop/API/BuildAnalyzerResult.cs deleted file mode 100644 index 7c348f7fa2b..00000000000 --- a/src/Build/BuildCop/API/BuildAnalyzerResult.cs +++ /dev/null @@ -1,115 +0,0 @@ -// 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.IO; -using Microsoft.Build.Construction; -using Microsoft.Build.Framework; - -namespace Microsoft.Build.Experimental.BuildCop; - -public class BuildAnalyzerResult -{ - public static BuildAnalyzerResult Create(BuildAnalyzerRule rule, ElementLocation location, params string[] messageArgs) - { - return new BuildAnalyzerResult(rule, location, messageArgs); - } - - public BuildAnalyzerResult(BuildAnalyzerRule buildAnalyzerRule, ElementLocation location, string[] messageArgs) - { - BuildAnalyzerRule = buildAnalyzerRule; - Location = location; - MessageArgs = messageArgs; - } - - internal BuildEventArgs ToEventArgs(BuildAnalyzerResultSeverity severity) - => severity switch - { - BuildAnalyzerResultSeverity.Info => new BuildAnalysisResultMessage(this), - BuildAnalyzerResultSeverity.Warning => new BuildAnalysisResultWarning(this), - BuildAnalyzerResultSeverity.Error => new BuildAnalysisResultError(this), - _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null), - }; - - public BuildAnalyzerRule BuildAnalyzerRule { get; } - public ElementLocation Location { get; } - public string[] MessageArgs { get; } - - private string? _message; - public string Message => _message ??= $"{(Equals(Location ?? ElementLocation.EmptyLocation, ElementLocation.EmptyLocation) ? string.Empty : (Location!.LocationString + ": "))}{BuildAnalyzerRule.Id}: {string.Format(BuildAnalyzerRule.MessageFormat, MessageArgs)}"; -} - -public sealed class BuildAnalysisResultWarning : BuildWarningEventArgs -{ - public BuildAnalysisResultWarning(BuildAnalyzerResult result) - { - this.Message = result.Message; - } - - - internal override void WriteToStream(BinaryWriter writer) - { - base.WriteToStream(writer); - - writer.Write(Message!); - } - - internal override void CreateFromStream(BinaryReader reader, int version) - { - base.CreateFromStream(reader, version); - - Message = reader.ReadString(); - } - - public override string? Message { get; protected set; } -} - -public sealed class BuildAnalysisResultError : BuildErrorEventArgs -{ - public BuildAnalysisResultError(BuildAnalyzerResult result) - { - this.Message = result.Message; - } - - - internal override void WriteToStream(BinaryWriter writer) - { - base.WriteToStream(writer); - - writer.Write(Message!); - } - - internal override void CreateFromStream(BinaryReader reader, int version) - { - base.CreateFromStream(reader, version); - - Message = reader.ReadString(); - } - - public override string? Message { get; protected set; } -} - -public sealed class BuildAnalysisResultMessage : BuildMessageEventArgs -{ - public BuildAnalysisResultMessage(BuildAnalyzerResult result) - { - this.Message = result.Message; - } - - - internal override void WriteToStream(BinaryWriter writer) - { - base.WriteToStream(writer); - - writer.Write(Message!); - } - - internal override void CreateFromStream(BinaryReader reader, int version) - { - base.CreateFromStream(reader, version); - - Message = reader.ReadString(); - } - - public override string? Message { get; protected set; } -} diff --git a/src/Build/BuildCop/API/BuildCopLoggerFactory.cs b/src/Build/BuildCop/API/BuildCopLoggerFactory.cs deleted file mode 100644 index d0d9c6a2e08..00000000000 --- a/src/Build/BuildCop/API/BuildCopLoggerFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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; -using Microsoft.Build.BuildCop.Infrastructure; -using Microsoft.Build.Framework; - -namespace Microsoft.Build.Experimental.BuildCop; - -public class BuildCopLoggerFactory : IBuildCopLoggerFactory -{ - public ILogger CreateBuildAnalysisLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory) - { - return new BuildCopConnectorLogger(loggingContextFactory, BuildCopManager.Instance); - } -} diff --git a/src/Build/BuildCop/API/BuildCopResult.cs b/src/Build/BuildCop/API/BuildCopResult.cs new file mode 100644 index 00000000000..9a2842178d0 --- /dev/null +++ b/src/Build/BuildCop/API/BuildCopResult.cs @@ -0,0 +1,52 @@ +// 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.IO; +using Microsoft.Build.Construction; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Experimental.BuildCop; + +/// +/// Representation of a single report of a single finding from a BuildAnalyzer +/// Each rule has upfront known message format - so only the concrete arguments are added +/// Optionally a location is attached - in the near future we might need to support multiple locations +/// (for 2 cases - a) grouped result for multiple occurrences; b) a single report for a finding resulting from combination of multiple locations) +/// +public sealed class BuildCopResult : IBuildCopResult +{ + public static BuildCopResult Create(BuildAnalyzerRule rule, ElementLocation location, params string[] messageArgs) + { + return new BuildCopResult(rule, location, messageArgs); + } + + public BuildCopResult(BuildAnalyzerRule buildAnalyzerRule, ElementLocation location, string[] messageArgs) + { + BuildAnalyzerRule = buildAnalyzerRule; + Location = location; + MessageArgs = messageArgs; + } + + internal BuildEventArgs ToEventArgs(BuildAnalyzerResultSeverity severity) + => severity switch + { + BuildAnalyzerResultSeverity.Info => new BuildCopResultMessage(this), + BuildAnalyzerResultSeverity.Warning => new BuildCopResultWarning(this), + BuildAnalyzerResultSeverity.Error => new BuildCopResultError(this), + _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null), + }; + + public BuildAnalyzerRule BuildAnalyzerRule { get; } + public ElementLocation Location { get; } + + public string LocationString => Location.LocationString; + + public string[] MessageArgs { get; } + public string MessageFormat => BuildAnalyzerRule.MessageFormat; + + public string FormatMessage() => + _message ??= $"{(Equals(Location ?? ElementLocation.EmptyLocation, ElementLocation.EmptyLocation) ? string.Empty : (Location!.LocationString + ": "))}{BuildAnalyzerRule.Id}: {string.Format(BuildAnalyzerRule.MessageFormat, MessageArgs)}"; + + private string? _message; +} diff --git a/src/Build/BuildCop/API/ConfigurationContext.cs b/src/Build/BuildCop/API/ConfigurationContext.cs index 0aba0f87344..83fdbfdcbde 100644 --- a/src/Build/BuildCop/API/ConfigurationContext.cs +++ b/src/Build/BuildCop/API/ConfigurationContext.cs @@ -1,16 +1,40 @@ // 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.BuildCop.Infrastructure; namespace Microsoft.Build.Experimental.BuildCop; /// -/// Holder of an optional configuration from .editorconfig file (not recognized by infrastructure) +/// Holder of an optional configuration from .editorconfig file (not recognized by the infrastructure) /// public class ConfigurationContext { - public static ConfigurationContext Null { get; } = new(); + private ConfigurationContext(CustomConfigurationData[] customConfigurationData) + { + CustomConfigurationData = customConfigurationData; + } - public IReadOnlyDictionary? ConfigurationData { get; init; } + public static ConfigurationContext FromDataEnumeration(CustomConfigurationData[] customConfigurationData) + { + if (!customConfigurationData.Any(BuildCop.CustomConfigurationData.NotNull)) + { + return Null; + } + + return new ConfigurationContext( + customConfigurationData + .Where(BuildCop.CustomConfigurationData.NotNull) + .ToArray()); + } + + public static ConfigurationContext Null { get; } = new(Array.Empty()); + + /// + /// Custom configuration data - per each rule that has some specified. + /// + public CustomConfigurationData[] CustomConfigurationData { get; init; } } diff --git a/src/Build/BuildCop/API/EvaluationAnalysisScope.cs b/src/Build/BuildCop/API/EvaluationAnalysisScope.cs index 843826723dc..f8979ed8257 100644 --- a/src/Build/BuildCop/API/EvaluationAnalysisScope.cs +++ b/src/Build/BuildCop/API/EvaluationAnalysisScope.cs @@ -3,10 +3,31 @@ namespace Microsoft.Build.Experimental.BuildCop; +/// +/// For datasource events that can differentiate from where exactly they originate - e.g. +/// For a condition string or AST - was that directly in hte analyzed project or imported? +/// +/// Ignored by infrastructure if the current datasource doesn't support this level of setting. +/// public enum EvaluationAnalysisScope { + /// + /// Only the data from currently analyzed project will be sent to the analyzer. Imports will be discarded. + /// AnalyzedProjectOnly, + + /// + /// Only the data from currently analyzed project and imports from files under the entry project or solution will be sent to the analyzer. Other imports will be discarded. + /// AnalyzedProjectWithImportsFromCurrentWorkTree, + + /// + /// Imports from SDKs will not be sent to the analyzer. Other imports will be sent. + /// AnalyzedProjectWithImportsWithoutSdks, + + /// + /// All data will be sent to the analyzer. + /// AnalyzedProjectWithAllImports, } diff --git a/src/Build/BuildCop/API/LifeTimeScope.cs b/src/Build/BuildCop/API/LifeTimeScope.cs deleted file mode 100644 index 485da9c1781..00000000000 --- a/src/Build/BuildCop/API/LifeTimeScope.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Build.Experimental.BuildCop; - -public enum LifeTimeScope -{ - Stateless, - PerProject, - PerBuild, -} diff --git a/src/Build/BuildCop/Acquisition/AnalyzerAcquisitionData.cs b/src/Build/BuildCop/Acquisition/AnalyzerAcquisitionData.cs new file mode 100644 index 00000000000..1f75152284f --- /dev/null +++ b/src/Build/BuildCop/Acquisition/AnalyzerAcquisitionData.cs @@ -0,0 +1,26 @@ +// 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; +using Microsoft.Build.Experimental.BuildCop; + +namespace Microsoft.Build.BuildCop.Acquisition; + +// TODO: Acquisition +// define the data that will be passed to the acquisition module (and remoted if needed) +internal class AnalyzerAcquisitionData(string data) +{ + public string Data { get; init; } = data; +} + +internal static class AnalyzerAcquisitionDataExtensions +{ + public static AnalyzerAcquisitionData ToAnalyzerAcquisitionData(this BuildCopAcquisitionEventArgs eventArgs) => + new(eventArgs.AcquisitionData); + + public static BuildCopAcquisitionEventArgs ToBuildEventArgs(this AnalyzerAcquisitionData data) => new(data.Data); +} diff --git a/src/Build/BuildCop/Acquisition/BuildCopAcquisitionModule.cs b/src/Build/BuildCop/Acquisition/BuildCopAcquisitionModule.cs new file mode 100644 index 00000000000..9131be44995 --- /dev/null +++ b/src/Build/BuildCop/Acquisition/BuildCopAcquisitionModule.cs @@ -0,0 +1,22 @@ +// 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; +using Microsoft.Build.BuildCop.Analyzers; +using Microsoft.Build.BuildCop.Infrastructure; + +namespace Microsoft.Build.BuildCop.Acquisition; + +internal class BuildCopAcquisitionModule +{ + private static T Construct() where T : new() => new(); + public BuildAnalyzerFactory CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData) + { + // TODO: Acquisition module + return Construct; + } +} diff --git a/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs b/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs index 298643a7ced..fcb39a80dfc 100644 --- a/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs +++ b/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.Construction; @@ -27,7 +28,7 @@ internal sealed class SharedOutputPathAnalyzer : BuildAnalyzer public override string FriendlyName => "MSBuild.SharedOutputPathAnalyzer"; - public override ImmutableArray SupportedRules { get; } =[SupportedRule]; + public override IReadOnlyList SupportedRules { get; } =[SupportedRule]; public override void Initialize(ConfigurationContext configurationContext) { @@ -42,17 +43,17 @@ public override void RegisterActions(IBuildCopContext context) private readonly Dictionary _projectsPerOutputPath = new(StringComparer.CurrentCultureIgnoreCase); private readonly HashSet _projects = new(StringComparer.CurrentCultureIgnoreCase); - private void EvaluatedPropertiesAction(EvaluatedPropertiesContext context) + private void EvaluatedPropertiesAction(BuildAnalysisContext context) { - if (!_projects.Add(context.ProjectFilePath)) + if (!_projects.Add(context.Data.ProjectFilePath)) { return; } string? binPath, objPath; - context.EvaluatedProperties.TryGetValue("OutputPath", out binPath); - context.EvaluatedProperties.TryGetValue("IntermediateOutputPath", out objPath); + context.Data.EvaluatedProperties.TryGetValue("OutputPath", out binPath); + context.Data.EvaluatedProperties.TryGetValue("IntermediateOutputPath", out objPath); string? absoluteBinPath = CheckAndAddFullOutputPath(binPath, context); if ( @@ -65,14 +66,14 @@ private void EvaluatedPropertiesAction(EvaluatedPropertiesContext context) } } - private string? CheckAndAddFullOutputPath(string? path, EvaluatedPropertiesContext context) + private string? CheckAndAddFullOutputPath(string? path, BuildAnalysisContext context) { if (string.IsNullOrEmpty(path)) { return path; } - string projectPath = context.ProjectFilePath; + string projectPath = context.Data.ProjectFilePath; if (!Path.IsPathRooted(path)) { @@ -81,7 +82,7 @@ private void EvaluatedPropertiesAction(EvaluatedPropertiesContext context) if (_projectsPerOutputPath.TryGetValue(path!, out string? conflictingProject)) { - context.ReportResult(BuildAnalyzerResult.Create( + context.ReportResult(BuildCopResult.Create( SupportedRule, // TODO: let's support transmitting locations of specific properties ElementLocation.EmptyLocation, diff --git a/src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs b/src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs deleted file mode 100644 index 46bac4c5f64..00000000000 --- a/src/Build/BuildCop/Infrastructure/AnalyzersDelegates.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Build.Experimental.BuildCop; - -public delegate void EvaluatedPropertiesAction(EvaluatedPropertiesContext context); - -public delegate void ParsedItemsAction(ParsedItemsContext context); diff --git a/src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs b/src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs index 7d8f6d9ebcf..2452c7e5124 100644 --- a/src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs +++ b/src/Build/BuildCop/Infrastructure/BuildAnalyzerConfigurationInternal.cs @@ -10,8 +10,25 @@ namespace Microsoft.Build.BuildCop.Infrastructure; /// internal sealed class BuildAnalyzerConfigurationInternal { - public LifeTimeScope LifeTimeScope { get; internal init; } - public EvaluationAnalysisScope EvaluationAnalysisScope { get; internal init; } - public BuildAnalyzerResultSeverity Severity { get; internal init; } - public bool IsEnabled { get; internal init; } + public BuildAnalyzerConfigurationInternal(string ruleId, EvaluationAnalysisScope evaluationAnalysisScope, BuildAnalyzerResultSeverity severity, bool isEnabled) + { + RuleId = ruleId; + EvaluationAnalysisScope = evaluationAnalysisScope; + Severity = severity; + IsEnabled = isEnabled; + } + + public string RuleId { get; } + public EvaluationAnalysisScope EvaluationAnalysisScope { get; } + public BuildAnalyzerResultSeverity Severity { get; } + public bool IsEnabled { get; } + + // Intentionally not checking the RuleId + // as for analyzers with multiple rules, we can squash config to a single one, + // if the ruleId is the only thing differing. + public bool IsEqual(BuildAnalyzerConfigurationInternal? other) => + other != null && + Severity == other.Severity && + IsEnabled == other.IsEnabled && + EvaluationAnalysisScope == other.EvaluationAnalysisScope; } diff --git a/src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs b/src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs deleted file mode 100644 index da2e5181af9..00000000000 --- a/src/Build/BuildCop/Infrastructure/BuildAnalyzerTracingWrapper.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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.Diagnostics; -using Microsoft.Build.Experimental.BuildCop; - -namespace Microsoft.Build.BuildCop.Infrastructure; - -internal sealed class BuildAnalyzerTracingWrapper -{ - private readonly Stopwatch _stopwatch = new Stopwatch(); - - public BuildAnalyzerTracingWrapper(BuildAnalyzer buildAnalyzer) - => BuildAnalyzer = buildAnalyzer; - - internal BuildAnalyzer BuildAnalyzer { get; } - - internal TimeSpan Elapsed => _stopwatch.Elapsed; - - internal IDisposable StartSpan() - { - _stopwatch.Start(); - return new CleanupScope(_stopwatch.Stop); - } - - internal readonly struct CleanupScope(Action disposeAction) : IDisposable - { - public void Dispose() => disposeAction(); - } -} diff --git a/src/Build/BuildCop/Infrastructure/BuildAnalyzerWrapper.cs b/src/Build/BuildCop/Infrastructure/BuildAnalyzerWrapper.cs new file mode 100644 index 00000000000..173b3e48a5a --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/BuildAnalyzerWrapper.cs @@ -0,0 +1,72 @@ +// 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.Diagnostics; +using System.Linq; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Experimental.BuildCop; + +namespace Microsoft.Build.BuildCop.Infrastructure; + +/// +/// A wrapping, enriching class for BuildAnalyzer - so that we have additional data and functionality. +/// +internal sealed class BuildAnalyzerWrapper +{ + private readonly Stopwatch _stopwatch = new Stopwatch(); + + public BuildAnalyzerWrapper(BuildAnalyzer buildAnalyzer) + { + BuildAnalyzer = buildAnalyzer; + } + + internal BuildAnalyzer BuildAnalyzer { get; } + private bool _isInitialized = false; + + internal BuildAnalyzerConfigurationInternal? CommonConfig { get; private set; } + + // start new project + internal void StartNewProject( + string fullProjectPath, + IReadOnlyList userConfigs) + { + if (!_isInitialized) + { + _isInitialized = true; + CommonConfig = userConfigs[0]; + + if (userConfigs.Count == 1) + { + return; + } + } + + if (CommonConfig == null || !userConfigs.All(t => t.IsEqual(CommonConfig))) + { + CommonConfig = null; + } + } + + // to be used on eval node (BuildCopDataSource.BuildExecution) + internal void Uninitialize() + { + _isInitialized = false; + } + + internal TimeSpan Elapsed => _stopwatch.Elapsed; + + internal void ClearStats() => _stopwatch.Reset(); + + internal IDisposable StartSpan() + { + _stopwatch.Start(); + return new CleanupScope(_stopwatch.Stop); + } + + internal readonly struct CleanupScope(Action disposeAction) : IDisposable + { + public void Dispose() => disposeAction(); + } +} diff --git a/src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs b/src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs index 6e970ab6557..7ad82c37c1f 100644 --- a/src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs +++ b/src/Build/BuildCop/Infrastructure/BuildCopCentralContext.cs @@ -1,39 +1,135 @@ // 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.Threading.Tasks; +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Experimental.BuildCop; namespace Microsoft.Build.BuildCop.Infrastructure; +/// +/// A manager of the runs of the analyzers - deciding based on configuration of what to run and what to postfilter. +/// internal sealed class BuildCopCentralContext { - private EvaluatedPropertiesAction? _evaluatedPropertiesActions; - private ParsedItemsAction? _parsedItemsActions; + private record CallbackRegistry( + List<(BuildAnalyzerWrapper, Action>)> EvaluatedPropertiesActions, + List<(BuildAnalyzerWrapper, Action>)> ParsedItemsActions) + { + public CallbackRegistry() : this([],[]) { } + } + + // In a future we can have callbacks per project as well + private readonly CallbackRegistry _globalCallbacks = new(); // This we can potentially use to subscribe for receiving evaluated props in the // build event args. However - this needs to be done early on, when analyzers might not be known yet - internal bool HasEvaluatedPropertiesActions => _evaluatedPropertiesActions != null; - internal bool HasParsedItemsActions => _parsedItemsActions != null; + internal bool HasEvaluatedPropertiesActions => _globalCallbacks.EvaluatedPropertiesActions.Any(); + internal bool HasParsedItemsActions => _globalCallbacks.ParsedItemsActions.Any(); - internal void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction) - { + internal void RegisterEvaluatedPropertiesAction(BuildAnalyzerWrapper analyzer, Action> evaluatedPropertiesAction) // Here we might want to communicate to node that props need to be sent. // (it was being communicated via MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION) - _evaluatedPropertiesActions += evaluatedPropertiesAction; - } + => RegisterAction(analyzer, evaluatedPropertiesAction, _globalCallbacks.EvaluatedPropertiesActions); + + internal void RegisterParsedItemsAction(BuildAnalyzerWrapper analyzer, Action> parsedItemsAction) + => RegisterAction(analyzer, parsedItemsAction, _globalCallbacks.ParsedItemsActions); - internal void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction) + private void RegisterAction( + BuildAnalyzerWrapper wrappedAnalyzer, + Action> handler, + List<(BuildAnalyzerWrapper, Action>)> handlersRegistry) + where T : AnalysisData { - _parsedItemsActions += parsedItemsAction; + void WrappedHandler(BuildAnalysisContext context) + { + using var _ = wrappedAnalyzer.StartSpan(); + handler(context); + } + + lock (handlersRegistry) + { + handlersRegistry.Add((wrappedAnalyzer, WrappedHandler)); + } } - internal void RunEvaluatedPropertiesActions(EvaluatedPropertiesContext evaluatedPropertiesContext) + internal void DeregisterAnalyzer(BuildAnalyzerWrapper analyzer) { - _evaluatedPropertiesActions?.Invoke(evaluatedPropertiesContext); + _globalCallbacks.EvaluatedPropertiesActions.RemoveAll(a => a.Item1 == analyzer); + _globalCallbacks.ParsedItemsActions.RemoveAll(a => a.Item1 == analyzer); } - internal void RunParsedItemsActions(ParsedItemsContext parsedItemsContext) + internal void RunEvaluatedPropertiesActions( + EvaluatedPropertiesAnalysisData evaluatedPropertiesAnalysisData, + LoggingContext loggingContext, + Action + resultHandler) + => RunRegisteredActions(_globalCallbacks.EvaluatedPropertiesActions, evaluatedPropertiesAnalysisData, + loggingContext, resultHandler); + + internal void RunParsedItemsActions( + ParsedItemsAnalysisData parsedItemsAnalysisData, + LoggingContext loggingContext, + Action + resultHandler) + => RunRegisteredActions(_globalCallbacks.ParsedItemsActions, parsedItemsAnalysisData, + loggingContext, resultHandler); + + private void RunRegisteredActions( + List<(BuildAnalyzerWrapper, Action>)> registeredCallbacks, + T analysisData, + LoggingContext loggingContext, + Action resultHandler) + where T : AnalysisData { - _parsedItemsActions?.Invoke(parsedItemsContext); + string projectFullPath = analysisData.ProjectFilePath; + + // Alternatively we might want to actually do this all in serial, but asynchronously (blocking queue) + Parallel.ForEach( + registeredCallbacks, + new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, + /* (BuildAnalyzerWrapper2, Action>) */ + analyzerCallback => + { + // TODO: tracing - we might want tp account this entire block + // to the relevant analyzer (with only the currently accounted part as being the 'core-execution' subspan) + + BuildAnalyzerConfigurationInternal? commonConfig = analyzerCallback.Item1.CommonConfig; + BuildAnalyzerConfigurationInternal[] configPerRule; + + if (commonConfig != null) + { + if (!commonConfig.IsEnabled) + { + return; + } + + configPerRule = new[] { commonConfig }; + } + else + { + configPerRule = + ConfigurationProvider.GetMergedConfigurations(projectFullPath, + analyzerCallback.Item1.BuildAnalyzer); + if (configPerRule.All(c => !c.IsEnabled)) + { + return; + } + } + + // TODO: if the input data supports that - check the configPerRule[0].EvaluationAnalysisScope + + BuildAnalysisContext context = new BuildAnalysisContext( + analyzerCallback.Item1, + loggingContext, + configPerRule, + resultHandler, + analysisData); + + analyzerCallback.Item2(context); + }); } } diff --git a/src/Build/BuildCop/Infrastructure/BuildCopConfigurationException.cs b/src/Build/BuildCop/Infrastructure/BuildCopConfigurationException.cs new file mode 100644 index 00000000000..4580fb03b81 --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/BuildCopConfigurationException.cs @@ -0,0 +1,21 @@ +// 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.BuildCop.Infrastructure; + +internal class BuildCopConfigurationException : Exception +{ + /// + /// Exception to communicate issues with user specified configuration - unsupported scenarios, malformations, etc. + /// This exception usually leads to defuncting the particular analyzer for the rest of the build (even if issue occured with a single project). + /// + public BuildCopConfigurationException(string message) : base(message) + { + } +} diff --git a/src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs b/src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs index 174bb24373f..16bdd714ea2 100644 --- a/src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs +++ b/src/Build/BuildCop/Infrastructure/BuildCopConnectorLogger.cs @@ -2,11 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Acquisition; using Microsoft.Build.BuildCop.Logging; using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Framework; + // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. @@ -25,9 +29,13 @@ public void Initialize(IEventSource eventSource) private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) { - if (e is ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs && - !(projectEvaluationFinishedEventArgs.ProjectFile?.EndsWith(".metaproj") ?? false)) + if (e is ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs) { + if (projectEvaluationFinishedEventArgs.ProjectFile?.EndsWith(".metaproj") ?? false) + { + return; + } + try { buildCopManager.ProcessEvaluationFinishedEventArgs( @@ -40,20 +48,56 @@ private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) Console.WriteLine(exception); throw; } + + buildCopManager.EndProjectEvaluation(BuildCopDataSource.EventArgs, e.BuildEventContext!); + } + else if (e is ProjectEvaluationStartedEventArgs projectEvaluationStartedEventArgs) + { + if (projectEvaluationStartedEventArgs.ProjectFile?.EndsWith(".metaproj") ?? false) + { + return; + } + + buildCopManager.StartProjectEvaluation(BuildCopDataSource.EventArgs, e.BuildEventContext!, + projectEvaluationStartedEventArgs.ProjectFile!); + } + else if (e is ProjectStartedEventArgs projectStartedEvent) + { + buildCopManager.StartProjectRequest(BuildCopDataSource.EventArgs, e.BuildEventContext!); + } + else if (e is ProjectFinishedEventArgs projectFinishedEventArgs) + { + buildCopManager.EndProjectRequest(BuildCopDataSource.EventArgs, e.BuildEventContext!); + } + else if (e is BuildCopEventArgs buildCopBuildEventArgs) + { + if (buildCopBuildEventArgs is BuildCopTracingEventArgs tracingEventArgs) + { + _stats.Merge(tracingEventArgs.TracingData, (span1, span2) => span1 + span2); + } + else if (buildCopBuildEventArgs is BuildCopAcquisitionEventArgs acquisitionEventArgs) + { + buildCopManager.ProcessAnalyzerAcquisition(acquisitionEventArgs.ToAnalyzerAcquisitionData()); + } } - // here handling of other event types } + private readonly Dictionary _stats = new Dictionary(); + private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e) { + _stats.Merge(buildCopManager.CreateTracingStats(), (span1, span2) => span1 + span2); + string msg = string.Join(Environment.NewLine, _stats.Select(a => a.Key + ": " + a.Value)); + + BuildEventContext buildEventContext = e.BuildEventContext ?? new BuildEventContext( BuildEventContext.InvalidNodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); LoggingContext loggingContext = loggingContextFactory.CreateLoggingContext(buildEventContext).ToLoggingContext(); - // TODO: here flush the tracing stats: https://github.com/dotnet/msbuild/issues/9629 - loggingContext.LogCommentFromText(MessageImportance.High, buildCopManager.CreateTracingStats()); + // TODO: tracing: https://github.com/dotnet/msbuild/issues/9629 + loggingContext.LogCommentFromText(MessageImportance.High, msg); } public void Shutdown() diff --git a/src/Build/BuildCop/Infrastructure/BuildCopContext.cs b/src/Build/BuildCop/Infrastructure/BuildCopContext.cs index 240468b1700..2bfa8c6eaae 100644 --- a/src/Build/BuildCop/Infrastructure/BuildCopContext.cs +++ b/src/Build/BuildCop/Infrastructure/BuildCopContext.cs @@ -1,31 +1,36 @@ // 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.Threading; using Microsoft.Build.Experimental.BuildCop; namespace Microsoft.Build.BuildCop.Infrastructure; -internal sealed class BuildCopContext(BuildAnalyzerTracingWrapper analyzer, BuildCopCentralContext buildCopCentralContext) : IBuildCopContext +internal sealed class BuildCopContext(BuildAnalyzerWrapper analyzerWrapper, BuildCopCentralContext buildCopCentralContext) : IBuildCopContext { - public void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction) + private int _evaluatedPropertiesActionCount; + private int _parsedItemsActionCount; + + public void RegisterEvaluatedPropertiesAction(Action> evaluatedPropertiesAction) { - void WrappedEvaluatedPropertiesAction(EvaluatedPropertiesContext context) + if (Interlocked.Increment(ref _evaluatedPropertiesActionCount) > 1) { - using var _ = analyzer.StartSpan(); - evaluatedPropertiesAction(context); + throw new BuildCopConfigurationException( + $"Analyzer '{analyzerWrapper.BuildAnalyzer.FriendlyName}' attempted to call '{nameof(RegisterEvaluatedPropertiesAction)}' multiple times."); } - buildCopCentralContext.RegisterEvaluatedPropertiesAction(WrappedEvaluatedPropertiesAction); + buildCopCentralContext.RegisterEvaluatedPropertiesAction(analyzerWrapper, evaluatedPropertiesAction); } - public void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction) + public void RegisterParsedItemsAction(Action> parsedItemsAction) { - void WrappedParsedItemsAction(ParsedItemsContext context) + if (Interlocked.Increment(ref _parsedItemsActionCount) > 1) { - using var _ = analyzer.StartSpan(); - parsedItemsAction(context); + throw new BuildCopConfigurationException( + $"Analyzer '{analyzerWrapper.BuildAnalyzer.FriendlyName}' attempted to call '{nameof(RegisterParsedItemsAction)}' multiple times."); } - buildCopCentralContext.RegisterParsedItemsAction(WrappedParsedItemsAction); + buildCopCentralContext.RegisterParsedItemsAction(analyzerWrapper, parsedItemsAction); } } diff --git a/src/Build/BuildCop/Infrastructure/BuildCopManager.cs b/src/Build/BuildCop/Infrastructure/BuildCopManager.cs deleted file mode 100644 index 984bb2dfb71..00000000000 --- a/src/Build/BuildCop/Infrastructure/BuildCopManager.cs +++ /dev/null @@ -1,101 +0,0 @@ -// 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.Collections.ObjectModel; -using System.Linq; -using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.BuildCop.Analyzers; -using Microsoft.Build.BuildCop.Logging; -using Microsoft.Build.Construction; -using Microsoft.Build.Evaluation; -using Microsoft.Build.Experimental.BuildCop; -using Microsoft.Build.Framework; - -namespace Microsoft.Build.BuildCop.Infrastructure; - -internal sealed class BuildCopManager : IBuildCopManager -{ - private readonly List _analyzers = new(); - private readonly BuildCopCentralContext _buildCopCentralContext = new(); - - private BuildCopManager() { } - - internal static IBuildCopManager Instance => CreateBuildAnalysisManager(); - - public void RegisterAnalyzer(BuildAnalyzer analyzer) - { - if (!analyzer.SupportedRules.Any()) - { - // error out - return; - } - - IEnumerable configuration = analyzer.SupportedRules.Select(ConfigurationProvider.GetMergedConfiguration); - - if (configuration.All(c => !c.IsEnabled)) - { - return; - } - - // TODO: the config module should return any possible user configurations per rule - ConfigurationContext configurationContext = ConfigurationContext.Null; - analyzer.Initialize(configurationContext); - var wrappedAnalyzer = new BuildAnalyzerTracingWrapper(analyzer); - var wrappedContext = new BuildCopContext(wrappedAnalyzer, _buildCopCentralContext); - analyzer.RegisterActions(wrappedContext); - _analyzers.Add(wrappedAnalyzer); - } - - // TODO: all this processing should be queued and done async. We might even want to run analyzers in parallel - - private SimpleProjectRootElementCache _cache = new SimpleProjectRootElementCache(); - - // This requires MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION set to 1 - public void ProcessEvaluationFinishedEventArgs(IBuildAnalysisLoggingContext buildAnalysisContext, - ProjectEvaluationFinishedEventArgs evaluationFinishedEventArgs) - { - LoggingContext loggingContext = buildAnalysisContext.ToLoggingContext(); - - Dictionary propertiesLookup = new Dictionary(); - Internal.Utilities.EnumerateProperties(evaluationFinishedEventArgs.Properties, propertiesLookup, - static (dict, kvp) => dict.Add(kvp.Key, kvp.Value)); - - EvaluatedPropertiesContext context = new EvaluatedPropertiesContext(loggingContext, - new ReadOnlyDictionary(propertiesLookup), - evaluationFinishedEventArgs.ProjectFile!); - - _buildCopCentralContext.RunEvaluatedPropertiesActions(context); - - if (_buildCopCentralContext.HasParsedItemsActions) - { - ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(evaluationFinishedEventArgs.ProjectFile!, /*unused*/ - null, /*unused*/null, _cache, false /*Not explicitly loaded - unused*/); - - ParsedItemsContext parsedItemsContext = new ParsedItemsContext(loggingContext, - new ItemsHolder(xml.Items, xml.ItemGroups)); - - _buildCopCentralContext.RunParsedItemsActions(parsedItemsContext); - } - } - - // TODO: tracing: https://github.com/dotnet/msbuild/issues/9629 - // should have infra as well, should log to BuildCopConnectorLogger upon shutdown (if requested) - public string CreateTracingStats() - { - return string.Join(Environment.NewLine, - _analyzers.Select(a => GetAnalyzerDescriptor(a.BuildAnalyzer) + ": " + a.Elapsed)); - - string GetAnalyzerDescriptor(BuildAnalyzer buildAnalyzer) - => buildAnalyzer.FriendlyName + " (" + buildAnalyzer.GetType() + ")"; - } - - internal static BuildCopManager CreateBuildAnalysisManager() - { - var buildAnalysisManager = new BuildCopManager(); - buildAnalysisManager.RegisterAnalyzer(new SharedOutputPathAnalyzer()); - // ... Register other internal analyzers - return buildAnalysisManager; - } -} diff --git a/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs b/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs new file mode 100644 index 00000000000..c826f3854bc --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs @@ -0,0 +1,366 @@ +// 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.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.IO; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using Microsoft.Build.BackEnd; +using Microsoft.Build.BackEnd.Components.Caching; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Acquisition; +using Microsoft.Build.BuildCop.Analyzers; +using Microsoft.Build.BuildCop.Logging; +using Microsoft.Build.Collections; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Experimental.BuildCop; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.BuildCop.Infrastructure; + +internal delegate BuildAnalyzer BuildAnalyzerFactory(); +internal delegate BuildAnalyzerWrapper BuildAnalyzerWrapperFactory(ConfigurationContext configurationContext); + +internal sealed class BuildCopManagerProvider : IBuildComponent +{ + private static bool s_isInitialized = false; + private static IBuildCopManager s_globalInstance = new NullBuildCopManager(); + internal static IBuildCopManager GlobalInstance => s_isInitialized ? s_globalInstance : throw new InvalidOperationException("BuildCopManagerProvider not initialized"); + + internal IBuildCopManager Instance => GlobalInstance; + + internal static IBuildComponent CreateComponent(BuildComponentType type) + { + ErrorUtilities.VerifyThrow(type == BuildComponentType.BuildCop, "Cannot create components of type {0}", type); + return new BuildCopManagerProvider(); + } + + public void InitializeComponent(IBuildComponentHost host) + { + ErrorUtilities.VerifyThrow(host != null, "BuildComponentHost was null"); + + if (s_isInitialized) + { + throw new InvalidOperationException("BuildCopManagerProvider is already initialized"); + } + s_isInitialized = true; + + if (host!.BuildParameters.IsBuildCopEnabled) + { + s_globalInstance = new BuildCopManager(host.LoggingService); + } + else + { + s_globalInstance = new NullBuildCopManager(); + } + } + + public void ShutdownComponent() => GlobalInstance.Shutdown(); + + + private sealed class BuildCopManager : IBuildCopManager + { + private readonly TracingReporter _tracingReporter = new TracingReporter(); + private readonly BuildCopCentralContext _buildCopCentralContext = new(); + private readonly ILoggingService _loggingService; + private readonly List _analyzersRegistry =[]; + private readonly bool[] _enabledDataSources = new bool[(int)BuildCopDataSource.ValuesCount]; + private readonly BuildEventsProcessor _buildEventsProcessor; + private readonly BuildCopAcquisitionModule _acquisitionModule = new(); + + private bool IsInProcNode => _enabledDataSources[(int)BuildCopDataSource.EventArgs] && + _enabledDataSources[(int)BuildCopDataSource.BuildExecution]; + + /// + /// Notifies the manager that the data source will be used - + /// so it should register the built-in analyzers for the source if it hasn't been done yet. + /// + /// + public void SetDataSource(BuildCopDataSource buildCopDataSource) + { + if (!_enabledDataSources[(int)buildCopDataSource]) + { + _enabledDataSources[(int)buildCopDataSource] = true; + RegisterBuiltInAnalyzers(buildCopDataSource); + } + } + + public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) + { + if (IsInProcNode) + { + var factory = _acquisitionModule.CreateBuildAnalyzerFactory(acquisitionData); + RegisterCustomAnalyzer(BuildCopDataSource.EventArgs, factory); + } + else + { + BuildCopAcquisitionEventArgs eventArgs = acquisitionData.ToBuildEventArgs(); + + // TODO: We may want to pass the real context here (from evaluation) + eventArgs.BuildEventContext = new BuildEventContext( + BuildEventContext.InvalidNodeId, + BuildEventContext.InvalidProjectInstanceId, + BuildEventContext.InvalidProjectContextId, + BuildEventContext.InvalidTargetId, + BuildEventContext.InvalidTaskId); + } + } + + internal BuildCopManager(ILoggingService loggingService) + { + _loggingService = loggingService; + _buildEventsProcessor = new(_buildCopCentralContext); + } + + private static T Construct() where T : new() => new(); + private static readonly (string[] ruleIds, bool defaultEnablement, BuildAnalyzerFactory factory)[][] s_builtInFactoriesPerDataSource = + [ + // BuildCopDataSource.EventArgs + [ + ([SharedOutputPathAnalyzer.SupportedRule.Id], SharedOutputPathAnalyzer.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct) + ], + // BuildCopDataSource.Execution + [] + ]; + + private void RegisterBuiltInAnalyzers(BuildCopDataSource buildCopDataSource) + { + _analyzersRegistry.AddRange( + s_builtInFactoriesPerDataSource[(int)buildCopDataSource] + .Select(v => new BuildAnalyzerFactoryContext(v.factory, v.ruleIds, v.defaultEnablement))); + } + + /// + /// To be used by acquisition module + /// Registeres the custom analyzer, the construction of analyzer is deferred until the first using project is encountered + /// + internal void RegisterCustomAnalyzer( + BuildCopDataSource buildCopDataSource, + BuildAnalyzerFactory factory, + string[] ruleIds, + bool defaultEnablement) + { + if (_enabledDataSources[(int)buildCopDataSource]) + { + _analyzersRegistry.Add(new BuildAnalyzerFactoryContext(factory, ruleIds, defaultEnablement)); + } + } + + /// + /// To be used by acquisition module + /// Registeres the custom analyzer, the construction of analyzer is needed during registration + /// + internal void RegisterCustomAnalyzer( + BuildCopDataSource buildCopDataSource, + BuildAnalyzerFactory factory) + { + if (_enabledDataSources[(int)buildCopDataSource]) + { + var instance = factory(); + _analyzersRegistry.Add(new BuildAnalyzerFactoryContext(factory, + instance.SupportedRules.Select(r => r.Id).ToArray(), + instance.SupportedRules.Any(r => r.DefaultConfiguration.IsEnabled == true))); + } + } + + private void SetupSingleAnalyzer(BuildAnalyzerFactoryContext analyzerFactoryContext, string projectFullPath, BuildEventContext buildEventContext) + { + // TODO: For user analyzers - it should run only on projects where referenced + // on others it should work similarly as disabling them. + // Disabled analyzer should not only post-filter results - it shouldn't even see the data + + BuildAnalyzerWrapper wrapper; + BuildAnalyzerConfigurationInternal[] configurations; + if (analyzerFactoryContext.MaterializedAnalyzer == null) + { + BuildAnalyzerConfiguration[] userConfigs = + ConfigurationProvider.GetUserConfigurations(projectFullPath, analyzerFactoryContext.RuleIds); + + if (userConfigs.All(c => !(c.IsEnabled ?? analyzerFactoryContext.IsEnabledByDefault))) + { + // the analyzer was not yet instantiated nor mounted - so nothing to do here now. + return; + } + + CustomConfigurationData[] customConfigData = + ConfigurationProvider.GetCustomConfigurations(projectFullPath, analyzerFactoryContext.RuleIds); + + ConfigurationContext configurationContext = ConfigurationContext.FromDataEnumeration(customConfigData); + + wrapper = analyzerFactoryContext.Factory(configurationContext); + analyzerFactoryContext.MaterializedAnalyzer = wrapper; + BuildAnalyzer analyzer = wrapper.BuildAnalyzer; + + if ( + analyzer.SupportedRules.Count != analyzerFactoryContext.RuleIds.Length + || + !analyzer.SupportedRules.Select(r => r.Id) + .SequenceEqual(analyzerFactoryContext.RuleIds, StringComparer.CurrentCultureIgnoreCase) + ) + { + throw new BuildCopConfigurationException( + $"The analyzer '{analyzer.FriendlyName}' exposes rules '{analyzer.SupportedRules.Select(r => r.Id).ToCsvString()}', but different rules were declared during registration: '{analyzerFactoryContext.RuleIds.ToCsvString()}'"); + } + + configurations = ConfigurationProvider.GetMergedConfigurations(userConfigs, analyzer); + + // technically all analyzers rules could be disabled, but that would mean + // that the provided 'IsEnabledByDefault' value wasn't correct - the only + // price to be paid in that case is slight performance cost. + + // Create the wrapper and register to central context + wrapper.StartNewProject(projectFullPath, configurations); + var wrappedContext = new BuildCopContext(wrapper, _buildCopCentralContext); + analyzer.RegisterActions(wrappedContext); + } + else + { + wrapper = analyzerFactoryContext.MaterializedAnalyzer; + + configurations = ConfigurationProvider.GetMergedConfigurations(projectFullPath, wrapper.BuildAnalyzer); + + ConfigurationProvider.CheckCustomConfigurationDataValidity(projectFullPath, + analyzerFactoryContext.RuleIds[0]); + + // Update the wrapper + wrapper.StartNewProject(projectFullPath, configurations); + } + + if (configurations.GroupBy(c => c.EvaluationAnalysisScope).Count() > 1) + { + throw new BuildCopConfigurationException( + string.Format("All rules for a single analyzer should have the same EvaluationAnalysisScope for a single project (violating rules: [{0}], project: {1})", + analyzerFactoryContext.RuleIds.ToCsvString(), + projectFullPath)); + } + } + + private void SetupAnalyzersForNewProject(string projectFullPath, BuildEventContext buildEventContext) + { + // Only add analyzers here + // On an execution node - we might remove and dispose the analyzers once project is done + + // If it's already constructed - just control the custom settings do not differ + + List analyzersToRemove = new(); + foreach (BuildAnalyzerFactoryContext analyzerFactoryContext in _analyzersRegistry) + { + try + { + SetupSingleAnalyzer(analyzerFactoryContext, projectFullPath, buildEventContext); + } + catch (BuildCopConfigurationException e) + { + _loggingService.LogErrorFromText(buildEventContext, null, null, null, + new BuildEventFileInfo(projectFullPath), + e.Message); + _loggingService.LogCommentFromText(buildEventContext, MessageImportance.High, $"Dismounting analyzer '{analyzerFactoryContext.FriendlyName}'"); + analyzersToRemove.Add(analyzerFactoryContext); + } + } + + analyzersToRemove.ForEach(c => _analyzersRegistry.Remove(c)); + foreach (var analyzerToRemove in analyzersToRemove.Select(a => a.MaterializedAnalyzer).Where(a => a != null)) + { + _buildCopCentralContext.DeregisterAnalyzer(analyzerToRemove!); + _tracingReporter.AddStats(analyzerToRemove!.BuildAnalyzer.FriendlyName, analyzerToRemove.Elapsed); + analyzerToRemove.BuildAnalyzer.Dispose(); + } + } + + + public void ProcessEvaluationFinishedEventArgs( + IBuildAnalysisLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs evaluationFinishedEventArgs) + => _buildEventsProcessor + .ProcessEvaluationFinishedEventArgs(buildAnalysisContext, evaluationFinishedEventArgs); + + // TODO: tracing: https://github.com/dotnet/msbuild/issues/9629 + public Dictionary CreateTracingStats() + { + foreach (BuildAnalyzerFactoryContext analyzerFactoryContext in _analyzersRegistry) + { + if (analyzerFactoryContext.MaterializedAnalyzer != null) + { + _tracingReporter.AddStats(analyzerFactoryContext.FriendlyName, + analyzerFactoryContext.MaterializedAnalyzer.Elapsed); + analyzerFactoryContext.MaterializedAnalyzer.ClearStats(); + } + } + + return _tracingReporter.TracingStats; + } + + public void FinalizeProcessing(LoggingContext loggingContext) + { + if (IsInProcNode) + { + // We do not want to send tracing stats from in-proc node + return; + } + + BuildCopTracingEventArgs eventArgs = + new(CreateTracingStats()) { BuildEventContext = loggingContext.BuildEventContext }; + loggingContext.LogBuildEvent(eventArgs); + } + + public void StartProjectEvaluation(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext, + string fullPath) + { + if (buildCopDataSource == BuildCopDataSource.EventArgs && IsInProcNode) + { + // Skipping this event - as it was already handled by the in-proc node. + // This is because in-proc node has the BuildEventArgs source and BuildExecution source + // both in a single manager. The project started is first encountered by the execution before the EventArg is sent + return; + } + + SetupAnalyzersForNewProject(fullPath, buildEventContext); + } + + /* + * + * Following methods are for future use (should we decide to approach in-execution analysis) + * + */ + + + public void EndProjectEvaluation(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext) + { + } + + public void StartProjectRequest(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext) + { + } + + public void EndProjectRequest(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext) + { + } + + public void Shutdown() + { /* Too late here for any communication to the main node or for logging anything */ } + + private class BuildAnalyzerFactoryContext( + BuildAnalyzerFactory factory, + string[] ruleIds, + bool isEnabledByDefault) + { + public BuildAnalyzerWrapperFactory Factory { get; init; } = configContext => + { + BuildAnalyzer ba = factory(); + ba.Initialize(configContext); + return new BuildAnalyzerWrapper(ba); + }; + public BuildAnalyzerWrapper? MaterializedAnalyzer { get; set; } + public string[] RuleIds { get; init; } = ruleIds; + public bool IsEnabledByDefault { get; init; } = isEnabledByDefault; + public string FriendlyName => MaterializedAnalyzer?.BuildAnalyzer.FriendlyName ?? factory().FriendlyName; + } + } +} diff --git a/src/Build/BuildCop/Infrastructure/BuildEventsProcessor.cs b/src/Build/BuildCop/Infrastructure/BuildEventsProcessor.cs new file mode 100644 index 00000000000..b3299f9624d --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/BuildEventsProcessor.cs @@ -0,0 +1,88 @@ +// 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.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.IO; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using Microsoft.Build.BackEnd; +using Microsoft.Build.BackEnd.Components.Caching; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Analyzers; +using Microsoft.Build.BuildCop.Logging; +using Microsoft.Build.Collections; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Experimental.BuildCop; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.BuildCop.Infrastructure; + +internal class BuildEventsProcessor(BuildCopCentralContext buildCopCentralContext) +{ + private readonly SimpleProjectRootElementCache _cache = new SimpleProjectRootElementCache(); + private readonly BuildCopCentralContext _buildCopCentralContext = buildCopCentralContext; + + // This requires MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION set to 1 + public void ProcessEvaluationFinishedEventArgs( + IBuildAnalysisLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs evaluationFinishedEventArgs) + { + LoggingContext loggingContext = buildAnalysisContext.ToLoggingContext(); + + Dictionary propertiesLookup = new Dictionary(); + Internal.Utilities.EnumerateProperties(evaluationFinishedEventArgs.Properties, propertiesLookup, + static (dict, kvp) => dict.Add(kvp.Key, kvp.Value)); + + EvaluatedPropertiesAnalysisData analysisData = + new(evaluationFinishedEventArgs.ProjectFile!, propertiesLookup); + + _buildCopCentralContext.RunEvaluatedPropertiesActions(analysisData, loggingContext, ReportResult); + + if (_buildCopCentralContext.HasParsedItemsActions) + { + ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution( + evaluationFinishedEventArgs.ProjectFile!, /*unused*/ + null, /*unused*/null, _cache, false /*Not explicitly loaded - unused*/); + + ParsedItemsAnalysisData itemsAnalysisData = new(evaluationFinishedEventArgs.ProjectFile!, + new ItemsHolder(xml.Items, xml.ItemGroups)); + + _buildCopCentralContext.RunParsedItemsActions(itemsAnalysisData, loggingContext, ReportResult); + } + } + + private static void ReportResult( + BuildAnalyzerWrapper analyzerWrapper, + LoggingContext loggingContext, + BuildAnalyzerConfigurationInternal[] configPerRule, + BuildCopResult result) + { + if (!analyzerWrapper.BuildAnalyzer.SupportedRules.Contains(result.BuildAnalyzerRule)) + { + loggingContext.LogErrorFromText(null, null, null, + BuildEventFileInfo.Empty, + $"The analyzer '{analyzerWrapper.BuildAnalyzer.FriendlyName}' reported a result for a rule '{result.BuildAnalyzerRule.Id}' that it does not support."); + return; + } + + BuildAnalyzerConfigurationInternal config = configPerRule.Length == 1 + ? configPerRule[0] + : configPerRule.First(r => + r.RuleId.Equals(result.BuildAnalyzerRule.Id, StringComparison.CurrentCultureIgnoreCase)); + + if (!config.IsEnabled) + { + return; + } + + BuildEventArgs eventArgs = result.ToEventArgs(config.Severity); + eventArgs.BuildEventContext = loggingContext.BuildEventContext; + loggingContext.LogBuildEvent(eventArgs); + } +} diff --git a/src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs b/src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs index a1fe319f80c..48db5678d98 100644 --- a/src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs +++ b/src/Build/BuildCop/Infrastructure/ConfigurationProvider.cs @@ -10,10 +10,13 @@ using System.Text.Json.Serialization; using System.Text.Json; using Microsoft.Build.Experimental.BuildCop; +using System.Configuration; namespace Microsoft.Build.BuildCop.Infrastructure; + // TODO: https://github.com/dotnet/msbuild/issues/9628 +// Let's flip form statics to instance, with exposed interface (so that we can easily swap implementations) internal static class ConfigurationProvider { // TODO: This module should have a mechanism for removing unneeded configurations @@ -68,44 +71,136 @@ private static Dictionary LoadConfiguration( } /// - /// Gets effective configuration for the given analyzer rule. - /// The configuration values are guaranteed to be non-null upon this merge operation. + /// Gets the user specified unrecognized configuration for the given analyzer rule. + /// + /// The configuration module should as well check that CustomConfigurationData + /// for a particular rule is equal across the whole build (for all projects) - otherwise it should error out. + /// This should apply to all rules for which the configuration is fetched. /// - /// + /// + /// + /// + public static CustomConfigurationData GetCustomConfiguration(string projectFullPath, string ruleId) + { + return CustomConfigurationData.Null; + } + + /// + /// + /// + /// + /// + /// If CustomConfigurationData differs in a build for a same ruleId /// - public static BuildAnalyzerConfigurationInternal GetMergedConfiguration(BuildAnalyzerRule analyzerRule) + public static void CheckCustomConfigurationDataValidity(string projectFullPath, string ruleId) + { + // TBD + } + + public static BuildAnalyzerConfigurationInternal[] GetMergedConfigurations( + string projectFullPath, + BuildAnalyzer analyzer) + => FillConfiguration(projectFullPath, analyzer.SupportedRules, GetMergedConfiguration); + + public static BuildAnalyzerConfiguration[] GetUserConfigurations( + string projectFullPath, + IReadOnlyList ruleIds) + => FillConfiguration(projectFullPath, ruleIds, GetUserConfiguration); + + public static CustomConfigurationData[] GetCustomConfigurations( + string projectFullPath, + IReadOnlyList ruleIds) + => FillConfiguration(projectFullPath, ruleIds, GetCustomConfiguration); + + public static BuildAnalyzerConfigurationInternal[] GetMergedConfigurations( + BuildAnalyzerConfiguration[] userConfigs, + BuildAnalyzer analyzer) { - if (!_editorConfig.TryGetValue(analyzerRule.Id, out BuildAnalyzerConfiguration? editorConfig)) + var configurations = new BuildAnalyzerConfigurationInternal[userConfigs.Length]; + + for (int idx = 0; idx < userConfigs.Length; idx++) { - editorConfig = BuildAnalyzerConfiguration.Null; + configurations[idx] = ConfigurationProvider.MergeConfiguration( + analyzer.SupportedRules[idx].Id, + analyzer.SupportedRules[idx].DefaultConfiguration, + userConfigs[idx]); } - var defaultConfig = analyzerRule.DefaultConfiguration; + return configurations; + } - return new BuildAnalyzerConfigurationInternal() + private static TConfig[] FillConfiguration(string projectFullPath, IReadOnlyList ruleIds, Func configurationProvider) + { + TConfig[] configurations = new TConfig[ruleIds.Count]; + for (int i = 0; i < ruleIds.Count; i++) { - EvaluationAnalysisScope = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.EvaluationAnalysisScope), - IsEnabled = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.IsEnabled), - LifeTimeScope = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.LifeTimeScope), - Severity = GetConfigValue(editorConfig, defaultConfig, cfg => cfg.Severity) - }; - - T GetConfigValue( - BuildAnalyzerConfiguration editorConfigValue, - BuildAnalyzerConfiguration defaultValue, - Func propertyGetter) where T : struct - => propertyGetter(editorConfigValue) ?? - propertyGetter(defaultValue) ?? - EnsureNonNull(propertyGetter(BuildAnalyzerConfiguration.Default)); - - T EnsureNonNull(T? value) where T : struct + configurations[i] = configurationProvider(projectFullPath, ruleIds[i]); + } + + return configurations; + } + + /// + /// Gets effective user specified (or default) configuration for the given analyzer rule. + /// The configuration values CAN be null upon this operation. + /// + /// The configuration module should as well check that BuildAnalyzerConfigurationInternal.EvaluationAnalysisScope + /// for all rules is equal - otherwise it should error out. + /// + /// + /// + /// + public static BuildAnalyzerConfiguration GetUserConfiguration(string projectFullPath, string ruleId) + { + if (!_editorConfig.TryGetValue(ruleId, out BuildAnalyzerConfiguration? editorConfig)) { - if (value is null) - { - throw new InvalidOperationException("Value is null"); - } + editorConfig = BuildAnalyzerConfiguration.Null; + } + + return editorConfig; + } - return value.Value; + /// + /// Gets effective configuration for the given analyzer rule. + /// The configuration values are guaranteed to be non-null upon this merge operation. + /// + /// + /// + /// + public static BuildAnalyzerConfigurationInternal GetMergedConfiguration(string projectFullPath, BuildAnalyzerRule analyzerRule) + => GetMergedConfiguration(projectFullPath, analyzerRule.Id, analyzerRule.DefaultConfiguration); + + public static BuildAnalyzerConfigurationInternal MergeConfiguration( + string ruleId, + BuildAnalyzerConfiguration defaultConfig, + BuildAnalyzerConfiguration editorConfig) + => new BuildAnalyzerConfigurationInternal( + ruleId: ruleId, + evaluationAnalysisScope: GetConfigValue(editorConfig, defaultConfig, cfg => cfg.EvaluationAnalysisScope), + isEnabled: GetConfigValue(editorConfig, defaultConfig, cfg => cfg.IsEnabled), + severity: GetConfigValue(editorConfig, defaultConfig, cfg => cfg.Severity)); + + private static BuildAnalyzerConfigurationInternal GetMergedConfiguration( + string projectFullPath, + string ruleId, + BuildAnalyzerConfiguration defaultConfig) + => MergeConfiguration(ruleId, defaultConfig, GetUserConfiguration(projectFullPath, ruleId)); + + private static T GetConfigValue( + BuildAnalyzerConfiguration editorConfigValue, + BuildAnalyzerConfiguration defaultValue, + Func propertyGetter) where T : struct + => propertyGetter(editorConfigValue) ?? + propertyGetter(defaultValue) ?? + EnsureNonNull(propertyGetter(BuildAnalyzerConfiguration.Default)); + + private static T EnsureNonNull(T? value) where T : struct + { + if (value is null) + { + throw new InvalidOperationException("Value is null"); } + + return value.Value; } } diff --git a/src/Build/BuildCop/Infrastructure/CustomConfigurationData.cs b/src/Build/BuildCop/Infrastructure/CustomConfigurationData.cs new file mode 100644 index 00000000000..75834e1305a --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/CustomConfigurationData.cs @@ -0,0 +1,45 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Build.Experimental.BuildCop; + +public class CustomConfigurationData(string ruleId) +{ + public static CustomConfigurationData Null { get; } = new(string.Empty); + + public static bool NotNull(CustomConfigurationData data) => !Null.Equals(data); + + public string RuleId { get; init; } = ruleId; + public IReadOnlyDictionary? ConfigurationData { get; init; } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((CustomConfigurationData)obj); + } + + protected bool Equals(CustomConfigurationData other) => Equals(ConfigurationData, other.ConfigurationData); + + public override int GetHashCode() => (ConfigurationData != null ? ConfigurationData.GetHashCode() : 0); +} diff --git a/src/Build/BuildCop/Infrastructure/IBuildCopContext.cs b/src/Build/BuildCop/Infrastructure/IBuildCopContext.cs index 22cefa9e976..9ce8c8c65a8 100644 --- a/src/Build/BuildCop/Infrastructure/IBuildCopContext.cs +++ b/src/Build/BuildCop/Infrastructure/IBuildCopContext.cs @@ -1,12 +1,13 @@ // 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 Microsoft.Build.Experimental.BuildCop; namespace Microsoft.Build.BuildCop.Infrastructure; public interface IBuildCopContext { - void RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction evaluatedPropertiesAction); - void RegisterParsedItemsAction(ParsedItemsAction parsedItemsAction); + void RegisterEvaluatedPropertiesAction(Action> evaluatedPropertiesAction); + void RegisterParsedItemsAction(Action> parsedItemsAction); } diff --git a/src/Build/BuildCop/Infrastructure/IBuildCopManager.cs b/src/Build/BuildCop/Infrastructure/IBuildCopManager.cs new file mode 100644 index 00000000000..9d5d8c89f5a --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/IBuildCopManager.cs @@ -0,0 +1,51 @@ +// 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; +using Microsoft.Build.BackEnd; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Acquisition; +using Microsoft.Build.BuildCop.Infrastructure; +using Microsoft.Build.Framework; +using static Microsoft.Build.BuildCop.Infrastructure.BuildCopManagerProvider; + +namespace Microsoft.Build.Experimental.BuildCop; + +internal enum BuildCopDataSource +{ + EventArgs, + BuildExecution, + + ValuesCount = BuildExecution + 1 +} + +internal interface IBuildCopManager +{ + void ProcessEvaluationFinishedEventArgs( + IBuildAnalysisLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs); + + void SetDataSource(BuildCopDataSource buildCopDataSource); + + void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData); + + Dictionary CreateTracingStats(); + + void FinalizeProcessing(LoggingContext loggingContext); + + // All those to be called from RequestBuilder, + // but as well from the ConnectorLogger - as even if interleaved, it gives the info + // to manager about what analyzers need to be materialized and configuration fetched. + // No unloading of analyzers is yet considered - once loaded it stays for whole build. + + void StartProjectEvaluation(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext, string fullPath); + void EndProjectEvaluation(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext); + void StartProjectRequest(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext); + void EndProjectRequest(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext); + + void Shutdown(); +} diff --git a/src/Build/BuildCop/Infrastructure/NullBuildCopManager.cs b/src/Build/BuildCop/Infrastructure/NullBuildCopManager.cs new file mode 100644 index 00000000000..bfcf6e7be2c --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/NullBuildCopManager.cs @@ -0,0 +1,50 @@ +// 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; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Acquisition; +using Microsoft.Build.Experimental.BuildCop; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.BuildCop.Infrastructure; + +internal class NullBuildCopManager : IBuildCopManager +{ + public void Shutdown() { } + + public void ProcessEvaluationFinishedEventArgs(IBuildAnalysisLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs) + { } + + public void SetDataSource(BuildCopDataSource buildCopDataSource) { } + public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) { } + + public Dictionary CreateTracingStats() => throw new NotImplementedException(); + + public void FinalizeProcessing(LoggingContext loggingContext) + { } + + public void StartProjectEvaluation(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext, + string fullPath) + { } + + public void EndProjectEvaluation(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext) + { } + + public void StartProjectRequest(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext) + { } + + public void EndProjectRequest(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext) + { } + + public void YieldProject(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext) + { } + + public void ResumeProject(BuildCopDataSource buildCopDataSource, BuildEventContext buildEventContext) + { } +} diff --git a/src/Build/BuildCop/Infrastructure/TracingReporter.cs b/src/Build/BuildCop/Infrastructure/TracingReporter.cs new file mode 100644 index 00000000000..434ac882d7c --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/TracingReporter.cs @@ -0,0 +1,28 @@ +// 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.BuildCop.Infrastructure; + +internal class TracingReporter +{ + internal const string INFRA_STAT_NAME = "Infrastructure"; + internal Dictionary TracingStats { get; } = new(); + + public void AddStats(string name, TimeSpan subtotal) + { + if (TracingStats.TryGetValue(name, out TimeSpan existing)) + { + TracingStats[name] = existing + subtotal; + } + else + { + TracingStats[name] = subtotal; + } + } +} diff --git a/src/Framework/BuildCop/IBuildAnalysisLoggingContext.cs b/src/Build/BuildCop/Logging/IBuildAnalysisLoggingContext.cs similarity index 100% rename from src/Framework/BuildCop/IBuildAnalysisLoggingContext.cs rename to src/Build/BuildCop/Logging/IBuildAnalysisLoggingContext.cs diff --git a/src/Framework/BuildCop/IBuildAnalysisLoggingContextFactory.cs b/src/Build/BuildCop/Logging/IBuildAnalysisLoggingContextFactory.cs similarity index 100% rename from src/Framework/BuildCop/IBuildAnalysisLoggingContextFactory.cs rename to src/Build/BuildCop/Logging/IBuildAnalysisLoggingContextFactory.cs diff --git a/src/Build/BuildCop/OM/BuildAnalysisContext.cs b/src/Build/BuildCop/OM/BuildAnalysisContext.cs new file mode 100644 index 00000000000..f6b85906a53 --- /dev/null +++ b/src/Build/BuildCop/OM/BuildAnalysisContext.cs @@ -0,0 +1,60 @@ +// 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; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCop.Infrastructure; +using Microsoft.Build.Experimental; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Experimental.BuildCop; + +/// +/// Base for a data passed from infrastructure to build analyzers. +/// +/// Currently build project. +public abstract class AnalysisData(string projectFilePath) +{ + /// + /// Full path to the project file being built. + /// + public string ProjectFilePath { get; } = projectFilePath; +} + +public class BuildAnalysisContext where T : AnalysisData +{ + private readonly BuildAnalyzerWrapper _analyzerWrapper; + private readonly LoggingContext _loggingContext; + private readonly BuildAnalyzerConfigurationInternal[] _configPerRule; + private readonly Action _resultHandler; + + internal BuildAnalysisContext( + BuildAnalyzerWrapper analyzerWrapper, + LoggingContext loggingContext, + BuildAnalyzerConfigurationInternal[] configPerRule, + Action resultHandler, + T data) + { + _analyzerWrapper = analyzerWrapper; + _loggingContext = loggingContext; + _configPerRule = configPerRule; + _resultHandler = resultHandler; + Data = data; + } + + /// + /// Method for reporting the result of the build analyzer rule. + /// + /// + public void ReportResult(BuildCopResult result) + => _resultHandler(_analyzerWrapper, _loggingContext, _configPerRule, result); + + /// + /// Data to be analyzed. + /// + public T Data { get; } +} diff --git a/src/Build/BuildCop/OM/EvaluatedPropertiesAnalysisData.cs b/src/Build/BuildCop/OM/EvaluatedPropertiesAnalysisData.cs new file mode 100644 index 00000000000..fda8d3ffdf6 --- /dev/null +++ b/src/Build/BuildCop/OM/EvaluatedPropertiesAnalysisData.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.Build.BackEnd.Logging; + +namespace Microsoft.Build.Experimental.BuildCop; +public class EvaluatedPropertiesAnalysisData : AnalysisData +{ + internal EvaluatedPropertiesAnalysisData( + string projectFilePath, + IReadOnlyDictionary evaluatedProperties) : + base(projectFilePath) => EvaluatedProperties = evaluatedProperties; + + public IReadOnlyDictionary EvaluatedProperties { get; } +} diff --git a/src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs b/src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs deleted file mode 100644 index d219e825c2e..00000000000 --- a/src/Build/BuildCop/OM/EvaluatedPropertiesContext.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Microsoft.Build.BackEnd.Logging; - -namespace Microsoft.Build.Experimental.BuildCop; -public class EvaluatedPropertiesContext : BuildAnalysisContext -{ - internal EvaluatedPropertiesContext( - LoggingContext loggingContext, - IReadOnlyDictionary evaluatedProperties, - string projectFilePath) : - base(loggingContext) => (EvaluatedProperties, ProjectFilePath) = - (evaluatedProperties, projectFilePath); - - public IReadOnlyDictionary EvaluatedProperties { get; } - - public string ProjectFilePath { get; } -} diff --git a/src/Build/BuildCop/OM/ParsedItemsContext.cs b/src/Build/BuildCop/OM/ParsedItemsAnalysisData.cs similarity index 86% rename from src/Build/BuildCop/OM/ParsedItemsContext.cs rename to src/Build/BuildCop/OM/ParsedItemsAnalysisData.cs index e65a176f3ca..043491ee4f1 100644 --- a/src/Build/BuildCop/OM/ParsedItemsContext.cs +++ b/src/Build/BuildCop/OM/ParsedItemsAnalysisData.cs @@ -32,12 +32,12 @@ public IEnumerable GetItemsOfType(string itemType) } } -public class ParsedItemsContext : BuildAnalysisContext +public class ParsedItemsAnalysisData : AnalysisData { - internal ParsedItemsContext( - LoggingContext loggingContext, + internal ParsedItemsAnalysisData( + string projectFilePath, ItemsHolder itemsHolder) : - base(loggingContext) => ItemsHolder = itemsHolder; + base(projectFilePath) => ItemsHolder = itemsHolder; public ItemsHolder ItemsHolder { get; } } diff --git a/src/Build/BuildCop/Utilities/EnumerableExtensions.cs b/src/Build/BuildCop/Utilities/EnumerableExtensions.cs new file mode 100644 index 00000000000..70b95b22f51 --- /dev/null +++ b/src/Build/BuildCop/Utilities/EnumerableExtensions.cs @@ -0,0 +1,65 @@ +// 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.Experimental.BuildCop; + +internal static class EnumerableExtensions +{ + /// + /// Concatenates items of input sequence into csv string. + /// + /// + /// Sequence to be turned into csv string. + /// Indicates whether space should be inserted between comas and following items. + /// Csv string. + public static string ToCsvString(this IEnumerable? source, bool useSpace = true) + { + return source == null ? "" : string.Join("," + (useSpace ? " " : string.Empty), source); + } + + /// + /// Performs an action for each element in given sequence. + /// + /// + /// + /// + public static void ForEach(this IEnumerable sequence, Action action) + { + foreach (T element in sequence) + { + action(element); + } + } + + /// + /// Adds a content of given dictionary to current dictionary. + /// + /// + /// + /// Dictionary to receive another values. + /// Dictionary to be merged into current. + /// Way of resolving keys conflicts. + public static void Merge( + this IDictionary dict, + IReadOnlyDictionary another, + Func mergeValues) + { + foreach (var pair in another) + { + if (!dict.ContainsKey(pair.Key)) + { + dict[pair.Key] = pair.Value; + } + else + { + dict[pair.Key] = mergeValues(dict[pair.Key], pair.Value); + } + } + } +} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 5c9fec8bc7b..024368af5f9 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -153,28 +153,36 @@ + + - + + + + + - + - - - - + - + + - - + + + + + + diff --git a/src/Framework/BuildCop/BuildCopEventArgs.cs b/src/Framework/BuildCop/BuildCopEventArgs.cs new file mode 100644 index 00000000000..d7051e2689a --- /dev/null +++ b/src/Framework/BuildCop/BuildCopEventArgs.cs @@ -0,0 +1,150 @@ +// 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 System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.Experimental.BuildCop; + +public abstract class BuildCopEventArgs : BuildEventArgs +{ } + +public sealed class BuildCopTracingEventArgs(Dictionary tracingData) : BuildCopEventArgs +{ + internal BuildCopTracingEventArgs() : this(new Dictionary()) + { } + + public Dictionary TracingData { get; private set; } = tracingData; + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + + writer.Write7BitEncodedInt(TracingData.Count); + foreach (KeyValuePair kvp in TracingData) + { + writer.Write(kvp.Key); + writer.Write(kvp.Value.Ticks); + } + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + + int count = reader.Read7BitEncodedInt(); + TracingData = new Dictionary(count); + for (int i = 0; i < count; i++) + { + string key = reader.ReadString(); + TimeSpan value = TimeSpan.FromTicks(reader.ReadInt64()); + + TracingData.Add(key, value); + } + } +} + +public sealed class BuildCopAcquisitionEventArgs(string acquisitionData) : BuildCopEventArgs +{ + internal BuildCopAcquisitionEventArgs() : this(string.Empty) + { } + + public string AcquisitionData { get; private set; } = acquisitionData; + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + + writer.Write(AcquisitionData); + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + + AcquisitionData = reader.ReadString(); + } +} +public sealed class BuildCopResultWarning : BuildWarningEventArgs +{ + public BuildCopResultWarning(IBuildCopResult result) + { + this.Message = result.FormatMessage(); + } + + internal BuildCopResultWarning() { } + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + + writer.Write(Message!); + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + + Message = reader.ReadString(); + } + + public override string? Message { get; protected set; } +} + +public sealed class BuildCopResultError : BuildErrorEventArgs +{ + public BuildCopResultError(IBuildCopResult result) + { + this.Message = result.FormatMessage(); + } + + internal BuildCopResultError() { } + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + + writer.Write(Message!); + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + + Message = reader.ReadString(); + } + + public override string? Message { get; protected set; } +} + +public sealed class BuildCopResultMessage : BuildMessageEventArgs +{ + public BuildCopResultMessage(IBuildCopResult result) + { + this.Message = result.FormatMessage(); + } + + internal BuildCopResultMessage() { } + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + + writer.Write(Message!); + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + + Message = reader.ReadString(); + } + + public override string? Message { get; protected set; } +} diff --git a/src/Framework/BuildCop/IBuildCopLoggerFactory.cs b/src/Framework/BuildCop/IBuildCopLoggerFactory.cs deleted file mode 100644 index 2fb4e2d0ffb..00000000000 --- a/src/Framework/BuildCop/IBuildCopLoggerFactory.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Build.Framework; - -namespace Microsoft.Build.Experimental.BuildCop; - -public interface IBuildCopLoggerFactory -{ - ILogger CreateBuildAnalysisLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory); -} diff --git a/src/Framework/BuildCop/IBuildCopManager.cs b/src/Framework/BuildCop/IBuildCopManager.cs deleted file mode 100644 index 30e076b2395..00000000000 --- a/src/Framework/BuildCop/IBuildCopManager.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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; -using Microsoft.Build.Framework; - -namespace Microsoft.Build.Experimental.BuildCop; - -public interface IBuildCopManager -{ - void ProcessEvaluationFinishedEventArgs( - IBuildAnalysisLoggingContext buildAnalysisContext, - ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs); - - string CreateTracingStats(); -} diff --git a/src/Framework/BuildCop/IBuildCopResult.cs b/src/Framework/BuildCop/IBuildCopResult.cs new file mode 100644 index 00000000000..c47e03c5365 --- /dev/null +++ b/src/Framework/BuildCop/IBuildCopResult.cs @@ -0,0 +1,25 @@ +// 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.Experimental.BuildCop; + +/// +/// Holder for the reported result of a build cop rule. +/// +public interface IBuildCopResult +{ + /// + /// Optional location of the finding (in near future we might need to support multiple locations). + /// + string LocationString { get; } + string[] MessageArgs { get; } + string MessageFormat { get; } + + string FormatMessage(); +} diff --git a/src/Framework/IEventSource.cs b/src/Framework/IEventSource.cs index 187c105d386..18be5acb2c0 100644 --- a/src/Framework/IEventSource.cs +++ b/src/Framework/IEventSource.cs @@ -3,6 +3,8 @@ #nullable disable +using Microsoft.Build.Experimental.BuildCop; + namespace Microsoft.Build.Framework { /// @@ -75,6 +77,11 @@ namespace Microsoft.Build.Framework /// public delegate void AnyEventHandler(object sender, BuildEventArgs e); + /// + /// Type of handler for BuildCopEventRaised events + /// + public delegate void BuildCopEventHandler(object sender, BuildCopEventArgs e); + /// /// This interface defines the events raised by the build engine. /// Loggers use this interface to subscribe to the events they diff --git a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs index 35d2241c072..df213d22248 100644 --- a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs +++ b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs @@ -1185,7 +1185,7 @@ public void InvalidToolsVersionErrors() graphBuildOptions: null, lowPriority: false, question: false, - buildCopLoggerFactory: null, + isBuildCopEnabled: false, inputResultsCaches: null, outputResultsCache: null, saveProjectResult: false, diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 433d06e9011..e3a99e1eff0 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -25,7 +25,6 @@ using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; using Microsoft.Build.Experimental; -using Microsoft.Build.Experimental.BuildCop; using Microsoft.Build.Experimental.ProjectCache; using Microsoft.Build.Framework; using Microsoft.Build.Framework.Telemetry; @@ -718,7 +717,7 @@ public static ExitType Execute( string[] inputResultsCaches = null; string outputResultsCache = null; bool question = false; - IBuildCopLoggerFactory buildCopLoggerFactory = null; + bool isBuildCopEnabled = false; string[] getProperty = Array.Empty(); string[] getItem = Array.Empty(); string[] getTargetResult = Array.Empty(); @@ -765,7 +764,7 @@ public static ExitType Execute( #endif ref lowPriority, ref question, - ref buildCopLoggerFactory, + ref isBuildCopEnabled, ref getProperty, ref getItem, ref getTargetResult, @@ -866,7 +865,7 @@ public static ExitType Execute( graphBuildOptions, lowPriority, question, - buildCopLoggerFactory, + isBuildCopEnabled, inputResultsCaches, outputResultsCache, saveProjectResult: outputPropertiesItemsOrTargetResults, @@ -1248,7 +1247,7 @@ internal static bool BuildProject( GraphBuildOptions graphBuildOptions, bool lowPriority, bool question, - IBuildCopLoggerFactory buildCopLoggerFactory, + bool isBuildCopEnabled, string[] inputResultsCaches, string outputResultsCache, bool saveProjectResult, @@ -1450,7 +1449,7 @@ internal static bool BuildProject( parameters.InputResultsCacheFiles = inputResultsCaches; parameters.OutputResultsCacheFile = outputResultsCache; parameters.Question = question; - parameters.BuildCopLoggerFactory = buildCopLoggerFactory; + parameters.IsBuildCopEnabled = isBuildCopEnabled; #if FEATURE_REPORTFILEACCESSES parameters.ReportFileAccesses = reportFileAccesses; #endif @@ -2422,7 +2421,7 @@ private static bool ProcessCommandLineSwitches( #endif ref bool lowPriority, ref bool question, - ref IBuildCopLoggerFactory buildCopLoggerFactory, + ref bool isBuildCopEnabled, ref string[] getProperty, ref string[] getItem, ref string[] getTargetResult, @@ -2564,7 +2563,7 @@ private static bool ProcessCommandLineSwitches( #endif ref lowPriority, ref question, - ref buildCopLoggerFactory, + ref isBuildCopEnabled, ref getProperty, ref getItem, ref getTargetResult, @@ -2646,7 +2645,7 @@ private static bool ProcessCommandLineSwitches( question = commandLineSwitches.IsParameterizedSwitchSet(CommandLineSwitches.ParameterizedSwitch.Question); - buildCopLoggerFactory = ProcessBuildAnalysisLoggerFactorySwitch(commandLineSwitches); + isBuildCopEnabled = IsBuildCopEnabled(commandLineSwitches); inputResultsCaches = ProcessInputResultsCaches(commandLineSwitches); @@ -2722,11 +2721,11 @@ private static bool ProcessCommandLineSwitches( return invokeBuild; } - private static IBuildCopLoggerFactory ProcessBuildAnalysisLoggerFactorySwitch(CommandLineSwitches commandLineSwitches) + private static bool IsBuildCopEnabled(CommandLineSwitches commandLineSwitches) { // todo: opt-in behavior: https://github.com/dotnet/msbuild/issues/9723 bool isAnalysisEnabled = commandLineSwitches.IsParameterizedSwitchSet(CommandLineSwitches.ParameterizedSwitch.Analyze); - return isAnalysisEnabled ? new BuildCopLoggerFactory() : null; + return isAnalysisEnabled; } private static bool ProcessTerminalLoggerConfiguration(CommandLineSwitches commandLineSwitches, out string aggregatedParameters) diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index 92cc46106f0..5c5748939c9 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -9,6 +9,11 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; +#if !TASKHOST +using Microsoft.Build.Experimental.BuildCop; +using Microsoft.Build.BuildCop.Infrastructure; +#endif + #if !TASKHOST && !MSBUILDENTRYPOINTEXE using Microsoft.Build.Collections; using Microsoft.Build.Framework.Profiler; @@ -205,6 +210,31 @@ internal enum LoggingEventType : int /// Event is /// ExtendedCriticalBuildMessageEvent = 33, + + /// + /// Event is + /// + BuildCopMessageEvent = 34, + + /// + /// Event is + /// + BuildCopWarningEvent = 35, + + /// + /// Event is + /// + BuildCopErrorEvent = 36, + + /// + /// Event is + /// + BuildCopTracingEvent = 37, + + /// + /// Event is + /// + BuildCopAcquisitionEvent = 38, } #endregion @@ -610,6 +640,11 @@ private BuildEventArgs GetBuildEventArgFromId() LoggingEventType.PropertyInitialValueSet => new PropertyInitialValueSetEventArgs(), LoggingEventType.PropertyReassignment => new PropertyReassignmentEventArgs(), LoggingEventType.UninitializedPropertyRead => new UninitializedPropertyReadEventArgs(), + LoggingEventType.BuildCopMessageEvent => new BuildCopResultMessage(), + LoggingEventType.BuildCopWarningEvent => new BuildCopResultWarning(), + LoggingEventType.BuildCopErrorEvent => new BuildCopResultError(), + LoggingEventType.BuildCopAcquisitionEvent => new BuildCopAcquisitionEventArgs(), + LoggingEventType.BuildCopTracingEvent => new BuildCopTracingEventArgs(), #endif _ => throw new InternalErrorException("Should not get to the default of GetBuildEventArgFromId ID: " + _eventType) }; @@ -721,6 +756,26 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.UninitializedPropertyRead; } + else if (eventType == typeof(BuildCopResultMessage)) + { + return LoggingEventType.BuildCopMessageEvent; + } + else if (eventType == typeof(BuildCopResultWarning)) + { + return LoggingEventType.BuildCopWarningEvent; + } + else if (eventType == typeof(BuildCopResultError)) + { + return LoggingEventType.BuildCopErrorEvent; + } + else if (eventType == typeof(BuildCopAcquisitionEventArgs)) + { + return LoggingEventType.BuildCopAcquisitionEvent; + } + else if (eventType == typeof(BuildCopTracingEventArgs)) + { + return LoggingEventType.BuildCopTracingEvent; + } #endif else if (eventType == typeof(TargetStartedEventArgs)) { From 0467261a12db78679974ba354e1650e4bd0c385b Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Wed, 28 Feb 2024 17:52:17 +0100 Subject: [PATCH 18/58] Add more comments --- .../BackEnd/BuildManager/BuildParameters.cs | 2 +- .../BuildComponentFactoryCollection.cs | 1 - .../RequestBuilder/RequestBuilder.cs | 13 +++---- .../Shared/BuildRequestConfiguration.cs | 2 +- src/Build/BuildCop/API/BuildAnalyzer.cs | 25 +++++++++++++ .../API/BuildAnalyzerResultSeverity.cs | 3 ++ src/Build/BuildCop/API/BuildAnalyzerRule.cs | 36 ++++++++++++++++++- .../Analyzers/SharedOutputPathAnalyzer.cs | 7 +--- 8 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index 43683f7e658..952133d7ee2 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -840,7 +840,7 @@ public bool Question } /// - /// Gets or sets a factory for build analysis infrastructure logger + /// Gets or sets an indication of build analysis enablement. /// public bool IsBuildCopEnabled { diff --git a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs index 16a51cfb086..930225a0c9f 100644 --- a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs +++ b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; using Microsoft.Build.BackEnd.Components.Caching; using Microsoft.Build.BackEnd.SdkResolution; using Microsoft.Build.BuildCop.Infrastructure; diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index d1e4318702f..e3bd9f69f58 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1147,11 +1147,6 @@ private async Task BuildProject() RequestEntry.Request.BuildRequestDataFlags, RequestEntry.Request.SubmissionId, _nodeLoggingContext.BuildEventContext.NodeId); - - // todo: in a using scope (autocleanup) - buildCopManager.EndProjectEvaluation( - BuildCopDataSource.BuildExecution, - _requestEntry.Request.ParentBuildEventContext); } } catch @@ -1165,6 +1160,12 @@ private async Task BuildProject() throw; } + finally + { + buildCopManager.EndProjectEvaluation( + BuildCopDataSource.BuildExecution, + _requestEntry.Request.ParentBuildEventContext); + } _projectLoggingContext = _nodeLoggingContext.LogProjectStarted(_requestEntry); buildCopManager.StartProjectRequest( @@ -1222,7 +1223,7 @@ private async Task BuildProject() MSBuildEventSource.Log.BuildProjectStop(_requestEntry.RequestConfiguration.ProjectFullPath, string.Join(", ", allTargets)); } - buildCopManager.EndProjectEvaluation( + buildCopManager.EndProjectRequest( BuildCopDataSource.BuildExecution, _requestEntry.Request.ParentBuildEventContext); diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index 75abeacda63..d821a43951f 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -476,7 +476,7 @@ internal void LoadProjectIntoConfiguration( projectLoadSettings |= ProjectLoadSettings.FailOnUnresolvedSdk; } - // Here - is we'll have in-execution analysis and it'll need DOM from Project, + // Here - if we'll have in-execution analysis and it'll need DOM from Project, // this is the place for Project creation. return new ProjectInstance( diff --git a/src/Build/BuildCop/API/BuildAnalyzer.cs b/src/Build/BuildCop/API/BuildAnalyzer.cs index 1eac8835fa3..261054e14a4 100644 --- a/src/Build/BuildCop/API/BuildAnalyzer.cs +++ b/src/Build/BuildCop/API/BuildAnalyzer.cs @@ -8,12 +8,37 @@ namespace Microsoft.Build.Experimental.BuildCop; +/// +/// Base class for build analyzers. +/// Same base will be used for custom and built-in analyzers. +/// is a unit of build analysis execution. But it can contain multiple rules - each representing a distinct violation. +/// public abstract class BuildAnalyzer : IDisposable { + /// + /// Friendly name of the analyzer. + /// Should be unique - as it will be used in the tracing stats, infrastructure error messages, etc. + /// public abstract string FriendlyName { get; } + + /// + /// Single or multiple rules supported by the analyzer. + /// public abstract IReadOnlyList SupportedRules { get; } + + /// + /// Optional initialization of the analyzer. + /// + /// + /// Custom data (not recognized by the infrastructure) passed from .editorconfig + /// Currently the custom data has to be identical for all rules in the analyzer and all projects. + /// public abstract void Initialize(ConfigurationContext configurationContext); + /// + /// + /// + /// public abstract void RegisterActions(IBuildCopContext context); public virtual void Dispose() diff --git a/src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs b/src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs index 345c0bfbcd6..955b91f52ad 100644 --- a/src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs +++ b/src/Build/BuildCop/API/BuildAnalyzerResultSeverity.cs @@ -3,6 +3,9 @@ namespace Microsoft.Build.Experimental.BuildCop; +/// +/// The severity of reported result (or preconfigured or user configured severity for a rule). +/// public enum BuildAnalyzerResultSeverity { Info, diff --git a/src/Build/BuildCop/API/BuildAnalyzerRule.cs b/src/Build/BuildCop/API/BuildAnalyzerRule.cs index 991bee27edb..25d34e08a6b 100644 --- a/src/Build/BuildCop/API/BuildAnalyzerRule.cs +++ b/src/Build/BuildCop/API/BuildAnalyzerRule.cs @@ -3,6 +3,11 @@ namespace Microsoft.Build.Experimental.BuildCop; +/// +/// Represents a rule that is a unit of build analysis. +/// is a unit of executing the analysis, but it can be discovering multiple distinct violation types. +/// for this reason a single can expose s. +/// public class BuildAnalyzerRule { public BuildAnalyzerRule(string id, string title, string description, string category, string messageFormat, @@ -16,12 +21,41 @@ public BuildAnalyzerRule(string id, string title, string description, string cat DefaultConfiguration = defaultConfiguration; } + /// + /// The identification of the rule. + /// + /// Some background on ids: + /// * https://github.com/dotnet/roslyn-analyzers/blob/main/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt + /// * https://github.com/dotnet/roslyn/issues/40351 + /// + /// Quick suggestion now - let's force external ids to start with 'X', for ours - avoid 'MSB' + /// maybe - BT - build static/styling; BA - build authoring; BE - build execution/environment; BC - build configuration + /// public string Id { get; } + + /// + /// The descriptive short summary of the rule. + /// public string Title { get; } + + /// + /// More detailed description of the violation the rule can be reporting (with possible suggestions). + /// public string Description { get; } - // or maybe enum? eval, syntax, etc + /// + /// TODO: We might turn this into enum, or just remove this. + /// public string Category { get; } + + /// + /// Message format that will be used by the actual findings () - those will just supply the actual arguments. + /// public string MessageFormat { get; } + + /// + /// The default configuration - overridable by the user via .editorconfig. + /// If no user specified configuration is provided, this default will be used. + /// public BuildAnalyzerConfiguration DefaultConfiguration { get; } } diff --git a/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs b/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs index fcb39a80dfc..59eb865169b 100644 --- a/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs +++ b/src/Build/BuildCop/Analyzers/SharedOutputPathAnalyzer.cs @@ -12,12 +12,7 @@ namespace Microsoft.Build.BuildCop.Analyzers; -// Some background on ids: -// * https://github.com/dotnet/roslyn-analyzers/blob/main/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt -// * https://github.com/dotnet/roslyn/issues/40351 -// -// quick suggestion now - let's force external ids to start with 'X', for ours - avoid 'MSB' -// maybe - BT - build static/styling; BA - build authoring; BE - build execution/environment; BC - build configuration + internal sealed class SharedOutputPathAnalyzer : BuildAnalyzer { From 5f9d94192a42441942c443ba43bbefa49ec9b06a Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 29 Feb 2024 12:13:30 +0100 Subject: [PATCH 19/58] Grace handle double initialization attempts --- src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs b/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs index c826f3854bc..ada79d28df3 100644 --- a/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs +++ b/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs @@ -47,7 +47,9 @@ public void InitializeComponent(IBuildComponentHost host) if (s_isInitialized) { - throw new InvalidOperationException("BuildCopManagerProvider is already initialized"); + // TODO: change to interlocked + return; + // throw new InvalidOperationException("BuildCopManagerProvider is already initialized"); } s_isInitialized = true; From 40dd6e01abf4fb873c7a514af55f259a3db6798f Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 29 Feb 2024 14:16:41 +0100 Subject: [PATCH 20/58] Fix tests --- src/Build.UnitTests/BackEnd/MockHost.cs | 7 +++++++ .../BackEnd/BuildManager/BuildManager.cs | 8 ++++---- .../BuildRequestEngine/BuildRequestEngine.cs | 2 +- .../RequestBuilder/RequestBuilder.cs | 2 +- .../Infrastructure/BuildCopManagerProvider.cs | 18 +++++++++-------- .../Infrastructure/IBuildCopManager.cs | 4 +++- .../IBuildCopManagerProvider.cs | 17 ++++++++++++++++ .../NullBuildCopManagerProvider.cs | 20 +++++++++++++++++++ src/Build/Microsoft.Build.csproj | 2 ++ 9 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 src/Build/BuildCop/Infrastructure/IBuildCopManagerProvider.cs create mode 100644 src/Build/BuildCop/Infrastructure/NullBuildCopManagerProvider.cs diff --git a/src/Build.UnitTests/BackEnd/MockHost.cs b/src/Build.UnitTests/BackEnd/MockHost.cs index 4b8f3b07286..649c7488669 100644 --- a/src/Build.UnitTests/BackEnd/MockHost.cs +++ b/src/Build.UnitTests/BackEnd/MockHost.cs @@ -5,6 +5,7 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BackEnd.SdkResolution; +using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.Engine.UnitTests.BackEnd; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; @@ -61,6 +62,8 @@ internal sealed class MockHost : MockLoggingService, IBuildComponentHost, IBuild private ISdkResolverService _sdkResolverService; + private IBuildCopManagerProvider _buildCopManagerProvider; + #region SystemParameterFields #endregion; @@ -126,6 +129,9 @@ public MockHost(BuildParameters buildParameters, ConfigCache overrideConfigCache _sdkResolverService = new MockSdkResolverService(); ((IBuildComponent)_sdkResolverService).InitializeComponent(this); + + _buildCopManagerProvider = new NullBuildCopManagerProvider(); + ((IBuildComponent)_buildCopManagerProvider).InitializeComponent(this); } /// @@ -194,6 +200,7 @@ public IBuildComponent GetComponent(BuildComponentType type) BuildComponentType.ResultsCache => (IBuildComponent)_resultsCache, BuildComponentType.RequestBuilder => (IBuildComponent)_requestBuilder, BuildComponentType.SdkResolverService => (IBuildComponent)_sdkResolverService, + BuildComponentType.BuildCop => (IBuildComponent)_buildCopManagerProvider, _ => throw new ArgumentException("Unexpected type " + type), }; } diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index aa8310d26a7..135e311eb8f 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2975,12 +2975,12 @@ private ILoggingService CreateLoggingService( loggingService.WarningsNotAsErrors = warningsNotAsErrors; loggingService.WarningsAsMessages = warningsAsMessages; - var buildCopManagerProvider = - ((IBuildComponentHost)this).GetComponent(BuildComponentType.BuildCop) as BuildCopManagerProvider; - buildCopManagerProvider!.Instance.SetDataSource(BuildCopDataSource.EventArgs); - if (((IBuildComponentHost)this).BuildParameters.IsBuildCopEnabled) { + var buildCopManagerProvider = + ((IBuildComponentHost)this).GetComponent(BuildComponentType.BuildCop) as IBuildCopManagerProvider; + buildCopManagerProvider!.Instance.SetDataSource(BuildCopDataSource.EventArgs); + loggers = (loggers ?? Enumerable.Empty()).Concat(new[] { new BuildCopConnectorLogger(new AnalyzerLoggingContextFactory(loggingService), buildCopManagerProvider.Instance) diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index 6b880af201a..4932f0c2293 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -283,7 +283,7 @@ public void CleanupForBuild() throw new AggregateException(deactivateExceptions); } - var buildCopManager = (_componentHost.GetComponent(BuildComponentType.BuildCop) as BuildCopManagerProvider)!.Instance; + var buildCopManager = (_componentHost.GetComponent(BuildComponentType.BuildCop) as IBuildCopManagerProvider)!.Instance; buildCopManager.FinalizeProcessing(_nodeLoggingContext); }, isLastTask: true); diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index e3bd9f69f58..ce2f4a1e79e 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1120,7 +1120,7 @@ private async Task BuildProject() { // We consider this the entrypoint for the project build for purposes of BuildCop processing - var buildCopManager = (_componentHost.GetComponent(BuildComponentType.BuildCop) as BuildCopManagerProvider)!.Instance; + var buildCopManager = (_componentHost.GetComponent(BuildComponentType.BuildCop) as IBuildCopManagerProvider)!.Instance; buildCopManager.SetDataSource(BuildCopDataSource.BuildExecution); ErrorUtilities.VerifyThrow(_targetBuilder != null, "Target builder is null"); diff --git a/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs b/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs index ada79d28df3..67c7027e61e 100644 --- a/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs +++ b/src/Build/BuildCop/Infrastructure/BuildCopManagerProvider.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Runtime.ConstrainedExecution; +using System.Threading; using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Components.Caching; using Microsoft.Build.BackEnd.Logging; @@ -27,13 +28,16 @@ namespace Microsoft.Build.BuildCop.Infrastructure; internal delegate BuildAnalyzer BuildAnalyzerFactory(); internal delegate BuildAnalyzerWrapper BuildAnalyzerWrapperFactory(ConfigurationContext configurationContext); -internal sealed class BuildCopManagerProvider : IBuildComponent +/// +/// The central manager for the BuildCop - this is the integration point with MSBuild infrastructure. +/// +internal sealed class BuildCopManagerProvider : IBuildCopManagerProvider { - private static bool s_isInitialized = false; + private static int s_isInitialized = 0; private static IBuildCopManager s_globalInstance = new NullBuildCopManager(); - internal static IBuildCopManager GlobalInstance => s_isInitialized ? s_globalInstance : throw new InvalidOperationException("BuildCopManagerProvider not initialized"); + internal static IBuildCopManager GlobalInstance => s_isInitialized != 0 ? s_globalInstance : throw new InvalidOperationException("BuildCopManagerProvider not initialized"); - internal IBuildCopManager Instance => GlobalInstance; + public IBuildCopManager Instance => GlobalInstance; internal static IBuildComponent CreateComponent(BuildComponentType type) { @@ -45,13 +49,11 @@ public void InitializeComponent(IBuildComponentHost host) { ErrorUtilities.VerifyThrow(host != null, "BuildComponentHost was null"); - if (s_isInitialized) + if (Interlocked.CompareExchange(ref s_isInitialized, 1, 0) == 1) { - // TODO: change to interlocked + // Already initialized return; - // throw new InvalidOperationException("BuildCopManagerProvider is already initialized"); } - s_isInitialized = true; if (host!.BuildParameters.IsBuildCopEnabled) { diff --git a/src/Build/BuildCop/Infrastructure/IBuildCopManager.cs b/src/Build/BuildCop/Infrastructure/IBuildCopManager.cs index 9d5d8c89f5a..2082c82f286 100644 --- a/src/Build/BuildCop/Infrastructure/IBuildCopManager.cs +++ b/src/Build/BuildCop/Infrastructure/IBuildCopManager.cs @@ -11,7 +11,6 @@ using Microsoft.Build.BuildCop.Acquisition; using Microsoft.Build.BuildCop.Infrastructure; using Microsoft.Build.Framework; -using static Microsoft.Build.BuildCop.Infrastructure.BuildCopManagerProvider; namespace Microsoft.Build.Experimental.BuildCop; @@ -23,6 +22,9 @@ internal enum BuildCopDataSource ValuesCount = BuildExecution + 1 } +/// +/// The central manager for the BuildCop - this is the integration point with MSBuild infrastructure. +/// internal interface IBuildCopManager { void ProcessEvaluationFinishedEventArgs( diff --git a/src/Build/BuildCop/Infrastructure/IBuildCopManagerProvider.cs b/src/Build/BuildCop/Infrastructure/IBuildCopManagerProvider.cs new file mode 100644 index 00000000000..a58bc5ae321 --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/IBuildCopManagerProvider.cs @@ -0,0 +1,17 @@ +// 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; +using Microsoft.Build.BackEnd; +using Microsoft.Build.Experimental.BuildCop; + +namespace Microsoft.Build.BuildCop.Infrastructure; + +internal interface IBuildCopManagerProvider : IBuildComponent +{ + IBuildCopManager Instance { get; } +} diff --git a/src/Build/BuildCop/Infrastructure/NullBuildCopManagerProvider.cs b/src/Build/BuildCop/Infrastructure/NullBuildCopManagerProvider.cs new file mode 100644 index 00000000000..3aaf8708347 --- /dev/null +++ b/src/Build/BuildCop/Infrastructure/NullBuildCopManagerProvider.cs @@ -0,0 +1,20 @@ +// 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; +using Microsoft.Build.BackEnd; +using Microsoft.Build.Experimental.BuildCop; + +namespace Microsoft.Build.BuildCop.Infrastructure; + +internal class NullBuildCopManagerProvider : IBuildCopManagerProvider +{ + public IBuildCopManager Instance { get; } = new NullBuildCopManager(); + + public void InitializeComponent(IBuildComponentHost host) { } + public void ShutdownComponent() { } +} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 024368af5f9..3eb6f37c3a1 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -158,7 +158,9 @@ + + From bddef4cd1f7c6b0e3b772437840ade284f9466f5 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 29 Feb 2024 18:45:21 +0100 Subject: [PATCH 21/58] Troubleshoot test, comment --- .../Infrastructure/CustomConfigurationData.cs | 19 +++++++++++++++++++ src/Tasks.UnitTests/Exec_Tests.cs | 6 +++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Build/BuildCop/Infrastructure/CustomConfigurationData.cs b/src/Build/BuildCop/Infrastructure/CustomConfigurationData.cs index 75834e1305a..f6ecf0b91cf 100644 --- a/src/Build/BuildCop/Infrastructure/CustomConfigurationData.cs +++ b/src/Build/BuildCop/Infrastructure/CustomConfigurationData.cs @@ -10,13 +10,32 @@ namespace Microsoft.Build.Experimental.BuildCop; +/// +/// Holder for the key-value pairs of unstructured data from .editorconfig file, +/// that were attribute to a particular rule, but were not recognized by the infrastructure. +/// The configuration data that is recognized by the infrastructure is passed as . +/// +/// public class CustomConfigurationData(string ruleId) { public static CustomConfigurationData Null { get; } = new(string.Empty); public static bool NotNull(CustomConfigurationData data) => !Null.Equals(data); + /// + /// Identifier of the rule that the configuration data is for. + /// public string RuleId { get; init; } = ruleId; + + /// + /// Key-value pairs of unstructured data from .editorconfig file. + /// E.g. if in editorconfig file we'd have: + /// [*.csrpoj] + /// build_analyzer.microsoft.BC0101.name_of_targets_to_restrict = "Build,CoreCompile,ResolveAssemblyReferences" + /// + /// the ConfigurationData would be: + /// "name_of_targets_to_restrict" -> "Build,CoreCompile,ResolveAssemblyReferences" + /// public IReadOnlyDictionary? ConfigurationData { get; init; } public override bool Equals(object? obj) diff --git a/src/Tasks.UnitTests/Exec_Tests.cs b/src/Tasks.UnitTests/Exec_Tests.cs index 38700fed179..bbba6eab034 100644 --- a/src/Tasks.UnitTests/Exec_Tests.cs +++ b/src/Tasks.UnitTests/Exec_Tests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Text; using Microsoft.Build.Evaluation; @@ -169,7 +170,10 @@ public void Timeout() Assert.False(result); Assert.Equal(expectedExitCode, exec.ExitCode); ((MockEngine)exec.BuildEngine).AssertLogContains("MSB5002"); - Assert.Equal(1, ((MockEngine)exec.BuildEngine).Warnings); + int warningsCount = ((MockEngine)exec.BuildEngine).Warnings; + warningsCount.ShouldBe(1, + $"Expected 1 warning, encountered {warningsCount}: " + string.Join(",", + ((MockEngine)exec.BuildEngine).WarningEvents.Select(w => w.Message))); // ToolTask does not log an error on timeout. Assert.Equal(0, ((MockEngine)exec.BuildEngine).Errors); From 296faa9688adf1578ec871f7964bf3340c88fd64 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Fri, 1 Mar 2024 19:22:43 +0100 Subject: [PATCH 22/58] Reflect PR comments --- eng/BootStrapMSBuild.targets | 2 - src/Analyzers.UnitTests/AssemblyInfo.cs | 10 ---- src/Analyzers.UnitTests/BootstrapRunner.cs | 48 ------------------- src/Analyzers.UnitTests/EndToEndTests.cs | 3 +- ...Microsoft.Build.Analyzers.UnitTests.csproj | 18 ------- .../Infrastructure/BuildCopConnectorLogger.cs | 3 -- src/Build/Microsoft.Build.csproj | 4 +- src/Framework/Traits.cs | 1 + .../MSBuild.Bootstrap.csproj | 2 + .../Utilities => Shared}/IsExternalInit.cs | 0 src/UnitTests.Shared/AssemblyInfo.cs | 10 ++++ src/UnitTests.Shared/IsExternalInit.cs | 7 --- .../Microsoft.Build.UnitTests.Shared.csproj | 17 +++++++ src/UnitTests.Shared/RunnerUtilities.cs | 46 +++++++++++++++++- 14 files changed, 79 insertions(+), 92 deletions(-) delete mode 100644 src/Analyzers.UnitTests/BootstrapRunner.cs rename src/{Build/BuildCop/Utilities => Shared}/IsExternalInit.cs (100%) delete mode 100644 src/UnitTests.Shared/IsExternalInit.cs diff --git a/eng/BootStrapMSBuild.targets b/eng/BootStrapMSBuild.targets index 1bfa14e0f34..07531f6633b 100644 --- a/eng/BootStrapMSBuild.targets +++ b/eng/BootStrapMSBuild.targets @@ -1,7 +1,5 @@ - - + NU5101;NU5128 + + + + + + + + + + + + + + + + + + + + + <_PackagesToPack Remove="@(_PackagesToPack)" Condition="%(NuGetPackageId) == 'NETStandard.Library'" /> + <_PackagesToPack Remove="@(_PackagesToPack)" Condition="%(_PackagesToPack.IncludeInPackage) != 'true'" /> + + + + + + + + + + + + + diff --git a/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props b/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props new file mode 100644 index 00000000000..8de4380640c --- /dev/null +++ b/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props @@ -0,0 +1,9 @@ + + + + $([MSBuild]::RegisterAnalyzer($(MSBuildThisFileDirectory)..\lib\Company.AnalyzerTemplate.dll)) + + + + + diff --git a/template_feed/Microsoft.AnalyzerTemplate/README.md b/template_feed/Microsoft.AnalyzerTemplate/README.md new file mode 100644 index 00000000000..4f29145e7f0 --- /dev/null +++ b/template_feed/Microsoft.AnalyzerTemplate/README.md @@ -0,0 +1,21 @@ +# MSBuild Custom Analyzer Template + +## Overview +MSBuild Custom Analyzer Template is a .NET template designed to streamline the creation of MSBuild analyzer libraries. This template facilitates the development of custom analyzers targeting .NET Standard, enabling developers to inspect and enforce conventions, standards, or patterns within their MSBuild builds. + +## Features +- Simplified template for creating MSBuild analyzer libraries. +- Targeting .NET Standard for cross-platform compatibility. +- Provides a starting point for implementing custom analysis rules. + +## Getting Started +To use the MSBuild Custom Analyzer Template, follow these steps: +1. Install the template using the following command: + ```bash + dotnet new install msbuildanalyzer +2. Instantiate a custom template: + ```bash + dotnet new msbuildanalyzer -n + +### Prerequisites +- .NET SDK installed on your machine. \ No newline at end of file From 2aeda7e81c185633ebc8ac88a096ca8187281812 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Tue, 26 Mar 2024 10:35:52 +0100 Subject: [PATCH 30/58] init changes --- .../Logging/EvaluationLoggingContext.cs | 6 +-- .../Acquisition/AnalyzerAcquisitionData.cs | 8 +-- .../BuildCheckAcquisitionModule.cs | 40 ++++++++++++++- .../Analyzers/SharedOutputPathAnalyzer.cs | 2 - .../BuildCheckConnectorLogger.cs | 5 +- .../BuildCheckManagerProvider.cs | 5 +- .../Infrastructure/NullBuildCheckManager.cs | 35 ++++++++----- .../Logging/AnalyzerLoggingContextFactory.cs | 1 + src/Build/Evaluation/Evaluator.cs | 2 +- src/Build/Evaluation/IntrinsicFunctions.cs | 50 ++++++++++++++++++- .../BuildCheck/BuildCheckEventArgs.cs | 34 +++++++++---- 11 files changed, 149 insertions(+), 39 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs b/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs index 31223745d8b..9a4a3f7bd58 100644 --- a/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs @@ -12,14 +12,14 @@ namespace Microsoft.Build.BackEnd.Components.Logging { /// - /// Logging context and helpers for evaluation logging + /// Logging context and helpers for evaluation logging. /// internal class EvaluationLoggingContext : LoggingContext { private readonly string _projectFile; - public EvaluationLoggingContext(ILoggingService loggingService, BuildEventContext buildEventContext, string projectFile) : - base( + public EvaluationLoggingContext(ILoggingService loggingService, BuildEventContext buildEventContext, string projectFile) + : base( loggingService, loggingService.CreateEvaluationBuildEventContext(buildEventContext.NodeId, buildEventContext.SubmissionId)) { diff --git a/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs b/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs index c71d69d115c..74a8bf6a1ce 100644 --- a/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs +++ b/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs @@ -12,15 +12,15 @@ namespace Microsoft.Build.BuildCheck.Acquisition; // TODO: Acquisition // define the data that will be passed to the acquisition module (and remoted if needed) -internal class AnalyzerAcquisitionData(string data) +internal class AnalyzerAcquisitionData(string assemblyPath) { - public string Data { get; init; } = data; + public string AssemblyPath { get; init; } = assemblyPath; } internal static class AnalyzerAcquisitionDataExtensions { public static AnalyzerAcquisitionData ToAnalyzerAcquisitionData(this BuildCheckAcquisitionEventArgs eventArgs) => - new(eventArgs.AcquisitionData); + new(eventArgs.AcquisitionPath); - public static BuildCheckAcquisitionEventArgs ToBuildEventArgs(this AnalyzerAcquisitionData data) => new(data.Data); + public static BuildCheckAcquisitionEventArgs ToBuildEventArgs(this AnalyzerAcquisitionData data) => new(data.AssemblyPath); } diff --git a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs index bd7463085a1..aafe4099278 100644 --- a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs @@ -3,20 +3,56 @@ using System; using System.Collections.Generic; +using System.Configuration.Assemblies; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; using Microsoft.Build.BuildCheck.Analyzers; using Microsoft.Build.BuildCheck.Infrastructure; +using Microsoft.Build.Experimental.BuildCheck; namespace Microsoft.Build.BuildCheck.Acquisition; internal class BuildCheckAcquisitionModule { private static T Construct() where T : new() => new(); + public BuildAnalyzerFactory CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData) { - // TODO: Acquisition module - return Construct; + try + { + Assembly? assembly = null; +#if FEATURE_ASSEMBLYLOADCONTEXT + assembly = s_coreClrAssemblyLoader.LoadFromPath(assemblyPath); +#else + assembly = Assembly.LoadFrom(analyzerAcquisitionData.AssemblyPath); +#endif + + Type type = assembly.GetTypes().FirstOrDefault(); + + if (type != null) + { + // Check if the type is assignable to T + if (!typeof(BuildAnalyzer).IsAssignableFrom(type)) + { + throw new ArgumentException($"The type is not assignable to {typeof(BuildAnalyzer).FullName}"); + } + else + { + // ??? how to instantiate + } + } + } + catch (ReflectionTypeLoadException ex) + { + Console.WriteLine("Failed to load one or more types from the assembly:"); + foreach (Exception loaderException in ex.LoaderExceptions) + { + Console.WriteLine(loaderException.Message); + } + } + + return null; } } diff --git a/src/Build/BuildCheck/Analyzers/SharedOutputPathAnalyzer.cs b/src/Build/BuildCheck/Analyzers/SharedOutputPathAnalyzer.cs index 970b644d495..ff8e78ad5f8 100644 --- a/src/Build/BuildCheck/Analyzers/SharedOutputPathAnalyzer.cs +++ b/src/Build/BuildCheck/Analyzers/SharedOutputPathAnalyzer.cs @@ -12,8 +12,6 @@ namespace Microsoft.Build.BuildCheck.Analyzers; - - internal sealed class SharedOutputPathAnalyzer : BuildAnalyzer { public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule("BC0101", "ConflictingOutputPath", diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs index b860423748e..832a611ad81 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs @@ -12,10 +12,12 @@ using Microsoft.Build.Framework; namespace Microsoft.Build.BuildCheck.Infrastructure; + internal sealed class BuildCheckConnectorLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory, IBuildCheckManager buildCheckManager) : ILogger { public LoggerVerbosity Verbosity { get; set; } + public string? Parameters { get; set; } public void Initialize(IEventSource eventSource) @@ -55,8 +57,7 @@ private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) return; } - buildCheckManager.StartProjectEvaluation(BuildCheckDataSource.EventArgs, e.BuildEventContext!, - projectEvaluationStartedEventArgs.ProjectFile!); + buildCheckManager.StartProjectEvaluation(BuildCheckDataSource.EventArgs, e.BuildEventContext!, projectEvaluationStartedEventArgs.ProjectFile!); } else if (e is ProjectStartedEventArgs projectStartedEvent) { diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index df5385b08ba..541df4b8fa5 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -160,7 +160,7 @@ internal void RegisterCustomAnalyzer( /// /// To be used by acquisition module - /// Registeres the custom analyzer, the construction of analyzer is needed during registration + /// Registeres the custom analyzer, the construction of analyzer is needed during registration. /// internal void RegisterCustomAnalyzer( BuildCheckDataSource buildCheckDataSource, @@ -169,7 +169,8 @@ internal void RegisterCustomAnalyzer( if (_enabledDataSources[(int)buildCheckDataSource]) { var instance = factory(); - _analyzersRegistry.Add(new BuildAnalyzerFactoryContext(factory, + _analyzersRegistry.Add(new BuildAnalyzerFactoryContext( + factory, instance.SupportedRules.Select(r => r.Id).ToArray(), instance.SupportedRules.Any(r => r.DefaultConfiguration.IsEnabled == true))); } diff --git a/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs b/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs index d6685345652..22ae0b70bae 100644 --- a/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs +++ b/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs @@ -15,36 +15,45 @@ namespace Microsoft.Build.BuildCheck.Infrastructure; internal class NullBuildCheckManager : IBuildCheckManager { - public void Shutdown() { } + public void Shutdown() + { + } - public void ProcessEvaluationFinishedEventArgs(IBuildAnalysisLoggingContext buildAnalysisContext, - ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs) - { } + public void ProcessEvaluationFinishedEventArgs(IBuildAnalysisLoggingContext buildAnalysisContext, ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs) + { + } public void SetDataSource(BuildCheckDataSource buildCheckDataSource) { } + public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) { } public Dictionary CreateTracingStats() => throw new NotImplementedException(); public void FinalizeProcessing(LoggingContext loggingContext) - { } + { + } - public void StartProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext, - string fullPath) - { } + public void StartProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext, string fullPath) + { + } public void EndProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) - { } + { + } public void StartProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) - { } + { + } public void EndProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) - { } + { + } public void YieldProject(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) - { } + { + } public void ResumeProject(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) - { } + { + } } diff --git a/src/Build/BuildCheck/Logging/AnalyzerLoggingContextFactory.cs b/src/Build/BuildCheck/Logging/AnalyzerLoggingContextFactory.cs index 8c437f45174..1cd2f468885 100644 --- a/src/Build/BuildCheck/Logging/AnalyzerLoggingContextFactory.cs +++ b/src/Build/BuildCheck/Logging/AnalyzerLoggingContextFactory.cs @@ -6,6 +6,7 @@ using Microsoft.Build.Framework; namespace Microsoft.Build.BuildCheck.Logging; + internal class AnalyzerLoggingContextFactory(ILoggingService loggingService) : IBuildAnalysisLoggingContextFactory { public IBuildAnalysisLoggingContext CreateLoggingContext(BuildEventContext eventContext) => diff --git a/src/Build/Evaluation/Evaluator.cs b/src/Build/Evaluation/Evaluator.cs index 780d58db6b1..27bcd78bcba 100644 --- a/src/Build/Evaluation/Evaluator.cs +++ b/src/Build/Evaluation/Evaluator.cs @@ -163,7 +163,7 @@ internal class Evaluator private readonly ProjectRootElementCacheBase _projectRootElementCache; /// - /// The logging context to be used and piped down throughout evaluation + /// The logging context to be used and piped down throughout evaluation. /// private EvaluationLoggingContext _evaluationLoggingContext; diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index 37312b8c83e..77b48115c6d 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; - +using Microsoft.Build.BackEnd.Components.Logging; +using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; @@ -619,6 +621,52 @@ public static bool IsRunningFromVisualStudio() return BuildEnvironmentHelper.Instance.Mode == BuildEnvironmentMode.VisualStudio; } + public static bool RegisterAnalyzer(string pathToAssembly) + { + pathToAssembly = FileUtilities.GetFullPathNoThrow(pathToAssembly); + + try + { + if (File.Exists(pathToAssembly)) + { + Assembly assembly = null; +#if FEATURE_ASSEMBLYLOADCONTEXT + Console.WriteLine($"Hi from FEATURE_ASSEMBLYLOADCONTEXT."); + assembly = s_coreClrAssemblyLoader.LoadFromPath(pathToAssembly); +#else + assembly = Assembly.LoadFrom(pathToAssembly); +#endif + Console.WriteLine($"Loaded assembly: {assembly.FullName}"); + + Type type = assembly.GetTypes()[0]; + object instance = Activator.CreateInstance(type); + + PropertyInfo property = type.GetProperty("Name"); + var value = property.GetValue(instance); + Console.WriteLine($"Loaded property analyzer name: {value}"); + + // need to have a logging context here. + new BuildCheckAcquisitionEventArgs(pathToAssembly); + + return true; + } + } + catch (ReflectionTypeLoadException ex) + { + Console.WriteLine("Failed to load one or more types from the assembly:"); + foreach (Exception loaderException in ex.LoaderExceptions) + { + Console.WriteLine(loaderException.Message); + } + } + catch (Exception ex) + { + Console.WriteLine($"Failed to load assembly '{pathToAssembly}': {ex.Message}"); + } + + return false; + } + #region Debug only intrinsics /// diff --git a/src/Framework/BuildCheck/BuildCheckEventArgs.cs b/src/Framework/BuildCheck/BuildCheckEventArgs.cs index 106754be327..1a415871da0 100644 --- a/src/Framework/BuildCheck/BuildCheckEventArgs.cs +++ b/src/Framework/BuildCheck/BuildCheckEventArgs.cs @@ -13,12 +13,15 @@ namespace Microsoft.Build.Experimental.BuildCheck; public abstract class BuildCheckEventArgs : BuildEventArgs -{ } +{ +} public sealed class BuildCheckTracingEventArgs(Dictionary tracingData) : BuildCheckEventArgs { - internal BuildCheckTracingEventArgs() : this(new Dictionary()) - { } + internal BuildCheckTracingEventArgs() + : this([]) + { + } public Dictionary TracingData { get; private set; } = tracingData; @@ -50,25 +53,38 @@ internal override void CreateFromStream(BinaryReader reader, int version) } } -public sealed class BuildCheckAcquisitionEventArgs(string acquisitionData) : BuildCheckEventArgs +public sealed class BuildCheckAcquisitionEventArgs(string acquisitionPath) : BuildCheckEventArgs { - internal BuildCheckAcquisitionEventArgs() : this(string.Empty) - { } + internal BuildCheckAcquisitionEventArgs() + : this(string.Empty) + { + } - public string AcquisitionData { get; private set; } = acquisitionData; + /// + /// Gets the path to the analyzer assembly that needs to be loaded into the application context. + /// + /// + /// The property contains the file system path to the assembly + /// that is required to be loaded into the application context. This path is used for loading + /// the specified assembly dynamically during runtime. + /// + /// + /// A representing the file system path to the assembly. + /// + public string AcquisitionPath { get; private set; } = acquisitionPath; internal override void WriteToStream(BinaryWriter writer) { base.WriteToStream(writer); - writer.Write(AcquisitionData); + writer.Write(AcquisitionPath); } internal override void CreateFromStream(BinaryReader reader, int version) { base.CreateFromStream(reader, version); - AcquisitionData = reader.ReadString(); + AcquisitionPath = reader.ReadString(); } } public sealed class BuildCheckResultWarning : BuildWarningEventArgs From 5fab936eeb1a112eb4fb3e611818d33d70238903 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Wed, 3 Apr 2024 11:04:05 +0200 Subject: [PATCH 31/58] Run tests against just-built bootstrap environment --- eng/cibuild_bootstrapped_msbuild.ps1 | 8 ++------ eng/cibuild_bootstrapped_msbuild.sh | 8 ++------ src/UnitTests.Shared/RunnerUtilities.cs | 24 +----------------------- 3 files changed, 5 insertions(+), 35 deletions(-) diff --git a/eng/cibuild_bootstrapped_msbuild.ps1 b/eng/cibuild_bootstrapped_msbuild.ps1 index f1cb3b4a4e9..b6e3c089135 100644 --- a/eng/cibuild_bootstrapped_msbuild.ps1 +++ b/eng/cibuild_bootstrapped_msbuild.ps1 @@ -113,18 +113,14 @@ try { # Opt into performance logging. https://github.com/dotnet/msbuild/issues/5900 $env:DOTNET_PERFLOG_DIR=$PerfLogDir - # Expose stage 1 path so unit tests can find the bootstrapped MSBuild. - $env:MSBUILD_BOOTSTRAPPED_BINDIR=$Stage1BinDir - # When using bootstrapped MSBuild: # - Turn off node reuse (so that bootstrapped MSBuild processes don't stay running and lock files) - # - Do run tests - # - Don't try to create a bootstrap deployment + # - Create bootstrap environment as it's required when also running tests if ($onlyDocChanged) { & $PSScriptRoot\Common\Build.ps1 -restore -build -ci /p:CreateBootstrap=false /nr:false @properties } else { - & $PSScriptRoot\Common\Build.ps1 -restore -build -test -ci /p:CreateBootstrap=false /nr:false @properties + & $PSScriptRoot\Common\Build.ps1 -restore -build -test -ci /p:CreateBootstrap=true /nr:false @properties } exit $lastExitCode diff --git a/eng/cibuild_bootstrapped_msbuild.sh b/eng/cibuild_bootstrapped_msbuild.sh index 43fa422fad4..8edd377ec73 100755 --- a/eng/cibuild_bootstrapped_msbuild.sh +++ b/eng/cibuild_bootstrapped_msbuild.sh @@ -71,9 +71,6 @@ mv $ArtifactsDir $Stage1Dir # Ensure that debug bits fail fast, rather than hanging waiting for a debugger attach. export MSBUILDDONOTLAUNCHDEBUGGER=true -# Expose stage 1 path so unit tests can find the bootstrapped MSBuild. -export MSBUILD_BOOTSTRAPPED_BINDIR="$Stage1Dir/bin" - # Opt into performance logging. export DOTNET_PERFLOG_DIR=$PerfLogDir @@ -83,11 +80,10 @@ export DOTNET_HOST_PATH="$_InitializeDotNetCli/dotnet" # When using bootstrapped MSBuild: # - Turn off node reuse (so that bootstrapped MSBuild processes don't stay running and lock files) -# - Do run tests -# - Don't try to create a bootstrap deployment +# - Create bootstrap environment as it's required when also running tests if [ $onlyDocChanged = 0 ] then - . "$ScriptRoot/common/build.sh" --restore --build --test --ci --nodereuse false --configuration $configuration /p:CreateBootstrap=false $properties $extra_properties + . "$ScriptRoot/common/build.sh" --restore --build --test --ci --nodereuse false --configuration $configuration /p:CreateBootstrap=true $properties $extra_properties else . "$ScriptRoot/common/build.sh" --restore --build --ci --nodereuse false --configuration $configuration /p:CreateBootstrap=false $properties $extra_properties diff --git a/src/UnitTests.Shared/RunnerUtilities.cs b/src/UnitTests.Shared/RunnerUtilities.cs index 373692d37f5..6310534a391 100644 --- a/src/UnitTests.Shared/RunnerUtilities.cs +++ b/src/UnitTests.Shared/RunnerUtilities.cs @@ -58,33 +58,11 @@ public static string ExecBootstrapedMSBuild(string msbuildParameters, out bool s ?? throw new InvalidOperationException("This test assembly does not have the BootstrapLocationAttribute"); string binaryFolder = attribute.BootstrapMsbuildBinaryLocation; - string bindirOverride = Environment.GetEnvironmentVariable("MSBUILD_BOOTSTRAPPED_BINDIR"); - if (!string.IsNullOrEmpty(bindirOverride)) - { - // The bootstrap environment has moved to another location. Assume the same relative layout and adjust the path. -#if NET - string relativePath = Path.GetRelativePath(attribute.BootstrapRoot, binaryFolder); - binaryFolder = Path.GetFullPath(relativePath, bindirOverride); -#else - binaryFolder = Path.GetFullPath(binaryFolder); - if (binaryFolder.StartsWith(attribute.BootstrapRoot)) - { - binaryFolder = binaryFolder.Substring(attribute.BootstrapRoot.Length); - if (binaryFolder.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - binaryFolder = binaryFolder.Substring(1); - } - - binaryFolder = Path.Combine(bindirOverride, binaryFolder); - } -#endif - } #if NET string pathToExecutable = EnvironmentProvider.GetDotnetExePath()!; msbuildParameters = Path.Combine(binaryFolder, "MSBuild.dll") + " " + msbuildParameters; #else - string pathToExecutable = - Path.Combine(binaryFolder, "msbuild.exe"); + string pathToExecutable = Path.Combine(binaryFolder, "MSBuild.exe"); #endif return RunProcessAndGetOutput(pathToExecutable, msbuildParameters, out successfulExit, shellExecute, outputHelper); } From 9b5b92502dc09e881fb8c167c102e4122946a00f Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 4 Apr 2024 16:29:18 +0200 Subject: [PATCH 32/58] Reflecting PR comments --- eng/BootStrapMSBuild.props | 2 +- src/Analyzers.UnitTests/EndToEndTests.cs | 2 +- src/Build.UnitTests/BackEnd/MockHost.cs | 2 +- .../BackEnd/BuildManager/BuildManager.cs | 4 +- .../BuildComponentFactoryCollection.cs | 2 +- .../BuildRequestEngine/BuildRequestEngine.cs | 2 +- .../BackEnd/Components/IBuildComponentHost.cs | 2 +- .../Components/Logging/EventSourceSink.cs | 4 +- .../RequestBuilder/RequestBuilder.cs | 2 +- .../Shared/BuildRequestConfiguration.cs | 3 - src/Build/BuildCheck/API/BuildAnalyzerRule.cs | 8 +-- src/Build/BuildCheck/API/BuildCheckResult.cs | 4 ++ .../BuildCheck/API/ConfigurationContext.cs | 2 +- .../Acquisition/AnalyzerAcquisitionData.cs | 3 +- .../BuildCheckAcquisitionModule.cs | 2 +- .../Analyzers/SharedOutputPathAnalyzer.cs | 6 +- .../BuildAnalyzerConfigurationInternal.cs | 2 +- .../Infrastructure/BuildAnalyzerWrapper.cs | 4 +- .../BuildCheckCentralContext.cs | 6 +- .../BuildCheckConfigurationException.cs | 2 +- .../BuildCheckConnectorLogger.cs | 4 +- .../BuildCheckManagerProvider.cs | 13 +++-- .../Infrastructure/BuildEventsProcessor.cs | 10 ++-- .../Infrastructure/ConfigurationProvider.cs | 8 +-- .../Infrastructure/IBuildCheckManager.cs | 3 +- .../Infrastructure/NullBuildCheckManager.cs | 3 +- .../Infrastructure/TracingReporter.cs | 1 - .../Logging/AnalyzerLoggingContext.cs | 2 +- .../Logging/AnalyzerLoggingContextFactory.cs | 2 +- .../BuildAnalysisLoggingContextExtensions.cs | 15 ----- .../Logging/IBuildAnalysisLoggingContext.cs | 7 --- .../IBuildAnalysisLoggingContextFactory.cs | 5 +- .../BuildCheck/OM/BuildCheckDataContext.cs | 4 ++ .../OM/EvaluatedPropertiesAnalysisData.cs | 4 ++ .../BuildCheck/OM/ParsedItemsAnalysisData.cs | 14 ++++- .../Utilities/EnumerableExtensions.cs | 22 ++------ src/Build/Microsoft.Build.csproj | 2 - .../BuildCheck/BuildCheckEventArgs.cs | 20 +++++-- src/Framework/BuildCheck/IBuildCheckResult.cs | 2 +- src/Framework/IEventSource.cs | 2 +- .../MSBuild.Bootstrap.csproj | 6 +- src/MSBuild/CommandLineSwitches.cs | 2 +- src/MSBuild/XMake.cs | 2 +- src/Shared/IsExternalInit.cs | 2 + ...yInfo.cs => BootstrapLocationAttribute.cs} | 3 +- .../Microsoft.Build.UnitTests.Shared.csproj | 5 +- .../.template.config/template.json | 2 +- .../Company.AnalyzerTemplate.csproj | 56 +++++++++---------- .../Directory.Build.props | 2 +- 49 files changed, 137 insertions(+), 150 deletions(-) delete mode 100644 src/Build/BuildCheck/Logging/BuildAnalysisLoggingContextExtensions.cs delete mode 100644 src/Build/BuildCheck/Logging/IBuildAnalysisLoggingContext.cs rename src/UnitTests.Shared/{AssemblyInfo.cs => BootstrapLocationAttribute.cs} (82%) diff --git a/eng/BootStrapMSBuild.props b/eng/BootStrapMSBuild.props index e70bcb3489d..858cf76ac54 100644 --- a/eng/BootStrapMSBuild.props +++ b/eng/BootStrapMSBuild.props @@ -12,7 +12,7 @@ - $(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin + $(BootstrapDestination)$(TargetMSBuildToolsVersion)\Bin diff --git a/src/Analyzers.UnitTests/EndToEndTests.cs b/src/Analyzers.UnitTests/EndToEndTests.cs index dc6bce0563b..6a90e2734bb 100644 --- a/src/Analyzers.UnitTests/EndToEndTests.cs +++ b/src/Analyzers.UnitTests/EndToEndTests.cs @@ -113,7 +113,7 @@ public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode) // OSX links /var into /private, which makes Path.GetTempPath() return "/var..." but Directory.GetCurrentDirectory return "/private/var...". // This discrepancy breaks path equality checks in analyzers if we pass to MSBuild full path to the initial project. - // TODO: See if there is a way of fixing it in the engine. + // See if there is a way of fixing it in the engine - tracked: https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=55702688. _env.SetCurrentDirectory(Path.GetDirectoryName(projectFile.Path)); _env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", buildInOutOfProcessNode ? "1" : "0"); diff --git a/src/Build.UnitTests/BackEnd/MockHost.cs b/src/Build.UnitTests/BackEnd/MockHost.cs index a5df5d99cfa..920e49b51e1 100644 --- a/src/Build.UnitTests/BackEnd/MockHost.cs +++ b/src/Build.UnitTests/BackEnd/MockHost.cs @@ -200,7 +200,7 @@ public IBuildComponent GetComponent(BuildComponentType type) BuildComponentType.ResultsCache => (IBuildComponent)_resultsCache, BuildComponentType.RequestBuilder => (IBuildComponent)_requestBuilder, BuildComponentType.SdkResolverService => (IBuildComponent)_sdkResolverService, - BuildComponentType.BuildCheck => (IBuildComponent)_buildCheckManagerProvider, + BuildComponentType.BuildCheckManagerProvider => (IBuildComponent)_buildCheckManagerProvider, _ => throw new ArgumentException("Unexpected type " + type), }; } diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index f99833cdc15..25bcabbeed3 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2984,10 +2984,10 @@ private ILoggingService CreateLoggingService( loggingService.WarningsNotAsErrors = warningsNotAsErrors; loggingService.WarningsAsMessages = warningsAsMessages; - if (((IBuildComponentHost)this).BuildParameters.IsBuildCheckEnabled) + if (_buildParameters.IsBuildCheckEnabled) { var buildCheckManagerProvider = - ((IBuildComponentHost)this).GetComponent(BuildComponentType.BuildCheck) as IBuildCheckManagerProvider; + ((IBuildComponentHost)this).GetComponent(BuildComponentType.BuildCheckManagerProvider) as IBuildCheckManagerProvider; buildCheckManagerProvider!.Instance.SetDataSource(BuildCheckDataSource.EventArgs); loggers = (loggers ?? Enumerable.Empty()).Concat(new[] diff --git a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs index 865d1da9149..abcb6c22fb0 100644 --- a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs +++ b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs @@ -78,7 +78,7 @@ public void RegisterDefaultFactories() _componentEntriesByType[BuildComponentType.LoggingService] = new BuildComponentEntry(BuildComponentType.LoggingService, null); _componentEntriesByType[BuildComponentType.RequestBuilder] = new BuildComponentEntry(BuildComponentType.RequestBuilder, RequestBuilder.CreateComponent, CreationPattern.CreateAlways); // This conditionally registers real or no-op implementation based on BuildParameters - _componentEntriesByType[BuildComponentType.BuildCheck] = new BuildComponentEntry(BuildComponentType.BuildCheck, BuildCheckManagerProvider.CreateComponent, CreationPattern.Singleton); + _componentEntriesByType[BuildComponentType.BuildCheckManagerProvider] = new BuildComponentEntry(BuildComponentType.BuildCheckManagerProvider, BuildCheckManagerProvider.CreateComponent, CreationPattern.Singleton); _componentEntriesByType[BuildComponentType.TargetBuilder] = new BuildComponentEntry(BuildComponentType.TargetBuilder, TargetBuilder.CreateComponent, CreationPattern.CreateAlways); _componentEntriesByType[BuildComponentType.TaskBuilder] = new BuildComponentEntry(BuildComponentType.TaskBuilder, TaskBuilder.CreateComponent, CreationPattern.CreateAlways); _componentEntriesByType[BuildComponentType.RegisteredTaskObjectCache] = new BuildComponentEntry(BuildComponentType.RegisteredTaskObjectCache, RegisteredTaskObjectCache.CreateComponent, CreationPattern.Singleton); diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index 05d11dc38cb..7e72bd3a159 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -283,7 +283,7 @@ public void CleanupForBuild() throw new AggregateException(deactivateExceptions); } - var buildCheckManager = (_componentHost.GetComponent(BuildComponentType.BuildCheck) as IBuildCheckManagerProvider)!.Instance; + var buildCheckManager = (_componentHost.GetComponent(BuildComponentType.BuildCheckManagerProvider) as IBuildCheckManagerProvider)!.Instance; buildCheckManager.FinalizeProcessing(_nodeLoggingContext); }, isLastTask: true); diff --git a/src/Build/BackEnd/Components/IBuildComponentHost.cs b/src/Build/BackEnd/Components/IBuildComponentHost.cs index 2ebea5a290a..8b2ded4d251 100644 --- a/src/Build/BackEnd/Components/IBuildComponentHost.cs +++ b/src/Build/BackEnd/Components/IBuildComponentHost.cs @@ -146,7 +146,7 @@ internal enum BuildComponentType /// /// The Build Analyzer Manager. /// - BuildCheck, + BuildCheckManagerProvider, } /// diff --git a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs index 5c58d92561e..1a16e9f2190 100644 --- a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs +++ b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs @@ -102,9 +102,9 @@ internal sealed class EventSourceSink : public event TelemetryEventHandler TelemetryLogged; /// - /// This event is raised to log build cop events. + /// This event is raised to log BuildCheck events. /// - public event BuildCheckEventHandler BuildCheckEventRaised; + internal event BuildCheckEventHandler BuildCheckEventRaised; #endregion #region Properties diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 5d4938c7b7b..b5b3454e36f 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1120,7 +1120,7 @@ private async Task BuildProject() { // We consider this the entrypoint for the project build for purposes of BuildCheck processing - var buildCheckManager = (_componentHost.GetComponent(BuildComponentType.BuildCheck) as IBuildCheckManagerProvider)!.Instance; + var buildCheckManager = (_componentHost.GetComponent(BuildComponentType.BuildCheckManagerProvider) as IBuildCheckManagerProvider)!.Instance; buildCheckManager.SetDataSource(BuildCheckDataSource.BuildExecution); ErrorUtilities.VerifyThrow(_targetBuilder != null, "Target builder is null"); diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index d821a43951f..27e1b307aab 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -476,9 +476,6 @@ internal void LoadProjectIntoConfiguration( projectLoadSettings |= ProjectLoadSettings.FailOnUnresolvedSdk; } - // Here - if we'll have in-execution analysis and it'll need DOM from Project, - // this is the place for Project creation. - return new ProjectInstance( ProjectFullPath, globalProperties, diff --git a/src/Build/BuildCheck/API/BuildAnalyzerRule.cs b/src/Build/BuildCheck/API/BuildAnalyzerRule.cs index 9e610834691..30b34dfa65f 100644 --- a/src/Build/BuildCheck/API/BuildAnalyzerRule.cs +++ b/src/Build/BuildCheck/API/BuildAnalyzerRule.cs @@ -10,13 +10,12 @@ namespace Microsoft.Build.Experimental.BuildCheck; /// public class BuildAnalyzerRule { - public BuildAnalyzerRule(string id, string title, string description, string category, string messageFormat, + public BuildAnalyzerRule(string id, string title, string description, string messageFormat, BuildAnalyzerConfiguration defaultConfiguration) { Id = id; Title = title; Description = description; - Category = category; MessageFormat = messageFormat; DefaultConfiguration = defaultConfiguration; } @@ -43,11 +42,6 @@ public BuildAnalyzerRule(string id, string title, string description, string cat /// public string Description { get; } - /// - /// TODO: We might turn this into enum, or just remove this. - /// - public string Category { get; } - /// /// Message format that will be used by the actual reports () - those will just supply the actual arguments. /// diff --git a/src/Build/BuildCheck/API/BuildCheckResult.cs b/src/Build/BuildCheck/API/BuildCheckResult.cs index 97975cf7051..03a69e02939 100644 --- a/src/Build/BuildCheck/API/BuildCheckResult.cs +++ b/src/Build/BuildCheck/API/BuildCheckResult.cs @@ -38,6 +38,10 @@ internal BuildEventArgs ToEventArgs(BuildAnalyzerResultSeverity severity) }; public BuildAnalyzerRule BuildAnalyzerRule { get; } + + /// + /// Optional location of the finding (in near future we might need to support multiple locations). + /// public ElementLocation Location { get; } public string LocationString => Location.LocationString; diff --git a/src/Build/BuildCheck/API/ConfigurationContext.cs b/src/Build/BuildCheck/API/ConfigurationContext.cs index 4ea815eb2b8..49dcdcfafec 100644 --- a/src/Build/BuildCheck/API/ConfigurationContext.cs +++ b/src/Build/BuildCheck/API/ConfigurationContext.cs @@ -36,5 +36,5 @@ internal static ConfigurationContext FromDataEnumeration(CustomConfigurationData /// /// Custom configuration data - per each rule that has some specified. /// - public CustomConfigurationData[] CustomConfigurationData { get; init; } + public IReadOnlyList CustomConfigurationData { get; init; } } diff --git a/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs b/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs index c71d69d115c..872aba3440f 100644 --- a/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs +++ b/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs @@ -10,7 +10,8 @@ namespace Microsoft.Build.BuildCheck.Acquisition; -// TODO: Acquisition +// https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=52643036 +// Acquisition // define the data that will be passed to the acquisition module (and remoted if needed) internal class AnalyzerAcquisitionData(string data) { diff --git a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs index bd7463085a1..60744d8aa38 100644 --- a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs @@ -16,7 +16,7 @@ internal class BuildCheckAcquisitionModule private static T Construct() where T : new() => new(); public BuildAnalyzerFactory CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData) { - // TODO: Acquisition module + // Acquisition module - https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=52643036 return Construct; } } diff --git a/src/Build/BuildCheck/Analyzers/SharedOutputPathAnalyzer.cs b/src/Build/BuildCheck/Analyzers/SharedOutputPathAnalyzer.cs index 970b644d495..174fb305b83 100644 --- a/src/Build/BuildCheck/Analyzers/SharedOutputPathAnalyzer.cs +++ b/src/Build/BuildCheck/Analyzers/SharedOutputPathAnalyzer.cs @@ -12,12 +12,10 @@ namespace Microsoft.Build.BuildCheck.Analyzers; - - internal sealed class SharedOutputPathAnalyzer : BuildAnalyzer { public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule("BC0101", "ConflictingOutputPath", - "Two projects should not share their OutputPath nor IntermediateOutputPath locations", "Configuration", + "Two projects should not share their OutputPath nor IntermediateOutputPath locations", "Projects {0} and {1} have conflicting output paths: {2}.", new BuildAnalyzerConfiguration() { Severity = BuildAnalyzerResultSeverity.Warning, IsEnabled = true }); @@ -79,7 +77,7 @@ private void EvaluatedPropertiesAction(BuildCheckDataContext + public bool IsSameConfigurationAs(BuildAnalyzerConfigurationInternal? other) => other != null && Severity == other.Severity && IsEnabled == other.IsEnabled && diff --git a/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs b/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs index 22d116d95a1..92673cf7f79 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs @@ -43,7 +43,7 @@ internal void StartNewProject( } } - if (CommonConfig == null || !userConfigs.All(t => t.IsEqual(CommonConfig))) + if (CommonConfig == null || !userConfigs.All(t => t.IsSameConfigurationAs(CommonConfig))) { CommonConfig = null; } @@ -59,7 +59,7 @@ internal void Uninitialize() internal void ClearStats() => _stopwatch.Reset(); - internal IDisposable StartSpan() + internal CleanupScope StartSpan() { _stopwatch.Start(); return new CleanupScope(_stopwatch.Stop); diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckCentralContext.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckCentralContext.cs index 55b70993bb5..9995aef71b3 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckCentralContext.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckCentralContext.cs @@ -94,7 +94,7 @@ private void RunRegisteredActions( /* (BuildAnalyzerWrapper2, Action>) */ analyzerCallback => { - // TODO: tracing - we might want tp account this entire block + // Tracing - https://github.com/dotnet/msbuild/issues/9629 - we might want to account this entire block // to the relevant analyzer (with only the currently accounted part as being the 'core-execution' subspan) BuildAnalyzerConfigurationInternal? commonConfig = analyzerCallback.Item1.CommonConfig; @@ -120,7 +120,9 @@ private void RunRegisteredActions( } } - // TODO: if the input data supports that - check the configPerRule[0].EvaluationAnalysisScope + // Here we might want to check the configPerRule[0].EvaluationAnalysisScope - if the input data supports that + // The decision and implementation depends on the outcome of the investigation tracked in: + // https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=57851137 BuildCheckDataContext context = new BuildCheckDataContext( analyzerCallback.Item1, diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConfigurationException.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConfigurationException.cs index a87f8939229..29a0a8acf50 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConfigurationException.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConfigurationException.cs @@ -9,7 +9,7 @@ namespace Microsoft.Build.BuildCheck.Infrastructure; -internal class BuildCheckConfigurationException : Exception +internal sealed class BuildCheckConfigurationException : Exception { /// /// Exception to communicate issues with user specified configuration - unsupported scenarios, malformations, etc. diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs index b860423748e..20d095fc889 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs @@ -91,9 +91,9 @@ private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e) BuildEventContext.InvalidNodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); - LoggingContext loggingContext = loggingContextFactory.CreateLoggingContext(buildEventContext).ToLoggingContext(); + LoggingContext loggingContext = loggingContextFactory.CreateLoggingContext(buildEventContext); - // TODO: tracing: https://github.com/dotnet/msbuild/issues/9629 + // Tracing: https://github.com/dotnet/msbuild/issues/9629 loggingContext.LogCommentFromText(MessageImportance.High, msg); } diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index df5385b08ba..9cc89118eef 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -41,7 +41,7 @@ internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider internal static IBuildComponent CreateComponent(BuildComponentType type) { - ErrorUtilities.VerifyThrow(type == BuildComponentType.BuildCheck, "Cannot create components of type {0}", type); + ErrorUtilities.VerifyThrow(type == BuildComponentType.BuildCheckManagerProvider, "Cannot create components of type {0}", type); return new BuildCheckManagerProvider(); } @@ -51,7 +51,7 @@ public void InitializeComponent(IBuildComponentHost host) if (Interlocked.CompareExchange(ref s_isInitialized, 1, 0) == 1) { - // Already initialized + // Initialization code already run(ing) return; } @@ -106,7 +106,7 @@ public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) { BuildCheckAcquisitionEventArgs eventArgs = acquisitionData.ToBuildEventArgs(); - // TODO: We may want to pass the real context here (from evaluation) + // We may want to pass the real context here (from evaluation) eventArgs.BuildEventContext = new BuildEventContext( BuildEventContext.InvalidNodeId, BuildEventContext.InvalidProjectInstanceId, @@ -177,7 +177,8 @@ internal void RegisterCustomAnalyzer( private void SetupSingleAnalyzer(BuildAnalyzerFactoryContext analyzerFactoryContext, string projectFullPath, BuildEventContext buildEventContext) { - // TODO: For user analyzers - it should run only on projects where referenced + // For custom analyzers - it should run only on projects where referenced + // (otherwise error out - https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=57849480) // on others it should work similarly as disabling them. // Disabled analyzer should not only post-filter results - it shouldn't even see the data @@ -282,12 +283,12 @@ private void SetupAnalyzersForNewProject(string projectFullPath, BuildEventConte public void ProcessEvaluationFinishedEventArgs( - IBuildAnalysisLoggingContext buildAnalysisContext, + AnalyzerLoggingContext buildAnalysisContext, ProjectEvaluationFinishedEventArgs evaluationFinishedEventArgs) => _buildEventsProcessor .ProcessEvaluationFinishedEventArgs(buildAnalysisContext, evaluationFinishedEventArgs); - // TODO: tracing: https://github.com/dotnet/msbuild/issues/9629 + // Tracing: https://github.com/dotnet/msbuild/issues/9629 public Dictionary CreateTracingStats() { foreach (BuildAnalyzerFactoryContext analyzerFactoryContext in _analyzersRegistry) diff --git a/src/Build/BuildCheck/Infrastructure/BuildEventsProcessor.cs b/src/Build/BuildCheck/Infrastructure/BuildEventsProcessor.cs index 723e0430636..9514f0a7ca0 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildEventsProcessor.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildEventsProcessor.cs @@ -29,12 +29,10 @@ internal class BuildEventsProcessor(BuildCheckCentralContext buildCheckCentralCo private readonly BuildCheckCentralContext _buildCheckCentralContext = buildCheckCentralContext; // This requires MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION set to 1 - public void ProcessEvaluationFinishedEventArgs( - IBuildAnalysisLoggingContext buildAnalysisContext, + internal void ProcessEvaluationFinishedEventArgs( + AnalyzerLoggingContext buildAnalysisContext, ProjectEvaluationFinishedEventArgs evaluationFinishedEventArgs) { - LoggingContext loggingContext = buildAnalysisContext.ToLoggingContext(); - Dictionary propertiesLookup = new Dictionary(); Internal.Utilities.EnumerateProperties(evaluationFinishedEventArgs.Properties, propertiesLookup, static (dict, kvp) => dict.Add(kvp.Key, kvp.Value)); @@ -42,7 +40,7 @@ public void ProcessEvaluationFinishedEventArgs( EvaluatedPropertiesAnalysisData analysisData = new(evaluationFinishedEventArgs.ProjectFile!, propertiesLookup); - _buildCheckCentralContext.RunEvaluatedPropertiesActions(analysisData, loggingContext, ReportResult); + _buildCheckCentralContext.RunEvaluatedPropertiesActions(analysisData, buildAnalysisContext, ReportResult); if (_buildCheckCentralContext.HasParsedItemsActions) { @@ -53,7 +51,7 @@ public void ProcessEvaluationFinishedEventArgs( ParsedItemsAnalysisData itemsAnalysisData = new(evaluationFinishedEventArgs.ProjectFile!, new ItemsHolder(xml.Items, xml.ItemGroups)); - _buildCheckCentralContext.RunParsedItemsActions(itemsAnalysisData, loggingContext, ReportResult); + _buildCheckCentralContext.RunParsedItemsActions(itemsAnalysisData, buildAnalysisContext, ReportResult); } } diff --git a/src/Build/BuildCheck/Infrastructure/ConfigurationProvider.cs b/src/Build/BuildCheck/Infrastructure/ConfigurationProvider.cs index d77f5c2ce16..67c2155500e 100644 --- a/src/Build/BuildCheck/Infrastructure/ConfigurationProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/ConfigurationProvider.cs @@ -15,11 +15,11 @@ namespace Microsoft.Build.BuildCheck.Infrastructure; -// TODO: https://github.com/dotnet/msbuild/issues/9628 -// Let's flip form statics to instance, with exposed interface (so that we can easily swap implementations) +// Let's flip form statics to instance, with exposed interface (so that we can easily swap implementations) +// Tracked via: https://github.com/dotnet/msbuild/issues/9828 internal static class ConfigurationProvider { - // TODO: This module should have a mechanism for removing unneeded configurations + // We might want to have a mechanism for removing unneeded configurations // (disabled rules and analyzers that need to run in different node) private static readonly Dictionary _editorConfig = LoadConfiguration(); @@ -54,7 +54,7 @@ private static Dictionary LoadConfiguration( if (!File.Exists(configPath)) { - // TODO: pass the current project path + // This is just a dummy implementation for testing purposes var dir = Environment.CurrentDirectory; configPath = Path.Combine(dir, configFileName); diff --git a/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs b/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs index 703d0b6bfa9..ca91897ad44 100644 --- a/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs +++ b/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs @@ -10,6 +10,7 @@ using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BuildCheck.Acquisition; using Microsoft.Build.BuildCheck.Infrastructure; +using Microsoft.Build.BuildCheck.Logging; using Microsoft.Build.Framework; namespace Microsoft.Build.Experimental.BuildCheck; @@ -28,7 +29,7 @@ internal enum BuildCheckDataSource internal interface IBuildCheckManager { void ProcessEvaluationFinishedEventArgs( - IBuildAnalysisLoggingContext buildAnalysisContext, + AnalyzerLoggingContext buildAnalysisContext, ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs); void SetDataSource(BuildCheckDataSource buildCheckDataSource); diff --git a/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs b/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs index d6685345652..00ed2266d09 100644 --- a/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs +++ b/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BuildCheck.Acquisition; +using Microsoft.Build.BuildCheck.Logging; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; @@ -17,7 +18,7 @@ internal class NullBuildCheckManager : IBuildCheckManager { public void Shutdown() { } - public void ProcessEvaluationFinishedEventArgs(IBuildAnalysisLoggingContext buildAnalysisContext, + public void ProcessEvaluationFinishedEventArgs(AnalyzerLoggingContext buildAnalysisContext, ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs) { } diff --git a/src/Build/BuildCheck/Infrastructure/TracingReporter.cs b/src/Build/BuildCheck/Infrastructure/TracingReporter.cs index 614a1711a77..2d6d850737b 100644 --- a/src/Build/BuildCheck/Infrastructure/TracingReporter.cs +++ b/src/Build/BuildCheck/Infrastructure/TracingReporter.cs @@ -11,7 +11,6 @@ namespace Microsoft.Build.BuildCheck.Infrastructure; internal class TracingReporter { - internal const string INFRA_STAT_NAME = "Infrastructure"; internal Dictionary TracingStats { get; } = new(); public void AddStats(string name, TimeSpan subtotal) diff --git a/src/Build/BuildCheck/Logging/AnalyzerLoggingContext.cs b/src/Build/BuildCheck/Logging/AnalyzerLoggingContext.cs index 7d1d5badc92..1b3eb9cb4bb 100644 --- a/src/Build/BuildCheck/Logging/AnalyzerLoggingContext.cs +++ b/src/Build/BuildCheck/Logging/AnalyzerLoggingContext.cs @@ -7,7 +7,7 @@ namespace Microsoft.Build.BuildCheck.Logging; -internal class AnalyzerLoggingContext : LoggingContext, IBuildAnalysisLoggingContext +internal class AnalyzerLoggingContext : LoggingContext { public AnalyzerLoggingContext(ILoggingService loggingService, BuildEventContext eventContext) : base(loggingService, eventContext) diff --git a/src/Build/BuildCheck/Logging/AnalyzerLoggingContextFactory.cs b/src/Build/BuildCheck/Logging/AnalyzerLoggingContextFactory.cs index 8c437f45174..baee4b681be 100644 --- a/src/Build/BuildCheck/Logging/AnalyzerLoggingContextFactory.cs +++ b/src/Build/BuildCheck/Logging/AnalyzerLoggingContextFactory.cs @@ -8,6 +8,6 @@ namespace Microsoft.Build.BuildCheck.Logging; internal class AnalyzerLoggingContextFactory(ILoggingService loggingService) : IBuildAnalysisLoggingContextFactory { - public IBuildAnalysisLoggingContext CreateLoggingContext(BuildEventContext eventContext) => + public AnalyzerLoggingContext CreateLoggingContext(BuildEventContext eventContext) => new AnalyzerLoggingContext(loggingService, eventContext); } diff --git a/src/Build/BuildCheck/Logging/BuildAnalysisLoggingContextExtensions.cs b/src/Build/BuildCheck/Logging/BuildAnalysisLoggingContextExtensions.cs deleted file mode 100644 index 4951fd7e3c6..00000000000 --- a/src/Build/BuildCheck/Logging/BuildAnalysisLoggingContextExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Experimental.BuildCheck; - -namespace Microsoft.Build.BuildCheck.Logging; - -internal static class BuildAnalysisLoggingContextExtensions -{ - public static LoggingContext ToLoggingContext(this IBuildAnalysisLoggingContext loggingContext) => - loggingContext as AnalyzerLoggingContext ?? - throw new InvalidOperationException("The logging context is not an AnalyzerLoggingContext"); -} diff --git a/src/Build/BuildCheck/Logging/IBuildAnalysisLoggingContext.cs b/src/Build/BuildCheck/Logging/IBuildAnalysisLoggingContext.cs deleted file mode 100644 index c7433a14eb9..00000000000 --- a/src/Build/BuildCheck/Logging/IBuildAnalysisLoggingContext.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Build.Experimental.BuildCheck; - -public interface IBuildAnalysisLoggingContext -{ } diff --git a/src/Build/BuildCheck/Logging/IBuildAnalysisLoggingContextFactory.cs b/src/Build/BuildCheck/Logging/IBuildAnalysisLoggingContextFactory.cs index e239c8dc73d..e5188703ff7 100644 --- a/src/Build/BuildCheck/Logging/IBuildAnalysisLoggingContextFactory.cs +++ b/src/Build/BuildCheck/Logging/IBuildAnalysisLoggingContextFactory.cs @@ -1,11 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Build.BuildCheck.Logging; using Microsoft.Build.Framework; namespace Microsoft.Build.Experimental.BuildCheck; -public interface IBuildAnalysisLoggingContextFactory +internal interface IBuildAnalysisLoggingContextFactory { - IBuildAnalysisLoggingContext CreateLoggingContext(BuildEventContext eventContext); + AnalyzerLoggingContext CreateLoggingContext(BuildEventContext eventContext); } diff --git a/src/Build/BuildCheck/OM/BuildCheckDataContext.cs b/src/Build/BuildCheck/OM/BuildCheckDataContext.cs index d9a560c7e60..d0738fe6b71 100644 --- a/src/Build/BuildCheck/OM/BuildCheckDataContext.cs +++ b/src/Build/BuildCheck/OM/BuildCheckDataContext.cs @@ -25,6 +25,10 @@ public abstract class AnalysisData(string projectFilePath) public string ProjectFilePath { get; } = projectFilePath; } +/// +/// Data passed from infrastructure to build analyzers. +/// +/// The type of the actual data for analysis. public class BuildCheckDataContext where T : AnalysisData { private readonly BuildAnalyzerWrapper _analyzerWrapper; diff --git a/src/Build/BuildCheck/OM/EvaluatedPropertiesAnalysisData.cs b/src/Build/BuildCheck/OM/EvaluatedPropertiesAnalysisData.cs index 0a31bdf675a..f3a336a41ec 100644 --- a/src/Build/BuildCheck/OM/EvaluatedPropertiesAnalysisData.cs +++ b/src/Build/BuildCheck/OM/EvaluatedPropertiesAnalysisData.cs @@ -5,6 +5,10 @@ using Microsoft.Build.BackEnd.Logging; namespace Microsoft.Build.Experimental.BuildCheck; + +/// +/// BuildCheck OM data representing the evaluated properties of a project. +/// public class EvaluatedPropertiesAnalysisData : AnalysisData { internal EvaluatedPropertiesAnalysisData( diff --git a/src/Build/BuildCheck/OM/ParsedItemsAnalysisData.cs b/src/Build/BuildCheck/OM/ParsedItemsAnalysisData.cs index 62a0e588ae3..a6b34446258 100644 --- a/src/Build/BuildCheck/OM/ParsedItemsAnalysisData.cs +++ b/src/Build/BuildCheck/OM/ParsedItemsAnalysisData.cs @@ -7,20 +7,29 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Collections; using Microsoft.Build.Construction; namespace Microsoft.Build.Experimental.BuildCheck; +/// +/// Extension methods for . +/// public static class ItemTypeExtensions { public static IEnumerable GetItemsOfType(this IEnumerable items, string itemType) { return items.Where(i => - i.ItemType.Equals(itemType, StringComparison.CurrentCultureIgnoreCase)); + MSBuildNameIgnoreCaseComparer.Default.Equals(i.ItemType, itemType)); } } +/// +/// Holder for evaluated items and item groups. +/// +/// +/// public class ItemsHolder(IEnumerable items, IEnumerable itemGroups) { public IEnumerable Items { get; } = items; @@ -32,6 +41,9 @@ public IEnumerable GetItemsOfType(string itemType) } } +/// +/// BuildCheck OM data representing the evaluated items of a project. +/// public class ParsedItemsAnalysisData : AnalysisData { internal ParsedItemsAnalysisData( diff --git a/src/Build/BuildCheck/Utilities/EnumerableExtensions.cs b/src/Build/BuildCheck/Utilities/EnumerableExtensions.cs index 1a978642eb8..7ca6aeb69f9 100644 --- a/src/Build/BuildCheck/Utilities/EnumerableExtensions.cs +++ b/src/Build/BuildCheck/Utilities/EnumerableExtensions.cs @@ -16,25 +16,11 @@ internal static class EnumerableExtensions /// /// /// Sequence to be turned into csv string. - /// Indicates whether space should be inserted between comas and following items. + /// Indicates whether space should be inserted between commas and following items. /// Csv string. public static string ToCsvString(this IEnumerable? source, bool useSpace = true) { - return source == null ? "" : string.Join("," + (useSpace ? " " : string.Empty), source); - } - - /// - /// Performs an action for each element in given sequence. - /// - /// - /// - /// - public static void ForEach(this IEnumerable sequence, Action action) - { - foreach (T element in sequence) - { - action(element); - } + return source == null ? "" : string.Join(useSpace ? ", " : ",", source); } /// @@ -52,13 +38,13 @@ public static void Merge( { foreach (var pair in another) { - if (!dict.ContainsKey(pair.Key)) + if (!dict.TryGetValue(pair.Key, out TValue value)) { dict[pair.Key] = pair.Value; } else { - dict[pair.Key] = mergeValues(dict[pair.Key], pair.Value); + dict[pair.Key] = mergeValues(value, pair.Value); } } } diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 2daff2fe714..dc2d2ed13ca 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -183,7 +183,6 @@ - @@ -197,7 +196,6 @@ - diff --git a/src/Framework/BuildCheck/BuildCheckEventArgs.cs b/src/Framework/BuildCheck/BuildCheckEventArgs.cs index 106754be327..8a6faa13fb4 100644 --- a/src/Framework/BuildCheck/BuildCheckEventArgs.cs +++ b/src/Framework/BuildCheck/BuildCheckEventArgs.cs @@ -12,10 +12,18 @@ namespace Microsoft.Build.Experimental.BuildCheck; -public abstract class BuildCheckEventArgs : BuildEventArgs +/// +/// Base class for all build check event args. +/// Not intended to be extended by external code. +/// +internal abstract class BuildCheckEventArgs : BuildEventArgs { } -public sealed class BuildCheckTracingEventArgs(Dictionary tracingData) : BuildCheckEventArgs +/// +/// Transport mean for the BuildCheck tracing data from additional nodes. +/// +/// +internal sealed class BuildCheckTracingEventArgs(Dictionary tracingData) : BuildCheckEventArgs { internal BuildCheckTracingEventArgs() : this(new Dictionary()) { } @@ -50,7 +58,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) } } -public sealed class BuildCheckAcquisitionEventArgs(string acquisitionData) : BuildCheckEventArgs +internal sealed class BuildCheckAcquisitionEventArgs(string acquisitionData) : BuildCheckEventArgs { internal BuildCheckAcquisitionEventArgs() : this(string.Empty) { } @@ -71,7 +79,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) AcquisitionData = reader.ReadString(); } } -public sealed class BuildCheckResultWarning : BuildWarningEventArgs +internal sealed class BuildCheckResultWarning : BuildWarningEventArgs { public BuildCheckResultWarning(IBuildCheckResult result) { @@ -97,7 +105,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) public override string? Message { get; protected set; } } -public sealed class BuildCheckResultError : BuildErrorEventArgs +internal sealed class BuildCheckResultError : BuildErrorEventArgs { public BuildCheckResultError(IBuildCheckResult result) { @@ -123,7 +131,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) public override string? Message { get; protected set; } } -public sealed class BuildCheckResultMessage : BuildMessageEventArgs +internal sealed class BuildCheckResultMessage : BuildMessageEventArgs { public BuildCheckResultMessage(IBuildCheckResult result) { diff --git a/src/Framework/BuildCheck/IBuildCheckResult.cs b/src/Framework/BuildCheck/IBuildCheckResult.cs index e6f04518305..1d471e6c9bc 100644 --- a/src/Framework/BuildCheck/IBuildCheckResult.cs +++ b/src/Framework/BuildCheck/IBuildCheckResult.cs @@ -12,7 +12,7 @@ namespace Microsoft.Build.Experimental.BuildCheck; /// /// Holder for the reported result of a build cop rule. /// -public interface IBuildCheckResult +internal interface IBuildCheckResult { /// /// Optional location of the finding (in near future we might need to support multiple locations). diff --git a/src/Framework/IEventSource.cs b/src/Framework/IEventSource.cs index 146142b488a..8e5402b6fd3 100644 --- a/src/Framework/IEventSource.cs +++ b/src/Framework/IEventSource.cs @@ -80,7 +80,7 @@ namespace Microsoft.Build.Framework /// /// Type of handler for BuildCheckEventRaised events /// - public delegate void BuildCheckEventHandler(object sender, BuildCheckEventArgs e); + internal delegate void BuildCheckEventHandler(object sender, BuildCheckEventArgs e); /// /// This interface defines the events raised by the build engine. diff --git a/src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj b/src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj index 2ae43884646..8a2a558e452 100644 --- a/src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj +++ b/src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj @@ -1,6 +1,6 @@ - + - + $(RuntimeOutputTargetFrameworks) @@ -48,6 +48,6 @@ - + diff --git a/src/MSBuild/CommandLineSwitches.cs b/src/MSBuild/CommandLineSwitches.cs index d85b33302ed..e7800bdf4ca 100644 --- a/src/MSBuild/CommandLineSwitches.cs +++ b/src/MSBuild/CommandLineSwitches.cs @@ -268,7 +268,7 @@ internal ParameterizedSwitchInfo( new ParameterizedSwitchInfo( new string[] { "warnnotaserror", "noerr" }, ParameterizedSwitch.WarningsNotAsErrors, null, true, "MissingWarnNotAsErrorParameterError", true, false), new ParameterizedSwitchInfo( new string[] { "warnasmessage", "nowarn" }, ParameterizedSwitch.WarningsAsMessages, null, true, "MissingWarnAsMessageParameterError", true, false), new ParameterizedSwitchInfo( new string[] { "binarylogger", "bl" }, ParameterizedSwitch.BinaryLogger, null, false, null, true, false), - new ParameterizedSwitchInfo( new string[] { "analyze", "al" }, ParameterizedSwitch.Analyze, null, false, null, true, false), + new ParameterizedSwitchInfo( new string[] { "analyze", }, ParameterizedSwitch.Analyze, null, false, null, true, false), new ParameterizedSwitchInfo( new string[] { "restore", "r" }, ParameterizedSwitch.Restore, null, false, null, true, false), new ParameterizedSwitchInfo( new string[] { "profileevaluation", "prof" }, ParameterizedSwitch.ProfileEvaluation, null, false, "MissingProfileParameterError", true, false), new ParameterizedSwitchInfo( new string[] { "restoreproperty", "rp" }, ParameterizedSwitch.RestoreProperty, null, true, "MissingPropertyError", true, false), diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index d2849ba5f45..52dfa6daf87 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -2745,7 +2745,7 @@ private static bool ProcessCommandLineSwitches( private static bool IsBuildCheckEnabled(CommandLineSwitches commandLineSwitches) { - // todo: opt-in behavior: https://github.com/dotnet/msbuild/issues/9723 + // Opt-in behavior to be determined by: https://github.com/dotnet/msbuild/issues/9723 bool isAnalysisEnabled = commandLineSwitches.IsParameterizedSwitchSet(CommandLineSwitches.ParameterizedSwitch.Analyze); return isAnalysisEnabled; } diff --git a/src/Shared/IsExternalInit.cs b/src/Shared/IsExternalInit.cs index 92d5c4c320a..ae2ffb321cd 100644 --- a/src/Shared/IsExternalInit.cs +++ b/src/Shared/IsExternalInit.cs @@ -3,5 +3,7 @@ namespace System.Runtime.CompilerServices { + // Needed so we can use init setters in full fw or netstandard + // (details: https://developercommunity.visualstudio.com/t/error-cs0518-predefined-type-systemruntimecompiler/1244809) internal static class IsExternalInit { } } diff --git a/src/UnitTests.Shared/AssemblyInfo.cs b/src/UnitTests.Shared/BootstrapLocationAttribute.cs similarity index 82% rename from src/UnitTests.Shared/AssemblyInfo.cs rename to src/UnitTests.Shared/BootstrapLocationAttribute.cs index 5b383e24105..7f8627a69b3 100644 --- a/src/UnitTests.Shared/AssemblyInfo.cs +++ b/src/UnitTests.Shared/BootstrapLocationAttribute.cs @@ -6,9 +6,8 @@ namespace Microsoft.Build.UnitTests.Shared; [System.AttributeUsage(System.AttributeTargets.Assembly)] -internal sealed class BootstrapLocationAttribute(string bootstrapRoot, string bootstrapMsbuildBinaryLocation) +internal sealed class BootstrapLocationAttribute(string bootstrapMsbuildBinaryLocation) : System.Attribute { - public string BootstrapRoot { get; } = bootstrapRoot; public string BootstrapMsbuildBinaryLocation { get; } = bootstrapMsbuildBinaryLocation; } diff --git a/src/UnitTests.Shared/Microsoft.Build.UnitTests.Shared.csproj b/src/UnitTests.Shared/Microsoft.Build.UnitTests.Shared.csproj index 9a63822e930..fee3abf670f 100644 --- a/src/UnitTests.Shared/Microsoft.Build.UnitTests.Shared.csproj +++ b/src/UnitTests.Shared/Microsoft.Build.UnitTests.Shared.csproj @@ -28,12 +28,11 @@ - + - <_Parameter1>$(ArtifactsBinDir) - <_Parameter2>$(BootstrapBinaryDestination) + <_Parameter1>$(BootstrapBinaryDestination) diff --git a/template_feed/Microsoft.AnalyzerTemplate/.template.config/template.json b/template_feed/Microsoft.AnalyzerTemplate/.template.config/template.json index 071723c97cc..52c4467e930 100644 --- a/template_feed/Microsoft.AnalyzerTemplate/.template.config/template.json +++ b/template_feed/Microsoft.AnalyzerTemplate/.template.config/template.json @@ -27,7 +27,7 @@ "type": "parameter", "description": "Overrides the default Microsoft.Build version where analyzer's interfaces are placed", "datatype": "text", - "defaultValue": "17.9.5", + "defaultValue": "17.11.0", "replaces": "1.0.0-MicrosoftBuildPackageVersion", "displayName": "Microsoft.Build default package version override" } diff --git a/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj b/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj index 6de9fb1f434..b18c65a87dd 100644 --- a/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj +++ b/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj @@ -1,17 +1,17 @@ - netstandard2.0 - true - false - True - + netstandard2.0 + true + false + True + NU5101;NU5128 - - + + @@ -20,26 +20,26 @@ - - - - - - - - <_PackagesToPack Remove="@(_PackagesToPack)" Condition="%(NuGetPackageId) == 'NETStandard.Library'" /> - <_PackagesToPack Remove="@(_PackagesToPack)" Condition="%(_PackagesToPack.IncludeInPackage) != 'true'" /> - - - - - - - - - - - - + + + + + + + <_PackagesToPack Remove="@(_PackagesToPack)" Condition="%(NuGetPackageId) == 'NETStandard.Library'" /> + <_PackagesToPack Remove="@(_PackagesToPack)" Condition="%(_PackagesToPack.IncludeInPackage) != 'true'" /> + + + + + + + + + + + + diff --git a/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props b/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props index 8de4380640c..3b752b831cc 100644 --- a/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props +++ b/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props @@ -1,5 +1,5 @@ - + $([MSBuild]::RegisterAnalyzer($(MSBuildThisFileDirectory)..\lib\Company.AnalyzerTemplate.dll)) From d97a6118caf9a2a07277706f9d1de6d45fced589 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 4 Apr 2024 18:51:54 +0200 Subject: [PATCH 33/58] Add test without analysis --- src/Analyzers.UnitTests/EndToEndTests.cs | 22 ++++++++++++++----- .../Utilities/EnumerableExtensions.cs | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Analyzers.UnitTests/EndToEndTests.cs b/src/Analyzers.UnitTests/EndToEndTests.cs index 6a90e2734bb..8457ff1d90c 100644 --- a/src/Analyzers.UnitTests/EndToEndTests.cs +++ b/src/Analyzers.UnitTests/EndToEndTests.cs @@ -30,9 +30,10 @@ public EndToEndTests(ITestOutputHelper output) public void Dispose() => _env.Dispose(); [Theory] - [InlineData(true)] - [InlineData(false)] - public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode) + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(false, false)] + public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode, bool analysisRequested) { string contents = $""" @@ -118,11 +119,20 @@ public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode) _env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", buildInOutOfProcessNode ? "1" : "0"); _env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); - string output = RunnerUtilities.ExecBootstrapedMSBuild($"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -analyze", out bool success); + string output = RunnerUtilities.ExecBootstrapedMSBuild( + $"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore" + + (analysisRequested ? " -analyze" : string.Empty), out bool success); _env.Output.WriteLine(output); success.ShouldBeTrue(); - // The conflicting outputs warning appears - output.ShouldContain("BC0101"); + // The conflicting outputs warning appears - but only if analysis was requested + if (analysisRequested) + { + output.ShouldContain("BC0101"); + } + else + { + output.ShouldNotContain("BC0101"); + } } } } diff --git a/src/Build/BuildCheck/Utilities/EnumerableExtensions.cs b/src/Build/BuildCheck/Utilities/EnumerableExtensions.cs index 7ca6aeb69f9..96efc8ff2fd 100644 --- a/src/Build/BuildCheck/Utilities/EnumerableExtensions.cs +++ b/src/Build/BuildCheck/Utilities/EnumerableExtensions.cs @@ -38,7 +38,7 @@ public static void Merge( { foreach (var pair in another) { - if (!dict.TryGetValue(pair.Key, out TValue value)) + if (!dict.TryGetValue(pair.Key, out TValue? value)) { dict[pair.Key] = pair.Value; } From e18f00ff47e93fb9f0edbeccdfa20ca21e62eb89 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 4 Apr 2024 18:56:03 +0200 Subject: [PATCH 34/58] Rename tests project --- MSBuild.sln | 50 +++++++++---------- .../AssemblyInfo.cs | 0 .../EndToEndTests.cs | 2 +- ...crosoft.Build.BuildCheck.UnitTests.csproj} | 0 src/Framework/Properties/AssemblyInfo.cs | 2 +- 5 files changed, 27 insertions(+), 27 deletions(-) rename src/{Analyzers.UnitTests => BuildCheck.UnitTests}/AssemblyInfo.cs (100%) rename src/{Analyzers.UnitTests => BuildCheck.UnitTests}/EndToEndTests.cs (99%) rename src/{Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj => BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj} (100%) diff --git a/MSBuild.sln b/MSBuild.sln index 2254de84835..d1daf054f9e 100644 --- a/MSBuild.sln +++ b/MSBuild.sln @@ -80,7 +80,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSBuild.VSSetup.Arm64", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.UnitTests.Shared", "src\UnitTests.Shared\Microsoft.Build.UnitTests.Shared.csproj", "{52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Analyzers.UnitTests", "src\Analyzers.UnitTests\Microsoft.Build.Analyzers.UnitTests.csproj", "{B18BAE17-D78F-4F89-B7A4-808C05E64D73}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.BuildCheck.UnitTests", "src\BuildCheck.UnitTests\Microsoft.Build.BuildCheck.UnitTests.csproj", "{434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -866,30 +866,30 @@ Global {52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}.Release|x64.Build.0 = Release|x64 {52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}.Release|x86.ActiveCfg = Release|Any CPU {52A0B9C1-23B7-4CCC-B3FC-BDBA1C619E2A}.Release|x86.Build.0 = Release|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|ARM64.ActiveCfg = Debug|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|ARM64.Build.0 = Debug|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x64.ActiveCfg = Debug|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x64.Build.0 = Debug|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x86.ActiveCfg = Debug|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Debug|x86.Build.0 = Debug|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|Any CPU.ActiveCfg = MachineIndependent|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|Any CPU.Build.0 = MachineIndependent|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|ARM64.ActiveCfg = MachineIndependent|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|ARM64.Build.0 = MachineIndependent|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|x64.ActiveCfg = MachineIndependent|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|x64.Build.0 = MachineIndependent|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|x86.ActiveCfg = MachineIndependent|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.MachineIndependent|x86.Build.0 = MachineIndependent|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|Any CPU.Build.0 = Release|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|ARM64.ActiveCfg = Release|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|ARM64.Build.0 = Release|arm64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x64.ActiveCfg = Release|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x64.Build.0 = Release|x64 - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x86.ActiveCfg = Release|Any CPU - {B18BAE17-D78F-4F89-B7A4-808C05E64D73}.Release|x86.Build.0 = Release|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Debug|ARM64.ActiveCfg = Debug|arm64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Debug|ARM64.Build.0 = Debug|arm64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Debug|x64.ActiveCfg = Debug|x64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Debug|x64.Build.0 = Debug|x64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Debug|x86.ActiveCfg = Debug|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Debug|x86.Build.0 = Debug|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.MachineIndependent|Any CPU.ActiveCfg = MachineIndependent|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.MachineIndependent|Any CPU.Build.0 = MachineIndependent|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.MachineIndependent|ARM64.ActiveCfg = MachineIndependent|arm64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.MachineIndependent|ARM64.Build.0 = MachineIndependent|arm64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.MachineIndependent|x64.ActiveCfg = MachineIndependent|x64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.MachineIndependent|x64.Build.0 = MachineIndependent|x64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.MachineIndependent|x86.ActiveCfg = MachineIndependent|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.MachineIndependent|x86.Build.0 = MachineIndependent|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Release|Any CPU.Build.0 = Release|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Release|ARM64.ActiveCfg = Release|arm64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Release|ARM64.Build.0 = Release|arm64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Release|x64.ActiveCfg = Release|x64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Release|x64.Build.0 = Release|x64 + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Release|x86.ActiveCfg = Release|Any CPU + {434CC6DB-1E66-4FB1-A66C-D5BBE99F0ED8}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Analyzers.UnitTests/AssemblyInfo.cs b/src/BuildCheck.UnitTests/AssemblyInfo.cs similarity index 100% rename from src/Analyzers.UnitTests/AssemblyInfo.cs rename to src/BuildCheck.UnitTests/AssemblyInfo.cs diff --git a/src/Analyzers.UnitTests/EndToEndTests.cs b/src/BuildCheck.UnitTests/EndToEndTests.cs similarity index 99% rename from src/Analyzers.UnitTests/EndToEndTests.cs rename to src/BuildCheck.UnitTests/EndToEndTests.cs index 8457ff1d90c..f0fda0d4b29 100644 --- a/src/Analyzers.UnitTests/EndToEndTests.cs +++ b/src/BuildCheck.UnitTests/EndToEndTests.cs @@ -14,7 +14,7 @@ using Xunit; using Xunit.Abstractions; -namespace Microsoft.Build.Analyzers.UnitTests +namespace Microsoft.Build.BuildCheck.UnitTests { public class EndToEndTests : IDisposable { diff --git a/src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj b/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj similarity index 100% rename from src/Analyzers.UnitTests/Microsoft.Build.Analyzers.UnitTests.csproj rename to src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj diff --git a/src/Framework/Properties/AssemblyInfo.cs b/src/Framework/Properties/AssemblyInfo.cs index 790d9898146..633749084f0 100644 --- a/src/Framework/Properties/AssemblyInfo.cs +++ b/src/Framework/Properties/AssemblyInfo.cs @@ -45,7 +45,7 @@ [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Engine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] -[assembly: InternalsVisibleTo("Microsoft.Build.Analyzers.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] +[assembly: InternalsVisibleTo("Microsoft.Build.BuildCheck.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] // Ideally we wouldn't need to IVT to OM.UnitTests, which is supposed to test // only the public surface area of Microsoft.Build. However, there's a bunch // of shared code in Framework that's used there, and we can still avoid IVT From 368cb08fa02f125382896ad8a4abefcac182cf85 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 4 Apr 2024 19:45:08 +0200 Subject: [PATCH 35/58] Force case renaming --- eng/{BootStrapMSBuild.props => BootStrapMsBuild.props} | 0 eng/{BootStrapMSBuild.targets => BootStrapMsBuild.targets} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename eng/{BootStrapMSBuild.props => BootStrapMsBuild.props} (100%) rename eng/{BootStrapMSBuild.targets => BootStrapMsBuild.targets} (100%) diff --git a/eng/BootStrapMSBuild.props b/eng/BootStrapMsBuild.props similarity index 100% rename from eng/BootStrapMSBuild.props rename to eng/BootStrapMsBuild.props diff --git a/eng/BootStrapMSBuild.targets b/eng/BootStrapMsBuild.targets similarity index 100% rename from eng/BootStrapMSBuild.targets rename to eng/BootStrapMsBuild.targets From e1112bed7b36ef5c8fbad40d979c37e5685c3735 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Thu, 4 Apr 2024 20:26:21 +0200 Subject: [PATCH 36/58] pass logging context to inrinsic function --- .../BuildCheckAcquisitionModule.cs | 61 +- .../BuildCheckManagerProvider.cs | 545 +++++++++--------- src/Build/Evaluation/Evaluator.cs | 2 +- src/Build/Evaluation/Expander.cs | 78 ++- src/Build/Evaluation/IntrinsicFunctions.cs | 44 +- .../LazyItemEvaluator.LazyItemOperation.cs | 2 +- src/Build/Evaluation/LazyItemEvaluator.cs | 3 +- 7 files changed, 381 insertions(+), 354 deletions(-) diff --git a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs index aafe4099278..08e8c753bdc 100644 --- a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs @@ -11,48 +11,53 @@ using Microsoft.Build.BuildCheck.Analyzers; using Microsoft.Build.BuildCheck.Infrastructure; using Microsoft.Build.Experimental.BuildCheck; +using Microsoft.Build.Shared; -namespace Microsoft.Build.BuildCheck.Acquisition; - -internal class BuildCheckAcquisitionModule +namespace Microsoft.Build.BuildCheck.Acquisition { - private static T Construct() where T : new() => new(); - - public BuildAnalyzerFactory CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData) + internal class BuildCheckAcquisitionModule { - try +#if FEATURE_ASSEMBLYLOADCONTEXT + /// + /// AssemblyContextLoader used to load DLLs outside of msbuild.exe directory + /// + private static readonly CoreClrAssemblyLoader s_coreClrAssemblyLoader = new CoreClrAssemblyLoader(); +#endif + public BuildAnalyzerFactory? CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData) { - Assembly? assembly = null; + try + { + Assembly? assembly = null; #if FEATURE_ASSEMBLYLOADCONTEXT - assembly = s_coreClrAssemblyLoader.LoadFromPath(assemblyPath); + assembly = s_coreClrAssemblyLoader.LoadFromPath(analyzerAcquisitionData.AssemblyPath); #else - assembly = Assembly.LoadFrom(analyzerAcquisitionData.AssemblyPath); + assembly = Assembly.LoadFrom(analyzerAcquisitionData.AssemblyPath); #endif - Type type = assembly.GetTypes().FirstOrDefault(); + Type? analyzerType = assembly.GetTypes().FirstOrDefault(t => typeof(BuildAnalyzer).IsAssignableFrom(t)); - if (type != null) - { - // Check if the type is assignable to T - if (!typeof(BuildAnalyzer).IsAssignableFrom(type)) - { - throw new ArgumentException($"The type is not assignable to {typeof(BuildAnalyzer).FullName}"); - } - else + if (analyzerType != null) { - // ??? how to instantiate + return () => + { + return Activator.CreateInstance(analyzerType) is not BuildAnalyzer instance + ? throw new InvalidOperationException($"Failed to create an instance of type {analyzerType.FullName} as BuildAnalyzer.") + : instance; + }; } } - } - catch (ReflectionTypeLoadException ex) - { - Console.WriteLine("Failed to load one or more types from the assembly:"); - foreach (Exception loaderException in ex.LoaderExceptions) + catch (ReflectionTypeLoadException ex) { - Console.WriteLine(loaderException.Message); + if (ex.LoaderExceptions.Length != 0) + { + foreach (Exception? loaderException in ex.LoaderExceptions) + { + Console.WriteLine(loaderException?.Message ?? "Unknown error occurred."); + } + } } - } - return null; + return null; + } } } diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 541df4b8fa5..df7263e2d4d 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -23,351 +23,358 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; -namespace Microsoft.Build.BuildCheck.Infrastructure; - -internal delegate BuildAnalyzer BuildAnalyzerFactory(); -internal delegate BuildAnalyzerWrapper BuildAnalyzerWrapperFactory(ConfigurationContext configurationContext); - -/// -/// The central manager for the BuildCheck - this is the integration point with MSBuild infrastructure. -/// -internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider +namespace Microsoft.Build.BuildCheck.Infrastructure { - private static int s_isInitialized = 0; - private static IBuildCheckManager s_globalInstance = new NullBuildCheckManager(); - internal static IBuildCheckManager GlobalInstance => s_isInitialized != 0 ? s_globalInstance : throw new InvalidOperationException("BuildCheckManagerProvider not initialized"); + internal delegate BuildAnalyzer BuildAnalyzerFactory(); - public IBuildCheckManager Instance => GlobalInstance; + internal delegate BuildAnalyzerWrapper BuildAnalyzerWrapperFactory(ConfigurationContext configurationContext); - internal static IBuildComponent CreateComponent(BuildComponentType type) + /// + /// The central manager for the BuildCheck - this is the integration point with MSBuild infrastructure. + /// + internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider { - ErrorUtilities.VerifyThrow(type == BuildComponentType.BuildCheck, "Cannot create components of type {0}", type); - return new BuildCheckManagerProvider(); - } + private static int s_isInitialized = 0; - public void InitializeComponent(IBuildComponentHost host) - { - ErrorUtilities.VerifyThrow(host != null, "BuildComponentHost was null"); + private static IBuildCheckManager s_globalInstance = new NullBuildCheckManager(); - if (Interlocked.CompareExchange(ref s_isInitialized, 1, 0) == 1) - { - // Already initialized - return; - } + public IBuildCheckManager Instance => GlobalInstance; - if (host!.BuildParameters.IsBuildCheckEnabled) - { - s_globalInstance = new BuildCheckManager(host.LoggingService); - } - else + internal static IBuildCheckManager GlobalInstance => s_isInitialized != 0 ? s_globalInstance : throw new InvalidOperationException("BuildCheckManagerProvider not initialized"); + + internal static IBuildComponent CreateComponent(BuildComponentType type) { - s_globalInstance = new NullBuildCheckManager(); + ErrorUtilities.VerifyThrow(type == BuildComponentType.BuildCheck, "Cannot create components of type {0}", type); + return new BuildCheckManagerProvider(); } - } - public void ShutdownComponent() => GlobalInstance.Shutdown(); + public void InitializeComponent(IBuildComponentHost host) + { + ErrorUtilities.VerifyThrow(host != null, "BuildComponentHost was null"); + if (Interlocked.CompareExchange(ref s_isInitialized, 1, 0) == 1) + { + // Already initialized + return; + } - private sealed class BuildCheckManager : IBuildCheckManager - { - private readonly TracingReporter _tracingReporter = new TracingReporter(); - private readonly BuildCheckCentralContext _buildCheckCentralContext = new(); - private readonly ILoggingService _loggingService; - private readonly List _analyzersRegistry =[]; - private readonly bool[] _enabledDataSources = new bool[(int)BuildCheckDataSource.ValuesCount]; - private readonly BuildEventsProcessor _buildEventsProcessor; - private readonly BuildCheckAcquisitionModule _acquisitionModule = new(); - - private bool IsInProcNode => _enabledDataSources[(int)BuildCheckDataSource.EventArgs] && - _enabledDataSources[(int)BuildCheckDataSource.BuildExecution]; - - /// - /// Notifies the manager that the data source will be used - - /// so it should register the built-in analyzers for the source if it hasn't been done yet. - /// - /// - public void SetDataSource(BuildCheckDataSource buildCheckDataSource) - { - if (!_enabledDataSources[(int)buildCheckDataSource]) + if (host!.BuildParameters.IsBuildCheckEnabled) { - _enabledDataSources[(int)buildCheckDataSource] = true; - RegisterBuiltInAnalyzers(buildCheckDataSource); + s_globalInstance = new BuildCheckManager(host.LoggingService); + } + else + { + s_globalInstance = new NullBuildCheckManager(); } } - public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) + public void ShutdownComponent() => GlobalInstance.Shutdown(); + + + private sealed class BuildCheckManager : IBuildCheckManager { - if (IsInProcNode) + private readonly TracingReporter _tracingReporter = new TracingReporter(); + private readonly BuildCheckCentralContext _buildCheckCentralContext = new(); + private readonly ILoggingService _loggingService; + private readonly List _analyzersRegistry =[]; + private readonly bool[] _enabledDataSources = new bool[(int)BuildCheckDataSource.ValuesCount]; + private readonly BuildEventsProcessor _buildEventsProcessor; + private readonly BuildCheckAcquisitionModule _acquisitionModule = new(); + + private bool IsInProcNode => _enabledDataSources[(int)BuildCheckDataSource.EventArgs] && + _enabledDataSources[(int)BuildCheckDataSource.BuildExecution]; + + /// + /// Notifies the manager that the data source will be used - + /// so it should register the built-in analyzers for the source if it hasn't been done yet. + /// + /// + public void SetDataSource(BuildCheckDataSource buildCheckDataSource) { - var factory = _acquisitionModule.CreateBuildAnalyzerFactory(acquisitionData); - RegisterCustomAnalyzer(BuildCheckDataSource.EventArgs, factory); + if (!_enabledDataSources[(int)buildCheckDataSource]) + { + _enabledDataSources[(int)buildCheckDataSource] = true; + RegisterBuiltInAnalyzers(buildCheckDataSource); + } } - else + + public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) { - BuildCheckAcquisitionEventArgs eventArgs = acquisitionData.ToBuildEventArgs(); + if (IsInProcNode) + { + BuildAnalyzerFactory? factory = _acquisitionModule.CreateBuildAnalyzerFactory(acquisitionData); + if (factory != null) + { + RegisterCustomAnalyzer(BuildCheckDataSource.EventArgs, factory); + } + } + else + { + BuildCheckAcquisitionEventArgs eventArgs = acquisitionData.ToBuildEventArgs(); - // TODO: We may want to pass the real context here (from evaluation) - eventArgs.BuildEventContext = new BuildEventContext( - BuildEventContext.InvalidNodeId, - BuildEventContext.InvalidProjectInstanceId, - BuildEventContext.InvalidProjectContextId, - BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidTaskId); + // TODO: We may want to pass the real context here (from evaluation) + eventArgs.BuildEventContext = new BuildEventContext( + BuildEventContext.InvalidNodeId, + BuildEventContext.InvalidProjectInstanceId, + BuildEventContext.InvalidProjectContextId, + BuildEventContext.InvalidTargetId, + BuildEventContext.InvalidTaskId); - _loggingService.LogBuildEvent(eventArgs); + _loggingService.LogBuildEvent(eventArgs); + } } - } - internal BuildCheckManager(ILoggingService loggingService) - { - _loggingService = loggingService; - _buildEventsProcessor = new(_buildCheckCentralContext); - } + internal BuildCheckManager(ILoggingService loggingService) + { + _loggingService = loggingService; + _buildEventsProcessor = new(_buildCheckCentralContext); + } - private static T Construct() where T : new() => new(); - private static readonly (string[] ruleIds, bool defaultEnablement, BuildAnalyzerFactory factory)[][] s_builtInFactoriesPerDataSource = - [ - // BuildCheckDataSource.EventArgs + private static T Construct() where T : new() => new(); + private static readonly (string[] ruleIds, bool defaultEnablement, BuildAnalyzerFactory factory)[][] s_builtInFactoriesPerDataSource = [ - ([SharedOutputPathAnalyzer.SupportedRule.Id], SharedOutputPathAnalyzer.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct) - ], - // BuildCheckDataSource.Execution - [] - ]; - - private void RegisterBuiltInAnalyzers(BuildCheckDataSource buildCheckDataSource) - { - _analyzersRegistry.AddRange( - s_builtInFactoriesPerDataSource[(int)buildCheckDataSource] - .Select(v => new BuildAnalyzerFactoryContext(v.factory, v.ruleIds, v.defaultEnablement))); - } - - /// - /// To be used by acquisition module - /// Registeres the custom analyzer, the construction of analyzer is deferred until the first using project is encountered - /// - internal void RegisterCustomAnalyzer( - BuildCheckDataSource buildCheckDataSource, - BuildAnalyzerFactory factory, - string[] ruleIds, - bool defaultEnablement) - { - if (_enabledDataSources[(int)buildCheckDataSource]) + // BuildCheckDataSource.EventArgs + [ + ([SharedOutputPathAnalyzer.SupportedRule.Id], SharedOutputPathAnalyzer.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct) + ], + // BuildCheckDataSource.Execution + [] + ]; + + private void RegisterBuiltInAnalyzers(BuildCheckDataSource buildCheckDataSource) { - _analyzersRegistry.Add(new BuildAnalyzerFactoryContext(factory, ruleIds, defaultEnablement)); + _analyzersRegistry.AddRange( + s_builtInFactoriesPerDataSource[(int)buildCheckDataSource] + .Select(v => new BuildAnalyzerFactoryContext(v.factory, v.ruleIds, v.defaultEnablement))); } - } - /// - /// To be used by acquisition module - /// Registeres the custom analyzer, the construction of analyzer is needed during registration. - /// - internal void RegisterCustomAnalyzer( - BuildCheckDataSource buildCheckDataSource, - BuildAnalyzerFactory factory) - { - if (_enabledDataSources[(int)buildCheckDataSource]) + /// + /// To be used by acquisition module + /// Registeres the custom analyzer, the construction of analyzer is deferred until the first using project is encountered + /// + internal void RegisterCustomAnalyzer( + BuildCheckDataSource buildCheckDataSource, + BuildAnalyzerFactory factory, + string[] ruleIds, + bool defaultEnablement) { - var instance = factory(); - _analyzersRegistry.Add(new BuildAnalyzerFactoryContext( - factory, - instance.SupportedRules.Select(r => r.Id).ToArray(), - instance.SupportedRules.Any(r => r.DefaultConfiguration.IsEnabled == true))); + if (_enabledDataSources[(int)buildCheckDataSource]) + { + _analyzersRegistry.Add(new BuildAnalyzerFactoryContext(factory, ruleIds, defaultEnablement)); + } } - } - private void SetupSingleAnalyzer(BuildAnalyzerFactoryContext analyzerFactoryContext, string projectFullPath, BuildEventContext buildEventContext) - { - // TODO: For user analyzers - it should run only on projects where referenced - // on others it should work similarly as disabling them. - // Disabled analyzer should not only post-filter results - it shouldn't even see the data + /// + /// To be used by acquisition module + /// Registeres the custom analyzer, the construction of analyzer is needed during registration. + /// + internal void RegisterCustomAnalyzer( + BuildCheckDataSource buildCheckDataSource, + BuildAnalyzerFactory factory) + { + if (_enabledDataSources[(int)buildCheckDataSource]) + { + var instance = factory(); + _analyzersRegistry.Add(new BuildAnalyzerFactoryContext( + factory, + instance.SupportedRules.Select(r => r.Id).ToArray(), + instance.SupportedRules.Any(r => r.DefaultConfiguration.IsEnabled == true))); + } + } - BuildAnalyzerWrapper wrapper; - BuildAnalyzerConfigurationInternal[] configurations; - if (analyzerFactoryContext.MaterializedAnalyzer == null) + private void SetupSingleAnalyzer(BuildAnalyzerFactoryContext analyzerFactoryContext, string projectFullPath, BuildEventContext buildEventContext) { - BuildAnalyzerConfiguration[] userConfigs = - ConfigurationProvider.GetUserConfigurations(projectFullPath, analyzerFactoryContext.RuleIds); + // TODO: For user analyzers - it should run only on projects where referenced + // on others it should work similarly as disabling them. + // Disabled analyzer should not only post-filter results - it shouldn't even see the data - if (userConfigs.All(c => !(c.IsEnabled ?? analyzerFactoryContext.IsEnabledByDefault))) + BuildAnalyzerWrapper wrapper; + BuildAnalyzerConfigurationInternal[] configurations; + if (analyzerFactoryContext.MaterializedAnalyzer == null) { - // the analyzer was not yet instantiated nor mounted - so nothing to do here now. - return; + BuildAnalyzerConfiguration[] userConfigs = + ConfigurationProvider.GetUserConfigurations(projectFullPath, analyzerFactoryContext.RuleIds); + + if (userConfigs.All(c => !(c.IsEnabled ?? analyzerFactoryContext.IsEnabledByDefault))) + { + // the analyzer was not yet instantiated nor mounted - so nothing to do here now. + return; + } + + CustomConfigurationData[] customConfigData = + ConfigurationProvider.GetCustomConfigurations(projectFullPath, analyzerFactoryContext.RuleIds); + + ConfigurationContext configurationContext = ConfigurationContext.FromDataEnumeration(customConfigData); + + wrapper = analyzerFactoryContext.Factory(configurationContext); + analyzerFactoryContext.MaterializedAnalyzer = wrapper; + BuildAnalyzer analyzer = wrapper.BuildAnalyzer; + + if ( + analyzer.SupportedRules.Count != analyzerFactoryContext.RuleIds.Length + || + !analyzer.SupportedRules.Select(r => r.Id) + .SequenceEqual(analyzerFactoryContext.RuleIds, StringComparer.CurrentCultureIgnoreCase) + ) + { + throw new BuildCheckConfigurationException( + $"The analyzer '{analyzer.FriendlyName}' exposes rules '{analyzer.SupportedRules.Select(r => r.Id).ToCsvString()}', but different rules were declared during registration: '{analyzerFactoryContext.RuleIds.ToCsvString()}'"); + } + + configurations = ConfigurationProvider.GetMergedConfigurations(userConfigs, analyzer); + + // technically all analyzers rules could be disabled, but that would mean + // that the provided 'IsEnabledByDefault' value wasn't correct - the only + // price to be paid in that case is slight performance cost. + + // Create the wrapper and register to central context + wrapper.StartNewProject(projectFullPath, configurations); + var wrappedContext = new BuildCheckRegistrationContext(wrapper, _buildCheckCentralContext); + analyzer.RegisterActions(wrappedContext); } + else + { + wrapper = analyzerFactoryContext.MaterializedAnalyzer; - CustomConfigurationData[] customConfigData = - ConfigurationProvider.GetCustomConfigurations(projectFullPath, analyzerFactoryContext.RuleIds); + configurations = ConfigurationProvider.GetMergedConfigurations(projectFullPath, wrapper.BuildAnalyzer); - ConfigurationContext configurationContext = ConfigurationContext.FromDataEnumeration(customConfigData); + ConfigurationProvider.CheckCustomConfigurationDataValidity(projectFullPath, + analyzerFactoryContext.RuleIds[0]); - wrapper = analyzerFactoryContext.Factory(configurationContext); - analyzerFactoryContext.MaterializedAnalyzer = wrapper; - BuildAnalyzer analyzer = wrapper.BuildAnalyzer; + // Update the wrapper + wrapper.StartNewProject(projectFullPath, configurations); + } - if ( - analyzer.SupportedRules.Count != analyzerFactoryContext.RuleIds.Length - || - !analyzer.SupportedRules.Select(r => r.Id) - .SequenceEqual(analyzerFactoryContext.RuleIds, StringComparer.CurrentCultureIgnoreCase) - ) + if (configurations.GroupBy(c => c.EvaluationAnalysisScope).Count() > 1) { throw new BuildCheckConfigurationException( - $"The analyzer '{analyzer.FriendlyName}' exposes rules '{analyzer.SupportedRules.Select(r => r.Id).ToCsvString()}', but different rules were declared during registration: '{analyzerFactoryContext.RuleIds.ToCsvString()}'"); + string.Format("All rules for a single analyzer should have the same EvaluationAnalysisScope for a single project (violating rules: [{0}], project: {1})", + analyzerFactoryContext.RuleIds.ToCsvString(), + projectFullPath)); } - - configurations = ConfigurationProvider.GetMergedConfigurations(userConfigs, analyzer); - - // technically all analyzers rules could be disabled, but that would mean - // that the provided 'IsEnabledByDefault' value wasn't correct - the only - // price to be paid in that case is slight performance cost. - - // Create the wrapper and register to central context - wrapper.StartNewProject(projectFullPath, configurations); - var wrappedContext = new BuildCheckRegistrationContext(wrapper, _buildCheckCentralContext); - analyzer.RegisterActions(wrappedContext); } - else - { - wrapper = analyzerFactoryContext.MaterializedAnalyzer; - configurations = ConfigurationProvider.GetMergedConfigurations(projectFullPath, wrapper.BuildAnalyzer); + private void SetupAnalyzersForNewProject(string projectFullPath, BuildEventContext buildEventContext) + { + // Only add analyzers here + // On an execution node - we might remove and dispose the analyzers once project is done - ConfigurationProvider.CheckCustomConfigurationDataValidity(projectFullPath, - analyzerFactoryContext.RuleIds[0]); + // If it's already constructed - just control the custom settings do not differ - // Update the wrapper - wrapper.StartNewProject(projectFullPath, configurations); - } + List analyzersToRemove = new(); + foreach (BuildAnalyzerFactoryContext analyzerFactoryContext in _analyzersRegistry) + { + try + { + SetupSingleAnalyzer(analyzerFactoryContext, projectFullPath, buildEventContext); + } + catch (BuildCheckConfigurationException e) + { + _loggingService.LogErrorFromText(buildEventContext, null, null, null, + new BuildEventFileInfo(projectFullPath), + e.Message); + _loggingService.LogCommentFromText(buildEventContext, MessageImportance.High, $"Dismounting analyzer '{analyzerFactoryContext.FriendlyName}'"); + analyzersToRemove.Add(analyzerFactoryContext); + } + } - if (configurations.GroupBy(c => c.EvaluationAnalysisScope).Count() > 1) - { - throw new BuildCheckConfigurationException( - string.Format("All rules for a single analyzer should have the same EvaluationAnalysisScope for a single project (violating rules: [{0}], project: {1})", - analyzerFactoryContext.RuleIds.ToCsvString(), - projectFullPath)); + analyzersToRemove.ForEach(c => _analyzersRegistry.Remove(c)); + foreach (var analyzerToRemove in analyzersToRemove.Select(a => a.MaterializedAnalyzer).Where(a => a != null)) + { + _buildCheckCentralContext.DeregisterAnalyzer(analyzerToRemove!); + _tracingReporter.AddStats(analyzerToRemove!.BuildAnalyzer.FriendlyName, analyzerToRemove.Elapsed); + analyzerToRemove.BuildAnalyzer.Dispose(); + } } - } - private void SetupAnalyzersForNewProject(string projectFullPath, BuildEventContext buildEventContext) - { - // Only add analyzers here - // On an execution node - we might remove and dispose the analyzers once project is done - // If it's already constructed - just control the custom settings do not differ + public void ProcessEvaluationFinishedEventArgs( + IBuildAnalysisLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs evaluationFinishedEventArgs) + => _buildEventsProcessor + .ProcessEvaluationFinishedEventArgs(buildAnalysisContext, evaluationFinishedEventArgs); - List analyzersToRemove = new(); - foreach (BuildAnalyzerFactoryContext analyzerFactoryContext in _analyzersRegistry) + // TODO: tracing: https://github.com/dotnet/msbuild/issues/9629 + public Dictionary CreateTracingStats() { - try - { - SetupSingleAnalyzer(analyzerFactoryContext, projectFullPath, buildEventContext); - } - catch (BuildCheckConfigurationException e) + foreach (BuildAnalyzerFactoryContext analyzerFactoryContext in _analyzersRegistry) { - _loggingService.LogErrorFromText(buildEventContext, null, null, null, - new BuildEventFileInfo(projectFullPath), - e.Message); - _loggingService.LogCommentFromText(buildEventContext, MessageImportance.High, $"Dismounting analyzer '{analyzerFactoryContext.FriendlyName}'"); - analyzersToRemove.Add(analyzerFactoryContext); + if (analyzerFactoryContext.MaterializedAnalyzer != null) + { + _tracingReporter.AddStats(analyzerFactoryContext.FriendlyName, + analyzerFactoryContext.MaterializedAnalyzer.Elapsed); + analyzerFactoryContext.MaterializedAnalyzer.ClearStats(); + } } - } - analyzersToRemove.ForEach(c => _analyzersRegistry.Remove(c)); - foreach (var analyzerToRemove in analyzersToRemove.Select(a => a.MaterializedAnalyzer).Where(a => a != null)) - { - _buildCheckCentralContext.DeregisterAnalyzer(analyzerToRemove!); - _tracingReporter.AddStats(analyzerToRemove!.BuildAnalyzer.FriendlyName, analyzerToRemove.Elapsed); - analyzerToRemove.BuildAnalyzer.Dispose(); + return _tracingReporter.TracingStats; } - } + public void FinalizeProcessing(LoggingContext loggingContext) + { + if (IsInProcNode) + { + // We do not want to send tracing stats from in-proc node + return; + } - public void ProcessEvaluationFinishedEventArgs( - IBuildAnalysisLoggingContext buildAnalysisContext, - ProjectEvaluationFinishedEventArgs evaluationFinishedEventArgs) - => _buildEventsProcessor - .ProcessEvaluationFinishedEventArgs(buildAnalysisContext, evaluationFinishedEventArgs); + BuildCheckTracingEventArgs eventArgs = + new(CreateTracingStats()) { BuildEventContext = loggingContext.BuildEventContext }; + loggingContext.LogBuildEvent(eventArgs); + } - // TODO: tracing: https://github.com/dotnet/msbuild/issues/9629 - public Dictionary CreateTracingStats() - { - foreach (BuildAnalyzerFactoryContext analyzerFactoryContext in _analyzersRegistry) + public void StartProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext, + string fullPath) { - if (analyzerFactoryContext.MaterializedAnalyzer != null) + if (buildCheckDataSource == BuildCheckDataSource.EventArgs && IsInProcNode) { - _tracingReporter.AddStats(analyzerFactoryContext.FriendlyName, - analyzerFactoryContext.MaterializedAnalyzer.Elapsed); - analyzerFactoryContext.MaterializedAnalyzer.ClearStats(); + // Skipping this event - as it was already handled by the in-proc node. + // This is because in-proc node has the BuildEventArgs source and BuildExecution source + // both in a single manager. The project started is first encountered by the execution before the EventArg is sent + return; } + + SetupAnalyzersForNewProject(fullPath, buildEventContext); } - return _tracingReporter.TracingStats; - } + /* + * + * Following methods are for future use (should we decide to approach in-execution analysis) + * + */ - public void FinalizeProcessing(LoggingContext loggingContext) - { - if (IsInProcNode) + + public void EndProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) { - // We do not want to send tracing stats from in-proc node - return; } - BuildCheckTracingEventArgs eventArgs = - new(CreateTracingStats()) { BuildEventContext = loggingContext.BuildEventContext }; - loggingContext.LogBuildEvent(eventArgs); - } - - public void StartProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext, - string fullPath) - { - if (buildCheckDataSource == BuildCheckDataSource.EventArgs && IsInProcNode) + public void StartProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) { - // Skipping this event - as it was already handled by the in-proc node. - // This is because in-proc node has the BuildEventArgs source and BuildExecution source - // both in a single manager. The project started is first encountered by the execution before the EventArg is sent - return; } - SetupAnalyzersForNewProject(fullPath, buildEventContext); - } - - /* - * - * Following methods are for future use (should we decide to approach in-execution analysis) - * - */ - - - public void EndProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) - { - } - - public void StartProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) - { - } - - public void EndProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) - { - } + public void EndProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext) + { + } - public void Shutdown() - { /* Too late here for any communication to the main node or for logging anything */ } + public void Shutdown() + { /* Too late here for any communication to the main node or for logging anything */ } - private class BuildAnalyzerFactoryContext( - BuildAnalyzerFactory factory, - string[] ruleIds, - bool isEnabledByDefault) - { - public BuildAnalyzerWrapperFactory Factory { get; init; } = configContext => + private class BuildAnalyzerFactoryContext( + BuildAnalyzerFactory factory, + string[] ruleIds, + bool isEnabledByDefault) { - BuildAnalyzer ba = factory(); - ba.Initialize(configContext); - return new BuildAnalyzerWrapper(ba); - }; - public BuildAnalyzerWrapper? MaterializedAnalyzer { get; set; } - public string[] RuleIds { get; init; } = ruleIds; - public bool IsEnabledByDefault { get; init; } = isEnabledByDefault; - public string FriendlyName => MaterializedAnalyzer?.BuildAnalyzer.FriendlyName ?? factory().FriendlyName; + public BuildAnalyzerWrapperFactory Factory { get; init; } = configContext => + { + BuildAnalyzer ba = factory(); + ba.Initialize(configContext); + return new BuildAnalyzerWrapper(ba); + }; + public BuildAnalyzerWrapper? MaterializedAnalyzer { get; set; } + public string[] RuleIds { get; init; } = ruleIds; + public bool IsEnabledByDefault { get; init; } = isEnabledByDefault; + public string FriendlyName => MaterializedAnalyzer?.BuildAnalyzer.FriendlyName ?? factory().FriendlyName; + } } } } diff --git a/src/Build/Evaluation/Evaluator.cs b/src/Build/Evaluation/Evaluator.cs index 27bcd78bcba..85447378533 100644 --- a/src/Build/Evaluation/Evaluator.cs +++ b/src/Build/Evaluation/Evaluator.cs @@ -242,7 +242,7 @@ private Evaluator( // Create containers for the evaluation results data.InitializeForEvaluation(toolsetProvider, _evaluationContext); - _expander = new Expander(data, data, _evaluationContext); + _expander = new Expander(data, data, _evaluationContext, _evaluationLoggingContext); // This setting may change after the build has started, therefore if the user has not set the property to true on the build parameters we need to check to see if it is set to true on the environment variable. _expander.WarnForUninitializedProperties = BuildParameters.WarnOnUninitializedProperty || Traits.Instance.EscapeHatches.WarnOnUninitializedProperty; diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index b98f902e994..f30ccbd0844 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; @@ -12,6 +13,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using Microsoft.Build.BackEnd.Components.Logging; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation.Context; @@ -311,11 +313,14 @@ private void FlushFirstValueIfNeeded() private readonly IFileSystem _fileSystem; + private readonly LoggingContext _loggingContext; + /// /// Non-null if the expander was constructed for evaluation. /// internal EvaluationContext EvaluationContext { get; } + /// /// Creates an expander passing it some properties to use. /// Properties may be null. @@ -350,6 +355,7 @@ internal Expander(IPropertyProvider

properties, IItemProvider items, IFile } ///

+ /// Initializes a new instance of the class. /// Creates an expander passing it some properties and items to use, and the evaluation context. /// Either or both may be null. /// @@ -359,6 +365,21 @@ internal Expander(IPropertyProvider

properties, IItemProvider items, Evalu _items = items; } + ///

+ /// Initializes a new instance of the class with the specified property provider, item provider, evaluation context, and logging context. + /// + /// The property provider supplying properties for expansion. + /// The item provider supplying items for expansion. + /// The evaluation context used during expansion. + /// The logging context used for logging or emmitting events during expansion. + /// Thrown when either or is null. + internal Expander(IPropertyProvider

properties, IItemProvider items, EvaluationContext evaluationContext, LoggingContext loggingContext) + : this(properties, evaluationContext) + { + _items = items; + _loggingContext = loggingContext; + } + ///

/// Creates an expander passing it some properties, items, and/or metadata to use. /// Any or all may be null. @@ -1253,7 +1274,8 @@ internal static object ExpandPropertiesLeaveTypedAndEscaped( options, elementLocation, usedUninitializedProperties, - fileSystem); + fileSystem, + loggingContext); } else // This is a regular property { @@ -1301,7 +1323,8 @@ internal static object ExpandPropertyBody( ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties, - IFileSystem fileSystem) + IFileSystem fileSystem, + LoggingContext loggingContext) { Function function = null; string propertyName = propertyBody; @@ -1332,7 +1355,8 @@ internal static object ExpandPropertyBody( elementLocation, propertyValue, usedUninitializedProperties, - fileSystem); + fileSystem, + loggingContext); // We may not have been able to parse out a function if (function != null) @@ -1371,7 +1395,8 @@ internal static object ExpandPropertyBody( options, elementLocation, usedUninitializedProperties, - fileSystem); + fileSystem, + loggingContext); } } else @@ -2726,7 +2751,8 @@ internal static IEnumerable> ExecuteStringFunction( BindingFlags.Public | BindingFlags.InvokeMethod, string.Empty, expander.UsedUninitializedProperties, - expander._fileSystem); + expander._fileSystem, + expander._loggingContext); object result = function.Execute(item.Key, expander._properties, ExpanderOptions.ExpandAll, elementLocation); @@ -3151,6 +3177,8 @@ private struct FunctionBuilder public IFileSystem FileSystem { get; set; } + public LoggingContext LoggingContext { get; set; } + /// /// List of properties which have been used but have not been initialized yet. /// @@ -3167,7 +3195,8 @@ internal readonly Function Build() BindingFlags, Remainder, UsedUninitializedProperties, - FileSystem); + FileSystem, + LoggingContext); } } @@ -3187,22 +3216,22 @@ internal class Function /// /// The name of the function. /// - private string _methodMethodName; + private readonly string _methodMethodName; /// /// The arguments for the function. /// - private string[] _arguments; + private readonly string[] _arguments; /// /// The expression that this function is part of. /// - private string _expression; + private readonly string _expression; /// /// The property name that this function is applied on. /// - private string _receiver; + private readonly string _receiver; /// /// The binding flags that will be used during invocation of this function. @@ -3212,14 +3241,16 @@ internal class Function /// /// The remainder of the body once the function and arguments have been extracted. /// - private string _remainder; + private readonly string _remainder; /// /// List of properties which have been used but have not been initialized yet. /// - private UsedUninitializedProperties _usedUninitializedProperties; + private readonly UsedUninitializedProperties _usedUninitializedProperties; + + private readonly IFileSystem _fileSystem; - private IFileSystem _fileSystem; + private readonly LoggingContext _loggingContext; /// /// Construct a function that will be executed during property evaluation. @@ -3233,7 +3264,8 @@ internal Function( BindingFlags bindingFlags, string remainder, UsedUninitializedProperties usedUninitializedProperties, - IFileSystem fileSystem) + IFileSystem fileSystem, + LoggingContext loggingContext) { _methodMethodName = methodName; if (arguments == null) @@ -3252,6 +3284,7 @@ internal Function( _remainder = remainder; _usedUninitializedProperties = usedUninitializedProperties; _fileSystem = fileSystem; + _loggingContext = loggingContext; } /// @@ -3274,10 +3307,11 @@ internal static Function ExtractPropertyFunction( IElementLocation elementLocation, object propertyValue, UsedUninitializedProperties usedUnInitializedProperties, - IFileSystem fileSystem) + IFileSystem fileSystem, + LoggingContext loggingContext) { // Used to aggregate all the components needed for a Function - FunctionBuilder functionBuilder = new FunctionBuilder { FileSystem = fileSystem }; + FunctionBuilder functionBuilder = new FunctionBuilder { FileSystem = fileSystem, LoggingContext = loggingContext }; // By default the expression root is the whole function expression ReadOnlySpan expressionRoot = expressionFunction == null ? ReadOnlySpan.Empty : expressionFunction.AsSpan(); @@ -3583,7 +3617,8 @@ internal object Execute(object objectInstance, IPropertyProvider properties, options, elementLocation, _usedUninitializedProperties, - _fileSystem); + _fileSystem, + _loggingContext); } // Exceptions coming from the actual function called are wrapped in a TargetInvocationException @@ -3877,6 +3912,15 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst } else if (_receiverType == typeof(IntrinsicFunctions)) { + if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.RegisterAnalyzer), StringComparison.OrdinalIgnoreCase)) + { + if (TryGetArg(args, out string arg0) && _loggingContext != null) + { + returnVal = IntrinsicFunctions.RegisterAnalyzer(arg0, _loggingContext); + return true; + } + } + if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.EnsureTrailingSlash), StringComparison.OrdinalIgnoreCase)) { if (TryGetArg(args, out string arg0)) diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index 77b48115c6d..a2ade0332e1 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.InteropServices; @@ -10,6 +11,8 @@ using System.Text; using System.Text.RegularExpressions; using Microsoft.Build.BackEnd.Components.Logging; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; using Microsoft.Build.Internal; @@ -621,47 +624,14 @@ public static bool IsRunningFromVisualStudio() return BuildEnvironmentHelper.Instance.Mode == BuildEnvironmentMode.VisualStudio; } - public static bool RegisterAnalyzer(string pathToAssembly) + public static bool RegisterAnalyzer(string pathToAssembly, LoggingContext loggingContext) { pathToAssembly = FileUtilities.GetFullPathNoThrow(pathToAssembly); - - try + if (File.Exists(pathToAssembly)) { - if (File.Exists(pathToAssembly)) - { - Assembly assembly = null; -#if FEATURE_ASSEMBLYLOADCONTEXT - Console.WriteLine($"Hi from FEATURE_ASSEMBLYLOADCONTEXT."); - assembly = s_coreClrAssemblyLoader.LoadFromPath(pathToAssembly); -#else - assembly = Assembly.LoadFrom(pathToAssembly); -#endif - Console.WriteLine($"Loaded assembly: {assembly.FullName}"); - - Type type = assembly.GetTypes()[0]; - object instance = Activator.CreateInstance(type); + loggingContext.LogBuildEvent(new BuildCheckAcquisitionEventArgs(pathToAssembly)); - PropertyInfo property = type.GetProperty("Name"); - var value = property.GetValue(instance); - Console.WriteLine($"Loaded property analyzer name: {value}"); - - // need to have a logging context here. - new BuildCheckAcquisitionEventArgs(pathToAssembly); - - return true; - } - } - catch (ReflectionTypeLoadException ex) - { - Console.WriteLine("Failed to load one or more types from the assembly:"); - foreach (Exception loaderException in ex.LoaderExceptions) - { - Console.WriteLine(loaderException.Message); - } - } - catch (Exception ex) - { - Console.WriteLine($"Failed to load assembly '{pathToAssembly}': {ex.Message}"); + return true; } return false; diff --git a/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs index 485162b1638..6bf5b325d63 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs @@ -45,7 +45,7 @@ protected LazyItemOperation(OperationBuilder builder, LazyItemEvaluator(_evaluatorData, _evaluatorData, _lazyEvaluator.EvaluationContext); + _expander = new Expander(_evaluatorData, _evaluatorData, _lazyEvaluator.EvaluationContext, _lazyEvaluator._loggingContext); _itemSpec.Expander = _expander; } diff --git a/src/Build/Evaluation/LazyItemEvaluator.cs b/src/Build/Evaluation/LazyItemEvaluator.cs index bd34997b839..bb298ee7662 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.cs @@ -46,12 +46,13 @@ internal partial class LazyItemEvaluator protected EvaluationContext EvaluationContext { get; } protected IFileSystem FileSystem => EvaluationContext.FileSystem; + protected FileMatcher FileMatcher => EvaluationContext.FileMatcher; public LazyItemEvaluator(IEvaluatorData data, IItemFactory itemFactory, LoggingContext loggingContext, EvaluationProfiler evaluationProfiler, EvaluationContext evaluationContext) { _outerEvaluatorData = data; - _outerExpander = new Expander(_outerEvaluatorData, _outerEvaluatorData, evaluationContext); + _outerExpander = new Expander(_outerEvaluatorData, _outerEvaluatorData, evaluationContext, loggingContext); _evaluatorData = new EvaluatorData(_outerEvaluatorData, _itemLists); _expander = new Expander(_evaluatorData, _evaluatorData, evaluationContext); _itemFactory = itemFactory; From 7025f3a4fc43a4fe856293d523399e571dff3e3a Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Fri, 5 Apr 2024 11:16:50 +0200 Subject: [PATCH 37/58] Simplify GlobalInstance initialization --- .../BuildCheckManagerProvider.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 9cc89118eef..0c132ce056b 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -33,9 +33,8 @@ namespace Microsoft.Build.BuildCheck.Infrastructure; /// internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider { - private static int s_isInitialized = 0; - private static IBuildCheckManager s_globalInstance = new NullBuildCheckManager(); - internal static IBuildCheckManager GlobalInstance => s_isInitialized != 0 ? s_globalInstance : throw new InvalidOperationException("BuildCheckManagerProvider not initialized"); + private static IBuildCheckManager? s_globalInstance; + internal static IBuildCheckManager GlobalInstance => s_globalInstance ?? throw new InvalidOperationException("BuildCheckManagerProvider not initialized"); public IBuildCheckManager Instance => GlobalInstance; @@ -49,19 +48,21 @@ public void InitializeComponent(IBuildComponentHost host) { ErrorUtilities.VerifyThrow(host != null, "BuildComponentHost was null"); - if (Interlocked.CompareExchange(ref s_isInitialized, 1, 0) == 1) + if (s_globalInstance == null) { - // Initialization code already run(ing) - return; - } + IBuildCheckManager instance; + if (host!.BuildParameters.IsBuildCheckEnabled) + { + instance = new BuildCheckManager(host.LoggingService); + } + else + { + instance = new NullBuildCheckManager(); + } - if (host!.BuildParameters.IsBuildCheckEnabled) - { - s_globalInstance = new BuildCheckManager(host.LoggingService); - } - else - { - s_globalInstance = new NullBuildCheckManager(); + // We are fine with the possibility of double creation here - as the construction is cheap + // and without side effects and the actual backing field is effectively immutable after the first assignment. + Interlocked.CompareExchange(ref s_globalInstance, instance, null); } } From aeb41072668d679693ac454553c88368fddef262 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Fri, 5 Apr 2024 14:03:36 +0200 Subject: [PATCH 38/58] Remove multiple registrations checking --- .../Infrastructure/BuildCheckContext.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckContext.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckContext.cs index 3546a6ab7b8..19a4e3d6967 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckContext.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckContext.cs @@ -9,28 +9,13 @@ namespace Microsoft.Build.BuildCheck.Infrastructure; internal sealed class BuildCheckRegistrationContext(BuildAnalyzerWrapper analyzerWrapper, BuildCheckCentralContext buildCheckCentralContext) : IBuildCheckRegistrationContext { - private int _evaluatedPropertiesActionCount; - private int _parsedItemsActionCount; - public void RegisterEvaluatedPropertiesAction(Action> evaluatedPropertiesAction) { - if (Interlocked.Increment(ref _evaluatedPropertiesActionCount) > 1) - { - throw new BuildCheckConfigurationException( - $"Analyzer '{analyzerWrapper.BuildAnalyzer.FriendlyName}' attempted to call '{nameof(RegisterEvaluatedPropertiesAction)}' multiple times."); - } - buildCheckCentralContext.RegisterEvaluatedPropertiesAction(analyzerWrapper, evaluatedPropertiesAction); } public void RegisterParsedItemsAction(Action> parsedItemsAction) { - if (Interlocked.Increment(ref _parsedItemsActionCount) > 1) - { - throw new BuildCheckConfigurationException( - $"Analyzer '{analyzerWrapper.BuildAnalyzer.FriendlyName}' attempted to call '{nameof(RegisterParsedItemsAction)}' multiple times."); - } - buildCheckCentralContext.RegisterParsedItemsAction(analyzerWrapper, parsedItemsAction); } } From 01510e2df40b98d0ed1453bbd1aa0dff3e453aa2 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Fri, 5 Apr 2024 17:59:00 +0200 Subject: [PATCH 39/58] Reflect on PR feedback --- src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs | 3 +++ .../BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs b/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs index 92673cf7f79..06e0aaa2439 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs @@ -25,6 +25,8 @@ public BuildAnalyzerWrapper(BuildAnalyzer buildAnalyzer) internal BuildAnalyzer BuildAnalyzer { get; } private bool _isInitialized = false; + // Let's optimize for the scenario where users have a single .editorconfig file that applies to the whole solution. + // In such case - configuration will be same for all projects. So we do not need to store it per project in a collection. internal BuildAnalyzerConfigurationInternal? CommonConfig { get; private set; } // start new project @@ -43,6 +45,7 @@ internal void StartNewProject( } } + // The Common configuration is not common anymore - let's nullify it and we will need to fetch configuration per project. if (CommonConfig == null || !userConfigs.All(t => t.IsSameConfigurationAs(CommonConfig))) { CommonConfig = null; diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs index 20d095fc889..b8f276884e3 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs @@ -50,6 +50,7 @@ private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) } else if (e is ProjectEvaluationStartedEventArgs projectEvaluationStartedEventArgs) { + // Skip autogenerated transient projects (as those are not user projects to be analyzed) if (projectEvaluationStartedEventArgs.ProjectFile?.EndsWith(".metaproj") ?? false) { return; From 1d7c4c635287a08e383a4f4486d7c4e949327a9e Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Fri, 5 Apr 2024 18:01:21 +0200 Subject: [PATCH 40/58] Apply suggestions from code review Co-authored-by: Mariana Dematte --- src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs | 2 +- src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs b/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs index 872aba3440f..f9c6fb6f75e 100644 --- a/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs +++ b/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs @@ -10,7 +10,7 @@ namespace Microsoft.Build.BuildCheck.Acquisition; -// https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=52643036 +// https://github.com/dotnet/msbuild/issues/9633 // Acquisition // define the data that will be passed to the acquisition module (and remoted if needed) internal class AnalyzerAcquisitionData(string data) diff --git a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs index 60744d8aa38..e2ab0ff880c 100644 --- a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs @@ -16,7 +16,7 @@ internal class BuildCheckAcquisitionModule private static T Construct() where T : new() => new(); public BuildAnalyzerFactory CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData) { - // Acquisition module - https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=52643036 + // Acquisition module - https://github.com/dotnet/msbuild/issues/9633 return Construct; } } From 25b5f75df908bf749b1283d76b37996c6a02bb67 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Fri, 5 Apr 2024 18:03:55 +0200 Subject: [PATCH 41/58] Reflect on PR comments --- src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs | 2 +- src/Build/BuildCheck/API/EvaluationAnalysisScope.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs b/src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs index 03cb9381080..4940db20fd1 100644 --- a/src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs +++ b/src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs @@ -16,7 +16,7 @@ public class BuildAnalyzerConfiguration // nor in the editorconfig configuration file. public static BuildAnalyzerConfiguration Default { get; } = new() { - EvaluationAnalysisScope = BuildCheck.EvaluationAnalysisScope.AnalyzedProjectOnly, + EvaluationAnalysisScope = BuildCheck.EvaluationAnalysisScope.ProjectOnly, Severity = BuildAnalyzerResultSeverity.Info, IsEnabled = false, }; diff --git a/src/Build/BuildCheck/API/EvaluationAnalysisScope.cs b/src/Build/BuildCheck/API/EvaluationAnalysisScope.cs index 7ff6471a39a..39781649359 100644 --- a/src/Build/BuildCheck/API/EvaluationAnalysisScope.cs +++ b/src/Build/BuildCheck/API/EvaluationAnalysisScope.cs @@ -14,20 +14,20 @@ public enum EvaluationAnalysisScope /// /// Only the data from currently analyzed project will be sent to the analyzer. Imports will be discarded. /// - AnalyzedProjectOnly, + ProjectOnly, /// /// Only the data from currently analyzed project and imports from files under the entry project or solution will be sent to the analyzer. Other imports will be discarded. /// - AnalyzedProjectWithImportsFromCurrentWorkTree, + ProjectWithImportsFromCurrentWorkTree, /// /// Imports from SDKs will not be sent to the analyzer. Other imports will be sent. /// - AnalyzedProjectWithImportsWithoutSdks, + ProjectWithImportsWithoutSdks, /// /// All data will be sent to the analyzer. /// - AnalyzedProjectWithAllImports, + ProjectWithAllImports, } From 53a7c8bfe7005d04c0f1fe247f9e03d7961f16f4 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Mon, 8 Apr 2024 13:15:58 +0200 Subject: [PATCH 42/58] implement full aquisition flow --- .../Logging/EvaluationLoggingContext.cs | 6 +- .../Components/Logging/EventSourceSink.cs | 1 + .../API/BuildAnalyzerConfiguration.cs | 2 +- .../BuildCheck/API/EvaluationAnalysisScope.cs | 8 +- .../Acquisition/AnalyzerAcquisitionData.cs | 2 +- .../BuildCheckAcquisitionModule.cs | 13 +- .../Infrastructure/BuildAnalyzerWrapper.cs | 3 + .../BuildCheckConnectorLogger.cs | 138 +++++++++--------- .../Infrastructure/BuildCheckContext.cs | 15 -- .../BuildCheckManagerProvider.cs | 47 +++--- .../Infrastructure/NullBuildCheckManager.cs | 11 +- src/Build/Evaluation/Expander.cs | 3 - src/Build/Evaluation/IntrinsicFunctions.cs | 6 +- .../BuildCheck/BuildCheckEventArgs.cs | 2 +- .../Microsoft.AnalyzerTemplate/Analyzer1.cs | 25 ++-- 15 files changed, 134 insertions(+), 148 deletions(-) diff --git a/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs b/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs index 9a4a3f7bd58..d9cb65d4b93 100644 --- a/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs @@ -33,8 +33,12 @@ public void LogProjectEvaluationStarted() } /// - /// Log that the project has finished + /// Logs that the project evaluation has finished. /// + /// Global properties used in the project evaluation. + /// Properties used in the project evaluation. + /// Items used in the project evaluation. + /// Parameter contains the profiler result of the project evaluation. internal void LogProjectEvaluationFinished(IEnumerable globalProperties, IEnumerable properties, IEnumerable items, ProfilerResult? profilerResult) { ErrorUtilities.VerifyThrow(IsValid, "invalid"); diff --git a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs index 1a16e9f2190..308e26cae4d 100644 --- a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs +++ b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs @@ -311,6 +311,7 @@ internal void UnregisterAllEventHandlers() StatusEventRaised = null; AnyEventRaised = null; TelemetryLogged = null; + BuildCheckEventRaised = null; } #endregion diff --git a/src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs b/src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs index 03cb9381080..4940db20fd1 100644 --- a/src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs +++ b/src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs @@ -16,7 +16,7 @@ public class BuildAnalyzerConfiguration // nor in the editorconfig configuration file. public static BuildAnalyzerConfiguration Default { get; } = new() { - EvaluationAnalysisScope = BuildCheck.EvaluationAnalysisScope.AnalyzedProjectOnly, + EvaluationAnalysisScope = BuildCheck.EvaluationAnalysisScope.ProjectOnly, Severity = BuildAnalyzerResultSeverity.Info, IsEnabled = false, }; diff --git a/src/Build/BuildCheck/API/EvaluationAnalysisScope.cs b/src/Build/BuildCheck/API/EvaluationAnalysisScope.cs index 7ff6471a39a..39781649359 100644 --- a/src/Build/BuildCheck/API/EvaluationAnalysisScope.cs +++ b/src/Build/BuildCheck/API/EvaluationAnalysisScope.cs @@ -14,20 +14,20 @@ public enum EvaluationAnalysisScope /// /// Only the data from currently analyzed project will be sent to the analyzer. Imports will be discarded. /// - AnalyzedProjectOnly, + ProjectOnly, /// /// Only the data from currently analyzed project and imports from files under the entry project or solution will be sent to the analyzer. Other imports will be discarded. /// - AnalyzedProjectWithImportsFromCurrentWorkTree, + ProjectWithImportsFromCurrentWorkTree, /// /// Imports from SDKs will not be sent to the analyzer. Other imports will be sent. /// - AnalyzedProjectWithImportsWithoutSdks, + ProjectWithImportsWithoutSdks, /// /// All data will be sent to the analyzer. /// - AnalyzedProjectWithAllImports, + ProjectWithAllImports, } diff --git a/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs b/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs index a746f581e14..125dc27458e 100644 --- a/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs +++ b/src/Build/BuildCheck/Acquisition/AnalyzerAcquisitionData.cs @@ -10,7 +10,7 @@ namespace Microsoft.Build.BuildCheck.Acquisition; -// https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=52643036 +// https://github.com/dotnet/msbuild/issues/9633 // Acquisition // define the data that will be passed to the acquisition module (and remoted if needed) internal class AnalyzerAcquisitionData(string assemblyPath) diff --git a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs index 08e8c753bdc..606759a34e2 100644 --- a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs @@ -2,13 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.Configuration.Assemblies; using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Build.BuildCheck.Analyzers; using Microsoft.Build.BuildCheck.Infrastructure; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Shared; @@ -21,7 +16,7 @@ internal class BuildCheckAcquisitionModule /// /// AssemblyContextLoader used to load DLLs outside of msbuild.exe directory /// - private static readonly CoreClrAssemblyLoader s_coreClrAssemblyLoader = new CoreClrAssemblyLoader(); + private static readonly CoreClrAssemblyLoader s_coreClrAssemblyLoader = new(); #endif public BuildAnalyzerFactory? CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData) { @@ -38,12 +33,9 @@ internal class BuildCheckAcquisitionModule if (analyzerType != null) { - return () => - { - return Activator.CreateInstance(analyzerType) is not BuildAnalyzer instance + return () => Activator.CreateInstance(analyzerType) is not BuildAnalyzer instance ? throw new InvalidOperationException($"Failed to create an instance of type {analyzerType.FullName} as BuildAnalyzer.") : instance; - }; } } catch (ReflectionTypeLoadException ex) @@ -52,6 +44,7 @@ internal class BuildCheckAcquisitionModule { foreach (Exception? loaderException in ex.LoaderExceptions) { + // How do we plan to handle these errors? Console.WriteLine(loaderException?.Message ?? "Unknown error occurred."); } } diff --git a/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs b/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs index 92673cf7f79..06e0aaa2439 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildAnalyzerWrapper.cs @@ -25,6 +25,8 @@ public BuildAnalyzerWrapper(BuildAnalyzer buildAnalyzer) internal BuildAnalyzer BuildAnalyzer { get; } private bool _isInitialized = false; + // Let's optimize for the scenario where users have a single .editorconfig file that applies to the whole solution. + // In such case - configuration will be same for all projects. So we do not need to store it per project in a collection. internal BuildAnalyzerConfigurationInternal? CommonConfig { get; private set; } // start new project @@ -43,6 +45,7 @@ internal void StartNewProject( } } + // The Common configuration is not common anymore - let's nullify it and we will need to fetch configuration per project. if (CommonConfig == null || !userConfigs.All(t => t.IsSameConfigurationAs(CommonConfig))) { CommonConfig = null; diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs index 68d924c9e5e..a5860c27bb4 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs @@ -3,101 +3,107 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BuildCheck.Acquisition; -using Microsoft.Build.BuildCheck.Logging; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; -namespace Microsoft.Build.BuildCheck.Infrastructure; - -internal sealed class BuildCheckConnectorLogger(IBuildAnalysisLoggingContextFactory loggingContextFactory, IBuildCheckManager buildCheckManager) - : ILogger +namespace Microsoft.Build.BuildCheck.Infrastructure { - public LoggerVerbosity Verbosity { get; set; } - - public string? Parameters { get; set; } - - public void Initialize(IEventSource eventSource) + internal sealed class BuildCheckConnectorLogger : ILogger { - eventSource.AnyEventRaised += EventSource_AnyEventRaised; - eventSource.BuildFinished += EventSource_BuildFinished; - } + private readonly Dictionary> _eventHandlers; + private readonly IBuildCheckManager _buildCheckManager; + private readonly IBuildAnalysisLoggingContextFactory _loggingContextFactory; - private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) - { - if (e is ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs) + internal BuildCheckConnectorLogger( + IBuildAnalysisLoggingContextFactory loggingContextFactory, + IBuildCheckManager buildCheckManager) { - if (projectEvaluationFinishedEventArgs.ProjectFile?.EndsWith(".metaproj") ?? false) - { - return; - } + _buildCheckManager = buildCheckManager; + _loggingContextFactory = loggingContextFactory; + _eventHandlers = GetBuildEventHandlers(); + } - try - { - buildCheckManager.ProcessEvaluationFinishedEventArgs( - loggingContextFactory.CreateLoggingContext(e.BuildEventContext!), - projectEvaluationFinishedEventArgs); - } - catch (Exception exception) - { - Debugger.Launch(); - Console.WriteLine(exception); - throw; - } + public LoggerVerbosity Verbosity { get; set; } - buildCheckManager.EndProjectEvaluation(BuildCheckDataSource.EventArgs, e.BuildEventContext!); - } - else if (e is ProjectEvaluationStartedEventArgs projectEvaluationStartedEventArgs) - { - if (projectEvaluationStartedEventArgs.ProjectFile?.EndsWith(".metaproj") ?? false) - { - return; - } + public string? Parameters { get; set; } - buildCheckManager.StartProjectEvaluation(BuildCheckDataSource.EventArgs, e.BuildEventContext!, projectEvaluationStartedEventArgs.ProjectFile!); + public void Initialize(IEventSource eventSource) + { + eventSource.AnyEventRaised += EventSource_AnyEventRaised; + eventSource.BuildFinished += EventSource_BuildFinished; } - else if (e is ProjectStartedEventArgs projectStartedEvent) + + public void Shutdown() { - buildCheckManager.StartProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!); } - else if (e is ProjectFinishedEventArgs projectFinishedEventArgs) + + private void HandleProjectEvaluationEvent(ProjectEvaluationFinishedEventArgs eventArgs) { - buildCheckManager.EndProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!); + if (!IsMetaProjFile(eventArgs.ProjectFile)) + { + try + { + _buildCheckManager.ProcessEvaluationFinishedEventArgs(_loggingContextFactory.CreateLoggingContext(eventArgs.BuildEventContext!), eventArgs); + } + catch (Exception exception) + { + Console.WriteLine(exception); + throw; + } + + _buildCheckManager.EndProjectEvaluation(BuildCheckDataSource.EventArgs, eventArgs.BuildEventContext!); + } } - else if (e is BuildCheckEventArgs buildCheckBuildEventArgs) + + private void HandleProjectEvaluationStartedEvent(ProjectEvaluationStartedEventArgs eventArgs) { - if (buildCheckBuildEventArgs is BuildCheckTracingEventArgs tracingEventArgs) + if (!IsMetaProjFile(eventArgs.ProjectFile)) { - _stats.Merge(tracingEventArgs.TracingData, (span1, span2) => span1 + span2); + _buildCheckManager.StartProjectEvaluation(BuildCheckDataSource.EventArgs, eventArgs.BuildEventContext!, eventArgs.ProjectFile!); } - else if (buildCheckBuildEventArgs is BuildCheckAcquisitionEventArgs acquisitionEventArgs) + } + + private bool IsMetaProjFile(string? projectFile) => !string.IsNullOrEmpty(projectFile) && projectFile!.EndsWith(".metaproj", StringComparison.OrdinalIgnoreCase); + + private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) + { + if (_eventHandlers.TryGetValue(e.GetType(), out Action? handler)) { - buildCheckManager.ProcessAnalyzerAcquisition(acquisitionEventArgs.ToAnalyzerAcquisitionData()); + handler(e); } } - } - private readonly Dictionary _stats = new Dictionary(); + private readonly Dictionary _stats = new Dictionary(); - private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e) - { - _stats.Merge(buildCheckManager.CreateTracingStats(), (span1, span2) => span1 + span2); - string msg = string.Join(Environment.NewLine, _stats.Select(a => a.Key + ": " + a.Value)); + private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e) + { + _stats.Merge(_buildCheckManager.CreateTracingStats(), (span1, span2) => span1 + span2); + string msg = string.Join(Environment.NewLine, _stats.Select(a => a.Key + ": " + a.Value)); + BuildEventContext buildEventContext = e.BuildEventContext + ?? new BuildEventContext( + BuildEventContext.InvalidNodeId, + BuildEventContext.InvalidTargetId, + BuildEventContext.InvalidProjectContextId, + BuildEventContext.InvalidTaskId); - BuildEventContext buildEventContext = e.BuildEventContext ?? new BuildEventContext( - BuildEventContext.InvalidNodeId, BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); + LoggingContext loggingContext = _loggingContextFactory.CreateLoggingContext(buildEventContext); - LoggingContext loggingContext = loggingContextFactory.CreateLoggingContext(buildEventContext); + // Tracing: https://github.com/dotnet/msbuild/issues/9629 + loggingContext.LogCommentFromText(MessageImportance.High, msg); + } - // Tracing: https://github.com/dotnet/msbuild/issues/9629 - loggingContext.LogCommentFromText(MessageImportance.High, msg); + private Dictionary> GetBuildEventHandlers() => new() + { + { typeof(ProjectEvaluationFinishedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationEvent((ProjectEvaluationFinishedEventArgs) e) }, + { typeof(ProjectEvaluationStartedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationStartedEvent((ProjectEvaluationStartedEventArgs) e) }, + { typeof(ProjectStartedEventArgs), (BuildEventArgs e) => _buildCheckManager.StartProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) }, + { typeof(ProjectFinishedEventArgs), (BuildEventArgs e) => _buildCheckManager.EndProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) }, + { typeof(BuildCheckTracingEventArgs), (BuildEventArgs e) => _stats.Merge(((BuildCheckTracingEventArgs)e).TracingData, (span1, span2) => span1 + span2) }, + { typeof(BuildCheckAcquisitionEventArgs), (BuildEventArgs e) => _buildCheckManager.ProcessAnalyzerAcquisition(((BuildCheckAcquisitionEventArgs)e).ToAnalyzerAcquisitionData()) }, + }; } - - public void Shutdown() - { } } diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckContext.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckContext.cs index 3546a6ab7b8..19a4e3d6967 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckContext.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckContext.cs @@ -9,28 +9,13 @@ namespace Microsoft.Build.BuildCheck.Infrastructure; internal sealed class BuildCheckRegistrationContext(BuildAnalyzerWrapper analyzerWrapper, BuildCheckCentralContext buildCheckCentralContext) : IBuildCheckRegistrationContext { - private int _evaluatedPropertiesActionCount; - private int _parsedItemsActionCount; - public void RegisterEvaluatedPropertiesAction(Action> evaluatedPropertiesAction) { - if (Interlocked.Increment(ref _evaluatedPropertiesActionCount) > 1) - { - throw new BuildCheckConfigurationException( - $"Analyzer '{analyzerWrapper.BuildAnalyzer.FriendlyName}' attempted to call '{nameof(RegisterEvaluatedPropertiesAction)}' multiple times."); - } - buildCheckCentralContext.RegisterEvaluatedPropertiesAction(analyzerWrapper, evaluatedPropertiesAction); } public void RegisterParsedItemsAction(Action> parsedItemsAction) { - if (Interlocked.Increment(ref _parsedItemsActionCount) > 1) - { - throw new BuildCheckConfigurationException( - $"Analyzer '{analyzerWrapper.BuildAnalyzer.FriendlyName}' attempted to call '{nameof(RegisterParsedItemsAction)}' multiple times."); - } - buildCheckCentralContext.RegisterParsedItemsAction(analyzerWrapper, parsedItemsAction); } } diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 9cc89118eef..79dcbbb820f 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -3,22 +3,13 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.Tracing; -using System.IO; using System.Linq; -using System.Runtime.ConstrainedExecution; using System.Threading; using Microsoft.Build.BackEnd; -using Microsoft.Build.BackEnd.Components.Caching; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BuildCheck.Acquisition; using Microsoft.Build.BuildCheck.Analyzers; using Microsoft.Build.BuildCheck.Logging; -using Microsoft.Build.Collections; -using Microsoft.Build.Construction; -using Microsoft.Build.Evaluation; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -33,9 +24,9 @@ namespace Microsoft.Build.BuildCheck.Infrastructure; /// internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider { - private static int s_isInitialized = 0; - private static IBuildCheckManager s_globalInstance = new NullBuildCheckManager(); - internal static IBuildCheckManager GlobalInstance => s_isInitialized != 0 ? s_globalInstance : throw new InvalidOperationException("BuildCheckManagerProvider not initialized"); + private static IBuildCheckManager? s_globalInstance; + + internal static IBuildCheckManager GlobalInstance => s_globalInstance ?? throw new InvalidOperationException("BuildCheckManagerProvider not initialized"); public IBuildCheckManager Instance => GlobalInstance; @@ -49,31 +40,32 @@ public void InitializeComponent(IBuildComponentHost host) { ErrorUtilities.VerifyThrow(host != null, "BuildComponentHost was null"); - if (Interlocked.CompareExchange(ref s_isInitialized, 1, 0) == 1) + if (s_globalInstance == null) { - // Initialization code already run(ing) - return; - } + IBuildCheckManager instance; + if (host!.BuildParameters.IsBuildCheckEnabled) + { + instance = new BuildCheckManager(host.LoggingService); + } + else + { + instance = new NullBuildCheckManager(); + } - if (host!.BuildParameters.IsBuildCheckEnabled) - { - s_globalInstance = new BuildCheckManager(host.LoggingService); - } - else - { - s_globalInstance = new NullBuildCheckManager(); + // We are fine with the possibility of double creation here - as the construction is cheap + // and without side effects and the actual backing field is effectively immutable after the first assignment. + Interlocked.CompareExchange(ref s_globalInstance, instance, null); } } public void ShutdownComponent() => GlobalInstance.Shutdown(); - private sealed class BuildCheckManager : IBuildCheckManager { private readonly TracingReporter _tracingReporter = new TracingReporter(); private readonly BuildCheckCentralContext _buildCheckCentralContext = new(); private readonly ILoggingService _loggingService; - private readonly List _analyzersRegistry =[]; + private readonly List _analyzersRegistry; private readonly bool[] _enabledDataSources = new bool[(int)BuildCheckDataSource.ValuesCount]; private readonly BuildEventsProcessor _buildEventsProcessor; private readonly BuildCheckAcquisitionModule _acquisitionModule = new(); @@ -100,7 +92,10 @@ public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) if (IsInProcNode) { var factory = _acquisitionModule.CreateBuildAnalyzerFactory(acquisitionData); - RegisterCustomAnalyzer(BuildCheckDataSource.EventArgs, factory); + if (factory != null) + { + RegisterCustomAnalyzer(BuildCheckDataSource.EventArgs, factory); + } } else { diff --git a/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs b/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs index baecbac2423..00516fb758b 100644 --- a/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs +++ b/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs @@ -3,9 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BuildCheck.Acquisition; using Microsoft.Build.BuildCheck.Logging; @@ -20,11 +17,15 @@ public void Shutdown() { } - public void ProcessEvaluationFinishedEventArgs(IBuildAnalysisLoggingContext buildAnalysisContext, ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs) + public void ProcessEvaluationFinishedEventArgs( + AnalyzerLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs) { } - public void SetDataSource(BuildCheckDataSource buildCheckDataSource) { } + public void SetDataSource(BuildCheckDataSource buildCheckDataSource) + { + } public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) { } diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index acabdac5e5f..7e1dd5ec428 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; @@ -13,7 +12,6 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; -using Microsoft.Build.BackEnd.Components.Logging; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation.Context; @@ -320,7 +318,6 @@ private void FlushFirstValueIfNeeded() /// internal EvaluationContext EvaluationContext { get; } - /// /// Creates an expander passing it some properties to use. /// Properties may be null. diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index 625246427fc..f8cb37bce93 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -3,17 +3,13 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; -using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; -using Microsoft.Build.BackEnd.Components.Logging; using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; using Microsoft.Build.Internal; @@ -32,7 +28,7 @@ namespace Microsoft.Build.Evaluation { /// /// The Intrinsic class provides static methods that can be accessed from MSBuild's - /// property functions using $([MSBuild]::Function(x,y)) + /// property functions using $([MSBuild]::Function(x,y)). /// internal static class IntrinsicFunctions { diff --git a/src/Framework/BuildCheck/BuildCheckEventArgs.cs b/src/Framework/BuildCheck/BuildCheckEventArgs.cs index 539fd2c3e4d..b7edcb5161a 100644 --- a/src/Framework/BuildCheck/BuildCheckEventArgs.cs +++ b/src/Framework/BuildCheck/BuildCheckEventArgs.cs @@ -60,7 +60,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) } } -public sealed class BuildCheckAcquisitionEventArgs(string acquisitionPath) : BuildCheckEventArgs +internal sealed class BuildCheckAcquisitionEventArgs(string acquisitionPath) : BuildCheckEventArgs { internal BuildCheckAcquisitionEventArgs() : this(string.Empty) diff --git a/template_feed/Microsoft.AnalyzerTemplate/Analyzer1.cs b/template_feed/Microsoft.AnalyzerTemplate/Analyzer1.cs index e6d225f21dd..78dae77947b 100644 --- a/template_feed/Microsoft.AnalyzerTemplate/Analyzer1.cs +++ b/template_feed/Microsoft.AnalyzerTemplate/Analyzer1.cs @@ -1,34 +1,39 @@ -using System; +using Microsoft.Build.Construction; +using Microsoft.Build.Experimental.BuildCheck; +using System.Collections.Generic; namespace Company.AnalyzerTemplate { public sealed class Analyzer1 : BuildAnalyzer { - public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule("X01234", "Title", - "Description", "Category", + public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule( + "X01234", + "Title", + "Description", "Message format: {0}", - new BuildAnalyzerConfiguration() { Severity = BuildAnalyzerResultSeverity.Warning, IsEnabled = true }); + new BuildAnalyzerConfiguration()); public override string FriendlyName => "Company.Analyzer1"; - public override IReadOnlyList SupportedRules { get; } =[SupportedRule]; + public override IReadOnlyList SupportedRules { get; } = new List() { SupportedRule }; public override void Initialize(ConfigurationContext configurationContext) { // configurationContext to be used only if analyzer needs external configuration data. } - public override void RegisterActions(IBuildCopRegistrationContext registrationContext) + + public override void RegisterActions(IBuildCheckRegistrationContext registrationContext) { registrationContext.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction); } - - private void EvaluatedPropertiesAction(BuildCopDataContext context) + + private void EvaluatedPropertiesAction(BuildCheckDataContext context) { - context.ReportResult(BuildCopResult.Create( + context.ReportResult(BuildCheckResult.Create( SupportedRule, ElementLocation.EmptyLocation, - "Argument for the message format"); + "Argument for the message format")); } } } From 06e06d53e2ad55a49fefa4ac02e09105e26f4176 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Mon, 8 Apr 2024 13:23:43 +0200 Subject: [PATCH 43/58] cleanup --- .../BuildCheckManagerProvider.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 5804a4fc4eb..c8c03f4e6fe 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -70,6 +70,13 @@ private sealed class BuildCheckManager : IBuildCheckManager private readonly BuildEventsProcessor _buildEventsProcessor; private readonly BuildCheckAcquisitionModule _acquisitionModule = new(); + internal BuildCheckManager(ILoggingService loggingService) + { + _analyzersRegistry = new List(); + _loggingService = loggingService; + _buildEventsProcessor = new(_buildCheckCentralContext); + } + private bool IsInProcNode => _enabledDataSources[(int)BuildCheckDataSource.EventArgs] && _enabledDataSources[(int)BuildCheckDataSource.BuildExecution]; @@ -91,7 +98,7 @@ public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) { if (IsInProcNode) { - var factory = _acquisitionModule.CreateBuildAnalyzerFactory(acquisitionData); + BuildAnalyzerFactory? factory = _acquisitionModule.CreateBuildAnalyzerFactory(acquisitionData); if (factory != null) { RegisterCustomAnalyzer(BuildCheckDataSource.EventArgs, factory); @@ -113,13 +120,8 @@ public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) } } - internal BuildCheckManager(ILoggingService loggingService) - { - _loggingService = loggingService; - _buildEventsProcessor = new(_buildCheckCentralContext); - } - private static T Construct() where T : new() => new(); + private static readonly (string[] ruleIds, bool defaultEnablement, BuildAnalyzerFactory factory)[][] s_builtInFactoriesPerDataSource = [ // BuildCheckDataSource.EventArgs @@ -164,7 +166,8 @@ internal void RegisterCustomAnalyzer( if (_enabledDataSources[(int)buildCheckDataSource]) { var instance = factory(); - _analyzersRegistry.Add(new BuildAnalyzerFactoryContext(factory, + _analyzersRegistry.Add(new BuildAnalyzerFactoryContext( + factory, instance.SupportedRules.Select(r => r.Id).ToArray(), instance.SupportedRules.Any(r => r.DefaultConfiguration.IsEnabled == true))); } From ab744f6f66c1bf4ea746aca9252498880ee401e8 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Mon, 8 Apr 2024 14:13:55 +0200 Subject: [PATCH 44/58] fix template --- .../Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj | 2 +- .../{Directory.Build.props => Company.AnalyzerTemplate.props} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename template_feed/Microsoft.AnalyzerTemplate/{Directory.Build.props => Company.AnalyzerTemplate.props} (100%) diff --git a/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj b/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj index b18c65a87dd..0a1b8f974fc 100644 --- a/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj +++ b/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj @@ -10,7 +10,7 @@ - + diff --git a/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props b/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.props similarity index 100% rename from template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props rename to template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.props From 5b86a3d2f35873805bb9fa7399fd62a1e2b55779 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Tue, 9 Apr 2024 11:28:55 +0200 Subject: [PATCH 45/58] add test coverage --- .../Evaluation/Expander_Tests.cs | 26 +++- src/Build/AssemblyInfo.cs | 1 + .../BuildCheckAcquisitionModule.cs | 3 +- .../IBuildCheckAcquisitionModule.cs | 12 ++ .../BuildCheckConnectorLogger.cs | 2 +- .../BuildCheckManagerProvider.cs | 24 ++-- .../Infrastructure/IBuildCheckManager.cs | 10 +- .../Infrastructure/NullBuildCheckManager.cs | 4 +- src/Build/Microsoft.Build.csproj | 2 + src/Build/Resources/Strings.resx | 87 +++++++------- src/Build/Resources/xlf/Strings.cs.xlf | 10 ++ src/Build/Resources/xlf/Strings.de.xlf | 10 ++ src/Build/Resources/xlf/Strings.es.xlf | 10 ++ src/Build/Resources/xlf/Strings.fr.xlf | 10 ++ src/Build/Resources/xlf/Strings.it.xlf | 10 ++ src/Build/Resources/xlf/Strings.ja.xlf | 10 ++ src/Build/Resources/xlf/Strings.ko.xlf | 10 ++ src/Build/Resources/xlf/Strings.pl.xlf | 10 ++ src/Build/Resources/xlf/Strings.pt-BR.xlf | 10 ++ src/Build/Resources/xlf/Strings.ru.xlf | 10 ++ src/Build/Resources/xlf/Strings.tr.xlf | 10 ++ src/Build/Resources/xlf/Strings.zh-Hans.xlf | 10 ++ src/Build/Resources/xlf/Strings.zh-Hant.xlf | 10 ++ .../BuildCheckManagerProviderTests.cs | 113 ++++++++++++++++++ src/BuildCheck.UnitTests/EndToEndTests.cs | 7 +- ...icrosoft.Build.BuildCheck.UnitTests.csproj | 6 - 26 files changed, 357 insertions(+), 70 deletions(-) create mode 100644 src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs create mode 100644 src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs index b7744d6ed4f..878d5c58c6e 100644 --- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs @@ -11,9 +11,12 @@ using System.Text; using System.Xml; using Microsoft.Build.BackEnd; +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; +using Microsoft.Build.Engine.UnitTests; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; +using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; @@ -22,7 +25,6 @@ using Microsoft.Win32; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; using ProjectHelpers = Microsoft.Build.UnitTests.BackEnd.ProjectHelpers; using ProjectItemInstanceFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.ProjectItemInstanceFactory; @@ -4893,5 +4895,27 @@ public void ExpandItemVectorFunctions_Exists_Directories() squiggleItems.Select(i => i.EvaluatedInclude).ShouldBe(new[] { alphaBetaPath, alphaDeltaPath }, Case.Insensitive); } } + + [Fact] + public void PropertyFunctionRegisterAnalyzer() + { + using (var env = TestEnvironment.Create()) + { + var logger = new MockLogger(); + ILoggingService loggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); + loggingService.RegisterLogger(logger); + var loggingContext = new MockLoggingContext( + loggingService, + new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0, 0)); + var dummyAssemblyFile = env.CreateFile(env.CreateFolder(), "test.dll"); + + var result = new Expander(new PropertyDictionary(), FileSystems.Default) + .ExpandIntoStringLeaveEscaped($"$([MSBuild]::RegisterAnalyzer({dummyAssemblyFile.Path}))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance, loggingContext); + + result.ShouldBe(Boolean.TrueString); + _ = logger.AllBuildEvents.Select(be => be.ShouldBeOfType()); + logger.AllBuildEvents.Count.ShouldBe(1); + } + } } } diff --git a/src/Build/AssemblyInfo.cs b/src/Build/AssemblyInfo.cs index 6e57337863d..7e0091c5d47 100644 --- a/src/Build/AssemblyInfo.cs +++ b/src/Build/AssemblyInfo.cs @@ -23,6 +23,7 @@ [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Unittest, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.Tasks.Cop, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] +[assembly: InternalsVisibleTo("Microsoft.Build.BuildCheck.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] // DO NOT expose Internals to "Microsoft.Build.UnitTests.OM.OrcasCompatibility" as this assembly is supposed to only see public interface // This will enable passing the SafeDirectories flag to any P/Invoke calls/implementations within the assembly, diff --git a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs index 606759a34e2..b7f79c796ac 100644 --- a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs @@ -10,7 +10,7 @@ namespace Microsoft.Build.BuildCheck.Acquisition { - internal class BuildCheckAcquisitionModule + internal class BuildCheckAcquisitionModule : IBuildCheckAcquisitionModule { #if FEATURE_ASSEMBLYLOADCONTEXT /// @@ -18,6 +18,7 @@ internal class BuildCheckAcquisitionModule /// private static readonly CoreClrAssemblyLoader s_coreClrAssemblyLoader = new(); #endif + public BuildAnalyzerFactory? CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData) { try diff --git a/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs new file mode 100644 index 00000000000..98df459aed5 --- /dev/null +++ b/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Build.BuildCheck.Infrastructure; + +namespace Microsoft.Build.BuildCheck.Acquisition +{ + internal interface IBuildCheckAcquisitionModule + { + BuildAnalyzerFactory? CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData); + } +} diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs index a5860c27bb4..6789ae47bd4 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs @@ -103,7 +103,7 @@ private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e) { typeof(ProjectStartedEventArgs), (BuildEventArgs e) => _buildCheckManager.StartProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) }, { typeof(ProjectFinishedEventArgs), (BuildEventArgs e) => _buildCheckManager.EndProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) }, { typeof(BuildCheckTracingEventArgs), (BuildEventArgs e) => _stats.Merge(((BuildCheckTracingEventArgs)e).TracingData, (span1, span2) => span1 + span2) }, - { typeof(BuildCheckAcquisitionEventArgs), (BuildEventArgs e) => _buildCheckManager.ProcessAnalyzerAcquisition(((BuildCheckAcquisitionEventArgs)e).ToAnalyzerAcquisitionData()) }, + { typeof(BuildCheckAcquisitionEventArgs), (BuildEventArgs e) => _buildCheckManager.ProcessAnalyzerAcquisition(((BuildCheckAcquisitionEventArgs)e).ToAnalyzerAcquisitionData(), e.BuildEventContext!) }, }; } } diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index c8c03f4e6fe..5bfe3f92490 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -25,7 +25,7 @@ namespace Microsoft.Build.BuildCheck.Infrastructure; internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider { private static IBuildCheckManager? s_globalInstance; - + internal static IBuildCheckManager GlobalInstance => s_globalInstance ?? throw new InvalidOperationException("BuildCheckManagerProvider not initialized"); public IBuildCheckManager Instance => GlobalInstance; @@ -60,7 +60,7 @@ public void InitializeComponent(IBuildComponentHost host) public void ShutdownComponent() => GlobalInstance.Shutdown(); - private sealed class BuildCheckManager : IBuildCheckManager + internal sealed class BuildCheckManager : IBuildCheckManager { private readonly TracingReporter _tracingReporter = new TracingReporter(); private readonly BuildCheckCentralContext _buildCheckCentralContext = new(); @@ -68,11 +68,12 @@ private sealed class BuildCheckManager : IBuildCheckManager private readonly List _analyzersRegistry; private readonly bool[] _enabledDataSources = new bool[(int)BuildCheckDataSource.ValuesCount]; private readonly BuildEventsProcessor _buildEventsProcessor; - private readonly BuildCheckAcquisitionModule _acquisitionModule = new(); + private readonly IBuildCheckAcquisitionModule _acquisitionModule; internal BuildCheckManager(ILoggingService loggingService) { _analyzersRegistry = new List(); + _acquisitionModule = new BuildCheckAcquisitionModule(); _loggingService = loggingService; _buildEventsProcessor = new(_buildCheckCentralContext); } @@ -94,14 +95,18 @@ public void SetDataSource(BuildCheckDataSource buildCheckDataSource) } } - public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) + public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData, BuildEventContext buildEventContext) { if (IsInProcNode) { BuildAnalyzerFactory? factory = _acquisitionModule.CreateBuildAnalyzerFactory(acquisitionData); if (factory != null) { - RegisterCustomAnalyzer(BuildCheckDataSource.EventArgs, factory); + RegisterCustomAnalyzer(BuildCheckDataSource.EventArgs, factory, buildEventContext); + } + else + { + _loggingService.LogComment(buildEventContext, MessageImportance.Normal, "CustomAnalyzerFailedAcquisition", acquisitionData.AssemblyPath); } } else @@ -161,7 +166,8 @@ internal void RegisterCustomAnalyzer( /// internal void RegisterCustomAnalyzer( BuildCheckDataSource buildCheckDataSource, - BuildAnalyzerFactory factory) + BuildAnalyzerFactory factory, + BuildEventContext buildEventContext) { if (_enabledDataSources[(int)buildCheckDataSource]) { @@ -170,6 +176,7 @@ internal void RegisterCustomAnalyzer( factory, instance.SupportedRules.Select(r => r.Id).ToArray(), instance.SupportedRules.Any(r => r.DefaultConfiguration.IsEnabled == true))); + _loggingService.LogComment(buildEventContext, MessageImportance.Normal, "CustomAnalyzerSuccessfulAcquisition", instance.GetType().Name); } } @@ -279,7 +286,6 @@ private void SetupAnalyzersForNewProject(string projectFullPath, BuildEventConte } } - public void ProcessEvaluationFinishedEventArgs( AnalyzerLoggingContext buildAnalysisContext, ProjectEvaluationFinishedEventArgs evaluationFinishedEventArgs) @@ -362,9 +368,13 @@ private class BuildAnalyzerFactoryContext( ba.Initialize(configContext); return new BuildAnalyzerWrapper(ba); }; + public BuildAnalyzerWrapper? MaterializedAnalyzer { get; set; } + public string[] RuleIds { get; init; } = ruleIds; + public bool IsEnabledByDefault { get; init; } = isEnabledByDefault; + public string FriendlyName => MaterializedAnalyzer?.BuildAnalyzer.FriendlyName ?? factory().FriendlyName; } } diff --git a/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs b/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs index ca91897ad44..8e82c543c32 100644 --- a/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs +++ b/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Tracing; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -19,8 +20,7 @@ internal enum BuildCheckDataSource { EventArgs, BuildExecution, - - ValuesCount = BuildExecution + 1 + ValuesCount = BuildExecution + 1, } /// @@ -34,7 +34,7 @@ void ProcessEvaluationFinishedEventArgs( void SetDataSource(BuildCheckDataSource buildCheckDataSource); - void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData); + void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData, BuildEventContext buildEventContext); Dictionary CreateTracingStats(); @@ -44,10 +44,12 @@ void ProcessEvaluationFinishedEventArgs( // but as well from the ConnectorLogger - as even if interleaved, it gives the info // to manager about what analyzers need to be materialized and configuration fetched. // No unloading of analyzers is yet considered - once loaded it stays for whole build. - void StartProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext, string fullPath); + void EndProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); + void StartProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); + void EndProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); void Shutdown(); diff --git a/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs b/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs index 00516fb758b..45ed260d425 100644 --- a/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs +++ b/src/Build/BuildCheck/Infrastructure/NullBuildCheckManager.cs @@ -27,7 +27,9 @@ public void SetDataSource(BuildCheckDataSource buildCheckDataSource) { } - public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData) { } + public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData, BuildEventContext buildEventContext) + { + } public Dictionary CreateTracingStats() => throw new NotImplementedException(); diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index dc2d2ed13ca..354571bb26b 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -158,6 +158,7 @@ + @@ -696,6 +697,7 @@ SharedUtilities\XmlUtilities.cs + diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index a037905be7e..8822c0212fe 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1,17 +1,17 @@  - @@ -142,7 +142,7 @@ The operation cannot be completed because EndBuild has already been called but existing submissions have not yet completed. - + Property '{0}' with value '{1}' expanded from the environment. @@ -476,7 +476,7 @@ likely because of a programming error in the logger). When a logger dies, we cannot proceed with the build, and we throw a special exception to abort the build. - + MSB3094: "{2}" refers to {0} item(s), and "{3}" refers to {1} item(s). They must have the same number of items. {StrBegin="MSB3094: "} @@ -1162,7 +1162,7 @@ LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. Also, Microsoft.Build.Framework should not be localized - + MSB4181: The "{0}" task returned false but did not log an error. {StrBegin="MSB4181: "} @@ -1292,7 +1292,7 @@ MSB4067: The element <{0}> beneath element <{1}> is unrecognized. {StrBegin="MSB4067: "} - + MSB4067: The element <{0}> beneath element <{1}> is unrecognized. If you intended this to be a property, enclose it within a <PropertyGroup> element. {StrBegin="MSB4067: "} @@ -1743,7 +1743,7 @@ Utilization: {0} Average Utilization: {1:###.0} MSB4231: ProjectRootElement can't reload if it contains unsaved changes. {StrBegin="MSB4231: "} - + The parameters have been truncated beyond this point. To view all parameters, clear the MSBUILDTRUNCATETASKINPUTLOGGING environment variable. @@ -1971,9 +1971,9 @@ Utilization: {0} Average Utilization: {1:###.0} - {0} -> Cache Hit + {0} -> Cache Hit - {StrBegin="{0} -> "}LOCALIZATION: This string is used to indicate progress and matches the format for a log message from Microsoft.Common.CurrentVersion.targets. {0} is a project name. + {StrBegin="{0} -> "}LOCALIZATION: This string is used to indicate progress and matches the format for a log message from Microsoft.Common.CurrentVersion.targets. {0} is a project name. @@ -2000,7 +2000,7 @@ Utilization: {0} Average Utilization: {1:###.0} LOCALIZATION: {0} is a file path. {1} is a comma-separated list of target names - + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. @@ -2042,7 +2042,7 @@ Utilization: {0} Average Utilization: {1:###.0} Imported files archive exceeded 2GB limit and it's not embedded. - Forward compatible reading is not supported for file format version {0} (needs >= 18). + Forward compatible reading is not supported for file format version {0} (needs >= 18). LOCALIZATION: {0} is an integer number denoting version. @@ -2095,11 +2095,12 @@ Utilization: {0} Average Utilization: {1:###.0} LOCALIZATION: {0} is integer number denoting number of bytes. 'int.MaxValue' should not be translated. - + + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + + + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 83d03235b23..563cdcc63c2 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: Výchozí překladač sady SDK nedokázal přeložit sadu SDK „{0}“, protože adresář „{1}“ neexistoval. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 3001fd096d9..668a612bf7a 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: Der SDK-Standardresolver konnte SDK "{0}" nicht auflösen, da das Verzeichnis "{1}" nicht vorhanden war. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index f5f6b78041f..1673d5e2a5a 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: La resolución predeterminada del SDK no pudo resolver el SDK "{0}" porque el directorio "{1}" no existía. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 28c5c4c14b9..125669fa641 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: Le programme de résolution du SDK par défaut n’a pas pu résoudre le SDK «{0}», car le répertoire «{1}» n’existait pas. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index db3bb216291..cb083681300 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: il resolver SDK predefinito non è riuscito a risolvere l'SDK "{0}" perché la directory "{1}" non esiste. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 82ddae9c55f..3fd9a12302e 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: ディレクトリ "{0}" が存在しなかったため、既定の SDK リゾルバーは SDK "{1}" を解決できませんでした。 diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 4b5c0ff47ac..e6e302839ce 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: 디렉터리 "{0}"이(가) 없으므로 기본 SDK 확인자가 SDK "{1}"을(를) 확인하지 못했습니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index b253894f759..2c0fd575dce 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: Domyślne narzędzie Resolver zestawu SDK nie może rozpoznać zestawu SDK „{0}”, ponieważ katalog „{1}” nie istnieje. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 7e767a652cb..aeaa1eba431 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: O resolvedor SDK padrão falhou ao resolver SDK "{0}" porque o diretório "{1}" não existia. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index bf90a1bb435..195a24979af 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: стандартному сопоставителю пакетов SDK не удалось разрешить пакет SDK "{0}", так как каталог "{1}" не существует. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index ec82a4c4384..0c4e6ecc1e0 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: "{1}" dizini olmadığından, varsayılan SDK çözümleyicisi "{0}" SDK’sını çözümleyemedi. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index d148cff64c7..b1fb3c6921d 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: 默认 SDK 解析程序解析 SDK“{0}”失败,因为目录“{1}”不存在。 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index ef485bd062f..63ac69da9b3 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -207,6 +207,16 @@ {StrBegin="MSB4006: "}UE: This message is shown when the build engine detects a target referenced in a circular manner -- a project cannot request a target to build itself (perhaps via a chain of other targets). + + Failed to ragister the custom analyzer: {0}. + Failed to ragister the custom analyzer: {0}. + The message is emmited on failed loading of the custom analyzer in app domain. + + + Custom analyzer {0} has been registered successfully. + Custom analyzer {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer in app domain. + MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. MSB4276: 預設的 SDK 解析程式無法解析 SDK "{0}",因為目錄 "{1}" 不存在。 diff --git a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs new file mode 100644 index 00000000000..a23089290be --- /dev/null +++ b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs @@ -0,0 +1,113 @@ +// 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.Reflection; +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.BuildCheck.Acquisition; +using Microsoft.Build.BuildCheck.Infrastructure; +using Microsoft.Build.Construction; +using Microsoft.Build.Experimental.BuildCheck; +using Microsoft.Build.Framework; +using Microsoft.Build.UnitTests; +using Shouldly; +using Xunit; +using Xunit.Abstractions; +using static Microsoft.Build.BuildCheck.Infrastructure.BuildCheckManagerProvider; + +namespace Microsoft.Build.BuildCheck.UnitTests +{ + public class BuildCheckManagerTests : IDisposable + { + private readonly TestEnvironment _env; + + private readonly IBuildCheckManager _testedInstance; + private readonly ILoggingService _loggingService; + private readonly MockLogger _logger; + + public BuildCheckManagerTests(ITestOutputHelper output) + { + _env = TestEnvironment.Create(output); + + // this is needed to ensure the binary logger does not pollute the environment + _env.WithEnvironmentInvariant(); + + _loggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); + _logger = new MockLogger(); + _loggingService.RegisterLogger(_logger); + _testedInstance = new BuildCheckManager(_loggingService); + } + + [Theory] + [InlineData(true, "Custom analyzer BuildAnalyzerRuleMock has been registered successfully.")] + [InlineData(false, "Failed to ragister the custom analyzer: DummyPath.")] + public void ProcessAnalyzerAcquisitionTest(bool isAnalyzerRuleExist, string expectedMessage) + { + MockBuildCheckAcquisition(isAnalyzerRuleExist); + MockEnabledDataSourcesDefinition(); + + _testedInstance.ProcessAnalyzerAcquisition(new AnalyzerAcquisitionData("DummyPath"), new BuildEventContext(1, 2, 3, 4, 5, 6, 7)); + + _logger.AllBuildEvents.Where(be => be.GetType() == typeof(BuildMessageEventArgs)) + .ShouldContain(be => be.Message == expectedMessage); + } + + public void Dispose() => _env.Dispose(); + + private void MockBuildCheckAcquisition(bool isAnalyzerRuleExist) => MockField("_acquisitionModule", new BuildCheckAcquisitionModuleMock(isAnalyzerRuleExist)); + + private void MockEnabledDataSourcesDefinition() => MockField("_enabledDataSources", new[] { true, true }); + + private void MockField(string fieldName, object mockedValue) + { + var mockedField = _testedInstance.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + mockedField.SetValue(_testedInstance, mockedValue); + } + } + + internal class BuildCheckAcquisitionModuleMock : IBuildCheckAcquisitionModule + { + private readonly bool _isAnalyzerRuleExistForTest = true; + + internal BuildCheckAcquisitionModuleMock(bool isAnalyzerRuleExistForTest) => _isAnalyzerRuleExistForTest = isAnalyzerRuleExistForTest; + + public BuildAnalyzerFactory? CreateBuildAnalyzerFactory(AnalyzerAcquisitionData analyzerAcquisitionData) => + _isAnalyzerRuleExistForTest + ? () => new BuildAnalyzerRuleMock() + : null; + } + + internal class BuildAnalyzerRuleMock : BuildAnalyzer + { + public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule( + "X01234", + "Title", + "Description", + "Message format: {0}", + new BuildAnalyzerConfiguration()); + + public override string FriendlyName => "BuildAnalyzerRuleMock"; + + public override IReadOnlyList SupportedRules { get; } = new List() { SupportedRule }; + + public override void Initialize(ConfigurationContext configurationContext) + { + // configurationContext to be used only if analyzer needs external configuration data. + } + + public override void RegisterActions(IBuildCheckRegistrationContext registrationContext) + { + registrationContext.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction); + } + + private void EvaluatedPropertiesAction(BuildCheckDataContext context) + { + context.ReportResult(BuildCheckResult.Create( + SupportedRule, + ElementLocation.EmptyLocation, + "Argument for the message format")); + } + } +} diff --git a/src/BuildCheck.UnitTests/EndToEndTests.cs b/src/BuildCheck.UnitTests/EndToEndTests.cs index f0fda0d4b29..c523f879b8e 100644 --- a/src/BuildCheck.UnitTests/EndToEndTests.cs +++ b/src/BuildCheck.UnitTests/EndToEndTests.cs @@ -2,12 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.UnitTests; using Microsoft.Build.UnitTests.Shared; using Shouldly; @@ -19,6 +14,7 @@ namespace Microsoft.Build.BuildCheck.UnitTests public class EndToEndTests : IDisposable { private readonly TestEnvironment _env; + public EndToEndTests(ITestOutputHelper output) { _env = TestEnvironment.Create(output); @@ -91,7 +87,6 @@ public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode, bool ana // var cache = new SimpleProjectRootElementCache(); // ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile.Path, /*unused*/null, /*unused*/null, cache, false /*Not explicitly loaded - unused*/); - TransientTestFile config = _env.CreateFile(workFolder, "editorconfig.json", /*lang=json,strict*/ """ diff --git a/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj b/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj index 1768410a565..566b6eff6d4 100644 --- a/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj +++ b/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj @@ -30,12 +30,6 @@ - - Shared\FileUtilities.cs - - - Shared\TempFileUtilities.cs - Shared\ErrorUtilities.cs From b73069ca65dc7fe164dfc5c51f8f2f12cea7df67 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Tue, 9 Apr 2024 11:35:27 +0200 Subject: [PATCH 46/58] cleanup --- .../Infrastructure/IBuildCheckManager.cs | 77 +++++++++++-------- src/Build/Microsoft.Build.csproj | 1 - .../BuildCheckManagerProviderTests.cs | 11 +-- 3 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs b/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs index 8e82c543c32..117d3aa8a03 100644 --- a/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs +++ b/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs @@ -3,54 +3,63 @@ using System; using System.Collections.Generic; -using System.Diagnostics.Tracing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BuildCheck.Acquisition; -using Microsoft.Build.BuildCheck.Infrastructure; using Microsoft.Build.BuildCheck.Logging; using Microsoft.Build.Framework; -namespace Microsoft.Build.Experimental.BuildCheck; - -internal enum BuildCheckDataSource +namespace Microsoft.Build.Experimental.BuildCheck { - EventArgs, - BuildExecution, - ValuesCount = BuildExecution + 1, -} + /// + /// Enumerates the different data sources used in build check operations. + /// + internal enum BuildCheckDataSource + { + /// + /// The data source is based on event arguments. + /// + EventArgs, -/// -/// The central manager for the BuildCheck - this is the integration point with MSBuild infrastructure. -/// -internal interface IBuildCheckManager -{ - void ProcessEvaluationFinishedEventArgs( - AnalyzerLoggingContext buildAnalysisContext, - ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs); + /// + /// The data source is based on build execution. + /// + BuildExecution, + + /// + /// Represents the total number of values in the enum, used for indexing purposes. + /// + ValuesCount = BuildExecution + 1, + } + + /// + /// The central manager for the BuildCheck - this is the integration point with MSBuild infrastructure. + /// + internal interface IBuildCheckManager + { + void ProcessEvaluationFinishedEventArgs( + AnalyzerLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs); - void SetDataSource(BuildCheckDataSource buildCheckDataSource); + void SetDataSource(BuildCheckDataSource buildCheckDataSource); - void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData, BuildEventContext buildEventContext); + void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData, BuildEventContext buildEventContext); - Dictionary CreateTracingStats(); + Dictionary CreateTracingStats(); - void FinalizeProcessing(LoggingContext loggingContext); + void FinalizeProcessing(LoggingContext loggingContext); - // All those to be called from RequestBuilder, - // but as well from the ConnectorLogger - as even if interleaved, it gives the info - // to manager about what analyzers need to be materialized and configuration fetched. - // No unloading of analyzers is yet considered - once loaded it stays for whole build. - void StartProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext, string fullPath); + // All those to be called from RequestBuilder, + // but as well from the ConnectorLogger - as even if interleaved, it gives the info + // to manager about what analyzers need to be materialized and configuration fetched. + // No unloading of analyzers is yet considered - once loaded it stays for whole build. + void StartProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext, string fullPath); - void EndProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); + void EndProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); - void StartProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); + void StartProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); - void EndProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); + void EndProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); - void Shutdown(); + void Shutdown(); + } } diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 354571bb26b..c5e4dedbb10 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -697,7 +697,6 @@ SharedUtilities\XmlUtilities.cs - diff --git a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs index a23089290be..307afe28c86 100644 --- a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs +++ b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs @@ -19,21 +19,14 @@ namespace Microsoft.Build.BuildCheck.UnitTests { - public class BuildCheckManagerTests : IDisposable + public class BuildCheckManagerTests { - private readonly TestEnvironment _env; - private readonly IBuildCheckManager _testedInstance; private readonly ILoggingService _loggingService; private readonly MockLogger _logger; public BuildCheckManagerTests(ITestOutputHelper output) { - _env = TestEnvironment.Create(output); - - // this is needed to ensure the binary logger does not pollute the environment - _env.WithEnvironmentInvariant(); - _loggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); _logger = new MockLogger(); _loggingService.RegisterLogger(_logger); @@ -54,8 +47,6 @@ public void ProcessAnalyzerAcquisitionTest(bool isAnalyzerRuleExist, string expe .ShouldContain(be => be.Message == expectedMessage); } - public void Dispose() => _env.Dispose(); - private void MockBuildCheckAcquisition(bool isAnalyzerRuleExist) => MockField("_acquisitionModule", new BuildCheckAcquisitionModuleMock(isAnalyzerRuleExist)); private void MockEnabledDataSourcesDefinition() => MockField("_enabledDataSources", new[] { true, true }); From 0fdf77f0d690b3425e9f02bf796c9427b0186e14 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Tue, 9 Apr 2024 12:13:31 +0200 Subject: [PATCH 47/58] cleanup --- .../BuildCheckManagerProviderTests.cs | 9 ++++-- ...icrosoft.Build.BuildCheck.UnitTests.csproj | 30 ------------------- 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs index 307afe28c86..f632b2054ba 100644 --- a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs +++ b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs @@ -54,11 +54,14 @@ public void ProcessAnalyzerAcquisitionTest(bool isAnalyzerRuleExist, string expe private void MockField(string fieldName, object mockedValue) { var mockedField = _testedInstance.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); - mockedField.SetValue(_testedInstance, mockedValue); + if (mockedField != null) + { + mockedField.SetValue(_testedInstance, mockedValue); + } } } - internal class BuildCheckAcquisitionModuleMock : IBuildCheckAcquisitionModule + internal sealed class BuildCheckAcquisitionModuleMock : IBuildCheckAcquisitionModule { private readonly bool _isAnalyzerRuleExistForTest = true; @@ -70,7 +73,7 @@ internal class BuildCheckAcquisitionModuleMock : IBuildCheckAcquisitionModule : null; } - internal class BuildAnalyzerRuleMock : BuildAnalyzer + internal sealed class BuildAnalyzerRuleMock : BuildAnalyzer { public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule( "X01234", diff --git a/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj b/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj index 566b6eff6d4..a2e65562ae1 100644 --- a/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj +++ b/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj @@ -1,8 +1,5 @@ - - - $(LatestDotNetCoreForMSBuild) @@ -29,33 +26,6 @@ - - - Shared\ErrorUtilities.cs - - - Shared\EscapingUtilities.cs - - - Shared\BuildEnvironmentHelper.cs - - - Shared\ProcessExtensions.cs - - - Shared\ResourceUtilities.cs - - - Shared\ExceptionHandling.cs - - - Shared\FileUtilitiesRegex.cs - - - Shared\AssemblyResources.cs - - - App.config From aaaafccae7ea79994554c2d04b606956f2ee58e4 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Wed, 10 Apr 2024 15:47:31 +0200 Subject: [PATCH 48/58] Update src/Build/BuildCheck/API/BuildAnalyzerRule.cs Co-authored-by: Farhad Alizada <104755925+f-alizada@users.noreply.github.com> --- src/Build/BuildCheck/API/BuildAnalyzerRule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Build/BuildCheck/API/BuildAnalyzerRule.cs b/src/Build/BuildCheck/API/BuildAnalyzerRule.cs index 30b34dfa65f..8b43dad4999 100644 --- a/src/Build/BuildCheck/API/BuildAnalyzerRule.cs +++ b/src/Build/BuildCheck/API/BuildAnalyzerRule.cs @@ -5,8 +5,8 @@ namespace Microsoft.Build.Experimental.BuildCheck; /// /// Represents a rule that is a unit of build analysis. -/// is a unit of executing the analysis, but it can be discovering multiple distinct violation types. -/// for this reason a single can expose s. +/// is a unit of executing the analysis, but it can be discovering multiple distinct violation types, +/// for this reason a single can expose multiple s. /// public class BuildAnalyzerRule { From cf29add1d3d61f89addbbb851b511841dfe45266 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Wed, 10 Apr 2024 18:14:56 +0200 Subject: [PATCH 49/58] Reflect PR comments --- src/Build/AssemblyInfo.cs | 1 + .../RequestBuilder/RequestBuilder.cs | 105 +++++---- src/Build/BuildCheck/API/BuildAnalyzer.cs | 8 +- .../BuildCheckConnectorLogger.cs | 16 +- .../BuildCheckManagerProvider.cs | 12 +- ...BuildAnalyzerConfigurationInternalTests.cs | 43 ++++ src/BuildCheck.UnitTests/EndToEndTests.cs | 213 +++++++++--------- ...icrosoft.Build.BuildCheck.UnitTests.csproj | 37 --- .../ParsedItemsAnalysisDataTests.cs | 46 ++++ 9 files changed, 274 insertions(+), 207 deletions(-) create mode 100644 src/BuildCheck.UnitTests/BuildAnalyzerConfigurationInternalTests.cs create mode 100644 src/BuildCheck.UnitTests/ParsedItemsAnalysisDataTests.cs diff --git a/src/Build/AssemblyInfo.cs b/src/Build/AssemblyInfo.cs index 6e57337863d..f07bea4f265 100644 --- a/src/Build/AssemblyInfo.cs +++ b/src/Build/AssemblyInfo.cs @@ -19,6 +19,7 @@ #endif [assembly: InternalsVisibleTo("Microsoft.Build.Framework.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.Engine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] +[assembly: InternalsVisibleTo("Microsoft.Build.BuildCheck.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.UnitTests.Shared, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Core, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.Build.Conversion.Unittest, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index b5b3454e36f..387af4a55b7 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1118,13 +1118,13 @@ private void SetProjectCurrentDirectory() /// private async Task BuildProject() { + ErrorUtilities.VerifyThrow(_targetBuilder != null, "Target builder is null"); + // We consider this the entrypoint for the project build for purposes of BuildCheck processing var buildCheckManager = (_componentHost.GetComponent(BuildComponentType.BuildCheckManagerProvider) as IBuildCheckManagerProvider)!.Instance; buildCheckManager.SetDataSource(BuildCheckDataSource.BuildExecution); - ErrorUtilities.VerifyThrow(_targetBuilder != null, "Target builder is null"); - // Make sure it is null before loading the configuration into the request, because if there is a problem // we do not wand to have an invalid projectLoggingContext floating around. Also if this is null the error will be // logged with the node logging context @@ -1172,63 +1172,78 @@ private async Task BuildProject() BuildCheckDataSource.BuildExecution, _requestEntry.Request.ParentBuildEventContext); - // Now that the project has started, parse a few known properties which indicate warning codes to treat as errors or messages - // - ConfigureWarningsAsErrorsAndMessages(); + try + { + // Now that the project has started, parse a few known properties which indicate warning codes to treat as errors or messages + ConfigureWarningsAsErrorsAndMessages(); - // Make sure to extract known immutable folders from properties and register them for fast up-to-date check - ConfigureKnownImmutableFolders(); + // Make sure to extract known immutable folders from properties and register them for fast up-to-date check + ConfigureKnownImmutableFolders(); - // See comment on Microsoft.Build.Internal.Utilities.GenerateToolsVersionToUse - _requestEntry.RequestConfiguration.RetrieveFromCache(); - if (_requestEntry.RequestConfiguration.Project.UsingDifferentToolsVersionFromProjectFile) - { - _projectLoggingContext.LogComment(MessageImportance.Low, "UsingDifferentToolsVersionFromProjectFile", _requestEntry.RequestConfiguration.Project.OriginalProjectToolsVersion, _requestEntry.RequestConfiguration.Project.ToolsVersion); - } + // See comment on Microsoft.Build.Internal.Utilities.GenerateToolsVersionToUse + _requestEntry.RequestConfiguration.RetrieveFromCache(); + if (_requestEntry.RequestConfiguration.Project.UsingDifferentToolsVersionFromProjectFile) + { + _projectLoggingContext.LogComment(MessageImportance.Low, + "UsingDifferentToolsVersionFromProjectFile", + _requestEntry.RequestConfiguration.Project.OriginalProjectToolsVersion, + _requestEntry.RequestConfiguration.Project.ToolsVersion); + } - _requestEntry.Request.BuildEventContext = _projectLoggingContext.BuildEventContext; + _requestEntry.Request.BuildEventContext = _projectLoggingContext.BuildEventContext; - // Determine the set of targets we need to build - string[] allTargets = _requestEntry.RequestConfiguration.GetTargetsUsedToBuildRequest(_requestEntry.Request).ToArray(); + // Determine the set of targets we need to build + string[] allTargets = _requestEntry.RequestConfiguration + .GetTargetsUsedToBuildRequest(_requestEntry.Request).ToArray(); - ProjectErrorUtilities.VerifyThrowInvalidProject(allTargets.Length > 0, _requestEntry.RequestConfiguration.Project.ProjectFileLocation, "NoTargetSpecified"); + ProjectErrorUtilities.VerifyThrowInvalidProject(allTargets.Length > 0, + _requestEntry.RequestConfiguration.Project.ProjectFileLocation, "NoTargetSpecified"); - // Set the current directory to that required by the project. - SetProjectCurrentDirectory(); + // Set the current directory to that required by the project. + SetProjectCurrentDirectory(); - // Transfer results and state from the previous node, if necessary. - // In order for the check for target completeness for this project to be valid, all of the target results from the project must be present - // in the results cache. It is possible that this project has been moved from its original node and when it was its results did not come - // with it. This would be signified by the ResultsNode value in the configuration pointing to a different node than the current one. In that - // case we will need to request those results be moved from their original node to this one. - if ((_requestEntry.RequestConfiguration.ResultsNodeId != Scheduler.InvalidNodeId) && - (_requestEntry.RequestConfiguration.ResultsNodeId != _componentHost.BuildParameters.NodeId)) - { - // This indicates to the system that we will block waiting for a results transfer. We will block here until those results become available. - await BlockOnTargetInProgress(Microsoft.Build.BackEnd.BuildRequest.InvalidGlobalRequestId, null); + // Transfer results and state from the previous node, if necessary. + // In order for the check for target completeness for this project to be valid, all of the target results from the project must be present + // in the results cache. It is possible that this project has been moved from its original node and when it was its results did not come + // with it. This would be signified by the ResultsNode value in the configuration pointing to a different node than the current one. In that + // case we will need to request those results be moved from their original node to this one. + if ((_requestEntry.RequestConfiguration.ResultsNodeId != Scheduler.InvalidNodeId) && + (_requestEntry.RequestConfiguration.ResultsNodeId != _componentHost.BuildParameters.NodeId)) + { + // This indicates to the system that we will block waiting for a results transfer. We will block here until those results become available. + await BlockOnTargetInProgress(Microsoft.Build.BackEnd.BuildRequest.InvalidGlobalRequestId, null); + + // All of the results should now be on this node. + ErrorUtilities.VerifyThrow( + _requestEntry.RequestConfiguration.ResultsNodeId == _componentHost.BuildParameters.NodeId, + "Results for configuration {0} were not retrieved from node {1}", + _requestEntry.RequestConfiguration.ConfigurationId, + _requestEntry.RequestConfiguration.ResultsNodeId); + } - // All of the results should now be on this node. - ErrorUtilities.VerifyThrow(_requestEntry.RequestConfiguration.ResultsNodeId == _componentHost.BuildParameters.NodeId, "Results for configuration {0} were not retrieved from node {1}", _requestEntry.RequestConfiguration.ConfigurationId, _requestEntry.RequestConfiguration.ResultsNodeId); - } + // Build the targets + BuildResult result = await _targetBuilder.BuildTargets(_projectLoggingContext, _requestEntry, this, + allTargets, _requestEntry.RequestConfiguration.BaseLookup, _cancellationTokenSource.Token); - // Build the targets - BuildResult result = await _targetBuilder.BuildTargets(_projectLoggingContext, _requestEntry, this, allTargets, _requestEntry.RequestConfiguration.BaseLookup, _cancellationTokenSource.Token); + result = _requestEntry.Request.ProxyTargets == null + ? result + : CopyTargetResultsFromProxyTargetsToRealTargets(result); - result = _requestEntry.Request.ProxyTargets == null - ? result - : CopyTargetResultsFromProxyTargetsToRealTargets(result); + if (MSBuildEventSource.Log.IsEnabled()) + { + MSBuildEventSource.Log.BuildProjectStop(_requestEntry.RequestConfiguration.ProjectFullPath, + string.Join(", ", allTargets)); + } - if (MSBuildEventSource.Log.IsEnabled()) + return result; + } + finally { - MSBuildEventSource.Log.BuildProjectStop(_requestEntry.RequestConfiguration.ProjectFullPath, string.Join(", ", allTargets)); + buildCheckManager.EndProjectRequest( + BuildCheckDataSource.BuildExecution, + _requestEntry.Request.ParentBuildEventContext); } - buildCheckManager.EndProjectRequest( - BuildCheckDataSource.BuildExecution, - _requestEntry.Request.ParentBuildEventContext); - - return result; - BuildResult CopyTargetResultsFromProxyTargetsToRealTargets(BuildResult resultFromTargetBuilder) { var proxyTargetMapping = _requestEntry.Request.ProxyTargets.ProxyTargetToRealTargetMap; diff --git a/src/Build/BuildCheck/API/BuildAnalyzer.cs b/src/Build/BuildCheck/API/BuildAnalyzer.cs index 0cb8cbaa629..f249dc98cc5 100644 --- a/src/Build/BuildCheck/API/BuildAnalyzer.cs +++ b/src/Build/BuildCheck/API/BuildAnalyzer.cs @@ -11,7 +11,7 @@ namespace Microsoft.Build.Experimental.BuildCheck; /// /// Base class for build analyzers. /// Same base will be used for custom and built-in analyzers. -/// is a unit of build analysis execution. But it can contain multiple rules - each representing a distinct violation. +/// is a unit of build analysis execution, but it can contain multiple rules - each representing a distinct violation. /// public abstract class BuildAnalyzer : IDisposable { @@ -36,9 +36,11 @@ public abstract class BuildAnalyzer : IDisposable public abstract void Initialize(ConfigurationContext configurationContext); /// - /// + /// Used by the implementors to subscribe to data and events they are interested in. /// - /// + /// + /// The context that enables subscriptions for data pumping from the infrastructure. + /// public abstract void RegisterActions(IBuildCheckRegistrationContext registrationContext); public virtual void Dispose() diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs index b8f276884e3..9be71d2a288 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs @@ -33,18 +33,9 @@ private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) return; } - try - { - buildCheckManager.ProcessEvaluationFinishedEventArgs( - loggingContextFactory.CreateLoggingContext(e.BuildEventContext!), - projectEvaluationFinishedEventArgs); - } - catch (Exception exception) - { - Debugger.Launch(); - Console.WriteLine(exception); - throw; - } + buildCheckManager.ProcessEvaluationFinishedEventArgs( + loggingContextFactory.CreateLoggingContext(e.BuildEventContext!), + projectEvaluationFinishedEventArgs); buildCheckManager.EndProjectEvaluation(BuildCheckDataSource.EventArgs, e.BuildEventContext!); } @@ -87,7 +78,6 @@ private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e) _stats.Merge(buildCheckManager.CreateTracingStats(), (span1, span2) => span1 + span2); string msg = string.Join(Environment.NewLine, _stats.Select(a => a.Key + ": " + a.Value)); - BuildEventContext buildEventContext = e.BuildEventContext ?? new BuildEventContext( BuildEventContext.InvalidNodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 0c132ce056b..2cc02231114 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -205,6 +205,11 @@ private void SetupSingleAnalyzer(BuildAnalyzerFactoryContext analyzerFactoryCont analyzerFactoryContext.MaterializedAnalyzer = wrapper; BuildAnalyzer analyzer = wrapper.BuildAnalyzer; + // This is to facilitate possible perf improvement for custom analyzers - as we might want to + // avoid loading the assembly and type just to check if it's supported. + // If we expose a way to declare the enablement status and rule ids during registration (e.g. via + // optional arguments of the intrinsic property function) - we can then avoid loading it. + // But once loaded - we should verify that the declared enablement status and rule ids match the actual ones. if ( analyzer.SupportedRules.Count != analyzerFactoryContext.RuleIds.Length || @@ -268,12 +273,15 @@ private void SetupAnalyzersForNewProject(string projectFullPath, BuildEventConte _loggingService.LogErrorFromText(buildEventContext, null, null, null, new BuildEventFileInfo(projectFullPath), e.Message); - _loggingService.LogCommentFromText(buildEventContext, MessageImportance.High, $"Dismounting analyzer '{analyzerFactoryContext.FriendlyName}'"); analyzersToRemove.Add(analyzerFactoryContext); } } - analyzersToRemove.ForEach(c => _analyzersRegistry.Remove(c)); + analyzersToRemove.ForEach(c => + { + _analyzersRegistry.Remove(c); + _loggingService.LogCommentFromText(buildEventContext, MessageImportance.High, $"Dismounting analyzer '{c.FriendlyName}'"); + }); foreach (var analyzerToRemove in analyzersToRemove.Select(a => a.MaterializedAnalyzer).Where(a => a != null)) { _buildCheckCentralContext.DeregisterAnalyzer(analyzerToRemove!); diff --git a/src/BuildCheck.UnitTests/BuildAnalyzerConfigurationInternalTests.cs b/src/BuildCheck.UnitTests/BuildAnalyzerConfigurationInternalTests.cs new file mode 100644 index 00000000000..248b66ea6b0 --- /dev/null +++ b/src/BuildCheck.UnitTests/BuildAnalyzerConfigurationInternalTests.cs @@ -0,0 +1,43 @@ +// 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; +using Xunit; +using Microsoft.Build.BuildCheck.Infrastructure; +using Microsoft.Build.Experimental.BuildCheck; +using Shouldly; + +namespace Microsoft.Build.BuildCheck.UnitTests; + +public class BuildAnalyzerConfigurationInternalTests +{ + [Theory] + [InlineData("ruleId", EvaluationAnalysisScope.ProjectOnly, BuildAnalyzerResultSeverity.Warning, true, true)] + [InlineData("ruleId2", EvaluationAnalysisScope.ProjectOnly, BuildAnalyzerResultSeverity.Warning, true, true)] + [InlineData("ruleId", EvaluationAnalysisScope.ProjectOnly, BuildAnalyzerResultSeverity.Error, true, false)] + public void IsSameConfigurationAsTest( + string secondRuleId, + EvaluationAnalysisScope secondScope, + BuildAnalyzerResultSeverity secondSeverity, + bool secondEnabled, + bool isExpectedToBeSame) + { + BuildAnalyzerConfigurationInternal configuration1 = new BuildAnalyzerConfigurationInternal( + ruleId: "ruleId", + evaluationAnalysisScope: EvaluationAnalysisScope.ProjectOnly, + severity: BuildAnalyzerResultSeverity.Warning, + isEnabled: true); + + BuildAnalyzerConfigurationInternal configuration2 = new BuildAnalyzerConfigurationInternal( + ruleId: secondRuleId, + evaluationAnalysisScope: secondScope, + severity: secondSeverity, + isEnabled: secondEnabled); + + configuration1.IsSameConfigurationAs(configuration2).ShouldBe(isExpectedToBeSame); + } +} diff --git a/src/BuildCheck.UnitTests/EndToEndTests.cs b/src/BuildCheck.UnitTests/EndToEndTests.cs index f0fda0d4b29..a0007d2c103 100644 --- a/src/BuildCheck.UnitTests/EndToEndTests.cs +++ b/src/BuildCheck.UnitTests/EndToEndTests.cs @@ -14,125 +14,124 @@ using Xunit; using Xunit.Abstractions; -namespace Microsoft.Build.BuildCheck.UnitTests +namespace Microsoft.Build.BuildCheck.UnitTests; + +public class EndToEndTests : IDisposable { - public class EndToEndTests : IDisposable + private readonly TestEnvironment _env; + public EndToEndTests(ITestOutputHelper output) { - private readonly TestEnvironment _env; - public EndToEndTests(ITestOutputHelper output) - { - _env = TestEnvironment.Create(output); + _env = TestEnvironment.Create(output); - // this is needed to ensure the binary logger does not pollute the environment - _env.WithEnvironmentInvariant(); - } + // this is needed to ensure the binary logger does not pollute the environment + _env.WithEnvironmentInvariant(); + } - public void Dispose() => _env.Dispose(); + public void Dispose() => _env.Dispose(); - [Theory] - [InlineData(true, true)] - [InlineData(false, true)] - [InlineData(false, false)] - public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode, bool analysisRequested) - { - string contents = $""" - - - - Exe - net8.0 - enable - enable - - - - Test - - - - - - - - - - - - """; + [Theory] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(false, false)] + public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode, bool analysisRequested) + { + string contents = $""" + + + + Exe + net8.0 + enable + enable + + + + Test + + + + + + + + + + + + """; - string contents2 = $""" - - - - Exe - net8.0 - enable - enable - - - - Test - - - - - - - - - - - - """; - TransientTestFolder workFolder = _env.CreateFolder(createFolder: true); - TransientTestFile projectFile = _env.CreateFile(workFolder, "FooBar.csproj", contents); - TransientTestFile projectFile2 = _env.CreateFile(workFolder, "FooBar-Copy.csproj", contents2); + string contents2 = $""" + + + + Exe + net8.0 + enable + enable + + + + Test + + + + + + + + + + + + """; + TransientTestFolder workFolder = _env.CreateFolder(createFolder: true); + TransientTestFile projectFile = _env.CreateFile(workFolder, "FooBar.csproj", contents); + TransientTestFile projectFile2 = _env.CreateFile(workFolder, "FooBar-Copy.csproj", contents2); - // var cache = new SimpleProjectRootElementCache(); - // ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile.Path, /*unused*/null, /*unused*/null, cache, false /*Not explicitly loaded - unused*/); + // var cache = new SimpleProjectRootElementCache(); + // ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile.Path, /*unused*/null, /*unused*/null, cache, false /*Not explicitly loaded - unused*/); - TransientTestFile config = _env.CreateFile(workFolder, "editorconfig.json", - /*lang=json,strict*/ - """ - { - "BC0101": { - "IsEnabled": true, - "Severity": "Error" - }, - "COND0543": { - "IsEnabled": false, - "Severity": "Error", - "EvaluationAnalysisScope": "AnalyzedProjectOnly", - "CustomSwitch": "QWERTY" - }, - "BLA": { - "IsEnabled": false - } + TransientTestFile config = _env.CreateFile(workFolder, "editorconfig.json", + /*lang=json,strict*/ + """ + { + "BC0101": { + "IsEnabled": true, + "Severity": "Error" + }, + "COND0543": { + "IsEnabled": false, + "Severity": "Error", + "EvaluationAnalysisScope": "AnalyzedProjectOnly", + "CustomSwitch": "QWERTY" + }, + "BLA": { + "IsEnabled": false } - """); + } + """); - // OSX links /var into /private, which makes Path.GetTempPath() return "/var..." but Directory.GetCurrentDirectory return "/private/var...". - // This discrepancy breaks path equality checks in analyzers if we pass to MSBuild full path to the initial project. - // See if there is a way of fixing it in the engine - tracked: https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=55702688. - _env.SetCurrentDirectory(Path.GetDirectoryName(projectFile.Path)); + // OSX links /var into /private, which makes Path.GetTempPath() return "/var..." but Directory.GetCurrentDirectory return "/private/var...". + // This discrepancy breaks path equality checks in analyzers if we pass to MSBuild full path to the initial project. + // See if there is a way of fixing it in the engine - tracked: https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=55702688. + _env.SetCurrentDirectory(Path.GetDirectoryName(projectFile.Path)); - _env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", buildInOutOfProcessNode ? "1" : "0"); - _env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); - string output = RunnerUtilities.ExecBootstrapedMSBuild( - $"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore" + - (analysisRequested ? " -analyze" : string.Empty), out bool success); - _env.Output.WriteLine(output); - success.ShouldBeTrue(); - // The conflicting outputs warning appears - but only if analysis was requested - if (analysisRequested) - { - output.ShouldContain("BC0101"); - } - else - { - output.ShouldNotContain("BC0101"); - } + _env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", buildInOutOfProcessNode ? "1" : "0"); + _env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); + string output = RunnerUtilities.ExecBootstrapedMSBuild( + $"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore" + + (analysisRequested ? " -analyze" : string.Empty), out bool success); + _env.Output.WriteLine(output); + success.ShouldBeTrue(); + // The conflicting outputs warning appears - but only if analysis was requested + if (analysisRequested) + { + output.ShouldContain("BC0101"); + } + else + { + output.ShouldNotContain("BC0101"); } } } diff --git a/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj b/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj index 1768410a565..3aa9eaff7d1 100644 --- a/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj +++ b/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj @@ -1,8 +1,4 @@ - - - - $(LatestDotNetCoreForMSBuild) @@ -29,39 +25,6 @@ - - - Shared\FileUtilities.cs - - - Shared\TempFileUtilities.cs - - - Shared\ErrorUtilities.cs - - - Shared\EscapingUtilities.cs - - - Shared\BuildEnvironmentHelper.cs - - - Shared\ProcessExtensions.cs - - - Shared\ResourceUtilities.cs - - - Shared\ExceptionHandling.cs - - - Shared\FileUtilitiesRegex.cs - - - Shared\AssemblyResources.cs - - - App.config diff --git a/src/BuildCheck.UnitTests/ParsedItemsAnalysisDataTests.cs b/src/BuildCheck.UnitTests/ParsedItemsAnalysisDataTests.cs new file mode 100644 index 00000000000..05d1266d2ac --- /dev/null +++ b/src/BuildCheck.UnitTests/ParsedItemsAnalysisDataTests.cs @@ -0,0 +1,46 @@ +// 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; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Experimental.BuildCheck; +using Microsoft.Build.UnitTests; +using Xunit; + +namespace Microsoft.Build.BuildCheck.UnitTests; + +public class ParsedItemsAnalysisDataTests +{ + [Fact] + public void ItemsHolder_GetItemsOfType_ShouldFilterProperly() + { + ProjectRootElement root = ProjectRootElement.Create(); + + ProjectItemElement el1 = ProjectItemElement.CreateDisconnected("ItemB", root); + ProjectItemElement el2 = ProjectItemElement.CreateDisconnected("ItemB", root); + ProjectItemElement el3 = ProjectItemElement.CreateDisconnected("ItemA", root); + ProjectItemElement el4 = ProjectItemElement.CreateDisconnected("ItemB", root); + ProjectItemElement el5 = ProjectItemElement.CreateDisconnected("ItemA", root); + + var items = new List() + { + el1, + el2, + el3, + el4, + el5 + }; + var itemsHolder = new ItemsHolder(items, new List()); + + var itemsA = itemsHolder.GetItemsOfType("ItemA").ToList(); + var itemsB = itemsHolder.GetItemsOfType("ItemB").ToList(); + + itemsA.ShouldBeSameIgnoringOrder(new List() { el3, el5 }); + itemsB.ShouldBeSameIgnoringOrder(new List() { el1, el2, el4 }); + } +} From c7d1b1dc0c8f853fca8fa2d72c98f4a897cd506f Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Thu, 11 Apr 2024 15:06:52 +0200 Subject: [PATCH 50/58] fix test failure --- .../BuildCheckManagerProvider.cs | 4 ++-- src/Build/Resources/Strings.resx | 4 ++-- src/Build/Resources/xlf/Strings.cs.xlf | 6 +++--- src/Build/Resources/xlf/Strings.de.xlf | 6 +++--- src/Build/Resources/xlf/Strings.es.xlf | 6 +++--- src/Build/Resources/xlf/Strings.fr.xlf | 6 +++--- src/Build/Resources/xlf/Strings.it.xlf | 6 +++--- src/Build/Resources/xlf/Strings.ja.xlf | 6 +++--- src/Build/Resources/xlf/Strings.ko.xlf | 6 +++--- src/Build/Resources/xlf/Strings.pl.xlf | 6 +++--- src/Build/Resources/xlf/Strings.pt-BR.xlf | 6 +++--- src/Build/Resources/xlf/Strings.ru.xlf | 6 +++--- src/Build/Resources/xlf/Strings.tr.xlf | 6 +++--- src/Build/Resources/xlf/Strings.zh-Hans.xlf | 6 +++--- src/Build/Resources/xlf/Strings.zh-Hant.xlf | 6 +++--- .../BuildCheckManagerProviderTests.cs | 20 +++++++++++-------- 16 files changed, 55 insertions(+), 51 deletions(-) diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 243406ca457..8ff2f376f36 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -181,8 +181,8 @@ internal void RegisterCustomAnalyzer( factory, instance.SupportedRules.Select(r => r.Id).ToArray(), instance.SupportedRules.Any(r => r.DefaultConfiguration.IsEnabled == true))); - _loggingService.LogComment(buildEventContext, MessageImportance.Normal, "CustomAnalyzerSuccessfulAcquisition", instance.GetType().Name); - } + _loggingService.LogComment(buildEventContext, MessageImportance.Normal, "CustomAnalyzerSuccessfulAcquisition", instance.FriendlyName); + } } } diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index b15e0af11dc..411664cf15e 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -2096,8 +2096,8 @@ Utilization: {0} Average Utilization: {1:###.0} - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. Failed to register the custom analyzer: {0}. diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index b6012e4e25b..138915e4e5c 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 3c406a8aa02..d20f945afef 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index b5ca6a54d32..d845d49371d 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 8bc5db5db18..b7f5bd632bf 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 8e77554cd99..eafdffdb722 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index cdc074c9f8e..87f2cd180cf 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 4ac484eee96..286afdc7f32 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 4b69db5542b..95d8d779152 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 54ea8154e0d..7f1c3ed8eba 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index fe01a60fd98..da30d2a9569 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 6a943e4649c..11500d3f554 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index e996b5c9b7e..e285f0ee332 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 8f91df055cf..a58adef6056 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -218,9 +218,9 @@ The message is emmited on failed loading of the custom analyzer rule in app domain. - Custom analyzer {0} has been registered successfully. - Custom analyzer {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer in app domain. + Custom analyzer rule: {0} has been registered successfully. + Custom analyzer rule: {0} has been registered successfully. + The message is emmited on successful loading of the custom analyzer rule in app domain. MSB4276: The default SDK resolver failed to resolve SDK "{0}" because directory "{1}" did not exist. diff --git a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs index 5b01f121d6b..9b9d42e18d1 100644 --- a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs +++ b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs @@ -1,7 +1,6 @@ // 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.Reflection; @@ -34,17 +33,17 @@ public BuildCheckManagerTests(ITestOutputHelper output) } [Theory] - [InlineData(true, "Custom analyzer BuildAnalyzerRuleMock has been registered successfully.")] - [InlineData(false, "Failed to ragister the custom analyzer: DummyPath.")] - public void ProcessAnalyzerAcquisitionTest(bool isAnalyzerRuleExist, string expectedMessage) + [InlineData(true, new[] { "Custom analyzer rule: Rule1 has been registered successfully.", "Custom analyzer rule: Rule2 has been registered successfully." })] + [InlineData(false, new[] { "Failed to register the custom analyzer: DummyPath." })] + public void ProcessAnalyzerAcquisitionTest(bool isAnalyzerRuleExist, string[] expectedMessages) { MockBuildCheckAcquisition(isAnalyzerRuleExist); MockEnabledDataSourcesDefinition(); _testedInstance.ProcessAnalyzerAcquisition(new AnalyzerAcquisitionData("DummyPath"), new BuildEventContext(1, 2, 3, 4, 5, 6, 7)); - _logger.AllBuildEvents.Where(be => be.GetType() == typeof(BuildMessageEventArgs)) - .ShouldContain(be => be.Message == expectedMessage); + _logger.AllBuildEvents.Where(be => be.GetType() == typeof(BuildMessageEventArgs)).Select(be => be.Message).ToArray() + .ShouldBeEquivalentTo(expectedMessages); } private void MockBuildCheckAcquisition(bool isAnalyzerRuleExist) => MockField("_acquisitionModule", new BuildCheckAcquisitionModuleMock(isAnalyzerRuleExist)); @@ -69,7 +68,7 @@ internal sealed class BuildCheckAcquisitionModuleMock : IBuildCheckAcquisitionMo public IEnumerable CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext) => _isAnalyzerRuleExistForTest - ? new List() { () => new BuildAnalyzerRuleMock() } + ? new List() { () => new BuildAnalyzerRuleMock("Rule1"), () => new BuildAnalyzerRuleMock("Rule2") } : new List(); } @@ -82,7 +81,12 @@ internal sealed class BuildAnalyzerRuleMock : BuildAnalyzer "Message format: {0}", new BuildAnalyzerConfiguration()); - public override string FriendlyName => "BuildAnalyzerRuleMock"; + internal BuildAnalyzerRuleMock(string friendlyName) + { + FriendlyName = friendlyName; + } + + public override string FriendlyName { get; } public override IReadOnlyList SupportedRules { get; } = new List() { SupportedRule }; From 724f1f36c1cf42564269a58dc6154cf99c1d42a3 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Mon, 15 Apr 2024 10:44:55 +0200 Subject: [PATCH 51/58] fix review comments --- .../BuildCheckAcquisitionModule.cs | 82 +++++++++---------- .../IBuildCheckAcquisitionModule.cs | 5 +- .../BuildCheckManagerProvider.cs | 11 ++- .../Infrastructure/IBuildCheckManager.cs | 75 +++++++++-------- .../BuildCheckManagerProviderTests.cs | 2 +- 5 files changed, 89 insertions(+), 86 deletions(-) diff --git a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs index 5ed589905e2..0c24f2cab6c 100644 --- a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs @@ -11,67 +11,65 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; -namespace Microsoft.Build.BuildCheck.Acquisition +namespace Microsoft.Build.BuildCheck.Acquisition; + +internal class BuildCheckAcquisitionModule : IBuildCheckAcquisitionModule { - internal class BuildCheckAcquisitionModule : IBuildCheckAcquisitionModule - { - private readonly ILoggingService _loggingService; + private readonly ILoggingService _loggingService; - internal BuildCheckAcquisitionModule(ILoggingService loggingService) - { - _loggingService = loggingService; - } + internal BuildCheckAcquisitionModule(ILoggingService loggingService) + { + _loggingService = loggingService; + } #if FEATURE_ASSEMBLYLOADCONTEXT - /// - /// AssemblyContextLoader used to load DLLs outside of msbuild.exe directory - /// - private static readonly CoreClrAssemblyLoader s_coreClrAssemblyLoader = new(); + /// + /// AssemblyContextLoader used to load DLLs outside of msbuild.exe directory + /// + private static readonly CoreClrAssemblyLoader s_coreClrAssemblyLoader = new(); #endif - public IEnumerable CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext) + /// + /// Creates a list of factory delegates for building analyzer rules instances from a given assembly path. + /// + public List CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext) + { + var analyzersFactories = new List(); + + try { - try - { - Assembly? assembly = null; + Assembly? assembly = null; #if FEATURE_ASSEMBLYLOADCONTEXT - assembly = s_coreClrAssemblyLoader.LoadFromPath(analyzerAcquisitionData.AssemblyPath); + assembly = s_coreClrAssemblyLoader.LoadFromPath(analyzerAcquisitionData.AssemblyPath); #else - assembly = Assembly.LoadFrom(analyzerAcquisitionData.AssemblyPath); + assembly = Assembly.LoadFrom(analyzerAcquisitionData.AssemblyPath); #endif - IEnumerable analyzerTypes = assembly.GetTypes().Where(t => typeof(BuildAnalyzer).IsAssignableFrom(t)); + IEnumerable analyzerTypes = assembly.GetTypes().Where(t => typeof(BuildAnalyzer).IsAssignableFrom(t)); - if (analyzerTypes.Any()) + foreach (Type analyzerType in analyzerTypes) + { + if (Activator.CreateInstance(analyzerType) is BuildAnalyzer instance) { - var analyzersFactory = new List(); - foreach (Type analyzerType in analyzerTypes) - { - if (Activator.CreateInstance(analyzerType) is BuildAnalyzer instance) - { - analyzersFactory.Add(() => instance); - } - else - { - throw new InvalidOperationException($"Failed to create an instance of type {analyzerType.FullName} as BuildAnalyzer."); - } - } - - return analyzersFactory; + analyzersFactories.Add(() => instance); + } + else + { + throw new InvalidOperationException($"Failed to create an instance of type {analyzerType.FullName} as BuildAnalyzer."); } } - catch (ReflectionTypeLoadException ex) + } + catch (ReflectionTypeLoadException ex) + { + if (ex.LoaderExceptions.Length != 0) { - if (ex.LoaderExceptions.Length != 0) + foreach (Exception? loaderException in ex.LoaderExceptions) { - foreach (Exception? loaderException in ex.LoaderExceptions) - { - _loggingService.LogComment(buildEventContext, MessageImportance.Normal, "CustomAnalyzerFailedRuleLoading", loaderException?.Message); - } + _loggingService.LogComment(buildEventContext, MessageImportance.Normal, "CustomAnalyzerFailedRuleLoading", loaderException?.Message); } } - - return Enumerable.Empty(); } + + return analyzersFactories; } } diff --git a/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs index 166aa3fb210..eb9f08b4625 100644 --- a/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs @@ -9,6 +9,9 @@ namespace Microsoft.Build.BuildCheck.Acquisition { internal interface IBuildCheckAcquisitionModule { - IEnumerable CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext); + /// + /// Creates a list of factory delegates for building analyzer rules instances from a given assembly path. + /// + List CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext); } } diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 8ff2f376f36..9704a5a5e9c 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -100,7 +100,7 @@ public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData, if (IsInProcNode) { var analyzersFactories = _acquisitionModule.CreateBuildAnalyzerFactories(acquisitionData, buildEventContext); - if (analyzersFactories.Any()) + if (analyzersFactories.Count != 0) { RegisterCustomAnalyzer(BuildCheckDataSource.EventArgs, analyzersFactories, buildEventContext); } @@ -146,7 +146,7 @@ private void RegisterBuiltInAnalyzers(BuildCheckDataSource buildCheckDataSource) /// /// To be used by acquisition module. - /// Registeres the custom analyzers, the construction of analyzers is deferred until the first using project is encountered. + /// Registers the custom analyzers, the construction of analyzers is deferred until the first using project is encountered. /// internal void RegisterCustomAnalyzers( BuildCheckDataSource buildCheckDataSource, @@ -165,8 +165,11 @@ internal void RegisterCustomAnalyzers( /// /// To be used by acquisition module - /// Registeres the custom analyzer, the construction of analyzer is needed during registration + /// Registers the custom analyzer, the construction of analyzer is needed during registration. /// + /// Represents different data sources used in build check operations. + /// A collection of build analyzer factories for rules instantiation. + /// The context of the build event. internal void RegisterCustomAnalyzer( BuildCheckDataSource buildCheckDataSource, IEnumerable factories, @@ -182,7 +185,7 @@ internal void RegisterCustomAnalyzer( instance.SupportedRules.Select(r => r.Id).ToArray(), instance.SupportedRules.Any(r => r.DefaultConfiguration.IsEnabled == true))); _loggingService.LogComment(buildEventContext, MessageImportance.Normal, "CustomAnalyzerSuccessfulAcquisition", instance.FriendlyName); - } + } } } diff --git a/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs b/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs index 117d3aa8a03..331502e95e0 100644 --- a/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs +++ b/src/Build/BuildCheck/Infrastructure/IBuildCheckManager.cs @@ -8,58 +8,57 @@ using Microsoft.Build.BuildCheck.Logging; using Microsoft.Build.Framework; -namespace Microsoft.Build.Experimental.BuildCheck +namespace Microsoft.Build.Experimental.BuildCheck; + +/// +/// Enumerates the different data sources used in build check operations. +/// +internal enum BuildCheckDataSource { /// - /// Enumerates the different data sources used in build check operations. + /// The data source is based on event arguments. /// - internal enum BuildCheckDataSource - { - /// - /// The data source is based on event arguments. - /// - EventArgs, - - /// - /// The data source is based on build execution. - /// - BuildExecution, + EventArgs, - /// - /// Represents the total number of values in the enum, used for indexing purposes. - /// - ValuesCount = BuildExecution + 1, - } + /// + /// The data source is based on build execution. + /// + BuildExecution, /// - /// The central manager for the BuildCheck - this is the integration point with MSBuild infrastructure. + /// Represents the total number of values in the enum, used for indexing purposes. /// - internal interface IBuildCheckManager - { - void ProcessEvaluationFinishedEventArgs( - AnalyzerLoggingContext buildAnalysisContext, - ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs); + ValuesCount = BuildExecution + 1, +} + +/// +/// The central manager for the BuildCheck - this is the integration point with MSBuild infrastructure. +/// +internal interface IBuildCheckManager +{ + void ProcessEvaluationFinishedEventArgs( + AnalyzerLoggingContext buildAnalysisContext, + ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs); - void SetDataSource(BuildCheckDataSource buildCheckDataSource); + void SetDataSource(BuildCheckDataSource buildCheckDataSource); - void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData, BuildEventContext buildEventContext); + void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData, BuildEventContext buildEventContext); - Dictionary CreateTracingStats(); + Dictionary CreateTracingStats(); - void FinalizeProcessing(LoggingContext loggingContext); + void FinalizeProcessing(LoggingContext loggingContext); - // All those to be called from RequestBuilder, - // but as well from the ConnectorLogger - as even if interleaved, it gives the info - // to manager about what analyzers need to be materialized and configuration fetched. - // No unloading of analyzers is yet considered - once loaded it stays for whole build. - void StartProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext, string fullPath); + // All those to be called from RequestBuilder, + // but as well from the ConnectorLogger - as even if interleaved, it gives the info + // to manager about what analyzers need to be materialized and configuration fetched. + // No unloading of analyzers is yet considered - once loaded it stays for whole build. + void StartProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext, string fullPath); - void EndProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); + void EndProjectEvaluation(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); - void StartProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); + void StartProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); - void EndProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); + void EndProjectRequest(BuildCheckDataSource buildCheckDataSource, BuildEventContext buildEventContext); - void Shutdown(); - } + void Shutdown(); } diff --git a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs index 9b9d42e18d1..1518241d42f 100644 --- a/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs +++ b/src/BuildCheck.UnitTests/BuildCheckManagerProviderTests.cs @@ -66,7 +66,7 @@ internal sealed class BuildCheckAcquisitionModuleMock : IBuildCheckAcquisitionMo internal BuildCheckAcquisitionModuleMock(bool isAnalyzerRuleExistForTest) => _isAnalyzerRuleExistForTest = isAnalyzerRuleExistForTest; - public IEnumerable CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext) + public List CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext) => _isAnalyzerRuleExistForTest ? new List() { () => new BuildAnalyzerRuleMock("Rule1"), () => new BuildAnalyzerRuleMock("Rule2") } : new List(); From 3e0b455bd310c9c8d0d0a7322a0e22bedcbab505 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Mon, 15 Apr 2024 10:45:04 +0200 Subject: [PATCH 52/58] fix review comments --- .../Acquisition/IBuildCheckAcquisitionModule.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs index eb9f08b4625..e86dc7d0c4a 100644 --- a/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/IBuildCheckAcquisitionModule.cs @@ -5,13 +5,12 @@ using Microsoft.Build.BuildCheck.Infrastructure; using Microsoft.Build.Framework; -namespace Microsoft.Build.BuildCheck.Acquisition +namespace Microsoft.Build.BuildCheck.Acquisition; + +internal interface IBuildCheckAcquisitionModule { - internal interface IBuildCheckAcquisitionModule - { - /// - /// Creates a list of factory delegates for building analyzer rules instances from a given assembly path. - /// - List CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext); - } + /// + /// Creates a list of factory delegates for building analyzer rules instances from a given assembly path. + /// + List CreateBuildAnalyzerFactories(AnalyzerAcquisitionData analyzerAcquisitionData, BuildEventContext buildEventContext); } From dde2231e31f4c0e0b2ef884332b188bd92841cd9 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Tue, 16 Apr 2024 12:28:21 +0200 Subject: [PATCH 53/58] remove extra file --- .../Microsoft.AnalyzerTemplate/Directory.Build.props | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props diff --git a/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props b/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props deleted file mode 100644 index 3b752b831cc..00000000000 --- a/template_feed/Microsoft.AnalyzerTemplate/Directory.Build.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - $([MSBuild]::RegisterAnalyzer($(MSBuildThisFileDirectory)..\lib\Company.AnalyzerTemplate.dll)) - - - - - From c5dbb564a8a6d4ecc61c20b35370209c9f2bc550 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Wed, 17 Apr 2024 16:43:07 +0200 Subject: [PATCH 54/58] fix review comments --- .../Infrastructure/BuildCheckManagerProvider.cs | 9 +-------- src/Build/Evaluation/IntrinsicFunctions.cs | 7 +++---- src/Build/Resources/Strings.resx | 12 ++++++++---- src/Build/Resources/xlf/Strings.cs.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.de.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.es.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.fr.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.it.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.ja.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.ko.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.pl.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.pt-BR.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.ru.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.tr.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.zh-Hans.xlf | 15 ++++++++++----- src/Build/Resources/xlf/Strings.zh-Hant.xlf | 15 ++++++++++----- 16 files changed, 142 insertions(+), 81 deletions(-) diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 9704a5a5e9c..33985a81707 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -112,14 +112,7 @@ public void ProcessAnalyzerAcquisition(AnalyzerAcquisitionData acquisitionData, else { BuildCheckAcquisitionEventArgs eventArgs = acquisitionData.ToBuildEventArgs(); - - // We may want to pass the real context here (from evaluation) - eventArgs.BuildEventContext = new BuildEventContext( - BuildEventContext.InvalidNodeId, - BuildEventContext.InvalidProjectInstanceId, - BuildEventContext.InvalidProjectContextId, - BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidTaskId); + eventArgs.BuildEventContext = buildEventContext; _loggingService.LogBuildEvent(eventArgs); } diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index f8cb37bce93..0453be18625 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -665,10 +665,7 @@ public static string GetMSBuildExtensionsPath() return BuildEnvironmentHelper.Instance.MSBuildExtensionsPath; } - public static bool IsRunningFromVisualStudio() - { - return BuildEnvironmentHelper.Instance.Mode == BuildEnvironmentMode.VisualStudio; - } + public static bool IsRunningFromVisualStudio() => BuildEnvironmentHelper.Instance.Mode == BuildEnvironmentMode.VisualStudio; public static bool RegisterAnalyzer(string pathToAssembly, LoggingContext loggingContext) { @@ -680,6 +677,8 @@ public static bool RegisterAnalyzer(string pathToAssembly, LoggingContext loggin return true; } + loggingContext.LogComment(MessageImportance.Low, "CustomAnalyzerAssemblyNotExist", pathToAssembly); + return false; } diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 411664cf15e..9604524cd08 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -2097,15 +2097,19 @@ Utilization: {0} Average Utilization: {1:###.0} Custom analyzer rule: {0} has been registered successfully. - The message is emmited on successful loading of the custom analyzer rule in app domain. + The message is emitted on successful loading of the custom analyzer rule in app domain. Failed to register the custom analyzer: {0}. - The message is emmited on failed loading of the custom analyzer in app domain. + The message is emitted on failed loading of the custom analyzer in app domain. - Failed to instantiate the custom analyzer rule with the next exception: {0}. - The message is emmited on failed loading of the custom analyzer rule in app domain. + Failed to instantiate the custom analyzer rule with the following exception: {0}. + The message is emitted on failed loading of the custom analyzer rule in app domain. + + + Failed to find the specified custom analyzer assembly: {0}. Please check if it exists. + The message is emitted when the custom analyzer assembly can not be found.