/// <summary>
        /// Export the build graph to a Json file for parsing by Horde
        /// </summary>
        /// <param name="File">Output file to write</param>
        public void ExportForHorde(FileReference File)
        {
            DirectoryReference.CreateDirectory(File.Directory);
            using (JsonWriter JsonWriter = new JsonWriter(File.FullName))
            {
                JsonWriter.WriteObjectStart();
                JsonWriter.WriteArrayStart("Groups");
                foreach (Agent Agent in Agents)
                {
                    JsonWriter.WriteObjectStart();
                    JsonWriter.WriteArrayStart("Nodes");
                    foreach (Node Node in Agent.Nodes)
                    {
                        JsonWriter.WriteObjectStart();
                        JsonWriter.WriteValue("Name", Node.Name);
                        JsonWriter.WriteValue("Group", Agent.Name);
                        JsonWriter.WriteValue("RunEarly", Node.bRunEarly);
                        JsonWriter.WriteValue("Exclusive", true);

                        JsonWriter.WriteArrayStart("InputDependencies");
                        foreach (string InputDependency in Node.GetDirectInputDependencies().Select(x => x.Name))
                        {
                            JsonWriter.WriteValue(InputDependency);
                        }
                        JsonWriter.WriteArrayEnd();

                        JsonWriter.WriteArrayStart("OrderDependencies");
                        foreach (string OrderDependency in Node.GetDirectOrderDependencies().Select(x => x.Name))
                        {
                            JsonWriter.WriteValue(OrderDependency);
                        }
                        JsonWriter.WriteArrayEnd();

                        JsonWriter.WriteObjectEnd();
                    }
                    JsonWriter.WriteArrayEnd();
                    JsonWriter.WriteObjectEnd();
                }
                JsonWriter.WriteArrayEnd();
                JsonWriter.WriteObjectEnd();
            }
        }
