22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System . Collections . Immutable ;
5+ using System . Reflection ;
6+ using System . Runtime . Versioning ;
57using Microsoft . Build . Evaluation ;
8+ using Microsoft . Build . Execution ;
69using Microsoft . Build . Graph ;
10+ using Microsoft . DotNet . ProjectTools ;
711using Microsoft . Extensions . Logging ;
812using ILogger = Microsoft . Extensions . Logging . ILogger ;
913
1014namespace Microsoft . DotNet . Watch ;
1115
12- internal sealed class ProjectGraphFactory ( ImmutableDictionary < string , string > globalOptions )
16+ internal sealed class ProjectGraphFactory
1317{
1418 /// <summary>
1519 /// Reuse <see cref="ProjectCollection"/> with XML element caching to improve performance.
1620 ///
1721 /// The cache is automatically updated when build files change.
1822 /// https://github.com/dotnet/msbuild/blob/b6f853defccd64ae1e9c7cf140e7e4de68bff07c/src/Build/Definition/ProjectCollection.cs#L343-L354
1923 /// </summary>
20- private readonly ProjectCollection _collection = new (
21- globalProperties : globalOptions ,
22- loggers : [ ] ,
23- remoteLoggers : [ ] ,
24- ToolsetDefinitionLocations . Default ,
25- maxNodeCount : 1 ,
26- onlyLogCriticalEvents : false ,
27- loadProjectsReadOnly : false ,
28- useAsynchronousLogging : false ,
29- reuseProjectRootElementCache : true ) ;
24+ private readonly ProjectCollection _collection ;
25+
26+ private readonly ImmutableDictionary < string , string > _globalOptions ;
27+ private readonly ProjectRepresentation _rootProject ;
28+
29+ // Only the root project can be virtual. #:project does not support targeting other single-file projects.
30+ private readonly VirtualProjectBuilder ? _virtualRootProjectBuilder ;
31+
32+ public ProjectGraphFactory (
33+ ProjectRepresentation rootProject ,
34+ string ? targetFramework ,
35+ ImmutableDictionary < string , string > globalOptions )
36+ {
37+ _collection = new (
38+ globalProperties : globalOptions ,
39+ loggers : [ ] ,
40+ remoteLoggers : [ ] ,
41+ ToolsetDefinitionLocations . Default ,
42+ maxNodeCount : 1 ,
43+ onlyLogCriticalEvents : false ,
44+ loadProjectsReadOnly : false ,
45+ useAsynchronousLogging : false ,
46+ reuseProjectRootElementCache : true ) ;
47+
48+ _globalOptions = globalOptions ;
49+ _rootProject = rootProject ;
50+
51+ if ( rootProject . EntryPointFilePath != null )
52+ {
53+ _virtualRootProjectBuilder = new VirtualProjectBuilder ( rootProject . EntryPointFilePath , targetFramework ?? GetProductTargetFramework ( ) ) ;
54+ }
55+ }
56+
57+ private static string GetProductTargetFramework ( )
58+ {
59+ var attribute = typeof ( VirtualProjectBuilder ) . Assembly . GetCustomAttribute < TargetFrameworkAttribute > ( ) ?? throw new InvalidOperationException ( ) ;
60+ var version = new FrameworkName ( attribute . FrameworkName ) . Version ;
61+ return $ "net{ version . Major } .{ version . Minor } ";
62+ }
3063
3164 /// <summary>
3265 /// Tries to create a project graph by running the build evaluation phase on the <paramref name="rootProjectFile"/>.
3366 /// </summary>
3467 public ProjectGraph ? TryLoadProjectGraph (
35- string rootProjectFile ,
3668 ILogger logger ,
3769 bool projectGraphRequired ,
3870 CancellationToken cancellationToken )
3971 {
40- var entryPoint = new ProjectGraphEntryPoint ( rootProjectFile , globalOptions ) ;
72+ var entryPoint = new ProjectGraphEntryPoint ( _rootProject . ProjectGraphPath , _globalOptions ) ;
4173 try
4274 {
43- return new ProjectGraph ( [ entryPoint ] , _collection , projectInstanceFactory : null , cancellationToken ) ;
75+ return new ProjectGraph ( [ entryPoint ] , _collection , ( path , globalProperties , collection ) => CreateProjectInstance ( path , globalProperties , collection , logger ) , cancellationToken ) ;
76+ }
77+ catch ( ProjectCreationFailedException )
78+ {
79+ // Errors have already been reported.
4480 }
4581 catch ( Exception e ) when ( e is not OperationCanceledException )
4682 {
47- // ProejctGraph aggregates OperationCanceledException exception,
83+ // ProjectGraph aggregates OperationCanceledException exception,
4884 // throw here to propagate the cancellation.
4985 cancellationToken . ThrowIfCancellationRequested ( ) ;
5086
@@ -54,7 +90,10 @@ internal sealed class ProjectGraphFactory(ImmutableDictionary<string, string> gl
5490 {
5591 foreach ( var inner in innerExceptions )
5692 {
57- Report ( inner ) ;
93+ if ( inner is not ProjectCreationFailedException )
94+ {
95+ Report ( inner ) ;
96+ }
5897 }
5998 }
6099 else
@@ -77,4 +116,38 @@ void Report(Exception e)
77116
78117 return null ;
79118 }
119+
120+ private ProjectInstance CreateProjectInstance ( string projectPath , Dictionary < string , string > globalProperties , ProjectCollection projectCollection , ILogger logger )
121+ {
122+ if ( _virtualRootProjectBuilder != null && projectPath == _rootProject . ProjectGraphPath )
123+ {
124+ var anyError = false ;
125+
126+ _virtualRootProjectBuilder . CreateProjectInstance (
127+ projectCollection ,
128+ ( sourceFile , textSpan , message ) =>
129+ {
130+ anyError = true ;
131+ logger . LogError ( "{Location}: {Message}" , sourceFile . GetLocationString ( textSpan ) , message ) ;
132+ } ,
133+ out var projectInstance ,
134+ out _ ) ;
135+
136+ if ( anyError )
137+ {
138+ throw new ProjectCreationFailedException ( ) ;
139+ }
140+
141+ return projectInstance ;
142+ }
143+
144+ return new ProjectInstance (
145+ projectPath ,
146+ globalProperties ,
147+ toolsVersion : "Current" ,
148+ subToolsetVersion : null ,
149+ projectCollection ) ;
150+ }
151+
152+ private sealed class ProjectCreationFailedException ( ) : Exception ( ) ;
80153}
0 commit comments