Exemple #1
0
        private async Task <int> MainInternalAsync(
            IReporter reporter,
            string project,
            IReadOnlyCollection <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 watchOptions = DotNetWatchOptions.Default;

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

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

            await using var watcher = new DotNetWatcher(reporter, fileSetFactory, watchOptions);
            await watcher.WatchAsync(processInfo, cancellationToken);

            return(0);
        }
Exemple #2
0
        public async Task WatchAsync(ProcessSpec processSpec, IFileSetFactory fileSetFactory, string replica,
                                     CancellationToken cancellationToken)
        {
            var cancelledTaskSource = new TaskCompletionSource <object>();

            cancellationToken.Register(state => ((TaskCompletionSource <object>)state !).TrySetResult(null !),
                                       cancelledTaskSource);

            var iteration = 1;

            while (true)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = iteration.ToString(CultureInfo.InvariantCulture);
                iteration++;

                var fileSet = await fileSetFactory.CreateAsync(cancellationToken);

                if (fileSet == null)
                {
                    _logger.LogError("watch: Failed to find a list of files to watch");
                    return;
                }

                using (var currentRunCancellationSource = new CancellationTokenSource())
                    using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
                               cancellationToken,
                               currentRunCancellationSource.Token))
                        using (var fileSetWatcher = new FileSetWatcher(fileSet, _logger))
                        {
                            var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                            var processTask = ProcessUtil.RunAsync(processSpec, combinedCancellationSource.Token, throwOnError: false);

                            var args = processSpec.Arguments !;
                            _logger.LogDebug($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                            _logger.LogInformation("watch: {Replica} Started", replica);

                            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.ExitCode != 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
                                _logger.LogError($"watch: Exited with error code {processTask.Result}");
                            }
                            else
                            {
                                _logger.LogInformation("watch: {Replica} Exited", replica);
                            }

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

                            if (finishedTask == processTask)
                            {
                                // Now wait for a file to change before restarting process
                                await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _logger.LogWarning("Waiting for a file to change before restarting dotnet..."));
                            }

                            if (!string.IsNullOrEmpty(fileSetTask.Result))
                            {
                                _logger.LogInformation($"watch: File changed: {fileSetTask.Result}");
                            }

                            if (processSpec.Build != null)
                            {
                                while (true)
                                {
                                    if (cancellationToken.IsCancellationRequested)
                                    {
                                        break;
                                    }

                                    var exitCode = await processSpec.Build();

                                    if (exitCode == 0)
                                    {
                                        break;
                                        // Build failed, keep retrying builds until successful build.
                                    }

                                    await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _logger.LogWarning("Waiting for a file to change before restarting dotnet..."));
                                }
                            }
                        }
            }
        }
Exemple #3
0
        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);
        }
Exemple #4
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);
                                }
Exemple #5
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);
            }
Exemple #6
0
        public async Task WatchAsync(ProcessSpec processSpec, IFileSetFactory fileSetFactory,
                                     CancellationToken cancellationToken)
        {
            Ensure.NotNull(processSpec, nameof(processSpec));

            var cancelledTaskSource = new TaskCompletionSource <object>();

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

            var iteration = 1;

            while (true)
            {
                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = iteration.ToString(CultureInfo.InvariantCulture);
                iteration++;

                var fileSet = await fileSetFactory.CreateAsync(cancellationToken);

                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;
                            }

                            if (finishedTask == processTask)
                            {
                                _reporter.Warn("Waiting for a file to change before restarting dotnet...");

                                // Now wait for a file to change before restarting process
                                await fileSetWatcher.GetChangedFileAsync(cancellationToken);
                            }

                            if (!string.IsNullOrEmpty(fileSetTask.Result))
                            {
                                _reporter.Output($"File changed: {fileSetTask.Result}");
                            }
                        }
            }
        }
Exemple #7
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 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}");
                            }
                        }
            }
        }