// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
#if NET472
using System.Runtime;
#else
using System.Runtime.Loader;
#endif
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CommandLine
{
internal delegate int CompileFunc(string[] arguments, BuildPaths buildPaths, TextWriter textWriter, IAnalyzerAssemblyLoader analyzerAssemblyLoader);
internal struct RunCompilationResult
{
internal static readonly RunCompilationResult Succeeded = new RunCompilationResult(CommonCompiler.Succeeded);
internal static readonly RunCompilationResult Failed = new RunCompilationResult(CommonCompiler.Failed);
internal int ExitCode { get; }
internal bool RanOnServer { get; }
internal RunCompilationResult(int exitCode, bool ranOnServer = false)
{
ExitCode = exitCode;
RanOnServer = ranOnServer;
}
}
///
/// Client class that handles communication to the server.
///
internal abstract class BuildClient
{
protected static bool IsRunningOnWindows => Path.DirectorySeparatorChar == '\\';
///
/// Returns the directory that contains mscorlib, or null when running on CoreCLR.
///
public static string GetSystemSdkDirectory()
{
return RuntimeHostInfo.IsCoreClrRuntime
? null
: RuntimeEnvironment.GetRuntimeDirectory();
}
///
/// Run a compilation through the compiler server and print the output
/// to the console. If the compiler server fails, run the fallback
/// compiler.
///
internal RunCompilationResult RunCompilation(IEnumerable originalArguments, BuildPaths buildPaths, TextWriter textWriter = null)
{
textWriter = textWriter ?? Console.Out;
var args = originalArguments.Select(arg => arg.Trim()).ToArray();
List parsedArgs;
bool hasShared;
string keepAliveOpt;
string sessionKeyOpt;
string errorMessageOpt;
if (!CommandLineParser.TryParseClientArgs(
args,
out parsedArgs,
out hasShared,
out keepAliveOpt,
out sessionKeyOpt,
out errorMessageOpt))
{
textWriter.WriteLine(errorMessageOpt);
return RunCompilationResult.Failed;
}
if (hasShared)
{
sessionKeyOpt = sessionKeyOpt ?? GetSessionKey(buildPaths);
var libDirectory = Environment.GetEnvironmentVariable("LIB");
var serverResult = RunServerCompilation(textWriter, parsedArgs, buildPaths, libDirectory, sessionKeyOpt, keepAliveOpt);
if (serverResult.HasValue)
{
Debug.Assert(serverResult.Value.RanOnServer);
return serverResult.Value;
}
}
// It's okay, and expected, for the server compilation to fail. In that case just fall
// back to normal compilation.
var exitCode = RunLocalCompilation(parsedArgs.ToArray(), buildPaths, textWriter);
return new RunCompilationResult(exitCode);
}
private static bool TryEnableMulticoreJitting(out string errorMessage)
{
errorMessage = null;
try
{
// Enable multi-core JITing
// https://blogs.msdn.microsoft.com/dotnet/2012/10/18/an-easy-solution-for-improving-app-launch-performance/
var profileRoot = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"RoslynCompiler",
"ProfileOptimization");
var assemblyName = Assembly.GetExecutingAssembly().GetName();
var profileName = assemblyName.Name + assemblyName.Version + ".profile";
Directory.CreateDirectory(profileRoot);
#if NET472
ProfileOptimization.SetProfileRoot(profileRoot);
ProfileOptimization.StartProfile(profileName);
#else
AssemblyLoadContext.Default.SetProfileOptimizationRoot(profileRoot);
AssemblyLoadContext.Default.StartProfileOptimization(profileName);
#endif
}
catch (Exception e)
{
errorMessage = string.Format(CodeAnalysisResources.ExceptionEnablingMulticoreJit, e.Message);
return false;
}
return true;
}
public Task RunCompilationAsync(IEnumerable originalArguments, BuildPaths buildPaths, TextWriter textWriter = null)
{
var tcs = new TaskCompletionSource();
ThreadStart action = () =>
{
try
{
var result = RunCompilation(originalArguments, buildPaths, textWriter);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
};
var thread = new Thread(action);
thread.Start();
return tcs.Task;
}
protected abstract int RunLocalCompilation(string[] arguments, BuildPaths buildPaths, TextWriter textWriter);
///
/// Runs the provided compilation on the server. If the compilation cannot be completed on the server then null
/// will be returned.
///
internal RunCompilationResult? RunServerCompilation(TextWriter textWriter, List arguments, BuildPaths buildPaths, string libDirectory, string sessionName, string keepAlive)
{
BuildResponse buildResponse;
try
{
var buildResponseTask = RunServerCompilation(
arguments,
buildPaths,
sessionName,
keepAlive,
libDirectory,
CancellationToken.None);
buildResponse = buildResponseTask.Result;
Debug.Assert(buildResponse != null);
if (buildResponse == null)
{
return null;
}
}
catch (Exception)
{
return null;
}
switch (buildResponse.Type)
{
case BuildResponse.ResponseType.Completed:
{
var completedResponse = (CompletedBuildResponse)buildResponse;
return ConsoleUtil.RunWithUtf8Output(completedResponse.Utf8Output, textWriter, tw =>
{
tw.Write(completedResponse.Output);
return new RunCompilationResult(completedResponse.ReturnCode, ranOnServer: true);
});
}
case BuildResponse.ResponseType.MismatchedVersion:
case BuildResponse.ResponseType.IncorrectHash:
case BuildResponse.ResponseType.Rejected:
case BuildResponse.ResponseType.AnalyzerInconsistency:
// Build could not be completed on the server.
return null;
default:
// Will not happen with our server but hypothetically could be sent by a rogue server. Should
// not let that block compilation.
Debug.Assert(false);
return null;
}
}
protected abstract Task RunServerCompilation(List arguments, BuildPaths buildPaths, string sessionName, string keepAlive, string libDirectory, CancellationToken cancellationToken);
protected abstract string GetSessionKey(BuildPaths buildPaths);
protected static IEnumerable GetCommandLineArgs(IEnumerable args)
{
if (UseNativeArguments())
{
return GetCommandLineWindows(args);
}
return args;
}
private static bool UseNativeArguments()
{
if (!IsRunningOnWindows)
{
return false;
}
if (PlatformInformation.IsRunningOnMono)
{
return false;
}
if (RuntimeHostInfo.IsCoreClrRuntime)
{
// The native invoke ends up giving us both CoreRun and the exe file.
// We've decided to ignore backcompat for CoreCLR,
// and use the Main()-provided arguments
// https://github.com/dotnet/roslyn/issues/6677
return false;
}
return true;
}
///
/// When running on Windows we can't take the command line which was provided to the
/// Main method of the application. That will go through normal windows command line
/// parsing which eliminates artifacts like quotes. This has the effect of normalizing
/// the below command line options, which are semantically different, into the same
/// value:
///
/// /reference:a,b
/// /reference:"a,b"
///
/// To get the correct semantics here on Windows we parse the original command line
/// provided to the process.
///
private static IEnumerable GetCommandLineWindows(IEnumerable args)
{
IntPtr ptr = NativeMethods.GetCommandLine();
if (ptr == IntPtr.Zero)
{
return args;
}
// This memory is owned by the operating system hence we shouldn't (and can't)
// free the memory.
var commandLine = Marshal.PtrToStringUni(ptr);
// The first argument will be the executable name hence we skip it.
return CommandLineParser.SplitCommandLineIntoArguments(commandLine, removeHashComments: false).Skip(1);
}
}
}