diff --git a/src/BuiltInTools/DotNetWatchTasks/DotNetWatchTasks.csproj b/src/BuiltInTools/DotNetWatchTasks/DotNetWatchTasks.csproj index aee7ab0bd9a3..af178129da01 100644 --- a/src/BuiltInTools/DotNetWatchTasks/DotNetWatchTasks.csproj +++ b/src/BuiltInTools/DotNetWatchTasks/DotNetWatchTasks.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/BuiltInTools/dotnet-watch/Internal/BrowserSpecificReporter.cs b/src/BuiltInTools/dotnet-watch/Browser/BrowserSpecificReporter.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/BrowserSpecificReporter.cs rename to src/BuiltInTools/dotnet-watch/Browser/BrowserSpecificReporter.cs diff --git a/src/BuiltInTools/dotnet-watch/DotNetWatch.targets b/src/BuiltInTools/dotnet-watch/Build/DotNetWatch.targets similarity index 100% rename from src/BuiltInTools/dotnet-watch/DotNetWatch.targets rename to src/BuiltInTools/dotnet-watch/Build/DotNetWatch.targets diff --git a/src/BuiltInTools/dotnet-watch/EvaluationResult.cs b/src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/EvaluationResult.cs rename to src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs diff --git a/src/BuiltInTools/dotnet-watch/FileItem.cs b/src/BuiltInTools/dotnet-watch/Build/FileItem.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/FileItem.cs rename to src/BuiltInTools/dotnet-watch/Build/FileItem.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/FilePathExclusions.cs b/src/BuiltInTools/dotnet-watch/Build/FilePathExclusions.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/FilePathExclusions.cs rename to src/BuiltInTools/dotnet-watch/Build/FilePathExclusions.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/MSBuildFileSetResult.cs b/src/BuiltInTools/dotnet-watch/Build/MSBuildFileSetResult.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/MSBuildFileSetResult.cs rename to src/BuiltInTools/dotnet-watch/Build/MSBuildFileSetResult.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/MsBuildFileSetFactory.cs b/src/BuiltInTools/dotnet-watch/Build/MsBuildFileSetFactory.cs similarity index 98% rename from src/BuiltInTools/dotnet-watch/Internal/MsBuildFileSetFactory.cs rename to src/BuiltInTools/dotnet-watch/Build/MsBuildFileSetFactory.cs index 166a2d19b08c..008634208eee 100644 --- a/src/BuiltInTools/dotnet-watch/Internal/MsBuildFileSetFactory.cs +++ b/src/BuiltInTools/dotnet-watch/Build/MsBuildFileSetFactory.cs @@ -68,7 +68,7 @@ internal class MSBuildFileSetFactory( reporter.Output($"MSBuild output from target '{TargetName}':"); } - BuildUtilities.ReportBuildOutput(reporter, capturedOutput, success, projectDisplay: null); + BuildOutput.ReportBuildOutput(reporter, capturedOutput, success, projectDisplay: null); if (!success) { return null; diff --git a/src/BuiltInTools/dotnet-watch/HotReload/ProjectNodeMap.cs b/src/BuiltInTools/dotnet-watch/Build/ProjectNodeMap.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/ProjectNodeMap.cs rename to src/BuiltInTools/dotnet-watch/Build/ProjectNodeMap.cs diff --git a/src/BuiltInTools/dotnet-watch/CommandLineOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/CommandLineOptions.cs rename to src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs diff --git a/src/BuiltInTools/dotnet-watch/DotNetWatchContext.cs b/src/BuiltInTools/dotnet-watch/CommandLine/DotNetWatchContext.cs similarity index 66% rename from src/BuiltInTools/dotnet-watch/DotNetWatchContext.cs rename to src/BuiltInTools/dotnet-watch/CommandLine/DotNetWatchContext.cs index 53dd2f99dbde..d0f597d134f8 100644 --- a/src/BuiltInTools/dotnet-watch/DotNetWatchContext.cs +++ b/src/BuiltInTools/dotnet-watch/CommandLine/DotNetWatchContext.cs @@ -12,5 +12,13 @@ internal sealed class DotNetWatchContext public required ProcessRunner ProcessRunner { get; init; } public required ProjectOptions RootProjectOptions { get; init; } + + public MSBuildFileSetFactory CreateMSBuildFileSetFactory() + => new( + RootProjectOptions.ProjectPath, + RootProjectOptions.BuildArguments, + EnvironmentOptions, + ProcessRunner, + Reporter); } } diff --git a/src/BuiltInTools/dotnet-watch/EnvironmentOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/EnvironmentOptions.cs rename to src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs diff --git a/src/BuiltInTools/dotnet-watch/EnvironmentVariables.cs b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/EnvironmentVariables.cs rename to src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs diff --git a/src/BuiltInTools/dotnet-watch/EnvironmentVariablesBuilder.cs b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariablesBuilder.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/EnvironmentVariablesBuilder.cs rename to src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariablesBuilder.cs diff --git a/src/BuiltInTools/dotnet-watch/GlobalOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/GlobalOptions.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/GlobalOptions.cs rename to src/BuiltInTools/dotnet-watch/CommandLine/GlobalOptions.cs diff --git a/src/BuiltInTools/dotnet-watch/ProjectOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/ProjectOptions.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/ProjectOptions.cs rename to src/BuiltInTools/dotnet-watch/CommandLine/ProjectOptions.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/ChangeKind.cs b/src/BuiltInTools/dotnet-watch/FileWatcher/ChangeKind.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/FileWatcher/ChangeKind.cs rename to src/BuiltInTools/dotnet-watch/FileWatcher/ChangeKind.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DirectoryWatcher.cs b/src/BuiltInTools/dotnet-watch/FileWatcher/DirectoryWatcher.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DirectoryWatcher.cs rename to src/BuiltInTools/dotnet-watch/FileWatcher/DirectoryWatcher.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/EventBasedDirectoryWatcher.cs b/src/BuiltInTools/dotnet-watch/FileWatcher/EventBasedDirectoryWatcher.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/FileWatcher/EventBasedDirectoryWatcher.cs rename to src/BuiltInTools/dotnet-watch/FileWatcher/EventBasedDirectoryWatcher.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs b/src/BuiltInTools/dotnet-watch/FileWatcher/FileWatcher.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs rename to src/BuiltInTools/dotnet-watch/FileWatcher/FileWatcher.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingDirectoryWatcher.cs b/src/BuiltInTools/dotnet-watch/FileWatcher/PollingDirectoryWatcher.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingDirectoryWatcher.cs rename to src/BuiltInTools/dotnet-watch/FileWatcher/PollingDirectoryWatcher.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/BlazorWebAssemblyAppModel.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyAppModel.cs rename to src/BuiltInTools/dotnet-watch/HotReload/AppModels/BlazorWebAssemblyAppModel.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/BlazorWebAssemblyDeltaApplier.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs rename to src/BuiltInTools/dotnet-watch/HotReload/AppModels/BlazorWebAssemblyDeltaApplier.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/BlazorWebAssemblyHostedAppModel.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedAppModel.cs rename to src/BuiltInTools/dotnet-watch/HotReload/AppModels/BlazorWebAssemblyHostedAppModel.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedDeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/BlazorWebAssemblyHostedDeltaApplier.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedDeltaApplier.cs rename to src/BuiltInTools/dotnet-watch/HotReload/AppModels/BlazorWebAssemblyHostedDeltaApplier.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/DefaultAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/DefaultAppModel.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/DefaultAppModel.cs rename to src/BuiltInTools/dotnet-watch/HotReload/AppModels/DefaultAppModel.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/DefaultDeltaApplier.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs rename to src/BuiltInTools/dotnet-watch/HotReload/AppModels/DefaultDeltaApplier.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/DeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/DeltaApplier.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/DeltaApplier.cs rename to src/BuiltInTools/dotnet-watch/HotReload/AppModels/DeltaApplier.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/HotReloadAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/HotReloadAppModel.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/HotReloadAppModel.cs rename to src/BuiltInTools/dotnet-watch/HotReload/AppModels/HotReloadAppModel.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/SingleProcessDeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/SingleProcessDeltaApplier.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/SingleProcessDeltaApplier.cs rename to src/BuiltInTools/dotnet-watch/HotReload/AppModels/SingleProcessDeltaApplier.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs similarity index 88% rename from src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs rename to src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs index fafecf41b950..e958d41308d3 100644 --- a/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs @@ -3,20 +3,24 @@ using System.Collections.Immutable; using System.Diagnostics; -using Microsoft.Build.Graph; using Microsoft.CodeAnalysis; namespace Microsoft.DotNet.Watch { - internal sealed partial class HotReloadDotNetWatcher : Watcher + internal sealed class HotReloadDotNetWatcher { private readonly IConsole _console; private readonly IRuntimeProcessLauncherFactory? _runtimeProcessLauncherFactory; private readonly RestartPrompt? _rudeEditRestartPrompt; - public HotReloadDotNetWatcher(DotNetWatchContext context, IConsole console, MSBuildFileSetFactory fileSetFactory, IRuntimeProcessLauncherFactory? runtimeProcessLauncherFactory) - : base(context, fileSetFactory) + private readonly DotNetWatchContext _context; + private readonly MSBuildFileSetFactory _fileSetFactory; + + public HotReloadDotNetWatcher(DotNetWatchContext context, IConsole console, IRuntimeProcessLauncherFactory? runtimeProcessLauncherFactory) { + _context = context; + _fileSetFactory = context.CreateMSBuildFileSetFactory(); + _console = console; _runtimeProcessLauncherFactory = runtimeProcessLauncherFactory; if (!context.Options.NonInteractive) @@ -33,32 +37,32 @@ public HotReloadDotNetWatcher(DotNetWatchContext context, IConsole console, MSBu } } - public override async Task WatchAsync(CancellationToken shutdownCancellationToken) + public async Task WatchAsync(CancellationToken shutdownCancellationToken) { CancellationTokenSource? forceRestartCancellationSource = null; var hotReloadEnabledMessage = "Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload."; - if (!Context.Options.NonInteractive) + if (!_context.Options.NonInteractive) { - Context.Reporter.Output($"{hotReloadEnabledMessage}{Environment.NewLine} {(Context.EnvironmentOptions.SuppressEmojis ? string.Empty : "💡")} Press \"Ctrl + R\" to restart.", emoji: "🔥"); + _context.Reporter.Output($"{hotReloadEnabledMessage}{Environment.NewLine} {(_context.EnvironmentOptions.SuppressEmojis ? string.Empty : "💡")} Press \"Ctrl + R\" to restart.", emoji: "🔥"); _console.KeyPressed += (key) => { if (key.Modifiers.HasFlag(ConsoleModifiers.Control) && key.Key == ConsoleKey.R && forceRestartCancellationSource is { } source) { // provide immediate feedback to the user: - Context.Reporter.Report(source.IsCancellationRequested ? MessageDescriptor.RestartInProgress : MessageDescriptor.RestartRequested); + _context.Reporter.Report(source.IsCancellationRequested ? MessageDescriptor.RestartInProgress : MessageDescriptor.RestartRequested); source.Cancel(); } }; } else { - Context.Reporter.Output(hotReloadEnabledMessage, emoji: "🔥"); + _context.Reporter.Output(hotReloadEnabledMessage, emoji: "🔥"); } - await using var browserConnector = new BrowserConnector(Context); - using var fileWatcher = new FileWatcher(Context.Reporter); + await using var browserConnector = new BrowserConnector(_context); + using var fileWatcher = new FileWatcher(_context.Reporter); for (var iteration = 0; !shutdownCancellationToken.IsCancellationRequested; iteration++) { @@ -79,7 +83,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke try { - var rootProjectOptions = Context.RootProjectOptions; + var rootProjectOptions = _context.RootProjectOptions; var runtimeProcessLauncherFactory = _runtimeProcessLauncherFactory; // Evaluate the target to find out the set of files to watch. @@ -96,14 +100,14 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke if (rootProjectCapabilities.Contains(AspireServiceFactory.AppHostProjectCapability)) { runtimeProcessLauncherFactory ??= AspireServiceFactory.Instance; - Context.Reporter.Verbose("Using Aspire process launcher."); + _context.Reporter.Verbose("Using Aspire process launcher."); } - var projectMap = new ProjectNodeMap(evaluationResult.ProjectGraph, Context.Reporter); - compilationHandler = new CompilationHandler(Context.Reporter, Context.ProcessRunner); - var scopedCssFileHandler = new ScopedCssFileHandler(Context.Reporter, projectMap, browserConnector); - var projectLauncher = new ProjectLauncher(Context, projectMap, browserConnector, compilationHandler, iteration); - evaluationResult.ItemExclusions.Report(Context.Reporter); + var projectMap = new ProjectNodeMap(evaluationResult.ProjectGraph, _context.Reporter); + compilationHandler = new CompilationHandler(_context.Reporter, _context.ProcessRunner); + var scopedCssFileHandler = new ScopedCssFileHandler(_context.Reporter, projectMap, browserConnector); + var projectLauncher = new ProjectLauncher(_context, projectMap, browserConnector, compilationHandler, iteration); + evaluationResult.ItemExclusions.Report(_context.Reporter); var rootProjectNode = evaluationResult.ProjectGraph.GraphRoots.Single(); @@ -118,7 +122,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke } var (buildSucceeded, buildOutput, _) = await BuildProjectAsync(rootProjectOptions.ProjectPath, rootProjectOptions.BuildArguments, iterationCancellationToken); - BuildUtilities.ReportBuildOutput(Context.Reporter, buildOutput, buildSucceeded, projectDisplay: rootProjectOptions.ProjectPath); + BuildOutput.ReportBuildOutput(_context.Reporter, buildOutput, buildSucceeded, projectDisplay: rootProjectOptions.ProjectPath); if (!buildSucceeded) { continue; @@ -165,7 +169,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke return; } - await compilationHandler.Workspace.UpdateProjectConeAsync(RootFileSetFactory.RootProjectFile, iterationCancellationToken); + await compilationHandler.Workspace.UpdateProjectConeAsync(_fileSetFactory.RootProjectFile, iterationCancellationToken); // Solution must be initialized after we load the solution but before we start watching for file changes to avoid race condition // when the EnC session captures content of the file after the changes has already been made. @@ -186,7 +190,7 @@ void FileChangedCallback(ChangedPath change) { if (AcceptChange(change, evaluationResult)) { - Context.Reporter.Verbose($"File change: {change.Kind} '{change.Path}'."); + _context.Reporter.Verbose($"File change: {change.Kind} '{change.Path}'."); ImmutableInterlocked.Update(ref changedFilesAccumulator, changedPaths => changedPaths.Add(change)); } } @@ -242,7 +246,7 @@ void FileChangedCallback(ChangedPath change) if (!rootProjectCapabilities.Contains("SupportsHotReload")) { - Context.Reporter.Warn($"Project '{rootProject.GetDisplayName()}' does not support Hot Reload and must be rebuilt."); + _context.Reporter.Warn($"Project '{rootProject.GetDisplayName()}' does not support Hot Reload and must be rebuilt."); // file change already detected waitForFileChangeBeforeRestarting = false; @@ -264,7 +268,7 @@ void FileChangedCallback(ChangedPath change) HotReloadEventSource.Log.HotReloadStart(HotReloadEventSource.StartType.CompilationHandler); var (projectsToRebuild, projectsToRestart) = await compilationHandler.HandleManagedCodeChangesAsync( - autoRestart: Context.Options.NonInteractive || _rudeEditRestartPrompt?.AutoRestartPreference is true, + autoRestart: _context.Options.NonInteractive || _rudeEditRestartPrompt?.AutoRestartPreference is true, restartPrompt: async (projectNames, cancellationToken) => { if (_rudeEditRestartPrompt != null) @@ -279,11 +283,11 @@ void FileChangedCallback(ChangedPath change) } else { - Context.Reporter.Output("Affected projects:"); + _context.Reporter.Output("Affected projects:"); foreach (var projectName in projectNames.OrderBy(n => n)) { - Context.Reporter.Output(" " + projectName); + _context.Reporter.Output(" " + projectName); } question = "Do you want to restart these projects?"; @@ -292,11 +296,11 @@ void FileChangedCallback(ChangedPath change) return await _rudeEditRestartPrompt.WaitForRestartConfirmationAsync(question, cancellationToken); } - Context.Reporter.Verbose("Restarting without prompt since dotnet-watch is running in non-interactive mode."); + _context.Reporter.Verbose("Restarting without prompt since dotnet-watch is running in non-interactive mode."); foreach (var projectName in projectNames) { - Context.Reporter.Verbose($" Project to restart: '{projectName}'"); + _context.Reporter.Verbose($" Project to restart: '{projectName}'"); } return true; @@ -332,7 +336,7 @@ void FileChangedCallback(ChangedPath change) foreach (var (success, output, projectPath) in buildResults) { - BuildUtilities.ReportBuildOutput(Context.Reporter, output, success, projectPath); + BuildOutput.ReportBuildOutput(_context.Reporter, output, success, projectPath); } if (buildResults.All(result => result.success)) @@ -349,7 +353,7 @@ void FileChangedCallback(ChangedPath change) _ = await fileWatcher.WaitForFileChangeAsync( change => AcceptChange(change, evaluationResult), - startedWatching: () => Context.Reporter.Report(MessageDescriptor.FixBuildError), + startedWatching: () => _context.Reporter.Report(MessageDescriptor.FixBuildError), shutdownCancellationToken); } @@ -357,7 +361,7 @@ void FileChangedCallback(ChangedPath change) // Apply them to the workspace. _ = await CaptureChangedFilesSnapshot(projectsToRebuild); - Context.Reporter.Report(MessageDescriptor.ProjectsRebuilt, projectsToRebuild.Count); + _context.Reporter.Report(MessageDescriptor.ProjectsRebuilt, projectsToRebuild.Count); } if (!projectsToRestart.IsEmpty) @@ -382,10 +386,10 @@ await Task.WhenAll( })) .WaitAsync(shutdownCancellationToken); - Context.Reporter.Report(MessageDescriptor.ProjectsRestarted, projectsToRestart.Length); + _context.Reporter.Report(MessageDescriptor.ProjectsRestarted, projectsToRestart.Length); } - Context.Reporter.Report(MessageDescriptor.HotReloadChangeHandled, stopwatch.ElapsedMilliseconds); + _context.Reporter.Report(MessageDescriptor.HotReloadChangeHandled, stopwatch.ElapsedMilliseconds); async Task> CaptureChangedFilesSnapshot(ImmutableDictionary? rebuiltProjects) { @@ -434,7 +438,7 @@ async Task> CaptureChangedFilesSnapshot(ImmutableDict if (evaluationRequired) { - Context.Reporter.Report(fileAdded ? MessageDescriptor.FileAdditionTriggeredReEvaluation : MessageDescriptor.ProjectChangeTriggeredReEvaluation); + _context.Reporter.Report(fileAdded ? MessageDescriptor.FileAdditionTriggeredReEvaluation : MessageDescriptor.ProjectChangeTriggeredReEvaluation); // TODO: consider re-evaluating only affected projects instead of the whole graph. evaluationResult = await EvaluateRootProjectAsync(iterationCancellationToken); @@ -442,7 +446,7 @@ async Task> CaptureChangedFilesSnapshot(ImmutableDict // additional directories may have been added: evaluationResult.WatchFiles(fileWatcher); - await compilationHandler.Workspace.UpdateProjectConeAsync(RootFileSetFactory.RootProjectFile, iterationCancellationToken); + await compilationHandler.Workspace.UpdateProjectConeAsync(_fileSetFactory.RootProjectFile, iterationCancellationToken); if (shutdownCancellationToken.IsCancellationRequested) { @@ -454,7 +458,7 @@ async Task> CaptureChangedFilesSnapshot(ImmutableDict changedFiles = [.. changedFiles .Select(f => evaluationResult.Files.TryGetValue(f.Item.FilePath, out var evaluatedFile) ? f with { Item = evaluatedFile } : f)]; - Context.Reporter.Report(MessageDescriptor.ReEvaluationCompleted); + _context.Reporter.Report(MessageDescriptor.ReEvaluationCompleted); } if (rebuiltProjects != null) @@ -558,17 +562,17 @@ private async ValueTask WaitForFileChangeBeforeRestarting(FileWatcher fileWatche _ = await fileWatcher.WaitForFileChangeAsync( evaluationResult.Files, - startedWatching: () => Context.Reporter.Report(MessageDescriptor.WaitingForFileChangeBeforeRestarting), + startedWatching: () => _context.Reporter.Report(MessageDescriptor.WaitingForFileChangeBeforeRestarting), cancellationToken); } else { // evaluation cancelled - watch for any changes in the directory tree containing the root project: - fileWatcher.WatchContainingDirectories([RootFileSetFactory.RootProjectFile], includeSubdirectories: true); + fileWatcher.WatchContainingDirectories([_fileSetFactory.RootProjectFile], includeSubdirectories: true); _ = await fileWatcher.WaitForFileChangeAsync( acceptChange: change => AcceptChange(change), - startedWatching: () => Context.Reporter.Report(MessageDescriptor.WaitingForFileChangeBeforeRestarting), + startedWatching: () => _context.Reporter.Report(MessageDescriptor.WaitingForFileChangeBeforeRestarting), cancellationToken); } } @@ -608,7 +612,7 @@ private bool AcceptChange(ChangedPath change, EvaluationResult evaluationResult) // // On the other hand, changes to source files produced by source generators will be registered // since the changes to additional file will trigger workspace update, which will trigger the source generator. - return !evaluationResult.ItemExclusions.IsExcluded(path, kind, Context.Reporter); + return !evaluationResult.ItemExclusions.IsExcluded(path, kind, _context.Reporter); } private bool AcceptChange(ChangedPath change) @@ -617,7 +621,7 @@ private bool AcceptChange(ChangedPath change) if (PathUtilities.GetContainingDirectories(path).FirstOrDefault(IsHiddenDirectory) is { } containingHiddenDir) { - Context.Reporter.Report(MessageDescriptor.IgnoringChangeInHiddenDirectory, containingHiddenDir, kind, path); + _context.Reporter.Report(MessageDescriptor.IgnoringChangeInHiddenDirectory, containingHiddenDir, kind, path); return false; } @@ -703,12 +707,12 @@ internal static IEnumerable NormalizePathChanges(IEnumerable changedFiles) @@ -722,7 +726,7 @@ void Report(ChangeKind kind) var items = changedFiles.Where(item => item.Kind == kind).ToArray(); if (items is not []) { - Context.Reporter.Output(GetMessage(items, kind)); + _context.Reporter.Output(GetMessage(items, kind)); } } @@ -756,7 +760,7 @@ private async ValueTask EvaluateRootProjectAsync(CancellationT { cancellationToken.ThrowIfCancellationRequested(); - var result = await RootFileSetFactory.TryCreateAsync(requireProjectGraph: true, cancellationToken); + var result = await _fileSetFactory.TryCreateAsync(requireProjectGraph: true, cancellationToken); if (result != null) { Debug.Assert(result.ProjectGraph != null); @@ -764,9 +768,9 @@ private async ValueTask EvaluateRootProjectAsync(CancellationT } await FileWatcher.WaitForFileChangeAsync( - RootFileSetFactory.RootProjectFile, - Context.Reporter, - startedWatching: () => Context.Reporter.Report(MessageDescriptor.FixBuildError), + _fileSetFactory.RootProjectFile, + _context.Reporter, + startedWatching: () => _context.Reporter.Report(MessageDescriptor.FixBuildError), cancellationToken); } } @@ -778,7 +782,7 @@ await FileWatcher.WaitForFileChangeAsync( var processSpec = new ProcessSpec { - Executable = Context.EnvironmentOptions.MuxerPath, + Executable = _context.EnvironmentOptions.MuxerPath, WorkingDirectory = Path.GetDirectoryName(projectPath)!, OnOutput = line => { @@ -791,16 +795,16 @@ await FileWatcher.WaitForFileChangeAsync( Arguments = ["build", projectPath, "-consoleLoggerParameters:NoSummary;Verbosity=minimal", .. buildArguments] }; - Context.Reporter.Output($"Building {projectPath} ..."); + _context.Reporter.Output($"Building {projectPath} ..."); - var exitCode = await Context.ProcessRunner.RunAsync(processSpec, Context.Reporter, isUserApplication: false, launchResult: null, cancellationToken); + var exitCode = await _context.ProcessRunner.RunAsync(processSpec, _context.Reporter, isUserApplication: false, launchResult: null, cancellationToken); return (exitCode == 0, buildOutput.ToImmutableArray(), projectPath); } private string GetRelativeFilePath(string path) { var relativePath = path; - var workingDirectory = Context.EnvironmentOptions.WorkingDirectory; + var workingDirectory = _context.EnvironmentOptions.WorkingDirectory; if (path.StartsWith(workingDirectory, StringComparison.Ordinal) && path.Length > workingDirectory.Length) { relativePath = path.Substring(workingDirectory.Length); diff --git a/src/BuiltInTools/dotnet-watch/Internal/Ensure.cs b/src/BuiltInTools/dotnet-watch/Internal/Ensure.cs deleted file mode 100644 index 21ab1478ab41..000000000000 --- a/src/BuiltInTools/dotnet-watch/Internal/Ensure.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Watch -{ - internal static class Ensure - { - public static T NotNull(T? obj, string paramName) - where T : class - { - if (obj == null) - { - throw new ArgumentNullException(paramName); - } - return obj; - } - - public static string NotNullOrEmpty(string obj, string paramName) - { - if (string.IsNullOrEmpty(obj)) - { - throw new ArgumentException("Value cannot be null or an empty string.", paramName); - } - return obj; - } - } -} diff --git a/src/BuiltInTools/dotnet-watch/Internal/MsBuildProjectFinder.cs b/src/BuiltInTools/dotnet-watch/Internal/MsBuildProjectFinder.cs deleted file mode 100644 index eb7ba724fa5c..000000000000 --- a/src/BuiltInTools/dotnet-watch/Internal/MsBuildProjectFinder.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Globalization; - -namespace Microsoft.DotNet.Watch -{ - internal class MsBuildProjectFinder - { - /// - /// Finds a compatible MSBuild project. - /// The base directory to search - /// The filename of the project. Can be null. - /// - public static string FindMsBuildProject(string searchBase, string? project) - { - Ensure.NotNullOrEmpty(searchBase, nameof(searchBase)); - - var projectPath = project ?? searchBase; - - if (!Path.IsPathRooted(projectPath)) - { - projectPath = Path.Combine(searchBase, projectPath); - } - - if (Directory.Exists(projectPath)) - { - var projects = Directory.EnumerateFileSystemEntries(projectPath, "*.*proj", SearchOption.TopDirectoryOnly) - .Where(f => !".xproj".Equals(Path.GetExtension(f), StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (projects.Count > 1) - { - throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Resources.Error_MultipleProjectsFound, projectPath)); - } - - if (projects.Count == 0) - { - throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Resources.Error_NoProjectsFound, projectPath)); - } - - return projects[0]; - } - - if (!File.Exists(projectPath)) - { - throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Resources.Error_ProjectPath_NotFound, projectPath)); - } - - return projectPath; - } - } -} diff --git a/src/BuiltInTools/dotnet-watch/Internal/ReporterTraceListener.cs b/src/BuiltInTools/dotnet-watch/Internal/ReporterTraceListener.cs deleted file mode 100644 index f67f58af6708..000000000000 --- a/src/BuiltInTools/dotnet-watch/Internal/ReporterTraceListener.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace Microsoft.DotNet.Watch; - -internal class ReporterTraceListener(IReporter reporter, string emoji) : TraceListener -{ - // unused - public override void Write(string? message) - => WriteLine(message); - - public override void WriteLine(string? message) - { - if (message != null) - { - reporter.Verbose(message, emoji); - } - } -} diff --git a/src/BuiltInTools/dotnet-watch/HotReload/IRuntimeProcessLauncher.cs b/src/BuiltInTools/dotnet-watch/Process/IRuntimeProcessLauncher.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/IRuntimeProcessLauncher.cs rename to src/BuiltInTools/dotnet-watch/Process/IRuntimeProcessLauncher.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/IRuntimeProcessLauncherFactory.cs b/src/BuiltInTools/dotnet-watch/Process/IRuntimeProcessLauncherFactory.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/IRuntimeProcessLauncherFactory.cs rename to src/BuiltInTools/dotnet-watch/Process/IRuntimeProcessLauncherFactory.cs diff --git a/src/BuiltInTools/dotnet-watch/LaunchSettingsProfile.cs b/src/BuiltInTools/dotnet-watch/Process/LaunchSettingsProfile.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/LaunchSettingsProfile.cs rename to src/BuiltInTools/dotnet-watch/Process/LaunchSettingsProfile.cs diff --git a/src/BuiltInTools/dotnet-watch/ProcessLaunchResult.cs b/src/BuiltInTools/dotnet-watch/Process/ProcessLaunchResult.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/ProcessLaunchResult.cs rename to src/BuiltInTools/dotnet-watch/Process/ProcessLaunchResult.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs b/src/BuiltInTools/dotnet-watch/Process/ProcessRunner.cs similarity index 99% rename from src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs rename to src/BuiltInTools/dotnet-watch/Process/ProcessRunner.cs index 1ebebc89b789..bd0ff20a08b3 100644 --- a/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs +++ b/src/BuiltInTools/dotnet-watch/Process/ProcessRunner.cs @@ -25,8 +25,6 @@ private sealed class ProcessState /// True if the process is a user application, false if it is a helper process (e.g. msbuild). public async Task RunAsync(ProcessSpec processSpec, IReporter reporter, bool isUserApplication, ProcessLaunchResult? launchResult, CancellationToken processTerminationToken) { - Ensure.NotNull(processSpec, nameof(processSpec)); - var state = new ProcessState(); var stopwatch = new Stopwatch(); diff --git a/src/BuiltInTools/dotnet-watch/ProcessSpec.cs b/src/BuiltInTools/dotnet-watch/Process/ProcessSpec.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/ProcessSpec.cs rename to src/BuiltInTools/dotnet-watch/Process/ProcessSpec.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs b/src/BuiltInTools/dotnet-watch/Process/ProjectLauncher.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs rename to src/BuiltInTools/dotnet-watch/Process/ProjectLauncher.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/RunningProject.cs b/src/BuiltInTools/dotnet-watch/Process/RunningProject.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/RunningProject.cs rename to src/BuiltInTools/dotnet-watch/Process/RunningProject.cs diff --git a/src/BuiltInTools/dotnet-watch/Program.cs b/src/BuiltInTools/dotnet-watch/Program.cs index 299e622a4334..b8cf8abc79f7 100644 --- a/src/BuiltInTools/dotnet-watch/Program.cs +++ b/src/BuiltInTools/dotnet-watch/Program.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Runtime.Loader; using Microsoft.Build.Locator; @@ -76,14 +78,8 @@ public static async Task Main(string[] args) reporter.Verbose($"Test flags: {environmentOptions.TestFlags}"); } - string projectPath; - try - { - projectPath = MsBuildProjectFinder.FindMsBuildProject(workingDirectory, options.ProjectPath); - } - catch (FileNotFoundException ex) + if (!TryFindProject(workingDirectory, options, reporter, out var projectPath)) { - reporter.Error(ex.Message); errorCode = 1; return null; } @@ -93,6 +89,51 @@ public static async Task Main(string[] args) return new Program(console, reporter, rootProjectOptions, options, environmentOptions); } + /// + /// Finds a compatible MSBuild project. + /// The base directory to search + /// The filename of the project. Can be null. + /// + private static bool TryFindProject(string searchBase, CommandLineOptions options, IReporter reporter, [NotNullWhen(true)] out string? projectPath) + { + projectPath = options.ProjectPath ?? searchBase; + + if (!Path.IsPathRooted(projectPath)) + { + projectPath = Path.Combine(searchBase, projectPath); + } + + if (Directory.Exists(projectPath)) + { + var projects = Directory.EnumerateFileSystemEntries(projectPath, "*.*proj", SearchOption.TopDirectoryOnly) + .Where(f => !".xproj".Equals(Path.GetExtension(f), StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (projects.Count > 1) + { + reporter.Error(string.Format(CultureInfo.CurrentCulture, Resources.Error_MultipleProjectsFound, projectPath)); + return false; + } + + if (projects.Count == 0) + { + reporter.Error(string.Format(CultureInfo.CurrentCulture, Resources.Error_NoProjectsFound, projectPath)); + return false; + } + + projectPath = projects[0]; + return true; + } + + if (!File.Exists(projectPath)) + { + reporter.Error(string.Format(CultureInfo.CurrentCulture, Resources.Error_ProjectPath_NotFound, projectPath)); + return false; + } + + return true; + } + // internal for testing internal async Task RunAsync() { @@ -132,8 +173,23 @@ internal async Task RunAsync() return await ListFilesAsync(processRunner, shutdownCancellationToken); } - var watcher = CreateWatcher(processRunner, runtimeProcessLauncherFactory: null); - await watcher.WatchAsync(shutdownCancellationToken); + if (environmentOptions.IsPollingEnabled) + { + reporter.Output("Polling file watcher is enabled"); + } + + var context = CreateContext(processRunner); + + if (IsHotReoadEnabled()) + { + var watcher = new HotReloadDotNetWatcher(context, console, runtimeProcessLauncherFactory: null); + await watcher.WatchAsync(shutdownCancellationToken); + } + else + { + await DotNetWatcher.WatchAsync(context, shutdownCancellationToken); + } + return 0; } catch (OperationCanceledException) when (shutdownCancellationToken.IsCancellationRequested) @@ -155,49 +211,32 @@ internal async Task RunAsync() } // internal for testing - internal Watcher CreateWatcher(ProcessRunner processRunner, IRuntimeProcessLauncherFactory? runtimeProcessLauncherFactory) - { - if (environmentOptions.IsPollingEnabled) + internal DotNetWatchContext CreateContext(ProcessRunner processRunner) + => new() { - reporter.Output("Polling file watcher is enabled"); - } - - var fileSetFactory = new MSBuildFileSetFactory( - rootProjectOptions.ProjectPath, - rootProjectOptions.BuildArguments, - environmentOptions, - processRunner, - reporter); + Reporter = reporter, + ProcessRunner = processRunner, + Options = options.GlobalOptions, + EnvironmentOptions = environmentOptions, + RootProjectOptions = rootProjectOptions, + }; - bool enableHotReload; + private bool IsHotReoadEnabled() + { if (rootProjectOptions.Command != "run") { reporter.Verbose($"Command '{rootProjectOptions.Command}' does not support Hot Reload."); - enableHotReload = false; + return false; } - else if (options.GlobalOptions.NoHotReload) + + if (options.GlobalOptions.NoHotReload) { reporter.Verbose("Hot Reload disabled by command line switch."); - enableHotReload = false; + return false; } - else - { - reporter.Report(MessageDescriptor.WatchingWithHotReload); - enableHotReload = true; - } - - var context = new DotNetWatchContext - { - Reporter = reporter, - ProcessRunner = processRunner, - Options = options.GlobalOptions, - EnvironmentOptions = environmentOptions, - RootProjectOptions = rootProjectOptions, - }; - return enableHotReload - ? new HotReloadDotNetWatcher(context, console, fileSetFactory, runtimeProcessLauncherFactory) - : new DotNetWatcher(context, fileSetFactory); + reporter.Report(MessageDescriptor.WatchingWithHotReload); + return true; } private async Task ListFilesAsync(ProcessRunner processRunner, CancellationToken cancellationToken) diff --git a/src/BuiltInTools/dotnet-watch/Utilities/BuildUtilities.cs b/src/BuiltInTools/dotnet-watch/UI/BuildOutput.cs similarity index 97% rename from src/BuiltInTools/dotnet-watch/Utilities/BuildUtilities.cs rename to src/BuiltInTools/dotnet-watch/UI/BuildOutput.cs index 291402fc337a..34fa4bed9964 100644 --- a/src/BuiltInTools/dotnet-watch/Utilities/BuildUtilities.cs +++ b/src/BuiltInTools/dotnet-watch/UI/BuildOutput.cs @@ -5,7 +5,7 @@ namespace Microsoft.DotNet.Watch; -internal static partial class BuildUtilities +internal static partial class BuildOutput { private const string BuildEmoji = "🔨"; private static readonly Regex s_buildDiagnosticRegex = GetBuildDiagnosticRegex(); diff --git a/src/BuiltInTools/dotnet-watch/Internal/ConsoleInputReader.cs b/src/BuiltInTools/dotnet-watch/UI/ConsoleInputReader.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/ConsoleInputReader.cs rename to src/BuiltInTools/dotnet-watch/UI/ConsoleInputReader.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/ConsoleReporter.cs b/src/BuiltInTools/dotnet-watch/UI/ConsoleReporter.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/ConsoleReporter.cs rename to src/BuiltInTools/dotnet-watch/UI/ConsoleReporter.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/IConsole.cs b/src/BuiltInTools/dotnet-watch/UI/IConsole.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/IConsole.cs rename to src/BuiltInTools/dotnet-watch/UI/IConsole.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/IReporter.cs b/src/BuiltInTools/dotnet-watch/UI/IReporter.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/IReporter.cs rename to src/BuiltInTools/dotnet-watch/UI/IReporter.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/NullReporter.cs b/src/BuiltInTools/dotnet-watch/UI/NullReporter.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/NullReporter.cs rename to src/BuiltInTools/dotnet-watch/UI/NullReporter.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/OutputLine.cs b/src/BuiltInTools/dotnet-watch/UI/OutputLine.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/OutputLine.cs rename to src/BuiltInTools/dotnet-watch/UI/OutputLine.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/PhysicalConsole.cs b/src/BuiltInTools/dotnet-watch/UI/PhysicalConsole.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/PhysicalConsole.cs rename to src/BuiltInTools/dotnet-watch/UI/PhysicalConsole.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/ProjectSpecificReporter.cs b/src/BuiltInTools/dotnet-watch/UI/ProjectSpecificReporter.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/ProjectSpecificReporter.cs rename to src/BuiltInTools/dotnet-watch/UI/ProjectSpecificReporter.cs diff --git a/src/BuiltInTools/dotnet-watch/HotReload/RestartPrompt.cs b/src/BuiltInTools/dotnet-watch/UI/RestartPrompt.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/HotReload/RestartPrompt.cs rename to src/BuiltInTools/dotnet-watch/UI/RestartPrompt.cs diff --git a/src/BuiltInTools/dotnet-watch/Internal/CommandLineUtilities.cs b/src/BuiltInTools/dotnet-watch/Utilities/CommandLineUtilities.cs similarity index 100% rename from src/BuiltInTools/dotnet-watch/Internal/CommandLineUtilities.cs rename to src/BuiltInTools/dotnet-watch/Utilities/CommandLineUtilities.cs diff --git a/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs b/src/BuiltInTools/dotnet-watch/Watch/BuildEvaluator.cs similarity index 73% rename from src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs rename to src/BuiltInTools/dotnet-watch/Watch/BuildEvaluator.cs index ae32359c539f..5a74aa5b032e 100644 --- a/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs +++ b/src/BuiltInTools/dotnet-watch/Watch/BuildEvaluator.cs @@ -6,7 +6,7 @@ namespace Microsoft.DotNet.Watch { - internal class BuildEvaluator(DotNetWatchContext context, MSBuildFileSetFactory rootProjectFileSetFactory) + internal class BuildEvaluator { // File types that require an MSBuild re-evaluation private static readonly string[] s_msBuildFileExtensions = new[] @@ -18,6 +18,9 @@ internal class BuildEvaluator(DotNetWatchContext context, MSBuildFileSetFactory .Select(e => e.GetHashCode(StringComparison.OrdinalIgnoreCase)) .ToArray(); + private readonly MSBuildFileSetFactory _fileSetFactory; + private readonly DotNetWatchContext _context; + private List<(string fileName, DateTime lastWriteTimeUtc)>? _msbuildFileTimestamps; // result of the last evaluation, or null if no evaluation has been performed yet. @@ -25,29 +28,38 @@ internal class BuildEvaluator(DotNetWatchContext context, MSBuildFileSetFactory public bool RequiresRevaluation { get; set; } + public BuildEvaluator(DotNetWatchContext context) + { + _context = context; + _fileSetFactory = CreateMSBuildFileSetFactory(); + } + + protected virtual MSBuildFileSetFactory CreateMSBuildFileSetFactory() + => _context.CreateMSBuildFileSetFactory(); + public IReadOnlyList GetProcessArguments(int iteration) { - if (!context.EnvironmentOptions.SuppressMSBuildIncrementalism && + if (!_context.EnvironmentOptions.SuppressMSBuildIncrementalism && iteration > 0 && - CommandLineOptions.IsCodeExecutionCommand(context.RootProjectOptions.Command)) + CommandLineOptions.IsCodeExecutionCommand(_context.RootProjectOptions.Command)) { if (RequiresRevaluation) { - context.Reporter.Verbose("Cannot use --no-restore since msbuild project files have changed."); + _context.Reporter.Verbose("Cannot use --no-restore since msbuild project files have changed."); } else { - context.Reporter.Verbose("Modifying command to use --no-restore"); - return [context.RootProjectOptions.Command, "--no-restore", .. context.RootProjectOptions.CommandArguments]; + _context.Reporter.Verbose("Modifying command to use --no-restore"); + return [_context.RootProjectOptions.Command, "--no-restore", .. _context.RootProjectOptions.CommandArguments]; } } - return [context.RootProjectOptions.Command, .. context.RootProjectOptions.CommandArguments]; + return [_context.RootProjectOptions.Command, .. _context.RootProjectOptions.CommandArguments]; } public async ValueTask EvaluateAsync(ChangedFile? changedFile, CancellationToken cancellationToken) { - if (context.EnvironmentOptions.SuppressMSBuildIncrementalism) + if (_context.EnvironmentOptions.SuppressMSBuildIncrementalism) { RequiresRevaluation = true; return _evaluationResult = await CreateEvaluationResult(cancellationToken); @@ -60,7 +72,7 @@ public async ValueTask EvaluateAsync(ChangedFile? changedFile, if (RequiresRevaluation) { - context.Reporter.Verbose("Evaluating dotnet-watch file set."); + _context.Reporter.Verbose("Evaluating dotnet-watch file set."); var result = await CreateEvaluationResult(cancellationToken); _msbuildFileTimestamps = GetMSBuildFileTimeStamps(result); @@ -77,16 +89,16 @@ private async ValueTask CreateEvaluationResult(CancellationTok { cancellationToken.ThrowIfCancellationRequested(); - var result = await rootProjectFileSetFactory.TryCreateAsync(requireProjectGraph: true, cancellationToken); + var result = await _fileSetFactory.TryCreateAsync(requireProjectGraph: true, cancellationToken); if (result != null) { return result; } await FileWatcher.WaitForFileChangeAsync( - rootProjectFileSetFactory.RootProjectFile, - context.Reporter, - startedWatching: () => context.Reporter.Report(MessageDescriptor.FixBuildError), + _fileSetFactory.RootProjectFile, + _context.Reporter, + startedWatching: () => _context.Reporter.Report(MessageDescriptor.FixBuildError), cancellationToken); } } @@ -110,7 +122,7 @@ private bool RequiresMSBuildRevaluation(FileItem? changedFile) { if (GetLastWriteTimeUtcSafely(file) != lastWriteTimeUtc) { - context.Reporter.Verbose($"Re-evaluation needed due to changes in {file}."); + _context.Reporter.Verbose($"Re-evaluation needed due to changes in {file}."); return true; } @@ -133,7 +145,7 @@ private bool RequiresMSBuildRevaluation(FileItem? changedFile) return msbuildFiles; } - private protected virtual DateTime GetLastWriteTimeUtcSafely(string file) + protected virtual DateTime GetLastWriteTimeUtcSafely(string file) { try { @@ -145,7 +157,7 @@ private protected virtual DateTime GetLastWriteTimeUtcSafely(string file) } } - static bool IsMsBuildFileExtension(string fileName) + private static bool IsMsBuildFileExtension(string fileName) { var extension = Path.GetExtension(fileName.AsSpan()); var hashCode = string.GetHashCode(extension, StringComparison.OrdinalIgnoreCase); diff --git a/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/Watch/DotNetWatcher.cs similarity index 81% rename from src/BuiltInTools/dotnet-watch/DotNetWatcher.cs rename to src/BuiltInTools/dotnet-watch/Watch/DotNetWatcher.cs index 36714a4c200f..b7901c81db05 100644 --- a/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/Watch/DotNetWatcher.cs @@ -7,30 +7,30 @@ namespace Microsoft.DotNet.Watch { - internal sealed class DotNetWatcher(DotNetWatchContext context, MSBuildFileSetFactory fileSetFactory) : Watcher(context, fileSetFactory) + internal static class DotNetWatcher { - public override async Task WatchAsync(CancellationToken shutdownCancellationToken) + public static async Task WatchAsync(DotNetWatchContext context, CancellationToken shutdownCancellationToken) { var cancelledTaskSource = new TaskCompletionSource(); shutdownCancellationToken.Register(state => ((TaskCompletionSource)state!).TrySetResult(), cancelledTaskSource); - if (Context.EnvironmentOptions.SuppressMSBuildIncrementalism) + if (context.EnvironmentOptions.SuppressMSBuildIncrementalism) { - Context.Reporter.Verbose("MSBuild incremental optimizations suppressed."); + context.Reporter.Verbose("MSBuild incremental optimizations suppressed."); } var environmentBuilder = EnvironmentVariablesBuilder.FromCurrentEnvironment(); ChangedFile? changedFile = null; - var buildEvaluator = new BuildEvaluator(Context, RootFileSetFactory); - await using var browserConnector = new BrowserConnector(Context); + var buildEvaluator = new BuildEvaluator(context); + await using var browserConnector = new BrowserConnector(context); for (var iteration = 0;;iteration++) { if (await buildEvaluator.EvaluateAsync(changedFile, shutdownCancellationToken) is not { } evaluationResult) { - Context.Reporter.Error("Failed to find a list of files to watch"); + context.Reporter.Error("Failed to find a list of files to watch"); return; } @@ -39,20 +39,20 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke if (evaluationResult.ProjectGraph != null) { projectRootNode = evaluationResult.ProjectGraph.GraphRoots.Single(); - var projectMap = new ProjectNodeMap(evaluationResult.ProjectGraph, Context.Reporter); - staticFileHandler = new StaticFileHandler(Context.Reporter, projectMap, browserConnector); + var projectMap = new ProjectNodeMap(evaluationResult.ProjectGraph, context.Reporter); + staticFileHandler = new StaticFileHandler(context.Reporter, projectMap, browserConnector); } else { - Context.Reporter.Verbose("Unable to determine if this project is a webapp."); + context.Reporter.Verbose("Unable to determine if this project is a webapp."); projectRootNode = null; staticFileHandler = null; } var processSpec = new ProcessSpec { - Executable = Context.EnvironmentOptions.MuxerPath, - WorkingDirectory = Context.EnvironmentOptions.WorkingDirectory, + Executable = context.EnvironmentOptions.MuxerPath, + WorkingDirectory = context.EnvironmentOptions.WorkingDirectory, Arguments = buildEvaluator.GetProcessArguments(iteration), EnvironmentVariables = { @@ -62,7 +62,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke }; var browserRefreshServer = (projectRootNode != null) - ? await browserConnector.GetOrCreateBrowserRefreshServerAsync(projectRootNode, processSpec, environmentBuilder, Context.RootProjectOptions, DefaultAppModel.Instance, shutdownCancellationToken) + ? await browserConnector.GetOrCreateBrowserRefreshServerAsync(projectRootNode, processSpec, environmentBuilder, context.RootProjectOptions, DefaultAppModel.Instance, shutdownCancellationToken) : null; environmentBuilder.SetProcessEnvironmentVariables(processSpec); @@ -77,11 +77,11 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke using var currentRunCancellationSource = new CancellationTokenSource(); using var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellationToken, currentRunCancellationSource.Token); - using var fileSetWatcher = new FileWatcher(Context.Reporter); + using var fileSetWatcher = new FileWatcher(context.Reporter); fileSetWatcher.WatchContainingDirectories(evaluationResult.Files.Keys, includeSubdirectories: true); - var processTask = Context.ProcessRunner.RunAsync(processSpec, Context.Reporter, isUserApplication: true, launchResult: null, combinedCancellationSource.Token); + var processTask = context.ProcessRunner.RunAsync(processSpec, context.Reporter, isUserApplication: true, launchResult: null, combinedCancellationSource.Token); Task fileSetTask; Task finishedTask; @@ -122,7 +122,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke // Now wait for a file to change before restarting process changedFile = await fileSetWatcher.WaitForFileChangeAsync( evaluationResult.Files, - startedWatching: () => Context.Reporter.Report(MessageDescriptor.WaitingForFileChangeBeforeRestarting), + startedWatching: () => context.Reporter.Report(MessageDescriptor.WaitingForFileChangeBeforeRestarting), shutdownCancellationToken); } else @@ -130,7 +130,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke Debug.Assert(finishedTask == fileSetTask); changedFile = fileSetTask.Result; Debug.Assert(changedFile != null, "ChangedFile should only be null when cancelled"); - Context.Reporter.Output($"File changed: {changedFile.Value.Item.FilePath}"); + context.Reporter.Output($"File changed: {changedFile.Value.Item.FilePath}"); } } } diff --git a/src/BuiltInTools/dotnet-watch/Watcher.cs b/src/BuiltInTools/dotnet-watch/Watcher.cs deleted file mode 100644 index 5a6f3240b20b..000000000000 --- a/src/BuiltInTools/dotnet-watch/Watcher.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - - -namespace Microsoft.DotNet.Watch -{ - internal abstract class Watcher(DotNetWatchContext context, MSBuildFileSetFactory rootFileSetFactory) - { - public DotNetWatchContext Context => context; - public MSBuildFileSetFactory RootFileSetFactory => rootFileSetFactory; - - public abstract Task WatchAsync(CancellationToken shutdownCancellationToken); - } -} diff --git a/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj b/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj index a9d3d3781a64..a7cdfc1169d1 100644 --- a/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj +++ b/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj @@ -29,7 +29,7 @@ - + @@ -68,17 +68,10 @@ Keep excluded files in sync with the list in GenerateLayout.targets. --> - <_DotnetWatchInputFile Include="$(TargetDir)**" - Condition="('%(Filename)' != 'Microsoft.CodeAnalysis' and - '%(Filename)' != 'Microsoft.CodeAnalysis.resources' and - '%(Filename)' != 'Microsoft.CodeAnalysis.CSharp' and - '%(Filename)' != 'Microsoft.CodeAnalysis.CSharp.resources') or - $([MSBuild]::ValueOrDefault('%(FullPath)', '').Contains('BuildHost'))" /> + <_DotnetWatchInputFile Include="$(TargetDir)**" Condition="('%(Filename)' != 'Microsoft.CodeAnalysis' and '%(Filename)' != 'Microsoft.CodeAnalysis.resources' and '%(Filename)' != 'Microsoft.CodeAnalysis.CSharp' and '%(Filename)' != 'Microsoft.CodeAnalysis.CSharp.resources') or $([MSBuild]::ValueOrDefault('%(FullPath)', '').Contains('BuildHost'))" /> - + diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj index 1d8dddb75f81..9d8892a21d53 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/dotnet-watch.Tests/Watch/BrowserLaunchTests.cs b/test/dotnet-watch.Tests/Browser/BrowserLaunchTests.cs similarity index 100% rename from test/dotnet-watch.Tests/Watch/BrowserLaunchTests.cs rename to test/dotnet-watch.Tests/Browser/BrowserLaunchTests.cs diff --git a/test/dotnet-watch.Tests/FileSetSerializerTests.cs b/test/dotnet-watch.Tests/Build/FileSetSerializerTests.cs similarity index 100% rename from test/dotnet-watch.Tests/FileSetSerializerTests.cs rename to test/dotnet-watch.Tests/Build/FileSetSerializerTests.cs diff --git a/test/dotnet-watch.Tests/MsBuildFileSetFactoryTest.cs b/test/dotnet-watch.Tests/Build/MsBuildFileSetFactoryTest.cs similarity index 100% rename from test/dotnet-watch.Tests/MsBuildFileSetFactoryTest.cs rename to test/dotnet-watch.Tests/Build/MsBuildFileSetFactoryTest.cs diff --git a/test/dotnet-watch.Tests/CommandLineOptionsTests.cs b/test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs similarity index 100% rename from test/dotnet-watch.Tests/CommandLineOptionsTests.cs rename to test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs diff --git a/test/dotnet-watch.Tests/Internal/EnvironmentVariablesBuilderTests.cs b/test/dotnet-watch.Tests/CommandLine/EnvironmentVariablesBuilderTests.cs similarity index 100% rename from test/dotnet-watch.Tests/Internal/EnvironmentVariablesBuilderTests.cs rename to test/dotnet-watch.Tests/CommandLine/EnvironmentVariablesBuilderTests.cs diff --git a/test/dotnet-watch.Tests/Watch/DotNetWatcherTests.cs b/test/dotnet-watch.Tests/CommandLine/LaunchSettingsTests.cs similarity index 100% rename from test/dotnet-watch.Tests/Watch/DotNetWatcherTests.cs rename to test/dotnet-watch.Tests/CommandLine/LaunchSettingsTests.cs diff --git a/test/dotnet-watch.Tests/Watch/ProgramTests.cs b/test/dotnet-watch.Tests/CommandLine/ProgramTests.cs similarity index 95% rename from test/dotnet-watch.Tests/Watch/ProgramTests.cs rename to test/dotnet-watch.Tests/CommandLine/ProgramTests.cs index f6004bb53143..8d36b33c2093 100644 --- a/test/dotnet-watch.Tests/Watch/ProgramTests.cs +++ b/test/dotnet-watch.Tests/CommandLine/ProgramTests.cs @@ -342,5 +342,27 @@ public async Task ProjectGraphLoadFailure() App.AssertOutputContains(@"dotnet watch ⌚ Failed to load project graph."); App.AssertOutputContains($"dotnet watch ❌ The project file could not be loaded. Could not find a part of the path '{Path.Combine(testAsset.Path, "AppWithDeps", "NonExistentDirectory", "X.csproj")}'"); } + + [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + public async Task ListsFiles() + { + var testAsset = TestAssets.CopyTestAsset("WatchGlobbingApp") + .WithSource(); + + App.DotnetWatchArgs.Clear(); + App.Start(testAsset, ["--list"]); + var lines = await App.Process.GetAllOutputLinesAsync(CancellationToken.None); + var files = lines.Where(l => !l.StartsWith("dotnet watch ⌚") && l.Trim() != ""); + + AssertEx.EqualFileList( + testAsset.Path, + new[] + { + "Program.cs", + "include/Foo.cs", + "WatchGlobbingApp.csproj", + }, + files); + } } } diff --git a/test/dotnet-watch.Tests/FileWatcherTests.cs b/test/dotnet-watch.Tests/FileWatcher/FileWatcherTests.cs similarity index 100% rename from test/dotnet-watch.Tests/FileWatcherTests.cs rename to test/dotnet-watch.Tests/FileWatcher/FileWatcherTests.cs diff --git a/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs b/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs index 9a51ac843e74..db768163c52c 100644 --- a/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs +++ b/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs @@ -113,7 +113,7 @@ private RunningWatcher StartWatcher(TestAsset testAsset, string[] args, string w serviceHolder.Value = s; }); - var watcher = Assert.IsType(program.CreateWatcher(processRunner, factory)); + var watcher = new HotReloadDotNetWatcher(program.CreateContext(processRunner), console, runtimeProcessLauncherFactory: factory); var shutdownSource = new CancellationTokenSource(); var watchTask = Task.Run(async () => diff --git a/test/dotnet-watch.Tests/LaunchSettingsProfileTest.cs b/test/dotnet-watch.Tests/Process/LaunchSettingsProfileTest.cs similarity index 100% rename from test/dotnet-watch.Tests/LaunchSettingsProfileTest.cs rename to test/dotnet-watch.Tests/Process/LaunchSettingsProfileTest.cs diff --git a/test/dotnet-watch.Tests/Utilities/AssertEx.cs b/test/dotnet-watch.Tests/TestUtilities/AssertEx.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/AssertEx.cs rename to test/dotnet-watch.Tests/TestUtilities/AssertEx.cs diff --git a/test/dotnet-watch.Tests/Utilities/AwaitableProcess.cs b/test/dotnet-watch.Tests/TestUtilities/AwaitableProcess.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/AwaitableProcess.cs rename to test/dotnet-watch.Tests/TestUtilities/AwaitableProcess.cs diff --git a/test/dotnet-watch.Tests/Utilities/DebugTestOutputLogger.cs b/test/dotnet-watch.Tests/TestUtilities/DebugTestOutputLogger.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/DebugTestOutputLogger.cs rename to test/dotnet-watch.Tests/TestUtilities/DebugTestOutputLogger.cs diff --git a/test/dotnet-watch.Tests/Watch/Utilities/DotNetWatchTestBase.cs b/test/dotnet-watch.Tests/TestUtilities/DotNetWatchTestBase.cs similarity index 100% rename from test/dotnet-watch.Tests/Watch/Utilities/DotNetWatchTestBase.cs rename to test/dotnet-watch.Tests/TestUtilities/DotNetWatchTestBase.cs diff --git a/test/dotnet-watch.Tests/Utilities/MockFileSetFactory.cs b/test/dotnet-watch.Tests/TestUtilities/MockFileSetFactory.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/MockFileSetFactory.cs rename to test/dotnet-watch.Tests/TestUtilities/MockFileSetFactory.cs diff --git a/test/dotnet-watch.Tests/Utilities/MockReporter.cs b/test/dotnet-watch.Tests/TestUtilities/MockReporter.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/MockReporter.cs rename to test/dotnet-watch.Tests/TestUtilities/MockReporter.cs diff --git a/test/dotnet-watch.Tests/Utilities/TaskExtensions.cs b/test/dotnet-watch.Tests/TestUtilities/TaskExtensions.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/TaskExtensions.cs rename to test/dotnet-watch.Tests/TestUtilities/TaskExtensions.cs diff --git a/test/dotnet-watch.Tests/TestUtilities/TestBuildEvaluator.cs b/test/dotnet-watch.Tests/TestUtilities/TestBuildEvaluator.cs new file mode 100644 index 000000000000..0824f643d55c --- /dev/null +++ b/test/dotnet-watch.Tests/TestUtilities/TestBuildEvaluator.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.DotNet.Watch.UnitTests; + +internal class TestBuildEvaluator(DotNetWatchContext context, MSBuildFileSetFactory factory) + : BuildEvaluator(context) +{ + public Dictionary Timestamps { get; } = []; + + protected override DateTime GetLastWriteTimeUtcSafely(string file) => Timestamps[file]; + protected override MSBuildFileSetFactory CreateMSBuildFileSetFactory() => factory; +} diff --git a/test/dotnet-watch.Tests/Utilities/TestConsole.cs b/test/dotnet-watch.Tests/TestUtilities/TestConsole.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/TestConsole.cs rename to test/dotnet-watch.Tests/TestUtilities/TestConsole.cs diff --git a/test/dotnet-watch.Tests/Utilities/TestOptions.cs b/test/dotnet-watch.Tests/TestUtilities/TestOptions.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/TestOptions.cs rename to test/dotnet-watch.Tests/TestUtilities/TestOptions.cs diff --git a/test/dotnet-watch.Tests/Utilities/TestReporter.cs b/test/dotnet-watch.Tests/TestUtilities/TestReporter.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/TestReporter.cs rename to test/dotnet-watch.Tests/TestUtilities/TestReporter.cs diff --git a/test/dotnet-watch.Tests/Utilities/TestRuntimeProcessLauncher.cs b/test/dotnet-watch.Tests/TestUtilities/TestRuntimeProcessLauncher.cs similarity index 100% rename from test/dotnet-watch.Tests/Utilities/TestRuntimeProcessLauncher.cs rename to test/dotnet-watch.Tests/TestUtilities/TestRuntimeProcessLauncher.cs diff --git a/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs b/test/dotnet-watch.Tests/TestUtilities/WatchableApp.cs similarity index 100% rename from test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs rename to test/dotnet-watch.Tests/TestUtilities/WatchableApp.cs diff --git a/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs b/test/dotnet-watch.Tests/Watch/BuildEvaluatorTests.cs similarity index 87% rename from test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs rename to test/dotnet-watch.Tests/Watch/BuildEvaluatorTests.cs index 436ece5d4397..f71b5a161ad1 100644 --- a/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs +++ b/test/dotnet-watch.Tests/Watch/BuildEvaluatorTests.cs @@ -3,7 +3,7 @@ namespace Microsoft.DotNet.Watch.UnitTests { - public class MSBuildEvaluationFilterTest + public partial class BuildEvaluatorTests { private static readonly EvaluationResult s_emptyEvaluationResult = new(new Dictionary(), projectGraph: null); @@ -30,7 +30,7 @@ public async Task ProcessAsync_EvaluatesFileSetIfProjFileChanges() var context = CreateContext(); var fileSetFactory = new MockFileSetFactory() { TryCreateImpl = () => s_emptyEvaluationResult }; - var evaluator = new BuildEvaluator(context, fileSetFactory); + var evaluator = new TestBuildEvaluator(context, fileSetFactory); await evaluator.EvaluateAsync(changedFile: null, CancellationToken.None); @@ -48,7 +48,7 @@ public async Task ProcessAsync_DoesNotEvaluateFileSetIfNonProjFileChanges() var counter = 0; var fileSetFactory = new MockFileSetFactory() { TryCreateImpl = () => { counter++; return s_emptyEvaluationResult; } }; - var evaluator = new BuildEvaluator(context, fileSetFactory); + var evaluator = new TestBuildEvaluator(context, fileSetFactory); await evaluator.EvaluateAsync(changedFile: null, CancellationToken.None); @@ -68,7 +68,7 @@ public async Task ProcessAsync_EvaluateFileSetOnEveryChangeIfOptimizationIsSuppr var counter = 0; var fileSetFactory = new MockFileSetFactory() { TryCreateImpl = () => { counter++; return s_emptyEvaluationResult; } }; - var evaluator = new BuildEvaluator(context, fileSetFactory); + var evaluator = new TestBuildEvaluator(context, fileSetFactory); await evaluator.EvaluateAsync(changedFile: null, CancellationToken.None); @@ -98,7 +98,7 @@ public async Task ProcessAsync_SetsEvaluationRequired_IfMSBuildFileChanges_ButIs var context = CreateContext(); - var evaluator = new TestableBuildEvaluator(context, fileSetFactory) + var evaluator = new TestBuildEvaluator(context, fileSetFactory) { Timestamps = { @@ -115,12 +115,5 @@ public async Task ProcessAsync_SetsEvaluationRequired_IfMSBuildFileChanges_ButIs Assert.True(evaluator.RequiresRevaluation); } - - private class TestableBuildEvaluator(DotNetWatchContext context, MSBuildFileSetFactory factory) - : BuildEvaluator(context, factory) - { - public Dictionary Timestamps { get; } = []; - private protected override DateTime GetLastWriteTimeUtcSafely(string file) => Timestamps[file]; - } } } diff --git a/test/dotnet-watch.Tests/Watch/GlobbingAppTests.cs b/test/dotnet-watch.Tests/Watch/GlobbingAppTests.cs index c42007d31818..381e1ead7f69 100644 --- a/test/dotnet-watch.Tests/Watch/GlobbingAppTests.cs +++ b/test/dotnet-watch.Tests/Watch/GlobbingAppTests.cs @@ -103,28 +103,6 @@ public async Task ChangeExcludedFile() Assert.NotSame(fileChanged, finished); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") - public async Task ListsFiles() - { - var testAsset = TestAssets.CopyTestAsset(AppName) - .WithSource(); - - App.DotnetWatchArgs.Clear(); - App.Start(testAsset, ["--list"]); - var lines = await App.Process.GetAllOutputLinesAsync(CancellationToken.None); - var files = lines.Where(l => !l.StartsWith("dotnet watch ⌚") && l.Trim() != ""); - - AssertEx.EqualFileList( - testAsset.Path, - new[] - { - "Program.cs", - "include/Foo.cs", - "WatchGlobbingApp.csproj", - }, - files); - } - private async Task AssertCompiledAppDefinedTypes(int expected) { var prefix = "Defined types = "; diff --git a/test/dotnet-watch.Tests/NoRestoreTests.cs b/test/dotnet-watch.Tests/Watch/NoRestoreTests.cs similarity index 87% rename from test/dotnet-watch.Tests/NoRestoreTests.cs rename to test/dotnet-watch.Tests/Watch/NoRestoreTests.cs index b5ee7134e0c6..a837b2022fec 100644 --- a/test/dotnet-watch.Tests/NoRestoreTests.cs +++ b/test/dotnet-watch.Tests/Watch/NoRestoreTests.cs @@ -27,7 +27,7 @@ private static DotNetWatchContext CreateContext(string[] args = null, Environmen public void LeavesArgumentsUnchangedOnFirstRun() { var context = CreateContext(); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["run", InteractiveFlag], evaluator.GetProcessArguments(iteration: 0)); } @@ -36,7 +36,7 @@ public void LeavesArgumentsUnchangedOnFirstRun() public void LeavesArgumentsUnchangedIfMsBuildRevaluationIsRequired() { var context = CreateContext(); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["run", InteractiveFlag], evaluator.GetProcessArguments(iteration: 0)); @@ -49,7 +49,7 @@ public void LeavesArgumentsUnchangedIfMsBuildRevaluationIsRequired() public void LeavesArgumentsUnchangedIfOptimizationIsSuppressed() { var context = CreateContext([], TestOptions.GetEnvironmentOptions() with { SuppressMSBuildIncrementalism = true }); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["run", InteractiveFlag], evaluator.GetProcessArguments(iteration: 0)); AssertEx.SequenceEqual(["run", InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); @@ -59,7 +59,7 @@ public void LeavesArgumentsUnchangedIfOptimizationIsSuppressed() public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent() { var context = CreateContext(["--no-restore"], TestOptions.GetEnvironmentOptions() with { SuppressMSBuildIncrementalism = true }); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["run", "--no-restore", InteractiveFlag], evaluator.GetProcessArguments(iteration: 0)); AssertEx.SequenceEqual(["run", "--no-restore", InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); @@ -69,7 +69,7 @@ public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent() public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent_UnlessAfterDashDash1() { var context = CreateContext(["--", "--no-restore"]); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["run", InteractiveFlag, "--", "--no-restore"], evaluator.GetProcessArguments(iteration: 0)); AssertEx.SequenceEqual(["run", "--no-restore", InteractiveFlag, "--", "--no-restore"], evaluator.GetProcessArguments(iteration: 1)); @@ -79,7 +79,7 @@ public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent_UnlessAfterDashDas public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent_UnlessAfterDashDash2() { var context = CreateContext(["--", "--", "--no-restore"]); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["run", InteractiveFlag, "--", "--", "--no-restore"], evaluator.GetProcessArguments(iteration: 0)); AssertEx.SequenceEqual(["run", "--no-restore", InteractiveFlag, "--", "--", "--no-restore"], evaluator.GetProcessArguments(iteration: 1)); @@ -89,7 +89,7 @@ public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent_UnlessAfterDashDas public void AddsNoRestoreSwitch() { var context = CreateContext(); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["run", InteractiveFlag], evaluator.GetProcessArguments(iteration: 0)); AssertEx.SequenceEqual(["run", "--no-restore", InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); @@ -99,7 +99,7 @@ public void AddsNoRestoreSwitch() public void AddsNoRestoreSwitch_WithAdditionalArguments() { var context = CreateContext(["run", "-f", ToolsetInfo.CurrentTargetFramework]); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["run", "-f", ToolsetInfo.CurrentTargetFramework, InteractiveFlag], evaluator.GetProcessArguments(iteration: 0)); AssertEx.SequenceEqual(["run", "--no-restore", "-f", ToolsetInfo.CurrentTargetFramework, InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); @@ -109,7 +109,7 @@ public void AddsNoRestoreSwitch_WithAdditionalArguments() public void AddsNoRestoreSwitch_ForTestCommand() { var context = CreateContext(["test", "--filter SomeFilter"]); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["test", InteractiveFlag, "--filter SomeFilter"], evaluator.GetProcessArguments(iteration: 0)); AssertEx.SequenceEqual(["test", "--no-restore", InteractiveFlag, "--filter SomeFilter"], evaluator.GetProcessArguments(iteration: 1)); @@ -119,7 +119,7 @@ public void AddsNoRestoreSwitch_ForTestCommand() public void DoesNotModifyArgumentsForUnknownCommands() { var context = CreateContext(["pack"]); - var evaluator = new BuildEvaluator(context, new MockFileSetFactory()); + var evaluator = new BuildEvaluator(context); AssertEx.SequenceEqual(["pack", InteractiveFlag], evaluator.GetProcessArguments(iteration: 0)); AssertEx.SequenceEqual(["pack", InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); diff --git a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj index 774159200782..0bd6266b9abf 100644 --- a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj +++ b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj @@ -6,8 +6,8 @@ Microsoft.DotNet.Watcher.Tools - - + +