コード例 #1
0
        public async Task WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken)
        {
            var processSpec = context.ProcessSpec;

            if (context.SuppressMSBuildIncrementalism)
            {
                _reporter.Verbose("MSBuild incremental optimizations suppressed.");
            }

            _reporter.Output("Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload. " +
                             "Press \"Ctrl + R\" to restart.");

            while (true)
            {
                context.Iteration++;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                // Reset for next run
                context.RequiresMSBuildRevaluation = false;

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (!fileSet.Project.IsNetCoreApp60OrNewer())
                {
                    _reporter.Error($"Hot reload based watching is only supported in .NET 6.0 or newer apps. Update the project's launchSettings.json to disable this feature.");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                if (context.Iteration == 0)
                {
                    ConfigureExecutable(context, processSpec);
                }

                using var currentRunCancellationSource = new CancellationTokenSource();
                var forceReload = _console.ListenForForceReloadRequest();
                using var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
                          cancellationToken,
                          currentRunCancellationSource.Token,
                          forceReload);
                using var fileSetWatcher = new FileSetWatcher(fileSet, _reporter);
                try
                {
                    using var hotReload = new HotReload(_processRunner, _reporter);
                    await hotReload.InitializeAsync(context, cancellationToken);

                    var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
                    var args        = string.Join(" ", processSpec.Arguments);
                    _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                    _reporter.Output("Started");

                    Task <FileItem?> fileSetTask;
                    Task             finishedTask;

                    while (true)
                    {
                        fileSetTask  = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                        finishedTask = await Task.WhenAny(processTask, fileSetTask).WaitAsync(combinedCancellationSource.Token);

                        if (finishedTask != fileSetTask || fileSetTask.Result is not FileItem fileItem)
                        {
                            // The app exited.
                            break;
                        }
                        else
                        {
                            _reporter.Output($"File changed: {fileItem.FilePath}.");

                            var start = Stopwatch.GetTimestamp();
                            if (await hotReload.TryHandleFileChange(context, fileItem, combinedCancellationSource.Token))
                            {
                                var totalTime = TimeSpan.FromTicks(Stopwatch.GetTimestamp() - start);
                                _reporter.Output($"Hot reload of changes succeeded.");
                                _reporter.Verbose($"Hot reload applied in {totalTime.TotalMilliseconds}ms.");
                            }
                            else
                            {
                                _reporter.Verbose($"Unable to handle changes to {fileItem.FilePath}. Rebuilding the app.");
                                break;
                            }
                        }
                    }

                    // Regardless of the which task finished first, make sure everything is cancelled
                    // and wait for dotnet to exit. We don't want orphan processes
                    currentRunCancellationSource.Cancel();

                    await Task.WhenAll(processTask, fileSetTask);

                    if (processTask.Result != 0 && finishedTask == processTask && !cancellationToken.IsCancellationRequested)
                    {
                        // Only show this error message if the process exited non-zero due to a normal process exit.
                        // Don't show this if dotnet-watch killed the inner process due to file change or CTRL+C by the user
                        _reporter.Error($"Exited with error code {processTask.Result}");
                    }
                    else
                    {
                        _reporter.Output("Exited");
                    }

                    if (finishedTask == processTask)
                    {
                        // Process exited. Redo evaludation
                        context.RequiresMSBuildRevaluation = true;
                        // Now wait for a file to change before restarting process
                        context.ChangedFile = await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _reporter.Warn("Waiting for a file to change before restarting dotnet..."));
                    }
                    else
                    {
                        Debug.Assert(finishedTask == fileSetTask);
                        var changedFile = fileSetTask.Result;
                        context.ChangedFile = changedFile;
                    }
                }
コード例 #2
0
ファイル: Program.cs プロジェクト: jkoritzinsky/dotnet-sdk
        private async Task <int> MainInternalAsync(
            IReporter reporter,
            string project,
            IReadOnlyList <string> args,
            CancellationToken cancellationToken)
        {
            // TODO multiple projects should be easy enough to add here
            string projectFile;

            try
            {
                projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDirectory, project);
            }
            catch (FileNotFoundException ex)
            {
                reporter.Error(ex.Message);
                return(1);
            }

            var isDefaultRunCommand = false;

            if (args.Count == 1 && args[0] == "run")
            {
                isDefaultRunCommand = true;
            }
            else if (args.Count == 0)
            {
                isDefaultRunCommand = true;
                args = new[] { "run" };
            }

            var watchOptions = DotNetWatchOptions.Default;

            var fileSetFactory = new MsBuildFileSetFactory(reporter,
                                                           watchOptions,
                                                           projectFile,
                                                           waitOnError: true,
                                                           trace: false);
            var processInfo = new ProcessSpec
            {
                Executable           = DotnetMuxer.MuxerPath,
                WorkingDirectory     = Path.GetDirectoryName(projectFile),
                Arguments            = args,
                EnvironmentVariables =
                {
                    ["DOTNET_WATCH"] = "1"
                },
            };

            if (CommandLineOptions.IsPollingEnabled)
            {
                _reporter.Output("Polling file watcher is enabled");
            }

            var defaultProfile = LaunchSettingsProfile.ReadDefaultProfile(_workingDirectory, reporter);

            var context = new DotNetWatchContext
            {
                ProcessSpec = processInfo,
                Reporter    = _reporter,
                SuppressMSBuildIncrementalism = watchOptions.SuppressMSBuildIncrementalism,
                DefaultLaunchSettingsProfile  = defaultProfile,
            };

            if (isDefaultRunCommand && !string.IsNullOrEmpty(defaultProfile?.HotReloadProfile))
            {
                _reporter.Verbose($"Found HotReloadProfile={defaultProfile.HotReloadProfile}. Watching with hot-reload");

                // We'll sue hot-reload based watching if
                // a) watch was invoked with no args or with exactly one arg - the run command e.g. `dotnet watch` or `dotnet watch run`
                // b) The launch profile supports hot-reload based watching.
                // The watcher will complain if users configure this for runtimes that would not support it.
                await using var watcher = new HotReloadDotNetWatcher(reporter, fileSetFactory, watchOptions, _console);
                await watcher.WatchAsync(context, cancellationToken);
            }
            else
            {
                _reporter.Verbose("Did not find a HotReloadProfile or running a non-default command. Watching with legacy behavior.");

                // We'll use the presence of a profile to decide if we're going to use the hot-reload based watching.
                // The watcher will complain if users configure this for runtimes that would not support it.
                await using var watcher = new DotNetWatcher(reporter, fileSetFactory, watchOptions);
                await watcher.WatchAsync(context, cancellationToken);
            }

            return(0);
        }
