Skip to content

Commit e677cd6

Browse files
committed
first stab at compile/exec
1 parent 4f8b335 commit e677cd6

File tree

3 files changed

+164
-1
lines changed

3 files changed

+164
-1
lines changed

src/Dotnet.Script.Core/ScriptCompiler.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,71 @@
1818
using System.Threading.Tasks;
1919
using Microsoft.CodeAnalysis.CSharp.Syntax;
2020
using RuntimeAssembly = Dotnet.Script.DependencyModel.Runtime.RuntimeAssembly;
21+
using System.IO;
22+
using Microsoft.CodeAnalysis.Emit;
2123

2224
namespace Dotnet.Script.Core
2325
{
26+
public class ScriptEmitResult
27+
{
28+
public static ScriptEmitResult Error { get; } = new ScriptEmitResult();
29+
30+
private ScriptEmitResult() { }
31+
32+
public ScriptEmitResult(MemoryStream peStream, MemoryStream pdbStream)
33+
{
34+
PeStream = peStream;
35+
PdbStream = pdbStream;
36+
}
37+
38+
public MemoryStream PeStream { get; }
39+
public MemoryStream PdbStream { get; }
40+
}
41+
42+
public class ScriptEmitter
43+
{
44+
private readonly ScriptConsole _scriptConsole;
45+
private readonly ScriptCompiler _scriptCompiler;
46+
47+
public ScriptEmitter(ScriptConsole scriptConsole, ScriptCompiler scriptCompiler)
48+
{
49+
_scriptConsole = scriptConsole;
50+
_scriptCompiler = scriptCompiler;
51+
}
52+
53+
public virtual ScriptEmitResult Emit<TReturn>(ScriptContext context)
54+
{
55+
try
56+
{
57+
var compilationContext = _scriptCompiler.CreateCompilationContext<TReturn, CommandLineScriptGlobals>(context);
58+
var compilation = compilationContext.Script.GetCompilation();
59+
60+
using (var peStream = new MemoryStream())
61+
using (var pdbStream = new MemoryStream())
62+
{
63+
var result = compilation.Emit(peStream, pdbStream: pdbStream, options: new EmitOptions().
64+
WithDebugInformationFormat(DebugInformationFormat.PortablePdb));
65+
66+
if (result.Success)
67+
{
68+
return new ScriptEmitResult(peStream, pdbStream);
69+
}
70+
71+
return ScriptEmitResult.Error;
72+
}
73+
}
74+
catch (CompilationErrorException e)
75+
{
76+
foreach (var diagnostic in e.Diagnostics)
77+
{
78+
_scriptConsole.WritePrettyError(diagnostic.ToString());
79+
}
80+
81+
throw;
82+
}
83+
}
84+
}
85+
2486
public class ScriptCompiler
2587
{
2688
// Note: Windows only, Mac and Linux needs something else?

src/Dotnet.Script.Core/ScriptRunner.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
using System.Reflection;
13
using System.Threading.Tasks;
24
using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting;
35
using Microsoft.CodeAnalysis.Scripting;
@@ -18,6 +20,19 @@ public ScriptRunner(ScriptCompiler scriptCompiler, ScriptLogger logger, ScriptCo
1820
ScriptConsole = scriptConsole;
1921
}
2022

23+
public async Task<TReturn> Execute<TReturn>(string dllPath)
24+
{
25+
var assembly = Assembly.LoadFrom(dllPath);
26+
27+
var type = assembly.GetType("Submission#0");
28+
var method = type.GetMethod("<Factory>", BindingFlags.Static | BindingFlags.Public);
29+
30+
var submissionStates = new object[2];
31+
submissionStates[0] = new CommandLineScriptGlobals(ScriptConsole.Out, CSharpObjectFormatter.Instance);
32+
var resultTask = method.Invoke(null, new[] { submissionStates }) as Task<TReturn>;
33+
return await resultTask;
34+
}
35+
2136
public Task<TReturn> Execute<TReturn>(ScriptContext context)
2237
{
2338
var globals = new CommandLineScriptGlobals(ScriptConsole.Out, CSharpObjectFormatter.Instance);

src/Dotnet.Script/Program.cs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private static int Wain(string[] args)
5959
var interactive = app.Option("-i | --interactive", "Execute a script and drop into the interactive mode afterwards.", CommandOptionType.NoValue);
6060

6161
var configuration = app.Option("-c | --configuration <configuration>", "Configuration to use for running the script [Release/Debug] Default is \"Debug\"", CommandOptionType.SingleValue);
62-
62+
6363
var debugMode = app.Option(DebugFlagShort + " | " + DebugFlagLong, "Enables debug output.", CommandOptionType.NoValue);
6464

6565
var argsBeforeDoubleHyphen = args.TakeWhile(a => a != "--").ToArray();
@@ -86,6 +86,44 @@ private static int Wain(string[] args)
8686
});
8787
});
8888

