File: CompilerRequestHandler.cs
Web Access
Project: ..\..\..\src\Compilers\Server\VBCSCompiler\AnyCpu\VBCSCompiler.csproj (VBCSCompiler)
// 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.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CommandLine;
 
using static Microsoft.CodeAnalysis.CommandLine.CompilerServerLogger;
 
namespace Microsoft.CodeAnalysis.CompilerServer
{
    internal readonly struct RunRequest
    {
        public Guid RequestId { get; }
        public string Language { get; }
        public string? WorkingDirectory { get; }
        public string? TempDirectory { get; }
        public string? LibDirectory { get; }
        public string[] Arguments { get; }
 
        public RunRequest(Guid requestId, string language, string? workingDirectory, string? tempDirectory, string? libDirectory, string[] arguments)
        {
            RequestId = requestId;
            Language = language;
            WorkingDirectory = workingDirectory;
            TempDirectory = tempDirectory;
            LibDirectory = libDirectory;
            Arguments = arguments;
        }
    }
 
    internal sealed class CompilerServerHost : ICompilerServerHost
    {
        public IAnalyzerAssemblyLoader AnalyzerAssemblyLoader { get; } = new ShadowCopyAnalyzerAssemblyLoader(baseDirectory: Path.Combine(Path.GetTempPath(), "VBCSCompiler", "AnalyzerAssemblyLoader"));
 
        public static Func<string, MetadataReferenceProperties, PortableExecutableReference> SharedAssemblyReferenceProvider { get; } = (path, properties) => new CachingMetadataReference(path, properties);
 
        /// <summary>
        /// The caching metadata provider used by the C# and VB compilers
        /// </summary>
        private Func<string, MetadataReferenceProperties, PortableExecutableReference> AssemblyReferenceProvider { get; } = SharedAssemblyReferenceProvider;
 
        /// <summary>
        /// Directory that contains the compiler executables and the response files. 
        /// </summary>
        private string ClientDirectory { get; }
 
        /// <summary>
        /// Directory that contains mscorlib.  Can be null when the host is executing in a CoreCLR context.
        /// </summary>
        private string SdkDirectory { get; }
 
        public ICompilerServerLogger Logger { get; }
 
        /// <summary>
        /// A cache that can store generator drivers in order to enable incrementalism across builds for the lifetime of the server.
        /// </summary>
        private readonly GeneratorDriverCache _driverCache = new GeneratorDriverCache();
 
        internal CompilerServerHost(string clientDirectory, string sdkDirectory, ICompilerServerLogger logger)
        {
            ClientDirectory = clientDirectory;
            SdkDirectory = sdkDirectory;
            Logger = logger;
        }
 
        public bool TryCreateCompiler(in RunRequest request, BuildPaths buildPaths, [NotNullWhen(true)] out CommonCompiler? compiler)
        {
            switch (request.Language)
            {
                case LanguageNames.CSharp:
                    compiler = new CSharpCompilerServer(
                        AssemblyReferenceProvider,
                        args: request.Arguments,
                        buildPaths: buildPaths,
                        libDirectory: request.LibDirectory,
                        analyzerLoader: AnalyzerAssemblyLoader,
                        _driverCache);
                    return true;
                case LanguageNames.VisualBasic:
                    compiler = new VisualBasicCompilerServer(
                        AssemblyReferenceProvider,
                        args: request.Arguments,
                        buildPaths: buildPaths,
                        libDirectory: request.LibDirectory,
                        analyzerLoader: AnalyzerAssemblyLoader,
                        _driverCache);
                    return true;
                default:
                    compiler = null;
                    return false;
            }
        }
 
        public BuildResponse RunCompilation(in RunRequest request, CancellationToken cancellationToken)
        {
            Logger.Log($@"
Run Compilation for {request.RequestId}
  Language = {request.Language}
  CurrentDirectory = '{request.WorkingDirectory}
  LIB = '{request.LibDirectory}'");
 
            // Compiler server must be provided with a valid current directory in order to correctly 
            // resolve files in the compilation
            if (string.IsNullOrEmpty(request.WorkingDirectory))
            {
                var message = "Missing working directory";
                Logger.Log($"Rejected: {request.RequestId}: {message}");
                return new RejectedBuildResponse(message);
            }
 
            // Compiler server must be provided with a valid temporary directory in order to correctly
            // isolate signing between compilations.
            if (string.IsNullOrEmpty(request.TempDirectory))
            {
                var message = "Missing temp directory";
                Logger.Log($"Rejected: {request.RequestId}: {message}");
                return new RejectedBuildResponse(message);
            }
 
            var buildPaths = new BuildPaths(ClientDirectory, request.WorkingDirectory, SdkDirectory, request.TempDirectory);
            if (!TryCreateCompiler(request, buildPaths, out CommonCompiler? compiler))
            {
                var message = $"Cannot create compiler for language id {request.Language}";
                Logger.Log($"Rejected: {request.RequestId}: {message}");
                return new RejectedBuildResponse(message);
            }
 
#if NETFRAMEWORK
            if (!AnalyzerConsistencyChecker.Check(request.WorkingDirectory, compiler.Arguments.AnalyzerReferences, AnalyzerAssemblyLoader, Logger, out List<string?> errorMessages))
            {
                Logger.Log($"Rejected: {request.RequestId}: for analyzer load issues {string.Join(";", errorMessages)}");
                return new AnalyzerInconsistencyBuildResponse(new ReadOnlyCollection<string>(errorMessages));
            }
#endif
 
            Logger.Log($"Begin {request.RequestId} {request.Language} compiler run");
            try
            {
                bool utf8output = compiler.Arguments.Utf8Output;
                TextWriter output = new StringWriter(CultureInfo.InvariantCulture);
                int returnCode = compiler.Run(output, cancellationToken);
                var outputString = output.ToString();
                Logger.Log(@$"End {request.RequestId} {request.Language} compiler run
Return code: {returnCode}
Output:
{outputString}");
                return new CompletedBuildResponse(returnCode, utf8output, outputString);
            }
            catch (Exception ex)
            {
                Logger.LogException(ex, $"Running compilation for {request.RequestId}");
                throw;
            }
        }
    }
}