44483ddc98
Also removed the dependency on Microsoft.Extensions.CommandLineUtils.Sources NuGet package and instead just checking the source files into our repo as internal classes. Fix #2526
184 lines
8 KiB
C#
184 lines
8 KiB
C#
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Microsoft.DotNet.Cli.CommandLine;
|
|
using Microsoft.DotNet.Cli.Utils;
|
|
using Microsoft.DotNet.ProjectModel;
|
|
using NuGet.Frameworks;
|
|
|
|
namespace Microsoft.DotNet.Tools.Repl.Csi
|
|
{
|
|
public sealed class ReplCsiCommand
|
|
{
|
|
public static int Run(string[] args)
|
|
{
|
|
DebugHelper.HandleDebugSwitch(ref args);
|
|
|
|
var app = new CommandLineApplication(throwOnUnexpectedArg: false);
|
|
app.Name = "dotnet repl csi";
|
|
app.FullName = "C# REPL";
|
|
app.Description = "C# REPL for the .NET platform";
|
|
app.HelpOption("-h|--help");
|
|
|
|
var script = app.Argument("<SCRIPT>", "The .csx file to run. Defaults to interactive mode");
|
|
var framework = app.Option("-f|--framework <FRAMEWORK>", "Compile a specific framework", CommandOptionType.SingleValue);
|
|
var configuration = app.Option("-c|--configuration <CONFIGURATION>", "Configuration under which to build", CommandOptionType.SingleValue);
|
|
var preserveTemporary = app.Option("-t|--preserve-temporary", "Preserve the temporary directory containing the compiled project", CommandOptionType.NoValue);
|
|
var project = app.Option("-p|--project <PROJECT>", "The path to the project to run. Can be a path to a project.json or a project directory", CommandOptionType.SingleValue);
|
|
|
|
app.OnExecute(() => Run(script.Value, framework.Value(), configuration.Value(), preserveTemporary.HasValue(), project.Value(), app.RemainingArguments));
|
|
return app.Execute(args);
|
|
}
|
|
|
|
private static ProjectContext GetProjectContext(string targetFramework, string projectPath)
|
|
{
|
|
// Selecting the target framework for the project is done in the same manner as dotnet run. If a target framework
|
|
// was specified, we attempt to create a context for that framework (and error out if the framework is unsupported).
|
|
// Otherwise, we pick the first context supported by the project.
|
|
|
|
var contexts = ProjectContext.CreateContextForEachFramework(Path.GetFullPath(projectPath));
|
|
var context = contexts.First();
|
|
|
|
if (targetFramework != null)
|
|
{
|
|
var framework = NuGetFramework.Parse(targetFramework);
|
|
context = contexts.FirstOrDefault(c => c.TargetFramework.Equals(framework));
|
|
}
|
|
|
|
return context;
|
|
}
|
|
|
|
private static int CompileProject(ProjectContext projectContext, string configuration, out string tempOutputDir)
|
|
{
|
|
tempOutputDir = Path.Combine(projectContext.ProjectDirectory, "bin", ".dotnetrepl", Guid.NewGuid().ToString("N"));
|
|
|
|
Reporter.Output.WriteLine($"Compiling {projectContext.RootProject.Identity.Name.Yellow()} for {projectContext.TargetFramework.DotNetFrameworkName.Yellow()} to use with the {"C# REPL".Yellow()} environment.");
|
|
|
|
return Build.BuildCommand.Run(new[]
|
|
{
|
|
$"--output",
|
|
$"{tempOutputDir}",
|
|
$"--framework",
|
|
$"{projectContext.TargetFramework}",
|
|
$"--configuration",
|
|
$"{configuration}",
|
|
$"{projectContext.ProjectDirectory}"
|
|
});
|
|
}
|
|
|
|
private static IEnumerable<string> GetRuntimeDependencies(ProjectContext projectContext, string buildConfiguration)
|
|
{
|
|
// We collect the full list of runtime dependencies here and pass them back so they can be
|
|
// referenced by the REPL environment when seeding the context. It appears that we need to
|
|
// explicitly list the dependencies as they may not exist in the output directory (as is the
|
|
// for library projects) or they may not exist anywhere on the path (e.g. they may only exist
|
|
// in the nuget package that was downloaded for the compilation) or they may be specific to a
|
|
// specific target framework.
|
|
|
|
var runtimeDependencies = new HashSet<string>();
|
|
|
|
var projectExporter = projectContext.CreateExporter(buildConfiguration);
|
|
var projectDependencies = projectExporter.GetDependencies();
|
|
|
|
foreach (var projectDependency in projectDependencies)
|
|
{
|
|
var runtimeAssemblies = projectDependency.RuntimeAssemblyGroups;
|
|
|
|
foreach (var runtimeAssembly in runtimeAssemblies.GetDefaultAssets())
|
|
{
|
|
var runtimeAssemblyPath = runtimeAssembly.ResolvedPath;
|
|
runtimeDependencies.Add(runtimeAssemblyPath);
|
|
}
|
|
}
|
|
|
|
return runtimeDependencies;
|
|
}
|
|
|
|
private static string CreateResponseFile(ProjectContext projectContext, string buildConfiguration, string tempOutputDir)
|
|
{
|
|
var outputFileName = projectContext.ProjectFile.Name;
|
|
var outputFilePath = Path.Combine(tempOutputDir, $"{outputFileName}.dll");
|
|
var projectResponseFilePath = Path.Combine(tempOutputDir, $"dotnet-repl.{outputFileName}{Constants.ResponseFileSuffix}");
|
|
|
|
var runtimeDependencies = GetRuntimeDependencies(projectContext, buildConfiguration);
|
|
|
|
using (var fileStream = new FileStream(projectResponseFilePath, FileMode.Create))
|
|
{
|
|
using (var streamWriter = new StreamWriter(fileStream))
|
|
{
|
|
streamWriter.WriteLine($"/r:\"{outputFilePath}\"");
|
|
|
|
foreach (var projectDependency in runtimeDependencies)
|
|
{
|
|
streamWriter.WriteLine($"/r:\"{projectDependency}\"");
|
|
}
|
|
}
|
|
}
|
|
|
|
return projectResponseFilePath;
|
|
}
|
|
|
|
private static int Run(string script, string targetFramework, string buildConfiguration, bool preserveTemporaryOutput, string projectPath, IEnumerable<string> remainingArguments)
|
|
{
|
|
var csiArgs = new List<string>();
|
|
|
|
if (buildConfiguration == null)
|
|
{
|
|
buildConfiguration = Constants.DefaultConfiguration;
|
|
}
|
|
|
|
string tempOutputDir = null;
|
|
try
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(projectPath))
|
|
{
|
|
var projectContext = GetProjectContext(targetFramework, projectPath);
|
|
|
|
if (projectContext == null)
|
|
{
|
|
Reporter.Error.WriteLine($"Unrecognized framework: {targetFramework.First()}".Red());
|
|
}
|
|
|
|
var compileResult = CompileProject(projectContext, buildConfiguration, out tempOutputDir);
|
|
|
|
if (compileResult != 0)
|
|
{
|
|
Reporter.Error.WriteLine($"Project compilation failed. Exiting REPL".Red());
|
|
return compileResult;
|
|
}
|
|
|
|
string responseFile = CreateResponseFile(projectContext, buildConfiguration, tempOutputDir);
|
|
csiArgs.Add($"@{responseFile}");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(script) && !remainingArguments.Any())
|
|
{
|
|
csiArgs.Add("-i");
|
|
}
|
|
else
|
|
{
|
|
csiArgs.Add(script);
|
|
}
|
|
|
|
csiArgs.AddRange(remainingArguments);
|
|
|
|
return Command.Create("csi", csiArgs)
|
|
.ForwardStdOut()
|
|
.ForwardStdErr()
|
|
.Execute()
|
|
.ExitCode;
|
|
}
|
|
finally
|
|
{
|
|
if ((tempOutputDir != null) && !preserveTemporaryOutput)
|
|
{
|
|
Directory.Delete(tempOutputDir, recursive: true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|