Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/Dotnet.Watch/Watch/Browser/BrowserLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ internal sealed class BrowserLauncher(ILogger logger, IProcessOutputReporter pro
private ImmutableHashSet<ProjectInstanceId> _browserLaunchAttempted = [];

/// <summary>
/// Installs browser launch/reload trigger.
/// Retruns an output observing action that triggers the launch of the browser, or null if the browser should not be launched.
/// </summary>
public void InstallBrowserLaunchTrigger(
ProcessSpec processSpec,
public Action<OutputLine>? TryGetBrowserLaunchOutputObserver(
ProjectGraphNode projectNode,
ProjectOptions projectOptions,
AbstractBrowserRefreshServer? server,
Expand All @@ -32,10 +31,10 @@ public void InstallBrowserLaunchTrigger(
logger.LogError("Test requires browser to launch");
}

return;
return null;
}

WebServerProcessStateObserver.Observe(projectNode, processSpec, url =>
return WebServerProcessStateObserver.GetObserver(projectNode, url =>
{
if (projectOptions.IsMainProject &&
ImmutableInterlocked.Update(ref _browserLaunchAttempted, static (set, key) => set.Add(key), projectNode.ProjectInstance.GetId()))
Expand Down
21 changes: 21 additions & 0 deletions src/Dotnet.Watch/Watch/Process/ProcessSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,25 @@ internal sealed class ProcessSpec

public string GetArgumentsDisplay()
=> CommandLineUtilities.JoinArguments(Arguments ?? []);

/// <summary>
/// Stream output lines to the process output reporter when
/// - output observer is installed so that the output is also streamd to the console;
/// - testing to synchonize the output of the process with the logger output, so that the printed lines don't interleave;
/// unless the caller has already redirected the output (e.g. for Aspire child processes).
///
/// Do not redirect output otherwise as it disables the ability of the process to use Console APIs.
/// </summary>
public void RedirectOutput(Action<OutputLine>? outputObserver, IProcessOutputReporter outputReporter, EnvironmentOptions environmentOptions, string projectDisplayName)
{
if (environmentOptions.RunningAsTest || outputObserver != null)
{
OnOutput ??= line =>
{
outputReporter.ReportOutput(outputReporter.PrefixProcessOutput ? line with { Content = $"[{projectDisplayName}] {line.Content}" } : line);
};

OnOutput += outputObserver;
}
}
}
16 changes: 4 additions & 12 deletions src/Dotnet.Watch/Watch/Process/ProjectLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,6 @@ public CompilationHandler CompilationHandler
OnExit = onExit,
};

// Stream output lines to the process output reporter.
// The reporter synchronizes the output of the process with the logger output,
// so that the printed lines don't interleave.
// Only send the output to the reporter if no custom output handler was provided (e.g. for Aspire child processes).
processSpec.OnOutput ??= line =>
{
context.ProcessOutputReporter.ReportOutput(context.ProcessOutputReporter.PrefixProcessOutput ? line with { Content = $"[{projectDisplayName}] {line.Content}" } : line);
};

var environmentBuilder = new Dictionary<string, string>();

// initialize with project settings:
Expand All @@ -91,9 +82,10 @@ public CompilationHandler CompilationHandler

processSpec.Arguments = GetProcessArguments(projectOptions, environmentBuilder);

// Attach trigger to the process that detects when the web server reports to the output that it's listening.
// Launches browser on the URL found in the process output for root projects.
context.BrowserLauncher.InstallBrowserLaunchTrigger(processSpec, projectNode, projectOptions, clients.BrowserRefreshServer, cancellationToken);
// Observes main project process output and launches browser when the URL is found in the output.
var outputObserver = context.BrowserLauncher.TryGetBrowserLaunchOutputObserver(projectNode, projectOptions, clients.BrowserRefreshServer, cancellationToken);

processSpec.RedirectOutput(outputObserver, context.ProcessOutputReporter, context.EnvironmentOptions, projectDisplayName);

return await compilationHandler.TrackRunningProjectAsync(
projectNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal static partial class WebServerProcessStateObserver
[GeneratedRegex(@"Login to the dashboard at (?<url>.*)\s*$", RegexOptions.Compiled)]
private static partial Regex GetAspireDashboardUrlRegex();

public static void Observe(ProjectGraphNode serverProject, ProcessSpec serverProcessSpec, Action<string> onServerListening)
public static Action<OutputLine> GetObserver(ProjectGraphNode serverProject, Action<string> onServerListening)
{
// Workaround for Aspire dashboard launching: scan for "Login to the dashboard at " prefix in the output and use the URL.
// TODO: https://github.com/dotnet/sdk/issues/9038
Expand All @@ -30,7 +30,7 @@ public static void Observe(ProjectGraphNode serverProject, ProcessSpec serverPro

var _notified = false;

serverProcessSpec.OnOutput += line =>
return line =>
{
if (_notified)
{
Expand Down
16 changes: 9 additions & 7 deletions src/Dotnet.Watch/dotnet-watch/Watch/DotNetWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ public static async Task WatchAsync(DotNetWatchContext context, CancellationToke
{
[EnvironmentVariables.Names.DotnetWatch] = "1",
[EnvironmentVariables.Names.DotnetWatchIteration] = (iteration + 1).ToString(CultureInfo.InvariantCulture),
},
OnOutput = line => context.ProcessOutputReporter.ReportOutput(line)
}
};

var browserRefreshServer = projectRootNode != null && HotReloadAppModel.InferFromProject(context, projectRootNode) is WebApplicationAppModel webAppModel
Expand All @@ -68,15 +67,18 @@ public static async Task WatchAsync(DotNetWatchContext context, CancellationToke

browserRefreshServer?.ConfigureLaunchEnvironment(environmentBuilder, enableHotReload: false);

foreach (var (name, value) in environmentBuilder)
Action<OutputLine>? outputObserver = null;
if (projectRootNode != null)
{
processSpec.EnvironmentVariables.Add(name, value);
Debug.Assert(context.MainProjectOptions != null);
outputObserver = context.BrowserLauncher.TryGetBrowserLaunchOutputObserver(projectRootNode, context.MainProjectOptions, browserRefreshServer, shutdownCancellationToken);
}

if (projectRootNode != null)
processSpec.RedirectOutput(outputObserver, context.ProcessOutputReporter, context.EnvironmentOptions, projectRootNode?.GetDisplayName() ?? "");

foreach (var (name, value) in environmentBuilder)
{
Debug.Assert(context.MainProjectOptions != null);
context.BrowserLauncher.InstallBrowserLaunchTrigger(processSpec, projectRootNode, context.MainProjectOptions, browserRefreshServer, shutdownCancellationToken);
processSpec.EnvironmentVariables.Add(name, value);
}

// Reset for next run
Expand Down
Loading