89+
app.Command("compile", c =>
90+
{
91+
c.Description = "Compile a script into a DLL.";
92+
var scriptPath = c.Argument("script", "Path to CSX script");
93+
var outputDll = c.Option("-o |--output <path>", "Path to which the file should be output. Defaults to <scriptName>.dll in current directory.", CommandOptionType.SingleValue);
94+
95+
c.OnExecute(() =>
96+
{
97+
int exitCode = 0;
98+
if (!string.IsNullOrWhiteSpace(scriptPath.Value))
99+
{
100+
var optimizationLevel = OptimizationLevel.Debug;
101+
if (configuration.HasValue() && configuration.Value().ToLower() == "release")
102+
{
103+
optimizationLevel = OptimizationLevel.Release;
104+
}
105+
exitCode = CompileToDll(scriptPath.Value, outputDll.Value(), debugMode.HasValue(), optimizationLevel, app.RemainingArguments.Concat(argsAfterDoubleHypen));
106+
}
107+
return exitCode;
108+
});
109+
});
110+
111+
app.Command("exec", c =>
112+
{
113+
c.Description = "Run a script from a DLL.";
114+
var dllPath = c.Argument("dll", "Path to DLL based script");
115+
116+
c.OnExecute(async () =>
117+
{
118+
int exitCode = 0;
119+
if (!string.IsNullOrWhiteSpace(dllPath.Value))
120+
{
121+
exitCode = await RunFromDll(dllPath.Value);
122+
}
123+
return exitCode;
124+
});
125+
});
126+
89127
app.Command("init", c =>
90128
{
91129
c.Description = "Creates a sample script along with the launch.json file needed to launch and debug the script.";
@@ -185,6 +223,54 @@ private static Task<int> Run(bool debugMode, ScriptContext context)
185223
return runner.Execute<int>(context);
186224
}
187225

226+
private static int CompileToDll(string file, string outputPath, bool debugMode, OptimizationLevel optimizationLevel, IEnumerable<string> args)
227+
{
228+
if (!File.Exists(file))
229+
{
230+
throw new Exception($"Couldn't find file '{file}'");
231+
}
232+
233+
var absoluteFilePath = Path.IsPathRooted(file) ? file : Path.Combine(Directory.GetCurrentDirectory(), file);
234+
var absoluteOutputPath = outputPath != null ? Path.IsPathRooted(outputPath) ? outputPath : Path.Combine(Directory.GetCurrentDirectory(), outputPath) : Path.ChangeExtension(absoluteFilePath, "dll");
235+
var directory = Path.GetDirectoryName(absoluteFilePath);
236+
237+
using (var filestream = new FileStream(absoluteFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
238+
{
239+
var sourceText = SourceText.From(filestream);
240+
var context = new ScriptContext(sourceText, directory, args, absoluteFilePath, optimizationLevel);
241+
var compiler = GetScriptCompiler(debugMode);
242+
var emitter = new ScriptEmitter(ScriptConsole.Default, compiler);
243+
var result = emitter.Emit<int>(context);
244+
245+
if (result == ScriptEmitResult.Error)
246+
{
247+
return -1;
248+
}
249+
250+
using (result.PeStream)
251+
using (result.PdbStream)
252+
{
253+
File.WriteAllBytes(absoluteOutputPath, result.PeStream.ToArray());
254+
File.WriteAllBytes(Path.ChangeExtension(absoluteOutputPath, "pdb"), result.PdbStream.ToArray());
255+
return 0;
256+
}
257+
}
258+
}
259+
260+
private static Task<int> RunFromDll(string file)
261+
{
262+
if (!File.Exists(file))
263+
{
264+
throw new Exception($"Couldn't find file '{file}'");
265+
}
266+
267+
var absoluteFilePath = Path.IsPathRooted(file) ? file : Path.Combine(Directory.GetCurrentDirectory(), file);
268+
269+
var compiler = GetScriptCompiler(false);
270+
var runner = new ScriptRunner(compiler, compiler.Logger, ScriptConsole.Default);
271+
return runner.Execute<int>(absoluteFilePath);
272+
}
273+
188274
private static string GetVersionInfo()
189275
{
190276
var versionAttribute = typeof(Program).GetTypeInfo().Assembly.GetCustomAttributes<AssemblyInformationalVersionAttribute>().SingleOrDefault();

0 commit comments

Comments
 (0)