-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #70588 from dibarbet/nuget_restore
Add restore support to the language server
- Loading branch information
Showing
18 changed files
with
668 additions
and
0 deletions.
There are no files selected for viewing
53 changes: 53 additions & 0 deletions
53
...t.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestorableProjectsHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Composition; | ||
using Microsoft.CodeAnalysis.Host.Mef; | ||
using Microsoft.CodeAnalysis.LanguageServer.Handler; | ||
using Microsoft.CodeAnalysis.LanguageServer.Handler.DebugConfiguration; | ||
using Microsoft.CodeAnalysis.PooledObjects; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.LanguageServer; | ||
|
||
/// <summary> | ||
/// Handler that allows the client to retrieve a set of restorable projects. | ||
/// Used to populate a list of projects that can be restored. | ||
/// </summary> | ||
[ExportCSharpVisualBasicStatelessLspService(typeof(RestorableProjectsHandler)), Shared] | ||
[Method(MethodName)] | ||
[method: ImportingConstructor] | ||
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
internal sealed class RestorableProjectsHandler(ProjectTargetFrameworkManager projectTargetFrameworkManager) : ILspServiceRequestHandler<string[]> | ||
{ | ||
internal const string MethodName = "workspace/_roslyn_restorableProjects"; | ||
|
||
public bool MutatesSolutionState => false; | ||
|
||
public bool RequiresLSPSolution => true; | ||
|
||
public Task<string[]> HandleRequestAsync(RequestContext context, CancellationToken cancellationToken) | ||
{ | ||
Contract.ThrowIfNull(context.Solution); | ||
|
||
using var _ = ArrayBuilder<string>.GetInstance(out var projectsBuilder); | ||
foreach (var project in context.Solution.Projects) | ||
{ | ||
// To restore via the dotnet CLI, we must have a file path and it must be a .NET core project. | ||
if (project.FilePath != null && projectTargetFrameworkManager.IsDotnetCoreProject(project.Id)) | ||
{ | ||
projectsBuilder.Add(project.FilePath); | ||
} | ||
} | ||
|
||
// We may have multiple projects with the same file path in multi-targeting scenarios. | ||
// They'll all get restored together so we only want one result per project file. | ||
projectsBuilder.RemoveDuplicates(); | ||
|
||
// Ensure the client gets a consistent ordering. | ||
projectsBuilder.Sort(StringComparer.OrdinalIgnoreCase); | ||
|
||
return Task.FromResult(projectsBuilder.ToArray()); | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
...er/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Collections.Immutable; | ||
using System.Composition; | ||
using Microsoft.CodeAnalysis.Host.Mef; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.LanguageServer.Handler; | ||
|
||
/// <summary> | ||
/// Given an input project (or none), runs restore on the project and streams the output | ||
/// back to the client to display. | ||
/// </summary> | ||
[ExportCSharpVisualBasicStatelessLspService(typeof(RestoreHandler)), Shared] | ||
[Method(MethodName)] | ||
[method: ImportingConstructor] | ||
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
internal sealed class RestoreHandler(DotnetCliHelper dotnetCliHelper) : ILspServiceRequestHandler<RestoreParams, RestorePartialResult[]> | ||
{ | ||
internal const string MethodName = "workspace/_roslyn_restore"; | ||
|
||
public bool MutatesSolutionState => false; | ||
|
||
public bool RequiresLSPSolution => true; | ||
|
||
public async Task<RestorePartialResult[]> HandleRequestAsync(RestoreParams request, RequestContext context, CancellationToken cancellationToken) | ||
{ | ||
Contract.ThrowIfNull(context.Solution); | ||
using var progress = BufferedProgress.Create(request.PartialResultToken); | ||
|
||
progress.Report(new RestorePartialResult(LanguageServerResources.Restore, LanguageServerResources.Restore_started)); | ||
|
||
var restorePaths = GetRestorePaths(request, context.Solution, context); | ||
if (restorePaths.IsEmpty) | ||
{ | ||
progress.Report(new RestorePartialResult(LanguageServerResources.Restore, LanguageServerResources.Nothing_found_to_restore)); | ||
return progress.GetValues() ?? []; | ||
} | ||
|
||
await RestoreAsync(restorePaths, progress, cancellationToken); | ||
|
||
progress.Report(new RestorePartialResult(LanguageServerResources.Restore, $"{LanguageServerResources.Restore_complete}{Environment.NewLine}")); | ||
return progress.GetValues() ?? []; | ||
} | ||
|
||
private async Task RestoreAsync(ImmutableArray<string> pathsToRestore, BufferedProgress<RestorePartialResult> progress, CancellationToken cancellationToken) | ||
{ | ||
foreach (var path in pathsToRestore) | ||
{ | ||
var arguments = $"restore \"{path}\""; | ||
var workingDirectory = Path.GetDirectoryName(path); | ||
var stageName = string.Format(LanguageServerResources.Restoring_0, Path.GetFileName(path)); | ||
ReportProgress(progress, stageName, string.Format(LanguageServerResources.Running_dotnet_restore_on_0, path)); | ||
|
||
var process = dotnetCliHelper.Run(arguments, workingDirectory, shouldLocalizeOutput: true); | ||
|
||
process.OutputDataReceived += (sender, args) => ReportProgress(progress, stageName, args.Data); | ||
process.ErrorDataReceived += (sender, args) => ReportProgress(progress, stageName, args.Data); | ||
process.BeginOutputReadLine(); | ||
process.BeginErrorReadLine(); | ||
|
||
await process.WaitForExitAsync(cancellationToken); | ||
|
||
if (process.ExitCode != 0) | ||
{ | ||
throw new InvalidOperationException(LanguageServerResources.Failed_to_run_restore_see_output_for_details); | ||
} | ||
} | ||
|
||
static void ReportProgress(BufferedProgress<RestorePartialResult> progress, string stage, string? restoreOutput) | ||
{ | ||
if (restoreOutput != null) | ||
{ | ||
progress.Report(new RestorePartialResult(stage, restoreOutput)); | ||
} | ||
} | ||
} | ||
|
||
private static ImmutableArray<string> GetRestorePaths(RestoreParams request, Solution solution, RequestContext context) | ||
{ | ||
if (request.ProjectFilePath != null) | ||
{ | ||
return ImmutableArray.Create(request.ProjectFilePath); | ||
} | ||
|
||
// No file paths were specified - this means we should restore all projects in the solution. | ||
// If there is a valid solution path, use that as the restore path. | ||
if (solution.FilePath != null) | ||
{ | ||
return ImmutableArray.Create(solution.FilePath); | ||
} | ||
|
||
// We don't have an addressable solution, so lets find all addressable projects. | ||
// We can only restore projects with file paths as we are using the dotnet CLI to address them. | ||
// We also need to remove duplicates as in multi targeting scenarios there will be multiple projects with the same file path. | ||
var projects = solution.Projects | ||
.Select(p => p.FilePath) | ||
.WhereNotNull() | ||
.Distinct() | ||
.ToImmutableArray(); | ||
|
||
context.TraceInformation($"Found {projects.Length} restorable projects from {solution.Projects.Count()} projects in solution"); | ||
return projects; | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...ver/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreParams.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Runtime.Serialization; | ||
using Microsoft.VisualStudio.LanguageServer.Protocol; | ||
using Newtonsoft.Json; | ||
|
||
namespace Microsoft.CodeAnalysis.LanguageServer.Handler; | ||
|
||
[DataContract] | ||
internal sealed record RestoreParams( | ||
[property: DataMember(Name = "projectFilePath"), JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? ProjectFilePath | ||
) : IPartialResultParams<RestorePartialResult> | ||
{ | ||
[DataMember(Name = Methods.PartialResultTokenName)] | ||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] | ||
public IProgress<RestorePartialResult>? PartialResultToken { get; set; } | ||
} |
13 changes: 13 additions & 0 deletions
13
...rosoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestorePartialResult.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Runtime.Serialization; | ||
|
||
namespace Microsoft.CodeAnalysis.LanguageServer.Handler; | ||
|
||
[DataContract] | ||
internal sealed record RestorePartialResult( | ||
[property: DataMember(Name = "stage")] string Stage, | ||
[property: DataMember(Name = "message")] string Message | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
...s/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.cs.xlf
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
35 changes: 35 additions & 0 deletions
35
...s/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.de.xlf
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.