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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
<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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
<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