Skip to content

Commit 26bc809

Browse files
Implement a new brokered service for passing project information
This defines a new brokered service that is pretty similar to IWorkspaceProjectContext, but more friendly for cross-process communication. It's currently implemented atop IWorkspaceProjectContext so that way the behavior is identical otherwise, but hopefully the project system can move to consuming this one soon enough and we can flatten the implementation.
1 parent 9fee6f5 commit 26bc809

File tree

12 files changed

+324
-0
lines changed

12 files changed

+324
-0
lines changed

eng/targets/Services.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@
4848
-->
4949
<ItemGroup>
5050
<InProcService Include="Microsoft.VisualStudio.LanguageServices.SolutionAssetProvider" />
51+
<InProcService Include="Microsoft.VisualStudio.LanguageServices.WorkspaceProjectFactoryService" />
5152
</ItemGroup>
5253
</Project>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.Remote.ProjectSystem;
12+
using Roslyn.Utilities;
13+
14+
namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem.BrokeredService
15+
{
16+
internal sealed class WorkspaceProject : IWorkspaceProject
17+
{
18+
// For the sake of the in-proc implementation here, we're going to build this atop IWorkspaceProjectContext so semantics are preserved
19+
// for a few edge cases. Once the project system has moved onto this directly, we can flatten the implementations out.
20+
private readonly IWorkspaceProjectContext _project;
21+
22+
public WorkspaceProject(IWorkspaceProjectContext project)
23+
{
24+
_project = project;
25+
}
26+
27+
public ValueTask DisposeAsync()
28+
{
29+
_project.Dispose();
30+
return ValueTaskFactory.CompletedTask;
31+
}
32+
33+
public async Task AddAdditionalFilesAsync(IReadOnlyCollection<string> additionalFilePaths, CancellationToken cancellationToken)
34+
{
35+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
36+
37+
foreach (var additionalFilePath in additionalFilePaths)
38+
_project.AddAdditionalFile(additionalFilePath);
39+
}
40+
41+
public async Task RemoveAdditionalFilesAsync(IReadOnlyCollection<string> additionalFilePaths, CancellationToken cancellationToken)
42+
{
43+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
44+
45+
foreach (var additionalFilePath in additionalFilePaths)
46+
_project.RemoveAdditionalFile(additionalFilePath);
47+
}
48+
49+
public async Task AddAnalyzerConfigFilesAsync(IReadOnlyCollection<string> analyzerConfigPaths, CancellationToken cancellationToken)
50+
{
51+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
52+
53+
foreach (var analyzerConfigPath in analyzerConfigPaths)
54+
_project.AddAnalyzerConfigFile(analyzerConfigPath);
55+
}
56+
public async Task RemoveAnalyzerConfigFilesAsync(IReadOnlyCollection<string> analyzerConfigPaths, CancellationToken cancellationToken)
57+
{
58+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
59+
60+
foreach (var analyzerConfigPath in analyzerConfigPaths)
61+
_project.RemoveAnalyzerConfigFile(analyzerConfigPath);
62+
}
63+
64+
public async Task AddAnalyzerReferencesAsync(IReadOnlyCollection<string> analyzerPaths, CancellationToken cancellationToken)
65+
{
66+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
67+
68+
foreach (var analyzerPath in analyzerPaths)
69+
_project.AddAnalyzerReference(analyzerPath);
70+
}
71+
72+
public async Task RemoveAnalyzerReferencesAsync(IReadOnlyCollection<string> analyzerPaths, CancellationToken cancellationToken)
73+
{
74+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
75+
76+
foreach (var analyzerPath in analyzerPaths)
77+
_project.RemoveAnalyzerReference(analyzerPath);
78+
}
79+
80+
public async Task AddMetadataReferencesAsync(IReadOnlyCollection<MetadataReferenceInfo> metadataReferences, CancellationToken cancellationToken)
81+
{
82+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
83+
84+
foreach (var metadataReference in metadataReferences)
85+
{
86+
_project.AddMetadataReference(
87+
metadataReference.FilePath,
88+
new MetadataReferenceProperties(MetadataImageKind.Assembly, default, metadataReference.EmbedInteropTypes));
89+
}
90+
}
91+
92+
public async Task RemoveMetadataReferencesAsync(IReadOnlyCollection<MetadataReferenceInfo> metadataReferences, CancellationToken cancellationToken)
93+
{
94+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
95+
96+
// The existing IWorkspaceProjectContext API here is a bit odd in that it only looks at the file path, and trusts that there aren't two
97+
// references with the same path but different properties.
98+
foreach (var metadataReference in metadataReferences)
99+
_project.RemoveMetadataReference(metadataReference.FilePath);
100+
}
101+
102+
public async Task AddSourceFilesAsync(IReadOnlyList<SourceFileInfo> sourceFiles, CancellationToken cancellationToken)
103+
{
104+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
105+
106+
foreach (var sourceFile in sourceFiles)
107+
{
108+
_project.AddSourceFile(
109+
sourceFile.FilePath,
110+
folderNames: sourceFile.FolderNames);
111+
}
112+
}
113+
public async Task RemoveSourceFilesAsync(IReadOnlyCollection<string> sourceFiles, CancellationToken cancellationToken)
114+
{
115+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
116+
117+
foreach (var sourceFile in sourceFiles)
118+
_project.RemoveSourceFile(sourceFile);
119+
}
120+
121+
public async Task SetBuildSystemPropertiesAsync(IReadOnlyDictionary<string, string> properties, CancellationToken cancellationToken)
122+
{
123+
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);
124+
125+
foreach (var property in properties)
126+
_project.SetProperty(property.Key, property.Value);
127+
}
128+
129+
public Task SetCommandLineArgumentsAsync(IReadOnlyList<string> arguments, CancellationToken cancellationToken)
130+
{
131+
_project.SetOptions(arguments.ToImmutableArray());
132+
return Task.CompletedTask;
133+
}
134+
135+
public Task SetDisplayNameAsync(string displayName, CancellationToken cancellationToken)
136+
{
137+
_project.DisplayName = displayName;
138+
return Task.CompletedTask;
139+
}
140+
141+
public Task<IAsyncDisposable> StartBatchAsync(CancellationToken cancellationToken)
142+
{
143+
return Task.FromResult(_project.CreateBatchScope());
144+
}
145+
}
146+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.CodeAnalysis.Remote;
12+
using Microsoft.CodeAnalysis.Remote.ProjectSystem;
13+
using Microsoft.ServiceHub.Framework;
14+
15+
namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem.BrokeredService
16+
{
17+
internal class WorkspaceProjectFactoryService : IWorkspaceProjectFactoryService
18+
{
19+
public const string ServiceName = "WorkspaceProjectFactoryService";
20+
public static readonly ServiceDescriptor ServiceDescriptor = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName);
21+
22+
private readonly IWorkspaceProjectContextFactory _workspaceProjectContextFactory;
23+
24+
// For the sake of the in-proc implementation here, we're going to build this atop IWorkspaceProjectContext so semantics are preserved
25+
// for a few edge cases. Once the project system has moved onto this directly, we can flatten the implementations out.
26+
public WorkspaceProjectFactoryService(IWorkspaceProjectContextFactory workspaceProjectContextFactory)
27+
{
28+
_workspaceProjectContextFactory = workspaceProjectContextFactory;
29+
}
30+
31+
public async Task<IWorkspaceProject> CreateAndAddProjectAsync(WorkspaceProjectCreationInfo creationInfo, CancellationToken cancellationToken)
32+
{
33+
var project = await _workspaceProjectContextFactory.CreateProjectContextAsync(
34+
Guid.NewGuid(), // TODO: figure out some other side-channel way of communicating this
35+
creationInfo.DisplayName,
36+
creationInfo.Language,
37+
new EvaluationDataShim(creationInfo.BuildSystemProperties),
38+
hostObject: null, // TODO: figure out some other side-channel way of communicating this
39+
cancellationToken).ConfigureAwait(false);
40+
41+
return new WorkspaceProject(project);
42+
}
43+
44+
public Task<IReadOnlyCollection<string>> GetSupportedBuildSystemPropertiesAsync(CancellationToken cancellationToken)
45+
{
46+
return Task.FromResult((IReadOnlyCollection<string>)_workspaceProjectContextFactory.EvaluationItemNames);
47+
}
48+
49+
private sealed class EvaluationDataShim : EvaluationData
50+
{
51+
private readonly IReadOnlyDictionary<string, string> _buildSystemProperties;
52+
53+
public EvaluationDataShim(IReadOnlyDictionary<string, string> buildSystemProperties)
54+
{
55+
_buildSystemProperties = buildSystemProperties;
56+
}
57+
58+
public override string GetPropertyValue(string name)
59+
{
60+
return _buildSystemProperties.TryGetValue(name, out var value) ? value : "";
61+
}
62+
}
63+
}
64+
}