Beispiel #2
0
        /// <summary>
        /// Print the contents of the graph
        /// </summary>
        /// <param name="CompletedNodes">Set of nodes which are already complete</param>
        /// <param name="PrintOptions">Options for how to print the graph</param>
        public void Print(HashSet <Node> CompletedNodes, GraphPrintOptions PrintOptions)
        {
            // Print the options
            if ((PrintOptions & GraphPrintOptions.ShowCommandLineOptions) != 0)
            {
                // Get the list of messages
                List <string> Messages = new List <string>();
                foreach (GraphOption Option in Options)
                {
                    StringBuilder Message = new StringBuilder();
                    Message.AppendFormat("-set:{0}=... {1}", Option.Name, Option.Description);
                    if (!String.IsNullOrEmpty(Option.DefaultValue))
                    {
                        Message.AppendFormat(" (Default: {0})", Option.DefaultValue);
                    }
                    Messages.Add(Message.ToString());
                }

                // Format them to the log
                if (Messages.Count > 0)
                {
                    CommandUtils.LogInformation("");
                    CommandUtils.LogInformation("Options:");
                    CommandUtils.LogInformation("");
                    foreach (string Line in CommandUtils.FormatParams(Messages, 4, 24))
                    {
                        CommandUtils.LogInformation(Line);
                    }
                }
            }

            // Get a list of all the triggers, including the null global one
            List <ManualTrigger> AllTriggers = new List <ManualTrigger>();

            AllTriggers.Add(null);
            AllTriggers.AddRange(NameToTrigger.Values.OrderBy(x => x.QualifiedName));

            // Output all the triggers in order
            CommandUtils.LogInformation("");
            CommandUtils.LogInformation("Graph:");
            foreach (ManualTrigger Trigger in AllTriggers)
            {
                // Filter everything by this trigger
                Dictionary <Agent, Node[]> FilteredAgentToNodes = new Dictionary <Agent, Node[]>();
                foreach (Agent Agent in Agents)
                {
                    Node[] Nodes = Agent.Nodes.Where(x => x.ControllingTrigger == Trigger).ToArray();
                    if (Nodes.Length > 0)
                    {
                        FilteredAgentToNodes[Agent] = Nodes;
                    }
                }

                // Skip this trigger if there's nothing to display
                if (FilteredAgentToNodes.Count == 0)
                {
                    continue;
                }

                // Print the trigger name
                CommandUtils.LogInformation("    Trigger: {0}", (Trigger == null)? "None" : Trigger.QualifiedName);
                if (Trigger != null && PrintOptions.HasFlag(GraphPrintOptions.ShowNotifications))
                {
                    foreach (string User in Trigger.NotifyUsers)
                    {
                        CommandUtils.LogInformation("            notify> {0}", User);
                    }
                }

                // Output all the agents for this trigger
                foreach (Agent Agent in Agents)
                {
                    Node[] Nodes;
                    if (FilteredAgentToNodes.TryGetValue(Agent, out Nodes))
                    {
                        CommandUtils.LogInformation("        Agent: {0} ({1})", Agent.Name, String.Join(";", Agent.PossibleTypes));
                        foreach (Node Node in Nodes)
                        {
                            CommandUtils.LogInformation("            Node: {0}{1}", Node.Name, CompletedNodes.Contains(Node)? " (completed)" : Node.bRunEarly? " (early)" : "");
                            if (PrintOptions.HasFlag(GraphPrintOptions.ShowDependencies))
                            {
                                HashSet <Node> InputDependencies = new HashSet <Node>(Node.GetDirectInputDependencies());
                                foreach (Node InputDependency in InputDependencies)
                                {
                                    CommandUtils.LogInformation("                input> {0}", InputDependency.Name);
                                }
                                HashSet <Node> OrderDependencies = new HashSet <Node>(Node.GetDirectOrderDependencies());
                                foreach (Node OrderDependency in OrderDependencies.Except(InputDependencies))
                                {
                                    CommandUtils.LogInformation("                after> {0}", OrderDependency.Name);
                                }
                            }
                            if (PrintOptions.HasFlag(GraphPrintOptions.ShowNotifications))
                            {
                                string Label = Node.bNotifyOnWarnings? "warnings" : "errors";
                                foreach (string User in Node.NotifyUsers)
                                {
                                    CommandUtils.LogInformation("                {0}> {1}", Label, User);
                                }
                                foreach (string Submitter in Node.NotifySubmitters)
                                {
                                    CommandUtils.LogInformation("                {0}> submitters to {1}", Label, Submitter);
                                }
                            }
                        }
                    }
                }
            }
            CommandUtils.LogInformation("");

            // Print out all the non-empty aggregates
            string[] AggregateNames = AggregateNameToNodes.Where(x => x.Value.Length > 0).Select(x => x.Key).OrderBy(x => x).ToArray();
            if (AggregateNames.Length > 0)
            {
                CommandUtils.LogInformation("Aggregates:");
                foreach (string AggregateName in AggregateNames)
                {
                    CommandUtils.LogInformation("    {0}", AggregateName);
                }
                CommandUtils.LogInformation("");
            }
        }
