Skip to content

Commit

Permalink
Merge pull request #65592 from jasonmalinowski/introduce-brokered-ser…
Browse files Browse the repository at this point in the history
…vice-for-project-system

Implement a new brokered service for passing project information
  • Loading branch information
jasonmalinowski authored Dec 6, 2022
2 parents becd261 + 91afef7 commit ff42dbf
Show file tree
Hide file tree
Showing 12 changed files with 319 additions and 0 deletions.
1 change: 1 addition & 0 deletions eng/targets/Services.props
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@
-->
<ItemGroup>
<InProcService Include="Microsoft.VisualStudio.LanguageServices.SolutionAssetProvider" />
<InProcService Include="Microsoft.VisualStudio.LanguageServices.WorkspaceProjectFactoryService" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Remote.ProjectSystem;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem.BrokeredService
{
internal sealed class WorkspaceProject : IWorkspaceProject
{
// For the sake of the in-proc implementation here, we're going to build this atop IWorkspaceProjectContext so semantics are preserved
// for a few edge cases. Once the project system has moved onto this directly, we can flatten the implementations out.
private readonly IWorkspaceProjectContext _project;

public WorkspaceProject(IWorkspaceProjectContext project)
{
_project = project;
}

public ValueTask DisposeAsync()
{
_project.Dispose();
return ValueTaskFactory.CompletedTask;
}

public async Task AddAdditionalFilesAsync(IReadOnlyList<string> additionalFilePaths, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var additionalFilePath in additionalFilePaths)
_project.AddAdditionalFile(additionalFilePath);
}

public async Task RemoveAdditionalFilesAsync(IReadOnlyList<string> additionalFilePaths, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var additionalFilePath in additionalFilePaths)
_project.RemoveAdditionalFile(additionalFilePath);
}

public async Task AddAnalyzerConfigFilesAsync(IReadOnlyList<string> analyzerConfigPaths, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var analyzerConfigPath in analyzerConfigPaths)
_project.AddAnalyzerConfigFile(analyzerConfigPath);
}
public async Task RemoveAnalyzerConfigFilesAsync(IReadOnlyList<string> analyzerConfigPaths, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var analyzerConfigPath in analyzerConfigPaths)
_project.RemoveAnalyzerConfigFile(analyzerConfigPath);
}

public async Task AddAnalyzerReferencesAsync(IReadOnlyList<string> analyzerPaths, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var analyzerPath in analyzerPaths)
_project.AddAnalyzerReference(analyzerPath);
}

public async Task RemoveAnalyzerReferencesAsync(IReadOnlyList<string> analyzerPaths, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var analyzerPath in analyzerPaths)
_project.RemoveAnalyzerReference(analyzerPath);
}

public async Task AddMetadataReferencesAsync(IReadOnlyList<MetadataReferenceInfo> metadataReferences, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var metadataReference in metadataReferences)
{
_project.AddMetadataReference(
metadataReference.FilePath,
new MetadataReferenceProperties(MetadataImageKind.Assembly, default, metadataReference.EmbedInteropTypes));
}
}

public async Task RemoveMetadataReferencesAsync(IReadOnlyList<MetadataReferenceInfo> metadataReferences, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

// 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
// references with the same path but different properties.
foreach (var metadataReference in metadataReferences)
_project.RemoveMetadataReference(metadataReference.FilePath);
}

public async Task AddSourceFilesAsync(IReadOnlyList<SourceFileInfo> sourceFiles, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var sourceFile in sourceFiles)
{
_project.AddSourceFile(
sourceFile.FilePath,
folderNames: sourceFile.FolderNames);
}
}
public async Task RemoveSourceFilesAsync(IReadOnlyList<string> sourceFiles, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var sourceFile in sourceFiles)
_project.RemoveSourceFile(sourceFile);
}

public async Task SetBuildSystemPropertiesAsync(IReadOnlyDictionary<string, string> properties, CancellationToken cancellationToken)
{
await using var batch = _project.CreateBatchScope().ConfigureAwait(false);

foreach (var property in properties)
_project.SetProperty(property.Key, property.Value);
}

public Task SetCommandLineArgumentsAsync(IReadOnlyList<string> arguments, CancellationToken cancellationToken)
{
_project.SetOptions(arguments.ToImmutableArray());
return Task.CompletedTask;
}

public Task SetDisplayNameAsync(string displayName, CancellationToken cancellationToken)
{
_project.DisplayName = displayName;
return Task.CompletedTask;
}

