public static async Task Main(string[] args) { bool waitForDebugger = false; Guid id = default; bool hotReloadEnabled = false; ProcessArgs(); using Pipe inOut = await Pipe.StartServerAsync(Pipe.InOutName, id, bidirectional : true, cancellationToken : default).ConfigureAwait(false); using BinaryReader pipeReader = new(inOut.Stream, Encoding.UTF8, leaveOpen : true); using (await Log.InitializeAsync(id).ConfigureAwait(false)) { await Log.WriteLineAsync($"Host started {RuntimeInformation.FrameworkDescription} ({RuntimeInformation.ProcessArchitecture})").ConfigureAwait(false); #if DEBUG await WaitForDebuggerAsync().ConfigureAwait(false); #else waitForDebugger.ToString(); #endif CancellationTokenSource cancelHotReloadLoop = new(); if (hotReloadEnabled) { #if NET6_0_OR_GREATER _ = HotReloadLoopAsync(cancelHotReloadLoop.Token); #endif } while (true) { try { await Log.WriteLineAsync("Waiting for next command").ConfigureAwait(false); ProjectData data; string typeName; using (ShutdownTimer.Start(s_shutdownTimerInterval)) { data = new ProjectData ( ProjectFilePath: pipeReader.ReadString(), Debug: pipeReader.ReadBoolean(), Platform: (Platform)pipeReader.ReadByte(), TargetFramework: pipeReader.ReadString(), Exe: pipeReader.ReadBoolean() ); typeName = pipeReader.ReadString(); } await Log.WriteLineAsync($"ANALYZE {data.ProjectFilePath} {typeName}").ConfigureAwait(false); try { ProjectLoader project = await ProjectLoader.GetOrCreateAsync(inOut.Stream, data).ConfigureAwait(false); await project.AnalyzeAsync(typeName).ConfigureAwait(false); } finally { inOut.Stream.WriteByte(0x27); } } catch (EndOfStreamException) { await Log.WriteLineAsync("End of stream").ConfigureAwait(false); break; } catch (Exception ex) { await Log.WriteLineAsync(ex.ToStringDemystified()).ConfigureAwait(false); } } cancelHotReloadLoop.Cancel(); await ProjectLoader.DisposeAllAsync().ConfigureAwait(false); } void ProcessArgs() { foreach (string arg in args) { if (arg.Equals("-wait", StringComparison.OrdinalIgnoreCase)) { waitForDebugger = true; } else if (arg.Equals("-hotreload", StringComparison.OrdinalIgnoreCase)) { hotReloadEnabled = true; } else if (arg.StartsWith("-id:", StringComparison.OrdinalIgnoreCase)) { if (Guid.TryParse(arg.Substring(4).Trim(), out Guid guid)) { id = guid; } } } if (id == default) { throw new InvalidOperationException("Parameter -id not found"); } } #if DEBUG async Task WaitForDebuggerAsync() { if (waitForDebugger) { int pid = #if NET5_0_OR_GREATER Environment.ProcessId; #else Process.GetCurrentProcess().Id; #endif await Log.WriteLineAsync($"PID {pid} Waiting for debugger...").ConfigureAwait(false); using (ShutdownTimer.Start(TimeSpan.FromMinutes(1))) while (!Debugger.IsAttached) { Thread.Sleep(TimeSpan.FromSeconds(1)); } await Log.WriteLineAsync("Debugger attached").ConfigureAwait(false); } } #endif #if NET6_0_OR_GREATER async Task HotReloadLoopAsync(CancellationToken cancellationToken) { try { Type updateHandler = typeof(object).Assembly.GetType("System.Reflection.Metadata.RuntimeTypeMetadataUpdateHandler") !; using Pipe pipe = await Pipe.ConnectAsync(Pipe.HotReloadName, id, bidirectional : true, cancellationToken).ConfigureAwait(false); using BinaryReader reader = new(pipe.Stream, Encoding.UTF8, leaveOpen : true); await Log.WriteLineAsync("HOT RELOAD Pipe connected").ConfigureAwait(false); while (true) { cancellationToken.ThrowIfCancellationRequested(); try { string projectFilePath = reader.ReadString(); string path = reader.ReadString(); int count = reader.ReadInt32(); var updates = new (Guid ModuleId, byte[] MetadataDelta, byte[] ILDelta)[count]; for (int i = 0; i < updates.Length; i++) { (Guid ModuleId, byte[] MetadataDelta, byte[] ILDelta)update = new() { ModuleId = Guid.Parse(reader.ReadString()), MetadataDelta = await ReadArrayAsync(reader, cancellationToken).ConfigureAwait(false), ILDelta = await ReadArrayAsync(reader, cancellationToken).ConfigureAwait(false), }; updates[i] = update; } bool success = true; ProjectLoader?projectLoader = ProjectLoader.Get(projectFilePath); if (projectLoader is null) { success = false; } else { foreach (var update in updates) { Assembly?assembly = AppDomain.CurrentDomain .GetAssemblies() .FirstOrDefault(a => a.Modules.FirstOrDefault() is Module m && m.ModuleVersionId == update.ModuleId); if (assembly is null) { success = false; } else { System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assembly, update.MetadataDelta, update.ILDelta, ReadOnlySpan <byte> .Empty); await Log.WriteLineAsync($"{count} updates applied successfully").ConfigureAwait(false); updateHandler.InvokeMember("BeforeUpdate", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, null, null, new[] { default(object) }); await Log.WriteLineAsync("UpdateHandler.BeforeUpdate(null) called").ConfigureAwait(false); } } } pipe.Stream.WriteByte(success ? (byte)0 : (byte)0xff); } catch (OperationCanceledException) { await Log.WriteLineAsync("HOT RELOAD Leaving apply loop").ConfigureAwait(false); break; }
public void Dispose() { ShutdownTimer.Stop(); }