Esempio n. 1
0
        public GUBPBranchConfig(IEnumerable<UnrealTargetPlatform> InHostPlatforms, string InBranchName, BranchInfo InBranch, GUBPBranchHacker.BranchOptions InBranchOptions, bool bInForceIncrementalCompile, JobInfo JobInfo)
		{
			HostPlatforms = new List<UnrealTargetPlatform>(InHostPlatforms);
			BranchName = InBranchName;
			Branch = InBranch;
			BranchOptions = InBranchOptions;
			bForceIncrementalCompile = bInForceIncrementalCompile;
			this.JobInfo = JobInfo;
		}
		public GUBPBranchConfig(int InCL, IEnumerable<UnrealTargetPlatform> InHostPlatforms, string InBranchName, BranchInfo InBranch, GUBPBranchHacker.BranchOptions InBranchOptions, bool bInForceIncrementalCompile, bool bInPreflightBuild, string InPreflightMangleSuffix)
		{
			CL = InCL;
			HostPlatforms = new List<UnrealTargetPlatform>(InHostPlatforms);
			BranchName = InBranchName;
			Branch = InBranch;
			BranchOptions = InBranchOptions;
			bForceIncrementalCompile = bInForceIncrementalCompile;
			bPreflightBuild = bInPreflightBuild;
			PreflightMangleSuffix = InPreflightMangleSuffix;
		}
Esempio n. 3
0
    private GUBPBranchHacker.BranchOptions GetBranchOptions(string Branch)
    {
        if (BranchHackers == null)
        {
            BranchHackers = new List<GUBPBranchHacker>();
            Assembly[] LoadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly Dll in LoadedAssemblies)
            {
				Type[] AllTypes = GetTypesFromAssembly(Dll);
                foreach (var PotentialConfigType in AllTypes)
                {
                    if (PotentialConfigType != typeof(GUBPBranchHacker) && typeof(GUBPBranchHacker).IsAssignableFrom(PotentialConfigType))
                    {
                        GUBPBranchHacker Config = Activator.CreateInstance(PotentialConfigType) as GUBPBranchHacker;
                        if (Config != null)
                        {
                            BranchHackers.Add(Config);
                        }
                    }
                }
            }
        }
        GUBPBranchHacker.BranchOptions Result = new GUBPBranchHacker.BranchOptions();
        foreach (GUBPBranchHacker Hacker in BranchHackers)
        {
            Hacker.ModifyOptions(this, ref Result, Branch);
        }
        return Result;
    }