public Task<IAsyncDisposable> StartBatchAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_project.CreateBatchScope());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Remote.ProjectSystem;
using Microsoft.ServiceHub.Framework;

namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem.BrokeredService
{
internal class WorkspaceProjectFactoryService : IWorkspaceProjectFactoryService
{
public const string ServiceName = "WorkspaceProjectFactoryService";
public static readonly ServiceDescriptor ServiceDescriptor = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName);

private readonly IWorkspaceProjectContextFactory _workspaceProjectContextFactory;

// For the sake of the in-proc implementation here, we're going to build this atop IWorkspaceProjectContext so semantics are preserved
// for a few edge cases. Once the project system has moved onto this directly, we can flatten the implementations out.
public WorkspaceProjectFactoryService(IWorkspaceProjectContextFactory workspaceProjectContextFactory)
{
_workspaceProjectContextFactory = workspaceProjectContextFactory;
}

public async Task<IWorkspaceProject> CreateAndAddProjectAsync(WorkspaceProjectCreationInfo creationInfo, CancellationToken cancellationToken)
{
var project = await _workspaceProjectContextFactory.CreateProjectContextAsync(
Guid.NewGuid(), // TODO: figure out some other side-channel way of communicating this
creationInfo.DisplayName,
creationInfo.Language,
new EvaluationDataShim(creationInfo.BuildSystemProperties),
hostObject: null, // TODO: figure out some other side-channel way of communicating this
cancellationToken).ConfigureAwait(false);

return new WorkspaceProject(project);
}

public Task<IReadOnlyCollection<string>> GetSupportedBuildSystemPropertiesAsync(CancellationToken cancellationToken)
{
return Task.FromResult((IReadOnlyCollection<string>)_workspaceProjectContextFactory.EvaluationItemNames);
}

private sealed class EvaluationDataShim : EvaluationData
{
private readonly IReadOnlyDictionary<string, string> _buildSystemProperties;

public EvaluationDataShim(IReadOnlyDictionary<string, string> buildSystemProperties)
{
_buildSystemProperties = buildSystemProperties;
}

public override string GetPropertyValue(string name)
{
return _buildSystemProperties.TryGetValue(name, out var value) ? value : "";
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;

namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem
{
Expand Down Expand Up @@ -72,6 +73,7 @@ internal interface IWorkspaceProjectContext : IDisposable
void RemoveAnalyzerConfigFile(string filePath);

void StartBatch();
IAsyncDisposable CreateBatchScope();
ValueTask EndBatchAsync();

void ReorderSourceFiles(IEnumerable<string> filePaths);
Expand Down
10 changes: 10 additions & 0 deletions src/VisualStudio/Core/Def/RoslynPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@
using Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource;
using Microsoft.VisualStudio.LanguageServices.Implementation.UnusedReferences;
using Microsoft.VisualStudio.LanguageServices.InheritanceMargin;
using Microsoft.VisualStudio.LanguageServices.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.ProjectSystem.BrokeredService;
using Microsoft.VisualStudio.LanguageServices.StackTraceExplorer;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.ServiceBroker;
using Microsoft.VisualStudio.TaskStatusCenter;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Threading;
Expand Down Expand Up @@ -170,6 +173,13 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
// doc events and appropriately map files to/from it and other relevant workspaces (like the
// metadata-as-source workspace).
await this.ComponentModel.GetService<MiscellaneousFilesWorkspace>().InitializeAsync(this).ConfigureAwait(false);

// Proffer in-process service broker services
var serviceBrokerContainer = await this.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>(this.JoinableTaskFactory).ConfigureAwait(false);

serviceBrokerContainer.Proffer(
WorkspaceProjectFactoryService.ServiceDescriptor,
(_, _, _, _) => ValueTaskFactory.FromResult<object?>(new WorkspaceProjectFactoryService(this.ComponentModel.GetService<IWorkspaceProjectContextFactory>())));
}

private async Task LoadOptionPersistersAsync(IComponentModel componentModel, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,5 +276,7 @@ public void AddAnalyzerConfigFile(string filePath)

public void RemoveAnalyzerConfigFile(string filePath)
=> _visualStudioProject.RemoveAnalyzerConfigFile(filePath);

public IAsyncDisposable CreateBatchScope() => _visualStudioProject.CreateBatchScope();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,13 @@
<InternalsVisibleTo Include="MonoDevelop.Ide.Tests" Key="$(MonoDevelopKey)" LoadsWithinVisualStudio="false" />
<InternalsVisibleTo Include="MonoDevelop.Refactoring.Tests" Key="$(MonoDevelopKey)" LoadsWithinVisualStudio="false" />
<!-- END MONODEVELOP -->

<!-- BEGIN PROJECT SYSTEM FOR VS CODE -->
<InternalsVisibleTo Include="Microsoft.VisualStudio.ProjectSystem.Managed" WorkItem="https://github.com/dotnet/roslyn/issues/35070" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.ProjectSystem.Managed.VSCode" WorkItem="https://github.com/dotnet/roslyn/issues/35070" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.ProjectSystem.Managed.UnitTests" WorkItem="https://github.com/dotnet/roslyn/issues/35070" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.ProjectSystem.Managed.VSCode.UnitTests" WorkItem="https://github.com/dotnet/roslyn/issues/35070" />
<!-- END PROJECT SYSTEM FOR VS CODE -->

</ItemGroup>
</Project>
37 changes: 37 additions & 0 deletions src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using StreamJsonRpc;

namespace Microsoft.CodeAnalysis.Remote.ProjectSystem;

[RpcMarshalable]
internal interface IWorkspaceProject : IAsyncDisposable
{
Task SetDisplayNameAsync(string displayName, CancellationToken cancellationToken);

Task SetCommandLineArgumentsAsync(IReadOnlyList<string> arguments, CancellationToken cancellationToken);
Task SetBuildSystemPropertiesAsync(IReadOnlyDictionary<string, string> properties, CancellationToken cancellationToken);

Task AddSourceFilesAsync(IReadOnlyList<SourceFileInfo> sourceFiles, CancellationToken cancellationToken);
Task RemoveSourceFilesAsync(IReadOnlyList<string> sourceFiles, CancellationToken cancellationToken);

Task AddMetadataReferencesAsync(IReadOnlyList<MetadataReferenceInfo> metadataReferences, CancellationToken cancellationToken);
Task RemoveMetadataReferencesAsync(IReadOnlyList<MetadataReferenceInfo> metadataReferences, CancellationToken cancellationToken);

Task AddAdditionalFilesAsync(IReadOnlyList<string> additionalFilePaths, CancellationToken cancellationToken);
Task RemoveAdditionalFilesAsync(IReadOnlyList<string> additionalFilePaths, CancellationToken cancellationToken);

Task AddAnalyzerReferencesAsync(IReadOnlyList<string> analyzerPaths, CancellationToken cancellationToken);
Task RemoveAnalyzerReferencesAsync(IReadOnlyList<string> analyzerPaths, CancellationToken cancellationToken);

Task AddAnalyzerConfigFilesAsync(IReadOnlyList<string> analyzerConfigPaths, CancellationToken cancellationToken);
Task RemoveAnalyzerConfigFilesAsync(IReadOnlyList<string> analyzerConfigPaths, CancellationToken cancellationToken);

Task<IAsyncDisposable> StartBatchAsync(CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.CodeAnalysis.Remote.ProjectSystem;

internal interface IWorkspaceProjectFactoryService
{
Task<IWorkspaceProject> CreateAndAddProjectAsync(WorkspaceProjectCreationInfo creationInfo, CancellationToken cancellationToken);

/// <summary>
/// Returns the list of properties that are understood by the language service and can be passed to
/// <see cref="IWorkspaceProject.SetBuildSystemPropertiesAsync(IReadOnlyDictionary{string, string}, CancellationToken)"/> and to
/// <see cref="WorkspaceProjectCreationInfo.BuildSystemProperties"/>.
/// </summary>
Task<IReadOnlyCollection<string>> GetSupportedBuildSystemPropertiesAsync(CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// 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.

namespace Microsoft.CodeAnalysis.Remote.ProjectSystem;

internal readonly record struct MetadataReferenceInfo(string FilePath, string Aliases, bool EmbedInteropTypes);
9 changes: 9 additions & 0 deletions src/Workspaces/Remote/Core/ProjectSystem/SourceFileInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 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.Generic;

namespace Microsoft.CodeAnalysis.Remote.ProjectSystem;

internal readonly record struct SourceFileInfo(string FilePath, IReadOnlyList<string> FolderNames);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 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.Generic;

namespace Microsoft.CodeAnalysis.Remote.ProjectSystem;

internal record WorkspaceProjectCreationInfo(string Language, string DisplayName, string? FilePath, IReadOnlyDictionary<string, string> BuildSystemProperties);

0 comments on commit ff42dbf

Please sign in to comment.