src/VisualStudio/Core/Def/ProjectSystem/CPS/IWorkspaceProjectContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Collections.Immutable;
1010
using System.Threading.Tasks;
1111
using Microsoft.CodeAnalysis;
12+
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
1213

1314
namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem
1415
{
@@ -72,6 +73,7 @@ internal interface IWorkspaceProjectContext : IDisposable
7273
void RemoveAnalyzerConfigFile(string filePath);
7374

7475
void StartBatch();
76+
IAsyncDisposable CreateBatchScope();
7577
ValueTask EndBatchAsync();
7678

7779
void ReorderSourceFiles(IEnumerable<string> filePaths);

src/VisualStudio/Core/Def/RoslynPackage.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@
3333
using Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource;
3434
using Microsoft.VisualStudio.LanguageServices.Implementation.UnusedReferences;
3535
using Microsoft.VisualStudio.LanguageServices.InheritanceMargin;
36+
using Microsoft.VisualStudio.LanguageServices.ProjectSystem;
37+
using Microsoft.VisualStudio.LanguageServices.ProjectSystem.BrokeredService;
3638
using Microsoft.VisualStudio.LanguageServices.StackTraceExplorer;
3739
using Microsoft.VisualStudio.Shell;
3840
using Microsoft.VisualStudio.Shell.Interop;
41+
using Microsoft.VisualStudio.Shell.ServiceBroker;
3942
using Microsoft.VisualStudio.TaskStatusCenter;
4043
using Microsoft.VisualStudio.TextManager.Interop;
4144
using Microsoft.VisualStudio.Threading;
@@ -170,6 +173,13 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
170173
// doc events and appropriately map files to/from it and other relevant workspaces (like the
171174
// metadata-as-source workspace).
172175
await this.ComponentModel.GetService<MiscellaneousFilesWorkspace>().InitializeAsync(this).ConfigureAwait(false);
176+
177+
// Proffer in-process service broker services
178+
var serviceBrokerContainer = await this.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>(this.JoinableTaskFactory).ConfigureAwait(false);
179+
180+
serviceBrokerContainer.Proffer(
181+
WorkspaceProjectFactoryService.ServiceDescriptor,
182+
(_, _, _, _) => ValueTaskFactory.FromResult<object?>(new WorkspaceProjectFactoryService(this.ComponentModel.GetService<IWorkspaceProjectContextFactory>())));
173183
}
174184

