Skip to content

Commit 6b9b8e7

Browse files
Fix restore for 'dotnet run app.cs' in IDE (#78990)
Co-authored-by: Jason Malinowski <[email protected]>
1 parent 933e73a commit 6b9b8e7

File tree

3 files changed

+44
-28
lines changed

3 files changed

+44
-28
lines changed

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/DotnetCliHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private async Task<string> GetDotnetSdkFolderFromDotnetExecutableAsync(string pr
7070

7171
public Process Run(string[] arguments, string? workingDirectory, bool shouldLocalizeOutput, bool redirectStandardInput = false)
7272
{
73-
_logger.LogDebug($"Running dotnet CLI command at {_dotnetExecutablePath.Value} in directory {workingDirectory} with arguments {arguments}");
73+
_logger.LogDebug($"Running dotnet CLI command at {_dotnetExecutablePath.Value} in directory {workingDirectory} with arguments '{string.Join(' ', arguments)}'");
7474

7575
var startInfo = new ProcessStartInfo(_dotnetExecutablePath.Value)
7676
{

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Immutable;
66
using Microsoft.CodeAnalysis.Host;
77
using Microsoft.CodeAnalysis.LanguageServer.Handler.DebugConfiguration;
8+
using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.FileWatching;
89
using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.ProjectTelemetry;
910
using Microsoft.CodeAnalysis.MSBuild;
1011
using Microsoft.CodeAnalysis.ProjectSystem;
@@ -25,7 +26,8 @@ internal sealed class LoadedProject : IDisposable
2526

2627
private readonly ProjectSystemProject _projectSystemProject;
2728
private readonly ProjectSystemProjectOptionsProcessor _optionsProcessor;
28-
private readonly IFileChangeContext _fileChangeContext;
29+
private readonly IFileChangeContext _sourceFileChangeContext;
30+
private readonly IFileChangeContext _projectFileChangeContext;
2931
private readonly ProjectTargetFrameworkManager _targetFrameworkManager;
3032

3133
/// <summary>
@@ -53,22 +55,16 @@ public LoadedProject(ProjectSystemProject projectSystemProject, SolutionServices
5355
// TODO: we only should listen for add/removals here, but we can't specify such a filter now
5456
_projectDirectory = Path.GetDirectoryName(_projectFilePath)!;
5557

56-
_fileChangeContext = fileWatcher.CreateContext([new(_projectDirectory, [".cs", ".cshtml", ".razor"])]);
57-
_fileChangeContext.FileChanged += FileChangedContext_FileChanged;
58+
_sourceFileChangeContext = fileWatcher.CreateContext([new(_projectDirectory, [".cs", ".cshtml", ".razor"])]);
59+
_sourceFileChangeContext.FileChanged += SourceFileChangeContext_FileChanged;
5860

59-
// Start watching for file changes for the project file as well
60-
_fileChangeContext.EnqueueWatchingFile(_projectFilePath);
61+
_projectFileChangeContext = fileWatcher.CreateContext([]);
62+
_projectFileChangeContext.FileChanged += ProjectFileChangeContext_FileChanged;
63+
_projectFileChangeContext.EnqueueWatchingFile(_projectFilePath);
6164
}
6265

63-
private void FileChangedContext_FileChanged(object? sender, string filePath)
66+
private void SourceFileChangeContext_FileChanged(object? sender, string filePath)
6467
{
65-
// If the project file itself changed, we almost certainly need to reload the project.
66-
if (string.Equals(filePath, _projectFilePath, StringComparison.OrdinalIgnoreCase))
67-
{
68-
NeedsReload?.Invoke(this, EventArgs.Empty);
69-
return;
70-
}
71-
7268
var matchers = _mostRecentFileMatchers?.Value;
7369
if (matchers is null)
7470
{
@@ -92,6 +88,11 @@ private void FileChangedContext_FileChanged(object? sender, string filePath)
9288
}
9389
}
9490

91+
private void ProjectFileChangeContext_FileChanged(object? sender, string filePath)
92+
{
93+
NeedsReload?.Invoke(this, EventArgs.Empty);
94+
}
95+
9596
public event EventHandler? NeedsReload;
9697

9798
public string? GetTargetFramework()
@@ -105,7 +106,8 @@ private void FileChangedContext_FileChanged(object? sender, string filePath)
105106
/// </summary>
106107
public void Dispose()
107108
{
108-
_fileChangeContext.Dispose();
109+
_sourceFileChangeContext.Dispose();
110+
_projectFileChangeContext.Dispose();
109111
_optionsProcessor.Dispose();
110112
_projectSystemProject.RemoveFromWorkspace();
111113
}
@@ -221,7 +223,7 @@ public void Dispose()
221223
document => _projectSystemProject.RemoveDynamicSourceFile(document.FilePath),
222224
"Project {0} now has {1} dynamic file(s).");
223225

224-
WatchProjectAssetsFile(newProjectInfo, _fileChangeContext);
226+
WatchProjectAssetsFile(newProjectInfo);
225227

226228
var needsRestore = ProjectDependencyHelper.NeedsRestore(newProjectInfo, _mostRecentFileInfo, logger);
227229

@@ -272,7 +274,7 @@ void UpdateProjectSystemProjectCollection<T>(IEnumerable<T> loadedCollection, IE
272274
logger.LogTrace(logMessage, projectFullPathWithTargetFramework, newItems.Count);
273275
}
274276

275-
void WatchProjectAssetsFile(ProjectFileInfo currentProjectInfo, IFileChangeContext fileChangeContext)
277+
void WatchProjectAssetsFile(ProjectFileInfo currentProjectInfo)
276278
{
277279
if (_mostRecentFileInfo?.ProjectAssetsFilePath == currentProjectInfo.ProjectAssetsFilePath)
278280
{
@@ -282,14 +284,9 @@ void WatchProjectAssetsFile(ProjectFileInfo currentProjectInfo, IFileChangeConte
282284

283285
// Dispose of the last once since we're changing the file we're watching.
284286
_mostRecentProjectAssetsFileWatcher?.Dispose();
285-
286-
IWatchedFile? currentWatcher = null;
287-
if (currentProjectInfo.ProjectAssetsFilePath != null)
288-
{
289-
currentWatcher = fileChangeContext.EnqueueWatchingFile(currentProjectInfo.ProjectAssetsFilePath);
290-
}
291-
292-
_mostRecentProjectAssetsFileWatcher = currentWatcher;
287+
_mostRecentProjectAssetsFileWatcher = currentProjectInfo.ProjectAssetsFilePath is { } assetsFilePath
288+
? _projectFileChangeContext.EnqueueWatchingFile(assetsFilePath)
289+
: null;
293290
}
294291
}
295292

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Immutable;
66
using System.Composition;
77
using Microsoft.CodeAnalysis.Host.Mef;
8+
using Microsoft.Extensions.Logging;
89
using Roslyn.Utilities;
910

1011
namespace Microsoft.CodeAnalysis.LanguageServer.Handler;
@@ -17,14 +18,16 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler;
1718
[Method(MethodName)]
1819
[method: ImportingConstructor]
1920
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
20-
internal sealed class RestoreHandler(DotnetCliHelper dotnetCliHelper) : ILspServiceRequestHandler<RestoreParams, RestorePartialResult[]>
21+
internal sealed class RestoreHandler(DotnetCliHelper dotnetCliHelper, ILoggerFactory loggerFactory) : ILspServiceRequestHandler<RestoreParams, RestorePartialResult[]>
2122
{
2223
internal const string MethodName = "workspace/_roslyn_restore";
2324

2425
public bool MutatesSolutionState => false;
2526

2627
public bool RequiresLSPSolution => true;
2728

29+
private readonly ILogger<RestoreHandler> _logger = loggerFactory.CreateLogger<RestoreHandler>();
30+
2831
public async Task<RestorePartialResult[]> HandleRequestAsync(RestoreParams request, RequestContext context, CancellationToken cancellationToken)
2932
{
3033
Contract.ThrowIfNull(context.Solution);
@@ -35,18 +38,31 @@ public async Task<RestorePartialResult[]> HandleRequestAsync(RestoreParams reque
3538
var restorePaths = GetRestorePaths(request, context.Solution, context);
3639
if (restorePaths.IsEmpty)
3740
{
41+
_logger.LogDebug($"Restore was requested but no paths were provided.");
3842
progress.Report(new RestorePartialResult(LanguageServerResources.Restore, LanguageServerResources.Nothing_found_to_restore));
3943
return progress.GetValues() ?? [];
4044
}
4145

42-
await RestoreAsync(restorePaths, progress, cancellationToken);
46+
_logger.LogDebug($"Running restore on {restorePaths.Length} paths, starting with '{restorePaths.First()}'.");
47+
bool success = await RestoreAsync(restorePaths, progress, cancellationToken);
4348

4449
progress.Report(new RestorePartialResult(LanguageServerResources.Restore, $"{LanguageServerResources.Restore_complete}{Environment.NewLine}"));
50+
if (success)
51+
{
52+
_logger.LogDebug($"Restore completed successfully.");
53+
}
54+
else
55+
{
56+
_logger.LogError($"Restore completed with errors. See '.NET NuGet Restore' output window for more details.");
57+
}
58+
4559
return progress.GetValues() ?? [];
4660
}
4761

48-
private async Task RestoreAsync(ImmutableArray<string> pathsToRestore, BufferedProgress<RestorePartialResult> progress, CancellationToken cancellationToken)
62+
/// <returns>True if all restore invocations exited with code 0. Otherwise, false.</returns>
63+
private async Task<bool> RestoreAsync(ImmutableArray<string> pathsToRestore, BufferedProgress<RestorePartialResult> progress, CancellationToken cancellationToken)
4964
{
65+
bool success = true;
5066
foreach (var path in pathsToRestore)
5167
{
5268
var arguments = new string[] { "restore", path };
@@ -71,9 +87,12 @@ private async Task RestoreAsync(ImmutableArray<string> pathsToRestore, BufferedP
7187
if (process.ExitCode != 0)
7288
{
7389
ReportProgress(progress, stageName, string.Format(LanguageServerResources.Failed_to_run_restore_on_0, path));
90+
success = false;
7491
}
7592
}
7693

94+
return success;
95+
7796
static void ReportProgress(BufferedProgress<RestorePartialResult> progress, string stage, string? restoreOutput)
7897
{
7998
if (restoreOutput != null)

0 commit comments

Comments
 (0)