Skip to content

Instantly share code, notes, and snippets.

@jrgcubano
Forked from jnm2/RunSimultaneously.cake
Created March 9, 2017 14:43
Show Gist options
  • Save jrgcubano/422273891ebd3868a73ea9f0ea2c2f96 to your computer and use it in GitHub Desktop.
Save jrgcubano/422273891ebd3868a73ea9f0ea2c2f96 to your computer and use it in GitHub Desktop.
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading;
using System.Xml.Linq;
public void RunSimultaneously(params Action[] actions)
{
if (actions.Length == 0) return;
var context = Context;
var processRunnerField = typeof(CakeContext).GetField("<ProcessRunner>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance);
var consoleField = Assembly.Load("Cake").GetType("Cake.Diagnostics.CakeBuildLog").GetField("_console", BindingFlags.NonPublic | BindingFlags.Instance);
var originalProcessRunner = context.ProcessRunner;
var log = context.Log;
var originalConsole = (IConsole)consoleField.GetValue(log);
AsyncLocal<TaskOutputBuffer> bufferAccessAsyncLocal;
var alreadyinstalledBufferedProcessRunner = originalProcessRunner as BufferedOutputProcessRunner;
if (alreadyinstalledBufferedProcessRunner != null)
{
bufferAccessAsyncLocal = alreadyinstalledBufferedProcessRunner.BufferAccess;
}
else
{
bufferAccessAsyncLocal = new AsyncLocal<TaskOutputBuffer>();
processRunnerField.SetValue(context, new BufferedOutputProcessRunner(context.ProcessRunner, bufferAccessAsyncLocal));
}
try
{
if (alreadyinstalledBufferedProcessRunner == null) consoleField.SetValue(log, new BufferedOutputConsole(originalConsole, bufferAccessAsyncLocal));
try
{
var outputBuffers = new BlockingCollection<TaskOutputBuffer>();
var actionOrderLock = new object();
var nextActionIndex = 0;
var tasks = new System.Threading.Tasks.Task[actions.Length];
for (var i = 0; i < tasks.Length; i++)
tasks[i] = System.Threading.Tasks.Task.Run(() =>
{
using (var outputBuffer = new TaskOutputBuffer())
{
// Ensure that outputBuffers.Add is called in the same order as actions, even if tasks start out of order
Action action;
lock (actionOrderLock)
{
action = actions[nextActionIndex];
nextActionIndex++;
outputBuffers.Add(outputBuffer);
}
bufferAccessAsyncLocal.Value = outputBuffer;
action.Invoke();
}
});
for (var i = 0; i < actions.Length; i++)
outputBuffers.Take().WriteOutputUntilCompleted(originalConsole);
System.Threading.Tasks.Task.WaitAll(tasks); // throw exceptions, if any
}
finally
{
consoleField.SetValue(log, originalConsole);
}
}
finally
{
processRunnerField.SetValue(context, originalProcessRunner);
}
}
private sealed class TaskOutputBuffer : IDisposable
{
private readonly BlockingCollection<Action<IConsole>> outputActions = new BlockingCollection<Action<IConsole>>();
public void AppendAction(Action<IConsole> outputAction)
{
outputActions.Add(outputAction);
}
public void Dispose()
{
outputActions.CompleteAdding();
}
public void WriteOutputUntilCompleted(IConsole console)
{
Action<IConsole> action;
while (outputActions.TryTake(out action, Timeout.Infinite))
action.Invoke(console);
}
}
private sealed class BufferedOutputConsole : IConsole
{
private readonly IConsole internalConsole;
private readonly AsyncLocal<TaskOutputBuffer> bufferAccess;
internal AsyncLocal<TaskOutputBuffer> BufferAccess { get { return bufferAccess; } }
public BufferedOutputConsole(IConsole internalConsole, AsyncLocal<TaskOutputBuffer> bufferAccess)
{
this.internalConsole = internalConsole;
this.bufferAccess = bufferAccess;
}
public void Write(string format, params object[] arg)
{
var outputBuffer = bufferAccess.Value;
if (outputBuffer != null)
bufferAccess.Value.AppendAction(console => console.Write(format, arg));
else
internalConsole.Write(format, arg);
}
public void WriteLine(string format, params object[] arg)
{
var outputBuffer = bufferAccess.Value;
if (outputBuffer != null)
bufferAccess.Value.AppendAction(console => console.WriteLine(format, arg));
else
internalConsole.WriteLine(format, arg);
}
public void WriteError(string format, params object[] arg)
{
var outputBuffer = bufferAccess.Value;
if (outputBuffer != null)
bufferAccess.Value.AppendAction(console => console.WriteError(format, arg));
else
internalConsole.WriteError(format, arg);
}
public void WriteErrorLine(string format, params object[] arg)
{
var outputBuffer = bufferAccess.Value;
if (outputBuffer != null)
bufferAccess.Value.AppendAction(console => console.WriteErrorLine(format, arg));
else
internalConsole.WriteErrorLine(format, arg);
}
public ConsoleColor ForegroundColor
{
get
{
throw new NotImplementedException();
}
set
{
var outputBuffer = bufferAccess.Value;
if (outputBuffer != null)
bufferAccess.Value.AppendAction(console => console.ForegroundColor = value);
else
internalConsole.ForegroundColor = value;
}
}
public ConsoleColor BackgroundColor
{
get
{
throw new NotImplementedException();
}
set
{
var outputBuffer = bufferAccess.Value;
if (outputBuffer != null)
bufferAccess.Value.AppendAction(console => console.BackgroundColor = value);
else
internalConsole.BackgroundColor = value;
}
}
public void ResetColor()
{
var outputBuffer = bufferAccess.Value;
if (outputBuffer != null)
bufferAccess.Value.AppendAction(console => console.ResetColor());
else
internalConsole.ResetColor();
}
}
private sealed class BufferedOutputProcessRunner : IProcessRunner
{
private readonly IProcessRunner internalRunner;
private readonly AsyncLocal<TaskOutputBuffer> bufferAccess;
internal AsyncLocal<TaskOutputBuffer> BufferAccess { get { return bufferAccess; } }
public BufferedOutputProcessRunner(IProcessRunner internalRunner, AsyncLocal<TaskOutputBuffer> bufferAccess)
{
this.internalRunner = internalRunner;
this.bufferAccess = bufferAccess;
}
public IProcess Start(FilePath filePath, ProcessSettings settings)
{
var outputBuffer = bufferAccess.Value;
if (outputBuffer == null) return internalRunner.Start(filePath, settings);
settings.RedirectStandardOutput = true;
var wrapper = internalRunner.Start(filePath, settings);
outputBuffer.AppendAction(console =>
{
foreach (var line in wrapper.GetStandardOutput())
console.WriteLine(line.Replace("{", "{{").Replace("}", "}}"));
});
return wrapper;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment