@@ -103,9 +103,8 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke
103
103
compilationHandler = new CompilationHandler ( Context . Reporter , Context . ProcessRunner ) ;
104
104
var scopedCssFileHandler = new ScopedCssFileHandler ( Context . Reporter , projectMap , browserConnector ) ;
105
105
var projectLauncher = new ProjectLauncher ( Context , projectMap , browserConnector , compilationHandler , iteration ) ;
106
- var outputDirectories = GetProjectOutputDirectories ( evaluationResult . ProjectGraph ) ;
107
- ReportOutputDirectories ( outputDirectories ) ;
108
- var changeFilter = new Predicate < ChangedPath > ( change => AcceptChange ( change , evaluationResult , outputDirectories ) ) ;
106
+ evaluationResult . ItemExclusions . Report ( Context . Reporter ) ;
107
+ var changeFilter = new Predicate < ChangedPath > ( change => AcceptChange ( change , evaluationResult ) ) ;
109
108
110
109
var rootProjectNode = evaluationResult . ProjectGraph . GraphRoots . Single ( ) ;
111
110
@@ -180,7 +179,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke
180
179
return ;
181
180
}
182
181
183
- fileWatcher . WatchContainingDirectories ( evaluationResult . Files . Keys , includeSubdirectories : true ) ;
182
+ evaluationResult . WatchFiles ( fileWatcher ) ;
184
183
185
184
var changedFilesAccumulator = ImmutableList < ChangedPath > . Empty ;
186
185
@@ -426,17 +425,21 @@ async Task<ImmutableList<ChangedFile>> CaptureChangedFilesSnapshot(ImmutableDict
426
425
427
426
// When a new file is added we need to run design-time build to find out
428
427
// what kind of the file it is and which project(s) does it belong to (can be linked, web asset, etc.).
428
+ // We also need to re-evaluate the project if any project files have been modified.
429
429
// We don't need to rebuild and restart the application though.
430
- var hasAddedFile = changedFiles . Any ( f => f . Kind is ChangeKind . Add ) ;
430
+ var fileAdded = changedFiles . Any ( f => f . Kind is ChangeKind . Add ) ;
431
+ var projectChanged = ! fileAdded && changedFiles . Any ( f => evaluationResult . BuildFiles . Contains ( f . Item . FilePath ) ) ;
432
+ var evaluationRequired = fileAdded || projectChanged ;
431
433
432
- if ( hasAddedFile )
434
+ if ( evaluationRequired )
433
435
{
434
- Context . Reporter . Report ( MessageDescriptor . FileAdditionTriggeredReEvaluation ) ;
436
+ Context . Reporter . Report ( fileAdded ? MessageDescriptor . FileAdditionTriggeredReEvaluation : MessageDescriptor . ProjectChangeTriggeredReEvaluation ) ;
435
437
438
+ // TODO: consider re-evaluating only affected projects instead of the whole graph.
436
439
evaluationResult = await EvaluateRootProjectAsync ( iterationCancellationToken ) ;
437
440
438
441
// additional directories may have been added:
439
- fileWatcher . WatchContainingDirectories ( evaluationResult . Files . Keys , includeSubdirectories : true ) ;
442
+ evaluationResult . WatchFiles ( fileWatcher ) ;
440
443
441
444
await compilationHandler . Workspace . UpdateProjectConeAsync ( RootFileSetFactory . RootProjectFile , iterationCancellationToken ) ;
442
445
@@ -482,7 +485,7 @@ async Task<ImmutableList<ChangedFile>> CaptureChangedFilesSnapshot(ImmutableDict
482
485
483
486
ReportFileChanges ( changedFiles ) ;
484
487
485
- if ( ! hasAddedFile )
488
+ if ( ! evaluationRequired )
486
489
{
487
490
// update the workspace to reflect changes in the file content:
488
491
await compilationHandler . Workspace . UpdateFileContentAsync ( changedFiles , iterationCancellationToken ) ;
@@ -551,7 +554,7 @@ private async ValueTask WaitForFileChangeBeforeRestarting(FileWatcher fileWatche
551
554
{
552
555
if ( ! fileWatcher . WatchingDirectories )
553
556
{
554
- fileWatcher . WatchContainingDirectories ( evaluationResult . Files . Keys , includeSubdirectories : true ) ;
557
+ evaluationResult . WatchFiles ( fileWatcher ) ;
555
558
}
556
559
557
560
_ = await fileWatcher . WaitForFileChangeAsync (
@@ -571,7 +574,7 @@ private async ValueTask WaitForFileChangeBeforeRestarting(FileWatcher fileWatche
571
574
}
572
575
}
573
576
574
- private bool AcceptChange ( ChangedPath change , EvaluationResult evaluationResult , IReadOnlySet < string > outputDirectories )
577
+ private bool AcceptChange ( ChangedPath change , EvaluationResult evaluationResult )
575
578
{
576
579
var ( path , kind ) = change ;
577
580
@@ -581,21 +584,27 @@ private bool AcceptChange(ChangedPath change, EvaluationResult evaluationResult,
581
584
return true ;
582
585
}
583
586
584
- // Ignore other changes to output and intermediate output directories.
587
+ if ( ! AcceptChange ( change ) )
588
+ {
589
+ return false ;
590
+ }
591
+
592
+ // changes in *.*proj, *.props, *.targets:
593
+ if ( evaluationResult . BuildFiles . Contains ( path ) )
594
+ {
595
+ return true ;
596
+ }
597
+
598
+ // Ignore other changes that match DefaultItemExcludes glob if EnableDefaultItems is true,
599
+ // otherwise changes under output and intermediate output directories.
585
600
//
586
601
// Unsupported scenario:
587
602
// - msbuild target adds source files to intermediate output directory and Compile items
588
603
// based on the content of non-source file.
589
604
//
590
605
// On the other hand, changes to source files produced by source generators will be registered
591
606
// since the changes to additional file will trigger workspace update, which will trigger the source generator.
592
- if ( PathUtilities . ContainsPath ( outputDirectories , path ) )
593
- {
594
- Context . Reporter . Report ( MessageDescriptor . IgnoringChangeInOutputDirectory , kind , path ) ;
595
- return false ;
596
- }
597
-
598
- return AcceptChange ( change ) ;
607
+ return ! evaluationResult . ItemExclusions . IsExcluded ( path , kind , Context . Reporter ) ;
599
608
}
600
609
601
610
private bool AcceptChange ( ChangedPath change )
@@ -618,54 +627,6 @@ private bool AcceptChange(ChangedPath change)
618
627
private static bool IsHiddenDirectory ( string dir )
619
628
=> Path . GetFileName ( dir ) . StartsWith ( '.' ) ;
620
629
621
- private static IReadOnlySet < string > GetProjectOutputDirectories ( ProjectGraph projectGraph )
622
- {
623
- // TODO: https://github.com/dotnet/sdk/issues/45539
624
- // Consider evaluating DefaultItemExcludes and DefaultExcludesInProjectFolder msbuild properties using
625
- // https://github.com/dotnet/msbuild/blob/37eb419ad2c986ac5530292e6ee08e962390249e/src/Build/Globbing/MSBuildGlob.cs
626
- // to determine which directories should be excluded.
627
-
628
- var projectOutputDirectories = new HashSet < string > ( PathUtilities . OSSpecificPathComparer ) ;
629
-
630
- foreach ( var projectNode in projectGraph . ProjectNodes )
631
- {
632
- TryAdd ( projectNode . GetOutputDirectory ( ) ) ;
633
- TryAdd ( projectNode . GetIntermediateOutputDirectory ( ) ) ;
634
- }
635
-
636
- return projectOutputDirectories ;
637
-
638
- void TryAdd ( string ? dir )
639
- {
640
- try
641
- {
642
- if ( dir != null )
643
- {
644
- // msbuild properties may use '\' as a directory separator even on Unix.
645
- // GetFullPath does not normalize '\' to '/' on Unix.
646
- if ( Path . DirectorySeparatorChar == '/' )
647
- {
648
- dir = dir . Replace ( '\\ ' , '/' ) ;
649
- }
650
-
651
- projectOutputDirectories . Add ( Path . TrimEndingDirectorySeparator ( Path . GetFullPath ( dir ) ) ) ;
652
- }
653
- }
654
- catch
655
- {
656
- // ignore
657
- }
658
- }
659
- }
660
-
661
- private void ReportOutputDirectories ( IReadOnlySet < string > directories )
662
- {
663
- foreach ( var dir in directories )
664
- {
665
- Context . Reporter . Verbose ( $ "Output directory: '{ dir } '") ;
666
- }
667
- }
668
-
669
630
internal static IEnumerable < ChangedPath > NormalizePathChanges ( IEnumerable < ChangedPath > changes )
670
631
=> changes
671
632
. GroupBy ( keySelector : change => change . Path )
0 commit comments