Browse Source

Revive old BuildWorker.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5640 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
pull/1/head
Daniel Grunwald 16 years ago
parent
commit
bef6b9422b
  1. 90
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/BuildJob.cs
  2. 17
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/Configuration/AssemblyInfo.cs
  3. 131
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/EventSource.cs
  4. 74
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/ICSharpCode.SharpDevelop.BuildWorker.csproj
  5. 112
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/HostProcess.cs
  6. 16
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/IHostObject.cs
  7. 148
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketReceiver.cs
  8. 70
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketSender.cs
  9. 270
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/WorkerProcess.cs
  10. 479
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/Program.cs
  11. 274
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/WorkerManager.cs
  12. 15
      src/Main/ICSharpCode.SharpDevelop.BuildWorker/app.config

90
src/Main/ICSharpCode.SharpDevelop.BuildWorker/BuildJob.cs

@ -0,0 +1,90 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Text;
namespace ICSharpCode.SharpDevelop.BuildWorker
{
/// <summary>
/// The settings used to start a build.
/// </summary>
[Serializable]
public class BuildJob
{
/// <summary>
/// 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.
/// </summary>
public int IntPtrSize { get; set; }
public string ProjectFileName { get; set; }
public string Target { get; set; }
EventTypes eventMask = EventTypes.All;
/// <summary>
/// Gets/Sets the mask that controls which events are reported back to the host.
/// </summary>
public EventTypes EventMask {
get { return eventMask; }
set { eventMask = value; }
}
Dictionary<string, string> properties = new Dictionary<string, string>();
public Dictionary<string, string> Properties {
get { return properties; }
}
List<string> additionalImports = new List<string>();
public IList<string> AdditionalImports {
get { return additionalImports; }
}
HashSet<string> interestingTaskNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public ICollection<string> 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<string, string> 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();
}
}
}
}

17
src/Main/ICSharpCode.SharpDevelop.BuildWorker/Configuration/AssemblyInfo.cs

@ -0,0 +1,17 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
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)]

131
src/Main/ICSharpCode.SharpDevelop.BuildWorker/EventSource.cs

@ -0,0 +1,131 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
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;
}
}
}
}

74
src/Main/ICSharpCode.SharpDevelop.BuildWorker/ICSharpCode.SharpDevelop.BuildWorker.csproj

@ -0,0 +1,74 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<PropertyGroup>
<ProjectGuid>{C3CBC8E3-81D8-4C5B-9941-DCCD12D50B1F}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<OutputType>Exe</OutputType>
<RootNamespace>ICSharpCode.SharpDevelop.BuildWorker</RootNamespace>
<AssemblyName>ICSharpCode.SharpDevelop.BuildWorker</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<AllowUnsafeBlocks>False</AllowUnsafeBlocks>
<NoStdLib>False</NoStdLib>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<OutputPath>..\..\..\bin\</OutputPath>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\ICSharpCode.SharpDevelop.snk</AssemblyOriginatorKeyFile>
<DelaySign>False</DelaySign>
<AssemblyOriginatorKeyMode>File</AssemblyOriginatorKeyMode>
<RunCodeAnalysis>False</RunCodeAnalysis>
<CodeAnalysisRules>-Microsoft.Globalization#CA1303</CodeAnalysisRules>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>Full</DebugType>
<Optimize>False</Optimize>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>False</DebugSymbols>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'AnyCPU' ">
<RegisterForComInterop>False</RegisterForComInterop>
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
<BaseAddress>4194304</BaseAddress>
<PlatformTarget>x86</PlatformTarget>
<FileAlignment>4096</FileAlignment>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
<ItemGroup>
<Reference Include="Microsoft.Build.Engine" />
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\GlobalAssemblyInfo.cs">
<Link>Configuration\GlobalAssemblyInfo.cs</Link>
</Compile>
<Compile Include="BuildJob.cs" />
<Compile Include="Configuration\AssemblyInfo.cs" />
<Compile Include="EventSource.cs" />
<Compile Include="Interprocess\HostProcess.cs" />
<Compile Include="Interprocess\IHostObject.cs" />
<Compile Include="Interprocess\PacketReceiver.cs" />
<Compile Include="Interprocess\PacketSender.cs" />
<Compile Include="Interprocess\WorkerProcess.cs" />
<Compile Include="Program.cs" />
<Compile Include="WorkerManager.cs" />
<None Include="app.config" />
</ItemGroup>
<ItemGroup>
<Folder Include="Configuration" />
<Folder Include="Interprocess" />
</ItemGroup>
</Project>

