-
-
Save jrgcubano/422273891ebd3868a73ea9f0ea2c2f96 to your computer and use it in GitHub Desktop.
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.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