Skip to content

Instantly share code, notes, and snippets.

@laurentkempe
Last active December 14, 2020 16:10
Show Gist options
  • Save laurentkempe/1ea03376d0ef2a4da3358ab2629cccf2 to your computer and use it in GitHub Desktop.
Save laurentkempe/1ea03376d0ef2a4da3358ab2629cccf2 to your computer and use it in GitHub Desktop.
DynamicRun
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));
}
}
}
using System;
namespace Hello
{
class DynamicProgram
{
static void Main(string[] args)
{
Console.WriteLine($"Hello World from {args[0]}!");
}
}
}
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();
}
}
}
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();
}
}
}
}
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);
}
}
}
}
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