Skip to content

Commit

Permalink
Fix rebuilding of projects when they are being restarted due to rude …
Browse files Browse the repository at this point in the history
…edits
  • Loading branch information
tmat committed Nov 10, 2024
1 parent b60190c commit 5c75a56
Show file tree
Hide file tree
Showing 23 changed files with 347 additions and 141 deletions.
4 changes: 4 additions & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@
<!-- Dependency from https://github.com/dotnet/arcade-services -->
<MicrosoftDotNetDarcLibVersion>1.1.0-beta.24367.3</MicrosoftDotNetDarcLibVersion>
</PropertyGroup>
<PropertyGroup>
<!-- Dependency from https://github.com/dotnet/aspire -->
<AspirePackageVersion>9.1.0-preview.1.24555.3</AspirePackageVersion>
</PropertyGroup>
<PropertyGroup>
<!-- Dependency from https://github.com/dotnet/winforms -->
<MicrosoftDotnetWinFormsProjectTemplatesPackageVersion>9.0.0-rc.2.24474.1</MicrosoftDotnetWinFormsProjectTemplatesPackageVersion>
Expand Down
9 changes: 4 additions & 5 deletions src/BuiltInTools/dotnet-watch/Aspire/AspireServiceFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ async ValueTask<string> IAspireServerEvents.StartProjectAsync(string dcpId, Proj

var projectOptions = GetProjectOptions(projectLaunchInfo);
var sessionId = Interlocked.Increment(ref _sessionIdDispenser).ToString(CultureInfo.InvariantCulture);
await StartProjectAsync(dcpId, sessionId, projectOptions, build: false, isRestart: false, cancellationToken);
await StartProjectAsync(dcpId, sessionId, projectOptions, isRestart: false, cancellationToken);
return sessionId;
}

public async ValueTask<RunningProject> StartProjectAsync(string dcpId, string sessionId, ProjectOptions projectOptions, bool build, bool isRestart, CancellationToken cancellationToken)
public async ValueTask<RunningProject> StartProjectAsync(string dcpId, string sessionId, ProjectOptions projectOptions, bool isRestart, CancellationToken cancellationToken)
{
ObjectDisposedException.ThrowIf(_isDisposed, this);

Expand All @@ -125,9 +125,8 @@ public async ValueTask<RunningProject> StartProjectAsync(string dcpId, string se
var writeResult = outputChannel.Writer.TryWrite(line);
Debug.Assert(writeResult);
},
restartOperation: (build, cancellationToken) =>
StartProjectAsync(dcpId, sessionId, projectOptions, build, isRestart: true, cancellationToken),
build: build,
restartOperation: cancellationToken =>
StartProjectAsync(dcpId, sessionId, projectOptions, isRestart: true, cancellationToken),
cancellationToken);

