File: Extensions\IExtensionAssemblyLoaderProvider.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Composition;
using System.IO;
using System.Reflection;
using System.Threading;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
 
namespace Microsoft.CodeAnalysis.Extensions;
 
/// <summary>
/// Abstraction around <see cref="IAnalyzerAssemblyLoaderProvider"/> so that we can mock that behavior in tests.
/// </summary>
internal interface IExtensionAssemblyLoaderProvider : IWorkspaceService
{
    (IExtensionAssemblyLoader? assemblyLoader, Exception? extensionException) CreateNewShadowCopyLoader(
        string assemblyFolderPath, CancellationToken cancellationToken);
}
 
/// <summary>
/// Abstraction around <see cref="IAnalyzerAssemblyLoader"/> so that we can mock that behavior in tests.
/// </summary>
internal interface IExtensionAssemblyLoader
{
    Assembly LoadFromPath(string assemblyFilePath);
    void Unload();
}
 
[ExportWorkspaceServiceFactory(typeof(IExtensionAssemblyLoaderProvider)), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class DefaultExtensionAssemblyLoaderProviderFactory() : IWorkspaceServiceFactory
{
    public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
        => new DefaultExtensionAssemblyLoaderProvider(workspaceServices);
 
    private sealed class DefaultExtensionAssemblyLoaderProvider(HostWorkspaceServices workspaceServices)
        : IExtensionAssemblyLoaderProvider
    {
#pragma warning disable IDE0052 // Remove unread private members
        private readonly HostWorkspaceServices _workspaceServices = workspaceServices;
#pragma warning restore IDE0052 // Remove unread private members
 
        public (IExtensionAssemblyLoader? assemblyLoader, Exception? extensionException) CreateNewShadowCopyLoader(
            string assemblyFolderPath, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
#if NET
            // These lines should always succeed.  If they don't, they indicate a bug in our code that we want
            // to bubble out as it must be fixed.
            var analyzerAssemblyLoaderProvider = _workspaceServices.GetRequiredService<IAnalyzerAssemblyLoaderProvider>();
            var analyzerAssemblyLoader = analyzerAssemblyLoaderProvider.CreateNewShadowCopyLoader();
 
            // Catch exceptions here related to working with the file system.  If we can't properly enumerate,
            // we want to report that back to the client, while not blocking the entire extension service.
            try
            {
                // Allow this assembly loader to load any dll in assemblyFolderPath.
                foreach (var dll in Directory.EnumerateFiles(assemblyFolderPath, "*.dll"))
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    try
                    {
                        // Check if the file is a valid .NET assembly.
                        AssemblyName.GetAssemblyName(dll);
                    }
                    catch
                    {
                        // The file is not a valid .NET assembly, skip it.
                        continue;
                    }
 
                    analyzerAssemblyLoader.AddDependencyLocation(dll);
                }
 
                return (new DefaultExtensionAssemblyLoader(analyzerAssemblyLoader), null);
            }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                // Capture any exceptions here to be reported back in CreateAssemblyHandlersAsync.
                return (null, ex);
            }
#else
            return default;
#endif
        }
    }
 
    private sealed class DefaultExtensionAssemblyLoader(
        IAnalyzerAssemblyLoaderInternal assemblyLoader) : IExtensionAssemblyLoader
    {
        public void Unload() => assemblyLoader.Dispose();
 
        public Assembly LoadFromPath(string assemblyFilePath)
            => assemblyLoader.LoadFromPath(assemblyFilePath);
    }
}