Browse Source
git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5640 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61pull/1/head
12 changed files with 1696 additions and 0 deletions
@ -0,0 +1,90 @@
@@ -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(); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,17 @@
@@ -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)] |
@ -0,0 +1,131 @@
@@ -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; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,74 @@
@@ -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> |
@ -0,0 +1,112 @@
@@ -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()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,16 @@
@@ -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); |
||||
} |
||||
} |
@ -0,0 +1,148 @@
@@ -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; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,70 @@
@@ -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; |
||||
} |
||||
} |
@ -0,0 +1,270 @@
@@ -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); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,479 @@
@@ -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) |
||||
{ |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,274 @@
@@ -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; } |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,15 @@
@@ -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…
Reference in new issue