public async Task ProjectReferences_Graph() { var graph = new TestProjectGraph(_tempDir); graph.OnCreate(p => p.WithTargetFrameworks("net45")); var matches = Regex.Matches(@" A->B B->C C->D D->E B->E A->F F->G G->E F->E W->U Y->Z Y->B Y->F", @"(\w)->(\w)"); Assert.Equal(13, matches.Count); foreach (Match m in matches) { var target = graph.GetOrCreate(m.Groups[2].Value); graph.GetOrCreate(m.Groups[1].Value).WithProjectReference(target); } var a = graph.Find("A"); Assert.NotNull(a); a !.WithProjectReference(graph.Find("W"), watch: false); var output = new OutputSink(); var filesetFactory = new MsBuildFileSetFactory(_reporter, DotNetWatchOptions.Default, graph.GetOrCreate("A").Path, output, trace: true); var fileset = await GetFileSet(filesetFactory); _reporter.Output(string.Join( Environment.NewLine, output.Current.Lines.Select(l => "Sink output: " + l))); var includedProjects = new[] { "A", "B", "C", "D", "E", "F", "G" }; AssertEx.EqualFileList( _tempDir.Root, includedProjects .Select(p => $"{p}/{p}.csproj"), fileset ); // ensure unreachable projects exist but where not included Assert.NotNull(graph.Find("W")); Assert.NotNull(graph.Find("U")); Assert.NotNull(graph.Find("Y")); Assert.NotNull(graph.Find("Z")); // ensure each project is only visited once for collecting watch items Assert.All(includedProjects, projectName => Assert.Single(output.Current.Lines, line => line.Contains($"Collecting watch items from '{projectName}'")) ); }
private async Task <IFileSet> GetFileSet(MsBuildFileSetFactory filesetFactory) { _tempDir.Create(); return(await filesetFactory .CreateAsync(CancellationToken.None) .TimeoutAfter(TimeSpan.FromSeconds(30))); }
private async Task <IFileSet> GetFileSet(MsBuildFileSetFactory filesetFactory) { _tempDir.Create(); var createTask = filesetFactory.CreateAsync(CancellationToken.None); var finished = await Task.WhenAny(createTask, Task.Delay(TimeSpan.FromSeconds(10))); Assert.Same(createTask, finished); Assert.NotNull(createTask.Result); return(createTask.Result); }
private async Task <int> ListFilesAsync( IReporter reporter, string project, 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 fileSetFactory = new MsBuildFileSetFactory(reporter, DotNetWatchOptions.Default, projectFile, waitOnError: false, trace: false); var files = await fileSetFactory.CreateAsync(cancellationToken); if (files == null) { return(1); } foreach (var group in files.GroupBy(g => g.FileKind).OrderBy(g => g.Key)) { if (group.Key == FileKind.StaticFile) { _console.Out.WriteLine("::: Watch Action: Refresh browser :::"); } foreach (var file in group) { if (file.FileKind == FileKind.StaticFile) { _console.Out.WriteLine($"{file.FilePath} ~/{file.StaticWebAssetPath}"); } else { _console.Out.WriteLine(file.FilePath); } } } return(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); }
public async Task ProjectReferences_Graph() { // A->B,F,W(Watch=False) // B->C,E // C->D // D->E // F->E,G // G->E // W->U // Y->B,F,Z var testDirectory = _testAssets.CopyTestAsset("ProjectReferences_Graph") .WithSource() .Path; var projectA = Path.Combine(testDirectory, "A", "A.csproj"); var output = new OutputSink(); var options = GetWatchOptions(); var filesetFactory = new MsBuildFileSetFactory(options, _reporter, projectA, output, waitOnError: false, trace: true); var fileset = await GetFileSet(filesetFactory); Assert.NotNull(fileset); _reporter.Output(string.Join( Environment.NewLine, output.Current.Lines.Select(l => "Sink output: " + l))); var includedProjects = new[] { "A", "B", "C", "D", "E", "F", "G" }; AssertEx.EqualFileList( testDirectory, includedProjects .Select(p => $"{p}/{p}.csproj"), fileset ); // ensure each project is only visited once for collecting watch items Assert.All(includedProjects, projectName => Assert.Single(output.Current.Lines, line => line.Contains($"Collecting watch items from '{projectName}'")) ); }
private async Task <int> ListFilesAsync( IReporter reporter, string project, 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 fileSetFactory = new MsBuildFileSetFactory( reporter, DotNetWatchOptions.Default, projectFile, waitOnError: false, trace: false); var files = await fileSetFactory.CreateAsync(cancellationToken); if (files == null) { return(1); } foreach (var file in files) { _console.Out.WriteLine(file.FilePath); } return(0); }
private void LaunchService(Application application, Service service) { var serviceDescription = service.Description; var processInfo = new ProcessInfo(new Task[service.Description.Replicas]); var serviceName = serviceDescription.Name; // Set by BuildAndRunService var args = service.Status.Args !; var path = service.Status.ExecutablePath !; var workingDirectory = service.Status.WorkingDirectory !; async Task RunApplicationAsync(IEnumerable <(int ExternalPort, int Port, string?Protocol)> ports, string copiedArgs) { // Make sure we yield before trying to start the process, this is important so we don't block startup await Task.Yield(); var hasPorts = ports.Any(); var environment = new Dictionary <string, string> { // Default to development environment ["DOTNET_ENVIRONMENT"] = "Development", ["ASPNETCORE_ENVIRONMENT"] = "Development", // Remove the color codes from the console output ["DOTNET_LOGGING__CONSOLE__DISABLECOLORS"] = "true", ["ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS"] = "true" }; // Set up environment variables to use the version of dotnet we're using to run // this is important for tests where we're not using a globally-installed dotnet. var dotnetRoot = GetDotnetRoot(); if (dotnetRoot is object) { environment["DOTNET_ROOT"] = dotnetRoot; environment["DOTNET_MULTILEVEL_LOOKUP"] = "0"; environment["PATH"] = $"{dotnetRoot};{Environment.GetEnvironmentVariable("PATH")}"; } application.PopulateEnvironment(service, (k, v) => environment[k] = v); if (_options.DebugMode && (_options.DebugAllServices || _options.ServicesToDebug !.Contains(serviceName, StringComparer.OrdinalIgnoreCase))) { environment["DOTNET_STARTUP_HOOKS"] = typeof(Hosting.Runtime.HostingRuntimeHelpers).Assembly.Location; } if (hasPorts) { // These are the ports that the application should use for binding // 1. Configure ASP.NET Core to bind to those same ports environment["ASPNETCORE_URLS"] = string.Join(";", ports.Select(p => $"{p.Protocol ?? "http"}://{application.ContainerEngine.AspNetUrlsHost}:{p.Port}")); // Set the HTTPS port for the redirect middleware foreach (var p in ports) { if (string.Equals(p.Protocol, "https", StringComparison.OrdinalIgnoreCase)) { // We need to set the redirect URL to the exposed port so the redirect works cleanly environment["HTTPS_PORT"] = p.ExternalPort.ToString(); } } // 3. For non-ASP.NET Core apps, pass the same information in the PORT env variable as a semicolon separated list. environment["PORT"] = string.Join(";", ports.Select(p => $"{p.Port}")); if (service.ServiceType == ServiceType.Function) { // Need to inject port and UseHttps as an argument to func.exe rather than environment variables. var binding = ports.First(); copiedArgs += $" --port {binding.Port}"; if (binding.Protocol == "https") { copiedArgs += " --useHttps"; } } } var backOff = TimeSpan.FromSeconds(5); while (!processInfo.StoppedTokenSource.IsCancellationRequested) { var replica = serviceName + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower(); var status = new ProcessStatus(service, replica); using var stoppingCts = new CancellationTokenSource(); status.StoppingTokenSource = stoppingCts; await using var _ = processInfo.StoppedTokenSource.Token.Register(() => status.StoppingTokenSource.Cancel()); if (!_options.Watch) { service.Replicas[replica] = status; service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status)); } // This isn't your host name environment["APP_INSTANCE"] = replica; status.ExitCode = null; status.Pid = null; status.Environment = environment; if (hasPorts) { status.Ports = ports.Select(p => p.Port); status.Bindings = ports.Select(p => new ReplicaBinding() { Port = p.Port, ExternalPort = p.ExternalPort, Protocol = p.Protocol }).ToList(); } // TODO clean this up. foreach (var env in environment) { copiedArgs = copiedArgs.Replace($"%{env.Key}%", env.Value); } _logger.LogInformation("Launching service {ServiceName}: {ExePath} {args}", replica, path, copiedArgs); try { service.Logs.OnNext($"[{replica}]:{path} {copiedArgs}"); var processInfo = new ProcessSpec { Executable = path, WorkingDirectory = workingDirectory, Arguments = copiedArgs, EnvironmentVariables = environment, OutputData = data => { service.Logs.OnNext($"[{replica}]: {data}"); }, ErrorData = data => service.Logs.OnNext($"[{replica}]: {data}"), OnStart = pid => { if (hasPorts) { _logger.LogInformation("{ServiceName} running on process id {PID} bound to {Address}", replica, pid, string.Join(", ", ports.Select(p => $"{p.Protocol ?? "http"}://localhost:{p.Port}"))); } else { _logger.LogInformation("{ServiceName} running on process id {PID}", replica, pid); } // Reset the backoff backOff = TimeSpan.FromSeconds(5); status.Pid = pid; WriteReplicaToStore(pid.ToString()); if (_options.Watch) { // OnStart/OnStop will be called multiple times for watch. // Watch will constantly be adding and removing from the list, so only add here for watch. service.Replicas[replica] = status; service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status)); } service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Started, status)); }, OnStop = exitCode => { status.ExitCode = exitCode; if (status.Pid != null) { service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Stopped, status)); } if (!_options.Watch) { // Only increase backoff when not watching project as watch will wait for file changes before rebuild. backOff *= 2; } service.Restarts++; service.Replicas.TryRemove(replica, out var _); service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status)); if (status.ExitCode != null) { _logger.LogInformation("{ServiceName} process exited with exit code {ExitCode}", replica, status.ExitCode); } }, Build = async() => { if (service.Description.RunInfo is ProjectRunInfo) { var buildResult = await ProcessUtil.RunAsync("dotnet", $"build \"{service.Status.ProjectFilePath}\" /nologo", throwOnError : false, workingDirectory : application.ContextDirectory); if (buildResult.ExitCode != 0) { _logger.LogInformation("Building projects failed with exit code {ExitCode}: \r\n" + buildResult.StandardOutput, buildResult.ExitCode); } return(buildResult.ExitCode); } return(0); } }; if (_options.Watch && (service.Description.RunInfo is ProjectRunInfo runInfo)) { var projectFile = runInfo.ProjectFile.FullName; var fileSetFactory = new MsBuildFileSetFactory(_logger, projectFile, waitOnError: true, trace: false); environment["DOTNET_WATCH"] = "1"; await new DotNetWatcher(_logger) .WatchAsync(processInfo, fileSetFactory, replica, status.StoppingTokenSource.Token); } else if (_options.Watch && (service.Description.RunInfo is AzureFunctionRunInfo azureFunctionRunInfo) && !string.IsNullOrEmpty(azureFunctionRunInfo.ProjectFile)) { var projectFile = azureFunctionRunInfo.ProjectFile; var fileSetFactory = new MsBuildFileSetFactory(_logger, projectFile, waitOnError: true, trace: false); environment["DOTNET_WATCH"] = "1"; await new DotNetWatcher(_logger) .WatchAsync(processInfo, fileSetFactory, replica, status.StoppingTokenSource.Token); } else { await ProcessUtil.RunAsync(processInfo, status.StoppingTokenSource.Token, throwOnError : false); } } catch (Exception ex) { _logger.LogError(0, ex, "Failed to launch process for service {ServiceName}", replica); if (!_options.Watch) { // Only increase backoff when not watching project as watch will wait for file changes before rebuild. backOff *= 2; } service.Restarts++; service.Replicas.TryRemove(replica, out var _); try { await Task.Delay(backOff, processInfo.StoppedTokenSource.Token); } catch (OperationCanceledException) { // Swallow cancellation exceptions and continue } } } } if (serviceDescription.Bindings.Count > 0) { // Each replica is assigned a list of internal ports, one mapped to each external // port for (int i = 0; i < serviceDescription.Replicas; i++) { var ports = new List <(int, int, string?)>(); foreach (var binding in serviceDescription.Bindings) { if (binding.Port == null) { continue; } ports.Add((binding.Port.Value, binding.ReplicaPorts[i], binding.Protocol)); } processInfo.Tasks[i] = RunApplicationAsync(ports, args); } } else { for (int i = 0; i < service.Description.Replicas; i++) { processInfo.Tasks[i] = RunApplicationAsync(Enumerable.Empty <(int, int, string?)>(), args); } } service.Items[typeof(ProcessInfo)] = processInfo; }
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); }
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); }