コード例 #3
0
        public async Task WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken)
        {
            var cancelledTaskSource = new TaskCompletionSource();

            cancellationToken.Register(state => ((TaskCompletionSource)state).TrySetResult(),
                                       cancelledTaskSource);

            var processSpec      = context.ProcessSpec;
            var initialArguments = processSpec.Arguments.ToArray();

            if (context.SuppressMSBuildIncrementalism)
            {
                _reporter.Verbose("MSBuild incremental optimizations suppressed.");
            }

            while (true)
            {
                context.Iteration++;

                // Reset arguments
                processSpec.Arguments = initialArguments;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                // Reset for next run
                context.RequiresMSBuildRevaluation = false;

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                using (var currentRunCancellationSource = new CancellationTokenSource())
                    using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
                               cancellationToken,
                               currentRunCancellationSource.Token))
                        using (var fileSetWatcher = new FileSetWatcher(fileSet, _reporter))
                        {
                            var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
                            var args        = string.Join(" ", processSpec.Arguments);
                            _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                            _reporter.Output("Started");

                            Task <FileItem?> fileSetTask;
                            Task             finishedTask;

                            while (true)
                            {
                                fileSetTask  = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                                finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);

                                if (finishedTask == fileSetTask &&
                                    fileSetTask.Result is FileItem fileItem &&
                                    await _staticFileHandler.TryHandleFileChange(context, fileItem, combinedCancellationSource.Token))
                                {
                                    // We're able to handle the file change event without doing a full-rebuild.
                                }
コード例 #4
0
        public async Task WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken)
        {
            var processSpec = context.ProcessSpec;

            _reporter.Output("Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload. " +
                             "Press \"Ctrl + R\" to restart.");

            var forceReload = new CancellationTokenSource();

            _console.KeyPressed += (key) =>
            {
                var modifiers = ConsoleModifiers.Control;
                if ((key.Modifiers & modifiers) == modifiers && key.Key == ConsoleKey.R)
                {
                    var cancellationTokenSource = Interlocked.Exchange(ref forceReload, new CancellationTokenSource());
                    cancellationTokenSource.Cancel();
                }
            };

            while (true)
            {
                context.Iteration++;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (!fileSet.Project.IsNetCoreApp60OrNewer())
                {
                    _reporter.Error($"Hot reload based watching is only supported in .NET 6.0 or newer apps. Update the project's launchSettings.json to disable this feature.");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                if (context.Iteration == 0)
                {
                    ConfigureExecutable(context, processSpec);
                }

                using var currentRunCancellationSource = new CancellationTokenSource();
                using var combinedCancellationSource   = CancellationTokenSource.CreateLinkedTokenSource(
                          cancellationToken,
                          currentRunCancellationSource.Token,
                          forceReload.Token);
                using var fileSetWatcher = new HotReloadFileSetWatcher(fileSet, _reporter);

                try
                {
                    using var hotReload = new HotReload(_processRunner, _reporter);
                    await hotReload.InitializeAsync(context, cancellationToken);

                    var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
                    var args        = string.Join(" ", processSpec.Arguments);
                    _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                    _reporter.Output("Started");

                    Task <FileItem[]> fileSetTask;
                    Task finishedTask;

                    while (true)
                    {
                        fileSetTask  = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                        finishedTask = await Task.WhenAny(processTask, fileSetTask).WaitAsync(combinedCancellationSource.Token);

                        if (finishedTask != fileSetTask || fileSetTask.Result is not FileItem[] fileItems)
                        {
                            // The app exited.
                            break;
                        }
                        else
                        {
                            if (MayRequireRecompilation(context, fileItems) is { } newFile)
                            {
                                _reporter.Output($"New file: {newFile.FilePath}. Rebuilding the application.");
                                break;
                            }
コード例 #5
0
        public async Task WatchAsync(ProcessSpec processSpec, CancellationToken cancellationToken)
        {
            Ensure.NotNull(processSpec, nameof(processSpec));

            var cancelledTaskSource = new TaskCompletionSource();

            cancellationToken.Register(state => ((TaskCompletionSource)state).TrySetResult(),
                                       cancelledTaskSource);

            var initialArguments = processSpec.Arguments.ToArray();
            var context          = new DotNetWatchContext
            {
                Iteration   = -1,
                ProcessSpec = processSpec,
                Reporter    = _reporter,
                SuppressMSBuildIncrementalism = _dotnetWatchOptions.SuppressMSBuildIncrementalism,
            };

            if (context.SuppressMSBuildIncrementalism)
            {
                _reporter.Verbose("MSBuild incremental optimizations suppressed.");
            }

            while (true)
            {
                context.Iteration++;

                // Reset arguments
                processSpec.Arguments = initialArguments;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                // Reset for next run
                context.RequiresMSBuildRevaluation = false;

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                using (var currentRunCancellationSource = new CancellationTokenSource())
                    using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
                               cancellationToken,
                               currentRunCancellationSource.Token))
                        using (var fileSetWatcher = new FileSetWatcher(fileSet, _reporter))
                        {
                            var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
                            var args        = ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments);
                            _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                            _reporter.Output("Started");

                            Task <FileItem?> fileSetTask;
                            Task             finishedTask;

                            while (true)
                            {
                                fileSetTask  = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                                finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);

                                if (context.BrowserRefreshServer is not null &&
                                    finishedTask == fileSetTask &&
                                    fileSetTask.Result is FileItem {
                                    FileKind : FileKind.StaticFile
                                } file)
                                {
                                    _reporter.Verbose($"Handling file change event for static content {file.FilePath}.");

                                    // If we can handle the file change without a browser refresh, do it.
                                    await StaticContentHandler.TryHandleFileAction(context.BrowserRefreshServer, file, combinedCancellationSource.Token);
                                }
コード例 #6
0
        private async Task <int> MainInternalAsync(CommandLineOptions options, CancellationToken cancellationToken)
        {
            // TODO multiple projects should be easy enough to add here
            string projectFile;

            try
            {
                projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDirectory, options.Project);
            }
            catch (FileNotFoundException ex)
            {
                _reporter.Error(ex.Message);
                return(1);
            }

            var args = options.RemainingArguments;

            var isDefaultRunCommand = false;

            if (args.Count == 1 && args[0] == "run")
            {
                isDefaultRunCommand = true;
            }
            else if (args.Count == 0)
            {
                isDefaultRunCommand = true;
                args = new[] { "run" };
            }

            var watchOptions = DotNetWatchOptions.Default;

            watchOptions.NonInteractive = options.NonInteractive;

            var fileSetFactory = new MsBuildFileSetFactory(_reporter,
                                                           watchOptions,
                                                           projectFile,
                                                           waitOnError: true,
                                                           trace: false);
            var processInfo = new ProcessSpec
            {
                Executable           = DotnetMuxer.MuxerPath,
                WorkingDirectory     = Path.GetDirectoryName(projectFile),
                Arguments            = args,
                EnvironmentVariables =
                {
                    ["DOTNET_WATCH"] = "1"
                },
            };

            if (CommandLineOptions.IsPollingEnabled)
            {
                _reporter.Output("Polling file watcher is enabled");
            }

            var defaultProfile = LaunchSettingsProfile.ReadDefaultProfile(processInfo.WorkingDirectory, _reporter) ?? new();

            var context = new DotNetWatchContext
            {
                ProcessSpec = processInfo,
                Reporter    = _reporter,
                SuppressMSBuildIncrementalism = watchOptions.SuppressMSBuildIncrementalism,
                DefaultLaunchSettingsProfile  = defaultProfile,
            };

            context.ProjectGraph = TryReadProject(projectFile);

            if (!options.NoHotReload && isDefaultRunCommand && context.ProjectGraph is not null && IsHotReloadSupported(context.ProjectGraph))
            {
                _reporter.Verbose($"Project supports hot reload and was configured to run with the default run-command. Watching with hot-reload");

                // Use hot-reload based watching if
                // a) watch was invoked with no args or with exactly one arg - the run command e.g. `dotnet watch` or `dotnet watch run`
                // b) The launch profile supports hot-reload based watching.
                // The watcher will complain if users configure this for runtimes that would not support it.
                await using var watcher = new HotReloadDotNetWatcher(_reporter, _requester, fileSetFactory, watchOptions, _console, _workingDirectory);
                await watcher.WatchAsync(context, cancellationToken);
            }
コード例 #7
0
ファイル: DotNetWatcher.cs プロジェクト: wht526/aspnetcore
        public async Task WatchAsync(ProcessSpec processSpec, CancellationToken cancellationToken)
        {
            Ensure.NotNull(processSpec, nameof(processSpec));

            var cancelledTaskSource = new TaskCompletionSource();

            cancellationToken.Register(state => ((TaskCompletionSource)state).TrySetResult(),
                                       cancelledTaskSource);

            var initialArguments = processSpec.Arguments.ToArray();
            var suppressMSBuildIncrementalism = Environment.GetEnvironmentVariable("DOTNET_WATCH_SUPPRESS_MSBUILD_INCREMENTALISM");
            var context = new DotNetWatchContext
            {
                Iteration   = -1,
                ProcessSpec = processSpec,
                Reporter    = _reporter,
                SuppressMSBuildIncrementalism = suppressMSBuildIncrementalism == "1" || suppressMSBuildIncrementalism == "true",
            };

            if (context.SuppressMSBuildIncrementalism)
            {
                _reporter.Verbose("MSBuild incremental optimizations suppressed.");
            }

            while (true)
            {
                context.Iteration++;

                // Reset arguments
                processSpec.Arguments = initialArguments;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                // Reset for next run
                context.RequiresMSBuildRevaluation = false;

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                using (var currentRunCancellationSource = new CancellationTokenSource())
                    using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
                               cancellationToken,
                               currentRunCancellationSource.Token))
                        using (var fileSetWatcher = new FileSetWatcher(fileSet, _reporter))
                        {
                            var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                            var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);

                            var args = ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments);
                            _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                            _reporter.Output("Started");

                            var finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);

                            // Regardless of the which task finished first, make sure everything is cancelled
                            // and wait for dotnet to exit. We don't want orphan processes
                            currentRunCancellationSource.Cancel();

                            await Task.WhenAll(processTask, fileSetTask);

                            if (processTask.Result != 0 && finishedTask == processTask && !cancellationToken.IsCancellationRequested)
                            {
                                // Only show this error message if the process exited non-zero due to a normal process exit.
                                // Don't show this if dotnet-watch killed the inner process due to file change or CTRL+C by the user
                                _reporter.Error($"Exited with error code {processTask.Result}");
                            }
                            else
                            {
                                _reporter.Output("Exited");
                            }

                            if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested)
                            {
                                return;
                            }

                            context.ChangedFile = fileSetTask.Result;
                            if (finishedTask == processTask)
                            {
                                // Process exited. Redo evaludation
                                context.RequiresMSBuildRevaluation = true;
                                // Now wait for a file to change before restarting process
                                context.ChangedFile = await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _reporter.Warn("Waiting for a file to change before restarting dotnet..."));
                            }

                            if (!string.IsNullOrEmpty(fileSetTask.Result))
                            {
                                _reporter.Output($"File changed: {fileSetTask.Result}");
                            }
                        }
            }
        }
