2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
// See the LICENSE file in the project root for more information.
4
4
5
- using System . Collections . Concurrent ;
6
5
using System . Collections . Immutable ;
7
6
using System . Diagnostics ;
8
- using System . IO ;
9
- using System . Threading ;
10
- using System . Threading . Tasks ;
11
7
using Microsoft . CodeAnalysis . Collections ;
12
- using Microsoft . CodeAnalysis . CSharp ;
13
8
using Microsoft . CodeAnalysis . Host ;
14
9
using Microsoft . CodeAnalysis . LanguageServer . Handler . DebugConfiguration ;
15
10
using Microsoft . CodeAnalysis . LanguageServer . HostWorkspace . ProjectTelemetry ;
21
16
using Microsoft . CodeAnalysis . Shared . Extensions ;
22
17
using Microsoft . CodeAnalysis . Shared . TestHooks ;
23
18
using Microsoft . CodeAnalysis . Shared . Utilities ;
24
- using Microsoft . CodeAnalysis . Text ;
25
19
using Microsoft . CodeAnalysis . Threading ;
26
20
using Microsoft . CodeAnalysis . Workspaces . ProjectSystem ;
27
21
using Microsoft . Extensions . Logging ;
@@ -35,7 +29,6 @@ internal abstract class LanguageServerProjectLoader
35
29
{
36
30
private readonly AsyncBatchingWorkQueue < ProjectToLoad > _projectsToReload ;
37
31
38
- protected readonly ProjectSystemProjectFactory ProjectFactory ;
39
32
private readonly ProjectTargetFrameworkManager _targetFrameworkManager ;
40
33
private readonly ProjectSystemHostInfo _projectSystemHostInfo ;
41
34
private readonly IFileChangeWatcher _fileChangeWatcher ;
@@ -76,7 +69,7 @@ private ProjectLoadState() { }
76
69
/// ID of the project which LSP uses to fulfill requests until the first design-time build is complete.
77
70
/// The project with this ID is removed from the workspace when unloading or when transitioning to <see cref="LoadedTargets"/> state.
78
71
/// </param>
79
- public sealed record Primordial ( ProjectId PrimordialProjectId ) : ProjectLoadState ;
72
+ public sealed record Primordial ( ProjectSystemProjectFactory Factory , ProjectId PrimordialProjectId ) : ProjectLoadState ;
80
73
81
74
/// <summary>
82
75
/// Represents a project for which we have loaded zero or more targets.
@@ -89,7 +82,6 @@ public sealed record LoadedTargets(ImmutableArray<LoadedProject> LoadedProjectTa
89
82
}
90
83
91
84
protected LanguageServerProjectLoader (
92
- ProjectSystemProjectFactory projectFactory ,
93
85
ProjectTargetFrameworkManager targetFrameworkManager ,
94
86
ProjectSystemHostInfo projectSystemHostInfo ,
95
87
IFileChangeWatcher fileChangeWatcher ,
@@ -100,7 +92,6 @@ protected LanguageServerProjectLoader(
100
92
ServerConfigurationFactory serverConfigurationFactory ,
101
93
IBinLogPathProvider binLogPathProvider )
102
94
{
103
- ProjectFactory = projectFactory ;
104
95
_targetFrameworkManager = targetFrameworkManager ;
105
96
_projectSystemHostInfo = projectSystemHostInfo ;
106
97
_fileChangeWatcher = fileChangeWatcher ;
@@ -109,7 +100,6 @@ protected LanguageServerProjectLoader(
109
100
_logger = loggerFactory . CreateLogger ( nameof ( LanguageServerProjectLoader ) ) ;
110
101
_projectLoadTelemetryReporter = projectLoadTelemetry ;
111
102
_binLogPathProvider = binLogPathProvider ;
112
- var workspace = projectFactory . Workspace ;
113
103
var razorDesignTimePath = serverConfigurationFactory . ServerConfiguration ? . RazorDesignTimePath ;
114
104
115
105
AdditionalProperties = razorDesignTimePath is null
@@ -180,7 +170,7 @@ private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList<ProjectToLoad
180
170
}
181
171
}
182
172
183
- protected sealed record RemoteProjectLoadResult ( RemoteProjectFile ProjectFile , bool HasAllInformation , BuildHostProcessKind Preferred , BuildHostProcessKind Actual ) ;
173
+ protected sealed record RemoteProjectLoadResult ( RemoteProjectFile ProjectFile , ProjectSystemProjectFactory ProjectFactory , bool HasAllInformation , BuildHostProcessKind Preferred , BuildHostProcessKind Actual ) ;
184
174
185
175
/// <summary>Loads a project in the MSBuild host.</summary>
186
176
/// <remarks>Caller needs to catch exceptions to avoid bringing down the project loader queue.</remarks>
@@ -215,7 +205,7 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
215
205
return false ;
216
206
}
217
207
218
- ( RemoteProjectFile remoteProjectFile , bool hasAllInformation , BuildHostProcessKind preferredBuildHostKind , BuildHostProcessKind actualBuildHostKind ) = remoteProjectLoadResult ;
208
+ ( RemoteProjectFile remoteProjectFile , ProjectSystemProjectFactory projectFactory , bool hasAllInformation , BuildHostProcessKind preferredBuildHostKind , BuildHostProcessKind actualBuildHostKind ) = remoteProjectLoadResult ;
219
209
if ( preferredBuildHostKind != actualBuildHostKind )
220
210
preferredBuildHostKindThatWeDidNotGet = preferredBuildHostKind ;
221
211
@@ -232,7 +222,7 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
232
222
// The out-of-proc build host supports more languages than we may actually have Workspace binaries for, so ensure we can actually process that
233
223
// language in-process.
234
224
var projectLanguage = loadedProjectInfos . FirstOrDefault ( ) ? . Language ;
235
- if ( projectLanguage != null && ProjectFactory . Workspace . Services . GetLanguageService < ICommandLineParserService > ( projectLanguage ) == null )
225
+ if ( projectLanguage != null && projectFactory . Workspace . Services . GetLanguageService < ICommandLineParserService > ( projectLanguage ) == null )
236
226
{
237
227
return false ;
238
228
}
@@ -252,7 +242,7 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
252
242
var newProjectTargetsBuilder = ArrayBuilder < LoadedProject > . GetInstance ( loadedProjectInfos . Length ) ;
253
243
foreach ( var loadedProjectInfo in loadedProjectInfos )
254
244
{
255
- var ( target , targetAlreadyExists ) = await GetOrCreateProjectTargetAsync ( previousProjectTargets , loadedProjectInfo ) ;
245
+ var ( target , targetAlreadyExists ) = await GetOrCreateProjectTargetAsync ( previousProjectTargets , projectFactory , loadedProjectInfo ) ;
256
246
newProjectTargetsBuilder . Add ( target ) ;
257
247
258
248
var ( targetTelemetryInfo , targetNeedsRestore ) = await target . UpdateWithNewProjectInfoAsync ( loadedProjectInfo , hasAllInformation , _logger ) ;
@@ -278,13 +268,13 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
278
268
await _projectLoadTelemetryReporter . ReportProjectLoadTelemetryAsync ( telemetryInfos , projectToLoad , cancellationToken ) ;
279
269
}
280
270
281
- if ( currentLoadState is ProjectLoadState . Primordial ( var projectId ) )
271
+ if ( currentLoadState is ProjectLoadState . Primordial ( var primordialProjectFactory , var projectId ) )
282
272
{
283
273
// Remove the primordial project now that the design-time build pass is finished. This ensures that
284
274
// we have the new project in place before we remove the primordial project; otherwise for
285
275
// Miscellaneous Files we could have a case where we'd get another request to create a project
286
276
// for the project we're currently processing.
287
- await ProjectFactory . ApplyChangeToWorkspaceAsync ( workspace => workspace . OnProjectRemoved ( projectId ) , cancellationToken ) ;
277
+ await primordialProjectFactory . ApplyChangeToWorkspaceAsync ( workspace => workspace . OnProjectRemoved ( projectId ) , cancellationToken ) ;
288
278
}
289
279
290
280
_loadedProjects [ projectPath ] = new ProjectLoadState . LoadedTargets ( newProjectTargets ) ;
@@ -312,9 +302,9 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
312
302
return false ;
313
303
}
314
304
315
- async Task < ( LoadedProject , bool alreadyExists ) > GetOrCreateProjectTargetAsync ( ImmutableArray < LoadedProject > previousProjectTargets , ProjectFileInfo loadedProjectInfo )
305
+ async Task < ( LoadedProject , bool alreadyExists ) > GetOrCreateProjectTargetAsync ( ImmutableArray < LoadedProject > previousProjectTargets , ProjectSystemProjectFactory projectFactory , ProjectFileInfo loadedProjectInfo )
316
306
{
317
- var existingProject = previousProjectTargets . FirstOrDefault ( p => p . GetTargetFramework ( ) == loadedProjectInfo . TargetFramework ) ;
307
+ var existingProject = previousProjectTargets . FirstOrDefault ( p => p . GetTargetFramework ( ) == loadedProjectInfo . TargetFramework && p . ProjectFactory == projectFactory ) ;
318
308
if ( existingProject != null )
319
309
{
320
310
return ( existingProject , alreadyExists : true ) ;
@@ -330,13 +320,13 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
330
320
CompilationOutputAssemblyFilePath = loadedProjectInfo . IntermediateOutputFilePath ,
331
321
} ;
332
322
333
- var projectSystemProject = await ProjectFactory . CreateAndAddToWorkspaceAsync (
323
+ var projectSystemProject = await projectFactory . CreateAndAddToWorkspaceAsync (
334
324
projectSystemName ,
335
325
loadedProjectInfo . Language ,
336
326
projectCreationInfo ,
337
327
_projectSystemHostInfo ) ;
338
328
339
- var loadedProject = new LoadedProject ( projectSystemProject , ProjectFactory . Workspace . Services . SolutionServices , _fileChangeWatcher , _targetFrameworkManager ) ;
329
+ var loadedProject = new LoadedProject ( projectSystemProject , projectFactory , _fileChangeWatcher , _targetFrameworkManager ) ;
340
330
loadedProject . NeedsReload += ( _ , _ ) => _projectsToReload . AddWork ( projectToLoad with { ReportTelemetry = false } ) ;
341
331
return ( loadedProject , alreadyExists : false ) ;
342
332
}
@@ -364,14 +354,22 @@ async Task LogDiagnosticsAsync(ImmutableArray<DiagnosticLogItem> diagnosticLogIt
364
354
}
365
355
}
366
356
357
+ protected async ValueTask < bool > IsProjectLoadedAsync ( string projectPath , CancellationToken cancellationToken )
358
+ {
359
+ using ( await _gate . DisposableWaitAsync ( cancellationToken ) )
360
+ {
361
+ return _loadedProjects . ContainsKey ( projectPath ) ;
362
+ }
363
+ }
364
+
367
365
/// <summary>
368
366
/// Begins loading a project with an associated primordial project. Must not be called for a project which has already begun loading.
369
367
/// </summary>
370
368
/// <param name="doDesignTimeBuild">
371
369
/// If <see langword="true"/>, initiates a design-time build now, and starts file watchers to repeat the design-time build on relevant changes.
372
370
/// If <see langword="false"/>, only tracks the primordial project.
373
371
/// </param>
374
- protected async ValueTask BeginLoadingProjectWithPrimordialAsync ( string projectPath , ProjectId primordialProjectId , bool doDesignTimeBuild )
372
+ protected async ValueTask BeginLoadingProjectWithPrimordialAsync ( string projectPath , ProjectSystemProjectFactory primordialProjectFactory , ProjectId primordialProjectId , bool doDesignTimeBuild )
375
373
{
376
374
using ( await _gate . DisposableWaitAsync ( CancellationToken . None ) )
377
375
{
@@ -383,7 +381,7 @@ protected async ValueTask BeginLoadingProjectWithPrimordialAsync(string projectP
383
381
Contract . Fail ( $ "Cannot begin loading project '{ projectPath } ' because it has already begun loading.") ;
384
382
}
385
383
386
- _loadedProjects . Add ( projectPath , new ProjectLoadState . Primordial ( primordialProjectId ) ) ;
384
+ _loadedProjects . Add ( projectPath , new ProjectLoadState . Primordial ( primordialProjectFactory , primordialProjectId ) ) ;
387
385
if ( doDesignTimeBuild )
388
386
{
389
387
_projectsToReload . AddWork ( new ProjectToLoad ( projectPath , ProjectGuid : null , ReportTelemetry : true ) ) ;
@@ -422,9 +420,9 @@ protected async ValueTask UnloadProjectAsync(string projectPath)
422
420
return ;
423
421
}
424
422
425
- if ( loadState is ProjectLoadState . Primordial ( var projectId ) )
423
+ if ( loadState is ProjectLoadState . Primordial ( var projectFactory , var projectId ) )
426
424
{
427
- await ProjectFactory . ApplyChangeToWorkspaceAsync ( workspace => workspace . OnProjectRemoved ( projectId ) ) ;
425
+ await projectFactory . ApplyChangeToWorkspaceAsync ( workspace => workspace . OnProjectRemoved ( projectId ) ) ;
428
426
}
429
427
else if ( loadState is ProjectLoadState . LoadedTargets ( var existingProjects ) )
430
428
{
0 commit comments