112
src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/HostProcess.cs

@ -0,0 +1,112 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
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
{
/// <summary>
/// Used in the worker process to refer to the host.
/// </summary>
[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());
}
}
}
}
}

16
src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/IHostObject.cs

@ -0,0 +1,16 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.SharpDevelop.BuildWorker.Interprocess
{
public interface IHostObject
{
void ReportException(string exceptionText);
}
}

148
src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketReceiver.cs

@ -0,0 +1,148 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
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;
/// <summary>
/// Gets/Sets the maximum allowed packet size in bytes.
/// </summary>
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<PacketReceivedEventArgs> 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;
}
}
}

70
src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/PacketSender.cs

@ -0,0 +1,70 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.IO;
using System.Threading;
namespace ICSharpCode.SharpDevelop.BuildWorker.Interprocess
{
/// <summary>
/// Description of PacketSender.
/// </summary>
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;
}
}

270
src/Main/ICSharpCode.SharpDevelop.BuildWorker/Interprocess/WorkerProcess.cs

@ -0,0 +1,270 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
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
{
/// <summary>
/// Manages a worker process that communicates with the host using a local TCP connection.
/// </summary>
[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);
}
/// <summary>
/// Occurs when the worker process is ready to execute a job.
/// </summary>
public event EventHandler Ready;
/// <summary>
/// Occurs when the connection to the worker process broke.
/// </summary>
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);
}
}
}
}

479
src/Main/ICSharpCode.SharpDevelop.BuildWorker/Program.cs

@ -0,0 +1,479 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
// 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<string, string> 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<string, string> newJobProperties)
{
Debug.Assert(newJobProperties != null);
if (lastJobProperties == null || lastJobProperties.Count != newJobProperties.Count) {
Log("Recreating engine: Number of build properties changed");
return true;
}
foreach (KeyValuePair<string, string> 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<string, string> 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<EventTypes, int> eventCounts = new Dictionary<EventTypes, int>();
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)
{
}
}
}
}

274
src/Main/ICSharpCode.SharpDevelop.BuildWorker/WorkerManager.cs

@ -0,0 +1,274 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
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
{
/// <summary>
/// Manages the list of running child worker processes.
/// </summary>
public static class WorkerManager
{
/// <summary>
/// Delegate executed on the host when an exception occurs.
/// </summary>
public static Action<Exception> ShowError { get; set; }
const int MaxWorkerProcessCount = 16;
static readonly object lockObject = new object();
static Queue<BuildRun> outstandingBuildRuns = new Queue<BuildRun>();
static int workerProcessCount;
static List<WorkerProcessHost> freeWorkerProcesses = new List<WorkerProcessHost>();
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<ILogger> logger = new List<ILogger>();
public Action<bool> BuildDoneCallback { get; set; }
public ICollection<ILogger> Logger {
get { return logger; }
}
}
}

15
src/Main/ICSharpCode.SharpDevelop.BuildWorker/app.config

@ -0,0 +1,15 @@
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- redirect MSBuild.Framework requests to make old task assemblies work with MSBuild 3.5 -->
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Framework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-99.9.9.9" newVersion="3.5.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.CompactFramework.Build.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-99.9.9.9" newVersion="9.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Loading…
Cancel
Save