public static int Execute(string ActionsFileName, int MaxProcesses, bool bStopOnErrors)
        {
            List <BuildAction> Actions = ReadActions(ActionsFileName);

            CommandUtils.Log("Building with {0} {1}...", MaxProcesses, (MaxProcesses == 1)? "process" : "processes");

            // Create the list of things to process
            List <BuildAction> QueuedActions = new List <BuildAction>();

            foreach (BuildAction Action in Actions)
            {
                if (Action.MissingDependencyCount == 0)
                {
                    QueuedActions.Add(Action);
                }
            }

            // Create a job object for all the child processes
            int    ExitCode      = 0;
            string CurrentPrefix = "";
            Dictionary <BuildActionExecutor, Thread> ExecutingActions = new Dictionary <BuildActionExecutor, Thread>();
            List <BuildActionExecutor> CompletedActions = new List <BuildActionExecutor>();

            using (ManualResetEvent CompletedEvent = new ManualResetEvent(false))
            {
                while (QueuedActions.Count > 0 || ExecutingActions.Count > 0)
                {
                    // Sort the actions by the number of things dependent on them
                    QueuedActions.Sort((A, B) => (A.TotalDependants == B.TotalDependants)? (B.SortIndex - A.SortIndex) : (B.TotalDependants - A.TotalDependants));

                    // Create threads up to the maximum number of actions
                    while (ExecutingActions.Count < MaxProcesses && QueuedActions.Count > 0)
                    {
                        BuildAction Action = QueuedActions[QueuedActions.Count - 1];
                        QueuedActions.RemoveAt(QueuedActions.Count - 1);

                        BuildActionExecutor ExecutingAction = new BuildActionExecutor(Action, CompletedEvent, CompletedActions);

                        Thread ExecutingThread = new Thread(() => { ExecutingAction.Run(); });
                        ExecutingThread.Name = String.Format("Build:{0}", Action.Caption);
                        ExecutingThread.Start();

                        ExecutingActions.Add(ExecutingAction, ExecutingThread);
                    }

                    // Wait for something to finish
                    CompletedEvent.WaitOne();

                    // Wait for something to finish and flush it to the log
                    lock (CompletedActions)
                    {
                        foreach (BuildActionExecutor CompletedAction in CompletedActions)
                        {
                            // Join the thread
                            Thread CompletedThread = ExecutingActions[CompletedAction];
                            CompletedThread.Join();
                            ExecutingActions.Remove(CompletedAction);

                            // Write it to the log
                            if (CompletedAction.LogLines.Count > 0)
                            {
                                if (CurrentPrefix != CompletedAction.Action.GroupPrefix)
                                {
                                    CurrentPrefix = CompletedAction.Action.GroupPrefix;
                                    CommandUtils.Log(CurrentPrefix);
                                }
                                foreach (string LogLine in CompletedAction.LogLines)
                                {
                                    CommandUtils.Log(LogLine);
                                }
                            }

                            // Check the exit code
                            if (CompletedAction.ExitCode == 0)
                            {
                                // Mark all the dependents as done
                                foreach (BuildAction DependantAction in CompletedAction.Action.Dependants)
                                {
                                    if (--DependantAction.MissingDependencyCount == 0)
                                    {
                                        QueuedActions.Add(DependantAction);
                                    }
                                }
                            }
                            else
                            {
                                // Update the exit code if it's not already set
                                if (ExitCode == 0)
                                {
                                    ExitCode = CompletedAction.ExitCode;
                                }
                            }
                        }
                        CompletedActions.Clear();
                    }

                    // If we've already got a non-zero exit code, clear out the list of queued actions so nothing else will run
                    if (ExitCode != 0 && bStopOnErrors)
                    {
                        QueuedActions.Clear();
                    }
                }
            }
            return(ExitCode);
        }
		public static int Execute(string ActionsFileName, int MaxProcesses)
		{
			List<BuildAction> Actions = ReadActions(ActionsFileName);

			CommandUtils.Log("Building with {0} {1}...", MaxProcesses, (MaxProcesses == 1)? "process" : "processes");

			// Create the list of things to process
			List<BuildAction> QueuedActions = new List<BuildAction>();
			foreach(BuildAction Action in Actions)
			{
				if(Action.MissingDependencyCount == 0)
				{
					QueuedActions.Add(Action);
				}
			}

			// Create a job object for all the child processes
			int ExitCode = 0;
			string CurrentPrefix = "";
			Dictionary<BuildActionExecutor, Thread> ExecutingActions = new Dictionary<BuildActionExecutor,Thread>();
			List<BuildActionExecutor> CompletedActions = new List<BuildActionExecutor>();

			using(ManualResetEvent CompletedEvent = new ManualResetEvent(false))
			{
				while(QueuedActions.Count > 0 || ExecutingActions.Count > 0)
				{
					// Sort the actions by the number of things dependent on them
					QueuedActions.Sort((A, B) => (A.TotalDependants == B.TotalDependants)? (B.SortIndex - A.SortIndex) : (B.TotalDependants - A.TotalDependants));

					// Create threads up to the maximum number of actions
					while(ExecutingActions.Count < MaxProcesses && QueuedActions.Count > 0)
					{
						BuildAction Action = QueuedActions[QueuedActions.Count - 1];
						QueuedActions.RemoveAt(QueuedActions.Count - 1);

						BuildActionExecutor ExecutingAction = new BuildActionExecutor(Action, CompletedEvent, CompletedActions);

						Thread ExecutingThread = new Thread(() => { ExecutingAction.Run(); });
						ExecutingThread.Name = String.Format("Build:{0}", Action.Caption);
						ExecutingThread.Start();

						ExecutingActions.Add(ExecutingAction, ExecutingThread);
					}

					// Wait for something to finish
					CompletedEvent.WaitOne();

					// Wait for something to finish and flush it to the log
					lock(CompletedActions)
					{
						foreach(BuildActionExecutor CompletedAction in CompletedActions)
						{
							// Join the thread
							Thread CompletedThread = ExecutingActions[CompletedAction];
							CompletedThread.Join();
							ExecutingActions.Remove(CompletedAction);

							// Write it to the log
							if(CompletedAction.LogLines.Count > 0)
							{
								if(CurrentPrefix != CompletedAction.Action.GroupPrefix)
								{
									CurrentPrefix = CompletedAction.Action.GroupPrefix;
									CommandUtils.Log(CurrentPrefix);
								}
								foreach(string LogLine in CompletedAction.LogLines)
								{
									CommandUtils.Log(LogLine);
								}
							}

							// Check the exit code
							if(CompletedAction.ExitCode == 0)
							{
								// Mark all the dependents as done
								foreach(BuildAction DependantAction in CompletedAction.Action.Dependants)
								{
									if(--DependantAction.MissingDependencyCount == 0)
									{
										QueuedActions.Add(DependantAction);
									}
								}
							}
							else
							{
								// Update the exit code if it's not already set
								if(ExitCode == 0)
								{
									ExitCode = CompletedAction.ExitCode;
								}
							}
						}
						CompletedActions.Clear();
					}

					// If we've already got a non-zero exit code, clear out the list of queued actions so nothing else will run
					if(ExitCode != 0)
					{
						QueuedActions.Clear();
					}
				}
			}
			return ExitCode;
		}