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:
Beispiel #2
0
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")
Beispiel #3
0
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