Skip to content

Simplify workspace initialization in the LSP server #78010

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer.Handler.DebugConfiguration;
using Microsoft.CodeAnalysis.LanguageServer.Services;
using Microsoft.CodeAnalysis.ProjectSystem;
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.Composition;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace;

[Export(typeof(LanguageServerWorkspaceFactory)), Shared]
internal sealed class LanguageServerWorkspaceFactory
{
private readonly ILogger _logger;
private readonly ImmutableArray<string> _solutionLevelAnalyzerPaths;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
Expand All @@ -28,12 +31,24 @@ public LanguageServerWorkspaceFactory(
[ImportMany] IEnumerable<Lazy<IDynamicFileInfoProvider, FileExtensionsMetadata>> dynamicFileInfoProviders,
ProjectTargetFrameworkManager projectTargetFrameworkManager,
ServerConfigurationFactory serverConfigurationFactory,
ExtensionAssemblyManager extensionManager,
ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger(nameof(LanguageServerWorkspaceFactory));

// Before we can create the workspace, let's figure out the solution-level analyzers; we'll pull in analyzers from our own binaries
// as well as anything coming from extensions.
_solutionLevelAnalyzerPaths = new DirectoryInfo(AppContext.BaseDirectory).GetFiles("*.dll")
.Where(f => f.Name.StartsWith("Microsoft.CodeAnalysis.", StringComparison.Ordinal) && !f.Name.Contains("LanguageServer", StringComparison.Ordinal))
.Select(f => f.FullName)
.Concat(extensionManager.ExtensionAssemblyPaths)
.ToImmutableArray();

// Create the workspace and set analyzer references for it
var workspace = new LanguageServerWorkspace(hostServicesProvider.HostServices);
workspace.SetCurrentSolution(s => s.WithAnalyzerReferences(CreateSolutionLevelAnalyzerReferencesForWorkspace(workspace)), WorkspaceChangeKind.SolutionChanged);
Workspace = workspace;

ProjectSystemProjectFactory = new ProjectSystemProjectFactory(
Workspace, fileChangeWatcher, static (_, _) => Task.CompletedTask, _ => { },
CancellationToken.None); // TODO: do we need to introduce a shutdown cancellation token for this?
Expand All @@ -54,17 +69,17 @@ public LanguageServerWorkspaceFactory(
public ProjectSystemHostInfo ProjectSystemHostInfo { get; }
public ProjectTargetFrameworkManager TargetFrameworkManager { get; }

public async Task InitializeSolutionLevelAnalyzersAsync(ImmutableArray<string> analyzerPaths)
public ImmutableArray<AnalyzerFileReference> CreateSolutionLevelAnalyzerReferencesForWorkspace(Workspace workspace)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RikkiGibson Now you could just MEF import this type and call this method and that'd give you your list of AnalyzerFileReferences that could be used for any workspace.

{
var references = new List<AnalyzerFileReference>();
var loaderProvider = Workspace.Services.GetRequiredService<IAnalyzerAssemblyLoaderProvider>();
var references = ImmutableArray.CreateBuilder<AnalyzerFileReference>();
var loaderProvider = workspace.Services.GetRequiredService<IAnalyzerAssemblyLoaderProvider>();

// Load all analyzers into a fresh shadow copied load context. In the future, if we want to support reloading
// of solution-level analyzer references, we should just need to listen for changes to those analyzer paths and
// then call back into this method to update the solution accordingly.
var analyzerLoader = loaderProvider.CreateNewShadowCopyLoader();

foreach (var analyzerPath in analyzerPaths)
foreach (var analyzerPath in _solutionLevelAnalyzerPaths)
{
if (File.Exists(analyzerPath))
{
Expand All @@ -77,9 +92,6 @@ public async Task InitializeSolutionLevelAnalyzersAsync(ImmutableArray<string> a
}
}

await ProjectSystemProjectFactory.ApplyChangeToWorkspaceAsync(w =>
{
w.SetCurrentSolution(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged);
});
return references.ToImmutableAndClear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,10 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation
var telemetryReporter = exportProvider.GetExports<ITelemetryReporter>().SingleOrDefault()?.Value;
RoslynLogger.Initialize(telemetryReporter, serverConfiguration.TelemetryLevel, serverConfiguration.SessionId);

// Create the workspace first, since right now the language server will assume there's at least one Workspace
// Create the workspace first, since right now the language server will assume there's at least one Workspace. This as a side effect creates the actual workspace
// object which is registered by the LspWorkspaceRegistrationEventListener.
var workspaceFactory = exportProvider.GetExportedValue<LanguageServerWorkspaceFactory>();

var analyzerPaths = new DirectoryInfo(AppContext.BaseDirectory).GetFiles("*.dll")
.Where(f => f.Name.StartsWith("Microsoft.CodeAnalysis.", StringComparison.Ordinal) && !f.Name.Contains("LanguageServer", StringComparison.Ordinal))
.Select(f => f.FullName)
.ToImmutableArray();

// Include analyzers from extension assemblies.
analyzerPaths = analyzerPaths.AddRange(extensionManager.ExtensionAssemblyPaths);

await workspaceFactory.InitializeSolutionLevelAnalyzersAsync(analyzerPaths);

var serviceBrokerFactory = exportProvider.GetExportedValue<ServiceBrokerFactory>();
StarredCompletionAssemblyHelper.InitializeInstance(serverConfiguration.StarredCompletionsPath, extensionManager, loggerFactory, serviceBrokerFactory);
// TODO: Remove, the path should match exactly. Workaround for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1830914.
Expand Down
Loading