File: Host\RemoteExportProviderBuilder.cs
Web Access
Project: src\src\Workspaces\Remote\ServiceHub\Microsoft.CodeAnalysis.Remote.ServiceHub.csproj (Microsoft.CodeAnalysis.Remote.ServiceHub)
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.Composition;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Remote;
 
internal sealed class RemoteExportProviderBuilder : ExportProviderBuilder
{
    internal static readonly ImmutableArray<string> RemoteHostAssemblyNames =
        MefHostServices.DefaultAssemblyNames
            .Add("Microsoft.CodeAnalysis.ExternalAccess.AspNetCore")
            .Add("Microsoft.CodeAnalysis.Remote.ServiceHub")
            .Add("Microsoft.CodeAnalysis.ExternalAccess.Razor.Features")
            .Add("Microsoft.CodeAnalysis.Remote.Workspaces")
            .Add("Microsoft.CodeAnalysis.ExternalAccess.Extensions");
 
    private static ExportProvider? s_instance;
    internal static ExportProvider ExportProvider
        => s_instance ?? throw new InvalidOperationException($"Default export provider not initialized. Call {nameof(InitializeAsync)} first.");
 
    private StringBuilder? _errorMessages;
 
    private RemoteExportProviderBuilder(
        ImmutableArray<string> assemblyPaths,
        Resolver resolver,
        string cacheDirectory,
        string catalogPrefix)
        : base(assemblyPaths, resolver, cacheDirectory, catalogPrefix)
    {
    }
 
    public static async Task<string?> InitializeAsync(string localSettingsDirectory, CancellationToken cancellationToken)
    {
        var assemblyPaths = RemoteHostAssemblyNames
            .Select(static assemblyName => MefHostServicesHelpers.TryFindNearbyAssemblyLocation(assemblyName))
            .WhereNotNull()
            .AsImmutable();
 
        var builder = new RemoteExportProviderBuilder(
            assemblyPaths: assemblyPaths,
            resolver: new Resolver(SimpleAssemblyLoader.Instance),
            cacheDirectory: Path.Combine(localSettingsDirectory, "Roslyn", "RemoteHost", "Cache"),
            catalogPrefix: "RoslynRemoteHost");
 
        s_instance = await builder.CreateExportProviderAsync(cancellationToken).ConfigureAwait(false);
 
        return builder._errorMessages?.ToString();
    }
 
    protected override void LogError(string message)
    {
        _errorMessages ??= new StringBuilder();
        _errorMessages.AppendLine(message);
    }
 
    protected override void LogTrace(string message)
    {
    }
 
    protected override bool ContainsUnexpectedErrors(IEnumerable<string> erroredParts, ImmutableList<PartDiscoveryException> partDiscoveryExceptions)
    {
        // Verify that we have exactly the MEF errors that we expect.  If we have less or more this needs to be updated to assert the expected behavior.
        var expectedErrorPartsSet = new HashSet<string>(["PythiaSignatureHelpProvider", "VSTypeScriptAnalyzerService", "CodeFixService"]);
        var hasUnexpectedErroredParts = erroredParts.Any(part => !expectedErrorPartsSet.Contains(part));
 
        if (hasUnexpectedErroredParts)
            return true;
 
        return partDiscoveryExceptions.Count > 0;
    }
 
    private sealed class SimpleAssemblyLoader : IAssemblyLoader
    {
        public static readonly IAssemblyLoader Instance = new SimpleAssemblyLoader();
 
        public Assembly LoadAssembly(AssemblyName assemblyName)
            => Assembly.Load(assemblyName);
 
        public Assembly LoadAssembly(string assemblyFullName, string? codeBasePath)
        {
            var assemblyName = new AssemblyName(assemblyFullName);
            if (!string.IsNullOrEmpty(codeBasePath))
            {
                // Set the codebase path, if known, as a hint for the assembly loader.
#pragma warning disable SYSLIB0044 // https://github.com/dotnet/roslyn/issues/71510
                assemblyName.CodeBase = codeBasePath;
#pragma warning restore SYSLIB0044
            }
 
            return LoadAssembly(assemblyName);
        }
    }
}