Skip to content

Commit

Permalink
Update to new Roslyn APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
tmat committed Nov 16, 2024
1 parent 47e79ef commit 84aa7ea
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 31 deletions.
26 changes: 15 additions & 11 deletions src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public async ValueTask TerminateNonRootProcessesAndDispose(CancellationToken can
Dispose();
}

public ValueTask RestartSessionAsync(ImmutableDictionary<ProjectId, string> projectsToBeRebuilt, CancellationToken cancellationToken)
public void UpdateProjectBaselines(ImmutableDictionary<ProjectId, string> projectsToBeRebuilt, CancellationToken cancellationToken)
{
// Remove previous updates to all modules that were affected by rude edits.
// All running projects that statically reference these modules have been terminated.
Expand All @@ -87,9 +87,8 @@ public ValueTask RestartSessionAsync(ImmutableDictionary<ProjectId, string> proj
_previousUpdates = _previousUpdates.RemoveAll(update => projectsToBeRebuilt.ContainsKey(update.ProjectId));
}

_hotReloadService.EndSession();
_reporter.Report(MessageDescriptor.HotReloadSessionEnded);
return StartSessionAsync(cancellationToken);
_hotReloadService.UpdateBaselines(Workspace.CurrentSolution, projectsToBeRebuilt.Keys.ToImmutableArray());
_reporter.Report(MessageDescriptor.ProjectBaselinesUpdates);
}

public async ValueTask StartSessionAsync(CancellationToken cancellationToken)
Expand Down Expand Up @@ -277,14 +276,19 @@ private static void PrepareCompilations(Solution solution, string projectPath, C
}

public async ValueTask<(ImmutableDictionary<ProjectId, string> projectsToRebuild, ImmutableArray<RunningProject> terminatedProjects)> HandleFileChangesAsync(
Func<IEnumerable<Project>, CancellationToken, Task> restartPrompt,
Func<IEnumerable<string>, CancellationToken, Task> restartPrompt,
CancellationToken cancellationToken)
{
var currentSolution = Workspace.CurrentSolution;
var runningProjects = _runningProjects;

var updates = await _hotReloadService.GetUpdatesAsync(currentSolution, isRunningProject: p => runningProjects.ContainsKey(p.FilePath!), cancellationToken);
var anyProcessNeedsRestart = updates.ProjectsToRestart.Count > 0;
var runningProjectIds = currentSolution.Projects
.Where(project => project.FilePath != null && runningProjects.ContainsKey(project.FilePath))
.Select(project => project.Id)
.ToImmutableHashSet();

var updates = await _hotReloadService.GetUpdatesAsync(currentSolution, runningProjectIds, cancellationToken);
var anyProcessNeedsRestart = !updates.ProjectIdsToRestart.IsEmpty;

await DisplayResultsAsync(updates, cancellationToken);

Expand All @@ -302,13 +306,13 @@ private static void PrepareCompilations(Solution solution, string projectPath, C
return (ImmutableDictionary<ProjectId, string>.Empty, []);
}

await restartPrompt.Invoke(updates.ProjectsToRestart, cancellationToken);
await restartPrompt.Invoke(updates.ProjectIdsToRestart.Select(id => currentSolution.GetProject(id)!.Name), cancellationToken);

// Terminate all tracked processes that need to be restarted,
// except for the root process, which will terminate later on.
var terminatedProjects = await TerminateNonRootProcessesAsync(updates.ProjectsToRestart.Select(p => p.FilePath!), cancellationToken);
var terminatedProjects = await TerminateNonRootProcessesAsync(updates.ProjectIdsToRestart.Select(id => currentSolution.GetProject(id)!.FilePath!), cancellationToken);

return (updates.ProjectsToRebuild.ToImmutableDictionary(keySelector: p => p.Id, elementSelector: p => p.FilePath!), terminatedProjects);
return (updates.ProjectIdsToRebuild.ToImmutableDictionary(keySelector: id => id, elementSelector: id => currentSolution.GetProject(id)!.FilePath!), terminatedProjects);
}