Esempio n. 4
0
    public override void ExecuteBuild()
    {
        Log("************************* GUBP");
        string PreflightShelveCLString = GetEnvVar("uebp_PreflightShelveCL");
        if ((!String.IsNullOrEmpty(PreflightShelveCLString) && IsBuildMachine) || ParseParam("PreflightTest"))
        {
            Log("**** Preflight shelve {0}", PreflightShelveCLString);
            if (!String.IsNullOrEmpty(PreflightShelveCLString))
            {
                PreflightShelveCL = int.Parse(PreflightShelveCLString);
                if (PreflightShelveCL < 2000000)
                {
                    throw new AutomationException(String.Format( "{0} does not look like a CL", PreflightShelveCL));
                }
            }
            bPreflightBuild = true;
        }

        ECProject = ParseParamValue("ECProject");
        if (ECProject == null)
        {
            ECProject = "";
        }
        HostPlatforms = new List<UnrealTargetPlatform>();
        if (!ParseParam("NoPC"))
        {
            HostPlatforms.Add(UnrealTargetPlatform.Win64);
        }
        if (P4Enabled)
        {
            BranchName = P4Env.BuildRootP4;
        }
        else
        {
            BranchName = ParseParamValue("BranchName", "");
        }
        BranchOptions = GetBranchOptions(BranchName);
        bool WithMac = !BranchOptions.PlatformsToRemove.Contains(UnrealTargetPlatform.Mac);
        if (ParseParam("NoMac"))
        {
            WithMac = false;
        }
        if (WithMac)
        {
            HostPlatforms.Add(UnrealTargetPlatform.Mac);
        }

        bool WithLinux = !BranchOptions.PlatformsToRemove.Contains(UnrealTargetPlatform.Linux);
        bool WithoutLinux = ParseParam("NoLinux");
        // @TODO: exclude temporarily unless running on a Linux machine to prevent spurious GUBP failures
        if (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Linux || ParseParam("NoLinux"))
        {
            WithLinux = false;
        }
        if (WithLinux)
        {
            HostPlatforms.Add(UnrealTargetPlatform.Linux);
        }

        bForceIncrementalCompile = ParseParam("ForceIncrementalCompile");
        bool bNoAutomatedTesting = ParseParam("NoAutomatedTesting") || BranchOptions.bNoAutomatedTesting;
        StoreName = ParseParamValue("Store");
        string StoreSuffix = ParseParamValue("StoreSuffix", "");

        if (bPreflightBuild)
        {
            int PreflightUID = ParseParamInt("PreflightUID", 0);
            PreflightMangleSuffix = String.Format("-PF-{0}-{1}", PreflightShelveCL, PreflightUID);
            StoreSuffix = StoreSuffix + PreflightMangleSuffix;
        }
        CL = ParseParamInt("CL", 0);
        bool bCleanLocalTempStorage = ParseParam("CleanLocal");
        bool bChanges = ParseParam("Changes") || ParseParam("AllChanges");
        bool bHistory = ParseParam("History") || bChanges;
        bool bListOnly = ParseParam("ListOnly") || bHistory;
        bool bSkipTriggers = ParseParam("SkipTriggers");
        bFake = ParseParam("fake");
        bool bFakeEC = ParseParam("FakeEC");
        TimeIndex = ParseParamInt("TimeIndex", 0);
        if (TimeIndex == 0)
        {
            TimeIndex = ParseParamInt("UserTimeIndex", 0);
        }

        bNoIOSOnPC = HostPlatforms.Contains(UnrealTargetPlatform.Mac);

        bool bSaveSharedTempStorage = false;

        if (bHistory && !P4Enabled)
        {
            throw new AutomationException("-Changes and -History require -P4.");
        }
        bool LocalOnly = true;
        string CLString = "";
        if (String.IsNullOrEmpty(StoreName))
        {
            if (P4Enabled)
            {
                if (CL == 0)
                {
                    CL = P4Env.Changelist;
                }
                CLString = String.Format("{0}", CL);
                StoreName = P4Env.BuildRootEscaped + "-" + CLString;
                bSaveSharedTempStorage = CommandUtils.IsBuildMachine;
                LocalOnly = false;
            }
            else
            {
                StoreName = "TempLocal";
                bSaveSharedTempStorage = false;
            }
        }
        StoreName = StoreName + StoreSuffix;
        if (bFakeEC)
        {
            LocalOnly = true;
        }
        if (bSaveSharedTempStorage)
        {
            if (!HaveSharedTempStorage(true))
            {
                throw new AutomationException("Request to save to temp storage, but {0} is unavailable.", UE4TempStorageDirectory());
            }
            bSignBuildProducts = true;
        }
        else if (!LocalOnly && !HaveSharedTempStorage(false))
        {
            Log("Looks like we want to use shared temp storage, but since we don't have it, we won't use it.");
            LocalOnly = true;
        }

        bool CommanderSetup = ParseParam("CommanderJobSetupOnly");
        string ExplicitTrigger = "";
        if (CommanderSetup)
        {
            ExplicitTrigger = ParseParamValue("TriggerNode");
            if (ExplicitTrigger == null)
            {
                ExplicitTrigger = "";
            }
        }

        if (ParseParam("CIS") && ExplicitTrigger == "" && CommanderSetup) // explicit triggers will already have a time index assigned
        {
            if (!P4Enabled)
            {
                throw new AutomationException("Can't have -CIS without P4 support");
            }
            var P4IndexFileP4 = CombinePaths(PathSeparator.Slash, CommandUtils.P4Env.BuildRootP4, "Engine", "Build", "CISCounter.txt");
            var P4IndexFileLocal = CombinePaths(CmdEnv.LocalRoot, "Engine", "Build", "CISCounter.txt");
            int Retry = 0;
            bool bDone = false;
            while (++Retry < 20 && !bDone)
            {
                int NowMinutes = (int)((DateTime.UtcNow - new DateTime(2014, 1, 1)).TotalMinutes);
                if (NowMinutes < 3 * 30 * 24)
                {
                    throw new AutomationException("bad date calc");
                }
                if (!FileExists_NoExceptions(P4IndexFileLocal))
                {
                    Log("{0} doesn't exist, checking in a new one", P4IndexFileP4);
                    WriteAllText(P4IndexFileLocal, "-1 0");
                    int WorkingCL = -1;
                    try
                    {
                        WorkingCL = P4.CreateChange(P4Env.Client, "Adding new CIS Counter");
                        P4.Add(WorkingCL, P4IndexFileP4);
                        int SubmittedCL;
                        P4.Submit(WorkingCL, out SubmittedCL);
                    }
                    catch (Exception)
                    {
                        Log("Add of CIS counter failed, assuming it now exists.");
                        if (WorkingCL > 0)
                        {
                            P4.DeleteChange(WorkingCL);
                        }
                    }
                }
                P4.Sync("-f " + P4IndexFileP4 + "#head");
                if (!FileExists_NoExceptions(P4IndexFileLocal))
                {
                    Log("{0} doesn't exist, checking in a new one", P4IndexFileP4);
                    WriteAllText(P4IndexFileLocal, "-1 0");
                    int WorkingCL = -1;
                    try
                    {
                        WorkingCL = P4.CreateChange(P4Env.Client, "Adding new CIS Counter");
                        P4.Add(WorkingCL, P4IndexFileP4);
                        int SubmittedCL;
                        P4.Submit(WorkingCL, out SubmittedCL);
                    }
                    catch (Exception)
                    {
                        Log("Add of CIS counter failed, assuming it now exists.");
                        if (WorkingCL > 0)
                        {
                            P4.DeleteChange(WorkingCL);
                        }
                    }
                }
                var Data = ReadAllText(P4IndexFileLocal);
                var Parts = Data.Split(" ".ToCharArray());
                int Index = int.Parse(Parts[0]);
                int Minutes = int.Parse(Parts[1]);

                int DeltaMinutes = NowMinutes - Minutes;

                int TimeQuantum = 20;
                if(BranchOptions.QuantumOverride != 0)
                {
                    TimeQuantum = BranchOptions.QuantumOverride;
                }
                int NewIndex = Index + 1;

                if (DeltaMinutes > TimeQuantum * 2)
                {
                    if (DeltaMinutes > TimeQuantum * (1 << 8))
                    {
                        // it has been forever, lets just start over
                        NewIndex = 0;
                    }
                    else
                    {
                        int WorkingIndex = NewIndex + 1;
                        for (int WorkingDelta = DeltaMinutes - TimeQuantum; WorkingDelta > 0; WorkingDelta -= TimeQuantum, WorkingIndex++)
                        {
                            if (CountZeros(NewIndex) < CountZeros(WorkingIndex))
                            {
                                NewIndex = WorkingIndex;
                            }
                        }
                    }
                }
                {
                    var Line = String.Format("{0} {1}", NewIndex, NowMinutes);
                    Log("Attempting to write {0} with {1}", P4IndexFileP4, Line);
                    int WorkingCL = -1;
                    try
                    {
                        WorkingCL = P4.CreateChange(P4Env.Client, "Updating CIS Counter");
                        P4.Edit(WorkingCL, P4IndexFileP4);
                        WriteAllText(P4IndexFileLocal, Line);
                        int SubmittedCL;
                        P4.Submit(WorkingCL, out SubmittedCL);
                        bDone = true;
                        TimeIndex = NewIndex;
                    }
                    catch (Exception)
                    {
                        Log("Edit of CIS counter failed, assuming someone else checked in, retrying.");
                        if (WorkingCL > 0)
                        {
                            P4.DeleteChange(WorkingCL);
                        }
                        System.Threading.Thread.Sleep(30000);
                    }
                }
            }
            if (!bDone)
            {
                throw new AutomationException("Failed to update the CIS counter after 20 tries.");
            }
            Log("Setting TimeIndex to {0}", TimeIndex);
        }

        Log("************************* CL:			                    {0}", CL);
        Log("************************* P4Enabled:			            {0}", P4Enabled);
        foreach (var HostPlatform in HostPlatforms)
        {
            Log("*************************   HostPlatform:		            {0}", HostPlatform.ToString());
        }
        Log("************************* StoreName:		                {0}", StoreName.ToString());
        Log("************************* bCleanLocalTempStorage:		    {0}", bCleanLocalTempStorage);
        Log("************************* bSkipTriggers:		            {0}", bSkipTriggers);
        Log("************************* bSaveSharedTempStorage:		    {0}", bSaveSharedTempStorage);
        Log("************************* bSignBuildProducts:		        {0}", bSignBuildProducts);
        Log("************************* bFake:           		        {0}", bFake);
        Log("************************* bFakeEC:           		        {0}", bFakeEC);
        Log("************************* bHistory:           		        {0}", bHistory);
        Log("************************* TimeIndex:           		    {0}", TimeIndex);

        GUBPNodes = new Dictionary<string, GUBPNode>();
        Branch = new BranchInfo(HostPlatforms);
        if (IsBuildMachine || ParseParam("AllPlatforms"))
        {
            ActivePlatforms = new List<UnrealTargetPlatform>();

            List<BranchInfo.BranchUProject> BranchCodeProjects = new List<BranchInfo.BranchUProject>();
            BranchCodeProjects.Add(Branch.BaseEngineProject);
            BranchCodeProjects.AddRange(Branch.CodeProjects);
            BranchCodeProjects.RemoveAll(Project => BranchOptions.ExcludeNodes.Contains(Project.GameName));

            foreach (var GameProj in BranchCodeProjects)
            {
                foreach (var Kind in BranchInfo.MonolithicKinds)
                {
                    if (GameProj.Properties.Targets.ContainsKey(Kind))
                    {
                        var Target = GameProj.Properties.Targets[Kind];
                        foreach (var HostPlatform in HostPlatforms)
                        {
                            var Platforms = Target.Rules.GUBP_GetPlatforms_MonolithicOnly(HostPlatform);
                            var AdditionalPlatforms = Target.Rules.GUBP_GetBuildOnlyPlatforms_MonolithicOnly(HostPlatform);
                            var AllPlatforms = Platforms.Union(AdditionalPlatforms);
                            foreach (var Plat in AllPlatforms)
                            {
                                if (Target.Rules.SupportsPlatform(Plat) && !ActivePlatforms.Contains(Plat))
                                {
                                    ActivePlatforms.Add(Plat);
                                }
                            }
                        }
                    }
                }
            }
        }
        else
        {
            ActivePlatforms     = new List<UnrealTargetPlatform>(CommandUtils.KnownTargetPlatforms);
        }
        var SupportedPlatforms = new List<UnrealTargetPlatform>();
        foreach(var Plat in ActivePlatforms)
        {
            if(!BranchOptions.PlatformsToRemove.Contains(Plat))
            {
                SupportedPlatforms.Add(Plat);
            }
        }
        ActivePlatforms = SupportedPlatforms;
        foreach (var Plat in ActivePlatforms)
        {
            Log("Active Platform: {0}", Plat.ToString());
        }

        if (HostPlatforms.Count >= 2)
        {
            // make sure each project is set up with the right assumptions on monolithics that prefer a platform.
            foreach (var CodeProj in Branch.CodeProjects)
            {
                var OptionsMac = CodeProj.Options(UnrealTargetPlatform.Mac);
                var OptionsPC = CodeProj.Options(UnrealTargetPlatform.Win64);

                var MacMonos = GetMonolithicPlatformsForUProject(UnrealTargetPlatform.Mac, CodeProj, false);
                var PCMonos = GetMonolithicPlatformsForUProject(UnrealTargetPlatform.Win64, CodeProj, false);

                if (!OptionsMac.bIsPromotable && OptionsPC.bIsPromotable &&
                    (MacMonos.Contains(UnrealTargetPlatform.IOS) || PCMonos.Contains(UnrealTargetPlatform.IOS)))
                {
                    throw new AutomationException("Project {0} is promotable for PC, not promotable for Mac and uses IOS monothics. Since Mac is the preferred platform for IOS, please add Mac as a promotable platform.", CodeProj.GameName);
                }
                if (OptionsMac.bIsPromotable && !OptionsPC.bIsPromotable &&
                    (MacMonos.Contains(UnrealTargetPlatform.Android) || PCMonos.Contains(UnrealTargetPlatform.Android)))
                {
                    throw new AutomationException("Project {0} is not promotable for PC, promotable for Mac and uses Android monothics. Since PC is the preferred platform for Android, please add PC as a promotable platform.", CodeProj.GameName);
                }
            }
        }

        AddNode(new VersionFilesNode());

        foreach (var HostPlatform in HostPlatforms)
        {
            AddNode(new ToolsForCompileNode(HostPlatform));

            if (!BranchOptions.ExcludePlatformsForEditor.Contains(HostPlatform))
            {
                AddNode(new RootEditorNode(HostPlatform));
                AddNode(new ToolsNode(HostPlatform));
                AddNode(new InternalToolsNode(HostPlatform));
            if (HostPlatform == UnrealTargetPlatform.Win64 && ActivePlatforms.Contains(UnrealTargetPlatform.Linux))
                {
                    if (!BranchOptions.ExcludePlatformsForEditor.Contains(UnrealTargetPlatform.Linux))
                    {
                        AddNode(new ToolsCrossCompileNode(HostPlatform));
                    }
                }
                foreach (var ProgramTarget in Branch.BaseEngineProject.Properties.Programs)
                {
                    bool bInternalOnly;
                    bool SeparateNode;
                    bool CrossCompile;

                    if (ProgramTarget.Rules.GUBP_AlwaysBuildWithTools(HostPlatform, out bInternalOnly, out SeparateNode, out CrossCompile) && ProgramTarget.Rules.SupportsPlatform(HostPlatform) && SeparateNode)
                    {
                        if (bInternalOnly)
                        {
                            AddNode(new SingleInternalToolsNode(HostPlatform, ProgramTarget));
                        }
                        else
                        {
                            AddNode(new SingleToolsNode(HostPlatform, ProgramTarget));
                        }
                    }
                    if (ProgramTarget.Rules.GUBP_IncludeNonUnityToolTest())
                    {
                        AddNode(new NonUnityToolNode(HostPlatform, ProgramTarget));
                    }
                }
                foreach(var CodeProj in Branch.CodeProjects)
                {
                    foreach(var ProgramTarget in CodeProj.Properties.Programs)
                    {
                        bool bInternalNodeOnly;
                        bool SeparateNode;
                        bool CrossCompile;

                        if(ProgramTarget.Rules.GUBP_AlwaysBuildWithTools(HostPlatform, out bInternalNodeOnly, out SeparateNode, out CrossCompile) && ProgramTarget.Rules.SupportsPlatform(HostPlatform) && SeparateNode)
                        {
                            if(bInternalNodeOnly)
                            {
                                AddNode(new SingleInternalToolsNode(HostPlatform, ProgramTarget));
                            }
                            else
                            {
                                AddNode(new SingleToolsNode(HostPlatform, ProgramTarget));
                            }
                        }
                        if(ProgramTarget.Rules.GUBP_IncludeNonUnityToolTest())
                        {
                            AddNode(new NonUnityToolNode(HostPlatform, ProgramTarget));
                        }
                    }
                }

                AddNode(new EditorAndToolsNode(this, HostPlatform));

                if (bOrthogonalizeEditorPlatforms)
                {
                    foreach (var Plat in ActivePlatforms)
                    {
                        if (Plat != HostPlatform && Plat != GetAltHostPlatform(HostPlatform))
                        {
                            if (Platform.Platforms[HostPlatform].CanHostPlatform(Plat))
                            {
                                AddNode(new EditorPlatformNode(HostPlatform, Plat));
                            }
                        }
                    }
                }
            }

            bool DoASharedPromotable = false;

            int NumSharedCode = 0;
            foreach (var CodeProj in Branch.CodeProjects)
            {
                var Options = CodeProj.Options(HostPlatform);

                if (Options.bIsPromotable && !Options.bSeparateGamePromotion)
                {
                    NumSharedCode++;
                }
            }

            var NonCodeProjectNames = new Dictionary<string, List<UnrealTargetPlatform>>();
            var NonCodeFormalBuilds = new Dictionary<string, List<TargetRules.GUBPFormalBuild>>();
            {
                var Target = Branch.BaseEngineProject.Properties.Targets[TargetRules.TargetType.Editor];

                foreach (var Codeless in Target.Rules.GUBP_NonCodeProjects_BaseEditorTypeOnly(HostPlatform))
                {
                    var Proj = Branch.FindGame(Codeless.Key);
                    if (Proj == null)
                    {
                        Log(System.Diagnostics.TraceEventType.Information, "{0} was listed as a codeless project by GUBP_NonCodeProjects_BaseEditorTypeOnly, however it does not exist in this branch.", Codeless.Key);
                    }
                    else if (Proj.Properties.bIsCodeBasedProject)
                    {
                        if (!Branch.NonCodeProjects.Contains(Proj))
                        {
                            Branch.NonCodeProjects.Add(Proj);
                            NonCodeProjectNames.Add(Codeless.Key, Codeless.Value);
                        }
                    }
                    else
                    {
                        NonCodeProjectNames.Add(Codeless.Key, Codeless.Value);
                    }
                }

                var TempNonCodeFormalBuilds = Target.Rules.GUBP_GetNonCodeFormalBuilds_BaseEditorTypeOnly();
                var HostMonos = GetMonolithicPlatformsForUProject(HostPlatform, Branch.BaseEngineProject, true);

                foreach (var Codeless in TempNonCodeFormalBuilds)
                {
                    if (NonCodeProjectNames.ContainsKey(Codeless.Key))
                    {
                        var PlatList = Codeless.Value;
                        var NewPlatList = new List<TargetRules.GUBPFormalBuild>();
                        foreach (var PlatPair in PlatList)
                        {
                            if (HostMonos.Contains(PlatPair.TargetPlatform))
                            {
                                NewPlatList.Add(PlatPair);
                            }
                        }
                        if (NewPlatList.Count > 0)
                        {
                            NonCodeFormalBuilds.Add(Codeless.Key, NewPlatList);
                        }
                    }
                    else
                    {
                        Log(System.Diagnostics.TraceEventType.Information, "{0} was listed as a codeless formal build GUBP_GetNonCodeFormalBuilds_BaseEditorTypeOnly, however it does not exist in this branch.", Codeless.Key);
                    }
                }
            }

            DoASharedPromotable = NumSharedCode > 0 || NonCodeProjectNames.Count > 0 || NonCodeFormalBuilds.Count > 0;

            AddNode(new NonUnityTestNode(HostPlatform));

            if (DoASharedPromotable)
            {
                var AgentSharingGroup = "Shared_EditorTests" + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform);

                var Options = Branch.BaseEngineProject.Options(HostPlatform);

                if (!Options.bIsPromotable || Options.bSeparateGamePromotion)
                {
                    throw new AutomationException("We assume that if we have shared promotable, the base engine is in it.");
                }

                if (HostPlatform == UnrealTargetPlatform.Win64) //temp hack till automated testing works on other platforms than Win64
                {

                    var EditorTests = Branch.BaseEngineProject.Properties.Targets[TargetRules.TargetType.Editor].Rules.GUBP_GetEditorTests_EditorTypeOnly(HostPlatform);
                    var EditorTestNodes = new List<string>();
                    foreach (var Test in EditorTests)
                    {
                        if (!bNoAutomatedTesting)
                        {
                            EditorTestNodes.Add(AddNode(new UATTestNode(this, HostPlatform, Branch.BaseEngineProject, Test.Key, Test.Value, AgentSharingGroup)));

                            foreach (var NonCodeProject in Branch.NonCodeProjects)
                            {
                                if (!NonCodeProjectNames.ContainsKey(NonCodeProject.GameName))
                                {
                                    continue;
                                }
                                if (HostPlatform == UnrealTargetPlatform.Mac) continue; //temp hack till mac automated testing works
                                EditorTestNodes.Add(AddNode(new UATTestNode(this, HostPlatform, NonCodeProject, Test.Key, Test.Value, AgentSharingGroup)));
                            }
                        }
                    }
                    if (EditorTestNodes.Count > 0)
                    {
                        AddNode(new GameAggregateNode(this, HostPlatform, Branch.BaseEngineProject, "AllEditorTests", EditorTestNodes, 0.0f));
                    }
                }

                var ServerPlatforms = new List<UnrealTargetPlatform>();
                var GamePlatforms = new List<UnrealTargetPlatform>();

                foreach (var Kind in BranchInfo.MonolithicKinds)
                {
                    if (Branch.BaseEngineProject.Properties.Targets.ContainsKey(Kind))
                    {
                        var Target = Branch.BaseEngineProject.Properties.Targets[Kind];
                        var Platforms = Target.Rules.GUBP_GetPlatforms_MonolithicOnly(HostPlatform);
                        if (Platforms.Contains(HostPlatform))
                        {
                            // we want the host platform first since some some pseudodependencies look to see if the shared promotable exists.
                            Platforms.Remove(HostPlatform);
                            Platforms.Insert(0, HostPlatform);
                        }
                        foreach (var Plat in Platforms)
                        {
                            if (!Platform.Platforms[HostPlatform].CanHostPlatform(Plat))
                            {
                                throw new AutomationException("Project {0} asked for platform {1} with host {2}, but the host platform cannot build that platform.", Branch.BaseEngineProject.GameName, Plat.ToString(), HostPlatform.ToString());
                            }
                            if (bNoIOSOnPC && Plat == UnrealTargetPlatform.IOS && HostPlatform == UnrealTargetPlatform.Win64)
                            {
                                continue;
                            }

                            if (ActivePlatforms.Contains(Plat))
                            {
                                if (Kind == TargetRules.TargetType.Server && !ServerPlatforms.Contains(Plat))
                                {
                                    ServerPlatforms.Add(Plat);
                                }
                                if (Kind == TargetRules.TargetType.Game && !GamePlatforms.Contains(Plat))
                                {
                                    GamePlatforms.Add(Plat);
                                }
                                if (!GUBPNodes.ContainsKey(GamePlatformMonolithicsNode.StaticGetFullName(HostPlatform, Branch.BaseEngineProject, Plat)))
                                {
                                    if(GamePlatformMonolithicsNode.HasPrecompiledTargets(Branch.BaseEngineProject, HostPlatform, Plat))
                                    {
                                        AddNode(new GamePlatformMonolithicsNode(this, HostPlatform, Branch.BaseEngineProject, Plat, InPrecompiled: true));
                                    }
                                    AddNode(new GamePlatformMonolithicsNode(this, HostPlatform, Branch.BaseEngineProject, Plat));
                                }
                                if (Plat == UnrealTargetPlatform.Win32 && Target.Rules.GUBP_BuildWindowsXPMonolithics() && Kind == TargetRules.TargetType.Game)
                                {
                                    if (!GUBPNodes.ContainsKey(GamePlatformMonolithicsNode.StaticGetFullName(HostPlatform, Branch.BaseEngineProject, Plat, true)))
                                    {
                                        AddNode(new GamePlatformMonolithicsNode(this, HostPlatform, Branch.BaseEngineProject, Plat, true));
                                    }
                                }
                            }
                        }
                    }
                }

                var CookedAgentSharingGroup = "Shared_CookedTests" + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform);

                var GameTestNodes = new List<string>();
                var GameCookNodes = new List<string>();
                //var FormalAgentSharingGroup = "Shared_FormalBuilds" + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform);

                //foreach (var Kind in BranchInfo.MonolithicKinds)//for now, non-code projects don't do client or server.
                {
                    var Kind = TargetRules.TargetType.Game;
                    if (Branch.BaseEngineProject.Properties.Targets.ContainsKey(Kind))
                    {
                        var Target = Branch.BaseEngineProject.Properties.Targets[Kind];
                        var Platforms = Target.Rules.GUBP_GetPlatforms_MonolithicOnly(HostPlatform);
                        foreach (var Plat in Platforms)
                        {
                            if (!Platform.Platforms[HostPlatform].CanHostPlatform(Plat))
                            {
                                throw new AutomationException("Project {0} asked for platform {1} with host {2}, but the host platform cannot build that platform.", Branch.BaseEngineProject.GameName, Plat.ToString(), HostPlatform.ToString());
                            }
                            if (bNoIOSOnPC && Plat == UnrealTargetPlatform.IOS && HostPlatform == UnrealTargetPlatform.Win64)
                            {
                                continue;
                            }

                            if (ActivePlatforms.Contains(Plat))
                            {
                                string CookedPlatform = Platform.Platforms[Plat].GetCookPlatform(Kind == TargetRules.TargetType.Server, Kind == TargetRules.TargetType.Client, "");
                                if (!GUBPNodes.ContainsKey(CookNode.StaticGetFullName(HostPlatform, Branch.BaseEngineProject, CookedPlatform)))
                                {
                                    GameCookNodes.Add(AddNode(new CookNode(this, HostPlatform, Branch.BaseEngineProject, Plat, CookedPlatform)));
                                }
                                if (!GUBPNodes.ContainsKey(GamePlatformCookedAndCompiledNode.StaticGetFullName(HostPlatform, Branch.BaseEngineProject, Plat)))
                                {
                                    AddNode(new GamePlatformCookedAndCompiledNode(this, HostPlatform, Branch.BaseEngineProject, Plat, false));
                                }
                                var GameTests = Target.Rules.GUBP_GetGameTests_MonolithicOnly(HostPlatform, GetAltHostPlatform(HostPlatform), Plat);
                                var RequiredPlatforms = new List<UnrealTargetPlatform> { Plat };
                                if (!bNoAutomatedTesting)
                                {
                                    var ThisMonoGameTestNodes = new List<string>();

                                    foreach (var Test in GameTests)
                                    {
                                        var TestName = Test.Key + "_" + Plat.ToString();
                                        ThisMonoGameTestNodes.Add(AddNode(new UATTestNode(this, HostPlatform, Branch.BaseEngineProject, TestName, Test.Value, CookedAgentSharingGroup, false, RequiredPlatforms)));
                                    }
                                    if (ThisMonoGameTestNodes.Count > 0)
                                    {
                                        GameTestNodes.Add(AddNode(new GameAggregateNode(this, HostPlatform, Branch.BaseEngineProject, "CookedTests_" + Plat.ToString() + "_" + Kind.ToString() + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform), ThisMonoGameTestNodes, 0.0f)));
                                    }
                                }

                                foreach (var NonCodeProject in Branch.NonCodeProjects)
                                {
                                    if (!NonCodeProjectNames.ContainsKey(NonCodeProject.GameName) || !NonCodeProjectNames[NonCodeProject.GameName].Contains(Plat))
                                    {
                                        continue;
                                    }
                                    if (!GUBPNodes.ContainsKey(CookNode.StaticGetFullName(HostPlatform, NonCodeProject, CookedPlatform)))
                                    {
                                        GameCookNodes.Add(AddNode(new CookNode(this, HostPlatform, NonCodeProject, Plat, CookedPlatform)));
                                    }
                                    if (!GUBPNodes.ContainsKey(GamePlatformCookedAndCompiledNode.StaticGetFullName(HostPlatform, NonCodeProject, Plat)))
                                    {
                                        AddNode(new GamePlatformCookedAndCompiledNode(this, HostPlatform, NonCodeProject, Plat, false));
                                        if (NonCodeFormalBuilds.ContainsKey(NonCodeProject.GameName))
                                        {
                                            var PlatList = NonCodeFormalBuilds[NonCodeProject.GameName];
                                            foreach (var PlatPair in PlatList)
                                            {
                                                if (PlatPair.TargetPlatform == Plat)
                                                {
                                                    var NodeName = AddNode(new FormalBuildNode(this, NonCodeProject, HostPlatform, new List<UnrealTargetPlatform>() { Plat }, new List<UnrealTargetConfiguration>() {PlatPair.TargetConfig}));
                                                    if(PlatPair.bBeforeTrigger)
                                                    {
                                                        RemovePseudodependencyFromNode(FormalBuildNode.StaticGetFullName(NonCodeProject, HostPlatform, new List<UnrealTargetPlatform>() { Plat }, new List<UnrealTargetConfiguration>() { PlatPair.TargetConfig }), WaitForFormalUserInput.StaticGetFullName());
                                                    }
                                                    // we don't want this delayed
                                                    // this would normally wait for the testing phase, we just want to build it right away
                                                    RemovePseudodependencyFromNode(
                                                        CookNode.StaticGetFullName(HostPlatform, NonCodeProject, CookedPlatform),
                                                        WaitForTestShared.StaticGetFullName());
                                                    string BuildAgentSharingGroup = "";
                                                    if (Options.bSeparateGamePromotion)
                                                    {
                                                        BuildAgentSharingGroup = NonCodeProject.GameName + "_MakeFormalBuild_" + Plat.ToString() + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform);
                                                        if (Plat == UnrealTargetPlatform.IOS || Plat == UnrealTargetPlatform.Android) // These trash build products, so we need to use different agents
                                                        {
                                                            BuildAgentSharingGroup = "";
                                                        }
                                                        GUBPNodes[CookNode.StaticGetFullName(HostPlatform, NonCodeProject, CookedPlatform)].AgentSharingGroup = BuildAgentSharingGroup;
                                                        GUBPNodes[GamePlatformCookedAndCompiledNode.StaticGetFullName(HostPlatform, NonCodeProject, Plat)].AgentSharingGroup = BuildAgentSharingGroup;
                                                        GUBPNodes[NodeName].AgentSharingGroup = BuildAgentSharingGroup;
                                                    }
                                                    else
                                                    {
                                                        //GUBPNodes[NodeName].AgentSharingGroup = FormalAgentSharingGroup;
                                                        if(Plat == UnrealTargetPlatform.XboxOne)
                                                        {
                                                            GUBPNodes[NodeName].AgentSharingGroup = "";
                                                        }
                                                    }
                                                    if (PlatPair.bTest)
                                                    {
                                                        AddNode(new FormalBuildTestNode(this, NonCodeProject, HostPlatform, Plat, PlatPair.TargetConfig));
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    if (!bNoAutomatedTesting)
                                    {
                                        if (HostPlatform == UnrealTargetPlatform.Mac || HostPlatform == UnrealTargetPlatform.Linux) continue; //temp hack till Linux and Mac automated testing works
                                        var ThisMonoGameTestNodes = new List<string>();
                                        foreach (var Test in GameTests)
                                        {
                                            var TestName = Test.Key + "_" + Plat.ToString();
                                            ThisMonoGameTestNodes.Add(AddNode(new UATTestNode(this, HostPlatform, NonCodeProject, TestName, Test.Value, CookedAgentSharingGroup, false, RequiredPlatforms)));
                                        }
                                        if (ThisMonoGameTestNodes.Count > 0)
                                        {
                                            GameTestNodes.Add(AddNode(new GameAggregateNode(this, HostPlatform, NonCodeProject, "CookedTests_" + Plat.ToString() + "_" + Kind.ToString() + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform), ThisMonoGameTestNodes, 0.0f)));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
        #if false
                //for now, non-code projects don't do client or server.
                foreach (var ServerPlatform in ServerPlatforms)
                {
                    var ServerTarget = Branch.BaseEngineProject.Properties.Targets[TargetRules.TargetType.Server];
                    foreach (var GamePlatform in GamePlatforms)
                    {
                        var Target = Branch.BaseEngineProject.Properties.Targets[TargetRules.TargetType.Game];

                        foreach (var NonCodeProject in Branch.NonCodeProjects)
                        {
                            if (!NonCodeProjectNames.ContainsKey(NonCodeProject.GameName) || !NonCodeProjectNames.ContainsKey(NonCodeProject.GameName) ||
                                    !NonCodeProjectNames[NonCodeProject.GameName].Contains(ServerPlatform)  || !NonCodeProjectNames[NonCodeProject.GameName].Contains(GamePlatform) )
                            {
                                continue;
                            }

                            var ClientServerTests = Target.Rules.GUBP_GetClientServerTests_MonolithicOnly(HostPlatform, GetAltHostPlatform(HostPlatform), ServerPlatform, GamePlatform);
                            var RequiredPlatforms = new List<UnrealTargetPlatform> { ServerPlatform };
                            if (ServerPlatform != GamePlatform)
                            {
                                RequiredPlatforms.Add(GamePlatform);
                            }
                            foreach (var Test in ClientServerTests)
                            {
                                GameTestNodes.Add(AddNode(new UATTestNode(this, HostPlatform, NonCodeProject, Test.Key + "_" + GamePlatform.ToString() + "_" + ServerPlatform.ToString(), Test.Value, false, RequiredPlatforms, true)));
                            }
                        }
                    }
                }
        #endif
                if (GameTestNodes.Count > 0)
                {
                    AddNode(new GameAggregateNode(this, HostPlatform, Branch.BaseEngineProject, "AllCookedTests", GameTestNodes));
                }
            }
            if (!GUBPNodes.ContainsKey(SharedCookAggregateNode.StaticGetFullName()))
            {
                AddNode(new SharedCookAggregateNode(this, HostPlatforms, NonCodeProjectNames, NonCodeFormalBuilds));
            }

            if(HostPlatform == MakeFeaturePacksNode.GetDefaultBuildPlatform(this))
            {
                AddNode(new MakeFeaturePacksNode(HostPlatform, Branch.AllProjects.Where(x => MakeFeaturePacksNode.IsFeaturePack(x))));
            }

            foreach (var CodeProj in Branch.CodeProjects)
            {
                var Options = CodeProj.Options(HostPlatform);

                if (!Options.bIsPromotable && !Options.bTestWithShared && !Options.bIsNonCode)
                {
                    continue; // we skip things that aren't promotable and aren't tested - except noncode as code situations
                }
                var AgentShareName = CodeProj.GameName;
                if (!Options.bSeparateGamePromotion)
                {
                    AgentShareName = "Shared";
                }

                if (!BranchOptions.ExcludePlatformsForEditor.Contains(HostPlatform) && !Options.bIsNonCode)
                {
                    EditorGameNode Node = (EditorGameNode)TryFindNode(EditorGameNode.StaticGetFullName(HostPlatform, CodeProj));
                    if(Node == null)
                    {
                        AddNode(new EditorGameNode(this, HostPlatform, CodeProj));
                    }
                    else
                    {
                        Node.AddProject(CodeProj);
                    }
                }

                if (!bNoAutomatedTesting && HostPlatform == UnrealTargetPlatform.Win64) //temp hack till automated testing works on other platforms than Win64
                {
                    if (CodeProj.Properties.Targets.ContainsKey(TargetRules.TargetType.Editor))
                    {
                        var EditorTests = CodeProj.Properties.Targets[TargetRules.TargetType.Editor].Rules.GUBP_GetEditorTests_EditorTypeOnly(HostPlatform);
                        var EditorTestNodes = new List<string>();
                        string AgentSharingGroup = "";
                        if (EditorTests.Count > 1)
                        {
                            AgentSharingGroup = AgentShareName + "_EditorTests" + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform);
                        }
                        foreach (var Test in EditorTests)
                        {
                            EditorTestNodes.Add(AddNode(new UATTestNode(this, HostPlatform, CodeProj, Test.Key, Test.Value, AgentSharingGroup)));
                            if (!Options.bTestWithShared || !HasNode(WaitForTestShared.StaticGetFullName()))
                            {
                                RemovePseudodependencyFromNode((UATTestNode.StaticGetFullName(HostPlatform, CodeProj, Test.Key)), WaitForTestShared.StaticGetFullName());
                            }
                        }
                        if (EditorTestNodes.Count > 0)
                        {
                            AddNode(new GameAggregateNode(this, HostPlatform, CodeProj, "AllEditorTests", EditorTestNodes, 0.0f));
                        }
                    }
                }

                var CookedAgentSharingGroup = AgentShareName + "_CookedTests" + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform);
                //var FormalAgentSharingGroup = "Shared_FormalBuilds" + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform);
                var ServerPlatforms = new List<UnrealTargetPlatform>();
                var GamePlatforms = new List<UnrealTargetPlatform>();
                var GameTestNodes = new List<string>();
                foreach (var Kind in BranchInfo.MonolithicKinds)
                {
                    if (CodeProj.Properties.Targets.ContainsKey(Kind))
                    {
                        var Target = CodeProj.Properties.Targets[Kind];
                        var Platforms = Target.Rules.GUBP_GetPlatforms_MonolithicOnly(HostPlatform);
                        var AdditionalPlatforms = Target.Rules.GUBP_GetBuildOnlyPlatforms_MonolithicOnly(HostPlatform);
                        var AllPlatforms = Platforms.Union(AdditionalPlatforms);
                        foreach (var Plat in AllPlatforms)
                        {
                            if (!Platform.Platforms[HostPlatform].CanHostPlatform(Plat))
                            {
                                throw new AutomationException("Project {0} asked for platform {1} with host {2}, but the host platform cannot build that platform.", CodeProj.GameName, Plat.ToString(), HostPlatform.ToString());
                            }
                            if (bNoIOSOnPC && Plat == UnrealTargetPlatform.IOS && HostPlatform == UnrealTargetPlatform.Win64)
                            {
                                continue;
                            }
                            if(Plat == UnrealTargetPlatform.Win32 && Target.Rules.GUBP_BuildWindowsXPMonolithics())
                            {
                                if(!GUBPNodes.ContainsKey(GamePlatformMonolithicsNode.StaticGetFullName(HostPlatform, CodeProj, Plat, true)))
                                {
                                    AddNode(new GamePlatformMonolithicsNode(this, HostPlatform, CodeProj, Plat, true));
                                }
                            }
                            if (ActivePlatforms.Contains(Plat))
                            {
                                if (Kind == TargetRules.TargetType.Server && !ServerPlatforms.Contains(Plat))
                                {
                                    ServerPlatforms.Add(Plat);
                                }
                                if (Kind == TargetRules.TargetType.Game && !GamePlatforms.Contains(Plat))
                                {
                                    GamePlatforms.Add(Plat);
                                }
                                if (!GUBPNodes.ContainsKey(GamePlatformMonolithicsNode.StaticGetFullName(HostPlatform, CodeProj, Plat)))
                                {
                                    if(GamePlatformMonolithicsNode.HasPrecompiledTargets(CodeProj, HostPlatform, Plat))
                                    {
                                        AddNode(new GamePlatformMonolithicsNode(this, HostPlatform, CodeProj, Plat, InPrecompiled: true));
                                    }
                                    AddNode(new GamePlatformMonolithicsNode(this, HostPlatform, CodeProj, Plat));
                                }
                                if (!AdditionalPlatforms.Contains(Plat))
                                {
                                    string CookedPlatform = Platform.Platforms[Plat].GetCookPlatform(Kind == TargetRules.TargetType.Server, Kind == TargetRules.TargetType.Client, "");
                                    if (Target.Rules.GUBP_AlternateCookPlatform(HostPlatform, CookedPlatform) != "")
                                    {
                                        CookedPlatform = Target.Rules.GUBP_AlternateCookPlatform(HostPlatform, CookedPlatform);
                                    }
                                    if (!GUBPNodes.ContainsKey(CookNode.StaticGetFullName(HostPlatform, CodeProj, CookedPlatform)))
                                    {
                                        AddNode(new CookNode(this, HostPlatform, CodeProj, Plat, CookedPlatform));
                                    }
                                    if (!GUBPNodes.ContainsKey(GamePlatformCookedAndCompiledNode.StaticGetFullName(HostPlatform, CodeProj, Plat)))
                                    {
                                        AddNode(new GamePlatformCookedAndCompiledNode(this, HostPlatform, CodeProj, Plat, true));
                                    }
                                    var FormalBuildConfigs = Target.Rules.GUBP_GetConfigsForFormalBuilds_MonolithicOnly(HostPlatform);

                                    foreach (var Config in FormalBuildConfigs)
                                    {
                                        string FormalNodeName = null;
                                        if (Kind == TargetRules.TargetType.Client)
                                        {
                                            if (Plat == Config.TargetPlatform)
                                            {
                                                FormalNodeName = AddNode(new FormalBuildNode(this, CodeProj, HostPlatform, InClientTargetPlatforms: new List<UnrealTargetPlatform>() { Config.TargetPlatform }, InClientConfigs: new List<UnrealTargetConfiguration>() { Config.TargetConfig }, InClientNotGame: true));
                                            }
                                        }
                                        else if (Kind == TargetRules.TargetType.Server)
                                        {
                                            if (Plat == Config.TargetPlatform)
                                            {
                                                FormalNodeName = AddNode(new FormalBuildNode(this, CodeProj, HostPlatform, InServerTargetPlatforms: new List<UnrealTargetPlatform>() { Config.TargetPlatform }, InServerConfigs: new List<UnrealTargetConfiguration>() { Config.TargetConfig }));
                                            }
                                        }
                                        else if (Kind == TargetRules.TargetType.Game)
                                        {
                                            if (Plat == Config.TargetPlatform)
                                            {
                                                FormalNodeName = AddNode(new FormalBuildNode(this, CodeProj, HostPlatform, InClientTargetPlatforms: new List<UnrealTargetPlatform>() { Config.TargetPlatform }, InClientConfigs: new List<UnrealTargetConfiguration>() { Config.TargetConfig }));
                                            }
                                        }
                                        if (FormalNodeName != null)
                                        {
                                            if (Config.bBeforeTrigger)
                                            {
                                                RemovePseudodependencyFromNode(FormalBuildNode.StaticGetFullName(CodeProj, HostPlatform, new List<UnrealTargetPlatform>() { Config.TargetPlatform }, new List<UnrealTargetConfiguration>() { Config.TargetConfig }), WaitForFormalUserInput.StaticGetFullName());
                                            }
                                            // we don't want this delayed
                                            // this would normally wait for the testing phase, we just want to build it right away
                                            RemovePseudodependencyFromNode(
                                                CookNode.StaticGetFullName(HostPlatform, CodeProj, CookedPlatform),
                                                WaitForTestShared.StaticGetFullName());
                                            string BuildAgentSharingGroup = "";

                                            if (Options.bSeparateGamePromotion)
                                            {
                                                BuildAgentSharingGroup = CodeProj.GameName + "_MakeFormalBuild_" + Plat.ToString() + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform);
                                                if (Plat == UnrealTargetPlatform.IOS || Plat == UnrealTargetPlatform.Android || Plat == UnrealTargetPlatform.XboxOne) // These trash build products, so we need to use different agents
                                                {
                                                    BuildAgentSharingGroup = "";
                                                }
                                                GUBPNodes[CookNode.StaticGetFullName(HostPlatform, CodeProj, CookedPlatform)].AgentSharingGroup = BuildAgentSharingGroup;
                                                GUBPNodes[GamePlatformCookedAndCompiledNode.StaticGetFullName(HostPlatform, CodeProj, Plat)].AgentSharingGroup = BuildAgentSharingGroup;
                                                GUBPNodes[FormalNodeName].AgentSharingGroup = BuildAgentSharingGroup;
                                            }
                                            else
                                            {
                                                //GUBPNodes[FormalNodeName].AgentSharingGroup = FormalAgentSharingGroup;
                                                if (Plat == UnrealTargetPlatform.XboxOne)
                                                {
                                                    GUBPNodes[FormalNodeName].AgentSharingGroup = "";
                                                }
                                            }
                                            if (Config.bTest)
                                            {
                                                AddNode(new FormalBuildTestNode(this, CodeProj, HostPlatform, Plat, Config.TargetConfig));
                                            }
                                        }
                                    }
                                    if (!bNoAutomatedTesting)
                                    {
                                        if (HostPlatform == UnrealTargetPlatform.Mac || HostPlatform == UnrealTargetPlatform.Linux) continue; //temp hack till Linux and Mac automated testing works
                                        var GameTests = Target.Rules.GUBP_GetGameTests_MonolithicOnly(HostPlatform, GetAltHostPlatform(HostPlatform), Plat);
                                        var RequiredPlatforms = new List<UnrealTargetPlatform> { Plat };
                                        var ThisMonoGameTestNodes = new List<string>();

                                        foreach (var Test in GameTests)
                                        {
                                            var TestNodeName = Test.Key + "_" + Plat.ToString();
                                            ThisMonoGameTestNodes.Add(AddNode(new UATTestNode(this, HostPlatform, CodeProj, TestNodeName, Test.Value, CookedAgentSharingGroup, false, RequiredPlatforms)));
                                            if (!Options.bTestWithShared || !HasNode(WaitForTestShared.StaticGetFullName()))
                                            {
                                                RemovePseudodependencyFromNode((UATTestNode.StaticGetFullName(HostPlatform, CodeProj, TestNodeName)), WaitForTestShared.StaticGetFullName());
                                            }
                                        }
                                        if (ThisMonoGameTestNodes.Count > 0)
                                        {
                                            GameTestNodes.Add(AddNode(new GameAggregateNode(this, HostPlatform, CodeProj, "CookedTests_" + Plat.ToString() + "_" + Kind.ToString() + HostPlatformNode.StaticGetHostPlatformSuffix(HostPlatform), ThisMonoGameTestNodes, 0.0f)));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                if (!bNoAutomatedTesting)
                {
                    foreach (var ServerPlatform in ServerPlatforms)
                    {
                        foreach (var GamePlatform in GamePlatforms)
                        {
                            if (HostPlatform == UnrealTargetPlatform.Mac || HostPlatform == UnrealTargetPlatform.Linux) continue; //temp hack till Linux and Mac automated testing works
                            var Target = CodeProj.Properties.Targets[TargetRules.TargetType.Game];
                            var ClientServerTests = Target.Rules.GUBP_GetClientServerTests_MonolithicOnly(HostPlatform, GetAltHostPlatform(HostPlatform), ServerPlatform, GamePlatform);
                            var RequiredPlatforms = new List<UnrealTargetPlatform> { ServerPlatform };
                            if (ServerPlatform != GamePlatform)
                            {
                                RequiredPlatforms.Add(GamePlatform);
                            }
                            foreach (var Test in ClientServerTests)
                            {
                                var TestNodeName = Test.Key + "_" + GamePlatform.ToString() + "_" + ServerPlatform.ToString();
                                GameTestNodes.Add(AddNode(new UATTestNode(this, HostPlatform, CodeProj, TestNodeName, Test.Value, CookedAgentSharingGroup, false, RequiredPlatforms)));
                            }
                        }
                    }
                    if (GameTestNodes.Count > 0)
                    {
                        AddNode(new GameAggregateNode(this, HostPlatform, CodeProj, "AllCookedTests", GameTestNodes));
                    }
                }
            }
        }

        int NumSharedAllHosts = 0;
        foreach (var CodeProj in Branch.CodeProjects)
        {
            if (CodeProj.Properties.Targets.ContainsKey(TargetRules.TargetType.Editor))
            {
                bool AnySeparate = false;
                var PromotedHosts = new List<UnrealTargetPlatform>();

                foreach (var HostPlatform in HostPlatforms)
                {
                    if (!BranchOptions.ExcludePlatformsForEditor.Contains(HostPlatform) && !BranchOptions.RemovePlatformFromPromotable.Contains(HostPlatform))
                    {
                        var Options = CodeProj.Options(HostPlatform);
                        AnySeparate = AnySeparate || Options.bSeparateGamePromotion;
                        if (Options.bIsPromotable)
                        {
                            if (!Options.bSeparateGamePromotion)
                            {
                                NumSharedAllHosts++;
                            }
                            PromotedHosts.Add(HostPlatform);
                        }
                    }
                }
                if (PromotedHosts.Count > 0)
                {
                    AddNode(new GameAggregatePromotableNode(this, PromotedHosts, CodeProj, true));
                    if (AnySeparate)
                    {
                        AddNode(new WaitForGamePromotionUserInput(this, CodeProj, false));
                        AddNode(new GameLabelPromotableNode(this, CodeProj, false));
                        AddNode(new WaitForGamePromotionUserInput(this, CodeProj, true));
                        AddNode(new GameLabelPromotableNode(this, CodeProj, true));
                    }
                }
            }
        }
        if (NumSharedAllHosts > 0)
        {
            AddNode(new GameAggregatePromotableNode(this, HostPlatforms, Branch.BaseEngineProject, false));

            AddNode(new SharedAggregatePromotableNode(this, HostPlatforms));
            AddNode(new WaitForSharedPromotionUserInput(this, false));
            AddNode(new SharedLabelPromotableNode(this, false));
            AddNode(new SharedLabelPromotableSuccessNode());
            AddNode(new WaitForTestShared(this));
            AddNode(new WaitForSharedPromotionUserInput(this, true));
            AddNode(new SharedLabelPromotableNode(this, true));
            AddNode(new WaitForFormalUserInput(this));
        }
        foreach (var HostPlatform in HostPlatforms)
        {
            AddCustomNodes(HostPlatform);
        }

        if (HasNode(ToolsForCompileNode.StaticGetFullName(UnrealTargetPlatform.Win64)))
        {
            if (HasNode(GamePlatformMonolithicsNode.StaticGetFullName(UnrealTargetPlatform.Mac, Branch.BaseEngineProject, UnrealTargetPlatform.IOS)) && HasNode(ToolsNode.StaticGetFullName(UnrealTargetPlatform.Win64)))
            {
                //AddNode(new IOSOnPCTestNode(this)); - Disable IOSOnPCTest until a1011 crash is fixed
            }
            AddNode(new VSExpressTestNode(this));
            if (ActivePlatforms.Contains(UnrealTargetPlatform.Linux) && !BranchOptions.ExcludePlatformsForEditor.Contains(UnrealTargetPlatform.Linux))
            {
                AddNode(new RootEditorCrossCompileLinuxNode(UnrealTargetPlatform.Win64));
            }
            if (!bPreflightBuild)
            {
                AddNode(new CleanSharedTempStorageNode(this));
            }
        }
        #if false
        // this doesn't work for lots of reasons...we can't figure out what the dependencies are until far later
        if (bPreflightBuild)
        {
            GeneralSuccessNode PreflightSuccessNode = new GeneralSuccessNode("Preflight");
            foreach (var NodeToDo in GUBPNodes)
            {
                if (NodeToDo.Value.RunInEC())
                {
                    PreflightSuccessNode.AddPseudodependency(NodeToDo.Key);
                }
            }
            AddNode(PreflightSuccessNode);
        }
        #endif
        foreach (var NodeToDo in GUBPNodes)
        {
            foreach (var Dep in GUBPNodes[NodeToDo.Key].FullNamesOfDependencies)
            {
                if (!GUBPNodes.ContainsKey(Dep))
                {
                    throw new AutomationException("Node {0} is not in the full graph. It is a dependency of {1}.", Dep, NodeToDo.Key);
                }
                if (Dep == NodeToDo.Key)
                {
                    throw new AutomationException("Node {0} has a self arc.", NodeToDo.Key);
                }
            }
            foreach (var Dep in GUBPNodes[NodeToDo.Key].FullNamesOfPseudosependencies)
            {
                if (!GUBPNodes.ContainsKey(Dep))
                {
                    throw new AutomationException("Node {0} is not in the full graph. It is a pseudodependency of {1}.", Dep, NodeToDo.Key);
                }
                if (Dep == NodeToDo.Key)
                {
                    throw new AutomationException("Node {0} has a self pseudoarc.", NodeToDo.Key);
                }
            }
        }

        if (bCleanLocalTempStorage)  // shared temp storage can never be wiped
        {
            DeleteLocalTempStorageManifests(CmdEnv);
        }

        GUBPNodesControllingTrigger = new Dictionary<string, string>();
        GUBPNodesControllingTriggerDotName = new Dictionary<string, string>();

        var FullNodeList = new Dictionary<string, string>();
        var FullNodeDirectDependencies = new Dictionary<string, string>();
        var FullNodeDependedOnBy = new Dictionary<string, string>();
        var FullNodeDependentPromotions = new Dictionary<string, string>();
        var SeparatePromotables = new List<string>();
        {
            foreach (var NodeToDo in GUBPNodes)
            {
                if (GUBPNodes[NodeToDo.Key].IsSeparatePromotable())
                {
                    SeparatePromotables.Add(GUBPNodes[NodeToDo.Key].GetFullName());
                    List<string> Dependencies = new List<string>();
                    Dependencies = GetECDependencies(NodeToDo.Key);
                    foreach (var Dep in Dependencies)
                    {
                        if (!GUBPNodes.ContainsKey(Dep))
                        {
                            throw new AutomationException("Node {0} is not in the graph.  It is a dependency of {1}.", Dep, NodeToDo);
                        }
                        if (!GUBPNodes[Dep].IsPromotableAggregate())
                        {
                            if (!GUBPNodes[Dep].DependentPromotions.Contains(NodeToDo.Key))
                            {
                                GUBPNodes[Dep].DependentPromotions.Add(NodeToDo.Key);
                            }
                        }
                    }
                }
            }

            // Make sure that everything that's listed as a frequency barrier is completed with the given interval
            Dictionary<string, int> FrequencyOverrides = new Dictionary<string,int>();
            foreach (KeyValuePair<string, sbyte> Barrier in BranchOptions.FrequencyBarriers)
            {
                // All the nodes which are dependencies of the barrier node
                HashSet<string> IncludedNodes = new HashSet<string> { Barrier.Key };

                // Find all the nodes which are indirect dependencies of this node
                List<string> SearchNodes = new List<string> { Barrier.Key };
                for (int Idx = 0; Idx < SearchNodes.Count; Idx++)
                {
                    GUBPNode Node = GUBPNodes[SearchNodes[Idx]];
                    foreach (string DependencyName in Node.FullNamesOfDependencies.Union(Node.FullNamesOfPseudosependencies))
                    {
                        if (!IncludedNodes.Contains(DependencyName))
                        {
                            IncludedNodes.Add(DependencyName);
                            SearchNodes.Add(DependencyName);
                        }
                    }
                }

                // Make sure that everything included in this list is before the cap, and everything not in the list is after it
                foreach (KeyValuePair<string, GUBPNode> NodePair in GUBPNodes)
                {
                    if (IncludedNodes.Contains(NodePair.Key))
                    {
                        int Frequency;
                        if(FrequencyOverrides.TryGetValue(NodePair.Key, out Frequency))
                        {
                            Frequency = Math.Min(Frequency, Barrier.Value);
                        }
                        else
                        {
                            Frequency = Barrier.Value;
                        }
                        FrequencyOverrides[NodePair.Key] = Frequency;
                    }
                }
            }

            // Compute all the frequencies
            foreach (var NodeToDo in GUBPNodes)
            {
                ComputeDependentCISFrequencyQuantumShift(NodeToDo.Key, FrequencyOverrides);
            }

            foreach (var NodeToDo in GUBPNodes)
            {
                var Deps = GUBPNodes[NodeToDo.Key].DependentPromotions;
                string All = "";
                foreach (var Dep in Deps)
                {
                    if (All != "")
                    {
                        All += " ";
                    }
                    All += Dep;
                }
                FullNodeDependentPromotions.Add(NodeToDo.Key, All);
            }
        }
        {
            Log("******* {0} GUBP Nodes", GUBPNodes.Count);
            var SortedNodes = TopologicalSort(new HashSet<string>(GUBPNodes.Keys), LocalOnly: true, DoNotConsiderCompletion: true);
            foreach (var Node in SortedNodes)
            {
                string Note = GetControllingTriggerDotName(Node);
                if (Note == "")
                {
                    Note = CISFrequencyQuantumShiftString(Node);
                }
                if (Note == "")
                {
                    Note = "always";
                }
                if (GUBPNodes[Node].RunInEC())
                {
                    var Deps = GetECDependencies(Node);
                    string All = "";
                    foreach (var Dep in Deps)
                    {
                        if (All != "")
                        {
                            All += " ";
                        }
                        All += Dep;
                    }
                    Log("  {0}: {1}      {2}", Node, Note, All);
                    FullNodeList.Add(Node, Note);
                    FullNodeDirectDependencies.Add(Node, All);
                }
                else
                {
                    Log("  {0}: {1} [Aggregate]", Node, Note);
                }
            }
        }
        Dictionary<string, int> FullNodeListSortKey = GetDisplayOrder(FullNodeList.Keys.ToList(), FullNodeDirectDependencies, GUBPNodes);

        bool bOnlyNode = false;
        bool bRelatedToNode = false;
        bool bGraphSubset = false;
        var NodesToDo = new HashSet<string>();

        {
            string NodeSpec = ParseParamValue("Node");
            if (String.IsNullOrEmpty(NodeSpec))
            {
                NodeSpec = ParseParamValue("RelatedToNode");
                if (!String.IsNullOrEmpty(NodeSpec))
                {
                    bRelatedToNode = true;
                }
            }
            if (String.IsNullOrEmpty(NodeSpec) && CommanderSetup)
            {
                NodeSpec = ParseParamValue("SetupNode");
                if (String.IsNullOrEmpty(NodeSpec))
                {
                    NodeSpec = ParseParamValue("SetupRelatedToNode");
                    if (!String.IsNullOrEmpty(NodeSpec))
                    {
                        bRelatedToNode = true;
                    }
                }
            }
            if (String.IsNullOrEmpty(NodeSpec))
            {
                NodeSpec = ParseParamValue("OnlyNode");
                if (!String.IsNullOrEmpty(NodeSpec))
                {
                    bOnlyNode = true;
                }
            }
            if (!String.IsNullOrEmpty(NodeSpec))
            {
                bGraphSubset = true;
                if (NodeSpec.Equals("Noop", StringComparison.InvariantCultureIgnoreCase))
                {
                    Log("Request for Noop node, done.");
                    PrintRunTime();
                    return;
                }
                List<string> Nodes = new List<string>(NodeSpec.Split('+'));
                foreach (var NodeArg in Nodes)
                {
                    var NodeName = NodeArg.Trim();
                    bool bFoundAnything = false;
                    if (!String.IsNullOrEmpty(NodeName))
                    {
                        foreach (var Node in GUBPNodes)
                        {
                            if (Node.Value.GetFullName().Equals(NodeArg, StringComparison.InvariantCultureIgnoreCase) ||
                                Node.Value.AgentSharingGroup.Equals(NodeArg, StringComparison.InvariantCultureIgnoreCase)
                                )
                            {
                                if (!NodesToDo.Contains(Node.Key))
                                {
                                    NodesToDo.Add(Node.Key);
                                }
                                bFoundAnything = true;
                            }
                        }
                        if (!bFoundAnything)
                        {
                            throw new AutomationException("Could not find node named {0}", NodeName);
                        }
                    }
                }
            }
        }
        string GameSpec = ParseParamValue("Game");
        if (!String.IsNullOrEmpty(GameSpec))
        {
            bGraphSubset = true;
            List<string> Games = new List<string>(GameSpec.Split('+'));
            foreach (var GameArg in Games)
            {
                var GameName = GameArg.Trim();
                if (!String.IsNullOrEmpty(GameName))
                {
                    foreach (var GameProj in Branch.CodeProjects)
                    {
                        if (GameProj.GameName.Equals(GameName, StringComparison.InvariantCultureIgnoreCase))
                        {

                            NodesToDo.Add(GameAggregatePromotableNode.StaticGetFullName(GameProj));
                            foreach (var Node in GUBPNodes)
                            {
                                if (Node.Value.GameNameIfAnyForTempStorage() == GameProj.GameName)
                                {
                                    NodesToDo.Add(Node.Key);
                                }
                            }

                            GameName = null;
                        }
                    }
                    if (GameName != null)
                    {
                        foreach (var GameProj in Branch.NonCodeProjects)
                        {
                            if (GameProj.GameName.Equals(GameName, StringComparison.InvariantCultureIgnoreCase))
                            {
                                foreach (var Node in GUBPNodes)
                                {
                                    if (Node.Value.GameNameIfAnyForTempStorage() == GameProj.GameName)
                                    {
                                        NodesToDo.Add(Node.Key);
                                    }
                                }
                                GameName = null;
                            }
                        }
                    }
                    if (GameName != null)
                    {
                        throw new AutomationException("Could not find game named {0}", GameName);
                    }
                }
            }
        }
        if (NodesToDo.Count == 0)
        {
            Log("No nodes specified, adding all nodes");
            foreach (var Node in GUBPNodes)
            {
                NodesToDo.Add(Node.Key);
            }
        }
        else if (TimeIndex != 0)
        {
            Log("Check to make sure we didn't ask for nodes that will be culled by time index");
            foreach (var NodeToDo in NodesToDo)
            {
                if (TimeIndex % (1 << GUBPNodes[NodeToDo].DependentCISFrequencyQuantumShift()) != 0)
                {
                    throw new AutomationException("You asked specifically for node {0}, but it is culled by the time quantum: TimeIndex = {1}, DependentCISFrequencyQuantumShift = {2}.", NodeToDo, TimeIndex, GUBPNodes[NodeToDo].DependentCISFrequencyQuantumShift());
                }
            }
        }

        Log("Desired Nodes");
        foreach (var NodeToDo in NodesToDo)
        {
            Log("  {0}", NodeToDo);
        }
        // if we are doing related to, then find things that depend on the selected nodes
        if (bRelatedToNode)
        {
            bool bDoneWithDependencies = false;

            while (!bDoneWithDependencies)
            {
                bDoneWithDependencies = true;
                var Fringe = new HashSet<string>();
                foreach (var NodeToDo in GUBPNodes)
                {
                    if (!NodesToDo.Contains(NodeToDo.Key))
                    {
                        foreach (var Dep in GUBPNodes[NodeToDo.Key].FullNamesOfDependencies)
                        {
                            if (!GUBPNodes.ContainsKey(Dep))
                            {
                                throw new AutomationException("Node {0} is not in the graph. It is a dependency of {1}.", Dep, NodeToDo.Key);
                            }
                            if (NodesToDo.Contains(Dep))
                            {
                                Fringe.Add(NodeToDo.Key);
                                bDoneWithDependencies = false;
                            }
                        }
                        foreach (var Dep in GUBPNodes[NodeToDo.Key].FullNamesOfPseudosependencies)
                        {
                            if (!GUBPNodes.ContainsKey(Dep))
                            {
                                throw new AutomationException("Node {0} is not in the graph. It is a pseudodependency of {1}.", Dep, NodeToDo.Key);
                            }
                        }
                    }
                }
                NodesToDo.UnionWith(Fringe);
            }
        }

        // find things that our nodes depend on
        if (!bOnlyNode)
        {
            bool bDoneWithDependencies = false;

            while (!bDoneWithDependencies)
            {
                bDoneWithDependencies = true;
                var Fringe = new HashSet<string>();
                foreach (var NodeToDo in NodesToDo)
                {
                    foreach (var Dep in GUBPNodes[NodeToDo].FullNamesOfDependencies)
                    {
                        if (!GUBPNodes.ContainsKey(Dep))
                        {
                            throw new AutomationException("Node {0} is not in the graph. It is a dependency of {1}.", Dep, NodeToDo);
                        }
                        if (!NodesToDo.Contains(Dep))
                        {
                            Fringe.Add(Dep);
                            bDoneWithDependencies = false;
                        }
                    }
                    foreach (var Dep in GUBPNodes[NodeToDo].FullNamesOfPseudosependencies)
                    {
                        if (!GUBPNodes.ContainsKey(Dep))
                        {
                            throw new AutomationException("Node {0} is not in the graph. It is a pseudodependency of {1}.", Dep, NodeToDo);
                        }
                    }
                }
                NodesToDo.UnionWith(Fringe);
            }
        }
        if (TimeIndex != 0)
        {
            Log("Culling based on time index");
            var NewNodesToDo = new HashSet<string>();
            foreach (var NodeToDo in NodesToDo)
            {
                if (TimeIndex % (1 << GUBPNodes[NodeToDo].DependentCISFrequencyQuantumShift()) == 0)
                {
                    Log("  Keeping {0}", NodeToDo);
                    NewNodesToDo.Add(NodeToDo);
                }
                else
                {
                    Log("  Rejecting {0}", NodeToDo);
                }
            }
            NodesToDo = NewNodesToDo;
        }
        //Remove Plat if specified
        if(WithoutLinux)
        {
            var NewNodesToDo = new HashSet<string>();
            foreach(var NodeToDo in NodesToDo)
            {
                if(!GUBPNodes[NodeToDo].GetFullName().Contains("Linux"))
                {
                    NewNodesToDo.Add(NodeToDo);
                }
                else
                {
                    Log(" Rejecting {0} because -NoLinux was requested", NodeToDo);
                }
            }
            NodesToDo = NewNodesToDo;
        }
        //find things that depend on our nodes and setup commander dictionary
        if (!bOnlyNode)
        {
            foreach(var NodeToDo in NodesToDo)
            {
                if (!GUBPNodes[NodeToDo].IsAggregate() && !GUBPNodes[NodeToDo].IsTest())
                {
                    List<string> ECDependencies = new List<string>();
                    ECDependencies = GetECDependencies(NodeToDo);
                    foreach (var Dep in ECDependencies)
                    {
                        if (!GUBPNodes.ContainsKey(Dep))
                        {
                            throw new AutomationException("Node {0} is not in the graph. It is a dependency of {1}.", Dep, NodeToDo);
                        }
                        if (!GUBPNodes[Dep].FullNamesOfDependedOn.Contains(NodeToDo))
                        {
                            GUBPNodes[Dep].FullNamesOfDependedOn.Add(NodeToDo);
                        }
                    }
                }
            }
            foreach(var NodeToDo in NodesToDo)
            {
                var Deps = GUBPNodes[NodeToDo].FullNamesOfDependedOn;
                string All = "";
                foreach (var Dep in Deps)
                {
                    if (All != "")
                    {
                        All += " ";
                    }
                    All += Dep;
                }
                FullNodeDependedOnBy.Add(NodeToDo, All);
            }
        }

        if (CommanderSetup)
        {
            if (!String.IsNullOrEmpty(ExplicitTrigger))
            {
                bool bFoundIt = false;
                foreach (var Node in GUBPNodes)
                {
                    if (Node.Value.GetFullName().Equals(ExplicitTrigger, StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (Node.Value.TriggerNode() && Node.Value.RunInEC())
                        {
                            Node.Value.SetAsExplicitTrigger();
                            bFoundIt = true;
                            break;
                        }
                    }
                }
                if (!bFoundIt)
                {
                    throw new AutomationException("Could not find trigger node named {0}", ExplicitTrigger);
                }
            }
            else
            {
                if (bSkipTriggers)
                {
                    foreach (var Node in GUBPNodes)
                    {
                        if (Node.Value.TriggerNode() && Node.Value.RunInEC())
                        {
                            Node.Value.SetAsExplicitTrigger();
                        }
                    }
                }
            }
        }
        if (bPreflightBuild)
        {
            Log("Culling triggers and downstream for preflight builds ");
            var NewNodesToDo = new HashSet<string>();
            foreach (var NodeToDo in NodesToDo)
            {
                var TriggerDot = GetControllingTriggerDotName(NodeToDo);
                if (TriggerDot == "" && !GUBPNodes[NodeToDo].TriggerNode())
                {
                    Log("  Keeping {0}", NodeToDo);
                    NewNodesToDo.Add(NodeToDo);
                }
                else
                {
                    Log("  Rejecting {0}", NodeToDo);
                }
            }
            NodesToDo = NewNodesToDo;
        }

        GUBPNodesCompleted = new Dictionary<string, bool>();
        GUBPNodesHistory = new Dictionary<string, NodeHistory>();

        Log("******* Caching completion");
        {
            var StartTime = DateTime.UtcNow;
            foreach (var Node in NodesToDo)
            {
                Log("** {0}", Node);
                NodeIsAlreadyComplete(Node, LocalOnly); // cache these now to avoid spam later
                GetControllingTriggerDotName(Node);
            }
            var BuildDuration = (DateTime.UtcNow - StartTime).TotalMilliseconds;
            Log("Took {0}s to cache completion for {1} nodes", BuildDuration / 1000, NodesToDo.Count);
        }
        /*if (CLString != "" && StoreName.Contains(CLString) && !ParseParam("NoHistory"))
        {
            Log("******* Updating history");
            var StartTime = DateTime.UtcNow;
            foreach (var Node in NodesToDo)
            {
                if (!NodeIsAlreadyComplete(Node, LocalOnly))
                {
                    UpdateNodeHistory(Node, CLString);
                }
            }
            var BuildDuration = (DateTime.UtcNow - StartTime).TotalMilliseconds;
            Log("Took {0}s to get history for {1} nodes", BuildDuration / 1000, NodesToDo.Count);
        }*/

        var OrdereredToDo = TopologicalSort(NodesToDo, ExplicitTrigger, LocalOnly);

        // find all unfinished triggers, excepting the one we are triggering right now
        var UnfinishedTriggers = new List<string>();
        if (!bSkipTriggers)
        {
            foreach (var NodeToDo in OrdereredToDo)
            {
                if (GUBPNodes[NodeToDo].TriggerNode() && !NodeIsAlreadyComplete(NodeToDo, LocalOnly))
                {
                    if (String.IsNullOrEmpty(ExplicitTrigger) || ExplicitTrigger != NodeToDo)
                    {
                        UnfinishedTriggers.Add(NodeToDo);
                    }
                }
            }
        }

        Log("*********** Desired And Dependent Nodes, in order.");
        PrintNodes(this, OrdereredToDo, LocalOnly, UnfinishedTriggers);
        //check sorting
        {
            foreach (var NodeToDo in OrdereredToDo)
            {
                if (GUBPNodes[NodeToDo].TriggerNode() && (GUBPNodes[NodeToDo].IsSticky() || NodeIsAlreadyComplete(NodeToDo, LocalOnly))) // these sticky triggers are ok, everything is already completed anyway
                {
                    continue;
                }
                if(GUBPNodes[NodeToDo].IsTest())
                {
                    bHasTests = true;
                }
                int MyIndex = OrdereredToDo.IndexOf(NodeToDo);
                foreach (var Dep in GUBPNodes[NodeToDo].FullNamesOfDependencies)
                {
                    int DepIndex = OrdereredToDo.IndexOf(Dep);
                    if (DepIndex >= MyIndex)
                    {
                        throw new AutomationException("Topological sort error, node {0} has a dependency of {1} which sorted after it.", NodeToDo, Dep);
                    }
                }
                foreach (var Dep in GUBPNodes[NodeToDo].FullNamesOfPseudosependencies)
                {
                    int DepIndex = OrdereredToDo.IndexOf(Dep);
                    if (DepIndex >= MyIndex)
                    {
                        throw new AutomationException("Topological sort error, node {0} has a pseduodependency of {1} which sorted after it.", NodeToDo, Dep);
                    }
                }
            }
        }

        string FakeFail = ParseParamValue("FakeFail");
        if (CommanderSetup)
        {

            if (OrdereredToDo.Count == 0)
            {
                throw new AutomationException("No nodes to do!");
            }
            var ECProps = new List<string>();
            ECProps.Add(String.Format("TimeIndex={0}", TimeIndex));
            foreach (var NodePair in FullNodeList)
            {
                ECProps.Add(string.Format("AllNodes/{0}={1}", NodePair.Key, NodePair.Value));
            }
            foreach (var NodePair in FullNodeDirectDependencies)
            {
                ECProps.Add(string.Format("DirectDependencies/{0}={1}", NodePair.Key, NodePair.Value));
            }
            foreach (var NodePair in FullNodeListSortKey)
            {
                ECProps.Add(string.Format("SortKey/{0}={1}", NodePair.Key, NodePair.Value));
            }
            foreach (var NodePair in FullNodeDependedOnBy)
            {
                ECProps.Add(string.Format("DependedOnBy/{0}={1}", NodePair.Key, NodePair.Value));
            }
            foreach (var NodePair in FullNodeDependentPromotions)
            {
                ECProps.Add(string.Format("DependentPromotions/{0}={1}", NodePair.Key, NodePair.Value));
            }
            foreach (var Node in SeparatePromotables)
            {
                ECProps.Add(string.Format("PossiblePromotables/{0}={1}", Node, ""));
            }
            var ECJobProps = new List<string>();
            if (ExplicitTrigger != "")
            {
                ECJobProps.Add("IsRoot=0");
            }
            else
            {
                ECJobProps.Add("IsRoot=1");
            }

            var FilteredOrdereredToDo = new List<string>();
            // remove nodes that have unfinished triggers
            foreach (var NodeToDo in OrdereredToDo)
            {
                string ControllingTrigger = GetControllingTrigger(NodeToDo);
                bool bNoUnfinishedTriggers = !UnfinishedTriggers.Contains(ControllingTrigger);

                if (bNoUnfinishedTriggers)
                {
                    // if we are triggering, then remove nodes that are not controlled by the trigger or are dependencies of this trigger
                    if (!String.IsNullOrEmpty(ExplicitTrigger))
                    {
                        if (ExplicitTrigger != NodeToDo && !NodeDependsOn(NodeToDo, ExplicitTrigger) && !NodeDependsOn(ExplicitTrigger, NodeToDo))
                        {
                            continue; // this wasn't on the chain related to the trigger we are triggering, so it is not relevant
                        }
                    }
                    if (bPreflightBuild && !bSkipTriggers && GUBPNodes[NodeToDo].TriggerNode())
                    {
                        // in preflight builds, we are either skipping triggers (and running things downstream) or we just stop at triggers and don't make them available for triggering.
                        continue;
                    }
                    FilteredOrdereredToDo.Add(NodeToDo);
                }
            }
            OrdereredToDo = FilteredOrdereredToDo;
            Log("*********** EC Nodes, in order.");
            PrintNodes(this, OrdereredToDo, LocalOnly, UnfinishedTriggers);

            // here we are just making sure everything before the explicit trigger is completed.
            if (!String.IsNullOrEmpty(ExplicitTrigger))
            {
                foreach (var NodeToDo in FilteredOrdereredToDo)
                {
                    if (GUBPNodes[NodeToDo].RunInEC() && !NodeIsAlreadyComplete(NodeToDo, LocalOnly) && NodeToDo != ExplicitTrigger && !NodeDependsOn(ExplicitTrigger, NodeToDo)) // if something is already finished, we don't put it into EC
                    {
                        throw new AutomationException("We are being asked to process node {0}, however, this is an explicit trigger {1}, so everything before it should already be handled. It seems likely that you waited too long to run the trigger. You will have to do a new build from scratch.", NodeToDo, ExplicitTrigger);
                    }
                }
            }

            string LastSticky = "";
            bool HitNonSticky = false;
            bool bHaveECNodes = false;
            List<string> StepList = new List<string>();
            StepList.Add("use strict;");
            StepList.Add("use diagnostics;");
            StepList.Add("use ElectricCommander();");
            StepList.Add("my $ec = new ElectricCommander;");
            StepList.Add("$ec->setTimeout(600);");
            StepList.Add("my $batch = $ec->newBatch(\"serial\");");
            // sticky nodes are ones that we run on the main agent. We run then first and they must not be intermixed with parallel jobs
            foreach (var NodeToDo in OrdereredToDo)
            {
                if (GUBPNodes[NodeToDo].RunInEC() && !NodeIsAlreadyComplete(NodeToDo, LocalOnly)) // if something is already finished, we don't put it into EC
                {
                    bHaveECNodes = true;
                    if (GUBPNodes[NodeToDo].IsSticky())
                    {
                        LastSticky = NodeToDo;
                        if (HitNonSticky && !bSkipTriggers)
                        {
                            throw new AutomationException("Sticky and non-sticky jobs did not sort right.");
                        }
                    }
                    else
                    {
                        HitNonSticky = true;
                    }
                }
            }

            string ParentPath = ParseParamValue("ParentPath");
            string BaseArgs = String.Format("$batch->createJobStep({{parentPath => '{0}'", ParentPath);

            bool bHasNoop = false;
            if (LastSticky == "" && bHaveECNodes)
            {
                // if we don't have any sticky nodes and we have other nodes, we run a fake noop just to release the resource
                string Args = String.Format("{0}, subprocedure => 'GUBP_UAT_Node', parallel => '0', jobStepName => 'Noop', actualParameter => [{{actualParameterName => 'NodeName', value => 'Noop'}}, {{actualParameterName => 'Sticky', value =>'1' }}], releaseMode => 'release'}});", BaseArgs);
                StepList.Add(Args);
                bHasNoop = true;
            }

            var FakeECArgs = new List<string>();
            var AgentGroupChains = new Dictionary<string, List<string>>();
            foreach (var NodeToDo in OrdereredToDo)
            {
                if (GUBPNodes[NodeToDo].RunInEC() && !NodeIsAlreadyComplete(NodeToDo, LocalOnly)) // if something is already finished, we don't put it into EC
                {
                    string MyAgentGroup = GUBPNodes[NodeToDo].AgentSharingGroup;
                    if (MyAgentGroup != "")
                    {
                        if (!AgentGroupChains.ContainsKey(MyAgentGroup))
                        {
                            AgentGroupChains.Add(MyAgentGroup, new List<string>{NodeToDo});
                        }
                        else
                        {
                            AgentGroupChains[MyAgentGroup].Add(NodeToDo);
                        }
                    }
                }
            }

            foreach (var NodeToDo in OrdereredToDo)
            {
                if (GUBPNodes[NodeToDo].RunInEC() && !NodeIsAlreadyComplete(NodeToDo, LocalOnly)) // if something is already finished, we don't put it into EC
                {
                    string EMails;
                    var NodeProps = GetECPropsForNode(NodeToDo, CLString, out EMails);
                    ECProps.AddRange(NodeProps);

                    bool Sticky = GUBPNodes[NodeToDo].IsSticky();
                    bool DoParallel = !Sticky;
                    if (Sticky && GUBPNodes[NodeToDo].ECAgentString() != "")
                    {
                        throw new AutomationException("Node {1} is sticky but has agent requirements.", NodeToDo);
                    }
                    string Procedure = GUBPNodes[NodeToDo].ECProcedure();
                    if(GUBPNodes[NodeToDo].IsSticky() && NodeToDo == LastSticky)
                    {
                        Procedure = Procedure + "_Release";
                    }
                    string Args = String.Format("{0}, subprocedure => '{1}', parallel => '{2}', jobStepName => '{3}', actualParameter => [{{actualParameterName => 'NodeName', value =>'{4}'}}",
                        BaseArgs, Procedure, DoParallel ? 1 : 0, NodeToDo, NodeToDo);
                    string ProcedureParams = GUBPNodes[NodeToDo].ECProcedureParams();
                    if (!String.IsNullOrEmpty(ProcedureParams))
                    {
                        Args = Args + ProcedureParams;
                    }

                    if ((Procedure == "GUBP_UAT_Trigger" || Procedure == "GUBP_Hardcoded_Trigger") && !String.IsNullOrEmpty(EMails))
                    {
                        Args = Args + ", {actualParameterName => 'EmailsForTrigger', value => \'" + EMails + "\'}";
                    }
                    Args = Args + "]";
                    string PreCondition = "";
                    string RunCondition = "";
                    var UncompletedEcDeps = new List<string>();
                    var UncompletedCompletedDeps = new List<string>();

                    string MyAgentGroup = GUBPNodes[NodeToDo].AgentSharingGroup;
                    bool bDoNestedJobstep = false;
                    bool bDoFirstNestedJobstep = false;

                    string NodeParentPath = ParentPath;
                    string PreconditionParentPath;
                    if (GUBPNodes[NodeToDo].GetFullName().Contains("MakeBuild") && GUBPNodes[NodeToDo].FullNamesOfPseudosependencies.Contains(WaitForFormalUserInput.StaticGetFullName()) && !bGraphSubset)
                    {
                        RemovePseudodependencyFromNode(NodeToDo, WaitForFormalUserInput.StaticGetFullName());
                        PreconditionParentPath = GetPropertyFromStep("/myWorkflow/ParentJob");
                        UncompletedEcDeps = GetECDependencies(NodeToDo);
                    }
                    else
                    {
                        PreconditionParentPath = ParentPath;
                        var EcDeps = GetECDependencies(NodeToDo);
                        foreach (var Dep in EcDeps)
                        {
                            if (GUBPNodes[Dep].RunInEC() && !NodeIsAlreadyComplete(Dep, LocalOnly) && OrdereredToDo.Contains(Dep)) // if something is already finished, we don't put it into EC
                            {
                                if (OrdereredToDo.IndexOf(Dep) > OrdereredToDo.IndexOf(NodeToDo))
                                {
                                    throw new AutomationException("Topological sort error, node {0} has a dependency of {1} which sorted after it.", NodeToDo, Dep);
                                }
                                UncompletedEcDeps.Add(Dep);
                            }
                        }
                    }
                    var PreConditionUncompletedEcDeps = UncompletedEcDeps;
                    var CompletedDeps = GetCompletedOnlyDependencies(NodeToDo);
                    foreach (var Dep in CompletedDeps)
                    {
                        if (GUBPNodes[Dep].RunInEC() && !NodeIsAlreadyComplete(Dep, LocalOnly) && OrdereredToDo.Contains(Dep)) // if something is already finished, we don't put it into EC
                        {
                            if (OrdereredToDo.IndexOf(Dep) > OrdereredToDo.IndexOf(NodeToDo))
                            {
                                throw new AutomationException("Topological sort error, node {0} has a dependency of {1} which sorted after it.", NodeToDo, Dep);
                            }
                            UncompletedCompletedDeps.Add(Dep);
                        }
                    }
                    if (MyAgentGroup != "")
                    {
                        bDoNestedJobstep = true;
                        NodeParentPath = ParentPath + "/jobSteps[" + MyAgentGroup + "]";

                        PreConditionUncompletedEcDeps = new List<string>();

                        var MyChain = AgentGroupChains[MyAgentGroup];
                        int MyIndex = MyChain.IndexOf(NodeToDo);
                        if (MyIndex  > 0)
                        {
                            PreConditionUncompletedEcDeps.Add(MyChain[MyIndex - 1]);
                        }
                        else
                        {
                            bDoFirstNestedJobstep = bDoNestedJobstep;
                            // to avoid idle agents (and also EC doesn't actually reserve our agent!), we promote all dependencies to the first one
                            foreach (var Chain in MyChain)
                            {
                                var EcDeps = GetECDependencies(Chain);
                                foreach (var Dep in EcDeps)
                                {
                                    if (GUBPNodes[Dep].RunInEC() && !NodeIsAlreadyComplete(Dep, LocalOnly) && OrdereredToDo.Contains(Dep)) // if something is already finished, we don't put it into EC
                                    {
                                        if (OrdereredToDo.IndexOf(Dep) > OrdereredToDo.IndexOf(Chain))
                                        {
                                            throw new AutomationException("Topological sort error, node {0} has a dependency of {1} which sorted after it.", Chain, Dep);
                                        }
                                        if (!MyChain.Contains(Dep) && !PreConditionUncompletedEcDeps.Contains(Dep))
                                        {
                                            PreConditionUncompletedEcDeps.Add(Dep);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (bHasNoop && PreConditionUncompletedEcDeps.Count == 0)
                    {
                        PreConditionUncompletedEcDeps.Add("Noop");
                    }
                    if (PreConditionUncompletedEcDeps.Count > 0)
                    {
                        PreCondition = "\"\\$\" . \"[/javascript if(";
                        // these run "parallel", but we add preconditions to serialize them
                        int Index = 0;
                        foreach (var Dep in PreConditionUncompletedEcDeps)
                        {
                            PreCondition = PreCondition + "getProperty('" + GetJobStep(PreconditionParentPath, Dep) + "/status\') == \'completed\'";
                            Index++;
                            if (Index != PreConditionUncompletedEcDeps.Count)
                            {
                                PreCondition = PreCondition + " && ";
                            }
                        }
                        PreCondition = PreCondition + ") true;]\"";
                    }
                    if(UncompletedCompletedDeps.Count > 0)
                    {
                        PreCondition = "\"\\$\" . \"[/javascript if(";
                        // these run "parallel", but we add preconditions to serialize them
                        int Index = 0;
                        foreach (var Dep in CompletedDeps)
                        {
                            if (GUBPNodes[Dep].RunInEC())
                            {
                                PreCondition = PreCondition + "getProperty('" + GetJobStep(PreconditionParentPath, Dep) + "/status\') == \'completed\'";
                                Index++;
                                if (Index != CompletedDeps.Count)
                                {
                                    PreCondition = PreCondition + " && ";
                                }
                            }
                        }
                        PreCondition = PreCondition + ") true;]\"";
                    }

                    if (UncompletedEcDeps.Count > 0)
                    {
                        RunCondition = "\"\\$\" . \"[/javascript if(";
                        int Index = 0;
                        foreach (var Dep in UncompletedEcDeps)
                        {
                            RunCondition = RunCondition + "((\'\\$\" . \"[" + GetJobStep(PreconditionParentPath, Dep) + "/outcome]\' == \'success\') || ";
                            RunCondition = RunCondition + "(\'\\$\" . \"[" + GetJobStep(PreconditionParentPath, Dep) + "/outcome]\' == \'warning\'))";

                            Index++;
                            if (Index != UncompletedEcDeps.Count)
                            {
                                RunCondition = RunCondition + " && ";
                            }
                        }
                        RunCondition = RunCondition + ")true; else false;]\"";
                    }

                    if (bDoNestedJobstep)
                    {
                        if (bDoFirstNestedJobstep)
                        {
                            {
                                string NestArgs = String.Format("$batch->createJobStep({{parentPath => '{0}', jobStepName => '{1}', parallel => '1'",
                                    ParentPath, MyAgentGroup);
                                if (!String.IsNullOrEmpty(PreCondition))
                                {
                                    NestArgs = NestArgs + ", precondition => " + PreCondition;
                                }
                                NestArgs = NestArgs + "});";
                                StepList.Add(NestArgs);
                            }
                            {
                                string NestArgs = String.Format("$batch->createJobStep({{parentPath => '{0}/jobSteps[{1}]', jobStepName => '{2}_GetPool', subprocedure => 'GUBP{3}_AgentShare_GetPool', parallel => '1', actualParameter => [{{actualParameterName => 'AgentSharingGroup', value => '{4}'}}, {{actualParameterName => 'NodeName', value => '{5}'}}]",
                                    ParentPath, MyAgentGroup, MyAgentGroup, GUBPNodes[NodeToDo].ECProcedureInfix(), MyAgentGroup, NodeToDo);
                                if (!String.IsNullOrEmpty(PreCondition))
                                {
                                    NestArgs = NestArgs + ", precondition => " + PreCondition;
                                }
                                NestArgs = NestArgs + "});";
                                StepList.Add(NestArgs);
                            }
                            {
                                string NestArgs = String.Format("$batch->createJobStep({{parentPath => '{0}/jobSteps[{1}]', jobStepName => '{2}_GetAgent', subprocedure => 'GUBP{3}_AgentShare_GetAgent', parallel => '1', exclusiveMode => 'call', resourceName => '{4}', actualParameter => [{{actualParameterName => 'AgentSharingGroup', value => '{5}'}}, {{actualParameterName => 'NodeName', value=> '{6}'}}]",
                                    ParentPath, MyAgentGroup, MyAgentGroup, GUBPNodes[NodeToDo].ECProcedureInfix(),
                                    String.Format("$[/myJob/jobSteps[{0}]/ResourcePool]", MyAgentGroup),
                                    MyAgentGroup, NodeToDo);
                                {
                                    NestArgs = NestArgs + ", precondition  => ";
                                    NestArgs = NestArgs + "\"\\$\" . \"[/javascript if(";
                                    NestArgs = NestArgs + "getProperty('" + PreconditionParentPath + "/jobSteps[" + MyAgentGroup + "]/jobSteps[" + MyAgentGroup + "_GetPool]/status') == 'completed'";
                                    NestArgs = NestArgs + ") true;]\"";
                                }
                                NestArgs = NestArgs + "});";
                                StepList.Add(NestArgs);
                            }
                            {
                                PreCondition = "\"\\$\" . \"[/javascript if(";
                                PreCondition = PreCondition + "getProperty('" + PreconditionParentPath + "/jobSteps[" + MyAgentGroup + "]/jobSteps[" + MyAgentGroup + "_GetAgent]/status') == 'completed'";
                                PreCondition = PreCondition + ") true;]\"";
                            }
                        }
                        Args = Args.Replace(String.Format("parentPath => '{0}'", ParentPath), String.Format("parentPath => '{0}'", NodeParentPath));
                        Args = Args.Replace("UAT_Node_Parallel_AgentShare", "UAT_Node_Parallel_AgentShare3");
                    }

                    if (!String.IsNullOrEmpty(PreCondition))
                    {
                        Args = Args + ", precondition => " + PreCondition;
                    }
                    if (!String.IsNullOrEmpty(RunCondition))
                    {
                        Args = Args + ", condition => " + RunCondition;
                    }
        #if false
                    // this doesn't work because it includes precondition time
                    if (GUBPNodes[NodeToDo].TimeoutInMinutes() > 0)
                    {
                        Args = Args + String.Format(" --timeLimitUnits minutes --timeLimit {0}", GUBPNodes[NodeToDo].TimeoutInMinutes());
                    }
        #endif
                    if (Sticky && NodeToDo == LastSticky)
                    {
                        Args = Args + ", releaseMode => 'release'";
                    }
                    Args = Args + "});";
                    StepList.Add(Args);
                    if (bFakeEC &&
                        !UnfinishedTriggers.Contains(NodeToDo) &&
                        (GUBPNodes[NodeToDo].ECProcedure().StartsWith("GUBP_UAT_Node") || GUBPNodes[NodeToDo].ECProcedure().StartsWith("GUBP_Mac_UAT_Node")) // other things we really can't test
                        ) // unfinished triggers are never run directly by EC, rather it does another job setup
                    {
                        string Arg = String.Format("gubp -Node={0} -FakeEC {1} {2} {3} {4} {5}",
                            NodeToDo,
                            bFake ? "-Fake" : "" ,
                            ParseParam("AllPlatforms") ? "-AllPlatforms" : "",
                            ParseParam("UnfinishedTriggersFirst") ? "-UnfinishedTriggersFirst" : "",
                            ParseParam("UnfinishedTriggersParallel") ? "-UnfinishedTriggersParallel" : "",
                            ParseParam("WithMac") ? "-WithMac" : ""
                            );

                        string Node = ParseParamValue("-Node");
                        if (!String.IsNullOrEmpty(Node))
                        {
                            Arg = Arg + " -Node=" + Node;
                        }
                        if (!String.IsNullOrEmpty(FakeFail))
                        {
                            Arg = Arg + " -FakeFail=" + FakeFail;
                        }
                        FakeECArgs.Add(Arg);
                    }

                    if (MyAgentGroup != "" && !bDoNestedJobstep)
                    {
                        var MyChain = AgentGroupChains[MyAgentGroup];
                        int MyIndex = MyChain.IndexOf(NodeToDo);
                        if (MyIndex == MyChain.Count - 1)
                        {
                            var RelPreCondition = "\"\\$\" . \"[/javascript if(";
                            // this runs "parallel", but we a precondition to serialize it
                            RelPreCondition = RelPreCondition + "getProperty('" + PreconditionParentPath + "/jobSteps[" + NodeToDo + "]/status') == 'completed'";
                            RelPreCondition = RelPreCondition + ") true;]\"";
                            // we need to release the resource
                            string RelArgs = String.Format("{0}, subprocedure => 'GUBP_Release_AgentShare', parallel => '1', jobStepName => 'Release_{1}', actualParameter => [{{actualParameterName => 'AgentSharingGroup', valued => '{2}'}}], releaseMode => 'release', precondition => '{3}'",
                                BaseArgs, MyAgentGroup, MyAgentGroup, RelPreCondition);
                            StepList.Add(RelArgs);
                        }
                    }
                }
            }
            WriteECPerl(StepList);
            RunECTool(String.Format("setProperty \"/myWorkflow/HasTests\" \"{0}\"", bHasTests));
            {
                ECProps.Add("GUBP_LoadedProps=1");
                string BranchDefFile = CommandUtils.CombinePaths(CommandUtils.CmdEnv.LogFolder, "BranchDef.properties");
                CommandUtils.WriteAllLines(BranchDefFile, ECProps.ToArray());
                RunECTool(String.Format("setProperty \"/myWorkflow/BranchDefFile\" \"{0}\"", BranchDefFile.Replace("\\", "\\\\")));
            }
            {
                ECProps.Add("GUBP_LoadedJobProps=1");
                string BranchJobDefFile = CommandUtils.CombinePaths(CommandUtils.CmdEnv.LogFolder, "BranchJobDef.properties");
                CommandUtils.WriteAllLines(BranchJobDefFile, ECProps.ToArray());
                RunECTool(String.Format("setProperty \"/myJob/BranchJobDefFile\" \"{0}\"", BranchJobDefFile.Replace("\\", "\\\\")));
            }
            if (bFakeEC)
            {
                foreach (var Args in FakeECArgs)
                {
                    RunUAT(CmdEnv, Args);
                }
            }
            Log("Commander setup only, done.");
            PrintRunTime();
            return;

        }
        if (ParseParam("SaveGraph"))
        {
            SaveGraphVisualization(OrdereredToDo);
        }
        if (bListOnly)
        {
            Log("List only, done.");
            return;
        }
        var BuildProductToNodeMap = new Dictionary<string, string>();
        foreach (var NodeToDo in OrdereredToDo)
        {
            if (GUBPNodes[NodeToDo].BuildProducts != null || GUBPNodes[NodeToDo].AllDependencyBuildProducts != null)
            {
                throw new AutomationException("topological sort error");
            }

            GUBPNodes[NodeToDo].AllDependencyBuildProducts = new List<string>();
            GUBPNodes[NodeToDo].AllDependencies = new List<string>();
            foreach (var Dep in GUBPNodes[NodeToDo].FullNamesOfDependencies)
            {
                GUBPNodes[NodeToDo].AddAllDependent(Dep);
                if (GUBPNodes[Dep].AllDependencies == null)
                {
                    if (!bOnlyNode)
                    {
                        throw new AutomationException("Node {0} was not processed yet3?  Processing {1}", Dep, NodeToDo);
                    }
                }
                else
                {
                    foreach (var DepDep in GUBPNodes[Dep].AllDependencies)
                    {
                        GUBPNodes[NodeToDo].AddAllDependent(DepDep);
                    }
                }

                if (GUBPNodes[Dep].BuildProducts == null)
                {
                    if (!bOnlyNode)
                    {
                        throw new AutomationException("Node {0} was not processed yet? Processing {1}", Dep, NodeToDo);
                    }
                }
                else
                {
                    foreach (var Prod in GUBPNodes[Dep].BuildProducts)
                    {
                        GUBPNodes[NodeToDo].AddDependentBuildProduct(Prod);
                    }
                    if (GUBPNodes[Dep].AllDependencyBuildProducts == null)
                    {
                        throw new AutomationException("Node {0} was not processed yet2?  Processing {1}", Dep, NodeToDo);
                    }
                    foreach (var Prod in GUBPNodes[Dep].AllDependencyBuildProducts)
                    {
                        GUBPNodes[NodeToDo].AddDependentBuildProduct(Prod);
                    }
                }
            }

            string NodeStoreName = StoreName + "-" + GUBPNodes[NodeToDo].GetFullName();

            string GameNameIfAny = GUBPNodes[NodeToDo].GameNameIfAnyForTempStorage();
            string StorageRootIfAny = GUBPNodes[NodeToDo].RootIfAnyForTempStorage();

            if (bFake)
            {
                StorageRootIfAny = ""; // we don't rebase fake runs since those are entirely "records of success", which are always in the logs folder
            }

            // this is kinda complicated
            bool SaveSuccessRecords = (IsBuildMachine || bFakeEC) && // no real reason to make these locally except for fakeEC tests
                (!GUBPNodes[NodeToDo].TriggerNode() || GUBPNodes[NodeToDo].IsSticky()) // trigger nodes are run twice, one to start the new workflow and once when it is actually triggered, we will save reconds for the latter
                && (GUBPNodes[NodeToDo].RunInEC() || !GUBPNodes[NodeToDo].IsAggregate()); //aggregates not in EC can be "run" multiple times, so we can't track those

            Log("***** Running GUBP Node {0} -> {1} : {2}", GUBPNodes[NodeToDo].GetFullName(), GameNameIfAny, NodeStoreName);
            if (NodeIsAlreadyComplete(NodeToDo, LocalOnly))
            {
                if (NodeToDo == VersionFilesNode.StaticGetFullName() && !IsBuildMachine)
                {
                    Log("***** NOT ****** Retrieving GUBP Node {0} from {1}; it is the version files.", GUBPNodes[NodeToDo].GetFullName(), NodeStoreName);
                    GUBPNodes[NodeToDo].BuildProducts = new List<string>();

                }
                else
                {
                    Log("***** Retrieving GUBP Node {0} from {1}", GUBPNodes[NodeToDo].GetFullName(), NodeStoreName);
                    bool WasLocal;
                    try
                    {
                        GUBPNodes[NodeToDo].BuildProducts = RetrieveFromTempStorage(CmdEnv, NodeStoreName, out WasLocal, GameNameIfAny, StorageRootIfAny);
                    }
                    catch
                    {
                        if(GameNameIfAny != "")
                        {
                            GUBPNodes[NodeToDo].BuildProducts = RetrieveFromTempStorage(CmdEnv, NodeStoreName, out WasLocal, "", StorageRootIfAny);
                        }
                        else
                        {
                            throw new AutomationException("Build Products cannot be found for node {0}", NodeToDo);
                        }
                    }
                    if (!WasLocal)
                    {
                        GUBPNodes[NodeToDo].PostLoadFromSharedTempStorage(this);
                    }
                }
            }
            else
            {
                if (SaveSuccessRecords)
                {
                    SaveStatus(NodeToDo, StartedTempStorageSuffix, NodeStoreName, bSaveSharedTempStorage, GameNameIfAny);
                }
                var BuildDuration = 0.0;
                try
                {
                    if (!String.IsNullOrEmpty(FakeFail) && FakeFail.Equals(NodeToDo, StringComparison.InvariantCultureIgnoreCase))
                    {
                        throw new AutomationException("Failing node {0} by request.", NodeToDo);
                    }
                    if (bFake)
                    {
                        Log("***** FAKE!! Building GUBP Node {0} for {1}", NodeToDo, NodeStoreName);
                        GUBPNodes[NodeToDo].DoFakeBuild(this);
                    }
                    else
                    {
                        Log("***** Building GUBP Node {0} for {1}", NodeToDo, NodeStoreName);
                        var StartTime = DateTime.UtcNow;
                        GUBPNodes[NodeToDo].DoBuild(this);
                        BuildDuration = (DateTime.UtcNow - StartTime).TotalMilliseconds / 1000;

                    }
                    if (!GUBPNodes[NodeToDo].IsAggregate())
                    {
                        var StoreDuration = 0.0;
                        var StartTime = DateTime.UtcNow;
                        StoreToTempStorage(CmdEnv, NodeStoreName, GUBPNodes[NodeToDo].BuildProducts, !bSaveSharedTempStorage, GameNameIfAny, StorageRootIfAny);
                        StoreDuration = (DateTime.UtcNow - StartTime).TotalMilliseconds / 1000;
                        Log("Took {0} seconds to store build products", StoreDuration);
                        if (IsBuildMachine)
                        {
                            RunECTool(String.Format("setProperty \"/myJobStep/StoreDuration\" \"{0}\"", StoreDuration.ToString()));
                        }
                        if (ParseParam("StompCheck"))
                        {
                            foreach (var Dep in GUBPNodes[NodeToDo].AllDependencies)
                            {
                                try
                                {
                                    bool WasLocal;
                                    RetrieveFromTempStorage(CmdEnv, NodeStoreName, out WasLocal, GameNameIfAny, StorageRootIfAny);
                                    if (!WasLocal)
                                    {
                                        throw new AutomationException("Retrieve was not local?");
                                    }

                                }
                                catch(Exception Ex)
                                {
                                    throw new AutomationException("Node {0} stomped Node {1}   Ex: {2}", NodeToDo, Dep, LogUtils.FormatException(Ex));
                                }
                            }
                        }

                    }
                }
                catch (Exception Ex)
                {
                    if (SaveSuccessRecords)
                    {
                        UpdateNodeHistory(NodeToDo, CLString);
                        SaveStatus(NodeToDo, FailedTempStorageSuffix, NodeStoreName, bSaveSharedTempStorage, GameNameIfAny, ParseParamValue("MyJobStepId"));
                        UpdateECProps(NodeToDo, CLString);
                        if (IsBuildMachine)
                        {
                            GetFailureEmails(NodeToDo, CLString);
                        }
                        UpdateECBuildTime(NodeToDo, BuildDuration);
                    }

                    Log("{0}", ExceptionToString(Ex));

                    if (GUBPNodesHistory.ContainsKey(NodeToDo))
                    {
                        var History = GUBPNodesHistory[NodeToDo];
                        Log("Changes since last green *********************************");
                        Log("");
                        Log("");
                        Log("");
                        PrintDetailedChanges(History);
                        Log("End changes since last green");
                    }

                    string FailInfo = "";
                    FailInfo += "********************************* Main log file";
                    FailInfo += Environment.NewLine + Environment.NewLine;
                    FailInfo += LogUtils.GetLogTail();
                    FailInfo += Environment.NewLine + Environment.NewLine + Environment.NewLine;

                    string OtherLog = "See logfile for details: '";
                    if (FailInfo.Contains(OtherLog))
                    {
                        string LogFile = FailInfo.Substring(FailInfo.IndexOf(OtherLog) + OtherLog.Length);
                        if (LogFile.Contains("'"))
                        {
                            LogFile = CombinePaths(CmdEnv.LogFolder, LogFile.Substring(0, LogFile.IndexOf("'")));
                            if (FileExists_NoExceptions(LogFile))
                            {
                                FailInfo += "********************************* Sub log file " + LogFile;
                                FailInfo += Environment.NewLine + Environment.NewLine;

                                FailInfo += LogUtils.GetLogTail(LogFile);
                                FailInfo += Environment.NewLine + Environment.NewLine + Environment.NewLine;
                            }
                        }
                    }

                    string Filename = CombinePaths(CmdEnv.LogFolder, "LogTailsAndChanges.log");
                    WriteAllText(Filename, FailInfo);

                    throw(Ex);
                }
                if (SaveSuccessRecords)
                {
                    UpdateNodeHistory(NodeToDo, CLString);
                    SaveStatus(NodeToDo, SucceededTempStorageSuffix, NodeStoreName, bSaveSharedTempStorage, GameNameIfAny);
                    UpdateECProps(NodeToDo, CLString);
                    if (IsBuildMachine)
                    {
                        GetFailureEmails(NodeToDo, CLString);
                    }
                    UpdateECBuildTime(NodeToDo, BuildDuration);
                }
            }
            foreach (var Product in GUBPNodes[NodeToDo].BuildProducts)
            {
                if (BuildProductToNodeMap.ContainsKey(Product))
                {
                    throw new AutomationException("Overlapping build product: {0} and {1} both produce {2}", BuildProductToNodeMap[Product], NodeToDo, Product);
                }
                BuildProductToNodeMap.Add(Product, NodeToDo);
            }
        }

        PrintRunTime();
    }