Browse Source
New features: - Supports non-MSBuild projects. - Setting different properties for each project in a solution. - Build can run in a worker process - Builds multiple projects in parallel git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@2694 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
40 changed files with 2584 additions and 983 deletions
@ -0,0 +1,492 @@
@@ -0,0 +1,492 @@
|
||||
// <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.Linq; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Project |
||||
{ |
||||
/// <summary>
|
||||
/// Supports building a project with dependencies or the whole solution.
|
||||
/// Creates a build graph and then builds it using topological sort.
|
||||
///
|
||||
/// Supports building multiple projects in parallel.
|
||||
/// </summary>
|
||||
public sealed class BuildEngine |
||||
{ |
||||
#region Building in the SharpDevelop GUI
|
||||
static bool guiBuildRunning; |
||||
|
||||
public static void BuildInGui(IBuildable project, BuildOptions options) |
||||
{ |
||||
if (guiBuildRunning) { |
||||
BuildResults results = new BuildResults(); |
||||
results.Add(new BuildError(null, Core.ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning"))); |
||||
results.Result = BuildResultCode.MSBuildAlreadyRunning; |
||||
options.Callback(results); |
||||
} else { |
||||
Gui.WorkbenchSingleton.Workbench.GetPad(typeof(Gui.CompilerMessageView)).BringPadToFront(); |
||||
StartBuild(project, options, new MessageViewSink(TaskService.BuildMessageViewCategory)); |
||||
} |
||||
} |
||||
|
||||
sealed class MessageViewSink : IBuildFeedbackSink |
||||
{ |
||||
Gui.MessageViewCategory messageView; |
||||
|
||||
public MessageViewSink(ICSharpCode.SharpDevelop.Gui.MessageViewCategory messageView) |
||||
{ |
||||
this.messageView = messageView; |
||||
} |
||||
|
||||
public void ReportError(BuildError error) |
||||
{ |
||||
} |
||||
|
||||
public void ReportMessage(string message) |
||||
{ |
||||
messageView.AppendLine(message); |
||||
} |
||||
|
||||
public void Done(bool success) |
||||
{ |
||||
guiBuildRunning = false; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region StartBuild
|
||||
public static void StartBuild(IBuildable project, BuildOptions options, IBuildFeedbackSink realtimeBuildFeedbackSink) |
||||
{ |
||||
if (project == null) |
||||
throw new ArgumentNullException("solution"); |
||||
if (options == null) |
||||
throw new ArgumentNullException("options"); |
||||
|
||||
Solution solution = project.ParentSolution; |
||||
if (solution == null) |
||||
throw new ArgumentException("project.ParentSolution must not be null", "project"); |
||||
|
||||
if (string.IsNullOrEmpty(options.SolutionConfiguration)) |
||||
options.SolutionConfiguration = solution.Preferences.ActiveConfiguration; |
||||
if (string.IsNullOrEmpty(options.SolutionPlatform)) |
||||
options.SolutionPlatform = solution.Preferences.ActivePlatform; |
||||
|
||||
BuildEngine engine = new BuildEngine(options, project); |
||||
engine.buildStart = DateTime.Now; |
||||
engine.combinedBuildFeedbackSink = realtimeBuildFeedbackSink; |
||||
engine.configMatchings = solution.GetActiveConfigurationsAndPlatformsForProjects(options.SolutionConfiguration, options.SolutionPlatform); |
||||
try { |
||||
engine.rootNode = engine.CreateBuildGraph(project); |
||||
} catch (CyclicDependencyException ex) { |
||||
if (ex.Project1 != null && ex.Project2 != null) |
||||
engine.results.Add(new BuildError(null, "Cyclic dependency between " + ex.Project1.Name + " and " + ex.Project2.Name)); |
||||
else |
||||
engine.results.Add(new BuildError(null, "Cyclic dependency")); |
||||
engine.results.Result = BuildResultCode.BuildFileError; |
||||
realtimeBuildFeedbackSink.Done(false); |
||||
options.Callback(engine.results); |
||||
return; |
||||
} |
||||
|
||||
engine.workersToStart = options.ParallelProjectCount; |
||||
if (engine.workersToStart < 1) |
||||
engine.workersToStart = 1; |
||||
|
||||
engine.ReportMessageInternal("${res:MainWindow.CompilerMessages.BuildStarted}"); |
||||
engine.StartBuildProjects(); |
||||
} |
||||
#endregion
|
||||
|
||||
#region inner class BuildNode
|
||||
sealed class BuildNode : IBuildFeedbackSink |
||||
{ |
||||
readonly BuildEngine engine; |
||||
internal readonly IBuildable project; |
||||
internal ProjectBuildOptions options; |
||||
internal BuildNode[] dependencies; |
||||
/// <summary>specifies whether the node has been constructed completely (all dependencies initialized)</summary>
|
||||
internal bool nodeComplete; |
||||
/// <summary>specifies whether this node has started building</summary>
|
||||
internal bool buildStarted; |
||||
/// <summary>specifies whether this node has finished building</summary>
|
||||
internal bool buildFinished; |
||||
|
||||
/// <summary>specifies whether the node produces build errors</summary>
|
||||
internal bool hasErrors; |
||||
|
||||
/// <summary>The number of dependencies missing until this node can be built</summary>
|
||||
internal int outstandingDependencies; |
||||
|
||||
/// <summary>The list of nodes that directly depend on this node</summary>
|
||||
internal List<BuildNode> dependentOnThis = new List<BuildNode>(); |
||||
|
||||
int totalDependentOnThisCount = -1; |
||||
|
||||
/// <summary>Gets the number of nodes that depend on this node
|
||||
/// directly or indirectly</summary>
|
||||
internal int TotalDependentOnThisCount { |
||||
get { |
||||
if (totalDependentOnThisCount >= 0) |
||||
return totalDependentOnThisCount; |
||||
|
||||
// make a depth-first search to determine the size of the tree of
|
||||
// projects that depends on this project:
|
||||
HashSet<BuildNode> visitedNodes = new HashSet<BuildNode>(); |
||||
VisitDependentOnThis(visitedNodes, this); |
||||
totalDependentOnThisCount = visitedNodes.Count; |
||||
return totalDependentOnThisCount; |
||||
} |
||||
} |
||||
|
||||
static void VisitDependentOnThis(HashSet<BuildNode> visitedNodes, BuildNode node) |
||||
{ |
||||
if (visitedNodes.Add(node)) { |
||||
foreach (BuildNode n in node.dependentOnThis) { |
||||
VisitDependentOnThis(visitedNodes, n); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>The list of messages that were not reported because another node held the
|
||||
/// output lock</summary>
|
||||
internal List<string> unreportedMessageList; |
||||
|
||||
public BuildNode(BuildEngine engine, IBuildable project) |
||||
{ |
||||
this.engine = engine; |
||||
this.project = project; |
||||
} |
||||
|
||||
public void ReportError(BuildError error) |
||||
{ |
||||
engine.ReportError(this, error); |
||||
} |
||||
|
||||
public void ReportMessage(string message) |
||||
{ |
||||
engine.ReportMessage(this, message); |
||||
} |
||||
|
||||
public void Done(bool success) |
||||
{ |
||||
engine.OnBuildFinished(this, success); |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region BuildEngine fields and constructor
|
||||
readonly Dictionary<IBuildable, BuildNode> nodeDict = new Dictionary<IBuildable, BuildNode>(); |
||||
readonly BuildOptions options; |
||||
List<Solution.ProjectConfigurationPlatformMatching> configMatchings; |
||||
BuildNode rootNode; |
||||
IBuildable rootProject; |
||||
BuildResults results = new BuildResults(); |
||||
DateTime buildStart; |
||||
|
||||
List<BuildNode> projectsReadyForBuildStart = new List<BuildNode>(); |
||||
int workersToStart, runningWorkers; |
||||
|
||||
private BuildEngine(BuildOptions options, IBuildable rootProject) |
||||
{ |
||||
this.options = options; |
||||
this.rootProject = rootProject; |
||||
} |
||||
#endregion
|
||||
|
||||
#region CreateBuildGraph
|
||||
BuildNode CreateBuildGraph(IBuildable project) |
||||
{ |
||||
BuildNode node; |
||||
if (nodeDict.TryGetValue(project, out node)) { |
||||
return node; |
||||
} |
||||
node = new BuildNode(this, project); |
||||
nodeDict[project] = node; |
||||
InitializeProjectOptions(node); |
||||
InitializeDependencies(node); |
||||
|
||||
node.nodeComplete = true; |
||||
return node; |
||||
} |
||||
|
||||
void InitializeProjectOptions(BuildNode node) |
||||
{ |
||||
IBuildable project = node.project; |
||||
// Create options for building the project
|
||||
node.options = new ProjectBuildOptions(options.Target); |
||||
// find the project configuration
|
||||
foreach (var matching in configMatchings) { |
||||
if (matching.Project == project) { |
||||
node.options.Configuration = matching.Configuration; |
||||
node.options.Platform = matching.Platform; |
||||
} |
||||
} |
||||
if (string.IsNullOrEmpty(node.options.Configuration)) |
||||
node.options.Configuration = options.SolutionConfiguration; |
||||
if (string.IsNullOrEmpty(node.options.Platform)) |
||||
node.options.Platform = options.SolutionPlatform; |
||||
|
||||
// copy properties to project options
|
||||
options.GlobalAdditionalProperties.Foreach(node.options.Properties.Add); |
||||
if (project == rootProject) { |
||||
foreach (var pair in options.ProjectAdditionalProperties) { |
||||
node.options.Properties[pair.Key] = pair.Value; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void InitializeDependencies(BuildNode node) |
||||
{ |
||||
// Initialize dependencies
|
||||
if (options.BuildDependentProjects) { |
||||
var dependencies = node.project.GetBuildDependencies(node.options); |
||||
if (dependencies != null) { |
||||
node.dependencies = dependencies |
||||
.Select<IBuildable, BuildNode>(CreateBuildGraph) |
||||
.Distinct().ToArray(); |
||||
} |
||||
} |
||||
if (node.dependencies == null) { |
||||
node.dependencies = new BuildNode[0]; |
||||
} |
||||
node.outstandingDependencies = node.dependencies.Length; |
||||
foreach (BuildNode d in node.dependencies) { |
||||
if (!d.nodeComplete) |
||||
throw new CyclicDependencyException(node.project, d.project); |
||||
d.dependentOnThis.Add(node); |
||||
} |
||||
if (node.outstandingDependencies == 0) { |
||||
projectsReadyForBuildStart.Add(node); |
||||
} |
||||
} |
||||
|
||||
[Serializable] |
||||
sealed class CyclicDependencyException : Exception |
||||
{ |
||||
public IBuildable Project1, Project2; |
||||
|
||||
public CyclicDependencyException() |
||||
{ |
||||
} |
||||
|
||||
public CyclicDependencyException(IBuildable project1, IBuildable project2) |
||||
{ |
||||
this.Project1 = project1; |
||||
this.Project2 = project2; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Building
|
||||
void StartBuildProjects() |
||||
{ |
||||
lock (this) { |
||||
while (workersToStart > 0) { |
||||
if (projectsReadyForBuildStart.Count == 0) { |
||||
if (runningWorkers == 0) { |
||||
BuildDone(); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
workersToStart--; |
||||
|
||||
int projectToStartIndex = 0; |
||||
for (int i = 1; i < projectsReadyForBuildStart.Count; i++) { |
||||
if (CompareBuildOrder(projectsReadyForBuildStart[i], |
||||
projectsReadyForBuildStart[projectToStartIndex]) < 0) |
||||
{ |
||||
projectToStartIndex = i; |
||||
} |
||||
} |
||||
BuildNode node = projectsReadyForBuildStart[projectToStartIndex]; |
||||
projectsReadyForBuildStart.RemoveAt(projectToStartIndex); |
||||
node.buildStarted = true; |
||||
|
||||
bool hasDependencyErrors = false; |
||||
foreach (BuildNode n in node.dependencies) { |
||||
if (!n.buildFinished) |
||||
throw new Exception("Trying to build project with unfinished dependencies"); |
||||
hasDependencyErrors |= n.hasErrors; |
||||
} |
||||
|
||||
ICSharpCode.Core.LoggingService.Debug("Start building " + node.project.Name); |
||||
runningWorkers++; |
||||
if (hasDependencyErrors) { |
||||
ICSharpCode.Core.LoggingService.Debug("Skipped building " + node.project.Name + " (errors in dependencies)"); |
||||
node.hasErrors = true; |
||||
node.Done(false); |
||||
} else { |
||||
node.project.StartBuild(node.options, node); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
static int CompareBuildOrder(BuildNode a, BuildNode b) |
||||
{ |
||||
// When the build graph looks like this:
|
||||
// 1 2
|
||||
// \ / \
|
||||
// 3 4
|
||||
// / \
|
||||
// 5 6
|
||||
// And we have 2 build workers, determining the build order alphabetically only would result
|
||||
// in this order:
|
||||
// 4, 5
|
||||
// 6
|
||||
// 3
|
||||
// 1, 2
|
||||
// However, we can build faster if we build 5+6 first:
|
||||
// 5, 6
|
||||
// 3, 4
|
||||
// 1, 2
|
||||
//
|
||||
// As heuristic, we first build the projects that have the most projects dependent on it.
|
||||
int r = -(a.TotalDependentOnThisCount.CompareTo(b.TotalDependentOnThisCount)); |
||||
if (r == 0) |
||||
r = string.Compare(a.project.Name, b.project.Name, StringComparison.CurrentCultureIgnoreCase); |
||||
return r; |
||||
} |
||||
|
||||
void OnBuildFinished(BuildNode node, bool success) |
||||
{ |
||||
ICSharpCode.Core.LoggingService.Debug("Finished building " + node.project.Name); |
||||
lock (this) { |
||||
if (node.buildFinished) { |
||||
throw new InvalidOperationException("This node already finished building, do not call IBuildFeedbackSink.Done() multiple times!"); |
||||
} |
||||
runningWorkers--; |
||||
node.buildFinished = true; |
||||
node.hasErrors = !success; |
||||
foreach (BuildNode n in node.dependentOnThis) { |
||||
n.outstandingDependencies--; |
||||
if (n.outstandingDependencies == 0) |
||||
projectsReadyForBuildStart.Add(n); |
||||
} |
||||
workersToStart++; |
||||
} |
||||
LogBuildFinished(node); |
||||
StartBuildProjects(); |
||||
} |
||||
|
||||
bool buildIsDone; |
||||
|
||||
void BuildDone() |
||||
{ |
||||
lock (this) { |
||||
if (buildIsDone) |
||||
return; |
||||
buildIsDone = true; |
||||
} |
||||
foreach (BuildNode n in nodeDict.Values) { |
||||
if (!n.buildFinished) { |
||||
throw new Exception("All workers done, but a project did not finish building"); |
||||
} |
||||
} |
||||
string buildTime = " (" + (DateTime.Now - buildStart).ToString() + ")"; |
||||
|
||||
if (rootNode.hasErrors) { |
||||
results.Result = BuildResultCode.Error; |
||||
|
||||
ReportMessageInternal("${res:MainWindow.CompilerMessages.BuildFailed}" + buildTime); |
||||
//StatusBarService.SetMessage("${res:MainWindow.CompilerMessages.BuildFailed}");
|
||||
} else { |
||||
results.Result = BuildResultCode.Success; |
||||
|
||||
ReportMessageInternal("${res:MainWindow.CompilerMessages.BuildFinished}" + buildTime); |
||||
//StatusBarService.SetMessage("${res:MainWindow.CompilerMessages.BuildFinished}");
|
||||
} |
||||
if (combinedBuildFeedbackSink != null) { |
||||
combinedBuildFeedbackSink.Done(results.Result == BuildResultCode.Success); |
||||
} |
||||
if (options.Callback != null) { |
||||
Gui.WorkbenchSingleton.MainForm.BeginInvoke(options.Callback, results); |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Logging
|
||||
IBuildFeedbackSink combinedBuildFeedbackSink; |
||||
/// <summary>
|
||||
/// The node that currently may output messages.
|
||||
/// To avoid confusing the user in parallel builds, only one project may output messages;
|
||||
/// the messages from other projects will be enqueued and written to the output as soon
|
||||
/// as the project holding the lock finishes building.
|
||||
/// </summary>
|
||||
BuildNode nodeWithOutputLock; |
||||
Queue<BuildNode> nodesWaitingForOutputLock = new Queue<BuildNode>(); |
||||
|
||||
void ReportError(BuildNode source, BuildError error) |
||||
{ |
||||
if (!error.IsWarning) |
||||
source.hasErrors = true; |
||||
results.Add(error); |
||||
ReportMessage(source, error.ToString()); |
||||
if (combinedBuildFeedbackSink != null) { |
||||
combinedBuildFeedbackSink.ReportError(error); |
||||
} |
||||
} |
||||
|
||||
void ReportMessage(BuildNode source, string message) |
||||
{ |
||||
Console.WriteLine(source.project.Name + " reports " + message); |
||||
bool hasOutputLock; |
||||
lock (this) { |
||||
if (nodeWithOutputLock == null) { |
||||
nodeWithOutputLock = source; |
||||
} |
||||
hasOutputLock = nodeWithOutputLock == source; |
||||
if (!hasOutputLock) { |
||||
if (source.unreportedMessageList == null) { |
||||
nodesWaitingForOutputLock.Enqueue(source); |
||||
source.unreportedMessageList = new List<string>(); |
||||
} |
||||
source.unreportedMessageList.Add(message); |
||||
} |
||||
} |
||||
if (hasOutputLock) { |
||||
ReportMessageInternal(message); |
||||
} |
||||
} |
||||
|
||||
void LogBuildFinished(BuildNode node) |
||||
{ |
||||
List<string> messagesToReport = null; |
||||
bool newNodeWithOutputLockAlreadyFinishedBuilding = false; |
||||
lock (this) { |
||||
if (node == nodeWithOutputLock) { |
||||
if (nodesWaitingForOutputLock.Count > 0) { |
||||
nodeWithOutputLock = nodesWaitingForOutputLock.Dequeue(); |
||||
messagesToReport = nodeWithOutputLock.unreportedMessageList; |
||||
nodeWithOutputLock.unreportedMessageList = null; |
||||
newNodeWithOutputLockAlreadyFinishedBuilding = nodeWithOutputLock.buildFinished; |
||||
} else { |
||||
nodeWithOutputLock = null; |
||||
} |
||||
} |
||||
} |
||||
if (messagesToReport != null) { |
||||
messagesToReport.Foreach(ReportMessageInternal); |
||||
} |
||||
if (newNodeWithOutputLockAlreadyFinishedBuilding) { |
||||
// if the node already finished building before it got the output lock, we need
|
||||
// to release the output lock again here
|
||||
LogBuildFinished(nodeWithOutputLock); |
||||
} |
||||
} |
||||
|
||||
void ReportMessageInternal(string message) |
||||
{ |
||||
if (combinedBuildFeedbackSink != null) |
||||
combinedBuildFeedbackSink.ReportMessage(message); |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
// <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.Project |
||||
{ |
||||
/// <summary>
|
||||
/// Interface for reporting build results in real-time.
|
||||
/// </summary>
|
||||
public interface IBuildFeedbackSink |
||||
{ |
||||
/// <summary>
|
||||
/// Reports an build error by adding it to the error list.
|
||||
/// This member is thread-safe.
|
||||
/// </summary>
|
||||
void ReportError(BuildError error); |
||||
|
||||
/// <summary>
|
||||
/// Reports a build message.
|
||||
/// This member is thread-safe.
|
||||
/// </summary>
|
||||
void ReportMessage(string message); |
||||
|
||||
/// <summary>
|
||||
/// Notifies the build engine that the build of a project has finished.
|
||||
/// You sould not call any methods after the Done() call.
|
||||
/// This member is thread-safe.
|
||||
/// </summary>
|
||||
void Done(bool success); |
||||
} |
||||
} |
@ -1,253 +0,0 @@
@@ -1,253 +0,0 @@
|
||||
// <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.Text; |
||||
using System.Collections.Generic; |
||||
using Microsoft.Build.Framework; |
||||
using Microsoft.Build.BuildEngine; |
||||
using ICSharpCode.SharpDevelop.Gui; |
||||
using System.IO; |
||||
using ICSharpCode.Core; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Project |
||||
{ |
||||
public sealed class MSBuildEngineWorker |
||||
{ |
||||
MSBuildEngine parentEngine; |
||||
MSBuildEngine.BuildRun buildRun; |
||||
Engine engine; |
||||
SharpDevelopLogger logger; |
||||
|
||||
internal MSBuildEngineWorker(MSBuildEngine parentEngine, MSBuildEngine.BuildRun buildRun) |
||||
{ |
||||
this.parentEngine = parentEngine; |
||||
this.buildRun = buildRun; |
||||
engine = buildRun.CreateEngine(); |
||||
|
||||
logger = new SharpDevelopLogger(this); |
||||
engine.RegisterLogger(logger); |
||||
foreach (IMSBuildAdditionalLogger loggerProvider in MSBuildEngine.AdditionalMSBuildLoggers) { |
||||
engine.RegisterLogger(loggerProvider.CreateLogger(this)); |
||||
} |
||||
} |
||||
|
||||
internal bool Build(MSBuildEngine.ProjectToBuild ptb) |
||||
{ |
||||
LoggingService.Debug("Run MSBuild on " + ptb.file); |
||||
|
||||
if (!string.IsNullOrEmpty(ptb.configuration)) { |
||||
engine.GlobalProperties.SetProperty("Configuration", ptb.configuration); |
||||
} |
||||
if (!string.IsNullOrEmpty(ptb.platform)) { |
||||
engine.GlobalProperties.SetProperty("Platform", ptb.platform); |
||||
} |
||||
|
||||
bool success; |
||||
lock (MSBuildInternals.InProcessMSBuildLock) { |
||||
Microsoft.Build.BuildEngine.Project project = buildRun.LoadProject(engine, ptb.file); |
||||
if (project == null) { |
||||
LoggingService.Debug("Error loading " + ptb.file); |
||||
return false; |
||||
} |
||||
foreach (string additionalTargetFile in MSBuildEngine.AdditionalTargetFiles) { |
||||
project.AddNewImport(additionalTargetFile, null); |
||||
} |
||||
|
||||
if (string.IsNullOrEmpty(ptb.targets)) { |
||||
success = engine.BuildProject(project); |
||||
} else { |
||||
success = engine.BuildProject(project, ptb.targets.Split(';')); |
||||
} |
||||
} |
||||
|
||||
logger.FlushCurrentError(); |
||||
ReleaseOutput(); |
||||
|
||||
LoggingService.Debug("MSBuild on " + ptb.file + " finished " + (success ? "successfully" : "with error")); |
||||
return success; |
||||
} |
||||
|
||||
bool outputAcquired; |
||||
StringBuilder cachedOutput; |
||||
|
||||
public void OutputText(string text) |
||||
{ |
||||
if (outputAcquired == false && cachedOutput == null) { |
||||
outputAcquired = buildRun.TryAquireOutputLock(); |
||||
if (!outputAcquired) { |
||||
cachedOutput = new StringBuilder(); |
||||
} |
||||
} |
||||
if (outputAcquired) { |
||||
parentEngine.MessageView.AppendText(text); |
||||
} else { |
||||
cachedOutput.Append(text); |
||||
} |
||||
} |
||||
|
||||
void ReleaseOutput() |
||||
{ |
||||
if (cachedOutput != null) { |
||||
buildRun.EnqueueTextForAppendWhenOutputLockIsReleased(cachedOutput.ToString()); |
||||
cachedOutput = null; |
||||
} |
||||
if (outputAcquired) { |
||||
buildRun.ReleaseOutputLock(); |
||||
} |
||||
} |
||||
|
||||
#region CurrentBuild properties
|
||||
BuildError currentErrorOrWarning; |
||||
|
||||
/// <summary>
|
||||
/// Gets the last build error/warning created by the default
|
||||
/// SharpDevelop logger.
|
||||
/// </summary>
|
||||
public BuildError CurrentErrorOrWarning { |
||||
get { |
||||
return currentErrorOrWarning; |
||||
} |
||||
} |
||||
|
||||
Stack<string> projectFiles = new Stack<string>(); |
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the currently building project file.
|
||||
/// </summary>
|
||||
public string CurrentProjectFile { |
||||
get { |
||||
if (projectFiles.Count == 0) |
||||
return null; |
||||
else |
||||
return projectFiles.Peek(); |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
class SharpDevelopLogger : ILogger |
||||
{ |
||||
MSBuildEngineWorker worker; |
||||
BuildResults results; |
||||
|
||||
public SharpDevelopLogger(MSBuildEngineWorker worker) |
||||
{ |
||||
this.worker = worker; |
||||
this.results = worker.buildRun.currentResults; |
||||
} |
||||
|
||||
void AppendText(string text) |
||||
{ |
||||
worker.OutputText(text + "\r\n"); |
||||
} |
||||
|
||||
internal void FlushCurrentError() |
||||
{ |
||||
if (worker.currentErrorOrWarning != null) { |
||||
AppendText(worker.currentErrorOrWarning.ToString()); |
||||
worker.currentErrorOrWarning = null; |
||||
} |
||||
} |
||||
|
||||
void OnProjectStarted(object sender, ProjectStartedEventArgs e) |
||||
{ |
||||
worker.projectFiles.Push(e.ProjectFile); |
||||
} |
||||
|
||||
void OnProjectFinished(object sender, ProjectFinishedEventArgs e) |
||||
{ |
||||
FlushCurrentError(); |
||||
worker.projectFiles.Pop(); |
||||
} |
||||
|
||||
string activeTaskName; |
||||
|
||||
void OnTaskStarted(object sender, TaskStartedEventArgs e) |
||||
{ |
||||
activeTaskName = e.TaskName; |
||||
if (MSBuildEngine.CompileTaskNames.Contains(e.TaskName.ToLowerInvariant())) { |
||||
AppendText("${res:MainWindow.CompilerMessages.CompileVerb} " + Path.GetFileNameWithoutExtension(e.ProjectFile)); |
||||
} |
||||
} |
||||
|
||||
void OnTaskFinished(object sender, TaskFinishedEventArgs e) |
||||
{ |
||||
FlushCurrentError(); |
||||
} |
||||
|
||||
void OnError(object sender, BuildErrorEventArgs e) |
||||
{ |
||||
AppendError(e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, false); |
||||
} |
||||
|
||||
void OnWarning(object sender, BuildWarningEventArgs e) |
||||
{ |
||||
AppendError(e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, true); |
||||
} |
||||
|
||||
void AppendError(string file, int lineNumber, int columnNumber, string code, string message, bool isWarning) |
||||
{ |
||||
if (string.Equals(file, activeTaskName, StringComparison.InvariantCultureIgnoreCase)) { |
||||
file = ""; |
||||
} else if (FileUtility.IsValidPath(file)) { |
||||
bool isShortFileName = file == Path.GetFileNameWithoutExtension(file); |
||||
if (worker.CurrentProjectFile != null) { |
||||
file = Path.Combine(Path.GetDirectoryName(worker.CurrentProjectFile), file); |
||||
} |
||||
if (isShortFileName && !File.Exists(file)) { |
||||
file = ""; |
||||
} |
||||
} |
||||
FlushCurrentError(); |
||||
BuildError error = new BuildError(file, lineNumber, columnNumber, code, message); |
||||
error.IsWarning = isWarning; |
||||
results.Add(error); |
||||
worker.currentErrorOrWarning = error; |
||||
} |
||||
|
||||
#region ILogger interface implementation
|
||||
LoggerVerbosity verbosity = LoggerVerbosity.Minimal; |
||||
|
||||
public LoggerVerbosity Verbosity { |
||||
get { |
||||
return verbosity; |
||||
} |
||||
set { |
||||
verbosity = value; |
||||
} |
||||
} |
||||
|
||||
string parameters; |
||||
|
||||
public string Parameters { |
||||
get { |
||||
return parameters; |
||||
} |
||||
set { |
||||
parameters = value; |
||||
} |
||||
} |
||||
|
||||
public void Initialize(IEventSource eventSource) |
||||
{ |
||||
eventSource.ProjectStarted += new ProjectStartedEventHandler(OnProjectStarted); |
||||
eventSource.ProjectFinished += new ProjectFinishedEventHandler(OnProjectFinished); |
||||
eventSource.TaskStarted += new TaskStartedEventHandler(OnTaskStarted); |
||||
eventSource.TaskFinished += new TaskFinishedEventHandler(OnTaskFinished); |
||||
|
||||
eventSource.ErrorRaised += new BuildErrorEventHandler(OnError); |
||||
eventSource.WarningRaised += new BuildWarningEventHandler(OnWarning); |
||||
} |
||||
|
||||
public void Shutdown() |
||||
{ |
||||
|
||||
} |
||||
#endregion
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
// <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 ICSharpCode.Core; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.Project |
||||
{ |
||||
/// <summary>
|
||||
/// A project that is not a real project, but a MSBuild file included as project.
|
||||
/// </summary>
|
||||
public class MSBuildFileProject : AbstractProject |
||||
{ |
||||
public MSBuildFileProject(string fileName, string title) |
||||
{ |
||||
Name = title; |
||||
FileName = fileName; |
||||
TypeGuid = "{00000000-0000-0000-0000-000000000000}"; |
||||
} |
||||
|
||||
public override void StartBuild(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink) |
||||
{ |
||||
MSBuildEngine.Build(this, options, feedbackSink); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
// <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 |
||||
{ |
||||
public string ProjectFileName { get; set; } |
||||
public string Target { get; set; } |
||||
|
||||
Dictionary<string, string> properties = new Dictionary<string, string>(); |
||||
|
||||
public Dictionary<string, string> Properties { |
||||
get { return properties; } |
||||
} |
||||
|
||||
List<string> additionalImports = new List<string>(); |
||||
|
||||
public List<string> AdditionalImports { |
||||
get { return additionalImports; } |
||||
} |
||||
|
||||
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); |
||||
} |
||||
return b.ToString(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
// <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("")] |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
// <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 |
||||
{ |
||||
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; |
||||
|
||||
public void RaiseEvent(BuildEventArgs e) |
||||
{ |
||||
if (e is BuildStatusEventArgs) { |
||||
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 TaskStartedEventArgs) { |
||||
if (TaskStarted != null) |
||||
TaskStarted(this, (TaskStartedEventArgs)e); |
||||
} else if (e is TaskFinishedEventArgs) { |
||||
if (TaskFinished != null) |
||||
TaskFinished(this, (TaskFinishedEventArgs)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); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
<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> |
||||
</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>AnyCPU</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.Data" /> |
||||
<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,102 @@
@@ -0,0 +1,102 @@
|
||||
// <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.Net; |
||||
using System.Net.Sockets; |
||||
using System.IO; |
||||
using System.Threading; |
||||
using System.Reflection; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.BuildWorker.Interprocess |
||||
{ |
||||
/// <summary>
|
||||
/// Used in the worker process to refer to the host.
|
||||
/// </summary>
|
||||
public class HostProcess |
||||
{ |
||||
object workerObject; |
||||
|
||||
public HostProcess(object workerObject) |
||||
{ |
||||
this.workerObject = workerObject; |
||||
} |
||||
|
||||
TcpClient client; |
||||
PacketReceiver receiver; |
||||
PacketSender sender; |
||||
|
||||
ManualResetEvent shutdownEvent; |
||||
bool connectionIsLost; |
||||
|
||||
internal const int SendKeepAliveInterval = 10000; |
||||
|
||||
public void WorkerProcessMain(string argument) |
||||
{ |
||||
int port = int.Parse(argument); |
||||
|
||||
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; |
||||
|
||||
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) |
||||
{ |
||||
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(); |
||||
} |
||||
} |
||||
|
||||
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); |
||||
} catch (Exception ex) { |
||||
Program.Log(ex.ToString()); |
||||
CallMethodOnHost("ReportException", ex); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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(Exception ex); |
||||
} |
||||
} |
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
// <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; |
||||
} |
||||
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)); |
||||
} |
||||
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,67 @@
@@ -0,0 +1,67 @@
|
||||
// <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 (Exception 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,224 @@
@@ -0,0 +1,224 @@
|
||||
// <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.Reflection; |
||||
using System.Net; |
||||
using System.Net.Sockets; |
||||
using System.IO; |
||||
using System.Runtime.Serialization.Formatters; |
||||
using System.Runtime.Serialization.Formatters.Binary; |
||||
using System.Runtime.Serialization; |
||||
using System.Threading; |
||||
|
||||
namespace ICSharpCode.SharpDevelop.BuildWorker.Interprocess |
||||
{ |
||||
/// <summary>
|
||||
/// Manages a worker process that communicates with the host using a local TCP connection.
|
||||
/// </summary>
|
||||
public sealed class WorkerProcess |
||||
{ |
||||
object hostObject; |
||||
|
||||
public WorkerProcess(IHostObject hostObject) |
||||
{ |
||||
if (hostObject == null) |
||||
throw new ArgumentException("hostObject"); |
||||
this.hostObject = hostObject; |
||||
} |
||||
|
||||
TcpListener listener; |
||||
Process process; |
||||
|
||||
TcpClient client; |
||||
PacketSender sender; |
||||
PacketReceiver receiver; |
||||
|
||||
public void Start(ProcessStartInfo info) |
||||
{ |
||||
listener = new TcpListener(IPAddress.Loopback, 0); |
||||
listener.Start(); |
||||
string argument = ((IPEndPoint)listener.LocalEndpoint).Port.ToString(); |
||||
|
||||
string oldArguments = info.Arguments; |
||||
info.Arguments += " " + argument; |
||||
process = Process.Start(info); |
||||
info.Arguments = oldArguments; |
||||
|
||||
SetTimeout(); |
||||
listener.BeginAcceptTcpClient(OnAcceptTcpClient, null); |
||||
} |
||||
|
||||
Timer currentTimer; |
||||
|
||||
void SetTimeout() |
||||
{ |
||||
lock (this) { |
||||
ClearTimeout(); |
||||
currentTimer = new Timer(OnTimeout, null, HostProcess.SendKeepAliveInterval * 3 / 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) |
||||
{ |
||||
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) { |
||||
MethodCall mc = (MethodCall)DeserializeObject(e.Packet); |
||||
mc.CallOn(hostObject); |
||||
} |
||||
} |
||||
|
||||
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) |
||||
{ |
||||
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); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,323 @@
@@ -0,0 +1,323 @@
|
||||
// <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.IO; |
||||
using System.Diagnostics; |
||||
using System.Threading; |
||||
using ICSharpCode.SharpDevelop.BuildWorker.Interprocess; |
||||
using Microsoft.Build.Framework; |
||||
using Microsoft.Build.BuildEngine; |
||||
|
||||
// activate this define to see the build worker window
|
||||
//#define WORKERDEBUG
|
||||
|
||||
namespace ICSharpCode.SharpDevelop.BuildWorker |
||||
{ |
||||
class Program |
||||
{ |
||||
static HostProcess host; |
||||
BuildJob currentJob; |
||||
bool requestCancellation; |
||||
|
||||
internal static void Main(string[] args) |
||||
{ |
||||
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomain_CurrentDomain_UnhandledException); |
||||
|
||||
if (args.Length == 2 && args[0] == "worker") { |
||||
try { |
||||
host = new HostProcess(new Program()); |
||||
host.WorkerProcessMain(args[1]); |
||||
} catch (Exception ex) { |
||||
ShowMessageBox(ex.ToString()); |
||||
} |
||||
} else { |
||||
Program.Log("ICSharpCode.SharpDevelop.BuildWorker.exe is used to compile " + |
||||
"MSBuild projects inside SharpDevelop."); |
||||
Program.Log("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()); |
||||
} |
||||
|
||||
internal static ProcessStartInfo CreateStartInfo() |
||||
{ |
||||
ProcessStartInfo info = new ProcessStartInfo(typeof(Program).Assembly.Location); |
||||
info.WorkingDirectory = Path.GetDirectoryName(info.FileName); |
||||
info.Arguments = "worker"; |
||||
#if RELEASE || !WORKERDEBUG
|
||||
info.UseShellExecute = false; |
||||
info.CreateNoWindow = true; |
||||
#endif
|
||||
return info; |
||||
} |
||||
|
||||
internal static void ShowMessageBox(string text) |
||||
{ |
||||
System.Windows.Forms.MessageBox.Show(text, "SharpDevelop Build Worker Process"); |
||||
} |
||||
|
||||
[Conditional("DEBUG")] |
||||
internal static void Log(string text) |
||||
{ |
||||
#if WORKERDEBUG
|
||||
DateTime now = DateTime.Now; |
||||
Console.WriteLine(now.ToString() + "," + now.Millisecond.ToString("d3") + " " + text); |
||||
#endif
|
||||
} |
||||
|
||||
// Called with CallMethodOnWorker
|
||||
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 DEBUG && 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
|
||||
public void CancelBuild() |
||||
{ |
||||
lock (this) { |
||||
requestCancellation = true; |
||||
} |
||||
} |
||||
|
||||
Engine engine; |
||||
|
||||
void RunThread() |
||||
{ |
||||
Program.Log("In build thread"); |
||||
bool success = false; |
||||
try { |
||||
if (engine == null) { |
||||
engine = CreateEngine(); |
||||
} |
||||
success = DoBuild(); |
||||
} catch (Exception ex) { |
||||
host.CallMethodOnHost("ReportException", ex); |
||||
} finally { |
||||
Program.Log("BuildDone"); |
||||
|
||||
#if DEBUG && WORKERDEBUG
|
||||
Console.Title = "BuildWorker - no job"; |
||||
#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)); |
||||
|
||||
return engine; |
||||
} |
||||
|
||||
InProcessLogger inProcessLogger; |
||||
|
||||
internal void BuildInProcess(BuildSettings settings, BuildJob job) |
||||
{ |
||||
lock (this) { |
||||
if (currentJob != null) |
||||
throw new InvalidOperationException("Already running a job"); |
||||
currentJob = job; |
||||
requestCancellation = false; |
||||
inProcessLogger = new InProcessLogger(); |
||||
} |
||||
bool success = false; |
||||
try { |
||||
if (engine == null) { |
||||
engine = new Engine(ToolsetDefinitionLocations.Registry |
||||
| ToolsetDefinitionLocations.ConfigurationFile); |
||||
} |
||||
engine.UnregisterAllLoggers(); |
||||
engine.RegisterLogger(inProcessLogger); |
||||
foreach (ILogger logger in settings.Logger) { |
||||
logger.Initialize(inProcessLogger.EventSource); |
||||
} |
||||
success = DoBuild(); |
||||
foreach (ILogger logger in settings.Logger) { |
||||
logger.Shutdown(); |
||||
} |
||||
engine.UnregisterAllLoggers(); |
||||
} catch (Exception ex) { |
||||
if (WorkerManager.ShowError != null) |
||||
WorkerManager.ShowError(ex); |
||||
else |
||||
Program.ShowMessageBox(ex.ToString()); |
||||
} finally { |
||||
lock (this) { |
||||
currentJob = null; |
||||
} |
||||
if (settings.BuildDoneCallback != null) |
||||
settings.BuildDoneCallback(success); |
||||
} |
||||
} |
||||
|
||||
bool DoBuild() |
||||
{ |
||||
engine.GlobalProperties.Clear(); |
||||
foreach (KeyValuePair<string, string> pair in currentJob.Properties) { |
||||
engine.GlobalProperties.SetProperty(pair.Key, pair.Value); |
||||
} |
||||
|
||||
Project project = LoadProject(engine, currentJob.ProjectFileName); |
||||
if (project == null) |
||||
return false; |
||||
|
||||
if (string.IsNullOrEmpty(currentJob.Target)) { |
||||
return engine.BuildProject(project); |
||||
} else { |
||||
return engine.BuildProject(project, currentJob.Target.Split(';')); |
||||
} |
||||
} |
||||
|
||||
Project LoadProject(Engine engine, string fileName) |
||||
{ |
||||
Project project = engine.CreateNewProject(); |
||||
try { |
||||
project.Load(fileName); |
||||
|
||||
// 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) |
||||
{ |
||||
if (host != null) { |
||||
HostReportEvent(e); |
||||
} else { |
||||
// enable error reporting for in-process builds
|
||||
InProcessLogger logger = inProcessLogger; |
||||
if (logger != null) { |
||||
logger.EventSource.RaiseEvent(e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void HostReportEvent(BuildEventArgs e) |
||||
{ |
||||
host.CallMethodOnHost("ReportEvent", 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; |
||||
eventSource.AnyEventRaised += OnAnyEventRaised; |
||||
} |
||||
|
||||
public void Shutdown() |
||||
{ |
||||
eventSource.AnyEventRaised -= OnAnyEventRaised; |
||||
} |
||||
|
||||
void OnAnyEventRaised(object sender, BuildEventArgs e) |
||||
{ |
||||
if (program.requestCancellation) |
||||
throw new BuildCancelException(); |
||||
program.HostReportEvent(e); |
||||
} |
||||
} |
||||
|
||||
sealed class InProcessLogger : ILogger |
||||
{ |
||||
public readonly EventSource EventSource = new EventSource(); |
||||
|
||||
public LoggerVerbosity Verbosity { get; set; } |
||||
|
||||
public string Parameters { get; set; } |
||||
|
||||
IEventSource realEventSource; |
||||
|
||||
public void Initialize(IEventSource eventSource) |
||||
{ |
||||
this.realEventSource = eventSource; |
||||
this.realEventSource.AnyEventRaised += OnAnyEventRaised; |
||||
} |
||||
|
||||
public void Shutdown() |
||||
{ |
||||
this.realEventSource.AnyEventRaised -= OnAnyEventRaised; |
||||
} |
||||
|
||||
void OnAnyEventRaised(object sender, BuildEventArgs e) |
||||
{ |
||||
this.EventSource.RaiseEvent(e); |
||||
} |
||||
} |
||||
|
||||
[Serializable] |
||||
sealed class BuildCancelException : Exception |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,265 @@
@@ -0,0 +1,265 @@
|
||||
// <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) { |
||||
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) |
||||
{ |
||||
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) |
||||
{ |
||||
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 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
|
||||
public void ReportEvent(BuildEventArgs e) |
||||
{ |
||||
BuildRun buildRun = currentBuildRun; |
||||
if (buildRun != null) { |
||||
buildRun.RaiseEvent(e); |
||||
} |
||||
} |
||||
|
||||
// Called with CallMethodOnHost
|
||||
public void BuildDone(bool success) |
||||
{ |
||||
BuildRun 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); |
||||
} |
||||
} |
||||
|
||||
public void ReportException(Exception ex) |
||||
{ |
||||
// shutdown worker if it produced an exception
|
||||
try { |
||||
process.Shutdown(); |
||||
} catch {} |
||||
|
||||
if (ShowError != null) |
||||
ShowError(ex); |
||||
else |
||||
Program.ShowMessageBox(ex.ToString()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
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