175185
private async Task LoadOptionPersistersAsync(IComponentModel componentModel, CancellationToken cancellationToken)

src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,5 +273,7 @@ public void AddAnalyzerConfigFile(string filePath)
273273

274274
public void RemoveAnalyzerConfigFile(string filePath)
275275
=> _visualStudioProject.RemoveAnalyzerConfigFile(filePath);
276+
277+
public IAsyncDisposable CreateBatchScope() => _visualStudioProject.CreateBatchScope();
276278
}
277279
}

src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,13 @@
7373
<InternalsVisibleTo Include="MonoDevelop.Ide.Tests" Key="$(MonoDevelopKey)" LoadsWithinVisualStudio="false" />
7474
<InternalsVisibleTo Include="MonoDevelop.Refactoring.Tests" Key="$(MonoDevelopKey)" LoadsWithinVisualStudio="false" />
7575
<!-- END MONODEVELOP -->
76+
77+
<!-- BEGIN PROJECT SYSTEM FOR VS CODE -->
78+
<InternalsVisibleTo Include="Microsoft.VisualStudio.ProjectSystem.Managed" WorkItem="https://github.com/dotnet/roslyn/issues/35070" />
79+
<InternalsVisibleTo Include="Microsoft.VisualStudio.ProjectSystem.Managed.VSCode" WorkItem="https://github.com/dotnet/roslyn/issues/35070" />
80+
<InternalsVisibleTo Include="Microsoft.VisualStudio.ProjectSystem.Managed.UnitTests" WorkItem="https://github.com/dotnet/roslyn/issues/35070" />
81+
<InternalsVisibleTo Include="Microsoft.VisualStudio.ProjectSystem.Managed.VSCode.UnitTests" WorkItem="https://github.com/dotnet/roslyn/issues/35070" />
82+
<!-- END PROJECT SYSTEM FOR VS CODE -->
83+
7684
</ItemGroup>
7785
</Project>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using StreamJsonRpc;
10+
11+
namespace Microsoft.CodeAnalysis.Remote.ProjectSystem
12+
{
13+
[RpcMarshalable]
14+
public interface IWorkspaceProject : IAsyncDisposable
15+
{
16+
Task SetDisplayNameAsync(string displayName, CancellationToken cancellationToken);
17+
18+
Task SetCommandLineArgumentsAsync(IReadOnlyList<string> arguments, CancellationToken cancellationToken);
19+
Task SetBuildSystemPropertiesAsync(IReadOnlyDictionary<string, string> properties, CancellationToken cancellationToken);
20+
21+
Task AddSourceFilesAsync(IReadOnlyList<SourceFileInfo> sourceFiles, CancellationToken cancellationToken);
22+
Task RemoveSourceFilesAsync(IReadOnlyCollection<string> sourceFiles, CancellationToken cancellationToken);
23+
24+
Task AddMetadataReferencesAsync(IReadOnlyCollection<MetadataReferenceInfo> metadataReferences, CancellationToken cancellationToken);
25+
Task RemoveMetadataReferencesAsync(IReadOnlyCollection<MetadataReferenceInfo> metadataReferences, CancellationToken cancellationToken);
26+
27+
Task AddAdditionalFilesAsync(IReadOnlyCollection<string> additionalFilePaths, CancellationToken cancellationToken);
28+
Task RemoveAdditionalFilesAsync(IReadOnlyCollection<string> additionalFilePaths, CancellationToken cancellationToken);
29+
30+
Task AddAnalyzerReferencesAsync(IReadOnlyCollection<string> analyzerPaths, CancellationToken cancellationToken);
31+
Task RemoveAnalyzerReferencesAsync(IReadOnlyCollection<string> analyzerPaths, CancellationToken cancellationToken);
32+
33+
Task AddAnalyzerConfigFilesAsync(IReadOnlyCollection<string> analyzerConfigPaths, CancellationToken cancellationToken);
34+
Task RemoveAnalyzerConfigFilesAsync(IReadOnlyCollection<string> analyzerConfigPaths, CancellationToken cancellationToken);
35+
36+
Task<IAsyncDisposable> StartBatchAsync(CancellationToken cancellationToken);
37+
}
38+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
12+
namespace Microsoft.CodeAnalysis.Remote.ProjectSystem
13+
{
14+
internal interface IWorkspaceProjectFactoryService
15+
{
16+
Task<IWorkspaceProject> CreateAndAddProjectAsync(WorkspaceProjectCreationInfo creationInfo, CancellationToken cancellationToken);
17+
18+
/// <summary>
19+
/// Returns the list of properties that are understood by the language service and can be passed to
20+
/// <see cref="IWorkspaceProject.SetBuildSystemPropertiesAsync(IReadOnlyDictionary{string, string}, CancellationToken)"/> and to
21+
/// <see cref="WorkspaceProjectCreationInfo.BuildSystemProperties"/>.
22+
/// </summary>
23+
Task<IReadOnlyCollection<string>> GetSupportedBuildSystemPropertiesAsync(CancellationToken cancellationToken);
24+
}
25+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace Microsoft.CodeAnalysis.Remote.ProjectSystem
6+
{
7+
public readonly record struct MetadataReferenceInfo(string FilePath, string Aliases, bool EmbedInteropTypes);
8+
}

0 commit comments

Comments
 (0)