if br_streams: buildrequires.append(f"{br}-{br_streams[-1]}") else: buildrequires.append(f"{br}") g = AGraph(directed=True, name="G", strict=False, label="Build Order Graph") ranks = {} refs = {} for key, value in yml['data']['components']['rpms'].items(): # Add each buildorder rank as a distinct sub-graph rank = value['buildorder'] if rank not in ranks: ranks[rank] = [key] else: ranks[rank].append(key) subg = g.get_subgraph(f"{rank}") if subg is None: subg = g.add_subgraph(name=f"{rank}", label=f"Build Order {rank}", rank="same") subg.add_node(f"{rank}") # Extract git ref from which to build if key not in refs: refs[key] = value['ref'] # Parse the dependency relationships reqs = [] breqs = [] api = False for r in value['rationale'].replace('\n', ' ').split('.'): r = r.strip() if not r:
class GraphGeneratingReporter(BaseReporter): def __init__(self): self.evolution = [] # List[str] self._evaluating = None # Dict[Candidate, Set[Requirement]] self._dependencies = defaultdict(set) # Dict[Candidate.name, Counter[Requirement]] self._active_requirements = defaultdict(Counter) self._node_names = {} self._counter = count() self.graph = AGraph( directed=True, rankdir="LR", labelloc="top", labeljust="center", nodesep="0", concentrate="true", ) self.graph.add_node("root", label=":root:", shape="Mdiamond") self._node_names[self._key(None)] = "root" del self.graph.node_attr["label"] self.graph.edge_attr.update({ "arrowhead": "empty", "style": "dashed", "color": "#808080" }) # # Internal Graph-handling API # def _prepare_node(self, obj): cls = obj.__class__.__name__ n = next(self._counter) node_name = f"{cls}_{n}" self._node_names[self._key(obj)] = node_name return node_name def _key(self, obj): if obj is None: return None return ( obj.__class__.__name__, repr(obj), ) def _get_subgraph(self, name, *, must_exist_already=True): name = canonicalize_name(name) c_name = f"cluster_{name}" subgraph = self.graph.get_subgraph(c_name) if subgraph is None: if must_exist_already: existing = [s.name for s in self.graph.subgraphs_iter()] raise RuntimeError( f"Graph for {name} not found. Existing: {existing}") else: subgraph = self.graph.add_subgraph(name=c_name, label=name) return subgraph def _add_candidate(self, candidate): if candidate is None: return if self._key(candidate) in self._node_names: return node_name = self._prepare_node(candidate) # A candidate is only seen after a requirement with the same name. subgraph = self._get_subgraph(candidate.name, must_exist_already=True) subgraph.add_node(node_name, label=candidate.version, shape="box") def _add_requirement(self, req): if self._key(req) in self._node_names: return name = self._prepare_node(req) subgraph = self._get_subgraph(req.name, must_exist_already=False) subgraph.add_node(name, label=str(req.specifier) or "*", shape="cds") def _ensure_edge(self, from_, *, to, **attrs): from_node = self._node_names[self._key(from_)] to_node = self._node_names[self._key(to)] try: existing = self.graph.get_edge(from_node, to_node) except KeyError: attrs.update(headport="w", tailport="e") self.graph.add_edge(from_node, to_node, **attrs) else: existing.attr.update(attrs) def _get_node_for(self, obj): node_name = self._node_names[self._key(obj)] node = self.graph.get_node(node_name) assert node is not None return node_name, node def _track_evaluating(self, candidate): if self._evaluating != candidate: if self._evaluating is not None: self.backtracking(self._evaluating, internal=True) self.evolution.append(self.graph.to_string()) self._evaluating = candidate # # Public reporter API # def starting(self): print("starting(self)") def starting_round(self, index): print(f"starting_round(self, {index})") # self.graph.graph_attr["label"] = f"Round {index}" self.evolution.append(self.graph.to_string()) def ending_round(self, index, state): print(f"ending_round(self, {index}, state)") def ending(self, state): print("ending(self, state)") def adding_requirement(self, req, parent): print(f"adding_requirement(self, {req!r}, {parent!r})") self._track_evaluating(parent) self._add_candidate(parent) self._add_requirement(req) self._ensure_edge(parent, to=req) self._active_requirements[canonicalize_name(req.name)][req] += 1 self._dependencies[parent].add(req) if parent is None: return # We're seeing the parent candidate (which is being "evaluated"), so # color all "active" requirements pointing to the it. # TODO: How does this interact with revisited candidates? for parent_req in self._active_requirements[canonicalize_name( parent.name)]: self._ensure_edge(parent_req, to=parent, color="#80CC80") def backtracking(self, candidate, internal=False): print(f"backtracking(self, {candidate!r}, internal={internal})") self._track_evaluating(candidate) self._evaluating = None # Update the graph! node_name, node = self._get_node_for(candidate) node.attr.update(shape="signature", color="red") for edge in self.graph.out_edges_iter([node_name]): edge.attr.update(style="dotted", arrowhead="vee", color="#FF9999") _, to = edge to.attr.update(color="black") for edge in self.graph.in_edges_iter([node_name]): edge.attr.update(style="dotted", color="#808080") # Trim "active" requirements to remove anything not relevant now. for requirement in self._dependencies[candidate]: active = self._active_requirements[canonicalize_name( requirement.name)] active[requirement] -= 1 if not active[requirement]: del active[requirement] def pinning(self, candidate): print(f"pinning(self, {candidate!r})") assert self._evaluating == candidate or self._evaluating is None self._evaluating = None self._add_candidate(candidate) # Update the graph! node_name, node = self._get_node_for(candidate) node.attr.update(color="#80CC80") # Requirement -> Candidate edges, from this candidate. for req in self._active_requirements[canonicalize_name( candidate.name)]: self._ensure_edge(req, to=candidate, arrowhead="vee", color="#80CC80") # Candidate -> Requirement edges, from this candidate. for edge in self.graph.out_edges_iter([node_name]): edge.attr.update(style="solid", arrowhead="vee", color="#80CC80") _, to = edge to.attr.update(color="#80C080")
def _recursive_visualize_state(state: State, graph: pgv.AGraph, visited_list: typing.List[str], leaf_list: typing.Mapping[str, str], root: bool = False) -> str: # get name of state state_name = state._name label_name = state_name if root: label_name = f"[init]{label_name}" parent_name = graph.name # remove cluster if it is part of it if parent_name.startswith("cluster_"): parent_name = parent_name[8:] global_name = f"{parent_name}_{state_name}" valid_node_name = "" # [Base Case] ignore if we already visited it. if state_name in visited_list: return # Check if its a nest class or machine sub_graph = None if not hasattr(state, "_children") and not isinstance(state, Machine): # add it to the graph graph.add_node(global_name, label=label_name) valid_node_name = global_name else: sub_graph = graph.add_subgraph(name=f"cluster_{global_name}", label=label_name) if isinstance(state, Machine): state: Machine sub_graph.graph_attr["pencolor"] = "red" valid_node_name = _recursive_visualize_state(state._root, sub_graph, [], {}, True) else: if isinstance(state, SequentialState) or isinstance(state, SelectorState): sub_graph.graph_attr["pencolor"] = "green" if isinstance(state, SelectorState): sub_graph.graph_attr["pencolor"] = "yellow" prev_valid_name = "" for children in state._children: valid_node_name = _recursive_visualize_state(children, sub_graph, [], {}) if prev_valid_name != "": sub_graph.add_edge(prev_valid_name, valid_node_name) prev_valid_name = valid_node_name else: sub_graph.graph_attr["pencolor"] = "blue" for children in state._children: valid_node_name = _recursive_visualize_state(children, sub_graph, [], {}) # registered as visited visited_list.append(state_name) leaf_list[state_name] = valid_node_name # Create connection between each states. for transition in state._transitions: # get references to the next state nxt_state = transition[1] nxt_state_name = nxt_state._name # run the recursive visualization function if not visted: if nxt_state_name not in visited_list: nxt_valid_node_name = _recursive_visualize_state(nxt_state, graph, visited_list, leaf_list) else: # we get a valid node to connect the clusters. nxt_valid_node_name = leaf_list[nxt_state_name] # construct the name if its a cluster nxt_cluster_name = f"cluster_{parent_name}_{nxt_state_name}" # HACK: graphviz require nodes to be linked up and cannot use cluster. Their way is to just hide it if graph.get_subgraph(nxt_cluster_name) is None: if sub_graph is None: graph.add_edge(valid_node_name, nxt_valid_node_name) else: graph.add_edge(valid_node_name, nxt_valid_node_name, ltail=sub_graph.name) else: nxt_graph = graph.get_subgraph(nxt_cluster_name) if sub_graph is None: graph.add_edge(valid_node_name, nxt_valid_node_name, lhead=nxt_graph.name) else: graph.add_edge(valid_node_name, nxt_valid_node_name, ltail=sub_graph.name, lhead=nxt_cluster_name) return valid_node_name