diff --git a/src/Build.UnitTests/BackEnd/MockLoggingService.cs b/src/Build.UnitTests/BackEnd/MockLoggingService.cs index a62b03686d4..e30cc99a0a8 100644 --- a/src/Build.UnitTests/BackEnd/MockLoggingService.cs +++ b/src/Build.UnitTests/BackEnd/MockLoggingService.cs @@ -496,7 +496,7 @@ public BuildEventContext CreateProjectCacheBuildEventContext(int submissionId, i => new BuildEventContext(0, 0, 0, 0, 0, 0, 0); /// - public void LogProjectEvaluationStarted(BuildEventContext eventContext, string projectFile) + public void LogProjectEvaluationStarted(BuildEventContext eventContext, string projectFile, bool isRestore) { } diff --git a/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs b/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs index d9cb65d4b93..3592c72faec 100644 --- a/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/EvaluationLoggingContext.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; +using System.Collections.Generic; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Framework; using Microsoft.Build.Framework.Profiler; @@ -27,9 +28,9 @@ public EvaluationLoggingContext(ILoggingService loggingService, BuildEventContex IsValid = true; } - public void LogProjectEvaluationStarted() + public void LogProjectEvaluationStarted(bool isRestore) { - LoggingService.LogProjectEvaluationStarted(BuildEventContext, _projectFile); + LoggingService.LogProjectEvaluationStarted(BuildEventContext, _projectFile, isRestore); } /// diff --git a/src/Build/BackEnd/Components/Logging/ILoggingService.cs b/src/Build/BackEnd/Components/Logging/ILoggingService.cs index 3e44402a61e..e8138d35a4e 100644 --- a/src/Build/BackEnd/Components/Logging/ILoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/ILoggingService.cs @@ -485,8 +485,9 @@ MessageImportance MinimumRequiredMessageImportance /// /// The event context to use for logging /// Project file being built + /// If the project is currently in restore phase /// The evaluation event context for the project. - void LogProjectEvaluationStarted(BuildEventContext eventContext, string projectFile); + void LogProjectEvaluationStarted(BuildEventContext eventContext, string projectFile, bool isRestore); /// /// Logs that a project evaluation has finished diff --git a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs index 547554d06d8..eb02baacd0c 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingServiceLogMethods.cs @@ -444,14 +444,15 @@ public BuildEventContext CreateProjectCacheBuildEventContext( } /// - public void LogProjectEvaluationStarted(BuildEventContext projectEvaluationEventContext, string projectFile) + public void LogProjectEvaluationStarted(BuildEventContext projectEvaluationEventContext, string projectFile, bool isRestore) { ProjectEvaluationStartedEventArgs evaluationEvent = new ProjectEvaluationStartedEventArgs(ResourceUtilities.GetResourceString("EvaluationStarted"), projectFile) { BuildEventContext = projectEvaluationEventContext, - ProjectFile = projectFile + ProjectFile = projectFile, + IsRestore = isRestore }; ProcessLoggingEvent(evaluationEvent); diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 1a4693ba685..fb1fbcd50d0 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -1105,9 +1105,9 @@ 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); + var propertyEntry = _requestEntry.RequestConfiguration.GlobalProperties[MSBuildConstants.MSBuildIsRestoring]; + IBuildCheckManager buildCheckManager = propertyEntry is not null ? null : (_componentHost.GetComponent(BuildComponentType.BuildCheckManagerProvider) as IBuildCheckManagerProvider)!.Instance; + buildCheckManager?.SetDataSource(BuildCheckDataSource.BuildExecution); // 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 @@ -1121,10 +1121,12 @@ private async Task BuildProject() // Load the project if (!_requestEntry.RequestConfiguration.IsLoaded) { - buildCheckManager.StartProjectEvaluation( + + buildCheckManager?.StartProjectEvaluation( BuildCheckDataSource.BuildExecution, _requestEntry.Request.ParentBuildEventContext, _requestEntry.RequestConfiguration.ProjectFullPath); + _requestEntry.RequestConfiguration.LoadProjectIntoConfiguration( _componentHost, @@ -1146,13 +1148,13 @@ private async Task BuildProject() } finally { - buildCheckManager.EndProjectEvaluation( + buildCheckManager?.EndProjectEvaluation( BuildCheckDataSource.BuildExecution, _requestEntry.Request.ParentBuildEventContext); } _projectLoggingContext = _nodeLoggingContext.LogProjectStarted(_requestEntry); - buildCheckManager.StartProjectRequest( + buildCheckManager?.StartProjectRequest( BuildCheckDataSource.BuildExecution, _requestEntry.Request.ParentBuildEventContext); @@ -1223,7 +1225,7 @@ private async Task BuildProject() } finally { - buildCheckManager.EndProjectRequest( + buildCheckManager?.EndProjectRequest( BuildCheckDataSource.BuildExecution, _requestEntry.Request.ParentBuildEventContext); } diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs index 361c30c4200..02e3b9cf78c 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs @@ -8,6 +8,7 @@ using Microsoft.Build.BuildCheck.Acquisition; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; +using Microsoft.Build.Shared; using static Microsoft.Build.BuildCheck.Infrastructure.BuildCheckManagerProvider; namespace Microsoft.Build.BuildCheck.Infrastructure; @@ -31,6 +32,8 @@ internal BuildCheckConnectorLogger( public string? Parameters { get; set; } + private bool isRestore = false; + public void Initialize(IEventSource eventSource) { eventSource.AnyEventRaised += EventSource_AnyEventRaised; @@ -48,6 +51,11 @@ public void Shutdown() private void HandleProjectEvaluationFinishedEvent(ProjectEvaluationFinishedEventArgs eventArgs) { + if (isRestore) + { + return; + } + if (!IsMetaProjFile(eventArgs.ProjectFile)) { _buildCheckManager.ProcessEvaluationFinishedEventArgs( @@ -60,6 +68,16 @@ private void HandleProjectEvaluationFinishedEvent(ProjectEvaluationFinishedEvent private void HandleProjectEvaluationStartedEvent(ProjectEvaluationStartedEventArgs eventArgs) { + if (eventArgs.IsRestore) + { + isRestore = true; + return; + } + if (isRestore) + { + isRestore = false; + } + if (!IsMetaProjFile(eventArgs.ProjectFile)) { _buildCheckManager.StartProjectEvaluation(BuildCheckDataSource.EventArgs, eventArgs.BuildEventContext!, eventArgs.ProjectFile!); @@ -100,8 +118,22 @@ private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e) { { typeof(ProjectEvaluationFinishedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationFinishedEvent((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(ProjectStartedEventArgs), (BuildEventArgs e) => + { + if (!isRestore) + { + _buildCheckManager.StartProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!); + } + } + }, + { typeof(ProjectFinishedEventArgs), (BuildEventArgs e) => + { + if (!isRestore) + { + _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(), e.BuildEventContext!) }, }; diff --git a/src/Build/Evaluation/Evaluator.cs b/src/Build/Evaluation/Evaluator.cs index 85447378533..9c411aa528d 100644 --- a/src/Build/Evaluation/Evaluator.cs +++ b/src/Build/Evaluation/Evaluator.cs @@ -626,7 +626,7 @@ private void Evaluate() } } - _evaluationLoggingContext.LogProjectEvaluationStarted(); + _evaluationLoggingContext.LogProjectEvaluationStarted(_data.GlobalPropertiesDictionary[MSBuildConstants.MSBuildIsRestoring] is not null); ; ErrorUtilities.VerifyThrow(_data.EvaluationId != BuildEventContext.InvalidEvaluationId, "Evaluation should produce an evaluation ID"); diff --git a/src/BuildCheck.UnitTests/EndToEndTests.cs b/src/BuildCheck.UnitTests/EndToEndTests.cs index 9351612060f..85f5f495d4b 100644 --- a/src/BuildCheck.UnitTests/EndToEndTests.cs +++ b/src/BuildCheck.UnitTests/EndToEndTests.cs @@ -35,32 +35,60 @@ public EndToEndTests(ITestOutputHelper output) [InlineData(false, false)] public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode, bool analysisRequested) { - string contents = $""" - - + TransientTestFile projectFile = SetupTestFiles(); + _env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", buildInOutOfProcessNode ? "1" : "0"); + _env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1"); + string output = RunnerUtilities.ExecBootstrapedMSBuild( + $"{Path.GetFileName(projectFile.Path)} /m:1 /p:BuildProjectReferences=false -nr:False -restore" + + (analysisRequested ? " -analyze" : string.Empty), out bool success, false, _env.Output); + _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"); + } + } + + [Fact] + public void NoRunOnRestore() + { + TransientTestFile projectFile = SetupTestFiles(); + string output = RunnerUtilities.ExecBootstrapedMSBuild( + $"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -analyze -t:restore", out bool success); + _env.Output.WriteLine(output); + success.ShouldBeTrue(); + output.ShouldNotContain("BC0101"); + } + + private TransientTestFile SetupTestFiles() + { + { + string contents = $""" + - Exe - net8.0 - enable - enable + Exe + net8.0 + enable + enable - + - Test + Test - + - + - - - - - + """; - string contents2 = $""" + string contents2 = $""" @@ -77,24 +105,21 @@ public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode, bool ana - - - - """; - 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*/); + 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*/ - """ + TransientTestFile config = _env.CreateFile(workFolder, "editorconfig.json", + /*lang=json,strict*/ + """ { "BC0101": { "IsEnabled": true, @@ -112,26 +137,11 @@ public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode, bool ana } """); - // 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, false, _env.Output); - _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"); + // 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)); + return (projectFile); } } } diff --git a/src/Framework/ProjectEvaluationStartedEventArgs.cs b/src/Framework/ProjectEvaluationStartedEventArgs.cs index 6d231fe1428..06bb2ceb994 100644 --- a/src/Framework/ProjectEvaluationStartedEventArgs.cs +++ b/src/Framework/ProjectEvaluationStartedEventArgs.cs @@ -30,5 +30,10 @@ public ProjectEvaluationStartedEventArgs(string? message, params object[]? messa /// Gets or sets the full path of the project that started evaluation. /// public string? ProjectFile { get; set; } + + /// + /// Gets or sets is the project is currently on restore phase. + /// + public bool IsRestore { get; internal set; } } }