|
// 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.
#pragma warning disable 436 // The type 'RelativePathResolver' conflicts with imported type
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Scripting.Hosting
{
internal sealed class CommandLineRunner
{
private readonly ScriptCompiler _scriptCompiler;
private readonly ObjectFormatter _objectFormatter;
private readonly Func<string, PEStreamOptions, MetadataReferenceProperties, MetadataImageReference> _createFromFileFunc;
internal CommandLineRunner(
ConsoleIO console,
CommonCompiler compiler,
ScriptCompiler scriptCompiler,
ObjectFormatter objectFormatter,
Func<string, PEStreamOptions, MetadataReferenceProperties, MetadataImageReference>? createFromFileFunc = null)
{
Debug.Assert(console != null);
Debug.Assert(compiler != null);
Debug.Assert(scriptCompiler != null);
Debug.Assert(objectFormatter != null);
Console = console;
Compiler = compiler;
_scriptCompiler = scriptCompiler;
_objectFormatter = objectFormatter;
_createFromFileFunc = createFromFileFunc ?? Script.CreateFromFile;
}
// for testing:
internal ConsoleIO Console { get; }
internal CommonCompiler Compiler { get; }
/// <summary>
/// csi.exe and vbi.exe entry point.
/// </summary>
internal int RunInteractive()
{
SarifErrorLogger? errorLogger = null;
if (Compiler.Arguments.ErrorLogOptions?.Path != null)
{
errorLogger = Compiler.GetErrorLogger(Console.Error);
if (errorLogger == null)
{
return CommonCompiler.Failed;
}
}
using (errorLogger)
{
return RunInteractiveCore(errorLogger);
}
}
/// <summary>
/// csi.exe and vbi.exe entry point.
/// </summary>
private int RunInteractiveCore(ErrorLogger? errorLogger)
{
Debug.Assert(Compiler.Arguments.IsScriptRunner);
var sourceFiles = Compiler.Arguments.SourceFiles;
if (Compiler.Arguments.DisplayVersion)
{
Compiler.PrintVersion(Console.Out);
return 0;
}
if (Compiler.Arguments.DisplayLangVersions)
{
Compiler.PrintLangVersions(Console.Out);
return 0;
}
if (sourceFiles.IsEmpty && Compiler.Arguments.DisplayLogo)
{
Compiler.PrintLogo(Console.Out);
if (!Compiler.Arguments.DisplayHelp)
{
Console.Out.WriteLine(ScriptingResources.HelpPrompt);
}
}
if (Compiler.Arguments.DisplayHelp)
{
Compiler.PrintHelp(Console.Out);
return 0;
}
SourceText? code = null;
var diagnosticsInfos = new List<DiagnosticInfo>();
if (!sourceFiles.IsEmpty)
{
if (sourceFiles.Length > 1 || !sourceFiles[0].IsScript)
{
diagnosticsInfos.Add(new DiagnosticInfo(Compiler.MessageProvider, Compiler.MessageProvider.ERR_ExpectedSingleScript));
}
else
{
code = Compiler.TryReadFileContent(sourceFiles[0], diagnosticsInfos);
}
}
// only emit symbols for non-interactive mode,
var emitDebugInformation = !Compiler.Arguments.InteractiveMode;
var scriptPathOpt = sourceFiles.IsEmpty ? null : sourceFiles[0].Path;
var scriptOptions = GetScriptOptions(Compiler.Arguments, scriptPathOpt, Compiler.MessageProvider, diagnosticsInfos, emitDebugInformation);
var errors = Compiler.Arguments.Errors.Concat(diagnosticsInfos.Select(Diagnostic.Create));
if (Compiler.ReportDiagnostics(errors, Console.Error, errorLogger, compilation: null))
{
return CommonCompiler.Failed;
}
var cancellationToken = new CancellationToken();
Debug.Assert(scriptOptions is not null);
if (Compiler.Arguments.InteractiveMode)
{
RunInteractiveLoop(scriptOptions, code?.ToString(), cancellationToken);
return CommonCompiler.Succeeded;
}
else
{
return RunScript(scriptOptions, code, errorLogger, cancellationToken);
}
}
private ScriptOptions? GetScriptOptions(CommandLineArguments arguments, string? scriptPathOpt, CommonMessageProvider messageProvider, List<DiagnosticInfo> diagnostics, bool emitDebugInformation)
{
var touchedFilesLoggerOpt = (arguments.TouchedFilesPath != null) ? new TouchedFileLogger() : null;
var metadataResolver = GetMetadataReferenceResolver(arguments, touchedFilesLoggerOpt, _createFromFileFunc);
var sourceResolver = GetSourceReferenceResolver(arguments, touchedFilesLoggerOpt);
var resolvedReferences = new List<MetadataReference>();
if (!arguments.ResolveMetadataReferences(metadataResolver, diagnostics, messageProvider, resolvedReferences))
{
// can't resolve some references
return null;
}
return new ScriptOptions(
filePath: scriptPathOpt ?? "",
references: ImmutableArray.CreateRange(resolvedReferences),
namespaces: CommandLineHelpers.GetImports(arguments),
metadataResolver: metadataResolver,
sourceResolver: sourceResolver,
emitDebugInformation: emitDebugInformation,
fileEncoding: null,
optimizationLevel: OptimizationLevel.Debug,
allowUnsafe: true,
checkOverflow: false,
warningLevel: 4,
parseOptions: arguments.ParseOptions,
createFromFileFunc: _createFromFileFunc);
}
internal static MetadataReferenceResolver GetMetadataReferenceResolver(
CommandLineArguments arguments,
TouchedFileLogger? loggerOpt,
Func<string, PEStreamOptions, MetadataReferenceProperties, MetadataImageReference> createFromFileFunc)
{
return RuntimeMetadataReferenceResolver.CreateCurrentPlatformResolver(
arguments.ReferencePaths,
arguments.BaseDirectory,
createFromFileFunc: (path, properties) =>
{
loggerOpt?.AddRead(path);
return createFromFileFunc(path, PEStreamOptions.PrefetchEntireImage, properties);
});
}
internal static SourceReferenceResolver GetSourceReferenceResolver(CommandLineArguments arguments, TouchedFileLogger? loggerOpt)
{
return new CommonCompiler.LoggingSourceFileResolver(arguments.SourcePaths, arguments.BaseDirectory, ImmutableArray<KeyValuePair<string, string>>.Empty, loggerOpt);
}
private int RunScript(ScriptOptions? options, SourceText? code, ErrorLogger? errorLogger, CancellationToken cancellationToken)
{
var globals = new CommandLineScriptGlobals(Console.Out, _objectFormatter);
globals.Args.AddRange(Compiler.Arguments.ScriptArguments);
var script = Script.CreateInitialScript<int>(_scriptCompiler, code, options, globals.GetType(), assemblyLoaderOpt: null);
try
{
return script.RunAsync(globals, cancellationToken).GetAwaiter().GetResult().ReturnValue;
}
catch (CompilationErrorException e)
{
Compiler.ReportDiagnostics(e.Diagnostics, Console.Error, errorLogger, compilation: null);
return CommonCompiler.Failed;
}
catch (Exception e)
{
DisplayException(e);
return e.HResult;
}
}
private void RunInteractiveLoop(ScriptOptions options, string? initialScriptCodeOpt, CancellationToken cancellationToken)
{
var globals = new InteractiveScriptGlobals(Console.Out, _objectFormatter);
globals.Args.AddRange(Compiler.Arguments.ScriptArguments);
ScriptState<object>? state = null;
if (initialScriptCodeOpt != null)
{
var script = Script.CreateInitialScript<object>(_scriptCompiler, SourceText.From(initialScriptCodeOpt), options, globals.GetType(), assemblyLoaderOpt: null);
BuildAndRun(script, globals, ref state, ref options, displayResult: false, cancellationToken: cancellationToken);
}
while (true)
{
Console.Out.Write("> ");
var input = new StringBuilder();
string? line;
bool cancelSubmission = false;
while (true)
{
line = Console.In.ReadLine();
if (line == null)
{
if (input.Length == 0)
{
return;
}
cancelSubmission = true;
break;
}
input.AppendLine(line);
var tree = _scriptCompiler.ParseSubmission(SourceText.From(input.ToString()), options.ParseOptions, cancellationToken);
if (_scriptCompiler.IsCompleteSubmission(tree))
{
break;
}
Console.Out.Write(". ");
}
if (cancelSubmission)
{
continue;
}
string code = input.ToString();
if (IsHelpCommand(code))
{
DisplayHelpText();
continue;
}
Script<object> newScript;
if (state == null)
{
newScript = Script.CreateInitialScript<object>(_scriptCompiler, SourceText.From(code ?? string.Empty), options, globals.GetType(), assemblyLoaderOpt: null);
}
else
{
newScript = state.Script.ContinueWith(code, options);
}
BuildAndRun(newScript, globals, ref state, ref options, displayResult: true, cancellationToken: cancellationToken);
}
}
private void BuildAndRun(Script<object> newScript, InteractiveScriptGlobals globals, ref ScriptState<object>? state, ref ScriptOptions options, bool displayResult, CancellationToken cancellationToken)
{
var diagnostics = newScript.Compile(cancellationToken);
DisplayDiagnostics(diagnostics);
if (diagnostics.HasAnyErrors())
{
return;
}
var task = (state == null)
? newScript.RunAsync(globals, catchException: e => true, cancellationToken: cancellationToken)
: newScript.RunFromAsync(state, catchException: e => true, cancellationToken: cancellationToken);
state = task.GetAwaiter().GetResult();
if (state.Exception != null)
{
DisplayException(state.Exception);
}
else if (displayResult && newScript.HasReturnValue())
{
globals.Print(state.ReturnValue);
}
options = UpdateOptions(options, globals);
}
private static ScriptOptions UpdateOptions(ScriptOptions options, InteractiveScriptGlobals globals)
{
var currentMetadataResolver = (RuntimeMetadataReferenceResolver)options.MetadataResolver;
var currentSourceResolver = (CommonCompiler.LoggingSourceFileResolver)options.SourceResolver;
string newWorkingDirectory = Directory.GetCurrentDirectory();
var newReferenceSearchPaths = ImmutableArray.CreateRange(globals.ReferencePaths);
var newSourceSearchPaths = ImmutableArray.CreateRange(globals.SourcePaths);
// remove references and imports from the options, they have been applied and will be inherited from now on:
return options.
RemoveImportsAndReferences().
WithMetadataResolver(currentMetadataResolver.
WithRelativePathResolver(
currentMetadataResolver.PathResolver.
WithBaseDirectory(newWorkingDirectory).
WithSearchPaths(newReferenceSearchPaths))).
WithSourceResolver(currentSourceResolver.
WithBaseDirectory(newWorkingDirectory).
WithSearchPaths(newSourceSearchPaths));
}
private void DisplayException(Exception e)
{
try
{
Console.SetForegroundColor(ConsoleColor.Red);
if (e is FileLoadException && e.InnerException is InteractiveAssemblyLoaderException)
{
Console.Error.WriteLine(e.InnerException.Message);
}
else
{
Console.Error.Write(_objectFormatter.FormatException(e));
}
}
finally
{
Console.ResetColor();
}
}
private static bool IsHelpCommand(string text)
{
const string helpCommand = "#help";
Debug.Assert(text != null);
return text.Trim() == helpCommand;
}
private void DisplayHelpText()
{
Console.Out.Write(ScriptingResources.HelpText);
Console.Out.WriteLine();
}
private void DisplayDiagnostics(ImmutableArray<Diagnostic> diagnostics)
{
const int MaxDisplayCount = 5;
// by severity, then by location
var ordered = diagnostics.OrderBy((d1, d2) =>
{
int delta = (int)d2.Severity - (int)d1.Severity;
return (delta != 0) ? delta : d1.Location.SourceSpan.Start - d2.Location.SourceSpan.Start;
});
try
{
foreach (var diagnostic in ordered.Take(MaxDisplayCount))
{
Console.SetForegroundColor(diagnostic.Severity == DiagnosticSeverity.Error ? ConsoleColor.Red : ConsoleColor.Yellow);
Console.Error.WriteLine(diagnostic.ToString());
}
if (diagnostics.Length > MaxDisplayCount)
{
int notShown = diagnostics.Length - MaxDisplayCount;
Console.SetForegroundColor(ConsoleColor.DarkRed);
Console.Error.WriteLine(string.Format(ScriptingResources.PlusAdditionalError, notShown));
}
}
finally
{
Console.ResetColor();
}
}
}
}
|