Beispiel #3
0
        /// <summary>
        /// Export the build graph to a Json file, for parallel execution by the build system
        /// </summary>
        /// <param name="File">Output file to write</param>
        /// <param name="Trigger">The trigger whose nodes to run. Null for the default nodes.</param>
        /// <param name="CompletedNodes">Set of nodes which have been completed</param>
        public void Export(FileReference File, ManualTrigger Trigger, HashSet <Node> CompletedNodes)
        {
            // Find all the nodes which we're actually going to execute. We'll use this to filter the graph.
            HashSet <Node> NodesToExecute = new HashSet <Node>();

            foreach (Node Node in Agents.SelectMany(x => x.Nodes))
            {
                if (!CompletedNodes.Contains(Node) && Node.IsBehind(Trigger))
                {
                    NodesToExecute.Add(Node);
                }
            }

            // Open the output file
            using (JsonWriter JsonWriter = new JsonWriter(File.FullName))
            {
                JsonWriter.WriteObjectStart();

                // Write all the agents
                JsonWriter.WriteArrayStart("Groups");
                foreach (Agent Agent in Agents)
                {
                    Node[] Nodes = Agent.Nodes.Where(x => NodesToExecute.Contains(x) && x.ControllingTrigger == Trigger).ToArray();
                    if (Nodes.Length > 0)
                    {
                        JsonWriter.WriteObjectStart();
                        JsonWriter.WriteValue("Name", Agent.Name);
                        JsonWriter.WriteArrayStart("Agent Types");
                        foreach (string AgentType in Agent.PossibleTypes)
                        {
                            JsonWriter.WriteValue(AgentType);
                        }
                        JsonWriter.WriteArrayEnd();
                        JsonWriter.WriteArrayStart("Nodes");
                        foreach (Node Node in Nodes)
                        {
                            JsonWriter.WriteObjectStart();
                            JsonWriter.WriteValue("Name", Node.Name);
                            JsonWriter.WriteValue("DependsOn", String.Join(";", Node.GetDirectOrderDependencies().Where(x => NodesToExecute.Contains(x) && x.ControllingTrigger == Trigger)));
                            JsonWriter.WriteValue("RunEarly", Node.bRunEarly);
                            JsonWriter.WriteObjectStart("Notify");
                            JsonWriter.WriteValue("Default", String.Join(";", Node.NotifyUsers));
                            JsonWriter.WriteValue("Submitters", String.Join(";", Node.NotifySubmitters));
                            JsonWriter.WriteValue("Warnings", Node.bNotifyOnWarnings);
                            JsonWriter.WriteObjectEnd();
                            JsonWriter.WriteObjectEnd();
                        }
                        JsonWriter.WriteArrayEnd();
                        JsonWriter.WriteObjectEnd();
                    }
                }
                JsonWriter.WriteArrayEnd();

                // Write all the badges
                JsonWriter.WriteArrayStart("Badges");
                foreach (Badge Badge in Badges)
                {
                    Node[] Dependencies = Badge.Nodes.Where(x => NodesToExecute.Contains(x) && x.ControllingTrigger == Trigger).ToArray();
                    if (Dependencies.Length > 0)
                    {
                        // Reduce that list to the smallest subset of direct dependencies
                        HashSet <Node> DirectDependencies = new HashSet <Node>(Dependencies);
                        foreach (Node Dependency in Dependencies)
                        {
                            DirectDependencies.ExceptWith(Dependency.OrderDependencies);
                        }

                        JsonWriter.WriteObjectStart();
                        JsonWriter.WriteValue("Name", Badge.Name);
                        if (!String.IsNullOrEmpty(Badge.Project))
                        {
                            JsonWriter.WriteValue("Project", Badge.Project);
                        }
                        if (Badge.Change != 0)
                        {
                            JsonWriter.WriteValue("Change", Badge.Change);
                        }
                        JsonWriter.WriteValue("AllDependencies", String.Join(";", Agents.SelectMany(x => x.Nodes).Where(x => Dependencies.Contains(x)).Select(x => x.Name)));
                        JsonWriter.WriteValue("DirectDependencies", String.Join(";", DirectDependencies.Select(x => x.Name)));
                        JsonWriter.WriteObjectEnd();
                    }
                }
                JsonWriter.WriteArrayEnd();

                // Write all the triggers and reports.
                JsonWriter.WriteArrayStart("Reports");
                foreach (Report Report in NameToReport.Values)
                {
                    Node[] Dependencies = Report.Nodes.Where(x => NodesToExecute.Contains(x) && x.ControllingTrigger == Trigger).ToArray();
                    if (Dependencies.Length > 0)
                    {
                        // Reduce that list to the smallest subset of direct dependencies
                        HashSet <Node> DirectDependencies = new HashSet <Node>(Dependencies);
                        foreach (Node Dependency in Dependencies)
                        {
                            DirectDependencies.ExceptWith(Dependency.OrderDependencies);
                        }

                        JsonWriter.WriteObjectStart();
                        JsonWriter.WriteValue("Name", Report.Name);
                        JsonWriter.WriteValue("AllDependencies", String.Join(";", Agents.SelectMany(x => x.Nodes).Where(x => Dependencies.Contains(x)).Select(x => x.Name)));
                        JsonWriter.WriteValue("DirectDependencies", String.Join(";", DirectDependencies.Select(x => x.Name)));
                        JsonWriter.WriteValue("Notify", String.Join(";", Report.NotifyUsers));
                        JsonWriter.WriteValue("IsTrigger", false);
                        JsonWriter.WriteObjectEnd();
                    }
                }
                foreach (ManualTrigger DownstreamTrigger in NameToTrigger.Values)
                {
                    if (DownstreamTrigger.Parent == Trigger)
                    {
                        // Find all the nodes that this trigger is dependent on
                        HashSet <Node> Dependencies = new HashSet <Node>();
                        foreach (Node NodeToExecute in NodesToExecute)
                        {
                            if (NodeToExecute.IsBehind(DownstreamTrigger))
                            {
                                Dependencies.UnionWith(NodeToExecute.OrderDependencies.Where(x => x.ControllingTrigger == Trigger));
                            }
                        }

                        // Reduce that list to the smallest subset of direct dependencies
                        HashSet <Node> DirectDependencies = new HashSet <Node>(Dependencies);
                        foreach (Node Dependency in Dependencies)
                        {
                            DirectDependencies.ExceptWith(Dependency.OrderDependencies);
                        }

                        // Write out the object
                        JsonWriter.WriteObjectStart();
                        JsonWriter.WriteValue("Name", DownstreamTrigger.Name);
                        JsonWriter.WriteValue("AllDependencies", String.Join(";", Agents.SelectMany(x => x.Nodes).Where(x => Dependencies.Contains(x)).Select(x => x.Name)));
                        JsonWriter.WriteValue("DirectDependencies", String.Join(";", Dependencies.Where(x => DirectDependencies.Contains(x)).Select(x => x.Name)));
                        JsonWriter.WriteValue("Notify", String.Join(";", DownstreamTrigger.NotifyUsers));
                        JsonWriter.WriteValue("IsTrigger", true);
                        JsonWriter.WriteObjectEnd();
                    }
                }
                JsonWriter.WriteArrayEnd();

                JsonWriter.WriteObjectEnd();
            }
        }
        /// <summary>
        /// Print the contents of the graph
        /// </summary>
        /// <param name="NodeToState">Mapping of node to its current state</param>
        /// <param name="Options">Options for how to print the graph</param>
        public void Print(HashSet <Node> CompletedNodes, GraphPrintOptions Options)
        {
            // Get a list of all the triggers, including the null global one
            List <ManualTrigger> AllTriggers = new List <ManualTrigger>();

            AllTriggers.Add(null);
            AllTriggers.AddRange(NameToTrigger.Values.OrderBy(x => x.QualifiedName));

            // Output all the triggers in order
            CommandUtils.Log("");
            CommandUtils.Log("Graph:");
            foreach (ManualTrigger Trigger in AllTriggers)
            {
                // Filter everything by this trigger
                Dictionary <AgentGroup, Node[]> FilteredGroupToNodes = new Dictionary <AgentGroup, Node[]>();
                foreach (AgentGroup Group in Groups)
                {
                    Node[] Nodes = Group.Nodes.Where(x => x.ControllingTrigger == Trigger).ToArray();
                    if (Nodes.Length > 0)
                    {
                        FilteredGroupToNodes[Group] = Nodes;
                    }
                }

                // Skip this trigger if there's nothing to display
                if (FilteredGroupToNodes.Count == 0)
                {
                    continue;
                }

                // Print the trigger name
                CommandUtils.Log("    Trigger: {0}", (Trigger == null)? "None" : Trigger.QualifiedName);
                if (Trigger != null && Options.HasFlag(GraphPrintOptions.ShowNotifications))
                {
                    foreach (string User in Trigger.NotifyUsers)
                    {
                        CommandUtils.Log("            notify> {0}", User);
                    }
                }

                // Output all the groups for this trigger
                foreach (AgentGroup Group in Groups)
                {
                    Node[] Nodes;
                    if (FilteredGroupToNodes.TryGetValue(Group, out Nodes))
                    {
                        CommandUtils.Log("        Group: {0} ({1})", Group.Name, String.Join(";", Group.PossibleTypes));
                        foreach (Node Node in Nodes)
                        {
                            CommandUtils.Log("            Node: {0}{1}", Node.Name, CompletedNodes.Contains(Node)? " (completed)" : "");
                            if (Options.HasFlag(GraphPrintOptions.ShowDependencies))
                            {
                                HashSet <Node> InputDependencies = new HashSet <Node>(Node.GetDirectInputDependencies());
                                foreach (Node InputDependency in InputDependencies)
                                {
                                    CommandUtils.Log("                input> {0}", InputDependency.Name);
                                }
                                HashSet <Node> OrderDependencies = new HashSet <Node>(Node.GetDirectOrderDependencies());
                                foreach (Node OrderDependency in OrderDependencies.Except(InputDependencies))
                                {
                                    CommandUtils.Log("                after> {0}", OrderDependency.Name);
                                }
                            }
                            if (Options.HasFlag(GraphPrintOptions.ShowNotifications))
                            {
                                string Label = Node.bNotifyOnWarnings? "warnings" : "errors";
                                foreach (string User in Node.NotifyUsers)
                                {
                                    CommandUtils.Log("                {0}> {1}", Label, User);
                                }
                                foreach (string Submitter in Node.NotifySubmitters)
                                {
                                    CommandUtils.Log("                {0}> submitters to {1}", Label, Submitter);
                                }
                            }
                        }
                    }
                }
            }
            CommandUtils.Log("");

            // Print out all the aggregates
            string[] AggregateNames = AggregateNameToNodes.Keys.OrderBy(x => x).ToArray();
            if (AggregateNames.Length > 0)
            {
                CommandUtils.Log("Aggregates:");
                foreach (string AggregateName in AggregateNames)
                {
                    CommandUtils.Log("    {0}", AggregateName);
                }
                CommandUtils.Log("");
            }
        }
        /// <summary>
        /// Export the build graph to a Json file, for parallel execution by the build system
        /// </summary>
        /// <param name="File">Output file to write</param>
        /// <param name="ActivatedTriggers">Set of triggers which have been activated</param>
        /// <param name="CompletedNodes">Set of nodes which have been completed</param>
        public void Export(FileReference File, HashSet <ManualTrigger> ActivatedTriggers, HashSet <Node> CompletedNodes)
        {
            // Find all the nodes which we're actually going to execute. We'll use this to filter the graph.
            HashSet <Node> NodesToExecute = new HashSet <Node>();

            foreach (Node Node in Groups.SelectMany(x => x.Nodes))
            {
                if (!CompletedNodes.Contains(Node))
                {
                    if (Node.ControllingTrigger == null || ActivatedTriggers.Contains(Node.ControllingTrigger))
                    {
                        NodesToExecute.Add(Node);
                    }
                }
            }

            // Open the output file
            using (JsonWriter JsonWriter = new JsonWriter(File.FullName))
            {
                JsonWriter.WriteObjectStart();

                // Write all the agent groups
                JsonWriter.WriteArrayStart("Groups");
                foreach (AgentGroup Group in Groups)
                {
                    Node[] Nodes = Group.Nodes.Where(x => NodesToExecute.Contains(x)).ToArray();
                    if (Nodes.Length > 0)
                    {
                        JsonWriter.WriteObjectStart();
                        JsonWriter.WriteValue("Name", Group.Name);
                        JsonWriter.WriteArrayStart("Agent Types");
                        foreach (string AgentType in Group.PossibleTypes)
                        {
                            JsonWriter.WriteValue(AgentType);
                        }
                        JsonWriter.WriteArrayEnd();
                        JsonWriter.WriteArrayStart("Nodes");
                        foreach (Node Node in Nodes)
                        {
                            JsonWriter.WriteObjectStart();
                            JsonWriter.WriteValue("Name", Node.Name);
                            JsonWriter.WriteValue("DependsOn", String.Join(";", Node.GetDirectOrderDependencies().Where(x => !CompletedNodes.Contains(x))));
                            JsonWriter.WriteObjectStart("Notify");
                            JsonWriter.WriteValue("Default", String.Join(";", Node.NotifyUsers));
                            JsonWriter.WriteValue("Submitters", String.Join(";", Node.NotifySubmitters));
                            JsonWriter.WriteValue("Warnings", Node.bNotifyOnWarnings);
                            JsonWriter.WriteObjectEnd();
                            JsonWriter.WriteObjectEnd();
                        }
                        JsonWriter.WriteArrayEnd();
                        JsonWriter.WriteObjectEnd();
                    }
                }
                JsonWriter.WriteArrayEnd();

                // Write all the triggers
                JsonWriter.WriteArrayStart("Triggers");
                foreach (ManualTrigger Trigger in NameToTrigger.Values)
                {
                    if (!ActivatedTriggers.Contains(Trigger) && NodesToExecute.Any(x => x.ControllingTrigger == Trigger.Parent))
                    {
                        // Find all the nodes that this trigger is dependent on
                        HashSet <Node> Dependencies = new HashSet <Node>();
                        foreach (Node Node in Groups.SelectMany(x => x.Nodes))
                        {
                            for (ManualTrigger ControllingTrigger = Node.ControllingTrigger; ControllingTrigger != null; ControllingTrigger = ControllingTrigger.Parent)
                            {
                                if (ControllingTrigger == Trigger)
                                {
                                    Dependencies.UnionWith(Node.OrderDependencies.Where(x => x.ControllingTrigger != Trigger && NodesToExecute.Contains(x)));
                                    break;
                                }
                            }
                        }

                        // Reduce that list to the smallest subset of direct dependencies
                        HashSet <Node> DirectDependencies = new HashSet <Node>(Dependencies);
                        foreach (Node Dependency in Dependencies)
                        {
                            DirectDependencies.ExceptWith(Dependency.OrderDependencies);
                        }

                        // Write out the object
                        JsonWriter.WriteObjectStart();
                        JsonWriter.WriteValue("Name", Trigger.Name);
                        JsonWriter.WriteValue("AllDependencies", String.Join(";", Groups.SelectMany(x => x.Nodes).Where(x => Dependencies.Contains(x)).Select(x => x.Name)));
                        JsonWriter.WriteValue("DirectDependencies", String.Join(";", Dependencies.Where(x => DirectDependencies.Contains(x)).Select(x => x.Name)));
                        JsonWriter.WriteValue("Notify", String.Join(";", Trigger.NotifyUsers));
                        JsonWriter.WriteObjectEnd();
                    }
                }
                JsonWriter.WriteArrayEnd();
                JsonWriter.WriteObjectEnd();
            }
        }