コード例 #8
0
ファイル: HotReloadDotNetWatcher.cs プロジェクト: nohwnd/sdk
        public async Task WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken)
        {
            var processSpec = context.ProcessSpec;

            var forceReload             = new CancellationTokenSource();
            var hotReloadEnabledMessage = "Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload.";

            if (!_dotNetWatchOptions.NonInteractive)
            {
                _reporter.Output($"{hotReloadEnabledMessage}{Environment.NewLine}  {(_dotNetWatchOptions.SuppressEmojis ? string.Empty : "💡")} Press \"Ctrl + R\" to restart.", emoji: "🔥");

                _console.KeyPressed += (key) =>
                {
                    var modifiers = ConsoleModifiers.Control;
                    if ((key.Modifiers & modifiers) == modifiers && key.Key == ConsoleKey.R)
                    {
                        var cancellationTokenSource = Interlocked.Exchange(ref forceReload, new CancellationTokenSource());
                        cancellationTokenSource.Cancel();
                    }
                };
            }
            else
            {
                _reporter.Output(hotReloadEnabledMessage, emoji: "🔥");
            }

            while (true)
            {
                context.Iteration++;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (!fileSet.Project.IsNetCoreApp60OrNewer())
                {
                    _reporter.Error($"Hot reload based watching is only supported in .NET 6.0 or newer apps. Update the project's launchSettings.json to disable this feature.");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                if (context.Iteration == 0)
                {
                    ConfigureExecutable(context, processSpec);
                }

                using var currentRunCancellationSource = new CancellationTokenSource();
                using var combinedCancellationSource   = CancellationTokenSource.CreateLinkedTokenSource(
                          cancellationToken,
                          currentRunCancellationSource.Token,
                          forceReload.Token);
                using var fileSetWatcher = new HotReloadFileSetWatcher(fileSet, _reporter);

                try
                {
                    using var hotReload = new HotReload(_processRunner, _reporter);
                    await hotReload.InitializeAsync(context, cancellationToken);

                    var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
                    var args        = string.Join(" ", processSpec.Arguments);
                    _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                    _reporter.Output("Started", emoji: "🚀");

                    Task <FileItem[]?> fileSetTask;
                    Task finishedTask;

                    while (true)
                    {
                        fileSetTask  = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                        finishedTask = await Task.WhenAny(processTask, fileSetTask).WaitAsync(combinedCancellationSource.Token);

                        if (finishedTask != fileSetTask || fileSetTask.Result is not FileItem[] fileItems)
                        {
                            if (processTask.IsFaulted && finishedTask == processTask && !cancellationToken.IsCancellationRequested)
                            {
                                // Only show this error message if the process exited non-zero due to a normal process exit.
                                // Don't show this if dotnet-watch killed the inner process due to file change or CTRL+C by the user
                                _reporter.Error($"Application failed to start: {processTask.Exception?.InnerException?.Message}");
                            }
                            break;
                        }
                        else
                        {
                            if (MayRequireRecompilation(context, fileItems) is { } newFile)
                            {
                                _reporter.Output($"New file: {GetRelativeFilePath(newFile.FilePath)}. Rebuilding the application.");
                                break;
                            }