Simplify exception handling for graph builds#7831
Conversation
|
|
||
| if (ex is CircularDependencyException) | ||
| { | ||
| LogInvalidProjectFileError(new InvalidProjectFileException(ex.Message, ex)); |
There was a problem hiding this comment.
Two questions:
Does this get the right line number, column number, etc.? Maybe have to use one of the constructors with more parameters?
Doesn't this mean we aren't retaining if this has already been logged?
There was a problem hiding this comment.
After the latest iteration, it looks like this:
MSBuild version 17.4.0-dev-22365-01+1223c9a6b for .NET Framework
Build started 7/15/2022 1:28:30 PM.
MSBUILD : error : MSB4251: There is a circular dependency involving the following projects:
MSBUILD : error : C:\Users\David\Code\tmp\a.proj ->
MSBUILD : error : C:\Users\David\Code\tmp\b.proj ->
MSBUILD : error : C:\Users\David\Code\tmp\a.proj
Build FAILED.
MSBUILD : error : MSB4251: There is a circular dependency involving the following projects:
MSBUILD : error : C:\Users\David\Code\tmp\a.proj ->
MSBUILD : error : C:\Users\David\Code\tmp\b.proj ->
MSBUILD : error : C:\Users\David\Code\tmp\a.proj
0 Warning(s)
1 Error(s)
Time Elapsed 00:00:00.25
I don't think there are line numbers to give really since this isn't like non-graph builds where the circular dependency comes from a specific <MSBuild> task call. I guess in a graph build it comes from a <ProjectReference>, but we don't really keep the xml elements around to figure out where those are.
There was a problem hiding this comment.
Compare with today's pretty awful behavior:
Microsoft (R) Build Engine version 17.2.1+52cd2da31 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
Build started 7/15/2022 1:43:31 PM.
Build FAILED.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03
MSBUILD : error MSB1025: An internal failure occurred while running MSBuild.
Microsoft.Build.Exceptions.CircularDependencyException: MSB4251: There is a circular dependency involving the following projects:
C:\Users\David\Code\tmp\a.proj ->
C:\Users\David\Code\tmp\b.proj ->
C:\Users\David\Code\tmp\a.proj
at Microsoft.Build.Graph.GraphBuilder.<DetectCycles>g__VisitNode|32_0(ProjectGraphNode node, IDictionary`2 nodeState)
at Microsoft.Build.Graph.GraphBuilder.DetectCycles(IReadOnlyCollection`1 entryPointNodes, ProjectInterpretation projectInterpretation, Dictionary`2 allParsedProjects)
at Microsoft.Build.Graph.GraphBuilder.BuildGraph()
at Microsoft.Build.Graph.ProjectGraph..ctor(IEnumerable`1 entryPoints, ProjectCollection projectCollection, ProjectInstanceFactoryFunc projectInstanceFactory, Int32 degreeOfParallelism, CancellationToken cancellationToken)
at Microsoft.Build.Execution.BuildManager.ExecuteGraphBuildScheduler(GraphBuildSubmission submission)
at Microsoft.Build.Execution.BuildManager.<>c__DisplayClass85_0.<ExecuteSubmission>b__0()
MSBUILD : error MSB1025: An internal failure occurred while running MSBuild.
Microsoft.Build.Exceptions.CircularDependencyException: MSB4251: There is a circular dependency involving the following projects:
C:\Users\David\Code\tmp\a.proj ->
C:\Users\David\Code\tmp\b.proj ->
C:\Users\David\Code\tmp\a.proj
at Microsoft.Build.CommandLine.MSBuildApp.BuildProject(String projectFile, String[] targets, String toolsVersion, Dictionary`2 globalProperties, Dictionary`2 restoreProperties, ILogger[] loggers, LoggerVerbosity verbosity, DistributedLoggerRecord[] distributedLoggerRecords, Boolean needToValidateProject, String schemaFile, Int32 cpuCount, Boolean enableNodeReuse, TextWriter preprocessWriter, TextWriter targetsWriter, Boolean detailedSummary, ISet`1 warningsAsErrors, ISet`1 warningsNotAsErrors, ISet`1 warningsAsMessages, Boolean enableRestore, ProfilerLogger profilerLogger, Boolean enableProfiler, Boolean interactive, Boolean isolateProjects, GraphBuildOptions graphBuildOptions, Boolean lowPriority, String[] inputResultsCaches, String outputResultsCache, String commandLine)
at Microsoft.Build.CommandLine.MSBuildApp.Execute(String commandLine)
Unhandled Exception: Microsoft.Build.Exceptions.CircularDependencyException: MSB4251: There is a circular dependency involving the following projects:
C:\Users\David\Code\tmp\a.proj ->
C:\Users\David\Code\tmp\b.proj ->
C:\Users\David\Code\tmp\a.proj
at Microsoft.Build.CommandLine.MSBuildApp.BuildProject(String projectFile, String[] targets, String toolsVersion, Dictionary`2 globalProperties, Dictionary`2 restoreProperties, ILogger[] loggers, LoggerVerbosity verbosity, DistributedLoggerRecord[] distributedLoggerRecords, Boolean needToValidateProject, String schemaFile, Int32 cpuCount, Boolean enableNodeReuse, TextWriter preprocessWriter, TextWriter targetsWriter, Boolean detailedSummary, ISet`1 warningsAsErrors, ISet`1 warningsNotAsErrors, ISet`1 warningsAsMessages, Boolean enableRestore, ProfilerLogger profilerLogger, Boolean enableProfiler, Boolean interactive, Boolean isolateProjects, GraphBuildOptions graphBuildOptions, Boolean lowPriority, String[] inputResultsCaches, String outputResultsCache, String commandLine)
at Microsoft.Build.CommandLine.MSBuildApp.Execute(String commandLine)
at Microsoft.Build.CommandLine.MSBuildApp.Main()
| } | ||
| }, | ||
| ShouldExpectException = true | ||
| NumExpectedExceptions = 3 |
|
|
||
| AggregateException aggException = result.Exception.ShouldBeOfType<AggregateException>(); | ||
| aggException.InnerExceptions.Count.ShouldBe(2); | ||
| aggException.InnerExceptions[0].ShouldBeOfType<InvalidProjectFileException>().ProjectFile.ShouldBeOneOf(project2, project3); |
There was a problem hiding this comment.
Can you assert the ordering here? The previous version seemed to assert project2 was always first.
There was a problem hiding this comment.
Oh, because there was no project3. Never mind.
| // InvalidProjectFileExceptions and its aggregates have already been logged. | ||
| if (exception is not InvalidProjectFileException | ||
| && !(exception is AggregateException aggregateException && aggregateException.InnerExceptions.All(innerException => innerException is InvalidProjectFileException))) | ||
| && !(exception is AggregateException aggregateException && aggregateException.InnerExceptions.All(innerException => innerException is InvalidProjectFileException)) |
There was a problem hiding this comment.
| && !(exception is AggregateException aggregateException && aggregateException.InnerExceptions.All(innerException => innerException is InvalidProjectFileException)) | |
| && !(exception is AggregateException aggregateException && aggregateException.InnerExceptions.All(innerException => innerException is InvalidProjectFileException || innerException is CircularDependencyException)) |
?
There was a problem hiding this comment.
It actually has to be && exception is not CircularDependencyException
There was a problem hiding this comment.
You already added that one; I meant within the AggregateException. I imagine it's ok if it's an aggregation of InvalidProjectFileExceptions and CircularDependencyExceptions, right?
There was a problem hiding this comment.
I don't think a InvalidProjectFileException and CircularDependencyException are both possible at the same time. I think all projects are evaluated and then as a separate pass the edges are added and that's where circular dependencies are detected. The 1st pass would error with InvalidProjectFileExceptions
|
Compare before and after when there are multiple invalid projects in the graph: |
Just some cleanup and simplification in the exception handling for graph builds.
Functionally here are the differences:
ParallelWorkSetnow throws an aggregate exception containing all exceptions instead of just the first exception that gets thrown.ParallelWorkSettasks no longer go away when they throw. They're caught and added to a list used in the point above.BuildManagernow properly logs invalid project errors for each invalid project (due to the help of the points above). Previously it was attempting to catch the agg exception which was never thrown and also the way it was in the catch effectively made it unreachable in the first place.GraphBuildResultnow exposes theCircularDependencyExceptionvia theExceptionfield instead ofExceptionbeing null whileCircularDependencywas true.