Last active
December 14, 2020 16:10
-
-
Save laurentkempe/1ea03376d0ef2a4da3358ab2629cccf2 to your computer and use it in GitHub Desktop.
DynamicRun
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Linq; | |
using Microsoft.CodeAnalysis; | |
using Microsoft.CodeAnalysis.CSharp; | |
using Microsoft.CodeAnalysis.Text; | |
namespace DynamicRun.Builder | |
{ | |
internal class Compiler | |
{ | |
public byte[] Compile(string filepath) | |
{ | |
Console.WriteLine($"Starting compilation of: '{filepath}'"); | |
var sourceCode = File.ReadAllText(filepath); | |
using (var peStream = new MemoryStream()) | |
{ | |
var result = GenerateCode(sourceCode).Emit(peStream); | |
if (!result.Success) | |
{ | |
Console.WriteLine("Compilation done with error."); | |
var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); | |
foreach (var diagnostic in failures) | |
{ | |
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); | |
} | |
return null; | |
} | |
Console.WriteLine("Compilation done without any error."); | |
peStream.Seek(0, SeekOrigin.Begin); | |
return peStream.ToArray(); | |
} | |
} | |
private static CSharpCompilation GenerateCode(string sourceCode) | |
{ | |
var codeString = SourceText.From(sourceCode); | |
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3); | |
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options); | |
var references = new MetadataReference[] | |
{ | |
MetadataReference.CreateFromFile(typeof(object).Assembly.Location), | |
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), | |
MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location), | |
MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location), | |
}; | |
return CSharpCompilation.Create("Hello.dll", | |
new[] { parsedSyntaxTree }, | |
references: references, | |
options: new CSharpCompilationOptions(OutputKind.ConsoleApplication, | |
optimizationLevel: OptimizationLevel.Release, | |
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default)); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace Hello | |
{ | |
class DynamicProgram | |
{ | |
static void Main(string[] args) | |
{ | |
Console.WriteLine($"Hello World from {args[0]}!"); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Reactive.Linq; | |
namespace DynamicRun.Builder | |
{ | |
/// <summary> | |
/// This is a wrapper around a file system watcher to use the Rx framework instead of event handlers to handle | |
/// notifications of file system changes. | |
/// </summary> | |
public class ObservableFileSystemWatcher : IDisposable | |
{ | |
public readonly FileSystemWatcher Watcher; | |
public IObservable<FileSystemEventArgs> Changed { get; private set; } | |
public IObservable<RenamedEventArgs> Renamed { get; private set; } | |
public IObservable<FileSystemEventArgs> Deleted { get; private set; } | |
public IObservable<ErrorEventArgs> Errors { get; private set; } | |
public IObservable<FileSystemEventArgs> Created { get; private set; } | |
/// <summary> | |
/// Pass an existing FileSystemWatcher instance, this is just for the case where it's not possible to only pass the | |
/// configuration, be aware that disposing this wrapper will dispose the FileSystemWatcher instance too. | |
/// </summary> | |
/// <param name="watcher"></param> | |
public ObservableFileSystemWatcher(FileSystemWatcher watcher) | |
{ | |
Watcher = watcher; | |
Changed = Observable | |
.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => Watcher.Changed += h, h => Watcher.Changed -= h) | |
.Select(x => x.EventArgs); | |
Renamed = Observable | |
.FromEventPattern<RenamedEventHandler, RenamedEventArgs>(h => Watcher.Renamed += h, h => Watcher.Renamed -= h) | |
.Select(x => x.EventArgs); | |
Deleted = Observable | |
.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => Watcher.Deleted += h, h => Watcher.Deleted -= h) | |
.Select(x => x.EventArgs); | |
Errors = Observable | |
.FromEventPattern<ErrorEventHandler, ErrorEventArgs>(h => Watcher.Error += h, h => Watcher.Error -= h) | |
.Select(x => x.EventArgs); | |
Created = Observable | |
.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => Watcher.Created += h, h => Watcher.Created -= h) | |
.Select(x => x.EventArgs); | |
} | |
/// <summary> | |
/// Pass a function to configure the FileSystemWatcher as desired, this constructor will manage creating and applying | |
/// the configuration. | |
/// </summary> | |
public ObservableFileSystemWatcher(Action<FileSystemWatcher> configure) | |
: this(new FileSystemWatcher()) | |
{ | |
configure(Watcher); | |
} | |
public void Start() | |
{ | |
Watcher.EnableRaisingEvents = true; | |
} | |
public void Stop() | |
{ | |
Watcher.EnableRaisingEvents = false; | |
} | |
public void Dispose() | |
{ | |
Watcher.Dispose(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using DynamicRun.Builder; | |
using System; | |
using System.IO; | |
using System.Reactive.Linq; | |
namespace DynamicRun | |
{ | |
class Program | |
{ | |
static void Main() | |
{ | |
var sourcesPath = Path.Combine(Environment.CurrentDirectory, "Sources"); | |
Console.WriteLine($"Running from: {Environment.CurrentDirectory}"); | |
Console.WriteLine($"Sources from: {sourcesPath}"); | |
Console.WriteLine("Modify the sources to compile and run it!"); | |
var compiler = new Compiler(); | |
var runner = new Runner(); | |
using (var watcher = new ObservableFileSystemWatcher(c => { c.Path = @".\Sources"; })) | |
{ | |
var changes = watcher.Changed.Throttle(TimeSpan.FromSeconds(.5)).Where(c => c.FullPath.EndsWith(@"DynamicProgram.cs")).Select(c => c.FullPath); | |
changes.Subscribe(filepath => runner.Execute(compiler.Compile(filepath), new[] { "France" })); | |
watcher.Start(); | |
Console.WriteLine("Press any key to exit!"); | |
Console.ReadLine(); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Runtime.CompilerServices; | |
namespace DynamicRun.Builder | |
{ | |
internal class Runner | |
{ | |
public void Execute(byte[] compiledAssembly, string[] args) | |
{ | |
var assemblyLoadContextWeakRef = LoadAndExecute(compiledAssembly, args); | |
for (var i = 0; i < 8 && assemblyLoadContextWeakRef.IsAlive; i++) | |
{ | |
GC.Collect(); | |
GC.WaitForPendingFinalizers(); | |
} | |
Console.WriteLine(assemblyLoadContextWeakRef.IsAlive ? "Unloading failed!" : "Unloading success!"); | |
} | |
[MethodImpl(MethodImplOptions.NoInlining)] | |
private static WeakReference LoadAndExecute(byte[] compiledAssembly, string[] args) | |
{ | |
using (var asm = new MemoryStream(compiledAssembly)) | |
{ | |
var assemblyLoadContext = new SimpleUnloadableAssemblyLoadContext(); | |
var assembly = assemblyLoadContext.LoadFromStream(asm); | |
var entry = assembly.EntryPoint; | |
_ = entry != null && entry.GetParameters().Length > 0 | |
? entry.Invoke(null, new object[] {args}) | |
: entry.Invoke(null, null); | |
assemblyLoadContext.Unload(); | |
return new WeakReference(assemblyLoadContext); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Reflection; | |
using System.Runtime.Loader; | |
namespace DynamicRun.Builder | |
{ | |
internal class SimpleUnloadableAssemblyLoadContext : AssemblyLoadContext | |
{ | |
public SimpleUnloadableAssemblyLoadContext() | |
: base(true) | |
{ | |
} | |
protected override Assembly Load(AssemblyName assemblyName) | |
{ | |
return null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment