diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/BuildJob.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/BuildJob.cs
new file mode 100644
index 0000000000..3002226b46
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/BuildJob.cs
@@ -0,0 +1,90 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ICSharpCode.SharpDevelop.BuildWorker
+{
+ ///
+ /// The settings used to start a build.
+ ///
+ [Serializable]
+ public class BuildJob
+ {
+ ///
+ /// The value of IntPtr.Size on the host. The build worker will report an error if its IntPtr.Size
+ /// doesn't match. This is a safety feature to prevent compiling half of a solution using 32-bit MSBuild
+ /// and the other half using 64-bit MSBuild.
+ ///
+ public int IntPtrSize { get; set; }
+
+
+ public string ProjectFileName { get; set; }
+ public string Target { get; set; }
+
+ EventTypes eventMask = EventTypes.All;
+
+ ///
+ /// Gets/Sets the mask that controls which events are reported back to the host.
+ ///
+ public EventTypes EventMask {
+ get { return eventMask; }
+ set { eventMask = value; }
+ }
+
+ Dictionary properties = new Dictionary();
+
+ public Dictionary Properties {
+ get { return properties; }
+ }
+
+ List additionalImports = new List();
+
+ public IList AdditionalImports {
+ get { return additionalImports; }
+ }
+
+ HashSet interestingTaskNames = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ public ICollection InterestingTaskNames {
+ get { return interestingTaskNames; }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder b = new StringBuilder();
+ b.AppendLine("BuildJob:");
+ b.AppendLine(" ProjectFileName = " + ProjectFileName);
+ b.AppendLine(" Target = " + Target);
+ b.AppendLine(" " + Properties.Count + " Properties:");
+ foreach (KeyValuePair pair in Properties) {
+ b.AppendLine(" " + pair.Key + " = " + pair.Value);
+ }
+ b.AppendLine(" " + AdditionalImports.Count + " Additional Imports:");
+ foreach (string import in AdditionalImports) {
+ b.AppendLine(" " + import);
+ }
+ b.AppendLine(" " + InterestingTaskNames.Count + " Interesting Task Names:");
+ foreach (string name in InterestingTaskNames) {
+ b.AppendLine(" " + name);
+ }
+ return b.ToString();
+ }
+
+ [NonSerialized]
+ internal Action CancelCallback;
+
+ public void Cancel()
+ {
+ if (CancelCallback != null) {
+ CancelCallback();
+ }
+ }
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Configuration/AssemblyInfo.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Configuration/AssemblyInfo.cs
new file mode 100644
index 0000000000..3d3864f779
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Configuration/AssemblyInfo.cs
@@ -0,0 +1,17 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+[assembly: AssemblyTitle("SharpDevelop Build Worker")]
+[assembly: AssemblyDescription("Runs MSBuild")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: CLSCompliant(true)]
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/EventSource.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/EventSource.cs
new file mode 100644
index 0000000000..7818baf8e4
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/EventSource.cs
@@ -0,0 +1,131 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using Microsoft.Build.Framework;
+
+namespace ICSharpCode.SharpDevelop.BuildWorker
+{
+ [Flags]
+ public enum EventTypes
+ {
+ None = 0,
+ Message = 0x0001,
+ Error = 0x0002,
+ Warning = 0x0004,
+ BuildStarted = 0x0008,
+ BuildFinished = 0x0010,
+ ProjectStarted = 0x0020,
+ ProjectFinished = 0x0040,
+ TargetStarted = 0x0080,
+ TargetFinished = 0x0100,
+ TaskStarted = 0x0200,
+ TaskFinished = 0x0400,
+ Custom = 0x0800,
+ Unknown = 0x1000,
+ All = 0x1fff
+ }
+
+ class EventSource : IEventSource
+ {
+ public event BuildMessageEventHandler MessageRaised;
+ public event BuildErrorEventHandler ErrorRaised;
+ public event BuildWarningEventHandler WarningRaised;
+ public event BuildStartedEventHandler BuildStarted;
+ public event BuildFinishedEventHandler BuildFinished;
+ public event ProjectStartedEventHandler ProjectStarted;
+ public event ProjectFinishedEventHandler ProjectFinished;
+ public event TargetStartedEventHandler TargetStarted;
+ public event TargetFinishedEventHandler TargetFinished;
+ public event TaskStartedEventHandler TaskStarted;
+ public event TaskFinishedEventHandler TaskFinished;
+ public event CustomBuildEventHandler CustomEventRaised;
+ public event BuildStatusEventHandler StatusEventRaised;
+
+ public event AnyEventHandler AnyEventRaised;
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
+ public void RaiseEvent(BuildEventArgs e)
+ {
+ if (e is BuildStatusEventArgs) {
+ if (e is TaskStartedEventArgs) {
+ if (TaskStarted != null)
+ TaskStarted(this, (TaskStartedEventArgs)e);
+ } else if (e is TaskFinishedEventArgs) {
+ if (TaskFinished != null)
+ TaskFinished(this, (TaskFinishedEventArgs)e);
+ } else if (e is TargetStartedEventArgs) {
+ if (TargetStarted != null)
+ TargetStarted(this, (TargetStartedEventArgs)e);
+ } else if (e is TargetFinishedEventArgs) {
+ if (TargetFinished != null)
+ TargetFinished(this, (TargetFinishedEventArgs)e);
+ } else if (e is ProjectStartedEventArgs) {
+ if (ProjectStarted != null)
+ ProjectStarted(this, (ProjectStartedEventArgs)e);
+ } else if (e is ProjectFinishedEventArgs) {
+ if (ProjectFinished != null)
+ ProjectFinished(this, (ProjectFinishedEventArgs)e);
+ } else if (e is BuildStartedEventArgs) {
+ if (BuildStarted != null)
+ BuildStarted(this, (BuildStartedEventArgs)e);
+ } else if (e is BuildFinishedEventArgs) {
+ if (BuildFinished != null)
+ BuildFinished(this, (BuildFinishedEventArgs)e);
+ }
+ if (StatusEventRaised != null)
+ StatusEventRaised(this, (BuildStatusEventArgs)e);
+ } else if (e is BuildMessageEventArgs) {
+ if (MessageRaised != null)
+ MessageRaised(this, (BuildMessageEventArgs)e);
+ } else if (e is BuildWarningEventArgs) {
+ if (WarningRaised != null)
+ WarningRaised(this, (BuildWarningEventArgs)e);
+ } else if (e is BuildErrorEventArgs) {
+ if (ErrorRaised != null)
+ ErrorRaised(this, (BuildErrorEventArgs)e);
+ } else if (e is CustomBuildEventArgs) {
+ if (CustomEventRaised != null)
+ CustomEventRaised(this, (CustomBuildEventArgs)e);
+ }
+
+ if (AnyEventRaised != null)
+ AnyEventRaised(this, e);
+ }
+
+ public static EventTypes GetEventType(BuildEventArgs e)
+ {
+ if (e is TaskStartedEventArgs) {
+ return EventTypes.TaskStarted;
+ } else if (e is TaskFinishedEventArgs) {
+ return EventTypes.TaskFinished;
+ } else if (e is TargetStartedEventArgs) {
+ return EventTypes.TargetStarted;
+ } else if (e is TargetFinishedEventArgs) {
+ return EventTypes.TargetFinished;
+ } else if (e is BuildMessageEventArgs) {
+ return EventTypes.Message;
+ } else if (e is BuildWarningEventArgs) {
+ return EventTypes.Warning;
+ } else if (e is BuildErrorEventArgs) {
+ return EventTypes.Error;
+ } else if (e is ProjectStartedEventArgs) {
+ return EventTypes.ProjectStarted;
+ } else if (e is ProjectFinishedEventArgs) {
+ return EventTypes.ProjectFinished;
+ } else if (e is BuildStartedEventArgs) {
+ return EventTypes.BuildStarted;
+ } else if (e is BuildFinishedEventArgs) {
+ return EventTypes.BuildFinished;
+ } else if (e is CustomBuildEventArgs) {
+ return EventTypes.Custom;
+ } else {
+ return EventTypes.Unknown;
+ }
+ }
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/ICSharpCode.SharpDevelop.BuildWorker.csproj b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/ICSharpCode.SharpDevelop.BuildWorker.csproj
new file mode 100644
index 0000000000..ba871edb97
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/ICSharpCode.SharpDevelop.BuildWorker.csproj
@@ -0,0 +1,74 @@
+
+
+ {C3CBC8E3-81D8-4C5B-9941-DCCD12D50B1F}
+ Debug
+ AnyCPU
+ Exe
+ ICSharpCode.SharpDevelop.BuildWorker
+ ICSharpCode.SharpDevelop.BuildWorker
+ v3.5
+ False
+ False
+ 4
+ false
+ ..\..\..\bin\
+ True
+ ..\ICSharpCode.SharpDevelop.snk
+ False
+ File
+ False
+ -Microsoft.Globalization#CA1303
+
+
+ true
+ Full
+ False
+ True
+ DEBUG;TRACE
+
+
+ False
+ None
+ True
+ False
+ TRACE
+
+
+ False
+ Auto
+ 4194304
+ x86
+ 4096
+
+
+
+
+
+
+
+ 3.5
+
+
+
+
+
+
+ Configuration\GlobalAssemblyInfo.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/HostProcess.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/HostProcess.cs
new file mode 100644
index 0000000000..30f9d45384
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/HostProcess.cs
@@ -0,0 +1,112 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Threading;
+
+namespace ICSharpCode.SharpDevelop.BuildWorker.Interprocess
+{
+ ///
+ /// Used in the worker process to refer to the host.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ public class HostProcess
+ {
+ readonly object workerObject;
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters")]
+ public HostProcess(object workerObject)
+ {
+ if (workerObject == null)
+ throw new ArgumentNullException("workerObject");
+ this.workerObject = workerObject;
+ }
+
+ TcpClient client;
+ PacketReceiver receiver;
+ PacketSender sender;
+
+ ManualResetEvent shutdownEvent;
+ bool connectionIsLost;
+
+ internal const int SendKeepAliveInterval = 10000;
+
+ public void WorkerProcessMain(string argument, string passwordBase64)
+ {
+ int port = int.Parse(argument, CultureInfo.InvariantCulture);
+
+ client = new TcpClient();
+ client.Connect(new IPEndPoint(IPAddress.Loopback, port));
+ Stream stream = client.GetStream();
+ receiver = new PacketReceiver();
+ sender = new PacketSender(stream);
+ shutdownEvent = new ManualResetEvent(false);
+ receiver.ConnectionLost += OnConnectionLost;
+ receiver.PacketReceived += OnPacketReceived;
+ sender.WriteFailed += OnConnectionLost;
+
+ // send password
+ sender.Send(Convert.FromBase64String(passwordBase64));
+
+ receiver.StartReceive(stream);
+ while (!shutdownEvent.WaitOne(SendKeepAliveInterval, false)) {
+ Program.Log("Sending keep-alive packet");
+ sender.Send(new byte[0]);
+ }
+
+ Program.Log("Closing client (end of WorkerProcessMain)");
+ client.Close();
+ shutdownEvent.Close();
+ }
+
+ public void Disconnect()
+ {
+ client.Close();
+ }
+
+ public void CallMethodOnHost(string methodName, params object[] args)
+ {
+ Program.Log("CallMethodOnHost: " + methodName);
+ sender.Send(WorkerProcess.SerializeObject(new WorkerProcess.MethodCall(methodName, args)));
+ }
+
+ void OnConnectionLost(object sender, EventArgs e)
+ {
+ lock (this) {
+ if (connectionIsLost)
+ return;
+ Program.Log("OnConnectionLost");
+ connectionIsLost = true;
+ shutdownEvent.Set();
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ void OnPacketReceived(object sender, PacketReceivedEventArgs e)
+ {
+ Program.Log("OnPacketReceived");
+ if (e.Packet.Length != 0) {
+ try {
+ WorkerProcess.MethodCall mc = (WorkerProcess.MethodCall)WorkerProcess.DeserializeObject(e.Packet);
+ mc.CallOn(workerObject);
+ } catch (TargetInvocationException ex) {
+ Program.Log(ex.ToString());
+ CallMethodOnHost("ReportException", ex.InnerException.ToString());
+ } catch (Exception ex) {
+ Program.Log(ex.ToString());
+ CallMethodOnHost("ReportException", ex.ToString());
+ }
+ }
+ }
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/IHostObject.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/IHostObject.cs
new file mode 100644
index 0000000000..df1e484c6c
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/IHostObject.cs
@@ -0,0 +1,16 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+
+namespace ICSharpCode.SharpDevelop.BuildWorker.Interprocess
+{
+ public interface IHostObject
+ {
+ void ReportException(string exceptionText);
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketReceiver.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketReceiver.cs
new file mode 100644
index 0000000000..ad9700d731
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketReceiver.cs
@@ -0,0 +1,148 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters;
+using System.Runtime.Serialization.Formatters.Binary;
+
+namespace ICSharpCode.SharpDevelop.BuildWorker.Interprocess
+{
+ public sealed class PacketReceiver
+ {
+ Stream stream;
+ byte[] buffer = new byte[10000];
+ int bufferReadOffset;
+
+ public void StartReceive(Stream stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException("stream");
+ if (this.stream != null)
+ throw new InvalidOperationException("StartReceive can be called only once.");
+ this.stream = stream;
+ try {
+ stream.BeginRead(buffer, 0, buffer.Length, OnReceive, null);
+ } catch (ObjectDisposedException) {
+ OnConnectionLost();
+ } catch (IOException) {
+ OnConnectionLost();
+ }
+ }
+
+ int maxPacketSize = int.MaxValue - 20000;
+
+ ///
+ /// Gets/Sets the maximum allowed packet size in bytes.
+ ///
+ public int MaxPacketSize {
+ get { return maxPacketSize; }
+ set {
+ if (value < 1)
+ throw new ArgumentOutOfRangeException("value", value, "MaxPacketSize must be >0");
+ maxPacketSize = value;
+ }
+ }
+
+ void OnReceive(IAsyncResult ar)
+ {
+ int bytes;
+ try {
+ bytes = stream.EndRead(ar);
+ } catch (ObjectDisposedException) {
+ OnConnectionLost();
+ return;
+ } catch (IOException) {
+ OnConnectionLost();
+ return;
+ }
+ if (bytes == 0) {
+ // 0 bytes read indicates the end of the stream
+ OnConnectionLost();
+ return;
+ }
+ bufferReadOffset += bytes;
+ int packetStart = 0;
+ int packetSize = -1;
+ while (bufferReadOffset >= packetStart + 4) {
+ packetSize = BitConverter.ToInt32(buffer, packetStart);
+ if (packetSize < 4)
+ throw new ProtocolViolationException("packetSize must be > 4");
+ if (packetSize - 4 > MaxPacketSize)
+ throw new ProtocolViolationException("packetSize must be smaller than MaxPacketSize");
+ if (bufferReadOffset >= packetStart + packetSize) {
+ //Debug.WriteLine("receiving packet of size " + packetSize);
+ byte[] packet = new byte[packetSize - 4];
+ Array.Copy(buffer, packetStart + 4, packet, 0, packet.Length);
+ OnPacketReceived(packet);
+ packetStart += packetSize;
+ } else {
+ break;
+ }
+ }
+ if (packetStart != 0) {
+ // copy half-received packet to the beginning of the buffer
+ int copyAmount = bufferReadOffset - packetStart;
+ for (int i = 0; i < copyAmount; i++) {
+ buffer[i] = buffer[i + packetStart];
+ }
+ bufferReadOffset = copyAmount;
+ }
+ if (packetSize > buffer.Length) {
+ Debug.WriteLine("resizing receive buffer for packet of size " + packetSize);
+ Array.Resize(ref buffer, Math.Max(packetSize, buffer.Length * 2));
+ }
+ if (bufferReadOffset >= buffer.Length) {
+ // should never happen - the buffer now is large enough to contain the packet,
+ // and we would have already processed the packet if received it completely
+ throw new InvalidOperationException("trying to read 0 bytes from socket");
+ }
+ try {
+ stream.BeginRead(buffer, bufferReadOffset, buffer.Length - bufferReadOffset, OnReceive, null);
+ } catch (ObjectDisposedException) {
+ OnConnectionLost();
+ } catch (IOException) {
+ OnConnectionLost();
+ }
+ }
+
+ void OnConnectionLost()
+ {
+ if (ConnectionLost != null)
+ ConnectionLost(this, EventArgs.Empty);
+ }
+
+ void OnPacketReceived(byte[] packet)
+ {
+ if (PacketReceived != null)
+ PacketReceived(this, new PacketReceivedEventArgs(packet));
+ }
+
+ public event EventHandler ConnectionLost;
+ public event EventHandler PacketReceived;
+ }
+
+ public class PacketReceivedEventArgs : EventArgs
+ {
+ byte[] packet;
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public byte[] Packet {
+ get { return packet; }
+ }
+
+ public PacketReceivedEventArgs(byte[] packet)
+ {
+ this.packet = packet;
+ }
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketSender.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketSender.cs
new file mode 100644
index 0000000000..61b5570bf8
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketSender.cs
@@ -0,0 +1,70 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.IO;
+using System.Threading;
+
+namespace ICSharpCode.SharpDevelop.BuildWorker.Interprocess
+{
+ ///
+ /// Description of PacketSender.
+ ///
+ public sealed class PacketSender
+ {
+ Stream targetStream;
+ public PacketSender(Stream stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException("stream");
+ targetStream = stream;
+ }
+
+ public void Send(byte[] packet)
+ {
+ if (packet == null)
+ throw new ArgumentNullException("packet");
+ Send(new MemoryStream(packet, false));
+ }
+
+ public void Send(Stream stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException("stream");
+ byte[] buffer = new byte[4 + stream.Length];
+ unchecked {
+ buffer[0] = (byte)buffer.Length;
+ buffer[1] = (byte)(buffer.Length >> 8);
+ buffer[2] = (byte)(buffer.Length >> 16);
+ buffer[3] = (byte)(buffer.Length >> 24);
+ }
+ int pos = 4;
+ int c;
+ do {
+ c = stream.Read(buffer, pos, buffer.Length - pos);
+ pos += c;
+ } while (c > 0);
+ try {
+ targetStream.Write(buffer, 0, buffer.Length);
+ } catch (IOException ex) {
+ ICSharpCode.SharpDevelop.BuildWorker.Program.Log(ex.ToString());
+ OnWriteFailed();
+ } catch (ObjectDisposedException ex) {
+ ICSharpCode.SharpDevelop.BuildWorker.Program.Log(ex.ToString());
+ OnWriteFailed();
+ }
+ }
+
+ void OnWriteFailed()
+ {
+ if (WriteFailed != null)
+ WriteFailed(this, EventArgs.Empty);
+ }
+
+ public event EventHandler WriteFailed;
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/WorkerProcess.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/WorkerProcess.cs
new file mode 100644
index 0000000000..8d10b78fcf
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/WorkerProcess.cs
@@ -0,0 +1,270 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Cryptography;
+using System.Threading;
+
+namespace ICSharpCode.SharpDevelop.BuildWorker.Interprocess
+{
+ ///
+ /// Manages a worker process that communicates with the host using a local TCP connection.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ public sealed class WorkerProcess
+ {
+ readonly object hostObject;
+
+ public WorkerProcess(IHostObject hostObject)
+ {
+ if (hostObject == null)
+ throw new ArgumentNullException("hostObject");
+ this.hostObject = hostObject;
+ }
+
+ TcpListener listener;
+ Process process;
+
+ TcpClient client;
+ PacketSender sender;
+ PacketReceiver receiver;
+
+ // We use TCP, so we'll have to deal with "attackers" trying to hijack our connection.
+ // There are two lines of defense:
+ // 1. the only local connections are accepted
+ // 2. the client must authenticate with a random password
+ // We're not protecting against DOS - we're only vulnerable to local attackers during a short
+ // time frame. The password is only to defend against deserializing arbitrary objects from
+ // unauthenticated clients.
+ // We probably should use Named Pipes instead of TCP.
+ byte[] password;
+ bool clientAuthenticated;
+
+ public void Start(ProcessStartInfo info)
+ {
+ if (info == null)
+ throw new ArgumentNullException("info");
+
+ password = new byte[16];
+ new RNGCryptoServiceProvider().GetBytes(password);
+
+ listener = new TcpListener(IPAddress.Loopback, 0);
+ listener.Start();
+ string argument = ((IPEndPoint)listener.LocalEndpoint).Port.ToString(CultureInfo.InvariantCulture);
+
+ string oldArguments = info.Arguments;
+ info.Arguments += " " + argument + " " + Convert.ToBase64String(password);
+ process = Process.Start(info);
+ // "manual process start" - useful for profiling the build worker
+ //System.Windows.Forms.MessageBox.Show(info.Arguments);
+ //process = Process.GetProcessesByName("ICSharpCode.SharpDevelop.BuildWorker")[0];
+
+ info.Arguments = oldArguments;
+
+ SetTimeout();
+ listener.BeginAcceptTcpClient(OnAcceptTcpClient, null);
+ }
+
+ Timer currentTimer;
+
+ void SetTimeout()
+ {
+ lock (this) {
+ ClearTimeout();
+ currentTimer = new Timer(OnTimeout, null, HostProcess.SendKeepAliveInterval * 5 / 2, -1);
+ }
+ }
+
+ void ClearTimeout()
+ {
+ lock (this) {
+ if (currentTimer != null) {
+ currentTimer.Dispose();
+ currentTimer = null;
+ }
+ }
+ }
+
+ void OnTimeout(object state)
+ {
+ Program.Log("OnTimeout");
+ Kill();
+ }
+
+ void OnAcceptTcpClient(IAsyncResult ar)
+ {
+ clientAuthenticated = false;
+ SetTimeout();
+ try {
+ client = listener.EndAcceptTcpClient(ar);
+ } catch (SocketException) {
+ // error connecting
+ }
+ listener.Stop();
+ listener = null;
+
+ if (client == null) {
+ OnConnectionLost(null, null);
+ } else {
+ Stream stream = client.GetStream();
+ receiver = new PacketReceiver();
+ sender = new PacketSender(stream);
+ receiver.ConnectionLost += OnConnectionLost;
+ receiver.PacketReceived += OnPacketReceived;
+
+ receiver.StartReceive(stream);
+ OnReady();
+ }
+ }
+
+ public void Shutdown()
+ {
+ Program.Log("Shutdown");
+ OnWorkerLost();
+ if (client != null) {
+ client.Close();
+ }
+ }
+
+ void OnConnectionLost(object sender, EventArgs e)
+ {
+ Program.Log("OnConnectionLost");
+ SetTimeout();
+ OnWorkerLost();
+ }
+
+ void OnPacketReceived(object sender, PacketReceivedEventArgs e)
+ {
+ SetTimeout();
+ if (e.Packet.Length != 0) {
+ if (clientAuthenticated) {
+ MethodCall mc = (MethodCall)DeserializeObject(e.Packet);
+ mc.CallOn(hostObject);
+ } else {
+ if (ArrayEquals(e.Packet, password)) {
+ clientAuthenticated = true;
+ } else {
+ Kill();
+ throw new InvalidOperationException("Worker process authentication failed.");
+ }
+ }
+ }
+ }
+
+ static bool ArrayEquals(byte[] a, byte[] b)
+ {
+ if (a.Length != b.Length)
+ return false;
+ for (int i = 0; i < a.Length; i++) {
+ if (a[i] != b[i])
+ return false;
+ }
+ return true;
+ }
+
+ public void Kill()
+ {
+ Program.Log("Kill");
+ ClearTimeout();
+ OnWorkerLost();
+ if (client != null) {
+ client.Close();
+ client = null;
+ }
+ if (process != null) {
+ try {
+ if (!process.HasExited) {
+ process.Kill();
+ }
+ } catch (InvalidOperationException) {
+ // may occur when the worker process crashes
+ }
+ process = null;
+ }
+ }
+
+ int workerIsLost;
+
+ void OnReady()
+ {
+ if (workerIsLost == 1)
+ return;
+ Program.Log("OnReady");
+ if (Ready != null)
+ Ready(this, EventArgs.Empty);
+ }
+
+ void OnWorkerLost()
+ {
+ if (Interlocked.Exchange(ref workerIsLost, 1) == 1)
+ return;
+ Program.Log("OnWorkerLost");
+ if (WorkerLost != null)
+ WorkerLost(this, EventArgs.Empty);
+ }
+
+ ///
+ /// Occurs when the worker process is ready to execute a job.
+ ///
+ public event EventHandler Ready;
+
+ ///
+ /// Occurs when the connection to the worker process broke.
+ ///
+ public event EventHandler WorkerLost;
+
+ public void CallMethodOnWorker(string methodName, params object[] args)
+ {
+ Debug.WriteLine("CallMethodOnWorker: " + methodName);
+ sender.Send(SerializeObject(new MethodCall(methodName, args)));
+ }
+
+ internal static MemoryStream SerializeObject(object obj)
+ {
+ MemoryStream ms = new MemoryStream();
+ BinaryFormatter bf = new BinaryFormatter();
+ bf.Serialize(ms, obj);
+ ms.Position = 0;
+ return ms;
+ }
+
+ internal static object DeserializeObject(byte[] packet)
+ {
+ BinaryFormatter bf = new BinaryFormatter();
+ return bf.Deserialize(new MemoryStream(packet, false));
+ }
+
+ [Serializable]
+ internal class MethodCall
+ {
+ public readonly string MethodName;
+ public readonly object[] Arguments;
+
+ public MethodCall(string methodName, object[] arguments)
+ {
+ this.MethodName = methodName;
+ this.Arguments = arguments;
+ }
+
+ public void CallOn(object target)
+ {
+ target.GetType().InvokeMember(MethodName,
+ BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,
+ null, target, Arguments, CultureInfo.InvariantCulture);
+ }
+ }
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Program.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Program.cs
new file mode 100644
index 0000000000..4bc9d0ebc4
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/Program.cs
@@ -0,0 +1,479 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+// activate this define to see the build worker window
+//#define WORKERDEBUG
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Threading;
+
+using ICSharpCode.SharpDevelop.BuildWorker.Interprocess;
+using Microsoft.Build.BuildEngine;
+using Microsoft.Build.Framework;
+
+namespace ICSharpCode.SharpDevelop.BuildWorker
+{
+ class Program
+ {
+ static HostProcess host;
+ BuildJob currentJob;
+ bool requestCancellation;
+
+ [STAThread]
+ internal static void Main(string[] args)
+ {
+ AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomain_CurrentDomain_UnhandledException);
+
+ if (args.Length == 3 && args[0] == "worker") {
+ try {
+ host = new HostProcess(new Program());
+ host.WorkerProcessMain(args[1], args[2]);
+ } catch (Exception ex) {
+ ShowMessageBox(ex.ToString());
+ }
+ } else {
+ Console.WriteLine("ICSharpCode.SharpDevelop.BuildWorker.exe is used to compile " +
+ "MSBuild projects inside SharpDevelop.");
+ Console.WriteLine("If you want to compile projects on the command line, use " +
+ "MSBuild.exe (part of the .NET Framework)");
+ }
+ }
+
+ static void AppDomain_CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ ShowMessageBox(e.ExceptionObject.ToString());
+ }
+
+ #if RELEASE && WORKERDEBUG
+ #error WORKERDEBUG must not be defined if RELEASE is defined
+ #endif
+
+ internal static ProcessStartInfo CreateStartInfo()
+ {
+ ProcessStartInfo info = new ProcessStartInfo(typeof(Program).Assembly.Location);
+ info.WorkingDirectory = Path.GetDirectoryName(info.FileName);
+ info.Arguments = "worker";
+ info.UseShellExecute = false;
+ #if RELEASE || !WORKERDEBUG
+ info.CreateNoWindow = true;
+ #endif
+ return info;
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions")]
+ internal static void ShowMessageBox(string text)
+ {
+ System.Windows.Forms.MessageBox.Show(text, "SharpDevelop Build Worker Process");
+ }
+
+ [Conditional("DEBUG")]
+ internal static void Log(string text)
+ {
+ Debug.WriteLine(text);
+ #if WORKERDEBUG
+ DateTime now = DateTime.Now;
+ Console.WriteLine(now.ToString() + "," + now.Millisecond.ToString("d3") + " " + text);
+ #endif
+ }
+
+ // Called with CallMethodOnWorker
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ public void StartBuild(BuildJob job)
+ {
+ if (job == null)
+ throw new ArgumentNullException("job");
+ lock (this) {
+ if (currentJob != null)
+ throw new InvalidOperationException("Already running a job");
+ currentJob = job;
+ requestCancellation = false;
+ }
+ #if WORKERDEBUG
+ Console.Title = "BuildWorker - " + Path.GetFileName(job.ProjectFileName);
+ #endif
+ Program.Log("Got job:");
+ Program.Log(job.ToString());
+ Program.Log("Start build thread");
+ Thread thread = new Thread(RunThread);
+ thread.Name = "Build thread";
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Start();
+ }
+
+ // Called with CallMethodOnWorker
+ //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ // TODO: make use of CancelBuild
+ public void CancelBuild()
+ {
+ lock (this) {
+ requestCancellation = true;
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ void RunThread()
+ {
+ Program.Log("In build thread");
+ bool success = false;
+ try {
+ success = DoBuild();
+ } catch (Exception ex) {
+ host.CallMethodOnHost("ReportException", ex.ToString());
+ } finally {
+ Program.Log("BuildDone");
+
+ #if WORKERDEBUG
+ Console.Title = "BuildWorker - no job";
+ DisplayEventCounts();
+ #endif
+
+ lock (this) {
+ currentJob = null;
+ }
+ // in the moment we call BuildDone, we can get the next job
+ host.CallMethodOnHost("BuildDone", success);
+ }
+ }
+
+ Engine CreateEngine()
+ {
+ Engine engine = new Engine(ToolsetDefinitionLocations.Registry
+ | ToolsetDefinitionLocations.ConfigurationFile);
+
+ engine.RegisterLogger(new ForwardingLogger(this));
+ //engine.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Diagnostic));
+
+ return engine;
+ }
+
+ EventSource hostEventSource;
+
+ internal void BuildInProcess(BuildSettings settings, BuildJob job)
+ {
+ lock (this) {
+ if (currentJob != null)
+ throw new InvalidOperationException("Already running a job");
+ currentJob = job;
+ requestCancellation = false;
+ hostEventSource = new EventSource();
+ }
+ job.CancelCallback = delegate {
+ lock (this) {
+ if (currentJob == job) {
+ requestCancellation = true;
+ }
+ }
+ };
+ bool success = false;
+ try {
+ foreach (ILogger logger in settings.Logger) {
+ logger.Initialize(hostEventSource);
+ }
+ success = DoBuild();
+ foreach (ILogger logger in settings.Logger) {
+ logger.Shutdown();
+ }
+ } finally {
+ lock (this) {
+ currentJob = null;
+ }
+ if (settings.BuildDoneCallback != null)
+ settings.BuildDoneCallback(success);
+ }
+ }
+
+ Engine engine;
+ Dictionary lastJobProperties;
+
+ // Fix for SD2-1533 - Project configurations get confused
+ // Whenever the global properties change, we have to create a new Engine
+ // to ensure MSBuild doesn't cache old paths
+ bool GlobalPropertiesChanged(Dictionary newJobProperties)
+ {
+ Debug.Assert(newJobProperties != null);
+ if (lastJobProperties == null || lastJobProperties.Count != newJobProperties.Count) {
+ Log("Recreating engine: Number of build properties changed");
+ return true;
+ }
+ foreach (KeyValuePair pair in lastJobProperties) {
+ string val;
+ if (!newJobProperties.TryGetValue(pair.Key, out val)) {
+ Log("Recreating engine: Build property removed: " + pair.Key);
+ return true;
+ }
+ if (val != pair.Value) {
+ Log("Recreating engine: Build property changed: " + pair.Key);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool DoBuild()
+ {
+ if (currentJob.IntPtrSize != IntPtr.Size)
+ throw new ApplicationException("Incompatible IntPtr.Size between host and worker");
+
+ if (engine == null || GlobalPropertiesChanged(currentJob.Properties)) {
+ engine = CreateEngine();
+ lastJobProperties = currentJob.Properties;
+ engine.GlobalProperties.Clear();
+ foreach (KeyValuePair pair in currentJob.Properties) {
+ engine.GlobalProperties.SetProperty(pair.Key, pair.Value);
+ }
+ }
+
+ Log("Loading " + currentJob.ProjectFileName);
+ Project project = LoadProject(engine, currentJob.ProjectFileName);
+ if (project == null)
+ return false;
+
+ if (string.IsNullOrEmpty(currentJob.Target)) {
+ Log("Building default target in " + currentJob.ProjectFileName);
+ return engine.BuildProject(project);
+ } else {
+ Log("Building target '" + currentJob.Target + "' in " + currentJob.ProjectFileName);
+ return engine.BuildProject(project, currentJob.Target.Split(';'));
+ }
+ }
+
+ Project LoadProject(Engine engine, string fileName)
+ {
+ Project project = engine.CreateNewProject();
+ try {
+ project.Load(fileName);
+
+ /* No longer necessary as we stopped using BuildingInsideVisualStudio
+ // When we set BuildingInsideVisualStudio, MSBuild tries to build all projects
+ // every time because in Visual Studio, the host compiler does the change detection
+ // We override the property '_ComputeNonExistentFileProperty' which is responsible
+ // for recompiling each time - our _ComputeNonExistentFileProperty does nothing,
+ // which re-enables the MSBuild's usual change detection
+ project.Targets.AddNewTarget("_ComputeNonExistentFileProperty");
+ */
+
+ foreach (string additionalImport in currentJob.AdditionalImports) {
+ project.AddNewImport(additionalImport, null);
+ }
+
+ return project;
+ } catch (ArgumentException ex) {
+ ReportError(ex.Message);
+ } catch (InvalidProjectFileException ex) {
+ ReportError(new BuildErrorEventArgs(ex.ErrorSubcategory, ex.ErrorCode, ex.ProjectFile,
+ ex.LineNumber, ex.ColumnNumber, ex.EndLineNumber, ex.EndColumnNumber,
+ ex.BaseMessage, ex.HelpKeyword, ex.Source));
+ }
+ return null;
+ }
+
+ void ReportError(string message)
+ {
+ ReportError(new BuildErrorEventArgs(null, null, null, -1, -1, -1, -1,
+ message, null, "SharpDevelopBuildWorker"));
+ }
+
+ void ReportError(BuildErrorEventArgs e)
+ {
+ HostReportEvent(e);
+ }
+
+ void HostReportEvent(BuildEventArgs e)
+ {
+ if (host != null) {
+ host.CallMethodOnHost("ReportEvent", e);
+ } else {
+ // enable error reporting for in-process builds
+ EventSource eventSource = hostEventSource;
+ if (eventSource != null) {
+ eventSource.RaiseEvent(e);
+ }
+ }
+ }
+
+ sealed class ForwardingLogger : ILogger
+ {
+ Program program;
+
+ public ForwardingLogger(Program program)
+ {
+ this.program = program;
+ }
+
+ IEventSource eventSource;
+
+ public LoggerVerbosity Verbosity { get; set; }
+ public string Parameters { get; set; }
+
+ public void Initialize(IEventSource eventSource)
+ {
+ this.eventSource = eventSource;
+ EventTypes eventMask = program.currentJob.EventMask;
+ if ((eventMask & EventTypes.Message) != 0)
+ eventSource.MessageRaised += OnEvent;
+ if ((eventMask & EventTypes.Error) != 0)
+ eventSource.ErrorRaised += OnEvent;
+ if ((eventMask & EventTypes.Warning) != 0)
+ eventSource.WarningRaised += OnEvent;
+ if ((eventMask & EventTypes.BuildStarted) != 0)
+ eventSource.BuildStarted += OnEvent;
+ if ((eventMask & EventTypes.BuildFinished) != 0)
+ eventSource.BuildFinished += OnEvent;
+ if ((eventMask & EventTypes.ProjectStarted) != 0)
+ eventSource.ProjectStarted += OnEvent;
+ if ((eventMask & EventTypes.ProjectFinished) != 0)
+ eventSource.ProjectFinished += OnEvent;
+ if ((eventMask & EventTypes.TargetStarted) != 0)
+ eventSource.TargetStarted += OnEvent;
+ if ((eventMask & EventTypes.TargetFinished) != 0)
+ eventSource.TargetFinished += OnEvent;
+ if ((eventMask & EventTypes.TaskStarted) != 0)
+ eventSource.TaskStarted += OnEvent;
+ else
+ eventSource.TaskStarted += OnTaskStarted;
+ if ((eventMask & EventTypes.TaskFinished) != 0)
+ eventSource.TaskFinished += OnEvent;
+ else
+ eventSource.TaskFinished += OnTaskFinished;
+ if ((eventMask & EventTypes.Custom) != 0)
+ eventSource.CustomEventRaised += OnEvent;
+ if ((eventMask & EventTypes.Unknown) != 0)
+ eventSource.AnyEventRaised += OnUnknownEventRaised;
+ if (eventMask != EventTypes.All)
+ eventSource.AnyEventRaised += OnAnyEvent;
+
+ #if WORKERDEBUG
+ eventSource.AnyEventRaised += CountEvent;
+ #endif
+ }
+
+ public void Shutdown()
+ {
+ EventTypes eventMask = program.currentJob.EventMask;
+ if ((eventMask & EventTypes.Message) != 0)
+ eventSource.MessageRaised -= OnEvent;
+ if ((eventMask & EventTypes.Error) != 0)
+ eventSource.ErrorRaised -= OnEvent;
+ if ((eventMask & EventTypes.Warning) != 0)
+ eventSource.WarningRaised -= OnEvent;
+ if ((eventMask & EventTypes.BuildStarted) != 0)
+ eventSource.BuildStarted -= OnEvent;
+ if ((eventMask & EventTypes.BuildFinished) != 0)
+ eventSource.BuildFinished -= OnEvent;
+ if ((eventMask & EventTypes.ProjectStarted) != 0)
+ eventSource.ProjectStarted -= OnEvent;
+ if ((eventMask & EventTypes.ProjectFinished) != 0)
+ eventSource.ProjectFinished -= OnEvent;
+ if ((eventMask & EventTypes.TargetStarted) != 0)
+ eventSource.TargetStarted -= OnEvent;
+ if ((eventMask & EventTypes.TargetFinished) != 0)
+ eventSource.TargetFinished -= OnEvent;
+ if ((eventMask & EventTypes.TaskStarted) != 0)
+ eventSource.TaskStarted -= OnEvent;
+ else
+ eventSource.TaskStarted -= OnTaskStarted;
+ if ((eventMask & EventTypes.TaskFinished) != 0)
+ eventSource.TaskFinished -= OnEvent;
+ else
+ eventSource.TaskFinished -= OnTaskFinished;
+ if ((eventMask & EventTypes.Custom) != 0)
+ eventSource.CustomEventRaised -= OnEvent;
+ if ((eventMask & EventTypes.Unknown) != 0)
+ eventSource.AnyEventRaised -= OnUnknownEventRaised;
+ if (eventMask != EventTypes.All)
+ eventSource.AnyEventRaised -= OnAnyEvent;
+
+ #if WORKERDEBUG
+ eventSource.AnyEventRaised -= CountEvent;
+ #endif
+ }
+
+ // registered for AnyEventRaised to support build cancellation.
+ // is not registered if all events should be forwarded, in that case, OnEvent
+ // already handles build cancellation
+ void OnAnyEvent(object sender, BuildEventArgs e)
+ {
+ if (program.requestCancellation)
+ throw new BuildCancelException();
+ }
+
+ // used for all events that should be forwarded
+ void OnEvent(object sender, BuildEventArgs e)
+ {
+ if (program.requestCancellation)
+ throw new BuildCancelException();
+ program.HostReportEvent(e);
+ }
+
+ // registered for AnyEventRaised to forward unknown events
+ void OnUnknownEventRaised(object sender, BuildEventArgs e)
+ {
+ if (EventSource.GetEventType(e) == EventTypes.Unknown)
+ OnEvent(sender, e);
+ }
+
+ // registered when only specific tasks should be forwarded
+ void OnTaskStarted(object sender, TaskStartedEventArgs e)
+ {
+ if (program.currentJob.InterestingTaskNames.Contains(e.TaskName))
+ OnEvent(sender, e);
+ }
+
+ // registered when only specific tasks should be forwarded
+ void OnTaskFinished(object sender, TaskFinishedEventArgs e)
+ {
+ if (program.currentJob.InterestingTaskNames.Contains(e.TaskName))
+ OnEvent(sender, e);
+ }
+
+
+ #if WORKERDEBUG
+ void CountEvent(object sender, BuildEventArgs e)
+ {
+ Program.CountEvent(EventSource.GetEventType(e));
+ }
+ #endif
+ }
+
+ #if WORKERDEBUG
+ static Dictionary eventCounts = new Dictionary();
+
+ static void CountEvent(EventTypes e)
+ {
+ if (eventCounts.ContainsKey(e))
+ eventCounts[e] += 1;
+ else
+ eventCounts[e] = 1;
+ }
+
+ static void DisplayEventCounts()
+ {
+ foreach (var pair in eventCounts) {
+ Console.WriteLine(" " + pair.Key.ToString() + ": " + pair.Value.ToString());
+ }
+ }
+ #endif
+
+ [Serializable]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic")]
+ sealed class BuildCancelException : Exception
+ {
+ public BuildCancelException()
+ {
+ }
+
+ BuildCancelException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/WorkerManager.cs b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/WorkerManager.cs
new file mode 100644
index 0000000000..b28bd949f7
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/WorkerManager.cs
@@ -0,0 +1,274 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using Microsoft.Build.Framework;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Diagnostics;
+using ICSharpCode.SharpDevelop.BuildWorker.Interprocess;
+
+namespace ICSharpCode.SharpDevelop.BuildWorker
+{
+ ///
+ /// Manages the list of running child worker processes.
+ ///
+ public static class WorkerManager
+ {
+ ///
+ /// Delegate executed on the host when an exception occurs.
+ ///
+ public static Action ShowError { get; set; }
+
+ const int MaxWorkerProcessCount = 16;
+
+ static readonly object lockObject = new object();
+ static Queue outstandingBuildRuns = new Queue();
+ static int workerProcessCount;
+ static List freeWorkerProcesses = new List();
+
+ static WorkerProcessHost DequeueFreeWorkerProcess()
+ {
+ WorkerProcessHost h = freeWorkerProcesses[freeWorkerProcesses.Count - 1];
+ freeWorkerProcesses.RemoveAt(freeWorkerProcesses.Count - 1);
+ return h;
+ }
+
+ public static void StartBuild(BuildJob job, BuildSettings settings)
+ {
+ if (job == null)
+ throw new ArgumentNullException("job");
+ if (settings == null)
+ throw new ArgumentNullException("settings");
+
+ BuildRun buildRun = new BuildRun(job, settings);
+ lock (lockObject) {
+ if (freeWorkerProcesses.Count > 0) {
+ DequeueFreeWorkerProcess().StartBuild(buildRun);
+ } else {
+ outstandingBuildRuns.Enqueue(buildRun);
+ if (workerProcessCount < MaxWorkerProcessCount) {
+ workerProcessCount++;
+ (new WorkerProcessHost()).Start();
+ }
+ }
+ }
+ }
+
+ static Program inProcessBuildWorker;
+
+ public static void RunBuildInProcess(BuildJob job, BuildSettings settings)
+ {
+ if (job == null)
+ throw new ArgumentNullException("job");
+ if (settings == null)
+ throw new ArgumentNullException("settings");
+ lock (lockObject) {
+ if (inProcessBuildWorker == null)
+ inProcessBuildWorker = new Program();
+ }
+ inProcessBuildWorker.BuildInProcess(settings, job);
+ }
+
+ static readonly object timerLock = new object();
+ static Timer lastBuildDoneTimer;
+
+ static void SetLastBuildDoneTimer()
+ {
+ lock (timerLock) {
+ if (lastBuildDoneTimer != null) {
+ lastBuildDoneTimer.Dispose();
+ lastBuildDoneTimer = null;
+ }
+ lastBuildDoneTimer = new Timer(LastBuildDoneTimerCallback, null, 60000, 20000);
+ }
+ }
+
+ static void ClearLastBuildDoneTimer()
+ {
+ lock (timerLock) {
+ if (lastBuildDoneTimer != null) {
+ lastBuildDoneTimer.Dispose();
+ lastBuildDoneTimer = null;
+ }
+ }
+ }
+
+ static void LastBuildDoneTimerCallback(object state)
+ {
+ lock (lockObject) {
+ if (freeWorkerProcesses.Count > 0) {
+ Debug.WriteLine("WorkerManager: shutting down free worker");
+ DequeueFreeWorkerProcess().Shutdown();
+ } else {
+ ClearLastBuildDoneTimer();
+ }
+ }
+ }
+
+ sealed class BuildRun
+ {
+ internal BuildJob job;
+ internal BuildSettings settings;
+ EventSource eventSource = new EventSource();
+
+ public BuildRun(BuildJob job, BuildSettings settings)
+ {
+ this.job = job;
+ this.settings = settings;
+ foreach (ILogger logger in settings.Logger) {
+ logger.Initialize(eventSource);
+ }
+ }
+
+ public void RaiseError(string message)
+ {
+ Debug.WriteLine(message);
+ RaiseEvent(new BuildErrorEventArgs(null, null, null, -1, -1, -1, -1, message, null, "SharpDevelopBuildWorkerManager"));
+ }
+
+ public void RaiseEvent(BuildEventArgs e)
+ {
+ eventSource.RaiseEvent(e);
+ }
+
+ public void Done(bool success)
+ {
+ SetLastBuildDoneTimer();
+ try {
+ foreach (ILogger logger in settings.Logger) {
+ logger.Shutdown();
+ }
+ } finally {
+ if (settings.BuildDoneCallback != null)
+ settings.BuildDoneCallback(success);
+ }
+ }
+ }
+
+ sealed class WorkerProcessHost : IHostObject
+ {
+ WorkerProcess process;
+
+ internal void Start()
+ {
+ process = new WorkerProcess(this);
+ process.Ready += OnReady;
+ process.WorkerLost += OnWorkerLost;
+
+ process.Start(Program.CreateStartInfo());
+ }
+
+ BuildRun currentBuildRun;
+
+ // runs in lock(lockObject)
+ internal void StartBuild(BuildRun nextBuildRun)
+ {
+ Debug.Assert(currentBuildRun == null);
+ currentBuildRun = nextBuildRun;
+ process.CallMethodOnWorker("StartBuild", currentBuildRun.job);
+ }
+
+ void OnReady(object sender, EventArgs e)
+ {
+ BuildRun nextBuildRun = null;
+ lock (lockObject) {
+ if (outstandingBuildRuns.Count > 0)
+ nextBuildRun = outstandingBuildRuns.Dequeue();
+ else
+ freeWorkerProcesses.Add(this);
+ }
+ if (nextBuildRun != null) {
+ StartBuild(nextBuildRun);
+ }
+ }
+
+ void OnWorkerLost(object sender, EventArgs e)
+ {
+ BuildRun buildRun;
+ lock (lockObject) {
+ workerProcessCount--;
+ freeWorkerProcesses.Remove(this);
+ if (workerProcessCount == 0 && currentBuildRun == null) {
+ // error starting worker => we must
+ // cancel all outstanding build runs to prevent them from waiting
+ // for a worker becoming ready when all workers are dead
+ while (workerProcessCount == 0 && outstandingBuildRuns.Count > 0) {
+ BuildRun r = outstandingBuildRuns.Dequeue();
+ Monitor.Exit(lockObject);
+ r.RaiseError("Error starting worker process.");
+ r.Done(false);
+ Monitor.Enter(lockObject);
+ }
+ }
+ buildRun = Interlocked.Exchange(ref currentBuildRun, null);
+ }
+ if (buildRun != null) {
+ buildRun.RaiseError("Worker process lost during build");
+ buildRun.Done(false);
+ }
+ }
+
+ internal void Shutdown()
+ {
+ process.Shutdown();
+ }
+
+ // Called with CallMethodOnHost
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ public void ReportEvent(BuildEventArgs e)
+ {
+ BuildRun buildRun = currentBuildRun;
+ if (buildRun != null) {
+ buildRun.RaiseEvent(e);
+ }
+ }
+
+ // Called with CallMethodOnHost
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ public void BuildDone(bool success)
+ {
+ BuildRun buildRun;
+ lock (lockObject) {
+ buildRun = Interlocked.Exchange(ref currentBuildRun, null);
+ }
+ if (buildRun != null) {
+ // OnReady must be called before buildRun.Done - the callback
+ // might trigger another build, and if this worker process
+ // isn't marked as ready, a new process will be created even
+ // though this one could do the work.
+ OnReady(null, null);
+ buildRun.Done(success);
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ public void ReportException(string exceptionText)
+ {
+ // shutdown worker if it produced an exception
+ try {
+ process.Shutdown();
+ } catch {}
+
+ if (ShowError != null)
+ ShowError(new Exception(exceptionText));
+ else
+ Program.ShowMessageBox(exceptionText);
+ }
+ }
+ }
+
+ public sealed class BuildSettings
+ {
+ List logger = new List();
+ public Action BuildDoneCallback { get; set; }
+
+ public ICollection Logger {
+ get { return logger; }
+ }
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.BuildWorker/app.config b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/app.config
new file mode 100644
index 0000000000..aa701ee4ae
--- /dev/null
+++ b/src/Main/ICSharpCode.SharpDevelop.BuildWorker/app.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+