if (runningProject == null)
Expand Down
6 changes: 4 additions & 2 deletions src/BuiltInTools/dotnet-watch/FileItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ namespace Microsoft.DotNet.Watcher
{
internal readonly record struct FileItem
{
public string FilePath { get; init; }
public required string FilePath { get; init; }

/// <summary>
/// List of all projects that contain this file (does not contain duplicates).
/// Empty if <see cref="Change"/> is <see cref="ChangeKind.Add"/> and the
/// item has not been assigned to a project yet.
/// </summary>
public List<string> ContainingProjectPaths { get; init; }
public required List<string> ContainingProjectPaths { get; init; }

public string? StaticWebAssetPath { get; init; }

Expand Down
3 changes: 2 additions & 1 deletion src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Diagnostics;
using Microsoft.DotNet.Watcher.Internal;
using Microsoft.Extensions.Tools.Internal;

namespace Microsoft.DotNet.Watcher.Tools
{
Expand Down Expand Up @@ -87,7 +88,7 @@ private async ValueTask<EvaluationResult> CreateEvaluationResult(CancellationTok
await FileWatcher.WaitForFileChangeAsync(
rootProjectFileSetFactory.RootProjectFile,
context.Reporter,
startedWatching: () => context.Reporter.Warn("Fix the error to continue or press Ctrl+C to exit."),
startedWatching: () => context.Reporter.Report(MessageDescriptor.FixBuildError),
cancellationToken);
}
}
Expand Down
14 changes: 7 additions & 7 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(IReadOnlySet<ProjectId> projectsToBeRebuilt, CancellationToken cancellationToken)
public ValueTask RestartSessionAsync(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 @@ -84,7 +84,7 @@ public ValueTask RestartSessionAsync(IReadOnlySet<ProjectId> projectsToBeRebuilt

lock (_runningProjectsAndUpdatesGuard)
{
_previousUpdates = _previousUpdates.RemoveAll(update => projectsToBeRebuilt.Contains(update.ProjectId));
_previousUpdates = _previousUpdates.RemoveAll(update => projectsToBeRebuilt.ContainsKey(update.ProjectId));
}

_hotReloadService.EndSession();
Expand Down Expand Up @@ -276,7 +276,7 @@ private static void PrepareCompilations(Solution solution, string projectPath, C
}
}

public async ValueTask<(IReadOnlySet<ProjectId> projectsToBeRebuilt, IEnumerable<RunningProject> terminatedProjects)> HandleFileChangesAsync(
public async ValueTask<(ImmutableDictionary<ProjectId, string> projectsToRebuild, ImmutableArray<RunningProject> terminatedProjects)> HandleFileChangesAsync(
Func<IEnumerable<Project>, CancellationToken, Task> restartPrompt,
CancellationToken cancellationToken)
{
Expand All @@ -292,14 +292,14 @@ private static void PrepareCompilations(Solution solution, string projectPath, C
{
// If Hot Reload is blocked (due to compilation error) we ignore the current
// changes and await the next file change.
return (ImmutableHashSet<ProjectId>.Empty, []);
return (ImmutableDictionary<ProjectId, string>.Empty, []);
}

if (updates.Status == ModuleUpdateStatus.RestartRequired)
{
if (!anyProcessNeedsRestart)
{
return (ImmutableHashSet<ProjectId>.Empty, []);
return (ImmutableDictionary<ProjectId, string>.Empty, []);
}

await restartPrompt.Invoke(updates.ProjectsToRestart, cancellationToken);
Expand All @@ -308,7 +308,7 @@ private static void PrepareCompilations(Solution solution, string projectPath, C
// except for the root process, which will terminate later on.
var terminatedProjects = await TerminateNonRootProcessesAsync(updates.ProjectsToRestart.Select(p => p.FilePath!), cancellationToken);

return (updates.ProjectsToRebuild.Select(p => p.Id).ToHashSet(), terminatedProjects);
return (updates.ProjectsToRebuild.ToImmutableDictionary(keySelector: p => p.Id, elementSelector: p => p.FilePath!), terminatedProjects);
}

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

return (ImmutableHashSet<ProjectId>.Empty, []);
return (ImmutableDictionary<ProjectId, string>.Empty, []);
}

private async ValueTask DisplayResultsAsync(WatchHotReloadService.Updates updates, CancellationToken cancellationToken)
Expand Down
5 changes: 1 addition & 4 deletions src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public EnvironmentOptions EnvironmentOptions
CancellationTokenSource processTerminationSource,
Action<OutputLine>? onOutput,
RestartOperation restartOperation,
bool build,
CancellationToken cancellationToken)
{
var projectNode = projectMap.TryGetProjectNode(projectOptions.ProjectPath, projectOptions.TargetFramework);
Expand All @@ -58,9 +57,7 @@ public EnvironmentOptions EnvironmentOptions
Executable = EnvironmentOptions.MuxerPath,
WorkingDirectory = projectOptions.WorkingDirectory,
OnOutput = onOutput,
Arguments = build || !CommandLineOptions.IsCodeExecutionCommand(projectOptions.Command)
? [projectOptions.Command, .. projectOptions.CommandArguments]
: [projectOptions.Command, "--no-build", .. projectOptions.CommandArguments]
Arguments = [projectOptions.Command, "--no-build", .. projectOptions.CommandArguments]
};

var environmentBuilder = EnvironmentVariablesBuilder.FromCurrentEnvironment();
Expand Down
1 change: 1 addition & 0 deletions src/BuiltInTools/dotnet-watch/HotReload/ProjectNodeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal readonly struct ProjectNodeMap(ProjectGraph graph, IReporter reporter)
{
public readonly ProjectGraph Graph = graph;

// full path of proj file to list of nodes representing all target frameworks of the project:
public readonly IReadOnlyDictionary<string, IReadOnlyList<ProjectGraphNode>> Map =
graph.ProjectNodes.GroupBy(n => n.ProjectInstance.FullPath).ToDictionary(
keySelector: static g => g.Key,
Expand Down
3 changes: 1 addition & 2 deletions src/BuiltInTools/dotnet-watch/HotReload/RunningProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Microsoft.DotNet.Watcher.Tools
{
internal delegate ValueTask<RunningProject> RestartOperation(bool build, CancellationToken cancellationToken);
internal delegate ValueTask<RunningProject> RestartOperation(CancellationToken cancellationToken);

internal sealed class RunningProject(
ProjectGraphNode projectNode,
Expand Down Expand Up @@ -68,7 +68,6 @@ public void Dispose()
public async ValueTask WaitForProcessRunningAsync(CancellationToken cancellationToken)
{
await DeltaApplier.WaitForProcessRunningAsync(cancellationToken);
Reporter.Report(MessageDescriptor.BuildCompleted);
}
}
}
Loading

0 comments on commit 5c75a56

Please sign in to comment.