Debug.Assert(updates.Status == ModuleUpdateStatus.Ready);
Expand Down Expand Up @@ -353,7 +357,7 @@ await ForEachProjectAsync(projectsToUpdate, async (runningProject, cancellationT

private async ValueTask DisplayResultsAsync(WatchHotReloadService.Updates updates, CancellationToken cancellationToken)
{
var anyProcessNeedsRestart = updates.ProjectsToRestart.Count > 0;
var anyProcessNeedsRestart = !updates.ProjectIdsToRestart.IsEmpty;

switch (updates.Status)
{
Expand Down
14 changes: 7 additions & 7 deletions src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ void FileChangedCallback(string path, ChangeKind kind)

HotReloadEventSource.Log.HotReloadStart(HotReloadEventSource.StartType.CompilationHandler);

var (projectsToRebuild, projectsToRestart) = await compilationHandler.HandleFileChangesAsync(restartPrompt: async (projects, cancellationToken) =>
var (projectsToRebuild, projectsToRestart) = await compilationHandler.HandleFileChangesAsync(restartPrompt: async (projectNames, cancellationToken) =>
{
if (_rudeEditRestartPrompt != null)
{
Expand All @@ -265,9 +265,9 @@ void FileChangedCallback(string path, ChangeKind kind)
{
Context.Reporter.Output("Affected projects:");

foreach (var project in projects.OrderBy(p => p.Name))
foreach (var projectName in projectNames.OrderBy(n => n))
{
Context.Reporter.Output(" " + project.Name);
Context.Reporter.Output(" " + projectName);
}

question = "Do you want to restart these projects?";
Expand All @@ -283,9 +283,9 @@ void FileChangedCallback(string path, ChangeKind kind)
{
Context.Reporter.Verbose("Restarting without prompt since dotnet-watch is running in non-interactive mode.");

foreach (var project in projects)
foreach (var projectName in projectNames)
{
Context.Reporter.Verbose($" Project to restart: '{project.Name}'");
Context.Reporter.Verbose($" Project to restart: '{projectName}'");
}
}
}, iterationCancellationToken);
Expand Down Expand Up @@ -341,8 +341,8 @@ void FileChangedCallback(string path, ChangeKind kind)
// Apply them to the workspace.
_ = await CaptureChangedFilesSnapshot(projectsToRebuild);

// Restart session to capture new baseline that reflects the changes to the restarted projects.
await compilationHandler.RestartSessionAsync(projectsToRebuild, iterationCancellationToken);
// Update project baselines to reflect changes to the restarted projects.
compilationHandler.UpdateProjectBaselines(projectsToRebuild, iterationCancellationToken);
}

if (projectsToRestart is not [])
Expand Down
2 changes: 1 addition & 1 deletion src/BuiltInTools/dotnet-watch/Internal/IReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public bool TryGetMessage(string? prefix, object?[] args, [NotNullWhen(true)] ou
// predefined messages used for testing:
public static readonly MessageDescriptor HotReloadSessionStarting = new(Format: null, Emoji: null, MessageSeverity.None, s_id++);
public static readonly MessageDescriptor HotReloadSessionStarted = new("Hot reload session started.", "🔥", MessageSeverity.Verbose, s_id++);
public static readonly MessageDescriptor HotReloadSessionEnded = new("Hot reload session ended.", "🔥", MessageSeverity.Verbose, s_id++);
public static readonly MessageDescriptor ProjectBaselinesUpdates = new("Project baselines updated.", "🔥", MessageSeverity.Verbose, s_id++);
public static readonly MessageDescriptor FixBuildError = new("Fix the error to continue or press Ctrl+C to exit.", "⌚", MessageSeverity.Warning, s_id++);
public static readonly MessageDescriptor WaitingForChanges = new("Waiting for changes", "⌚", MessageSeverity.Verbose, s_id++);
public static readonly MessageDescriptor LaunchedProcess = new("Launched '{0}' with arguments '{1}': process id {2}", "🚀", MessageSeverity.Verbose, s_id++);
Expand Down
14 changes: 2 additions & 12 deletions test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,6 @@ public async Task Aspire()
Assert.Equal(1, App.Process.Output.Count(line => line.StartsWith("dotnet watch ⌚ Launching browser: ")));
App.Process.ClearOutput();

#if TODO // needs Roslyn update
// rude edit with build error:
UpdateSourceFile(
serviceSourcePath,
Expand Down Expand Up @@ -441,16 +440,6 @@ public async Task Aspire()
App.AssertOutputContains("error CS0246: The type or namespace name 'WeatherForecast' could not be found");
App.Process.ClearOutput();

// TODO: remove
Log("dotnet build-server shutdown");
var workloadInstallCommandSpec = new DotnetCommand(Logger, ["build-server", "shutdown"])
{
WorkingDirectory = testAsset.Path,
};

var result = workloadInstallCommandSpec.Execute();
Assert.Equal(0, result.ExitCode);

// fix build error:
UpdateSourceFile(
serviceSourcePath,
Expand All @@ -459,8 +448,9 @@ public async Task Aspire()
await App.AssertOutputLineStartsWith("dotnet watch ⌚ [WatchAspire.ApiService (net9.0)] Capabilities");

App.AssertOutputContains("dotnet watch ⌚ Build succeeded.");
App.AssertOutputContains("dotnet watch 🔥 Project baselines updated.");
App.AssertOutputContains($"dotnet watch ⭐ Starting project: {serviceProjectPath}");
#endif

App.SendControlC();

await App.AssertOutputLineStartsWith("dotnet watch 🛑 Shutdown requested. Press Ctrl+C again to force exit.");
Expand Down

0 comments on commit 84aa7ea

Please sign in to comment.