Beispiel #6
0
        /// <summary>
        /// Export the build graph to a Json file for parsing by Horde
        /// </summary>
        /// <param name="File">Output file to write</param>
        public void ExportForHorde(FileReference File)
        {
            DirectoryReference.CreateDirectory(File.Directory);
            using (JsonWriter JsonWriter = new JsonWriter(File.FullName))
            {
                JsonWriter.WriteObjectStart();
                JsonWriter.WriteArrayStart("Groups");
                foreach (Agent Agent in Agents)
                {
                    JsonWriter.WriteObjectStart();
                    JsonWriter.WriteArrayStart("Types");
                    foreach (string PossibleType in Agent.PossibleTypes)
                    {
                        JsonWriter.WriteValue(PossibleType);
                    }
                    JsonWriter.WriteArrayEnd();
                    JsonWriter.WriteArrayStart("Nodes");
                    foreach (Node Node in Agent.Nodes)
                    {
                        JsonWriter.WriteObjectStart();
                        JsonWriter.WriteValue("Name", Node.Name);
                        JsonWriter.WriteValue("RunEarly", Node.bRunEarly);
                        JsonWriter.WriteValue("Warnings", Node.bNotifyOnWarnings);

                        JsonWriter.WriteArrayStart("InputDependencies");
                        foreach (string InputDependency in Node.GetDirectInputDependencies().Select(x => x.Name))
                        {
                            JsonWriter.WriteValue(InputDependency);
                        }
                        JsonWriter.WriteArrayEnd();

                        JsonWriter.WriteArrayStart("OrderDependencies");
                        foreach (string OrderDependency in Node.GetDirectOrderDependencies().Select(x => x.Name))
                        {
                            JsonWriter.WriteValue(OrderDependency);
                        }
                        JsonWriter.WriteArrayEnd();

                        JsonWriter.WriteObjectEnd();
                    }
                    JsonWriter.WriteArrayEnd();
                    JsonWriter.WriteObjectEnd();
                }
                JsonWriter.WriteArrayEnd();

                JsonWriter.WriteArrayStart("Aggregates");
                foreach (Aggregate Aggregate in NameToAggregate.Values)
                {
                    JsonWriter.WriteObjectStart();
                    JsonWriter.WriteValue("Name", Aggregate.Name);
                    JsonWriter.WriteArrayStart("Nodes");
                    foreach (Node RequiredNode in Aggregate.RequiredNodes.OrderBy(x => x.Name))
                    {
                        JsonWriter.WriteValue(RequiredNode.Name);
                    }
                    JsonWriter.WriteArrayEnd();
                    JsonWriter.WriteObjectEnd();
                }
                JsonWriter.WriteArrayEnd();

                JsonWriter.WriteArrayStart("Labels");
                foreach (Label Label in Labels)
                {
                    JsonWriter.WriteObjectStart();
                    if (!String.IsNullOrEmpty(Label.DashboardName))
                    {
                        JsonWriter.WriteValue("Name", Label.DashboardName);
                    }
                    if (!String.IsNullOrEmpty(Label.DashboardCategory))
                    {
                        JsonWriter.WriteValue("Category", Label.DashboardCategory);
                    }
                    if (!String.IsNullOrEmpty(Label.UgsBadge))
                    {
                        JsonWriter.WriteValue("UgsBadge", Label.UgsBadge);
                    }
                    if (!String.IsNullOrEmpty(Label.UgsProject))
                    {
                        JsonWriter.WriteValue("UgsProject", Label.UgsProject);
                    }
                    if (Label.Change != LabelChange.Current)
                    {
                        JsonWriter.WriteValue("Change", Label.Change.ToString());
                    }

                    JsonWriter.WriteArrayStart("RequiredNodes");
                    foreach (Node RequiredNode in Label.RequiredNodes.OrderBy(x => x.Name))
                    {
                        JsonWriter.WriteValue(RequiredNode.Name);
                    }
                    JsonWriter.WriteArrayEnd();
                    JsonWriter.WriteArrayStart("IncludedNodes");
                    foreach (Node IncludedNode in Label.IncludedNodes.OrderBy(x => x.Name))
                    {
                        JsonWriter.WriteValue(IncludedNode.Name);
                    }
                    JsonWriter.WriteArrayEnd();
                    JsonWriter.WriteObjectEnd();
                }
                JsonWriter.WriteArrayEnd();

                JsonWriter.WriteArrayStart("Badges");
                foreach (Badge Badge in Badges)
                {
                    HashSet <Node> Dependencies = Badge.Nodes;
                    if (Dependencies.Count > 0)
                    {
                        // Reduce that list to the smallest subset of direct dependencies
                        HashSet <Node> DirectDependencies = new HashSet <Node>(Dependencies);
                        foreach (Node Dependency in Dependencies)
                        {
                            DirectDependencies.ExceptWith(Dependency.OrderDependencies);
                        }

                        JsonWriter.WriteObjectStart();
                        JsonWriter.WriteValue("Name", Badge.Name);
                        if (!String.IsNullOrEmpty(Badge.Project))
                        {
                            JsonWriter.WriteValue("Project", Badge.Project);
                        }
                        if (Badge.Change != 0)
                        {
                            JsonWriter.WriteValue("Change", Badge.Change);
                        }
                        JsonWriter.WriteValue("Dependencies", String.Join(";", DirectDependencies.Select(x => x.Name)));
                        JsonWriter.WriteObjectEnd();
                    }
                }
                JsonWriter.WriteArrayEnd();

                JsonWriter.WriteObjectEnd();
            }
        }