1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System . Collections . Immutable ;
4
5
using Microsoft . Build . Graph ;
5
6
6
7
namespace Microsoft . DotNet . Watch ;
7
8
8
- internal sealed class EvaluationResult ( IReadOnlyDictionary < string , FileItem > files , ProjectGraph ? projectGraph )
9
+ internal sealed class EvaluationResult ( IReadOnlyDictionary < string , FileItem > files , ProjectGraph projectGraph )
9
10
{
10
11
public readonly IReadOnlyDictionary < string , FileItem > Files = files ;
11
- public readonly ProjectGraph ? ProjectGraph = projectGraph ;
12
+ public readonly ProjectGraph ProjectGraph = projectGraph ;
12
13
13
14
public readonly FilePathExclusions ItemExclusions
14
15
= projectGraph != null ? FilePathExclusions . Create ( projectGraph ) : FilePathExclusions . Empty ;
15
16
16
17
private readonly Lazy < IReadOnlySet < string > > _lazyBuildFiles
17
18
= new ( ( ) => projectGraph != null ? CreateBuildFileSet ( projectGraph ) : new HashSet < string > ( ) ) ;
18
19
19
- public static IReadOnlySet < string > CreateBuildFileSet ( ProjectGraph projectGraph )
20
+ private static IReadOnlySet < string > CreateBuildFileSet ( ProjectGraph projectGraph )
20
21
=> projectGraph . ProjectNodes . SelectMany ( p => p . ProjectInstance . ImportPaths )
21
22
. Concat ( projectGraph . ProjectNodes . Select ( p => p . ProjectInstance . FullPath ) )
22
23
. ToHashSet ( PathUtilities . OSSpecificPathComparer ) ;
@@ -29,4 +30,138 @@ public void WatchFiles(FileWatcher fileWatcher)
29
30
fileWatcher . WatchContainingDirectories ( Files . Keys , includeSubdirectories : true ) ;
30
31
fileWatcher . WatchFiles ( BuildFiles ) ;
31
32
}
33
+
34
+ /// <summary>
35
+ /// Loads project graph and performs design-time build.
36
+ /// </summary>
37
+ public static EvaluationResult ? TryCreate (
38
+ string rootProjectPath ,
39
+ IEnumerable < string > buildArguments ,
40
+ IReporter reporter ,
41
+ EnvironmentOptions environmentOptions ,
42
+ bool restore ,
43
+ CancellationToken cancellationToken )
44
+ {
45
+ var buildReporter = new BuildReporter ( reporter , environmentOptions ) ;
46
+
47
+ // See https://github.com/dotnet/project-system/blob/main/docs/well-known-project-properties.md
48
+
49
+ var globalOptions = CommandLineOptions . ParseBuildProperties ( buildArguments )
50
+ . ToImmutableDictionary ( keySelector : arg => arg . key , elementSelector : arg => arg . value )
51
+ . SetItem ( PropertyNames . DotNetWatchBuild , "true" )
52
+ . SetItem ( PropertyNames . DesignTimeBuild , "true" )
53
+ . SetItem ( PropertyNames . SkipCompilerExecution , "true" )
54
+ . SetItem ( PropertyNames . ProvideCommandLineArgs , "true" )
55
+ // F# targets depend on host path variable:
56
+ . SetItem ( "DOTNET_HOST_PATH" , environmentOptions . MuxerPath ) ;
57
+
58
+ var projectGraph = ProjectGraphUtilities . TryLoadProjectGraph (
59
+ rootProjectPath ,
60
+ globalOptions ,
61
+ reporter ,
62
+ projectGraphRequired : true ,
63
+ cancellationToken ) ;
64
+
65
+ if ( projectGraph == null )
66
+ {
67
+ return null ;
68
+ }
69
+
70
+ var rootNode = projectGraph . GraphRoots . Single ( ) ;
71
+
72
+ if ( restore )
73
+ {
74
+ using ( var loggers = buildReporter . GetLoggers ( rootNode . ProjectInstance . FullPath , "Restore" ) )
75
+ {
76
+ if ( ! rootNode . ProjectInstance . Build ( [ TargetNames . Restore ] , loggers ) )
77
+ {
78
+ reporter . Error ( $ "Failed to restore project '{ rootProjectPath } '.") ;
79
+ loggers . ReportOutput ( ) ;
80
+ return null ;
81
+ }
82
+ }
83
+ }
84
+
85
+ var fileItems = new Dictionary < string , FileItem > ( ) ;
86
+
87
+ foreach ( var project in projectGraph . ProjectNodesTopologicallySorted )
88
+ {
89
+ // Deep copy so that we can reuse the graph for building additional targets later on.
90
+ // If we didn't copy the instance the targets might duplicate items that were already
91
+ // populated by design-time build.
92
+ var projectInstance = project . ProjectInstance . DeepCopy ( ) ;
93
+
94
+ // skip outer build project nodes:
95
+ if ( projectInstance . GetPropertyValue ( PropertyNames . TargetFramework ) == "" )
96
+ {
97
+ continue ;
98
+ }
99
+
100
+ var customCollectWatchItems = projectInstance . GetStringListPropertyValue ( PropertyNames . CustomCollectWatchItems ) ;
101
+
102
+ using ( var loggers = buildReporter . GetLoggers ( projectInstance . FullPath , "DesignTimeBuild" ) )
103
+ {
104
+ if ( ! projectInstance . Build ( [ TargetNames . Compile , .. customCollectWatchItems ] , loggers ) )
105
+ {
106
+ reporter . Error ( $ "Failed to build project '{ projectInstance . FullPath } '.") ;
107
+ loggers . ReportOutput ( ) ;
108
+ return null ;
109
+ }
110
+ }
111
+
112
+ var projectPath = projectInstance . FullPath ;
113
+ var projectDirectory = Path . GetDirectoryName ( projectPath ) ! ;
114
+
115
+ // TODO: Compile and AdditionalItems should be provided by Roslyn
116
+ var items = projectInstance . GetItems ( ItemNames . Compile )
117
+ . Concat ( projectInstance . GetItems ( ItemNames . AdditionalFiles ) )
118
+ . Concat ( projectInstance . GetItems ( ItemNames . Watch ) ) ;
119
+
120
+ foreach ( var item in items )
121
+ {
122
+ AddFile ( item . EvaluatedInclude , staticWebAssetPath : null ) ;
123
+ }
124
+
125
+ if ( ! environmentOptions . SuppressHandlingStaticContentFiles &&
126
+ projectInstance . GetBooleanPropertyValue ( PropertyNames . UsingMicrosoftNETSdkRazor ) &&
127
+ projectInstance . GetBooleanPropertyValue ( PropertyNames . DotNetWatchContentFiles , defaultValue : true ) )
128
+ {
129
+ foreach ( var item in projectInstance . GetItems ( ItemNames . Content ) )
130
+ {
131
+ if ( item . GetBooleanMetadataValue ( MetadataNames . Watch , defaultValue : true ) )
132
+ {
133
+ var relativeUrl = item . EvaluatedInclude . Replace ( '\\ ' , '/' ) ;
134
+ if ( relativeUrl . StartsWith ( "wwwroot/" ) )
135
+ {
136
+ AddFile ( item . EvaluatedInclude , staticWebAssetPath : relativeUrl ) ;
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ void AddFile ( string include , string ? staticWebAssetPath )
143
+ {
144
+ var filePath = Path . GetFullPath ( Path . Combine ( projectDirectory , include ) ) ;
145
+
146
+ if ( ! fileItems . TryGetValue ( filePath , out var existingFile ) )
147
+ {
148
+ fileItems . Add ( filePath , new FileItem
149
+ {
150
+ FilePath = filePath ,
151
+ ContainingProjectPaths = [ projectPath ] ,
152
+ StaticWebAssetPath = staticWebAssetPath ,
153
+ } ) ;
154
+ }
155
+ else if ( ! existingFile . ContainingProjectPaths . Contains ( projectPath ) )
156
+ {
157
+ // linked files might be included to multiple projects:
158
+ existingFile . ContainingProjectPaths . Add ( projectPath ) ;
159
+ }
160
+ }
161
+ }
162
+
163
+ buildReporter . ReportWatchedFiles ( fileItems ) ;
164
+
165
+ return new EvaluationResult ( fileItems , projectGraph ) ;
166
+ }
32
167
}
0 commit comments